(null);
const [flipUpward, setFlipUpward] = React.useState(false);
React.useEffect(() => {
if (!ctx.open || !contentRef.current) return;
const rect = contentRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const spaceBelow = viewportHeight - rect.bottom;
// If dropdown extends below viewport (less than 20px space), flip upward
if (spaceBelow < 20) {
setFlipUpward(true);
} else {
setFlipUpward(false);
}
}, [ctx.open]);
if (!ctx.open) return null;
return (
{children}
);
}
export function SelectItem({
value,
children,
}: {
value: string;
children: React.ReactNode;
}) {
const ctx = React.useContext(SelectContext)!;
return (
{
e.preventDefault();
ctx.onChange(value);
}}
>
{children}
);
}
// ─── Badge ───────────────────────────────────────────────────────────────────
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",
secondary: "border-transparent bg-secondary text-secondary-foreground",
destructive: "border-transparent bg-destructive text-destructive-foreground",
outline: "text-foreground",
success: "border-transparent bg-green-600 text-white",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends React.HTMLAttributes,
VariantProps {}
export function Badge({ className, variant, ...props }: BadgeProps) {
return ;
}
// ─── Progress ────────────────────────────────────────────────────────────────
interface ProgressProps extends React.HTMLAttributes {
value?: number;
max?: number;
}
export function Progress({ value = 0, max = 100, className, ...props }: ProgressProps) {
const percentage = Math.min(100, Math.max(0, (value / max) * 100));
return (
);
}
// ─── RadioGroup ──────────────────────────────────────────────────────────────
interface RadioGroupContextValue {
value: string;
onValueChange: (value: string) => void;
}
const RadioGroupContext = React.createContext(null);
interface RadioGroupProps {
value: string;
onValueChange: (value: string) => void;
className?: string;
children: React.ReactNode;
}
export function RadioGroup({ value, onValueChange, className, children }: RadioGroupProps) {
return (
{children}
);
}
interface RadioGroupItemProps extends React.InputHTMLAttributes {
value: string;
}
export const RadioGroupItem = React.forwardRef(
({ value, className, ...props }, ref) => {
const ctx = React.useContext(RadioGroupContext);
if (!ctx) throw new Error("RadioGroupItem must be used within RadioGroup");
return (
ctx.onValueChange(value)}
{...props}
/>
);
}
);
RadioGroupItem.displayName = "RadioGroupItem";
// ─── Table ────────────────────────────────────────────────────────────────────
export const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
));
Table.displayName = "Table";
export const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
));
TableHeader.displayName = "TableHeader";
export const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
));
TableBody.displayName = "TableBody";
export const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
tr]:last:border-b-0", className)}
{...props}
/>
));
TableFooter.displayName = "TableFooter";
export const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes & { hover?: boolean }
>(({ className, hover: _hover, ...props }, ref) => (
));
TableRow.displayName = "TableRow";
export const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes
>(({ className, ...props }, ref) => (
|
));
TableHead.displayName = "TableHead";
export const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes
>(({ className, ...props }, ref) => (
|
));
TableCell.displayName = "TableCell";
export const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => (
));
TableCaption.displayName = "TableCaption";
// ─── Tabs ─────────────────────────────────────────────────────────────────────
const TabsContext = React.createContext<{
value: string;
onValueChange: (value: string) => void;
} | null>(null);
export function Tabs({ value, onValueChange, children }: { value: string; onValueChange: (value: string) => void; children: React.ReactNode }) {
return (
{children}
);
}
export function TabsList({ className, children }: { className?: string; children: React.ReactNode }) {
return (
{children}
);
}
export function TabsTrigger({ className, children, value }: { className?: string; children: React.ReactNode; value: string }) {
const ctx = React.useContext(TabsContext);
if (!ctx) throw new Error("TabsTrigger must be used within Tabs");
return (
);
}
export function TabsContent({ className, children, value }: { className?: string; children: React.ReactNode; value: string }) {
const ctx = React.useContext(TabsContext);
if (!ctx) throw new Error("TabsContent must be used within Tabs");
return (
{children}
);
}
// ─── Dialog ───────────────────────────────────────────────────────────────────
const DialogContext = React.createContext<{
open: boolean;
onOpenChange: (open: boolean) => void;
} | null>(null);
export function Dialog({ open, onOpenChange, children }: { open: boolean; onOpenChange: (open: boolean) => void; children: React.ReactNode }) {
return (
{children}
);
}
export function DialogTrigger({ children }: { children: React.ReactNode }) {
return <>{children}>;
}
export function DialogContent({ className, children }: { className?: string; children: React.ReactNode }) {
const ctx = React.useContext(DialogContext);
if (!ctx) throw new Error("DialogContent must be used within Dialog");
if (!ctx.open) return null;
return (
);
}
export function DialogHeader({ className, children }: { className?: string; children: React.ReactNode }) {
return (
{children}
);
}
export function DialogFooter({ className, children }: { className?: string; children: React.ReactNode }) {
return (
{children}
);
}
export function DialogTitle({ className, children }: { className?: string; children: React.ReactNode }) {
return (
{children}
);
}
export function DialogDescription({ className, children }: { className?: string; children: React.ReactNode }) {
return (
{children}
);
}
// ─── Alert ───────────────────────────────────────────────────────────────────
const alertVariants = cva(
"relative w-full rounded-lg border p-4",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface AlertProps extends React.HTMLAttributes, VariantProps {}
export function Alert({ className, variant, children, ...props }: AlertProps) {
return (
{children}
);
}
export function AlertTitle({ className, children, ...props }: React.HTMLAttributes) {
return (
{children}
);
}
export function AlertDescription({ className, children, ...props }: React.HTMLAttributes) {
return (
{children}
);
}
export { cn };