diff --git a/website/src/components/ui/button.tsx b/website/src/components/ui/button.tsx new file mode 100644 index 00000000..30719f90 --- /dev/null +++ b/website/src/components/ui/button.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@site/src/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*="size-")]]:h-4 [&_svg:not([class*="size-")]]:w-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-[0_1px_2px_rgba(0,0,0,0.04)]', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground shadow-[0_0_0_1px_rgba(0,0,0,0.08)]', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-10 px-5 py-2', + sm: 'h-8 rounded-full px-3 text-xs', + lg: 'h-11 rounded-full px-6', + icon: 'h-10 w-10', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); +Button.displayName = 'Button'; + +export { Button, buttonVariants }; diff --git a/website/src/components/ui/card.tsx b/website/src/components/ui/card.tsx new file mode 100644 index 00000000..f8894a90 --- /dev/null +++ b/website/src/components/ui/card.tsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import { cn } from '@site/src/lib/utils'; + +const Card = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +Card.displayName = 'Card'; + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardHeader.displayName = 'CardHeader'; + +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +); +CardTitle.displayName = 'CardTitle'; + +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +); +CardDescription.displayName = 'CardDescription'; + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +); +CardContent.displayName = 'CardContent'; + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +CardFooter.displayName = 'CardFooter'; + +export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }; diff --git a/website/src/components/ui/dialog.tsx b/website/src/components/ui/dialog.tsx new file mode 100644 index 00000000..56202652 --- /dev/null +++ b/website/src/components/ui/dialog.tsx @@ -0,0 +1,107 @@ +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; +import { cn } from '@site/src/lib/utils'; + +const Dialog = DialogPrimitive.Root; +const DialogTrigger = DialogPrimitive.Trigger; +const DialogPortal = DialogPrimitive.Portal; +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = 'DialogHeader'; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = 'DialogFooter'; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/website/src/components/ui/input.tsx b/website/src/components/ui/input.tsx new file mode 100644 index 00000000..fe440b95 --- /dev/null +++ b/website/src/components/ui/input.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { cn } from '@site/src/lib/utils'; + +export interface InputProps extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); +Input.displayName = 'Input'; + +export { Input }; diff --git a/website/src/components/ui/tabs.tsx b/website/src/components/ui/tabs.tsx new file mode 100644 index 00000000..9e5144a1 --- /dev/null +++ b/website/src/components/ui/tabs.tsx @@ -0,0 +1,52 @@ +import * as React from 'react'; +import * as TabsPrimitive from '@radix-ui/react-tabs'; +import { cn } from '@site/src/lib/utils'; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/website/src/components/ui/toast.tsx b/website/src/components/ui/toast.tsx new file mode 100644 index 00000000..a0306085 --- /dev/null +++ b/website/src/components/ui/toast.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { cn } from '@site/src/lib/utils'; + +export interface ToastProps { + message: string; + visible: boolean; + onClose?: () => void; + className?: string; +} + +export function Toast({ message, visible, onClose, className }: ToastProps) { + React.useEffect(() => { + if (!visible || !onClose) return; + const timer = setTimeout(onClose, 2000); + return () => clearTimeout(timer); + }, [visible, onClose]); + + return ( +
+ + + + {message} +
+ ); +} + +export function useToast() { + const [visible, setVisible] = React.useState(false); + const [message, setMessage] = React.useState(''); + + const show = React.useCallback((msg: string) => { + setMessage(msg); + setVisible(true); + }, []); + + const hide = React.useCallback(() => { + setVisible(false); + }, []); + + return { show, hide, message, visible }; +}