@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,350 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "./Card"
13
+ import type { ChartColor } from "./chart-utils"
14
+ import {
15
+ chartLabelToString,
16
+ clamp,
17
+ defaultChartValueFormatter,
18
+ getChartColor,
19
+ normalizeChartValue,
20
+ } from "./chart-utils"
21
+ import { ChartTooltip } from "./chart-tooltip"
22
+ import type { RetentionCohortCardVariantKey } from "./generated/variant-keys"
23
+ import { retentionCohortCardDefaultVariantKey } from "./generated/default-variant-keys"
24
+
25
+ export interface RetentionCohortCell {
26
+ value: number
27
+ color?: ChartColor
28
+ description?: React.ReactNode
29
+ }
30
+
31
+ export interface RetentionCohortRow {
32
+ label: React.ReactNode
33
+ size?: number
34
+ values: RetentionCohortCell[]
35
+ }
36
+
37
+ export interface RetentionCohortCardProps
38
+ extends Omit<React.ComponentPropsWithoutRef<typeof Card>, "title"> {
39
+ cohorts: RetentionCohortRow[]
40
+ periods: React.ReactNode[]
41
+ title?: React.ReactNode
42
+ description?: React.ReactNode
43
+ value?: React.ReactNode
44
+ delta?: React.ReactNode
45
+ deltaDescription?: React.ReactNode
46
+ caption?: React.ReactNode
47
+ variant?: RetentionCohortCardVariantKey
48
+ max?: number
49
+ color?: ChartColor
50
+ selectedCell?: { cohortIndex: number; periodIndex: number }
51
+ showValues?: boolean
52
+ formatValue?: (value: number) => React.ReactNode
53
+ formatSize?: (value: number) => React.ReactNode
54
+ onCellSelect?: (
55
+ cell: RetentionCohortCell,
56
+ selection: { cohortIndex: number; periodIndex: number }
57
+ ) => void
58
+ }
59
+
60
+ type RetentionCohortCardClassNames = {
61
+ card: string
62
+ header: string
63
+ content: string
64
+ cell: string
65
+ title: string
66
+ }
67
+
68
+ const variantClasses: Record<RetentionCohortCardVariantKey, RetentionCohortCardClassNames> = {
69
+ compact: {
70
+ card: "rounded-md",
71
+ header: "p-4 pb-3",
72
+ content: "px-4 pb-4",
73
+ cell: "min-h-6 rounded",
74
+ title: "text-sm",
75
+ },
76
+ default: {
77
+ card: "rounded-lg",
78
+ header: "p-5 pb-3",
79
+ content: "px-5 pb-5",
80
+ cell: "min-h-8 rounded-md",
81
+ title: "text-base",
82
+ },
83
+ }
84
+
85
+ function defaultPercentFormatter(value: number) {
86
+ return `${defaultChartValueFormatter(value)}%`
87
+ }
88
+
89
+ function defaultSizeFormatter(value: number) {
90
+ return defaultChartValueFormatter(value)
91
+ }
92
+
93
+ function getCellValue(cell: RetentionCohortCell | undefined) {
94
+ return Number.isFinite(cell?.value) ? Math.max(0, cell?.value ?? 0) : 0
95
+ }
96
+
97
+ function renderCohortLabel(label: React.ReactNode) {
98
+ if (typeof label !== "string") return label
99
+
100
+ const japaneseMatch = label.match(/^(.+?)(コホート)$/)
101
+ if (japaneseMatch) {
102
+ return (
103
+ <>
104
+ {japaneseMatch[1]}
105
+ <br className="sm:hidden" />
106
+ {japaneseMatch[2]}
107
+ </>
108
+ )
109
+ }
110
+
111
+ const englishMatch = label.match(/^(.+)\s+(cohort)$/i)
112
+ if (englishMatch) {
113
+ return (
114
+ <>
115
+ {englishMatch[1]}
116
+ <br className="sm:hidden" />
117
+ <span className="hidden sm:inline"> </span>
118
+ {englishMatch[2]}
119
+ </>
120
+ )
121
+ }
122
+
123
+ return label
124
+ }
125
+
126
+ const RetentionCohortCard = React.forwardRef<
127
+ HTMLDivElement,
128
+ RetentionCohortCardProps
129
+ >(
130
+ (
131
+ {
132
+ className,
133
+ cohorts,
134
+ periods,
135
+ title = "User retention",
136
+ description,
137
+ value,
138
+ delta,
139
+ deltaDescription,
140
+ caption,
141
+ variant = retentionCohortCardDefaultVariantKey,
142
+ max,
143
+ color = "warning",
144
+ selectedCell,
145
+ showValues = true,
146
+ formatValue = defaultPercentFormatter,
147
+ formatSize = defaultSizeFormatter,
148
+ onCellSelect,
149
+ ...props
150
+ },
151
+ ref
152
+ ) => {
153
+ const styles = variantClasses[variant]
154
+ const values = cohorts.flatMap((cohort) =>
155
+ cohort.values.map((cell) => getCellValue(cell))
156
+ )
157
+ const maxValue = Math.max(max ?? 0, ...values, 1)
158
+ const canSelectCells = typeof onCellSelect === "function"
159
+
160
+ const renderCell = (
161
+ cell: RetentionCohortCell | undefined,
162
+ cohort: RetentionCohortRow,
163
+ cohortIndex: number,
164
+ period: React.ReactNode,
165
+ periodIndex: number
166
+ ) => {
167
+ const hasCell = cell !== undefined
168
+ const rawValue = getCellValue(cell)
169
+ const percent = normalizeChartValue(rawValue, maxValue)
170
+ const opacity = 0.08 + (clamp(percent, 0, 100) / 100) * 0.92
171
+ const activeColor = getChartColor(cell?.color ?? color, periodIndex)
172
+ const isSelected =
173
+ selectedCell?.cohortIndex === cohortIndex &&
174
+ selectedCell?.periodIndex === periodIndex
175
+ const label = `${chartLabelToString(cohort.label, "Cohort")} · ${chartLabelToString(period, "Period")}`
176
+ const sizeLabel =
177
+ cohort.size === undefined
178
+ ? undefined
179
+ : ["Cohort size: ", formatSize(cohort.size)]
180
+
181
+ if (!hasCell) {
182
+ return (
183
+ <span
184
+ key={`${cohortIndex}-${periodIndex}`}
185
+ aria-hidden="true"
186
+ className={cn("block bg-transparent", styles.cell)}
187
+ />
188
+ )
189
+ }
190
+
191
+ const cellClassName = cn(
192
+ "relative block overflow-hidden bg-muted text-center font-medium tabular-nums text-foreground",
193
+ "w-full min-w-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
194
+ canSelectCells &&
195
+ "cursor-pointer border-0 p-0 transition-transform hover:scale-[1.04]",
196
+ !canSelectCells && "cursor-default",
197
+ styles.cell,
198
+ "text-[9px] sm:text-[10px]",
199
+ isSelected &&
200
+ "z-10 ring-2 ring-foreground ring-offset-1 ring-offset-background"
201
+ )
202
+ const cellContent = (
203
+ <>
204
+ <span
205
+ className="absolute inset-0"
206
+ style={{
207
+ backgroundColor: activeColor,
208
+ opacity,
209
+ }}
210
+ aria-hidden="true"
211
+ />
212
+ {showValues ? (
213
+ <span
214
+ className="absolute inset-0 z-10 flex items-center justify-center px-0.5 text-center text-foreground"
215
+ >
216
+ <span className="max-w-full truncate rounded-[3px] bg-background/75 px-1 shadow-sm">
217
+ {formatValue(rawValue)}
218
+ </span>
219
+ </span>
220
+ ) : null}
221
+ </>
222
+ )
223
+
224
+ return (
225
+ <ChartTooltip
226
+ key={`${cohortIndex}-${periodIndex}`}
227
+ label={label}
228
+ value={formatValue(rawValue)}
229
+ description={cell?.description ?? sizeLabel}
230
+ >
231
+ {canSelectCells ? (
232
+ <button
233
+ type="button"
234
+ className={cellClassName}
235
+ aria-current={isSelected ? "true" : undefined}
236
+ aria-pressed={isSelected}
237
+ aria-label={`${label}: ${chartLabelToString(formatValue(rawValue), "Value")}`}
238
+ onClick={() =>
239
+ onCellSelect?.(cell, {
240
+ cohortIndex,
241
+ periodIndex,
242
+ })
243
+ }
244
+ >
245
+ {cellContent}
246
+ </button>
247
+ ) : (
248
+ <span
249
+ className={cellClassName}
250
+ tabIndex={0}
251
+ aria-current={isSelected ? "true" : undefined}
252
+ aria-label={`${label}: ${chartLabelToString(formatValue(rawValue), "Value")}`}
253
+ >
254
+ {cellContent}
255
+ </span>
256
+ )}
257
+ </ChartTooltip>
258
+ )
259
+ }
260
+
261
+ return (
262
+ <Card
263
+ ref={ref}
264
+ className={cn("w-full min-w-0 overflow-hidden p-0", styles.card, className)}
265
+ {...props}
266
+ >
267
+ <CardHeader className={styles.header}>
268
+ <div className="flex min-w-0 items-start justify-between gap-3">
269
+ <div className="min-w-0 space-y-1">
270
+ <CardTitle className={cn("truncate", styles.title)}>
271
+ {title}
272
+ </CardTitle>
273
+ {description ? (
274
+ <CardDescription className="text-xs">
275
+ {description}
276
+ </CardDescription>
277
+ ) : null}
278
+ </div>
279
+ {value || delta ? (
280
+ <div className="shrink-0 text-right">
281
+ {value ? (
282
+ <div className="text-2xl font-semibold leading-none tabular-nums">
283
+ {value}
284
+ </div>
285
+ ) : null}
286
+ {delta ? (
287
+ <div
288
+ className="mt-1 text-xs font-medium text-success"
289
+ title={chartLabelToString(
290
+ deltaDescription,
291
+ "Delta description"
292
+ )}
293
+ >
294
+ {delta}
295
+ </div>
296
+ ) : null}
297
+ </div>
298
+ ) : null}
299
+ </div>
300
+ </CardHeader>
301
+ <CardContent className={cn("space-y-3", styles.content)}>
302
+ <div className="min-w-0 overflow-x-auto overflow-y-hidden pb-1">
303
+ <div
304
+ className="grid min-w-[34rem] grid-cols-[minmax(4.75rem,0.9fr)_repeat(var(--retention-period-count),minmax(2.35rem,1fr))] items-center gap-1 sm:min-w-0 sm:grid-cols-[minmax(3.5rem,1.15fr)_repeat(var(--retention-period-count),minmax(1.45rem,1fr))]"
305
+ style={{
306
+ "--retention-period-count": periods.length,
307
+ } as React.CSSProperties}
308
+ >
309
+ <span aria-hidden="true" />
310
+ {periods.map((period, index) => (
311
+ <span
312
+ key={`${chartLabelToString(period, "Period")}-${index}`}
313
+ className="truncate text-center text-[10px] text-muted-foreground sm:text-xs"
314
+ >
315
+ {period}
316
+ </span>
317
+ ))}
318
+ {cohorts.map((cohort, cohortIndex) => (
319
+ <React.Fragment
320
+ key={`${chartLabelToString(cohort.label, "Cohort")}-${cohortIndex}`}
321
+ >
322
+ <span className="min-w-0 whitespace-normal break-keep pr-1 text-[10px] leading-tight text-muted-foreground sm:truncate sm:text-xs">
323
+ {renderCohortLabel(cohort.label)}
324
+ </span>
325
+ {periods.map((period, periodIndex) =>
326
+ renderCell(
327
+ cohort.values[periodIndex],
328
+ cohort,
329
+ cohortIndex,
330
+ period,
331
+ periodIndex
332
+ )
333
+ )}
334
+ </React.Fragment>
335
+ ))}
336
+ </div>
337
+ </div>
338
+ {caption ? (
339
+ <div className="rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
340
+ {caption}
341
+ </div>
342
+ ) : null}
343
+ </CardContent>
344
+ </Card>
345
+ )
346
+ }
347
+ )
348
+ RetentionCohortCard.displayName = "RetentionCohortCard"
349
+
350
+ export { RetentionCohortCard }