117 lines
2.8 KiB
TypeScript
117 lines
2.8 KiB
TypeScript
import React, { createContext, useContext, useState } from "react";
|
|
import { cn } from "~/lib/utils";
|
|
|
|
interface TabsContextType {
|
|
value: string;
|
|
onValueChange: (value: string) => void;
|
|
}
|
|
|
|
const TabsContext = createContext<TabsContextType | undefined>(undefined);
|
|
|
|
interface TabsProps {
|
|
defaultValue?: string;
|
|
value?: string;
|
|
onValueChange?: (value: string) => void;
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function Tabs({
|
|
defaultValue,
|
|
value,
|
|
onValueChange,
|
|
className,
|
|
children,
|
|
}: TabsProps) {
|
|
const [internalValue, setInternalValue] = useState(defaultValue || "");
|
|
|
|
const currentValue = value ?? internalValue;
|
|
const handleValueChange = onValueChange ?? setInternalValue;
|
|
|
|
return (
|
|
<TabsContext.Provider
|
|
value={{ value: currentValue, onValueChange: handleValueChange }}
|
|
>
|
|
<div className={cn("w-full", className)}>{children}</div>
|
|
</TabsContext.Provider>
|
|
);
|
|
}
|
|
|
|
interface TabsListProps {
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function TabsList({ className, children }: TabsListProps) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
|
className,
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface TabsTriggerProps {
|
|
value: string;
|
|
className?: string;
|
|
disabled?: boolean;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function TabsTrigger({
|
|
value,
|
|
className,
|
|
disabled,
|
|
children,
|
|
}: TabsTriggerProps) {
|
|
const context = useContext(TabsContext);
|
|
if (!context) throw new Error("TabsTrigger must be used within Tabs");
|
|
|
|
const isActive = context.value === value;
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
disabled={disabled}
|
|
onClick={() => !disabled && context.onValueChange(value)}
|
|
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",
|
|
isActive
|
|
? "bg-pr-gray text-foreground shadow-sm"
|
|
: "hover:bg-muted/50",
|
|
className,
|
|
)}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
interface TabsContentProps {
|
|
value: string;
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function TabsContent({ value, className, children }: TabsContentProps) {
|
|
const context = useContext(TabsContext);
|
|
if (!context) throw new Error("TabsContent must be used within Tabs");
|
|
|
|
if (context.value !== value) return null;
|
|
|
|
return (
|
|
<div
|
|
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,
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|