web/app/components/ui/tabs.tsxblame
View source
4dfd09b1"use client";
4dfd09b2
4dfd09b3import { createContext, useContext } from "react";
4dfd09b4import Link from "next/link";
4dfd09b5
4dfd09b6interface TabsContextValue {
4dfd09b7 value: string;
4dfd09b8 onValueChange?: (value: string) => void;
4dfd09b9}
4dfd09b10
4dfd09b11const TabsContext = createContext<TabsContextValue>({ value: "" });
4dfd09b12
4dfd09b13export interface TabsProps {
4dfd09b14 value: string;
4dfd09b15 onValueChange?: (value: string) => void;
4dfd09b16 children: React.ReactNode;
4dfd09b17 className?: string;
4dfd09b18}
4dfd09b19
4dfd09b20export function Tabs({ value, onValueChange, children, className = "" }: TabsProps) {
4dfd09b21 return (
4dfd09b22 <TabsContext.Provider value={{ value, onValueChange }}>
4dfd09b23 <div className={className}>{children}</div>
4dfd09b24 </TabsContext.Provider>
4dfd09b25 );
4dfd09b26}
4dfd09b27
4dfd09b28export interface TabListProps {
4dfd09b29 children: React.ReactNode;
4dfd09b30 className?: string;
4dfd09b31}
4dfd09b32
4dfd09b33export function TabList({ children, className = "" }: TabListProps) {
4dfd09b34 return (
4dfd09b35 <div
4dfd09b36 className={`flex gap-0 text-sm mb-6 ${className}`}
4dfd09b37 style={{ borderBottom: "1px solid var(--border)" }}
4dfd09b38 role="tablist"
4dfd09b39 >
4dfd09b40 {children}
4dfd09b41 </div>
4dfd09b42 );
4dfd09b43}
4dfd09b44
4dfd09b45export interface TabProps {
4dfd09b46 value: string;
4dfd09b47 children: React.ReactNode;
4dfd09b48 href?: string;
4dfd09b49}
4dfd09b50
4dfd09b51export function Tab({ value, children, href }: TabProps) {
4dfd09b52 const ctx = useContext(TabsContext);
4dfd09b53 const isActive = ctx.value === value;
4dfd09b54
4dfd09b55 const tabStyle: React.CSSProperties = {
4dfd09b56 color: isActive ? "var(--text-primary)" : "var(--text-muted)",
4dfd09b57 borderBottom: isActive
4dfd09b58 ? "2px solid var(--accent)"
4dfd09b59 : "2px solid transparent",
4dfd09b60 };
4dfd09b61
4dfd09b62 if (href) {
4dfd09b63 return (
4dfd09b64 <Link
4dfd09b65 href={href}
4dfd09b66 className="px-3 py-2 -mb-px transition-colors"
4dfd09b67 style={tabStyle}
4dfd09b68 role="tab"
4dfd09b69 aria-selected={isActive}
4dfd09b70 >
4dfd09b71 {children}
4dfd09b72 </Link>
4dfd09b73 );
4dfd09b74 }
4dfd09b75
4dfd09b76 return (
4dfd09b77 <button
4dfd09b78 onClick={() => ctx.onValueChange?.(value)}
4dfd09b79 className="px-3 py-2 -mb-px transition-colors"
4dfd09b80 style={{
4dfd09b81 ...tabStyle,
4dfd09b82 background: "none",
4dfd09b83 border: "none",
4dfd09b84 borderBottom: tabStyle.borderBottom,
4dfd09b85 cursor: "pointer",
4dfd09b86 font: "inherit",
4dfd09b87 fontSize: "inherit",
4dfd09b88 }}
4dfd09b89 role="tab"
4dfd09b90 aria-selected={isActive}
4dfd09b91 >
4dfd09b92 {children}
4dfd09b93 </button>
4dfd09b94 );
4dfd09b95}
4dfd09b96
4dfd09b97export interface TabPanelProps {
4dfd09b98 value: string;
4dfd09b99 children: React.ReactNode;
4dfd09b100}
4dfd09b101
4dfd09b102export function TabPanel({ value, children }: TabPanelProps) {
4dfd09b103 const ctx = useContext(TabsContext);
4dfd09b104 if (ctx.value !== value) return null;
4dfd09b105 return <div role="tabpanel">{children}</div>;
4dfd09b106}