diff --git a/app/components/ui/badge.tsx b/app/components/ui/badge.tsx new file mode 100644 index 0000000..8bf05ae --- /dev/null +++ b/app/components/ui/badge.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '~/lib/utils'; + +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 select-none uppercase tracking-wider', + { + variants: { + variant: { + default: + 'border-transparent bg-neutral-800 text-white hover:bg-neutral-700', + secondary: + 'border-transparent bg-neutral-900 text-neutral-400 hover:bg-neutral-850', + destructive: + 'border-transparent bg-red-500/10 text-red-500 border-red-500/20', + outline: 'border-neutral-800 text-neutral-400', + orange: + 'border-orange-500/20 bg-orange-500/5 text-orange-500 font-bold', + }, + }, + defaultVariants: { + variant: 'default', + }, + } +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/app/components/ui/button.tsx b/app/components/ui/button.tsx new file mode 100644 index 0000000..42c61fb --- /dev/null +++ b/app/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 '~/lib/utils'; + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl text-sm font-semibold transition-all duration-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-700 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 active:scale-[0.98] cursor-pointer', + { + variants: { + variant: { + default: 'bg-orange-500 text-black shadow-[0_0_24px_rgba(249,115,22,0.15)] hover:bg-orange-400 hover:shadow-[0_0_32px_rgba(249,115,22,0.3)]', + destructive: 'bg-red-500/10 text-red-500 border border-red-500/20 hover:bg-red-500 hover:text-white', + outline: 'border border-neutral-800 bg-[#121212] text-neutral-400 hover:bg-neutral-900 hover:text-white hover:border-orange-500/40', + secondary: 'bg-neutral-800 text-neutral-300 hover:bg-neutral-750 hover:text-white', + ghost: 'hover:bg-neutral-850 hover:text-white text-neutral-400', + link: 'text-orange-500 underline-offset-4 hover:underline', + }, + size: { + default: 'h-10 px-5 py-2.5', + sm: 'h-8 rounded-lg px-3 text-xs', + lg: 'h-12 rounded-xl px-8 text-base', + icon: 'h-9 w-9 rounded-lg', + }, + }, + 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/app/components/ui/card.tsx b/app/components/ui/card.tsx new file mode 100644 index 0000000..98fc819 --- /dev/null +++ b/app/components/ui/card.tsx @@ -0,0 +1,78 @@ +import * as React from 'react'; +import { cn } from '~/lib/utils'; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = 'Card'; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = 'CardHeader'; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

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

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

+)); +CardContent.displayName = 'CardContent'; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = 'CardFooter'; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/app/components/ui/confirm-dialog.tsx b/app/components/ui/confirm-dialog.tsx new file mode 100644 index 0000000..4b2c95a --- /dev/null +++ b/app/components/ui/confirm-dialog.tsx @@ -0,0 +1,84 @@ +import * as React from 'react'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from '~/components/ui/dialog'; +import { Button } from '~/components/ui/button'; +import { AlertTriangle, RefreshCw } from 'lucide-react'; + +interface ConfirmDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description: string; + confirmText?: string; + cancelText?: string; + variant?: 'destructive' | 'default'; + loading?: boolean; + onConfirm: () => void; +} + +export function ConfirmDialog({ + open, + onOpenChange, + title, + description, + confirmText = 'Confirm', + cancelText = 'Cancel', + variant = 'destructive', + loading = false, + onConfirm, +}: ConfirmDialogProps) { + return ( + + + +
+
+ +
+
+ + {title} + + + {description} + +
+
+
+ + + + + +
+
+ ); +} diff --git a/app/components/ui/dialog.tsx b/app/components/ui/dialog.tsx new file mode 100644 index 0000000..222b777 --- /dev/null +++ b/app/components/ui/dialog.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; +import { cn } from '~/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, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/app/components/ui/input.tsx b/app/components/ui/input.tsx new file mode 100644 index 0000000..e46302a --- /dev/null +++ b/app/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { cn } from '~/lib/utils'; + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); +Input.displayName = 'Input'; + +export { Input }; diff --git a/app/components/ui/label.tsx b/app/components/ui/label.tsx new file mode 100644 index 0000000..77e64e2 --- /dev/null +++ b/app/components/ui/label.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '~/lib/utils'; + +const labelVariants = cva( + 'text-xs font-bold text-neutral-400 uppercase tracking-wider select-none leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' +); + +const Label = React.forwardRef< + HTMLLabelElement, + React.LabelHTMLAttributes & VariantProps +>(({ className, ...props }, ref) => ( +