@srcroot/ui 0.0.55 → 0.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +151 -151
  2. package/dist/index.d.ts +0 -0
  3. package/dist/index.js +120 -93
  4. package/package.json +7 -2
  5. package/src/registry/analytics/google-analytics.tsx +36 -39
  6. package/src/registry/analytics/google-tag-manager.tsx +62 -65
  7. package/src/registry/analytics/meta-pixel.tsx +44 -47
  8. package/src/registry/analytics/microsoft-clarity.tsx +31 -34
  9. package/src/registry/analytics/tiktok-pixel.tsx +34 -37
  10. package/src/registry/lib/utils.ts +0 -0
  11. package/src/registry/themes/v3/blue.css +157 -157
  12. package/src/registry/themes/v3/glass.css +153 -153
  13. package/src/registry/themes/v3/gray.css +157 -157
  14. package/src/registry/themes/v3/green.css +157 -157
  15. package/src/registry/themes/v3/neutral.css +157 -157
  16. package/src/registry/themes/v3/orange.css +157 -157
  17. package/src/registry/themes/v3/rose.css +157 -157
  18. package/src/registry/themes/v3/slate.css +157 -157
  19. package/src/registry/themes/v3/stone.css +157 -157
  20. package/src/registry/themes/v3/violet.css +186 -186
  21. package/src/registry/themes/v3/zinc.css +157 -157
  22. package/src/registry/themes/v4/blue.css +184 -184
  23. package/src/registry/themes/v4/glass.css +180 -180
  24. package/src/registry/themes/v4/gray.css +184 -184
  25. package/src/registry/themes/v4/green.css +184 -184
  26. package/src/registry/themes/v4/neutral.css +184 -184
  27. package/src/registry/themes/v4/orange.css +184 -184
  28. package/src/registry/themes/v4/rose.css +184 -184
  29. package/src/registry/themes/v4/slate.css +184 -184
  30. package/src/registry/themes/v4/stone.css +184 -184
  31. package/src/registry/themes/v4/violet.css +184 -184
  32. package/src/registry/themes/v4/zinc.css +184 -184
  33. package/src/registry/ui/accordion.tsx +164 -165
  34. package/src/registry/ui/alert-dialog.tsx +213 -214
  35. package/src/registry/ui/alert.tsx +73 -76
  36. package/src/registry/ui/aspect-ratio.tsx +44 -47
  37. package/src/registry/ui/avatar.tsx +96 -97
  38. package/src/registry/ui/badge.tsx +52 -55
  39. package/src/registry/ui/breadcrumb.tsx +147 -150
  40. package/src/registry/ui/button-group.tsx +64 -67
  41. package/src/registry/ui/button.tsx +71 -72
  42. package/src/registry/ui/calendar.tsx +514 -515
  43. package/src/registry/ui/card.tsx +88 -91
  44. package/src/registry/ui/carousel.tsx +214 -214
  45. package/src/registry/ui/chart.tsx +373 -373
  46. package/src/registry/ui/chatbot.tsx +86 -13
  47. package/src/registry/ui/checkbox.tsx +93 -94
  48. package/src/registry/ui/collapsible.tsx +107 -108
  49. package/src/registry/ui/combobox.tsx +171 -171
  50. package/src/registry/ui/command.tsx +300 -300
  51. package/src/registry/ui/container.tsx +44 -47
  52. package/src/registry/ui/context-menu.tsx +221 -221
  53. package/src/registry/ui/date-picker.tsx +228 -228
  54. package/src/registry/ui/dialog.tsx +269 -270
  55. package/src/registry/ui/drawer.tsx +10 -4
  56. package/src/registry/ui/dropdown-menu.tsx +529 -530
  57. package/src/registry/ui/empty-state.tsx +0 -2
  58. package/src/registry/ui/file-upload.tsx +0 -0
  59. package/src/registry/ui/floating-dock.tsx +0 -0
  60. package/src/registry/ui/form-field.tsx +91 -94
  61. package/src/registry/ui/google-analytics.tsx +38 -0
  62. package/src/registry/ui/google-tag-manager.tsx +64 -0
  63. package/src/registry/ui/hover-card.tsx +223 -223
  64. package/src/registry/ui/image.tsx +144 -147
  65. package/src/registry/ui/input-group.tsx +82 -85
  66. package/src/registry/ui/input.tsx +125 -125
  67. package/src/registry/ui/kbd.tsx +60 -63
  68. package/src/registry/ui/label.tsx +36 -37
  69. package/src/registry/ui/loading-spinner.tsx +108 -111
  70. package/src/registry/ui/map.tsx +0 -0
  71. package/src/registry/ui/marquee.tsx +2 -0
  72. package/src/registry/ui/menubar.tsx +246 -246
  73. package/src/registry/ui/meta-pixel.tsx +46 -0
  74. package/src/registry/ui/microsoft-clarity.tsx +33 -0
  75. package/src/registry/ui/native-select.tsx +49 -52
  76. package/src/registry/ui/otp-input.tsx +163 -155
  77. package/src/registry/ui/pagination.tsx +149 -152
  78. package/src/registry/ui/patterns.tsx +28 -0
  79. package/src/registry/ui/popover.tsx +226 -227
  80. package/src/registry/ui/progress.tsx +51 -52
  81. package/src/registry/ui/radio.tsx +99 -102
  82. package/src/registry/ui/resizable.tsx +314 -314
  83. package/src/registry/ui/scroll-animation.tsx +45 -0
  84. package/src/registry/ui/scroll-area.tsx +121 -122
  85. package/src/registry/ui/scroll-to-top.tsx +0 -0
  86. package/src/registry/ui/search.tsx +162 -150
  87. package/src/registry/ui/select.tsx +292 -293
  88. package/src/registry/ui/separator.tsx +46 -47
  89. package/src/registry/ui/sheet.tsx +6 -3
  90. package/src/registry/ui/sidebar.tsx +628 -628
  91. package/src/registry/ui/skeleton.tsx +26 -29
  92. package/src/registry/ui/slider.tsx +196 -197
  93. package/src/registry/ui/slot.tsx +69 -72
  94. package/src/registry/ui/star-rating.tsx +146 -134
  95. package/src/registry/ui/switch.tsx +72 -73
  96. package/src/registry/ui/table-of-contents.tsx +96 -96
  97. package/src/registry/ui/table.tsx +138 -139
  98. package/src/registry/ui/tabs.tsx +124 -125
  99. package/src/registry/ui/text.tsx +61 -64
  100. package/src/registry/ui/textarea.tsx +41 -42
  101. package/src/registry/ui/theme-switcher.tsx +66 -66
  102. package/src/registry/ui/tiktok-pixel.tsx +36 -0
  103. package/src/registry/ui/toast.tsx +97 -98
  104. package/src/registry/ui/toggle-group.tsx +129 -129
  105. package/src/registry/ui/toggle.tsx +72 -72
  106. package/src/registry/ui/tooltip.tsx +143 -144
  107. package/src/registry/ui/whatsapp.tsx +0 -0
@@ -1,25 +1,31 @@
1
- "use client"
1
+ "use client"
2
2
 
3
3
  import * as React from "react"
4
4
  import { cva, type VariantProps } from "class-variance-authority"
5
+ import { FaComments, FaTimes, FaPaperPlane } from "react-icons/fa"
5
6
  import { cn } from "@/lib/utils"
6
7
 
7
8
  import { ScrollArea } from "@/components/ui/scroll-area"
9
+ import { Button } from "@/components/ui/button"
10
+ import { Input } from "@/components/ui/input"
11
+ import { LuMessageSquare, LuX } from "react-icons/lu"
8
12
 
9
- const Chatbot = React.forwardRef<
13
+ // --- Primitives ---
14
+
15
+ const ChatbotContainer = React.forwardRef<
10
16
  HTMLDivElement,
11
17
  React.HTMLAttributes<HTMLDivElement>
12
18
  >(({ className, ...props }, ref) => (
13
19
  <div
14
20
  ref={ref}
15
21
  className={cn(
16
- "flex flex-col w-full h-[500px] border rounded-lg bg-background text-foreground shadow-sm overflow-hidden",
22
+ "flex flex-col w-full border rounded-xl bg-background text-foreground shadow-2xl overflow-hidden",
17
23
  className
18
24
  )}
19
25
  {...props}
20
26
  />
21
27
  ))
22
- Chatbot.displayName = "Chatbot"
28
+ ChatbotContainer.displayName = "ChatbotContainer"
23
29
 
24
30
  const ChatbotHeader = React.forwardRef<
25
31
  HTMLDivElement,
@@ -28,7 +34,7 @@ const ChatbotHeader = React.forwardRef<
28
34
  <div
29
35
  ref={ref}
30
36
  className={cn(
31
- "flex items-center px-4 py-3 border-b bg-muted/40",
37
+ "flex items-center justify-between px-4 py-3 border-b bg-muted/40",
32
38
  className
33
39
  )}
34
40
  {...props}
@@ -42,7 +48,7 @@ const ChatbotContent = React.forwardRef<
42
48
  >(({ className, ...props }, ref) => (
43
49
  <ScrollArea
44
50
  ref={ref}
45
- className={cn("flex-1 p-2 space-y-4 flex flex-col", className)}
51
+ className={cn("flex-1 p-4", className)}
46
52
  scrollbarSize="thin"
47
53
  {...props}
48
54
  />
@@ -50,7 +56,7 @@ const ChatbotContent = React.forwardRef<
50
56
  ChatbotContent.displayName = "ChatbotContent"
51
57
 
52
58
  const chatbotMessageVariants = cva(
53
- "max-w-[80%] rounded-2xl px-4 py-2 text-sm break-words",
59
+ "max-w-[85%] rounded-2xl px-4 py-2 text-sm break-words shadow-sm",
54
60
  {
55
61
  variants: {
56
62
  variant: {
@@ -71,9 +77,14 @@ const ChatbotMessage = React.forwardRef<
71
77
  >(({ className, variant, ...props }, ref) => (
72
78
  <div
73
79
  ref={ref}
74
- className={cn(chatbotMessageVariants({ variant }), className)}
75
- {...props}
76
- />
80
+ // Wrapper div for alignment if needed, but styling handles float-like behavior
81
+ className={cn("mb-4 flex", variant === "user" ? "justify-end" : "justify-start")}
82
+ >
83
+ <div
84
+ className={cn(chatbotMessageVariants({ variant }), className)}
85
+ {...props}
86
+ />
87
+ </div>
77
88
  ))
78
89
  ChatbotMessage.displayName = "ChatbotMessage"
79
90
 
@@ -83,17 +94,79 @@ const ChatbotFooter = React.forwardRef<
83
94
  >(({ className, ...props }, ref) => (
84
95
  <div
85
96
  ref={ref}
86
- className={cn("flex items-center p-3 border-t bg-background rounded-b-lg", className)}
97
+ className={cn("flex items-center gap-2 p-3 border-t bg-background", className)}
87
98
  {...props}
88
99
  />
89
100
  ))
90
101
  ChatbotFooter.displayName = "ChatbotFooter"
91
102
 
103
+ // --- Main Widget Component ---
104
+
105
+ export function Chatbot() {
106
+ const [isOpen, setIsOpen] = React.useState(false)
107
+
108
+ return (
109
+ <>
110
+ {/* Chat Window - Fixed Position (left of dock) */}
111
+ {isOpen && (
112
+ <div className="fixed bottom-4 right-16 md:bottom-6 md:right-32 z-50 w-[calc(100vw-80px)] sm:w-[350px] h-[500px] max-h-[70vh] animate-in slide-in-from-right-10 fade-in duration-300">
113
+ <ChatbotContainer className="h-full">
114
+ <ChatbotHeader>
115
+ <div className="flex items-center gap-2">
116
+ <div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
117
+ <span className="font-semibold text-sm">AI Assistant</span>
118
+ </div>
119
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => setIsOpen(false)}>
120
+ <FaTimes className="h-4 w-4" />
121
+ </Button>
122
+ </ChatbotHeader>
123
+
124
+ <ChatbotContent>
125
+ <div className="flex flex-col">
126
+ <ChatbotMessage variant="bot">
127
+ Hello! 👋 I'm your AI assistant. How can I help you build your next project?
128
+ </ChatbotMessage>
129
+ <ChatbotMessage variant="user">
130
+ I need help with the new dashboard layout.
131
+ </ChatbotMessage>
132
+ <ChatbotMessage variant="bot">
133
+ Sure thing! I can help you with components, layout structures, or styling. What specifically are you looking for?
134
+ </ChatbotMessage>
135
+ </div>
136
+ </ChatbotContent>
137
+
138
+ <ChatbotFooter>
139
+ <Input placeholder="Type a message..." className="flex-1" />
140
+ <Button size="icon" type="submit">
141
+ <FaPaperPlane className="h-4 w-4" />
142
+ </Button>
143
+ </ChatbotFooter>
144
+ </ChatbotContainer>
145
+ </div >
146
+ )
147
+ }
148
+
149
+ {/* Trigger Button */}
150
+ <Button
151
+ variant="default"
152
+ size="icon"
153
+ onClick={() => setIsOpen(!isOpen)}
154
+ className={cn(
155
+ "rounded-full h-10 w-10 md:h-16 md:w-16 shadow-lg transition-all duration-300 hover:scale-110",
156
+ isOpen ? "bg-muted text-foreground hover:bg-muted/80" : "bg-primary text-primary-foreground"
157
+ )}
158
+ >
159
+ {isOpen ? <LuX className="!h-5 !w-5 md:!h-8 md:!w-8" /> : <LuMessageSquare className="!h-5 !w-5 md:!h-8 md:!w-8" />}
160
+ </Button>
161
+ </>
162
+ )
163
+ }
164
+
165
+ // Export parts if still needed by other consumers (optional, but good practice)
92
166
  export {
93
- Chatbot,
167
+ ChatbotContainer,
94
168
  ChatbotHeader,
95
169
  ChatbotContent,
96
170
  ChatbotMessage,
97
171
  ChatbotFooter,
98
172
  }
99
-
@@ -1,94 +1,93 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
-
6
- interface CheckboxProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
7
- /**
8
- * Whether the checkbox is checked
9
- */
10
- checked?: boolean
11
- /**
12
- * Callback when the checked state changes
13
- */
14
- onCheckedChange?: (checked: boolean) => void
15
- /**
16
- * Whether the checkbox is disabled
17
- */
18
- disabled?: boolean
19
- /**
20
- * The default checked state (for uncontrolled mode)
21
- */
22
- defaultChecked?: boolean
23
- }
24
-
25
- /**
26
- * Checkbox component with keyboard accessibility
27
- *
28
- * @example
29
- * const [checked, setChecked] = useState(false)
30
- * <Checkbox checked={checked} onCheckedChange={setChecked} />
31
- */
32
- const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
33
- ({ className, checked: controlledChecked, defaultChecked = false, onCheckedChange, disabled, ...props }, ref) => {
34
- const [isChecked, setIsChecked] = React.useState(defaultChecked)
35
-
36
- const checked = controlledChecked !== undefined ? controlledChecked : isChecked
37
-
38
- const handleClick = () => {
39
- if (!disabled) {
40
- const newChecked = !checked
41
- if (controlledChecked === undefined) {
42
- setIsChecked(newChecked)
43
- }
44
- onCheckedChange?.(newChecked)
45
- }
46
- }
47
-
48
- const handleKeyDown = (e: React.KeyboardEvent) => {
49
- if (e.key === " " || e.key === "Enter") {
50
- e.preventDefault()
51
- handleClick()
52
- }
53
- }
54
-
55
- return (
56
- <button
57
- type="button"
58
- role="checkbox"
59
- aria-checked={checked}
60
- aria-disabled={disabled}
61
- disabled={disabled}
62
- ref={ref}
63
- className={cn(
64
- "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
65
- checked && "bg-primary text-primary-foreground",
66
- className
67
- )}
68
- onClick={handleClick}
69
- onKeyDown={handleKeyDown}
70
- {...props}
71
- >
72
- {checked && (
73
- <svg
74
- className="h-full w-full"
75
- fill="none"
76
- viewBox="0 0 24 24"
77
- stroke="currentColor"
78
- strokeWidth={3}
79
- >
80
- <path
81
- strokeLinecap="round"
82
- strokeLinejoin="round"
83
- d="M5 13l4 4L19 7"
84
- />
85
- </svg>
86
- )}
87
- </button>
88
- )
89
- }
90
- )
91
- Checkbox.displayName = "Checkbox"
92
-
93
- export { Checkbox }
94
-
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ interface CheckboxProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
7
+ /**
8
+ * Whether the checkbox is checked
9
+ */
10
+ checked?: boolean
11
+ /**
12
+ * Callback when the checked state changes
13
+ */
14
+ onCheckedChange?: (checked: boolean) => void
15
+ /**
16
+ * Whether the checkbox is disabled
17
+ */
18
+ disabled?: boolean
19
+ /**
20
+ * The default checked state (for uncontrolled mode)
21
+ */
22
+ defaultChecked?: boolean
23
+ }
24
+
25
+ /**
26
+ * Checkbox component with keyboard accessibility
27
+ *
28
+ * @example
29
+ * const [checked, setChecked] = useState(false)
30
+ * <Checkbox checked={checked} onCheckedChange={setChecked} />
31
+ */
32
+ const Checkbox = React.forwardRef<HTMLButtonElement, CheckboxProps>(
33
+ ({ className, checked: controlledChecked, defaultChecked = false, onCheckedChange, disabled, ...props }, ref) => {
34
+ const [isChecked, setIsChecked] = React.useState(defaultChecked)
35
+
36
+ const checked = controlledChecked !== undefined ? controlledChecked : isChecked
37
+
38
+ const handleClick = () => {
39
+ if (!disabled) {
40
+ const newChecked = !checked
41
+ if (controlledChecked === undefined) {
42
+ setIsChecked(newChecked)
43
+ }
44
+ onCheckedChange?.(newChecked)
45
+ }
46
+ }
47
+
48
+ const handleKeyDown = (e: React.KeyboardEvent) => {
49
+ if (e.key === " " || e.key === "Enter") {
50
+ e.preventDefault()
51
+ handleClick()
52
+ }
53
+ }
54
+
55
+ return (
56
+ <button
57
+ type="button"
58
+ role="checkbox"
59
+ aria-checked={checked}
60
+ aria-disabled={disabled}
61
+ disabled={disabled}
62
+ ref={ref}
63
+ className={cn(
64
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
65
+ checked && "bg-primary text-primary-foreground",
66
+ className
67
+ )}
68
+ onClick={handleClick}
69
+ onKeyDown={handleKeyDown}
70
+ {...props}
71
+ >
72
+ {checked && (
73
+ <svg
74
+ className="h-full w-full"
75
+ fill="none"
76
+ viewBox="0 0 24 24"
77
+ stroke="currentColor"
78
+ strokeWidth={3}
79
+ >
80
+ <path
81
+ strokeLinecap="round"
82
+ strokeLinejoin="round"
83
+ d="M5 13l4 4L19 7"
84
+ />
85
+ </svg>
86
+ )}
87
+ </button>
88
+ )
89
+ }
90
+ )
91
+ Checkbox.displayName = "Checkbox"
92
+
93
+ export { Checkbox }
@@ -1,108 +1,107 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
- import { Slot } from "@/components/ui/slot"
6
-
7
- interface CollapsibleContextValue {
8
- open: boolean
9
- onOpenChange: (open: boolean) => void
10
- }
11
-
12
- const CollapsibleContext = React.createContext<CollapsibleContextValue | null>(null)
13
-
14
- interface CollapsibleProps {
15
- children: React.ReactNode
16
- open?: boolean
17
- onOpenChange?: (open: boolean) => void
18
- defaultOpen?: boolean
19
- className?: string
20
- }
21
-
22
- /**
23
- * Collapsible component for expandable content
24
- *
25
- * @example
26
- * <Collapsible>
27
- * <CollapsibleTrigger>Toggle</CollapsibleTrigger>
28
- * <CollapsibleContent>
29
- * Hidden content here
30
- * </CollapsibleContent>
31
- * </Collapsible>
32
- */
33
- const Collapsible = React.forwardRef<HTMLDivElement, CollapsibleProps>(
34
- ({ children, open: controlledOpen, onOpenChange, defaultOpen = false, className }, ref) => {
35
- const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
36
-
37
- const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
38
- const setOpen = onOpenChange || setUncontrolledOpen
39
-
40
- return (
41
- <CollapsibleContext.Provider value={{ open, onOpenChange: setOpen }}>
42
- <div ref={ref} className={className} data-state={open ? "open" : "closed"}>
43
- {children}
44
- </div>
45
- </CollapsibleContext.Provider>
46
- )
47
- }
48
- )
49
- Collapsible.displayName = "Collapsible"
50
-
51
- interface CollapsibleTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
52
- asChild?: boolean
53
- }
54
-
55
- const CollapsibleTrigger = React.forwardRef<HTMLButtonElement, CollapsibleTriggerProps>(
56
- ({ className, children, asChild, onClick, ...props }, ref) => {
57
- const context = React.useContext(CollapsibleContext)
58
- if (!context) throw new Error("CollapsibleTrigger must be used within Collapsible")
59
-
60
- const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
61
- onClick?.(e)
62
- context.onOpenChange(!context.open)
63
- }
64
-
65
- const Comp = asChild ? Slot : "button"
66
-
67
- return (
68
- <Comp
69
- ref={ref}
70
- type="button"
71
- aria-expanded={context.open}
72
- className={className}
73
- onClick={handleClick}
74
- {...props}
75
- >
76
- {children}
77
- </Comp>
78
- )
79
- }
80
- )
81
- CollapsibleTrigger.displayName = "CollapsibleTrigger"
82
-
83
- const CollapsibleContent = React.forwardRef<
84
- HTMLDivElement,
85
- React.HTMLAttributes<HTMLDivElement>
86
- >(({ className, children, ...props }, ref) => {
87
- const context = React.useContext(CollapsibleContext)
88
- if (!context) throw new Error("CollapsibleContent must be used within Collapsible")
89
-
90
- return (
91
- <div
92
- ref={ref}
93
- className={cn(
94
- "overflow-hidden transition-all",
95
- context.open ? "animate-collapsible-down" : "animate-collapsible-up hidden",
96
- className
97
- )}
98
- data-state={context.open ? "open" : "closed"}
99
- {...props}
100
- >
101
- {children}
102
- </div>
103
- )
104
- })
105
- CollapsibleContent.displayName = "CollapsibleContent"
106
-
107
- export { Collapsible, CollapsibleTrigger, CollapsibleContent }
108
-
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+ import { Slot } from "@/components/ui/slot"
6
+
7
+ interface CollapsibleContextValue {
8
+ open: boolean
9
+ onOpenChange: (open: boolean) => void
10
+ }
11
+
12
+ const CollapsibleContext = React.createContext<CollapsibleContextValue | null>(null)
13
+
14
+ interface CollapsibleProps {
15
+ children: React.ReactNode
16
+ open?: boolean
17
+ onOpenChange?: (open: boolean) => void
18
+ defaultOpen?: boolean
19
+ className?: string
20
+ }
21
+
22
+ /**
23
+ * Collapsible component for expandable content
24
+ *
25
+ * @example
26
+ * <Collapsible>
27
+ * <CollapsibleTrigger>Toggle</CollapsibleTrigger>
28
+ * <CollapsibleContent>
29
+ * Hidden content here
30
+ * </CollapsibleContent>
31
+ * </Collapsible>
32
+ */
33
+ const Collapsible = React.forwardRef<HTMLDivElement, CollapsibleProps>(
34
+ ({ children, open: controlledOpen, onOpenChange, defaultOpen = false, className }, ref) => {
35
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
36
+
37
+ const open = controlledOpen !== undefined ? controlledOpen : uncontrolledOpen
38
+ const setOpen = onOpenChange || setUncontrolledOpen
39
+
40
+ return (
41
+ <CollapsibleContext.Provider value={{ open, onOpenChange: setOpen }}>
42
+ <div ref={ref} className={className} data-state={open ? "open" : "closed"}>
43
+ {children}
44
+ </div>
45
+ </CollapsibleContext.Provider>
46
+ )
47
+ }
48
+ )
49
+ Collapsible.displayName = "Collapsible"
50
+
51
+ interface CollapsibleTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
52
+ asChild?: boolean
53
+ }
54
+
55
+ const CollapsibleTrigger = React.forwardRef<HTMLButtonElement, CollapsibleTriggerProps>(
56
+ ({ className, children, asChild, onClick, ...props }, ref) => {
57
+ const context = React.useContext(CollapsibleContext)
58
+ if (!context) throw new Error("CollapsibleTrigger must be used within Collapsible")
59
+
60
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
61
+ onClick?.(e)
62
+ context.onOpenChange(!context.open)
63
+ }
64
+
65
+ const Comp = asChild ? Slot : "button"
66
+
67
+ return (
68
+ <Comp
69
+ ref={ref}
70
+ type="button"
71
+ aria-expanded={context.open}
72
+ className={className}
73
+ onClick={handleClick}
74
+ {...props}
75
+ >
76
+ {children}
77
+ </Comp>
78
+ )
79
+ }
80
+ )
81
+ CollapsibleTrigger.displayName = "CollapsibleTrigger"
82
+
83
+ const CollapsibleContent = React.forwardRef<
84
+ HTMLDivElement,
85
+ React.HTMLAttributes<HTMLDivElement>
86
+ >(({ className, children, ...props }, ref) => {
87
+ const context = React.useContext(CollapsibleContext)
88
+ if (!context) throw new Error("CollapsibleContent must be used within Collapsible")
89
+
90
+ return (
91
+ <div
92
+ ref={ref}
93
+ className={cn(
94
+ "overflow-hidden transition-all",
95
+ context.open ? "animate-collapsible-down" : "animate-collapsible-up hidden",
96
+ className
97
+ )}
98
+ data-state={context.open ? "open" : "closed"}
99
+ {...props}
100
+ >
101
+ {children}
102
+ </div>
103
+ )
104
+ })
105
+ CollapsibleContent.displayName = "CollapsibleContent"
106
+
107
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }