Base UI Components
The base UI components in this project provide a consistent, accessible foundation for the user interface. Built on top of Radix UI primitives and styled with Tailwind CSS, these components use class-variance-authority (CVA) to manage visual states and variants.
Buttons and Actions
The project implements two primary button components: a standard Button and a specialized LoadingButton. Both utilize the buttonVariants configuration to ensure visual consistency across the application.
Standard Button
The Button component (defined in frontend/src/components/ui/button.tsx) is a polymorphic element that supports the asChild pattern via Radix UI's Slot. This allows the button to merge its props and styles onto a child element, such as a link from a routing library.
It supports several variants and sizes:
- Variants:
default,destructive,outline,secondary,ghost,link. - Sizes:
default,sm,lg,icon,icon-sm,icon-lg.
Loading Button
The LoadingButton (defined in frontend/src/components/ui/loading-button.tsx) extends the standard button logic by adding a loading state. When loading is true, the button displays a spinning Loader2 icon and is automatically disabled.
The ButtonProps interface, which governs this component, is defined as:
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
loading?: boolean
}
In practice, buttons are used for both form submissions and table actions. For example, in frontend/src/components/Common/DataTable.tsx, buttons are used for pagination controls:
<Button
variant="outline"
size="sm"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeft className="h-4 w-4" />
</Button>
Select Menus
The Select component suite (found in frontend/src/components/ui/select.tsx) provides an accessible dropdown interface. It is implemented as a collection of sub-components that work together through composition:
Select: The root provider.SelectTrigger: The button that opens the menu. It includes a customsizeprop (sm|default).SelectContent: The container for the dropdown items.SelectItem: Individual selectable options.SelectValue: Displays the currently selected value.
A common use case in this project is controlling the page size in data tables:
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => table.setPageSize(Number(value))}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[5, 10, 25, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
Password Input
The PasswordInput component (frontend/src/components/ui/password-input.tsx) is a specialized wrapper around a standard HTML input. It includes a built-in toggle to switch between password and text types, allowing users to reveal their input.
Key features include:
- Visibility Toggle: Uses
EyeandEyeOfficons fromlucide-react. - Error State: Accepts an
errorprop which setsaria-invalidand applies destructive styling. - Integration: Designed to work seamlessly with
react-hook-form, as seen infrontend/src/routes/login.tsx.
<PasswordInput
data-testid="password-input"
placeholder="Password"
error={form.formState.errors.password?.message}
{...field}
/>
Pagination Controls
The pagination system (frontend/src/components/ui/pagination.tsx) is built as a navigation list. It provides structural components like PaginationItem and PaginationLink.
Pagination Link
The PaginationLink is a critical part of the navigation UI. While it renders as an anchor (<a>) tag, it uses buttonVariants to mimic the appearance of a button. Its props are defined by PaginationLinkProps:
type PaginationLinkProps = {
isActive?: boolean
} & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<"a">
The component automatically adjusts its variant based on the isActive state:
- Active: Uses the
outlinevariant. - Inactive: Uses the
ghostvariant.
This structure allows for a flexible navigation bar that includes "Previous" and "Next" buttons, as well as page numbers and ellipses for truncated lists.