tabs.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. "use client"
  2. import * as React from "react"
  3. import { cn } from "@/lib/utils"
  4. interface TabsContextValue {
  5. value: string
  6. onValueChange: (value: string) => void
  7. }
  8. const TabsContext = React.createContext<TabsContextValue | undefined>(undefined)
  9. interface TabsProps {
  10. defaultValue: string
  11. value?: string
  12. onValueChange?: (value: string) => void
  13. className?: string
  14. children: React.ReactNode
  15. }
  16. const Tabs = ({ defaultValue, value: controlledValue, onValueChange: controlledOnValueChange, className, children }: TabsProps) => {
  17. const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue)
  18. const isControlled = controlledValue !== undefined
  19. const value = isControlled ? controlledValue : uncontrolledValue
  20. const onValueChange = isControlled ? controlledOnValueChange : setUncontrolledValue
  21. return (
  22. <TabsContext.Provider value={{ value, onValueChange: onValueChange || (() => {}) }}>
  23. <div className={className}>{children}</div>
  24. </TabsContext.Provider>
  25. )
  26. }
  27. interface TabsListProps {
  28. className?: string
  29. children: React.ReactNode
  30. }
  31. const TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(
  32. ({ className, ...props }, ref) => (
  33. <div
  34. ref={ref}
  35. className={cn(
  36. "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
  37. className
  38. )}
  39. {...props}
  40. />
  41. )
  42. )
  43. TabsList.displayName = "TabsList"
  44. interface TabsTriggerProps {
  45. value: string
  46. className?: string
  47. children: React.ReactNode
  48. }
  49. const TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(
  50. ({ value, className, children, ...props }, ref) => {
  51. const context = React.useContext(TabsContext)
  52. if (!context) {
  53. throw new Error("TabsTrigger must be used within Tabs")
  54. }
  55. const isActive = context.value === value
  56. return (
  57. <button
  58. ref={ref}
  59. type="button"
  60. onClick={() => context.onValueChange(value)}
  61. className={cn(
  62. "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",
  63. isActive && "bg-background text-foreground shadow-sm",
  64. className
  65. )}
  66. {...props}
  67. >
  68. {children}
  69. </button>
  70. )
  71. }
  72. )
  73. TabsTrigger.displayName = "TabsTrigger"
  74. interface TabsContentProps {
  75. value: string
  76. className?: string
  77. children: React.ReactNode
  78. }
  79. const TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(
  80. ({ value, className, children, ...props }, ref) => {
  81. const context = React.useContext(TabsContext)
  82. if (!context) {
  83. throw new Error("TabsContent must be used within Tabs")
  84. }
  85. if (context.value !== value) {
  86. return null
  87. }
  88. return (
  89. <div
  90. ref={ref}
  91. className={cn(
  92. "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
  93. className
  94. )}
  95. {...props}
  96. >
  97. {children}
  98. </div>
  99. )
  100. }
  101. )
  102. TabsContent.displayName = "TabsContent"
  103. export { Tabs, TabsList, TabsTrigger, TabsContent }