@gunjo/ui 0.0.1-alpha.1 → 0.0.1-alpha.2

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 (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +90 -0
  3. package/README.md +52 -91
  4. package/package.json +47 -6
  5. package/src/components/display/Accordion.tsx +185 -0
  6. package/src/components/display/AccordionGroup.tsx +155 -0
  7. package/src/components/display/ActionDataTable.tsx +413 -0
  8. package/src/components/display/ActivityTimelineCard.tsx +483 -0
  9. package/src/components/display/AnalyticsCard.tsx +167 -0
  10. package/src/components/display/AssetCard.tsx +242 -0
  11. package/src/components/display/AssetGrid.tsx +164 -0
  12. package/src/components/display/Avatar.tsx +127 -0
  13. package/src/components/display/AvatarGroup.tsx +131 -0
  14. package/src/components/{atoms → display}/Badge.tsx +3 -3
  15. package/src/components/display/BarChart.tsx +247 -0
  16. package/src/components/{molecules → display}/Card.tsx +1 -1
  17. package/src/components/display/Carousel.tsx +593 -0
  18. package/src/components/display/ChartLegend.tsx +124 -0
  19. package/src/components/display/ChatMessage.tsx +382 -0
  20. package/src/components/display/ChoroplethMap.tsx +613 -0
  21. package/src/components/display/Code.tsx +42 -0
  22. package/src/components/display/CodeBlock.tsx +338 -0
  23. package/src/components/display/ColorSwatch.tsx +71 -0
  24. package/src/components/display/ConcentricProgressCard.tsx +545 -0
  25. package/src/components/display/DataTable.tsx +522 -0
  26. package/src/components/display/DistributionBar.tsx +102 -0
  27. package/src/components/display/DocNote.tsx +36 -0
  28. package/src/components/display/DonutChart.tsx +257 -0
  29. package/src/components/display/EmptyState.tsx +44 -0
  30. package/src/components/display/FileTree.tsx +180 -0
  31. package/src/components/display/GaugeChart.tsx +219 -0
  32. package/src/components/display/HeatmapChart.tsx +266 -0
  33. package/src/components/display/Icon.tsx +66 -0
  34. package/src/components/display/ImagePreview.tsx +140 -0
  35. package/src/components/{atoms → display}/Img.tsx +46 -12
  36. package/src/components/display/LabeledDonutCard.tsx +475 -0
  37. package/src/components/display/LineChart.tsx +464 -0
  38. package/src/components/{molecules → display}/List.tsx +20 -13
  39. package/src/components/display/MarkdownRenderer.tsx +157 -0
  40. package/src/components/display/MetadataList.tsx +81 -0
  41. package/src/components/display/MiniDistributionBarCard.tsx +314 -0
  42. package/src/components/display/PieChart.tsx +234 -0
  43. package/src/components/display/QuadrantMatrix.tsx +330 -0
  44. package/src/components/display/RadarChart.tsx +335 -0
  45. package/src/components/display/RadialBarChart.tsx +264 -0
  46. package/src/components/display/RetentionCohortCard.tsx +350 -0
  47. package/src/components/display/RibbonChart.tsx +618 -0
  48. package/src/components/display/SearchableAccordion.tsx +270 -0
  49. package/src/components/display/SegmentTimelineCard.tsx +452 -0
  50. package/src/components/display/SegmentedGaugeCard.tsx +607 -0
  51. package/src/components/display/Spacer.tsx +51 -0
  52. package/src/components/display/SparklineChart.tsx +394 -0
  53. package/src/components/display/StackedBarChart.tsx +393 -0
  54. package/src/components/display/Statistic.tsx +70 -0
  55. package/src/components/{molecules → display}/Table.tsx +22 -7
  56. package/src/components/display/Tag.tsx +80 -0
  57. package/src/components/display/TagEditor.tsx +141 -0
  58. package/src/components/display/Timeline.tsx +121 -0
  59. package/src/components/{atoms → display}/ToolPill.tsx +42 -18
  60. package/src/components/display/TreeView.tsx +226 -0
  61. package/src/components/display/chart-tooltip.tsx +423 -0
  62. package/src/components/display/chart-utils.ts +71 -0
  63. package/src/components/display/circular-chart-utils.ts +147 -0
  64. package/src/components/display/generated/default-variant-keys.ts +90 -0
  65. package/src/components/display/generated/variant-keys.ts +169 -0
  66. package/src/components/{atoms → feedback}/Alert.tsx +12 -5
  67. package/src/components/feedback/Banner.tsx +90 -0
  68. package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
  69. package/src/components/feedback/ProgressWidget.tsx +44 -0
  70. package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
  71. package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
  72. package/src/components/feedback/StatusScreen.tsx +148 -0
  73. package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
  74. package/src/components/feedback/Toast.tsx +108 -0
  75. package/src/components/feedback/ToastProvider.tsx +78 -0
  76. package/src/components/feedback/generated/default-variant-keys.ts +16 -0
  77. package/src/components/feedback/generated/variant-keys.ts +21 -0
  78. package/src/components/generated/component-manifest.ts +1568 -454
  79. package/src/components/generated/component-style-hints.ts +1958 -718
  80. package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
  81. package/src/components/inputs/Calendar.tsx +212 -0
  82. package/src/components/inputs/ChatComposer.tsx +75 -0
  83. package/src/components/inputs/ChatInput.tsx +528 -0
  84. package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
  85. package/src/components/inputs/Combobox.tsx +175 -0
  86. package/src/components/inputs/CopyButton.tsx +187 -0
  87. package/src/components/inputs/DatePicker.tsx +519 -0
  88. package/src/components/inputs/DateRangePicker.tsx +878 -0
  89. package/src/components/inputs/EditableField.tsx +182 -0
  90. package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
  91. package/src/components/inputs/FilterButton.tsx +163 -0
  92. package/src/components/{molecules → inputs}/Form.tsx +20 -3
  93. package/src/components/{atoms → inputs}/Input.tsx +2 -0
  94. package/src/components/inputs/InputOTP.tsx +75 -0
  95. package/src/components/inputs/Mention.tsx +279 -0
  96. package/src/components/inputs/NumberInput.tsx +109 -0
  97. package/src/components/inputs/PasswordGroup.tsx +138 -0
  98. package/src/components/inputs/PasswordInput.tsx +74 -0
  99. package/src/components/inputs/PasswordRequirementList.tsx +96 -0
  100. package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
  101. package/src/components/inputs/PhoneInput.tsx +99 -0
  102. package/src/components/inputs/PostalCodeInput.tsx +98 -0
  103. package/src/components/inputs/RangeSlider.tsx +129 -0
  104. package/src/components/inputs/SearchInput.tsx +76 -0
  105. package/src/components/inputs/Select.tsx +39 -0
  106. package/src/components/{atoms → inputs}/Slider.tsx +18 -5
  107. package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
  108. package/src/components/{atoms → inputs}/Switch.tsx +15 -4
  109. package/src/components/inputs/TagInput.tsx +114 -0
  110. package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
  111. package/src/components/inputs/TimePicker.tsx +150 -0
  112. package/src/components/inputs/Toggle.tsx +48 -0
  113. package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
  114. package/src/components/inputs/TooltipButton.tsx +148 -0
  115. package/src/components/inputs/VoiceInputButton.tsx +317 -0
  116. package/src/components/inputs/calendar-holidays.ts +56 -0
  117. package/src/components/inputs/generated/default-variant-keys.ts +32 -0
  118. package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
  119. package/src/components/layout/AspectRatio.tsx +12 -0
  120. package/src/components/layout/AssetInspectorPanel.tsx +416 -0
  121. package/src/components/layout/Cluster.tsx +56 -0
  122. package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
  123. package/src/components/layout/Container.tsx +43 -0
  124. package/src/components/layout/DeviceFrame.tsx +227 -0
  125. package/src/components/layout/Grid.tsx +65 -0
  126. package/src/components/layout/HStack.tsx +73 -0
  127. package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
  128. package/src/components/layout/MarqueeFrame.tsx +158 -0
  129. package/src/components/layout/Resizable.tsx +94 -0
  130. package/src/components/layout/ScrollArea.tsx +71 -0
  131. package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
  132. package/src/components/layout/VStack.tsx +69 -0
  133. package/src/components/layout/generated/default-variant-keys.ts +16 -0
  134. package/src/components/layout/generated/variant-keys.ts +21 -0
  135. package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
  136. package/src/components/navigation/Command.tsx +266 -0
  137. package/src/components/navigation/CommandPalette.tsx +83 -0
  138. package/src/components/navigation/DocumentPager.tsx +171 -0
  139. package/src/components/navigation/Footer.tsx +88 -0
  140. package/src/components/navigation/Header.tsx +80 -0
  141. package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
  142. package/src/components/navigation/NavigationMenu.tsx +128 -0
  143. package/src/components/navigation/PageAside.tsx +84 -0
  144. package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
  145. package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
  146. package/src/components/navigation/Sidebar.tsx +223 -0
  147. package/src/components/navigation/SidebarItem.tsx +160 -0
  148. package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
  149. package/src/components/navigation/TextLink.tsx +71 -0
  150. package/src/components/navigation/generated/default-variant-keys.ts +12 -0
  151. package/src/components/navigation/generated/variant-keys.ts +13 -0
  152. package/src/components/overlay/AIChatInput.tsx +5 -0
  153. package/src/components/overlay/AIChatMessage.tsx +6 -0
  154. package/src/components/overlay/AlertDialog.tsx +145 -0
  155. package/src/components/overlay/ChatPanel.tsx +180 -0
  156. package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
  157. package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
  158. package/src/components/overlay/Drawer.tsx +131 -0
  159. package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
  160. package/src/components/overlay/FloatingPanel.tsx +90 -0
  161. package/src/components/overlay/HoverCard.tsx +36 -0
  162. package/src/components/overlay/MediaLightbox.tsx +403 -0
  163. package/src/components/overlay/MediaPickerDialog.tsx +198 -0
  164. package/src/components/overlay/Modal.tsx +103 -0
  165. package/src/components/overlay/OnboardingFlow.tsx +172 -0
  166. package/src/components/overlay/Popover.tsx +36 -0
  167. package/src/components/overlay/ShareModal.tsx +324 -0
  168. package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
  169. package/src/components/overlay/Tooltip.tsx +130 -0
  170. package/src/components/overlay/generated/default-variant-keys.ts +14 -0
  171. package/src/components/overlay/generated/variant-keys.ts +17 -0
  172. package/src/components/patterns/BlogTemplate.tsx +46 -0
  173. package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
  174. package/src/components/patterns/DocsTemplate.tsx +41 -0
  175. package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
  176. package/src/components/patterns/OnboardingTemplate.tsx +32 -0
  177. package/src/components/patterns/PricingTemplate.tsx +106 -0
  178. package/src/globals.css +173 -22
  179. package/src/index.ts +177 -76
  180. package/tailwind-theme-extend.cjs +48 -3
  181. package/design/atoms-metadata.json +0 -82
  182. package/design/molecules-metadata.json +0 -130
  183. package/design/organisms-metadata.json +0 -38
  184. package/design/templates-metadata.json +0 -38
  185. package/src/components/atoms/Avatar.tsx +0 -57
  186. package/src/components/atoms/Select.tsx +0 -28
  187. package/src/components/atoms/generated/default-variant-keys.ts +0 -36
  188. package/src/components/molecules/AIChatInput.tsx +0 -140
  189. package/src/components/molecules/AIChatMessage.tsx +0 -109
  190. package/src/components/molecules/Accordion.tsx +0 -99
  191. package/src/components/molecules/Calendar.tsx +0 -60
  192. package/src/components/molecules/Carousel.tsx +0 -261
  193. package/src/components/molecules/Command.tsx +0 -152
  194. package/src/components/molecules/FilterButton.tsx +0 -133
  195. package/src/components/molecules/HoverCard.tsx +0 -29
  196. package/src/components/molecules/Modal.tsx +0 -66
  197. package/src/components/molecules/Popover.tsx +0 -31
  198. package/src/components/molecules/ProgressWidget.tsx +0 -40
  199. package/src/components/molecules/Resizable.tsx +0 -47
  200. package/src/components/molecules/ScrollArea.tsx +0 -48
  201. package/src/components/molecules/SidebarItem.tsx +0 -134
  202. package/src/components/molecules/Toast.tsx +0 -57
  203. package/src/components/molecules/Tooltip.tsx +0 -30
  204. package/src/components/molecules/generated/default-variant-keys.ts +0 -22
  205. package/src/components/molecules/generated/variant-keys.ts +0 -33
  206. package/src/components/organisms/CommandPalette.tsx +0 -58
  207. package/src/components/organisms/FloatingPanel.tsx +0 -46
  208. package/src/components/organisms/ShareModal.tsx +0 -182
  209. package/src/components/organisms/ToastProvider.tsx +0 -49
  210. /package/src/components/{atoms → display}/Kbd.tsx +0 -0
  211. /package/src/components/{atoms → display}/Separator.tsx +0 -0
  212. /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
  213. /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
  214. /package/src/components/{atoms → inputs}/Button.tsx +0 -0
  215. /package/src/components/{atoms → inputs}/Label.tsx +0 -0
  216. /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
  217. /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
  218. /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
  219. /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
  220. /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
  221. /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
  222. /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
  223. /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
  224. /package/src/components/{templates → patterns}/SettingsTemplate.tsx +0 -0
@@ -0,0 +1,593 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import useEmblaCarousel, {
5
+ type UseEmblaCarouselType,
6
+ } from "embla-carousel-react"
7
+ import {
8
+ IconChevronLeft as ChevronLeft,
9
+ IconChevronRight as ChevronRight,
10
+ IconPlayerPause as PlayerPause,
11
+ IconPlayerPlay as PlayerPlay,
12
+ } from "@tabler/icons-react"
13
+
14
+ import { cn } from "../../lib/utils"
15
+ import { Button } from "../inputs/Button"
16
+ import { TooltipButton } from "../inputs/TooltipButton"
17
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
18
+ import { Icon } from "./Icon"
19
+
20
+ type CarouselApi = UseEmblaCarouselType[1]
21
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
22
+ type CarouselOptions = UseCarouselParameters[0]
23
+ type CarouselPlugin = UseCarouselParameters[1]
24
+
25
+ type CarouselControlOptions = {
26
+ navigation?: boolean
27
+ dots?: boolean
28
+ autoplayToggle?: boolean
29
+ hideNavigationWhenDisabled?: boolean
30
+ className?: string
31
+ previousClassName?: string
32
+ nextClassName?: string
33
+ dotsClassName?: string
34
+ autoplayToggleClassName?: string
35
+ labels?: {
36
+ previous?: string
37
+ next?: string
38
+ dots?: string
39
+ play?: string
40
+ pause?: string
41
+ getDotLabel?: (index: number) => string
42
+ }
43
+ }
44
+
45
+ type CarouselProps = {
46
+ opts?: CarouselOptions
47
+ plugins?: CarouselPlugin
48
+ orientation?: "horizontal" | "vertical"
49
+ setApi?: (api: CarouselApi) => void
50
+ autoPlay?: boolean
51
+ autoPlayInterval?: number
52
+ pauseOnHover?: boolean
53
+ controls?: boolean | CarouselControlOptions
54
+ }
55
+
56
+ type CarouselContextProps = {
57
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
58
+ api: CarouselApi
59
+ scrollPrev: () => void
60
+ scrollNext: () => void
61
+ scrollTo: (index: number) => void
62
+ canScrollPrev: boolean
63
+ canScrollNext: boolean
64
+ selectedIndex: number
65
+ scrollSnapCount: number
66
+ isAutoPlaying: boolean
67
+ setIsAutoPlaying: React.Dispatch<React.SetStateAction<boolean>>
68
+ } & CarouselProps
69
+
70
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
71
+
72
+ function useCarousel() {
73
+ const context = React.useContext(CarouselContext)
74
+
75
+ if (!context) {
76
+ throw new Error("useCarousel must be used within a <Carousel />")
77
+ }
78
+
79
+ return context
80
+ }
81
+
82
+ const Carousel = React.forwardRef<
83
+ HTMLDivElement,
84
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
85
+ >(
86
+ (
87
+ {
88
+ orientation = "horizontal",
89
+ opts,
90
+ setApi,
91
+ plugins,
92
+ autoPlay = false,
93
+ autoPlayInterval = 4000,
94
+ pauseOnHover = true,
95
+ controls,
96
+ className,
97
+ children,
98
+ ...props
99
+ },
100
+ ref
101
+ ) => {
102
+ const [carouselRef, api] = useEmblaCarousel(
103
+ {
104
+ ...opts,
105
+ axis: orientation === "horizontal" ? "x" : "y",
106
+ },
107
+ plugins
108
+ )
109
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
110
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
111
+ const [selectedIndex, setSelectedIndex] = React.useState(0)
112
+ const [scrollSnapCount, setScrollSnapCount] = React.useState(0)
113
+ const [isAutoPlaying, setIsAutoPlaying] = React.useState(autoPlay)
114
+ const pauseTimeoutRef = React.useRef<number | null>(null)
115
+
116
+ const onSelect = React.useCallback((api: CarouselApi) => {
117
+ if (!api) {
118
+ return
119
+ }
120
+
121
+ setCanScrollPrev(api.canScrollPrev())
122
+ setCanScrollNext(api.canScrollNext())
123
+ setSelectedIndex(api.selectedScrollSnap())
124
+ setScrollSnapCount(api.scrollSnapList().length)
125
+ }, [])
126
+
127
+ const scrollPrev = React.useCallback(() => {
128
+ api?.scrollPrev()
129
+ }, [api])
130
+
131
+ const scrollNext = React.useCallback(() => {
132
+ api?.scrollNext()
133
+ }, [api])
134
+
135
+ const scrollTo = React.useCallback((index: number) => {
136
+ api?.scrollTo(index)
137
+ }, [api])
138
+
139
+ const handleKeyDown = React.useCallback(
140
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
141
+ if (event.key === "ArrowLeft") {
142
+ event.preventDefault()
143
+ scrollPrev()
144
+ } else if (event.key === "ArrowRight") {
145
+ event.preventDefault()
146
+ scrollNext()
147
+ }
148
+ },
149
+ [scrollPrev, scrollNext]
150
+ )
151
+
152
+ React.useEffect(() => {
153
+ setIsAutoPlaying(autoPlay)
154
+ }, [autoPlay])
155
+
156
+ React.useEffect(() => {
157
+ if (!api || !setApi) {
158
+ return
159
+ }
160
+
161
+ setApi(api)
162
+ }, [api, setApi])
163
+
164
+ React.useEffect(() => {
165
+ if (!api) {
166
+ return
167
+ }
168
+
169
+ onSelect(api)
170
+ api.on("reInit", onSelect)
171
+ api.on("select", onSelect)
172
+
173
+ return () => {
174
+ api?.off("reInit", onSelect)
175
+ api?.off("select", onSelect)
176
+ }
177
+ }, [api, onSelect])
178
+
179
+ React.useEffect(() => {
180
+ if (!api || !isAutoPlaying || autoPlayInterval <= 0) {
181
+ return
182
+ }
183
+
184
+ const timer = window.setInterval(() => {
185
+ if (api.canScrollNext()) {
186
+ api.scrollNext()
187
+ } else {
188
+ api.scrollTo(0)
189
+ }
190
+ }, autoPlayInterval)
191
+
192
+ return () => window.clearInterval(timer)
193
+ }, [api, autoPlayInterval, isAutoPlaying])
194
+
195
+ const pauseAutoPlayTemporarily = React.useCallback(() => {
196
+ if (!pauseOnHover || !autoPlay) {
197
+ return
198
+ }
199
+
200
+ setIsAutoPlaying(false)
201
+ if (pauseTimeoutRef.current) {
202
+ window.clearTimeout(pauseTimeoutRef.current)
203
+ }
204
+ pauseTimeoutRef.current = window.setTimeout(() => {
205
+ setIsAutoPlaying(true)
206
+ }, autoPlayInterval)
207
+ }, [autoPlay, autoPlayInterval, pauseOnHover])
208
+
209
+ React.useEffect(() => {
210
+ return () => {
211
+ if (pauseTimeoutRef.current) {
212
+ window.clearTimeout(pauseTimeoutRef.current)
213
+ }
214
+ }
215
+ }, [])
216
+
217
+ return (
218
+ <CarouselContext.Provider
219
+ value={{
220
+ carouselRef,
221
+ api: api,
222
+ opts,
223
+ orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
224
+ scrollPrev,
225
+ scrollNext,
226
+ scrollTo,
227
+ canScrollPrev,
228
+ canScrollNext,
229
+ selectedIndex,
230
+ scrollSnapCount,
231
+ isAutoPlaying,
232
+ setIsAutoPlaying,
233
+ autoPlay,
234
+ autoPlayInterval,
235
+ pauseOnHover,
236
+ controls,
237
+ }}
238
+ >
239
+ <div
240
+ ref={ref}
241
+ onKeyDown={handleKeyDown}
242
+ onMouseEnter={pauseAutoPlayTemporarily}
243
+ onFocusCapture={pauseAutoPlayTemporarily}
244
+ className={cn("relative w-[640px]", className)}
245
+ role="region"
246
+ aria-roledescription="carousel"
247
+ {...props}
248
+ >
249
+ {children}
250
+ {controls ? <CarouselDefaultControls controls={controls} /> : null}
251
+ </div>
252
+ </CarouselContext.Provider>
253
+ )
254
+ }
255
+ )
256
+ Carousel.displayName = "Carousel"
257
+
258
+ const CarouselContent = React.forwardRef<
259
+ HTMLDivElement,
260
+ React.HTMLAttributes<HTMLDivElement> & {
261
+ viewportClassName?: string
262
+ }
263
+ >(({ className, viewportClassName, ...props }, ref) => {
264
+ const { carouselRef, orientation } = useCarousel()
265
+
266
+ return (
267
+ <div ref={carouselRef} className={cn("overflow-hidden", viewportClassName)}>
268
+ <div
269
+ ref={ref}
270
+ className={cn(
271
+ "flex",
272
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
273
+ className
274
+ )}
275
+ {...props}
276
+ />
277
+ </div>
278
+ )
279
+ })
280
+ CarouselContent.displayName = "CarouselContent"
281
+
282
+ const CarouselItem = React.forwardRef<
283
+ HTMLDivElement,
284
+ React.HTMLAttributes<HTMLDivElement>
285
+ >(({ className, ...props }, ref) => {
286
+ const { orientation } = useCarousel()
287
+
288
+ return (
289
+ <div
290
+ ref={ref}
291
+ role="group"
292
+ aria-roledescription="slide"
293
+ className={cn(
294
+ "min-w-0 shrink-0 grow-0 basis-full",
295
+ orientation === "horizontal" ? "pl-4" : "pt-4",
296
+ className
297
+ )}
298
+ {...props}
299
+ />
300
+ )
301
+ })
302
+ CarouselItem.displayName = "CarouselItem"
303
+
304
+ const CarouselPrevious = React.forwardRef<
305
+ HTMLButtonElement,
306
+ React.ComponentProps<typeof Button> & { label?: string; hideWhenDisabled?: boolean }
307
+ >(({ className, variant = "outline", size = "icon", label = "Previous slide", hideWhenDisabled = false, ...props }, ref) => {
308
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
309
+
310
+ if (hideWhenDisabled && !canScrollPrev) {
311
+ return null
312
+ }
313
+
314
+ return (
315
+ <TooltipButton
316
+ ref={ref}
317
+ variant={variant}
318
+ size={size}
319
+ tooltip={label}
320
+ className={cn(
321
+ "absolute h-8 w-8 rounded-full",
322
+ orientation === "horizontal"
323
+ ? "-left-12 top-1/2 -translate-y-1/2"
324
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
325
+ className
326
+ )}
327
+ disabled={!canScrollPrev}
328
+ aria-label={label}
329
+ onClick={scrollPrev}
330
+ {...props}
331
+ >
332
+ <Icon icon={ChevronLeft} size="sm" />
333
+ <span className="sr-only">{label}</span>
334
+ </TooltipButton>
335
+ )
336
+ })
337
+ CarouselPrevious.displayName = "CarouselPrevious"
338
+
339
+ const CarouselNext = React.forwardRef<
340
+ HTMLButtonElement,
341
+ React.ComponentProps<typeof Button> & { label?: string; hideWhenDisabled?: boolean }
342
+ >(({ className, variant = "outline", size = "icon", label = "Next slide", hideWhenDisabled = false, ...props }, ref) => {
343
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
344
+
345
+ if (hideWhenDisabled && !canScrollNext) {
346
+ return null
347
+ }
348
+
349
+ return (
350
+ <TooltipButton
351
+ ref={ref}
352
+ variant={variant}
353
+ size={size}
354
+ tooltip={label}
355
+ className={cn(
356
+ "absolute h-8 w-8 rounded-full",
357
+ orientation === "horizontal"
358
+ ? "-right-12 top-1/2 -translate-y-1/2"
359
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
360
+ className
361
+ )}
362
+ disabled={!canScrollNext}
363
+ aria-label={label}
364
+ onClick={scrollNext}
365
+ {...props}
366
+ >
367
+ <Icon icon={ChevronRight} size="sm" />
368
+ <span className="sr-only">{label}</span>
369
+ </TooltipButton>
370
+ )
371
+ })
372
+ CarouselNext.displayName = "CarouselNext"
373
+
374
+ const CarouselDots = React.forwardRef<
375
+ HTMLDivElement,
376
+ React.HTMLAttributes<HTMLDivElement> & {
377
+ label?: string
378
+ getDotLabel?: (index: number) => string
379
+ }
380
+ >(({ className, label = "Slide controls", getDotLabel, ...props }, ref) => {
381
+ const { scrollSnapCount, selectedIndex, scrollTo } = useCarousel()
382
+
383
+ if (scrollSnapCount <= 1) {
384
+ return null
385
+ }
386
+
387
+ return (
388
+ <div
389
+ ref={ref}
390
+ role="tablist"
391
+ aria-label={label}
392
+ className={cn("flex items-center justify-center gap-2", className)}
393
+ {...props}
394
+ >
395
+ {Array.from({ length: scrollSnapCount }).map((_, index) => {
396
+ const active = selectedIndex === index
397
+ const dotLabel = getDotLabel?.(index) ?? `${label} ${index + 1}`
398
+
399
+ return (
400
+ <Tooltip key={index}>
401
+ <TooltipTrigger asChild>
402
+ <button
403
+ type="button"
404
+ role="tab"
405
+ aria-selected={active}
406
+ aria-label={dotLabel}
407
+ className={cn(
408
+ "h-2.5 rounded-full border border-transparent transition-all duration-200",
409
+ active
410
+ ? "w-7 bg-primary"
411
+ : "w-2.5 bg-muted-foreground/35 hover:bg-muted-foreground/60"
412
+ )}
413
+ onClick={() => scrollTo(index)}
414
+ />
415
+ </TooltipTrigger>
416
+ <TooltipContent>{dotLabel}</TooltipContent>
417
+ </Tooltip>
418
+ )
419
+ })}
420
+ </div>
421
+ )
422
+ })
423
+ CarouselDots.displayName = "CarouselDots"
424
+
425
+ const CarouselThumbnails = React.forwardRef<
426
+ HTMLDivElement,
427
+ React.HTMLAttributes<HTMLDivElement> & { label?: string }
428
+ >(({ className, label = "Slide thumbnails", ...props }, ref) => {
429
+ const { scrollSnapCount } = useCarousel()
430
+
431
+ if (scrollSnapCount <= 1) {
432
+ return null
433
+ }
434
+
435
+ return (
436
+ <div
437
+ ref={ref}
438
+ role="tablist"
439
+ aria-label={label}
440
+ className={cn("flex items-center justify-center gap-2 overflow-x-auto py-1", className)}
441
+ {...props}
442
+ />
443
+ )
444
+ })
445
+ CarouselThumbnails.displayName = "CarouselThumbnails"
446
+
447
+ const CarouselThumbnail = React.forwardRef<
448
+ HTMLButtonElement,
449
+ React.ButtonHTMLAttributes<HTMLButtonElement> & {
450
+ index: number
451
+ label: string
452
+ }
453
+ >(({ className, index, label, children, onClick, ...props }, ref) => {
454
+ const { selectedIndex, scrollTo } = useCarousel()
455
+ const active = selectedIndex === index
456
+
457
+ return (
458
+ <Tooltip>
459
+ <TooltipTrigger asChild>
460
+ <button
461
+ ref={ref}
462
+ type="button"
463
+ role="tab"
464
+ aria-selected={active}
465
+ aria-label={label}
466
+ className={cn(
467
+ "relative h-14 w-20 shrink-0 overflow-hidden rounded-md border bg-background transition-all",
468
+ active
469
+ ? "border-primary-border shadow-sm ring-2 ring-primary-border"
470
+ : "border-border opacity-70 hover:border-muted-foreground hover:opacity-100",
471
+ className
472
+ )}
473
+ onClick={(event) => {
474
+ scrollTo(index)
475
+ onClick?.(event)
476
+ }}
477
+ {...props}
478
+ >
479
+ {children}
480
+ </button>
481
+ </TooltipTrigger>
482
+ <TooltipContent>{label}</TooltipContent>
483
+ </Tooltip>
484
+ )
485
+ })
486
+ CarouselThumbnail.displayName = "CarouselThumbnail"
487
+
488
+ const CarouselAutoplayToggle = React.forwardRef<
489
+ HTMLButtonElement,
490
+ Omit<React.ComponentProps<typeof TooltipButton>, "tooltip"> & {
491
+ tooltip?: React.ReactNode
492
+ playLabel?: string
493
+ pauseLabel?: string
494
+ }
495
+ >(
496
+ (
497
+ {
498
+ playLabel = "Play carousel",
499
+ pauseLabel = "Pause carousel",
500
+ tooltip,
501
+ variant = "secondary",
502
+ size = "icon",
503
+ className,
504
+ ...props
505
+ },
506
+ ref
507
+ ) => {
508
+ const { isAutoPlaying, setIsAutoPlaying } = useCarousel()
509
+ const label = isAutoPlaying ? pauseLabel : playLabel
510
+
511
+ return (
512
+ <TooltipButton
513
+ ref={ref}
514
+ type="button"
515
+ variant={variant}
516
+ size={size}
517
+ tooltip={tooltip ?? label}
518
+ aria-label={label}
519
+ aria-pressed={isAutoPlaying}
520
+ className={cn("h-9 w-9 rounded-full", className)}
521
+ onClick={() => setIsAutoPlaying((value) => !value)}
522
+ {...props}
523
+ >
524
+ <Icon icon={isAutoPlaying ? PlayerPause : PlayerPlay} size="sm" />
525
+ <span className="sr-only">{label}</span>
526
+ </TooltipButton>
527
+ )
528
+ }
529
+ )
530
+ CarouselAutoplayToggle.displayName = "CarouselAutoplayToggle"
531
+
532
+ function CarouselDefaultControls({ controls }: { controls: true | CarouselControlOptions }) {
533
+ const { autoPlay } = useCarousel()
534
+ const config = controls === true ? {} : controls
535
+ const showNavigation = controls === true ? true : config.navigation === true
536
+ const showDots = controls === true ? true : config.dots === true
537
+ const showAutoplayToggle = controls === true ? Boolean(autoPlay) : config.autoplayToggle === true
538
+
539
+ if (!showNavigation && !showDots && !showAutoplayToggle) {
540
+ return null
541
+ }
542
+
543
+ return (
544
+ <>
545
+ {showNavigation ? (
546
+ <>
547
+ <CarouselPrevious
548
+ label={config.labels?.previous}
549
+ hideWhenDisabled={config.hideNavigationWhenDisabled}
550
+ className={config.previousClassName}
551
+ />
552
+ <CarouselNext
553
+ label={config.labels?.next}
554
+ hideWhenDisabled={config.hideNavigationWhenDisabled}
555
+ className={config.nextClassName}
556
+ />
557
+ </>
558
+ ) : null}
559
+ {showDots || showAutoplayToggle ? (
560
+ <div className={cn("mt-4 flex items-center justify-center gap-3", config.className)}>
561
+ {showDots ? (
562
+ <CarouselDots
563
+ label={config.labels?.dots}
564
+ getDotLabel={config.labels?.getDotLabel}
565
+ className={config.dotsClassName}
566
+ />
567
+ ) : null}
568
+ {showAutoplayToggle ? (
569
+ <CarouselAutoplayToggle
570
+ playLabel={config.labels?.play}
571
+ pauseLabel={config.labels?.pause}
572
+ className={config.autoplayToggleClassName}
573
+ />
574
+ ) : null}
575
+ </div>
576
+ ) : null}
577
+ </>
578
+ )
579
+ }
580
+
581
+ export {
582
+ type CarouselApi,
583
+ type CarouselControlOptions,
584
+ Carousel,
585
+ CarouselContent,
586
+ CarouselItem,
587
+ CarouselPrevious,
588
+ CarouselNext,
589
+ CarouselDots,
590
+ CarouselThumbnails,
591
+ CarouselThumbnail,
592
+ CarouselAutoplayToggle,
593
+ }