@gunjo/ui 0.0.1-alpha.0 → 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,257 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import type { ChartDataPoint } from "./chart-utils"
7
+ import {
8
+ chartLabelToString,
9
+ defaultChartValueFormatter,
10
+ } from "./chart-utils"
11
+ import { ChartLegend } from "./ChartLegend"
12
+ import { ChartFloatingTooltip } from "./chart-tooltip"
13
+ import {
14
+ buildActiveCircularSegmentGradient,
15
+ buildConicGradient,
16
+ buildLegendItems,
17
+ getCircularSegmentAtPercent,
18
+ getCircularSegmentPosition,
19
+ getCircularSegmentShare,
20
+ type NormalizedCircularSegment,
21
+ normalizeCircularSegments,
22
+ } from "./circular-chart-utils"
23
+ import type { DonutChartVariantKey } from "./generated/variant-keys"
24
+ import { donutChartDefaultVariantKey } from "./generated/default-variant-keys"
25
+
26
+ export interface DonutChartProps extends React.HTMLAttributes<HTMLDivElement> {
27
+ segments: ChartDataPoint[]
28
+ variant?: DonutChartVariantKey
29
+ centerLabel?: React.ReactNode
30
+ centerValue?: React.ReactNode
31
+ thickness?: number
32
+ showLegend?: boolean
33
+ formatValue?: (value: number) => React.ReactNode
34
+ totalLabel?: React.ReactNode
35
+ }
36
+
37
+ const donutChartVariantClasses: Record<DonutChartVariantKey, string> = {
38
+ compact: "min-h-[144px] w-full p-0",
39
+ default: "min-h-[192px] w-full p-0",
40
+ }
41
+
42
+ const donutChartSizeClasses: Record<DonutChartVariantKey, string> = {
43
+ compact: "max-w-36",
44
+ default: "max-w-48",
45
+ }
46
+
47
+ const DonutChart = React.forwardRef<HTMLDivElement, DonutChartProps>(
48
+ (
49
+ {
50
+ className,
51
+ segments,
52
+ variant = donutChartDefaultVariantKey,
53
+ centerLabel,
54
+ centerValue,
55
+ thickness = variant === "compact" ? 18 : 24,
56
+ showLegend = false,
57
+ formatValue = defaultChartValueFormatter,
58
+ totalLabel = "Total",
59
+ ...props
60
+ },
61
+ ref
62
+ ) => {
63
+ const normalizedSegments = React.useMemo(
64
+ () => normalizeCircularSegments(segments),
65
+ [segments]
66
+ )
67
+ const background = buildConicGradient(normalizedSegments)
68
+ const legendItems = buildLegendItems(segments, formatValue, totalLabel)
69
+ const [activeSegment, setActiveSegment] = React.useState<
70
+ NormalizedCircularSegment | undefined
71
+ >(
72
+ normalizedSegments[0]
73
+ )
74
+ const [tooltipOpen, setTooltipOpen] = React.useState(false)
75
+ const [tooltipPosition, setTooltipPosition] = React.useState({
76
+ x: 50,
77
+ y: 18,
78
+ })
79
+ const chartRef = React.useRef<HTMLDivElement | null>(null)
80
+ const touchTooltipStickyRef = React.useRef(false)
81
+ const tooltipSegment = activeSegment ?? normalizedSegments[0]
82
+ const tooltipShare = getCircularSegmentShare(tooltipSegment)
83
+ const tooltipShareLabel =
84
+ tooltipShare !== undefined
85
+ ? `${defaultChartValueFormatter(tooltipShare)}%`
86
+ : undefined
87
+ const activeOverlay = tooltipOpen
88
+ ? buildActiveCircularSegmentGradient(tooltipSegment)
89
+ : undefined
90
+ const activeIndex = Math.max(
91
+ 0,
92
+ normalizedSegments.findIndex(
93
+ (segment) =>
94
+ segment === tooltipSegment ||
95
+ segment.label === tooltipSegment?.label
96
+ )
97
+ )
98
+
99
+ React.useEffect(() => {
100
+ setActiveSegment(normalizedSegments[0])
101
+ setTooltipPosition(getCircularSegmentPosition(normalizedSegments[0]))
102
+ }, [normalizedSegments])
103
+
104
+ const updateTooltipAtPoint = (
105
+ element: HTMLDivElement,
106
+ clientX: number,
107
+ clientY: number
108
+ ) => {
109
+ const rect = element.getBoundingClientRect()
110
+ const radius = rect.width / 2
111
+ const centerX = rect.left + radius
112
+ const centerY = rect.top + radius
113
+ const dx = clientX - centerX
114
+ const dy = clientY - centerY
115
+ const distance = Math.sqrt(dx * dx + dy * dy)
116
+ const innerRadius = Math.max(radius - thickness, 0)
117
+
118
+ if (distance < innerRadius || distance > radius) {
119
+ setTooltipOpen(false)
120
+ return
121
+ }
122
+
123
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI
124
+ const percent = ((((angle + 90) % 360) + 360) % 360) / 360 * 100
125
+ setActiveSegment(getCircularSegmentAtPercent(normalizedSegments, percent))
126
+ setTooltipPosition({
127
+ x: Math.min(100, Math.max(0, ((clientX - rect.left) / rect.width) * 100)),
128
+ y: Math.min(100, Math.max(0, ((clientY - rect.top) / rect.height) * 100)),
129
+ })
130
+ setTooltipOpen(true)
131
+ }
132
+ const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
133
+ if (event.pointerType !== "touch") {
134
+ touchTooltipStickyRef.current = false
135
+ }
136
+ updateTooltipAtPoint(event.currentTarget, event.clientX, event.clientY)
137
+ }
138
+ const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
139
+ touchTooltipStickyRef.current = event.pointerType === "touch"
140
+ if (event.pointerType === "touch") {
141
+ event.preventDefault()
142
+ }
143
+ event.currentTarget.focus({ preventScroll: true })
144
+ updateTooltipAtPoint(event.currentTarget, event.clientX, event.clientY)
145
+ }
146
+ const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {
147
+ const touch = event.touches[0]
148
+ if (!touch) return
149
+ touchTooltipStickyRef.current = true
150
+ event.preventDefault()
151
+ event.currentTarget.focus({ preventScroll: true })
152
+ updateTooltipAtPoint(event.currentTarget, touch.clientX, touch.clientY)
153
+ }
154
+
155
+ return (
156
+ <div
157
+ ref={ref}
158
+ className={cn(
159
+ donutChartVariantClasses[variant],
160
+ "flex flex-col items-center justify-center gap-3",
161
+ className
162
+ )}
163
+ {...props}
164
+ >
165
+ <div
166
+ ref={chartRef}
167
+ className={cn(
168
+ "relative aspect-square w-full rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
169
+ donutChartSizeClasses[variant]
170
+ )}
171
+ style={{ background }}
172
+ role="img"
173
+ aria-label={
174
+ tooltipSegment
175
+ ? `${chartLabelToString(tooltipSegment.label)}: ${formatValue(tooltipSegment.value)}${
176
+ tooltipShareLabel ? ` (${tooltipShareLabel})` : ""
177
+ }`
178
+ : props["aria-label"]
179
+ }
180
+ tabIndex={0}
181
+ onFocus={() => {
182
+ setActiveSegment(normalizedSegments[0])
183
+ setTooltipPosition(getCircularSegmentPosition(normalizedSegments[0]))
184
+ setTooltipOpen(true)
185
+ }}
186
+ onBlur={() => {
187
+ touchTooltipStickyRef.current = false
188
+ setTooltipOpen(false)
189
+ }}
190
+ onPointerDown={handlePointerDown}
191
+ onPointerMove={handlePointerMove}
192
+ onTouchStart={handleTouchStart}
193
+ onPointerLeave={() => {
194
+ if (touchTooltipStickyRef.current) return
195
+ setActiveSegment(normalizedSegments[0])
196
+ setTooltipPosition(getCircularSegmentPosition(normalizedSegments[0]))
197
+ setTooltipOpen(false)
198
+ }}
199
+ onPointerCancel={() => {
200
+ touchTooltipStickyRef.current = false
201
+ setTooltipOpen(false)
202
+ }}
203
+ >
204
+ {activeOverlay ? (
205
+ <span
206
+ className="pointer-events-none absolute inset-0 rounded-full mix-blend-multiply dark:mix-blend-screen"
207
+ style={{
208
+ background: activeOverlay,
209
+ filter: "drop-shadow(0 0 8px hsl(var(--foreground) / 0.32)) drop-shadow(0 0 14px hsl(var(--ring) / 0.26))",
210
+ }}
211
+ data-chart-active-overlay="true"
212
+ aria-hidden="true"
213
+ />
214
+ ) : null}
215
+ <div
216
+ className="absolute rounded-full bg-card"
217
+ style={{ inset: thickness }}
218
+ aria-hidden="true"
219
+ />
220
+ {centerLabel !== undefined || centerValue !== undefined ? (
221
+ <div className="absolute inset-0 flex flex-col items-center justify-center px-4 text-center">
222
+ {centerValue !== undefined ? (
223
+ <span className="text-2xl font-semibold tracking-tight">
224
+ {centerValue}
225
+ </span>
226
+ ) : null}
227
+ {centerLabel !== undefined ? (
228
+ <span className="text-xs text-muted-foreground">
229
+ {centerLabel}
230
+ </span>
231
+ ) : null}
232
+ </div>
233
+ ) : null}
234
+ <ChartFloatingTooltip
235
+ label={tooltipSegment?.label}
236
+ value={
237
+ tooltipSegment
238
+ ? formatValue(tooltipSegment.value)
239
+ : undefined
240
+ }
241
+ description={tooltipShareLabel}
242
+ position={tooltipPosition}
243
+ open={tooltipOpen}
244
+ anchorRef={chartRef}
245
+ onOpenChange={setTooltipOpen}
246
+ />
247
+ </div>
248
+ {showLegend ? (
249
+ <ChartLegend items={legendItems} activeIndex={activeIndex} />
250
+ ) : null}
251
+ </div>
252
+ )
253
+ }
254
+ )
255
+ DonutChart.displayName = "DonutChart"
256
+
257
+ export { DonutChart }
@@ -0,0 +1,44 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ export interface EmptyStateProps
6
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
7
+ icon?: React.ReactNode
8
+ title: React.ReactNode
9
+ description?: React.ReactNode
10
+ action?: React.ReactNode
11
+ }
12
+
13
+ const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(
14
+ (
15
+ { className, icon, title, description, action, children, ...props },
16
+ ref
17
+ ) => (
18
+ <div
19
+ ref={ref}
20
+ className={cn(
21
+ "flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-muted/40 px-6 py-10 text-center",
22
+ className
23
+ )}
24
+ {...props}
25
+ >
26
+ {icon ? (
27
+ <div className="flex h-12 w-12 items-center justify-center rounded-full bg-muted text-muted-foreground">
28
+ {icon}
29
+ </div>
30
+ ) : null}
31
+ <div className="space-y-1">
32
+ <p className="text-sm font-semibold text-foreground">{title}</p>
33
+ {description ? (
34
+ <p className="text-sm text-muted-foreground">{description}</p>
35
+ ) : null}
36
+ </div>
37
+ {action ? <div className="mt-2">{action}</div> : null}
38
+ {children}
39
+ </div>
40
+ )
41
+ )
42
+ EmptyState.displayName = "EmptyState"
43
+
44
+ export { EmptyState }
@@ -0,0 +1,180 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ IconFile as FileIcon,
6
+ IconFolder as FolderIcon,
7
+ } from "@tabler/icons-react"
8
+
9
+ import { cn } from "../../lib/utils"
10
+ import { TreeView, type TreeNode, type TreeViewProps } from "./TreeView"
11
+ import { fileTreeDefaultVariantKey } from "./generated/default-variant-keys"
12
+ import type { FileTreeVariantKey } from "./generated/variant-keys"
13
+
14
+ const fileTreeVariantClasses: Record<FileTreeVariantKey, string> = {
15
+ single: "p-0",
16
+ multiple: "p-0",
17
+ actions: "p-0",
18
+ }
19
+
20
+ const fileTreeVariantSelectionModes: Record<FileTreeVariantKey, "single" | "multiple" | "none"> = {
21
+ single: "single",
22
+ multiple: "multiple",
23
+ actions: "single",
24
+ }
25
+
26
+ export interface FileTreeNode {
27
+ id: string
28
+ label: React.ReactNode
29
+ type?: "file" | "folder"
30
+ icon?: React.ReactNode
31
+ children?: FileTreeNode[]
32
+ /** Supplemental row metadata, such as a file size, item count, or sync state. */
33
+ meta?: React.ReactNode
34
+ /** Convenience value for file size metadata. Used when meta is not provided. */
35
+ size?: React.ReactNode
36
+ /** Convenience value for folder count metadata. Used when meta and size are not provided. */
37
+ count?: React.ReactNode
38
+ }
39
+
40
+ export interface FileTreeProps
41
+ extends Omit<
42
+ TreeViewProps,
43
+ | "nodes"
44
+ | "selectedId"
45
+ | "selectedIds"
46
+ | "selectionMode"
47
+ | "onSelectedIdChange"
48
+ | "renderNodeMeta"
49
+ | "renderNodeActions"
50
+ | "getNodeRowProps"
51
+ > {
52
+ nodes: FileTreeNode[]
53
+ variant?: FileTreeVariantKey
54
+ selectionMode?: "single" | "multiple" | "none"
55
+ selectedIds?: Iterable<string>
56
+ defaultSelectedIds?: string[]
57
+ onSelectedIdsChange?: (ids: string[], node: FileTreeNode) => void
58
+ onNodeSelect?: (node: FileTreeNode) => void
59
+ renderNodeMeta?: (node: FileTreeNode) => React.ReactNode
60
+ renderNodeActions?: (node: FileTreeNode) => React.ReactNode
61
+ getNodeRowProps?: (node: FileTreeNode) => React.HTMLAttributes<HTMLDivElement> | undefined
62
+ }
63
+
64
+ function isFolderNode(node: FileTreeNode) {
65
+ return node.type === "folder" || Boolean(node.children?.length)
66
+ }
67
+
68
+ function defaultIconForNode(node: FileTreeNode) {
69
+ return isFolderNode(node) ? (
70
+ <FolderIcon className="h-4 w-4" aria-hidden="true" />
71
+ ) : (
72
+ <FileIcon className="h-4 w-4" aria-hidden="true" />
73
+ )
74
+ }
75
+
76
+ function toTreeNode(node: FileTreeNode): TreeNode {
77
+ return {
78
+ id: node.id,
79
+ label: node.label,
80
+ icon: node.icon ?? defaultIconForNode(node),
81
+ children: node.children?.map(toTreeNode),
82
+ }
83
+ }
84
+
85
+ function collectNodeMap(nodes: FileTreeNode[], map = new Map<string, FileTreeNode>()) {
86
+ for (const node of nodes) {
87
+ map.set(node.id, node)
88
+ if (node.children) collectNodeMap(node.children, map)
89
+ }
90
+ return map
91
+ }
92
+
93
+ function defaultMetaForNode(node: FileTreeNode) {
94
+ if (node.meta !== undefined && node.meta !== null) return node.meta
95
+ if (node.size !== undefined && node.size !== null) return node.size
96
+ if (node.count !== undefined && node.count !== null) return node.count
97
+ return null
98
+ }
99
+
100
+ const FileTree = React.forwardRef<HTMLUListElement, FileTreeProps>(
101
+ (
102
+ {
103
+ nodes,
104
+ variant = fileTreeDefaultVariantKey,
105
+ selectionMode,
106
+ selectedIds,
107
+ defaultSelectedIds,
108
+ onSelectedIdsChange,
109
+ onNodeSelect,
110
+ renderNodeMeta,
111
+ renderNodeActions,
112
+ getNodeRowProps,
113
+ className,
114
+ ...props
115
+ },
116
+ ref
117
+ ) => {
118
+ const [internalSelectedIds, setInternalSelectedIds] = React.useState<string[]>(
119
+ () => defaultSelectedIds ?? []
120
+ )
121
+ const resolvedSelectionMode = selectionMode ?? fileTreeVariantSelectionModes[variant]
122
+ const isControlled = selectedIds !== undefined
123
+ const selectedList = React.useMemo(
124
+ () => (selectedIds ? Array.from(selectedIds) : internalSelectedIds),
125
+ [internalSelectedIds, selectedIds]
126
+ )
127
+ const selectedSet = React.useMemo(() => new Set(selectedList), [selectedList])
128
+ const treeNodes = React.useMemo(() => nodes.map(toTreeNode), [nodes])
129
+ const nodeMap = React.useMemo(() => collectNodeMap(nodes), [nodes])
130
+
131
+ const updateSelection = (node: FileTreeNode) => {
132
+ if (resolvedSelectionMode === "none") {
133
+ onNodeSelect?.(node)
134
+ return
135
+ }
136
+
137
+ const nextIds =
138
+ resolvedSelectionMode === "multiple"
139
+ ? selectedSet.has(node.id)
140
+ ? selectedList.filter((id) => id !== node.id)
141
+ : [...selectedList, node.id]
142
+ : [node.id]
143
+
144
+ if (!isControlled) setInternalSelectedIds(nextIds)
145
+ onSelectedIdsChange?.(nextIds, node)
146
+ onNodeSelect?.(node)
147
+ }
148
+
149
+ return (
150
+ <TreeView
151
+ ref={ref}
152
+ className={cn("w-full p-0", fileTreeVariantClasses[variant], className)}
153
+ nodes={treeNodes}
154
+ selectedIds={resolvedSelectionMode === "none" ? undefined : selectedSet}
155
+ selectionMode={resolvedSelectionMode}
156
+ onSelectedIdChange={(id) => {
157
+ const node = nodeMap.get(id)
158
+ if (node) updateSelection(node)
159
+ }}
160
+ renderNodeMeta={(treeNode) => {
161
+ const node = nodeMap.get(treeNode.id)
162
+ if (!node) return null
163
+ return renderNodeMeta ? renderNodeMeta(node) : defaultMetaForNode(node)
164
+ }}
165
+ renderNodeActions={(treeNode) => {
166
+ const node = nodeMap.get(treeNode.id)
167
+ return node && renderNodeActions ? renderNodeActions(node) : null
168
+ }}
169
+ getNodeRowProps={(treeNode) => {
170
+ const node = nodeMap.get(treeNode.id)
171
+ return node && getNodeRowProps ? getNodeRowProps(node) : undefined
172
+ }}
173
+ {...props}
174
+ />
175
+ )
176
+ }
177
+ )
178
+ FileTree.displayName = "FileTree"
179
+
180
+ export { FileTree }