Replace all remaining bootstrap utility classes with tailwind classes

This commit is contained in:
Alejandro Celaya
2025-04-05 12:14:27 +02:00
parent d188d67c5a
commit 5e0db07ef3
9 changed files with 39 additions and 50 deletions

View File

@@ -45,26 +45,24 @@ export const Home = ({ servers }: HomeProps) => {
> >
Welcome! Welcome!
</h1> </h1>
<ServersListGroup servers={serversList}> {hasServers ? <ServersListGroup servers={serversList} /> : (
{!hasServers && ( <div className="tw:p-6 tw:text-center tw:flex tw:flex-col tw:gap-12 tw:text-xl">
<div className="tw:p-6 tw:text-center tw:flex tw:flex-col tw:gap-12"> <p>This application will help you manage your Shlink servers.</p>
<p>This application will help you manage your Shlink servers.</p> <p>
<p> <Button to="/server/create" size="lg" inline>
<Button to="/server/create" size="lg" inline> <FontAwesomeIcon icon={faPlus} /> Add a server
<FontAwesomeIcon icon={faPlus} /> Add a server </Button>
</Button> </p>
</p> <p>
<p> <ExternalLink href="https://shlink.io/documentation">
<ExternalLink href="https://shlink.io/documentation"> <small>
<small> <span className="tw:mr-2">Learn more about Shlink</span>
<span className="tw:mr-2">Learn more about Shlink</span> <FontAwesomeIcon icon={faExternalLinkAlt} />
<FontAwesomeIcon icon={faExternalLinkAlt} /> </small>
</small> </ExternalLink>
</ExternalLink> </p>
</p> </div>
</div> )}
)}
</ServersListGroup>
</div> </div>
</div> </div>
</Card> </Card>

View File

@@ -63,13 +63,17 @@ const ManageServers: FCWithDeps<ManageServersProps, ManageServersDeps> = ({ serv
<SimpleCard className="card"> <SimpleCard className="card">
<Table header={( <Table header={(
<Table.Row> <Table.Row>
{hasAutoConnect && <Table.Cell style={{ width: '50px' }}><span className="sr-only">Auto-connect</span></Table.Cell>} {hasAutoConnect && (
<Table.Cell className="tw:w-[35px]"><span className="tw:sr-only">Auto-connect</span></Table.Cell>
)}
<Table.Cell>Name</Table.Cell> <Table.Cell>Name</Table.Cell>
<Table.Cell>Base URL</Table.Cell> <Table.Cell>Base URL</Table.Cell>
<Table.Cell><span className="sr-only">Options</span></Table.Cell> <Table.Cell><span className="sr-only">Options</span></Table.Cell>
</Table.Row> </Table.Row>
)}> )}>
{!filteredServers.length && <Table.Row className="text-center"><Table.Cell colSpan={4}>No servers found.</Table.Cell></Table.Row>} {!filteredServers.length && (
<Table.Row className="tw:text-center"><Table.Cell colSpan={4}>No servers found.</Table.Cell></Table.Row>
)}
{filteredServers.map((server) => ( {filteredServers.map((server) => (
<ManageServersRow key={server.id} server={server} hasAutoConnect={hasAutoConnect} /> <ManageServersRow key={server.id} server={server} hasAutoConnect={hasAutoConnect} />
))} ))}

View File

@@ -27,7 +27,7 @@ const ManageServersRow: FCWithDeps<ManageServersRowProps, ManageServersRowDeps>
<Table.Cell columnName="Auto-connect"> <Table.Cell columnName="Auto-connect">
{server.autoConnect && ( {server.autoConnect && (
<> <>
<FontAwesomeIcon icon={checkIcon} className="text-primary" id="autoConnectIcon" /> <FontAwesomeIcon icon={checkIcon} className="tw:text-brand" id="autoConnectIcon" />
<UncontrolledTooltip target="autoConnectIcon" placement="right"> <UncontrolledTooltip target="autoConnectIcon" placement="right">
Auto-connect to this server Auto-connect to this server
</UncontrolledTooltip> </UncontrolledTooltip>

View File

@@ -17,7 +17,7 @@ export const ServersDropdown = ({ servers, selectedServer }: ServersDropdownProp
if (serversList.length === 0) { if (serversList.length === 0) {
return ( return (
<DropdownItem tag={Link} to="/server/create"> <DropdownItem tag={Link} to="/server/create">
<FontAwesomeIcon icon={plusIcon} /> <span className="ms-1">Add a server</span> <FontAwesomeIcon icon={plusIcon} /> <span className="tw:ml-1">Add a server</span>
</DropdownItem> </DropdownItem>
); );
} }
@@ -31,7 +31,7 @@ export const ServersDropdown = ({ servers, selectedServer }: ServersDropdownProp
))} ))}
<DropdownItem divider tag="hr" /> <DropdownItem divider tag="hr" />
<DropdownItem tag={Link} to="/manage-servers"> <DropdownItem tag={Link} to="/manage-servers">
<FontAwesomeIcon icon={serverIcon} /> <span className="ms-1">Manage servers</span> <FontAwesomeIcon icon={serverIcon} /> <span className="tw:ml-1">Manage servers</span>
</DropdownItem> </DropdownItem>
</> </>
); );
@@ -40,9 +40,9 @@ export const ServersDropdown = ({ servers, selectedServer }: ServersDropdownProp
return ( return (
<UncontrolledDropdown nav inNavbar> <UncontrolledDropdown nav inNavbar>
<DropdownToggle nav caret> <DropdownToggle nav caret>
<FontAwesomeIcon icon={serverIcon} /> <span className="ms-1">Servers</span> <FontAwesomeIcon icon={serverIcon} /> <span className="tw:ml-1">Servers</span>
</DropdownToggle> </DropdownToggle>
<DropdownMenu end style={{ right: 0 }}>{renderServers()}</DropdownMenu> <DropdownMenu end classNam="tw:right-0">{renderServers()}</DropdownMenu>
</UncontrolledDropdown> </UncontrolledDropdown>
); );
}; };

View File

@@ -1,14 +1,14 @@
import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons'; import { faChevronRight as chevronIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
import type { FC, PropsWithChildren } from 'react'; import type { FC } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import type { ServerWithId } from './data'; import type { ServerWithId } from './data';
type ServersListGroupProps = PropsWithChildren<{ type ServersListGroupProps = {
servers: ServerWithId[]; servers: ServerWithId[];
borderless?: boolean; borderless?: boolean;
}>; };
const ServerListItem = ({ id, name }: { id: string; name: string }) => ( const ServerListItem = ({ id, name }: { id: string; name: string }) => (
<Link <Link
@@ -25,9 +25,8 @@ const ServerListItem = ({ id, name }: { id: string; name: string }) => (
</Link> </Link>
); );
export const ServersListGroup: FC<ServersListGroupProps> = ({ servers, children, borderless }) => ( export const ServersListGroup: FC<ServersListGroupProps> = ({ servers, borderless }) => (
<> <>
{children && <div data-testid="title" className="fs-5 fw-normal lh-sm">{children}</div>}
{servers.length > 0 && ( {servers.length > 0 && (
<div <div
data-testid="list" data-testid="list"

View File

@@ -100,7 +100,7 @@ const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBt
<input <input
type="file" type="file"
accept=".csv" accept=".csv"
className="d-none" className="tw:hidden"
aria-hidden aria-hidden
ref={ref as any /* TODO Remove After updating to React 19 */} ref={ref as any /* TODO Remove After updating to React 19 */}
onChange={onFile} onChange={onFile}

View File

@@ -17,7 +17,7 @@ export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, child
const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey })); const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey }));
return ( return (
<form className="server-form" name="serverForm" onSubmit={handleSubmit}> <form name="serverForm" onSubmit={handleSubmit}>
<SimpleCard className="tw:mb-4" bodyClassName="tw:flex tw:flex-col tw:gap-y-3" title={title}> <SimpleCard className="tw:mb-4" bodyClassName="tw:flex tw:flex-col tw:gap-y-3" title={title}>
<LabelledInput label="Name" value={name} onChange={(e) => setName(e.target.value)} required /> <LabelledInput label="Name" value={name} onChange={(e) => setName(e.target.value)} required />
<LabelledInput label="URL" type="url" value={url} onChange={(e) => setUrl(e.target.value)} required /> <LabelledInput label="URL" type="url" value={url} onChange={(e) => setUrl(e.target.value)} required />

View File

@@ -10,30 +10,18 @@ describe('<ServersListGroup />', () => {
fromPartial({ name: 'foo', id: '123' }), fromPartial({ name: 'foo', id: '123' }),
fromPartial({ name: 'bar', id: '456' }), fromPartial({ name: 'bar', id: '456' }),
]; ];
const setUp = (params: { servers?: ServerWithId[]; withChildren?: boolean; borderless?: boolean } = {}) => { const setUp = (params: { servers?: ServerWithId[]; borderless?: boolean } = {}) => {
const { servers = [], withChildren = true, borderless } = params; const { servers = [], borderless } = params;
return render( return render(
<MemoryRouter> <MemoryRouter>
<ServersListGroup servers={servers} borderless={borderless}> <ServersListGroup servers={servers} borderless={borderless} />
{withChildren ? 'The list of servers' : undefined}
</ServersListGroup>
</MemoryRouter>, </MemoryRouter>,
); );
}; };
it('passes a11y checks', () => checkAccessibility(setUp())); it('passes a11y checks', () => checkAccessibility(setUp()));
it('renders title', () => {
setUp({});
expect(screen.getByTestId('title')).toHaveTextContent('The list of servers');
});
it('does not render title when children is not provided', () => {
setUp({ withChildren: false });
expect(screen.queryByTestId('title')).not.toBeInTheDocument();
});
it.each([ it.each([
[servers], [servers],
[[]], [[]],

View File

@@ -24,7 +24,7 @@ exports[`<ManageServersRow /> > renders auto-connect icon only if server is auto
> >
<svg <svg
aria-hidden="true" aria-hidden="true"
class="svg-inline--fa fa-check text-primary" class="svg-inline--fa fa-check tw:text-brand"
data-icon="check" data-icon="check"
data-prefix="fas" data-prefix="fas"
focusable="false" focusable="false"