Base UI Components
Foundation UI components, design system primitives, and reusable building blocks for Smart Shelf.
Base UI Components
Comprehensive collection of foundation UI components and design system primitives that form the building blocks of Smart Shelf's user interface.
Design System Foundation
Base Component Library (shadcn/ui)
Smart Shelf uses shadcn/ui as the foundation for its component library, providing:
- Accessible, unstyled components
- Customizable with Tailwind CSS
- TypeScript support
- Consistent API patterns
Button Component
Extensible button component with variants and sizes:
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
size?: 'default' | 'sm' | 'lg' | 'icon';
asChild?: boolean;
}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'default', size = 'default', asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
// Usage examples
<Button>Primary Action</Button>
<Button variant="outline">Secondary Action</Button>
<Button variant="destructive" size="sm">Delete</Button>
<Button variant="ghost" size="icon">
<Settings className="h-4 w-4" />
</Button>
Input Components
Basic Input
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
);
}
);
Textarea Component
interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
);
}
);
Select Component
interface SelectProps {
value?: string;
onValueChange?: (value: string) => void;
placeholder?: string;
children: React.ReactNode;
}
export function Select({ value, onValueChange, placeholder, children }: SelectProps) {
return (
<SelectPrimitive.Root value={value} onValueChange={onValueChange}>
<SelectPrimitive.Trigger className="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
<SelectPrimitive.Value placeholder={placeholder} />
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Content className="relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md">
<SelectPrimitive.Viewport className="p-1">
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
);
}
// Usage
<Select value={selectedValue} onValueChange={setSelectedValue} placeholder="Select option">
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</Select>
Form Components
Form Field with Label and Error Handling
interface FormFieldProps {
label: string;
error?: string;
required?: boolean;
children: React.ReactNode;
}
export function FormField({ label, error, required, children }: FormFieldProps) {
return (
<div className="space-y-2">
<Label className={required ? "after:content-['*'] after:text-red-500" : ""}>
{label}
</Label>
{children}
{error && <p className="text-sm text-red-500">{error}</p>}
</div>
);
}
// Enhanced Form Field with react-hook-form integration
interface FormFieldControlledProps {
name: string;
label: string;
required?: boolean;
description?: string;
children: (field: ControllerRenderProps) => React.ReactNode;
}
export function FormFieldControlled({
name,
label,
required,
description,
children
}: FormFieldControlledProps) {
return (
<FormField
name={name}
render={({ field, fieldState }) => (
<FormItem>
<FormLabel className={required ? "after:content-['*'] after:text-red-500" : ""}>
{label}
</FormLabel>
<FormControl>
{children(field)}
</FormControl>
{description && <FormDescription>{description}</FormDescription>}
<FormMessage>{fieldState.error?.message}</FormMessage>
</FormItem>
)}
/>
);
}
Checkbox and Radio Components
// Checkbox Component
interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {
label?: string;
}
export const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
CheckboxProps
>(({ className, label, ...props }, ref) => (
<div className="flex items-center space-x-2">
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator className="flex items-center justify-center text-current">
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{label && <Label htmlFor={props.id}>{label}</Label>}
</div>
));
// Radio Group Component
export function RadioGroup({ value, onValueChange, children }: {
value?: string;
onValueChange?: (value: string) => void;
children: React.ReactNode;
}) {
return (
<RadioGroupPrimitive.Root value={value} onValueChange={onValueChange}>
{children}
</RadioGroupPrimitive.Root>
);
}
export function RadioGroupItem({ value, label }: { value: string; label: string }) {
return (
<div className="flex items-center space-x-2">
<RadioGroupPrimitive.Item value={value} className="aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50">
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
<Label>{label}</Label>
</div>
);
}
Layout Components
Card Components
export const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
));
export const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
));
export const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
export const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
// Usage
<Card>
<CardHeader>
<CardTitle>Product Information</CardTitle>
<CardDescription>Manage your product details</CardDescription>
</CardHeader>
<CardContent>
{/* Card content */}
</CardContent>
</Card>
Separator and Divider
export const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
));
Navigation Components
Tabs Component
export function Tabs({ defaultValue, value, onValueChange, children }: {
defaultValue?: string;
value?: string;
onValueChange?: (value: string) => void;
children: React.ReactNode;
}) {
return (
<TabsPrimitive.Root
defaultValue={defaultValue}
value={value}
onValueChange={onValueChange}
>
{children}
</TabsPrimitive.Root>
);
}
export function TabsList({ children }: { children: React.ReactNode }) {
return (
<TabsPrimitive.List className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
{children}
</TabsPrimitive.List>
);
}
export function TabsTrigger({ value, children }: { value: string; children: React.ReactNode }) {
return (
<TabsPrimitive.Trigger
value={value}
className="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm"
>
{children}
</TabsPrimitive.Trigger>
);
}
export function TabsContent({ value, children }: { value: string; children: React.ReactNode }) {
return (
<TabsPrimitive.Content
value={value}
className="mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
>
{children}
</TabsPrimitive.Content>
);
}
Breadcrumb Component
interface BreadcrumbItem {
label: string;
href?: string;
current?: boolean;
}
interface BreadcrumbProps {
items: BreadcrumbItem[];
}
export function Breadcrumb({ items }: BreadcrumbProps) {
return (
<nav className="flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-4">
{items.map((item, index) => (
<li key={index} className="flex items-center">
{index > 0 && (
<ChevronRight className="h-4 w-4 text-muted-foreground mx-2" />
)}
{item.href && !item.current ? (
<Link
href={item.href}
className="text-sm font-medium text-muted-foreground hover:text-foreground"
>
{item.label}
</Link>
) : (
<span className={cn(
"text-sm font-medium",
item.current ? "text-foreground" : "text-muted-foreground"
)}>
{item.label}
</span>
)}
</li>
))}
</ol>
</nav>
);
}
Feedback Components
Alert Components
interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'destructive';
}
export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
({ className, variant = 'default', ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
)
);
export const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
export const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
// Usage
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>
Your session has expired. Please log in again.
</AlertDescription>
</Alert>
Badge Component
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'secondary' | 'destructive' | 'outline';
}
export function Badge({ className, variant = 'default', ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
Loading Components
// Spinner Component
export function Spinner({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-spin rounded-full border-2 border-gray-300 border-t-gray-600", className)}
{...props}
/>
);
}
// Loading Button
export function LoadingButton({
loading,
children,
disabled,
...props
}: ButtonProps & { loading?: boolean }) {
return (
<Button disabled={loading || disabled} {...props}>
{loading && <Spinner className="mr-2 h-4 w-4" />}
{children}
</Button>
);
}
// Skeleton Component
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
);
}
These base UI components provide a consistent, accessible, and customizable foundation for building the Smart Shelf application interface.