init: push source

This commit is contained in:
Mufeed VH
2025-06-19 19:24:01 +05:30
commit 8e76d016d4
136 changed files with 38177 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
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",
{
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",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@@ -0,0 +1,65 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* Button variants configuration using class-variance-authority
*/
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
/**
* Button component with multiple variants and sizes
*
* @example
* <Button variant="outline" size="lg" onClick={() => console.log('clicked')}>
* Click me
* </Button>
*/
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };

112
src/components/ui/card.tsx Normal file
View File

@@ -0,0 +1,112 @@
import * as React from "react";
import { cn } from "@/lib/utils";
/**
* Card component - A container with consistent styling and sections
*
* @example
* <Card>
* <CardHeader>
* <CardTitle>Card Title</CardTitle>
* <CardDescription>Card description</CardDescription>
* </CardHeader>
* <CardContent>
* Content goes here
* </CardContent>
* <CardFooter>
* Footer content
* </CardFooter>
* </Card>
*/
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border shadow-xs",
className
)}
style={{
borderColor: "var(--color-border)",
backgroundColor: "var(--color-card)",
color: "var(--color-card-foreground)"
}}
{...props}
/>
));
Card.displayName = "Card";
/**
* CardHeader component - Top section of a card
*/
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}
/>
));
CardHeader.displayName = "CardHeader";
/**
* CardTitle component - Main title within CardHeader
*/
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
/**
* CardDescription component - Descriptive text within CardHeader
*/
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
/**
* CardContent component - Main content area of a card
*/
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
/**
* CardFooter component - Bottom section of a card
*/
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View File

@@ -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<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -0,0 +1,39 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
/**
* Input component for text/number inputs
*
* @example
* <Input type="text" placeholder="Enter value..." />
*/
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-sm transition-colors",
"file:border-0 file:bg-transparent file:text-sm file:font-medium",
"focus-visible:outline-none focus-visible:ring-1",
"disabled:cursor-not-allowed disabled:opacity-50",
className
)}
style={{
borderColor: "var(--color-input)",
backgroundColor: "transparent",
color: "var(--color-foreground)"
}}
ref={ref}
{...props}
/>
);
}
);
Input.displayName = "Input";
export { Input };

View File

@@ -0,0 +1,28 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface LabelProps
extends React.LabelHTMLAttributes<HTMLLabelElement> {}
/**
* Label component for form fields
*
* @example
* <Label htmlFor="input-id">Field Label</Label>
*/
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ className, ...props }, ref) => (
<label
ref={ref}
className={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...props}
/>
)
);
Label.displayName = "Label";
export { Label };

View File

@@ -0,0 +1,72 @@
import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface PaginationProps {
/**
* Current page number (1-indexed)
*/
currentPage: number;
/**
* Total number of pages
*/
totalPages: number;
/**
* Callback when page changes
*/
onPageChange: (page: number) => void;
/**
* Optional className for styling
*/
className?: string;
}
/**
* Pagination component for navigating through paginated content
*
* @example
* <Pagination
* currentPage={1}
* totalPages={5}
* onPageChange={(page) => setCurrentPage(page)}
* />
*/
export const Pagination: React.FC<PaginationProps> = ({
currentPage,
totalPages,
onPageChange,
className,
}) => {
if (totalPages <= 1) {
return null;
}
return (
<div className={cn("flex items-center justify-center space-x-2", className)}>
<Button
variant="outline"
size="icon"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage <= 1}
className="h-8 w-8"
>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm text-muted-foreground">
Page {currentPage} of {totalPages}
</span>
<Button
variant="outline"
size="icon"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage >= totalPages}
className="h-8 w-8"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
);
};

View File

@@ -0,0 +1,134 @@
import * as React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { cn } from "@/lib/utils";
interface PopoverProps {
/**
* The trigger element
*/
trigger: React.ReactNode;
/**
* The content to display in the popover
*/
content: React.ReactNode;
/**
* Whether the popover is open
*/
open?: boolean;
/**
* Callback when the open state changes
*/
onOpenChange?: (open: boolean) => void;
/**
* Optional className for the content
*/
className?: string;
/**
* Alignment of the popover relative to the trigger
*/
align?: "start" | "center" | "end";
/**
* Side of the trigger to display the popover
*/
side?: "top" | "bottom";
}
/**
* Popover component for displaying floating content
*
* @example
* <Popover
* trigger={<Button>Click me</Button>}
* content={<div>Popover content</div>}
* side="top"
* />
*/
export const Popover: React.FC<PopoverProps> = ({
trigger,
content,
open: controlledOpen,
onOpenChange,
className,
align = "center",
side = "bottom",
}) => {
const [internalOpen, setInternalOpen] = React.useState(false);
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
const setOpen = onOpenChange || setInternalOpen;
const triggerRef = React.useRef<HTMLDivElement>(null);
const contentRef = React.useRef<HTMLDivElement>(null);
// Close on click outside
React.useEffect(() => {
if (!open) return;
const handleClickOutside = (event: MouseEvent) => {
if (
triggerRef.current &&
contentRef.current &&
!triggerRef.current.contains(event.target as Node) &&
!contentRef.current.contains(event.target as Node)
) {
setOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [open, setOpen]);
// Close on escape
React.useEffect(() => {
if (!open) return;
const handleEscape = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setOpen(false);
}
};
document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [open, setOpen]);
const alignClass = {
start: "left-0",
center: "left-1/2 -translate-x-1/2",
end: "right-0",
}[align];
const sideClass = side === "top" ? "bottom-full mb-2" : "top-full mt-2";
const animationY = side === "top" ? { initial: 10, exit: 10 } : { initial: -10, exit: -10 };
return (
<div className="relative inline-block">
<div
ref={triggerRef}
onClick={() => setOpen(!open)}
>
{trigger}
</div>
<AnimatePresence>
{open && (
<motion.div
ref={contentRef}
initial={{ opacity: 0, scale: 0.95, y: animationY.initial }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: animationY.exit }}
transition={{ duration: 0.15 }}
className={cn(
"absolute z-50 min-w-[200px] rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md",
sideClass,
alignClass,
className
)}
>
{content}
</motion.div>
)}
</AnimatePresence>
</div>
);
};

View File

@@ -0,0 +1,226 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils";
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
// Legacy interface for backward compatibility
export interface SelectOption {
value: string;
label: string;
}
export interface SelectProps {
/**
* The current value
*/
value: string;
/**
* Callback when value changes
*/
onValueChange: (value: string) => void;
/**
* Available options
*/
options: SelectOption[];
/**
* Placeholder text
*/
placeholder?: string;
/**
* Whether the select is disabled
*/
disabled?: boolean;
/**
* Additional CSS classes
*/
className?: string;
}
/**
* Simple select dropdown component
*
* @example
* <Select
* value={selected}
* onValueChange={setSelected}
* options={[
* { value: "option1", label: "Option 1" },
* { value: "option2", label: "Option 2" }
* ]}
* />
*/
const SimpleSelect: React.FC<SelectProps> = ({
value,
onValueChange,
options,
placeholder = "Select an option",
disabled = false,
className,
}) => {
return (
<Select value={value} onValueChange={onValueChange} disabled={disabled}>
<SelectTrigger className={className}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
};
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
SimpleSelect as SelectComponent,
};

View File

@@ -0,0 +1,65 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface SwitchProps
extends React.InputHTMLAttributes<HTMLInputElement> {
/**
* Whether the switch is checked
*/
checked?: boolean;
/**
* Callback when the switch state changes
*/
onCheckedChange?: (checked: boolean) => void;
}
/**
* Switch component for toggling boolean values
*
* @example
* <Switch checked={isEnabled} onCheckedChange={setIsEnabled} />
*/
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
({ className, checked, onCheckedChange, disabled, ...props }, ref) => {
return (
<button
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={() => onCheckedChange?.(!checked)}
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors",
"focus-visible:outline-none focus-visible:ring-2",
"disabled:cursor-not-allowed disabled:opacity-50",
className
)}
style={{
backgroundColor: checked ? "var(--color-primary)" : "var(--color-muted)"
}}
>
<span
className={cn(
"pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform",
checked ? "translate-x-4" : "translate-x-0"
)}
style={{
backgroundColor: "var(--color-background)"
}}
/>
<input
ref={ref}
type="checkbox"
checked={checked}
disabled={disabled}
className="sr-only"
{...props}
/>
</button>
);
}
);
Switch.displayName = "Switch";
export { Switch };

158
src/components/ui/tabs.tsx Normal file
View File

@@ -0,0 +1,158 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const TabsContext = React.createContext<{
value: string;
onValueChange: (value: string) => void;
}>({
value: "",
onValueChange: () => {},
});
export interface TabsProps {
/**
* The controlled value of the tab to activate
*/
value: string;
/**
* Event handler called when the value changes
*/
onValueChange: (value: string) => void;
/**
* The tabs and their content
*/
children: React.ReactNode;
/**
* Additional CSS classes
*/
className?: string;
}
/**
* Root tabs component
*
* @example
* <Tabs value={activeTab} onValueChange={setActiveTab}>
* <TabsList>
* <TabsTrigger value="general">General</TabsTrigger>
* </TabsList>
* <TabsContent value="general">Content</TabsContent>
* </Tabs>
*/
const Tabs: React.FC<TabsProps> = ({
value,
onValueChange,
children,
className,
}) => {
return (
<TabsContext.Provider value={{ value, onValueChange }}>
<div className={cn("w-full", className)}>{children}</div>
</TabsContext.Provider>
);
};
export interface TabsListProps {
children: React.ReactNode;
className?: string;
}
/**
* Container for tab triggers
*/
const TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"flex h-9 items-center justify-start rounded-lg p-1",
className
)}
style={{
backgroundColor: "var(--color-muted)",
color: "var(--color-muted-foreground)"
}}
{...props}
/>
)
);
TabsList.displayName = "TabsList";
export interface TabsTriggerProps {
value: string;
children: React.ReactNode;
className?: string;
disabled?: boolean;
}
/**
* Individual tab trigger button
*/
const TabsTrigger = React.forwardRef<
HTMLButtonElement,
TabsTriggerProps
>(({ className, value, disabled, ...props }, ref) => {
const { value: selectedValue, onValueChange } = React.useContext(TabsContext);
const isSelected = selectedValue === value;
return (
<button
ref={ref}
type="button"
role="tab"
aria-selected={isSelected}
disabled={disabled}
onClick={() => onValueChange(value)}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium transition-all",
"focus-visible:outline-none focus-visible:ring-2",
"disabled:pointer-events-none disabled:opacity-50",
className
)}
style={{
backgroundColor: isSelected ? "var(--color-background)" : "transparent",
color: isSelected ? "var(--color-foreground)" : "inherit",
boxShadow: isSelected ? "0 1px 2px rgba(0,0,0,0.1)" : "none"
}}
{...props}
/>
);
});
TabsTrigger.displayName = "TabsTrigger";
export interface TabsContentProps {
value: string;
children: React.ReactNode;
className?: string;
}
/**
* Tab content panel
*/
const TabsContent = React.forwardRef<
HTMLDivElement,
TabsContentProps
>(({ className, value, ...props }, ref) => {
const { value: selectedValue } = React.useContext(TabsContext);
const isSelected = selectedValue === value;
if (!isSelected) return null;
return (
<div
ref={ref}
role="tabpanel"
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
);
});
TabsContent.displayName = "TabsContent";
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@@ -0,0 +1,23 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex 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}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

111
src/components/ui/toast.tsx Normal file
View File

@@ -0,0 +1,111 @@
import * as React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X, CheckCircle, AlertCircle, Info } from "lucide-react";
import { cn } from "@/lib/utils";
export type ToastType = "success" | "error" | "info";
interface ToastProps {
/**
* The message to display
*/
message: string;
/**
* The type of toast
*/
type?: ToastType;
/**
* Duration in milliseconds before auto-dismiss
*/
duration?: number;
/**
* Callback when the toast is dismissed
*/
onDismiss?: () => void;
/**
* Optional className for styling
*/
className?: string;
}
/**
* Toast component for showing temporary notifications
*
* @example
* <Toast
* message="File saved successfully"
* type="success"
* duration={3000}
* onDismiss={() => setShowToast(false)}
* />
*/
export const Toast: React.FC<ToastProps> = ({
message,
type = "info",
duration = 3000,
onDismiss,
className,
}) => {
React.useEffect(() => {
if (duration && duration > 0) {
const timer = setTimeout(() => {
onDismiss?.();
}, duration);
return () => clearTimeout(timer);
}
}, [duration, onDismiss]);
const icons = {
success: <CheckCircle className="h-4 w-4" />,
error: <AlertCircle className="h-4 w-4" />,
info: <Info className="h-4 w-4" />,
};
const colors = {
success: "text-green-500",
error: "text-red-500",
info: "text-primary",
};
return (
<motion.div
initial={{ opacity: 0, y: 50, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.95 }}
transition={{ duration: 0.2 }}
className={cn(
"flex items-center space-x-3 rounded-lg border border-border bg-card px-4 py-3 shadow-lg",
className
)}
>
<span className={colors[type]}>{icons[type]}</span>
<span className="flex-1 text-sm">{message}</span>
{onDismiss && (
<button
onClick={onDismiss}
className="text-muted-foreground hover:text-foreground transition-colors"
>
<X className="h-4 w-4" />
</button>
)}
</motion.div>
);
};
// Toast container for positioning
interface ToastContainerProps {
children: React.ReactNode;
}
export const ToastContainer: React.FC<ToastContainerProps> = ({ children }) => {
return (
<div className="fixed bottom-0 left-0 right-0 z-50 flex justify-center p-4 pointer-events-none">
<div className="pointer-events-auto">
<AnimatePresence mode="wait">
{children}
</AnimatePresence>
</div>
</div>
);
};

View File

@@ -0,0 +1,29 @@
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }