Files
stripstream/src/components/ui/tabs.tsx

131 lines
3.7 KiB
TypeScript

"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
interface TabsProps extends React.HTMLAttributes<HTMLDivElement> {
defaultValue?: string;
value?: string;
onValueChange?: (value: string) => void;
}
interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {}
interface TabsTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
value: string;
}
interface TabsContentProps extends React.HTMLAttributes<HTMLDivElement> {
value: string;
}
const TabsContext = React.createContext<{
value: string;
onValueChange: (value: string) => void;
} | null>(null);
const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
({ className, defaultValue, value, onValueChange, children, ...props }, ref) => {
const [selectedValue, setSelectedValue] = React.useState(value || defaultValue || "");
const handleValueChange = React.useCallback(
(newValue: string) => {
if (value === undefined) {
setSelectedValue(newValue);
}
onValueChange?.(newValue);
},
[value, onValueChange]
);
const contextValue = React.useMemo(
() => ({
value: value ?? selectedValue,
onValueChange: handleValueChange,
}),
[value, selectedValue, handleValueChange]
);
return (
<TabsContext.Provider value={contextValue}>
<div ref={ref} className={cn("w-full", className)} {...props}>
{children}
</div>
</TabsContext.Provider>
);
}
);
Tabs.displayName = "Tabs";
const TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted/80 backdrop-blur-md p-1 text-muted-foreground",
className
)}
{...props}
/>
));
TabsList.displayName = "TabsList";
const TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(
({ className, value, ...props }, ref) => {
const context = React.useContext(TabsContext);
if (!context) {
throw new Error("TabsTrigger must be used within a Tabs component");
}
const { value: selectedValue, onValueChange } = context;
const isSelected = selectedValue === value;
return (
<button
ref={ref}
type="button"
role="tab"
aria-selected={isSelected}
className={cn(
"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",
isSelected && "bg-background/90 backdrop-blur-md text-foreground shadow-sm",
className
)}
onClick={() => onValueChange(value)}
{...props}
/>
);
}
);
TabsTrigger.displayName = "TabsTrigger";
const TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(
({ className, value, children, ...props }, ref) => {
const context = React.useContext(TabsContext);
if (!context) {
throw new Error("TabsContent must be used within a Tabs component");
}
const { value: selectedValue } = context;
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}
>
{children}
</div>
);
}
);
TabsContent.displayName = "TabsContent";
export { Tabs, TabsList, TabsTrigger, TabsContent };