Skip to main content

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 custom size prop (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 Eye and EyeOff icons from lucide-react.
  • Error State: Accepts an error prop which sets aria-invalid and applies destructive styling.
  • Integration: Designed to work seamlessly with react-hook-form, as seen in frontend/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.

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 outline variant.
  • Inactive: Uses the ghost variant.

This structure allows for a flexible navigation bar that includes "Previous" and "Next" buttons, as well as page numbers and ellipses for truncated lists.