@rfdtech/components
v1.15.0
Published
Shared React component library for Ghana School of Law (GSL) projects
Downloads
1,924
Readme
GSL Components
Shared React component library for Ghana School of Law (GSL) projects.
Requires React 18+ and a bundler that processes CSS (Vite, Webpack, etc.).
Changelog
See CHANGELOG.md for release history.
Styles
Component styles load automatically when you import from @rfdtech/components (the JS bundle includes import './index.css'). For reliable styling in production, add this once in your app entry (main.tsx or App.tsx):
import "@rfdtech/components/style.css";After upgrading the package, clear Vite's dependency cache if styles look stale: rm -rf node_modules/.vite.
Shared theming
Wrap your app in ThemeProvider for light, dark, and system themes. Design tokens live in src/styles/theme.css and apply to .gsl-theme, document.documentElement, and all components (including portaled modals and popovers).
import { ThemeProvider } from "@rfdtech/components";
import "@rfdtech/components/style.css";
<ThemeProvider defaultTheme="system">
<App />
</ThemeProvider>Use useTheme() to read or change the active theme at runtime. See the Theme docs page for token reference and controlled mode.
| Token | Light default | Use |
|-------|---------------|-----|
| --gsl-primary | #dc2626 | Buttons, focus rings, accents |
| --gsl-primary-light | #fef2f2 | Selected rows, hover fills |
| --gsl-bg | #ffffff | Surfaces |
| --gsl-text | #3c4043 | Body text |
| --gsl-text-secondary | #5f6368 | Labels, muted UI |
| --gsl-border | #dadce0 | Borders |
| --gsl-hover | #f1f3f4 | Row/cell hover |
| --gsl-error / --gsl-error-bg | #dc2626 / #fef2f2 | Errors |
| --gsl-success | #16a34a | Success states |
| --gsl-warning | #eab308 | Warnings |
You can still override tokens on any ancestor without ThemeProvider:
.my-app {
--gsl-primary: #1d4ed8;
--gsl-primary-light: #eff6ff;
}BulkImportModal also accepts legacy aliases (--gsl-bulk-import-primary, etc.) that map to the shared tokens.
Install
npm install @rfdtech/componentsRequires React 18+. npm 7+ auto-installs react and react-dom as peer dependencies. Radix UI and other runtime packages are included as dependencies of @rfdtech/components.
Hooks
Shared hooks for URL-driven overlay state and router integration. See the Hooks docs page for full API reference.
import {
Dialog,
DialogContent,
useDialogSearchParam,
useModalSearchParam,
} from "@rfdtech/components";
const { open, data, onOpenChange, openWith } = useDialogSearchParam<{
userId: string;
}>("edit-profile");
openWith({ userId: "42" });
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>Edit user {data?.userId}</DialogContent>
</Dialog>Exports: useSearchParamOverlay, useDialogSearchParam, useModalSearchParam, createSearchParamAdapter, createBrowserSearchParamAdapter, readOverlayData, writeOverlayData, clearOverlayData, getDataPrefix. Types: SearchParamOverlayState, SearchParamOverlayData, SearchParamAdapter, UseSearchParamOverlayOptions, UseSearchParamOverlayReturn.
AppHeader
Compound header bar with AppHeader, AppHeaderSearch, AppHeaderActions, AppHeaderNotifications, and AppHeaderProfile. Nest search on the left and group switcher, notifications, and profile inside AppHeaderActions on the right.
See the AppHeader docs page for props and exported types.
import {
AppHeader,
AppHeaderActions,
AppHeaderSearch,
AppHeaderNotifications,
AppHeaderProfile,
AppSwitcher,
} from "@rfdtech/components";
<AppHeader>
<AppHeaderSearch onSearch={setQuery} data={results} />
<AppHeaderActions>
<AppSwitcher apps={apps} />
<AppHeaderNotifications loading={loading}>
{notifications.map((n) => (
<div key={n.id} className="gsl-notif-popover__item">
<div className="gsl-notif-popover__body-text">{n.text}</div>
<div className="gsl-notif-popover__body-time">{n.time}</div>
</div>
))}
</AppHeaderNotifications>
<AppHeaderProfile user={{ name: "Kwame", role: "Admin", initials: "KA" }} variant="basic">
<button className="gsl-profile-popover__action">Settings</button>
</AppHeaderProfile>
</AppHeaderActions>
</AppHeader>Props: AppHeader — className, children. AppHeaderActions — className, children. Exported types: AppHeaderProps, AppHeaderActionsProps, AppHeaderSearchProps, AppHeaderSearchDataGroup, AppHeaderSearchItem, AppHeaderNotificationsProps, AppHeaderProfileProps, AppUser.
AppLayout
Application layout container that auto-positions AppHeader, AppSidebar, and AppBody by component type. Sidebar on the left, header sticky at the top, main content filling the rest. See the AppLayout docs page for props and exported types.
import { AppLayout, AppSidebar, AppBody } from "@rfdtech/components";
<AppLayout>
<AppHeader>
<AppHeaderActions>
<AppSwitcher apps={apps} />
</AppHeaderActions>
</AppHeader>
<AppSidebar>{/* sidebar */}</AppSidebar>
<AppBody>{/* page */}</AppBody>
</AppLayout>Props: AppLayout — children, className. AppSidebar — children, className. AppBody — children, className. Exported types: AppLayoutProps, AppSidebarProps, AppBodyProps.
AppSwitcher
Google Apps–style 9-dot launcher for switching between GSL systems. Drop it into your header to let users jump between products. Pass apps directly from your own data layer; use loading while data is being fetched.
See the AppSwitcher docs page for props and exported types.
import { AppSwitcher, type AppItem } from "@rfdtech/components";
function Header({ apps, loading }: { apps: AppItem[]; loading: boolean }) {
return (
<AppSwitcher
apps={apps}
loading={loading}
title="System directory"
onAppSelect={(app) => console.log(app.name)}
/>
);
}Props: apps, loading, loadingLabel, columns, open, onOpenChange, onAppSelect, triggerLabel, trigger, title, footer, placement, closeOnSelect, className, style. Exported types: AppSwitcherProps, AppItem, UseAppSwitcherOptions, UseAppSwitcherReturn.
Also exported: AppSwitcherItem, GridIcon, SystemAppIcon, useAppSwitcher.
Badge
Compact inline label for status, counts, and metadata with semantic color variants. See the Badge docs page for props and exported types.
import { Badge } from "@rfdtech/components";
<Badge variant="success">Active</Badge>
<Badge variant="warning">Pending</Badge>
<Badge variant="error">Failed</Badge>
<Badge variant="outline" size="md">
Draft
</Badge>Props: variant, size, classNames, className, and standard span attributes. Exported types: BadgeProps, BadgeClassNames, BadgeVariant, BadgeSize.
Breadcrumb
Compound breadcrumb primitives for hierarchical page trails. See the Breadcrumb docs page for props and exported types.
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@rfdtech/components";
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Profile</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>Exported parts: Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, BreadcrumbEllipsis. Exported types: BreadcrumbProps, BreadcrumbLinkProps, BreadcrumbPageProps, and related *ClassNames interfaces.
BulkImportModal
Four-step modal wizard for importing spreadsheet data (.xlsx, .xls, .csv). Parsing and validation run entirely in the browser.
Usage
import { BulkImportModal, useModalSearchParam } from "@rfdtech/components";
const fields = [
{ key: "email", label: "Email", required: true },
{ key: "full_name", label: "Full name", required: true },
{ key: "student_id", label: "Student ID" },
];
const { open, onOpenChange, openWith } = useModalSearchParam("bulk-import");
<button type="button" onClick={() => openWith()}>Import students</button>
<BulkImportModal
open={open}
onOpenChange={onOpenChange}
title="Import students"
fields={fields}
onComplete={(result) => {
console.log(result.rows, result.errors);
onOpenChange(false);
}}
/>Steps
- Upload Document — preview expected columns, then upload
.xlsx,.xls, or.csv - Select header row — choose the header row with radio buttons
- Match Columns — map each uploaded column to a target field (
Your table→Will become) - Validate data — review rows in a table, discard invalid rows, then Confirm
The first worksheet is used for multi-sheet workbooks. Discarded rows are excluded from onComplete.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| open | boolean | required | Controlled open state |
| onOpenChange | (open: boolean) => void | required | Open state callback |
| fields | BulkImportField[] | required | Target field schema for column matching |
| onComplete | (result: BulkImportResult) => void | required | Called with mapped rows on import |
| title | string | "Bulk import" | Modal title |
| maxFileSizeBytes | number | 5242880 | Maximum upload size (5 MB) |
| allowImportWithWarnings | boolean | false | Allow import when only warnings exist |
| className | string | — | Root CSS class |
Field schema
Each item in fields defines how a column is matched and validated:
type BulkImportFieldType =
| "string"
| "email"
| "number"
| "integer"
| "date"
| "boolean"
| "url"
| "phone";
interface BulkImportField {
key: string;
label: string;
required?: boolean;
type?: BulkImportFieldType;
pattern?: string | RegExp;
patternMessage?: string;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
options?: string[];
optionsIgnoreCase?: boolean;
optionsMessage?: string;
description?: string;
example?: string;
trim?: boolean;
validate?: (value: string) => string | null;
}Example:
const fields = [
{
key: "email",
label: "Email",
required: true,
type: "email",
description: "Student email address",
example: "[email protected]",
},
{
key: "student_id",
label: "Student ID",
pattern: "^STU-\\d{4}$",
patternMessage: "Use format STU-1234",
example: "STU-0042",
},
{
key: "status",
label: "Status",
options: ["active", "inactive"],
},
];Schema rules run before any custom validate function. Step 1 uses example when set; otherwise it derives preview values from options, type, min, and patternMessage. Step 3 maps each uploaded column to a target field and supports excluding columns with ×.
Types
interface BulkImportResult {
rows: Record<string, string>[];
errors: BulkImportValidationError[];
warnings: BulkImportValidationError[];
}Also exported: useBulkImportFlow, step components, and utilities (parseSpreadsheetFile, mapRowsToRecords, validateMappedRows, validateFieldValue, getFieldExampleValue, getFieldHint, autoMatchSourceColumns).
Theming
The modal uses a near full-viewport layout with a compulsory gutter on all sides (24px desktop, 16px mobile), enforced by overlay padding. Override shared tokens on .gsl-bulk-import via the className prop (see Shared theming):
.my-import {
--gsl-primary: #dc2626;
--gsl-primary-light: #fef2f2;
--gsl-success: #16a34a;
--gsl-error-bg: #fef2f2;
}Legacy aliases (--gsl-bulk-import-primary, --gsl-bulk-import-primary-light, etc.) still work and map to the shared tokens.
To change the gutter size, override --gsl-bulk-import-gutter on .gsl-bulk-import__overlay in your app CSS (do not override modal width/height — the dialog always fills the padded overlay area):
.gsl-bulk-import__overlay {
--gsl-bulk-import-gutter: 32px;
}Button
Shared button with primary, secondary, outline, and ghost variants, plus loading and disabled states. See the Button docs page for props and exported types.
import { Button } from "@rfdtech/components";
<Button variant="primary" onClick={() => save()}>
Save
</Button>
<Button loading loadingLabel="Saving">
Save
</Button>
<Button disabled>Unavailable</Button>
<Button
variant="outline"
classNames={{ root: "min-w-32", label: "font-semibold" }}
>
Custom
</Button>Props: variant, size, loading, loadingLabel, classNames, and standard button attributes. Exported types: ButtonProps, ButtonClassNames, ButtonVariant, ButtonSize.
Card
Surface card wrapper with optional header and design tokens for padding and background. Uses --gsl-surface-card for background and --gsl-card-padding for inner spacing. See the Card docs page for props and exported types.
import { Card } from "@rfdtech/components";
<Card header="Profile">
<p>Content goes here.</p>
</Card>Props: header, children, classNames, className, plus standard div attributes. Exported types: CardProps, CardClassNames.
Checkbox
Accessible checkbox with optional label and part-level classNames. See the Checkbox docs page for props and exported types.
import { Checkbox } from "@rfdtech/components";
<Checkbox
label="Accept terms and conditions"
checked={accepted}
onCheckedChange={setAccepted}
/>
<Checkbox
aria-label="Select row"
checked={selected}
onCheckedChange={setSelected}
/>Props: checked, defaultChecked, onCheckedChange, label, disabled, required, name, value, id, aria-label, classNames, className. Exported types: CheckboxProps, CheckboxClassNames.
CountrySelector
Country selector dropdown with flag emoji, country name, search filtering, and keyboard navigation. Built on @radix-ui/react-popover. See the CountrySelector docs page for props and exported types.
import { CountrySelector } from "@rfdtech/components";
<CountrySelector onChange={(code) => console.log(code)} />
<CountrySelector defaultValue="US" />Props: value, defaultValue, onChange, placeholder, invalid, disabled, classNames, className. Exported types: CountrySelectorProps, CountrySelectorClassNames.
DateRangeSelector
Date range picker with a single trigger showing the selected range, a calendar popover for two-click range selection, and auto-swap to keep start before end. See the DateRangeSelector docs page for props and exported types.
import { DateRangeSelector } from "@rfdtech/components";
<DateRangeSelector onChange={(range) => console.log(range)} />
<DateRangeSelector
defaultValue={{ start: new Date(2026, 5, 1), end: new Date(2026, 5, 18) }}
/>Props: value, defaultValue, onChange, placeholder, formatOptions, invalid, disabled, min, max, classNames, className. Exported types: DateRangeSelectorProps, DateRangeSelectorClassNames, DateRangeValue.
DateSelector
Date picker for choosing a single date from a calendar grid. Built on @radix-ui/react-popover. See the DateSelector docs page for props and exported types.
import { DateSelector } from "@rfdtech/components";
<DateSelector onChange={(date) => console.log(date)} />
<DateSelector
placeholder="Pick a date"
min={new Date(2025, 0, 1)}
max={new Date(2026, 11, 31)}
/>Props: value, defaultValue, onChange, placeholder, formatOptions, invalid, disabled, min, max, classNames, className. Exported types: DateSelectorProps, DateSelectorClassNames.
Command
Compound command menu primitives for searchable, keyboard-navigable action lists. Supports inline pickers and modal palettes via CommandDialog. See the Command docs page for props and exported types.
import {
Command,
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
useDialogSearchParam,
} from "@rfdtech/components";
<Command label="Field picker">
<CommandInput placeholder="Search fields..." />
<CommandList>
<CommandEmpty>No results.</CommandEmpty>
<CommandGroup heading="Fields">
<CommandItem value="email" onSelect={() => setField("email")}>
Email
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
const { open, onOpenChange, openWith } = useDialogSearchParam("command-menu");
<Button onClick={() => openWith()}>Open command menu</Button>
<CommandDialog open={open} onOpenChange={onOpenChange} shortcut label="Command menu">
<CommandInput placeholder="Type a command..." />
<CommandList>
<CommandItem onSelect={() => go("/dashboard")}>Dashboard</CommandItem>
<CommandItem onSelect={signOut}>
Sign out
<CommandShortcut><span>⌘</span><span>Q</span></CommandShortcut>
</CommandItem>
</CommandList>
</CommandDialog>Exports: Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandLoading, CommandShortcut, useCommandShortcut. Exported types: CommandProps, CommandDialogProps, CommandItemProps, CommandShortcutProps, UseCommandShortcutOptions, and related *ClassNames types.
RadioGroup
Single-choice radio group with optional labels and descriptions on each Radio item. Use variant="card" for bordered choice cards. See the RadioGroup docs page for props and exported types.
import { Radio, RadioGroup } from "@rfdtech/components";
<RadioGroup variant="card" value={plan} onValueChange={setPlan}>
<Radio
value="starter"
label="Starter"
description="For individuals getting started."
/>
<Radio
value="team"
label="Team"
description="Collaborate with up to 10 members."
/>
</RadioGroup>Props: RadioGroup — value, defaultValue, onValueChange, name, disabled, required, orientation, variant, classNames, className, children. Radio — value, label, description, disabled, id, aria-label, classNames, className. Exported types: RadioGroupProps, RadioProps, RadioGroupClassNames, RadioClassNames, RadioGroupVariant.
Dropdown
Select-style dropdown for choosing one option from a list. See the Dropdown docs page for props and exported types.
import { Dropdown } from "@rfdtech/components";
<Dropdown
aria-label="Field"
value={value}
onValueChange={setValue}
options={[
{ value: "email", label: "Email" },
{ value: "name", label: "Full name" },
]}
placeholder="Select..."
clearable
/>Props: value, onValueChange, options, placeholder, clearable, disabled, aria-label, classNames, className. Exported types: DropdownProps, DropdownOption, DropdownClassNames.
Form
Field, Input, and Textarea primitives for accessible form layouts. See the Form docs page for props and exported types.
import {
Field,
FieldControl,
FieldDescription,
FieldLabel,
Input,
Textarea,
} from "@rfdtech/components";
<Field>
<FieldLabel>Email</FieldLabel>
<FieldControl>
<Input type="email" placeholder="[email protected]" />
</FieldControl>
<FieldDescription>We will never share your email.</FieldDescription>
</Field>Exports: Field, FieldLabel, FieldDescription, FieldError, FieldControl, Input, Textarea. Types: FieldProps, FieldClassNames, InputProps, TextareaProps.
FormField
react-hook-form adapters with Zod validation support. See the FormField docs page for props and exported types.
npm install react-hook-form zod @hookform/resolversimport { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
Button,
Field,
FieldControl,
FieldError,
FieldLabel,
Form,
FormField,
Input,
} from "@rfdtech/components";
const formSchema = z.object({
email: z.string().min(1, "Email is required").email("Enter a valid email address"),
});
type FormValues = z.infer<typeof formSchema>;
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: { email: "" },
});
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="email"
render={({ field, fieldState }) => (
<Field invalid={!!fieldState.error}>
<FieldLabel>Email</FieldLabel>
<FieldControl>
<Input type="email" {...field} />
</FieldControl>
<FieldError>{fieldState.error?.message}</FieldError>
</Field>
)}
/>
<Button type="submit">Save</Button>
</form>
</Form>Exports: Form, FormField, useFormField. Types: FormProps, FormFieldProps, UseFormFieldReturn.
Dialog
Compound dialog primitives for modal overlays. See the Dialog docs page for props and exported types.
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogOverlay,
DialogPortal,
DialogTitle,
useDialogSearchParam,
} from "@rfdtech/components";
const { open, data, onOpenChange, openWith } = useDialogSearchParam<{
userId: string;
}>("edit-profile");
<Button variant="secondary" onClick={() => openWith({ userId: "42" })}>
Edit profile
</Button>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogPortal>
<DialogOverlay />
<DialogContent showCloseButton>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes here. Editing user {data?.userId}.
</DialogDescription>
</DialogContent>
</DialogPortal>
</Dialog>Props: Dialog — open, defaultOpen, onOpenChange. DialogContent — showCloseButton, classNames, className. Styled parts also support part-level classNames. Exported types: DialogOverlayProps, DialogContentProps, DialogTitleProps, DialogDescriptionProps, and related *ClassNames interfaces.
Draggable
Repositionable panel primitive with optional handle and bounded pointer dragging. See the Draggable docs page for props and exported types.
import { Draggable, DraggableHandle } from "@rfdtech/components";
<div style={{ position: "relative", height: 240 }}>
<Draggable defaultPosition={{ x: 24, y: 24 }} bounds="parent">
<DraggableHandle aria-label="Drag card" />
<div>Drag me</div>
</Draggable>
</div>Exports: Draggable, DraggableHandle, useDraggable, clampPosition. Types: DraggableProps, DraggableHandleProps, DraggablePosition, DraggableBounds, DraggableAxis, UseDraggableOptions, UseDraggableReturn.
MetricCard
Compact dashboard card for displaying a metric value, label, trend indicator, and optional icon or description. Variants (default, primary, success, warning, error) only affect the trend color — background and text stay neutral. Use the color prop for custom accent colors.
See the MetricCard docs page for props and exported types.
import { MetricCard } from "@rfdtech/components";
<MetricCard
label="Revenue"
value="$128.4k"
trend="up"
trendValue="+12.5%"
description="Total revenue this quarter"
/>Props: label, value, icon, description, trend, trendValue, variant, color, classNames, className, plus standard div attributes. Exported types: MetricCardProps, MetricCardClassNames, MetricCardVariant, MetricTrend.
Modal
Compound modal primitives for near full-viewport overlays with header, body, and footer slots. See the Modal docs page for props and exported types.
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalDescription,
ModalFooter,
ModalHeader,
ModalOverlay,
ModalPortal,
ModalTitle,
useModalSearchParam,
} from "@rfdtech/components";
const { open, onOpenChange, openWith } = useModalSearchParam("review-changes");
<Button variant="secondary" onClick={() => openWith()}>
Review changes
</Button>
<Modal open={open} onOpenChange={onOpenChange}>
<ModalPortal>
<ModalOverlay />
<ModalContent showCloseButton>
<ModalHeader>
<ModalTitle>Review changes</ModalTitle>
<ModalDescription>Confirm before publishing.</ModalDescription>
</ModalHeader>
<ModalBody>{children}</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={() => onOpenChange(false)}>Cancel</Button>
<Button onClick={() => onOpenChange(false)}>Publish</Button>
</ModalFooter>
</ModalContent>
</ModalPortal>
</Modal>Props: Modal — open, defaultOpen, onOpenChange. ModalContent — showCloseButton, classNames, className. Layout parts (ModalHeader, ModalBody, ModalFooter) support part-level classNames. Exported types: ModalOverlayProps, ModalContentProps, ModalHeaderProps, ModalTitleProps, ModalDescriptionProps, ModalBodyProps, ModalFooterProps, and related *ClassNames interfaces.
NetworkOperator
Network operator selector with operator image thumbnails (MTN, Vodafone, AirtelTigo, Glo), search filtering, and keyboard navigation. Built on @radix-ui/react-popover. See the NetworkOperator docs page for props and exported types.
import { NetworkOperator } from "@rfdtech/components";
<NetworkOperator onChange={(value) => console.log(value)} />
<NetworkOperator defaultValue="mtn" />Props: value, defaultValue, onChange, options, placeholder, invalid, disabled, classNames, className. Exported types: NetworkOperatorProps, NetworkOperatorOption, NetworkOperatorClassNames.
OtpInput
One-time password input with configurable digit length, paste support, keyboard navigation (arrow keys, backspace), auto-focus advancement, and onComplete callback. See the OtpInput docs page for props and exported types.
import { OtpInput } from "@rfdtech/components";
<OtpInput onChange={(val) => console.log(val)} />
<OtpInput length={4} onComplete={(val) => console.log("Done:", val)} />Props: length, value, onChange, onComplete, invalid, disabled, name, classNames, className. Exported types: OtpInputProps, OtpInputClassNames.
PhoneNumberInput
Phone number input with a country code selector (flag + dial code prefix) that auto-detects the country from the number prefix as the user types. Built on @radix-ui/react-popover. See the PhoneNumberInput docs page for props and exported types.
import { PhoneNumberInput } from "@rfdtech/components";
<PhoneNumberInput defaultCountry="GH" onChange={(val) => console.log(val)} />
<PhoneNumberInput value="+2332054321022" />Props: value, defaultValue, onChange, invalid, disabled, classNames, className. Exported types: PhoneNumberInputProps, PhoneNumberInputClassNames.
Popover
Compound popover primitives for floating panels. See the Popover docs page for props and exported types.
import {
Button,
Popover,
PopoverClose,
PopoverContent,
PopoverTrigger,
} from "@rfdtech/components";
<Popover>
<PopoverTrigger asChild>
<Button variant="secondary">Actions</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="end"
sideOffset={4}
className="gsl-popover--menu"
>
<div className="gsl-popover__menu" role="menu">
<PopoverClose asChild>
<button type="button" className="gsl-popover__menu-item" role="menuitem">
Edit
</button>
</PopoverClose>
<PopoverClose asChild>
<button
type="button"
className="gsl-popover__menu-item gsl-popover__menu-item--destructive"
role="menuitem"
>
Delete
</button>
</PopoverClose>
</div>
</PopoverContent>
</Popover>Exports: Popover, PopoverTrigger, PopoverContent, PopoverPortal, PopoverAnchor, PopoverClose. Exported types: PopoverContentProps, PopoverContentClassNames.
ProgressBar
Accessible progress indicator for task completion and loading states. See the ProgressBar docs page for props and exported types.
import { ProgressBar } from "@rfdtech/components";
<ProgressBar value={60} label="Upload progress" showValue />
<ProgressBar value={100} variant="success" />
<ProgressBar indeterminate label="Loading" size="md" />Props: value, max, variant, size, indeterminate, label, showValue, classNames, className, and standard div attributes. Exported types: ProgressBarProps, ProgressBarClassNames, ProgressBarVariant, ProgressBarSize.
Sidebar
Compound sidebar primitives for app shells and section navigation. Desktop uses a sticky card-style rail with optional collapse; mobile uses an offcanvas drawer with trigger and overlay. See the Sidebar docs page for props and exported types.
import {
Sidebar,
SidebarCollapse,
SidebarContent,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarItem,
SidebarLink,
SidebarNav,
SidebarOverlay,
SidebarProvider,
SidebarTrigger,
} from "@rfdtech/components";
<SidebarProvider>
<SidebarTrigger>Open menu</SidebarTrigger>
<SidebarOverlay />
<Sidebar>
<SidebarHeader>
<div className="gsl-sidebar__header-brand">
<span className="gsl-sidebar__header-title">GSL Admin</span>
</div>
<SidebarCollapse />
</SidebarHeader>
<SidebarContent>
<SidebarNav aria-label="Main">
<SidebarGroup>
<SidebarGroupLabel>General</SidebarGroupLabel>
<SidebarItem>
<SidebarLink href="/dashboard" icon={<DashboardIcon />} active={path === "/dashboard"}>
Dashboard
</SidebarLink>
</SidebarItem>
</SidebarGroup>
</SidebarNav>
</SidebarContent>
</Sidebar>
<main>{children}</main>
</SidebarProvider>Exports: SidebarProvider, useSidebar, Sidebar, SidebarBadge, SidebarCollapse, SidebarTrigger, SidebarOverlay, SidebarHeader, SidebarContent, SidebarFooter, SidebarNav, SidebarGroup, SidebarGroupLabel, SidebarItem, SidebarLink. Exported types: SidebarProviderProps, SidebarProps, SidebarLinkProps, SidebarBadgeProps, SidebarCollapseProps, and related *ClassNames types.
Sheet
Compound sheet primitives for edge-sliding panels. See the Sheet docs page for props and exported types.
import {
Button,
Sheet,
SheetBody,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetOverlay,
SheetPortal,
SheetTitle,
SheetTrigger,
} from "@rfdtech/components";
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button variant="secondary">Open filters</Button>
</SheetTrigger>
<SheetPortal>
<SheetOverlay />
<SheetContent side="right" showCloseButton>
<SheetHeader>
<SheetTitle>Filters</SheetTitle>
<SheetDescription>Refine the results below.</SheetDescription>
</SheetHeader>
<SheetBody>{children}</SheetBody>
<SheetFooter>
<Button variant="ghost" onClick={() => setOpen(false)}>Reset</Button>
<Button onClick={() => setOpen(false)}>Apply</Button>
</SheetFooter>
</SheetContent>
</SheetPortal>
</Sheet>Props: Sheet — open, defaultOpen, onOpenChange. SheetContent — side, showCloseButton, classNames, className. Layout parts support part-level classNames. Exported types: SheetSide, SheetOverlayProps, SheetContentProps, SheetHeaderProps, SheetTitleProps, SheetDescriptionProps, SheetBodyProps, SheetFooterProps, and related *ClassNames interfaces.
Sortable
Reorderable list primitive with optional drag handles and keyboard support. See the Sortable docs page for props and exported types.
import { useState } from "react";
import {
Sortable,
SortableHandle,
SortableItem,
SortableList,
} from "@rfdtech/components";
const [items, setItems] = useState(["alpha", "beta", "gamma"]);
<Sortable items={items} onReorder={setItems}>
<SortableList>
{items.map((id) => (
<SortableItem key={id} id={id}>
<SortableHandle aria-label={`Reorder ${id}`} />
<span>{id}</span>
</SortableItem>
))}
</SortableList>
</Sortable>Exports: Sortable, SortableList, SortableItem, SortableHandle, reorderItems. Types: SortableProps, SortableListProps, SortableItemProps, SortableHandleProps, SortableId, SortableStrategy, SortableClassNames.
Table
Compound table with URL-driven search, pagination, filter, sort, row selection, bulk actions, and loading skeletons. See the Table docs page for props and exported types.
import { useState } from "react";
import {
Table,
TableHeader,
TableSearch,
TableContent,
TableBulkActions,
TableFooter,
TablePagination,
} from "@rfdtech/components";
const [selected, setSelected] = useState<Set<string | number>>(new Set());
<Table paramPrefix="users">
<TableHeader>
<TableSearch placeholder="Search users..." />
</TableHeader>
<TableContent
selectable
selectedIds={selected}
onSelectionChange={setSelected}
columns={[
{ id: "name", header: "Name", accessorKey: "name", sortable: true },
{ id: "email", header: "Email", accessorKey: "email", sortable: true },
]}
data={users}
rowKey={(u) => u.id}
/>
<TableBulkActions
selectedIds={selected}
actions={[
{ id: "delete", label: "Delete", icon: <Trash2 size={14} />, onClick: (ids) => handleDelete(ids), destructive: true },
]}
/>
<TableFooter>
<TablePagination totalPages={5} totalItems={50} />
</TableFooter>
</Table>Props: paramPrefix, classNames, className. Exports: Table, TableHeader, TableSearch, TableFilter, TableContent, TableBulkActions, TableFooter, TablePagination. Types: TableProps, TableHeaderProps, TableContentProps, TableFooterProps, TableSearchProps, TableFilterProps, TableBulkActionsProps, TableBulkAction, TableBulkActionsClassNames, TableClassNames, PaginationControlsProps, TableColumn.
Tabs
Compound tabs primitives for switching between related panels. See the Tabs docs page for props and exported types.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rfdtech/components";
<Tabs defaultValue="account" variant="line">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings</TabsContent>
<TabsContent value="security">Security settings</TabsContent>
</Tabs>Exports: Tabs, TabsList, TabsTrigger, TabsContent. Props: Tabs — value, defaultValue, onValueChange, orientation, activationMode, variant, classNames, className, children. TabsTrigger — value, disabled, classNames, className. Exported types: TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps, TabsVariant, TabsClassNames, TabsListClassNames, TabsTriggerClassNames, TabsContentClassNames.
Toast
Transient notifications with an imperative useToast hook, powered by Sonner. See the Toast docs page for props and exported types.
import { CheckCircle2 } from "lucide-react";
import { ToastProvider, Toaster, useToast } from "@rfdtech/components";
<ToastProvider>
<App />
<Toaster />
</ToastProvider>
const { toast } = useToast();
toast({
title: "Profile saved",
description: "Your changes were applied.",
variant: "success",
icon: <CheckCircle2 size={18} strokeWidth={2} aria-hidden />,
});Exports: ToastProvider, Toaster, useToast. Types: ToastOptions, ToastVariant, ToastProviderProps, ToasterProps, UseToastReturn, ToastClassNames, ToastAction, ToastReturn.
UploadField
File upload with dashed-border dropzone, cloud upload icon, auto-generated file type/size support text, file cards with type icons, and a primary action button. See the UploadField docs page for props and exported types.
import { UploadField } from "@rfdtech/components";
<UploadField
accept=".csv"
maxSize={10 * 1024 * 1024}
onChange={(file) => console.log(file)}
/>
<UploadField accept="image/*" maxSize={5 * 1024 * 1024} />Props: accept, multiple, maxSize, value, onChange, invalid, disabled, name, classNames, className. Exported types: UploadFieldProps, UploadFieldClassNames.
Props: accept, multiple, maxSize, value, onChange, invalid, disabled, placeholder, classNames, className. Exported types: UploadFieldProps, UploadFieldClassNames.
Development
npm install
npm run demo # Start demo at http://localhost:5173 — interactive examples at / and MDX docs at /docs
npm run test # Run unit and component tests
npm run test:watch # Run tests in watch mode
npm run build # Build library to dist/
npm run typecheckThe demo reads credentials from environment variables:
VITE_API_BASE_URL=https://api.example.com VITE_ACCESS_TOKEN=<token> npm run demoLeave VITE_API_BASE_URL unset to use the built-in mock API at /v1/me/apps.
License
MIT
