@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,522 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ type Column,
6
+ type ColumnDef,
7
+ type ColumnFiltersState,
8
+ type SortingState,
9
+ flexRender,
10
+ getCoreRowModel,
11
+ getFilteredRowModel,
12
+ getPaginationRowModel,
13
+ getSortedRowModel,
14
+ useReactTable,
15
+ } from "@tanstack/react-table"
16
+ import {
17
+ IconArrowsSort as ArrowUpDown,
18
+ IconChevronLeft as ChevronLeft,
19
+ IconChevronLeftPipe as ChevronLeftPipe,
20
+ IconChevronRight as ChevronRight,
21
+ IconChevronRightPipe as ChevronRightPipe,
22
+ IconDots as Dots,
23
+ IconSortAscending,
24
+ IconSortDescending,
25
+ } from "@tabler/icons-react";
26
+
27
+ import { cn } from "../../lib/utils"
28
+ import { Button } from "../inputs/Button"
29
+ import { Input } from "../inputs/Input"
30
+ import { Select } from "../inputs/Select"
31
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
32
+ import { Icon, type IconGlyph } from "./Icon"
33
+
34
+ export interface DataTableLabels {
35
+ filterPlaceholder?: string
36
+ noResults?: string
37
+ page?: string
38
+ of?: string
39
+ previous?: string
40
+ next?: string
41
+ firstPage?: string
42
+ lastPage?: string
43
+ firstPageUnavailable?: string
44
+ previousPageUnavailable?: string
45
+ nextPageUnavailable?: string
46
+ lastPageUnavailable?: string
47
+ rowsPerPage?: string
48
+ pageSizeOption?: (pageSize: number) => string
49
+ paginationSummary?: (from: number, to: number, total: number) => string
50
+ pageSummary?: (currentPage: number, totalPages: number) => string
51
+ pageSelect?: string
52
+ pageOption?: (page: number, totalPages: number) => string
53
+ goToPage?: (page: number) => string
54
+ sortAscending?: string
55
+ sortDescending?: string
56
+ clearSort?: string
57
+ sortUnsorted?: string
58
+ sortCurrentAscending?: string
59
+ sortCurrentDescending?: string
60
+ }
61
+
62
+ export interface DataTableProps<TData, TValue> {
63
+ columns: ColumnDef<TData, TValue>[]
64
+ data: TData[]
65
+ /** Optional: column id to filter by + placeholder for the filter input. Pass null to disable. */
66
+ filter?: { columnId: string; placeholder?: string } | null
67
+ /** Page size for pagination. Default 10. */
68
+ pageSize?: number
69
+ /** Selectable page sizes. Default [10, 25, 50, 100, 200]. */
70
+ pageSizeOptions?: number[]
71
+ labels?: DataTableLabels
72
+ /** Optional actions rendered directly under the summary row in the table header area. */
73
+ headerActions?: React.ReactNode
74
+ /** Optional row state used for styling composed tables. */
75
+ getRowState?: (row: TData, index: number) => string | undefined
76
+ className?: string
77
+ }
78
+
79
+ type PageItem = number | "ellipsis-start" | "ellipsis-end"
80
+
81
+ function getColumnWidth<TData, TValue>(column: Column<TData, TValue>) {
82
+ const size = column.getSize()
83
+ return Number.isFinite(size) && size !== 150 ? `${size}px` : undefined
84
+ }
85
+
86
+ function getActionColumnInteractionClass(columnId: string) {
87
+ if (columnId !== "__action_row_actions") return null
88
+
89
+ return cn(
90
+ "group-hover:sticky group-hover:right-0 group-hover:z-10 group-hover:border-l group-hover:bg-background",
91
+ "group-data-[state=selected]:sticky group-data-[state=selected]:right-0 group-data-[state=selected]:z-10 group-data-[state=selected]:border-l group-data-[state=selected]:bg-muted"
92
+ )
93
+ }
94
+
95
+ function getPageItems(currentPage: number, pageCount: number): PageItem[] {
96
+ if (pageCount <= 7) {
97
+ return Array.from({ length: pageCount }, (_, index) => index + 1)
98
+ }
99
+
100
+ if (currentPage <= 4) {
101
+ return [1, 2, 3, 4, 5, "ellipsis-end", pageCount]
102
+ }
103
+
104
+ if (currentPage >= pageCount - 3) {
105
+ return [1, "ellipsis-start", pageCount - 4, pageCount - 3, pageCount - 2, pageCount - 1, pageCount]
106
+ }
107
+
108
+ return [1, "ellipsis-start", currentPage - 1, currentPage, currentPage + 1, "ellipsis-end", pageCount]
109
+ }
110
+
111
+ function PaginationIconButton({
112
+ label,
113
+ disabledLabel,
114
+ disabled,
115
+ icon,
116
+ onClick,
117
+ }: {
118
+ label: string
119
+ disabledLabel: string
120
+ disabled: boolean
121
+ icon: IconGlyph
122
+ onClick: () => void
123
+ }) {
124
+ return (
125
+ <Tooltip>
126
+ <TooltipTrigger asChild>
127
+ <span className="inline-flex">
128
+ <Button
129
+ type="button"
130
+ variant="outline"
131
+ size="icon"
132
+ className="h-8 w-8"
133
+ onClick={onClick}
134
+ disabled={disabled}
135
+ aria-label={label}
136
+ >
137
+ <Icon icon={icon} size="sm" />
138
+ </Button>
139
+ </span>
140
+ </TooltipTrigger>
141
+ <TooltipContent>{disabled ? disabledLabel : label}</TooltipContent>
142
+ </Tooltip>
143
+ )
144
+ }
145
+
146
+ export function DataTable<TData, TValue>({
147
+ columns,
148
+ data,
149
+ filter,
150
+ pageSize = 10,
151
+ pageSizeOptions = [10, 25, 50, 100, 200],
152
+ labels,
153
+ headerActions,
154
+ getRowState,
155
+ className,
156
+ }: DataTableProps<TData, TValue>) {
157
+ const [sorting, setSorting] = React.useState<SortingState>([])
158
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
159
+ []
160
+ )
161
+ const normalizedPageSizeOptions = React.useMemo(
162
+ () => Array.from(new Set([...pageSizeOptions, pageSize])).filter((value) => value > 0).sort((a, b) => a - b),
163
+ [pageSize, pageSizeOptions]
164
+ )
165
+
166
+ const table = useReactTable({
167
+ data,
168
+ columns,
169
+ onSortingChange: setSorting,
170
+ getCoreRowModel: getCoreRowModel(),
171
+ getSortedRowModel: getSortedRowModel(),
172
+ getFilteredRowModel: getFilteredRowModel(),
173
+ getPaginationRowModel: getPaginationRowModel(),
174
+ onColumnFiltersChange: setColumnFilters,
175
+ initialState: { pagination: { pageSize } },
176
+ state: { sorting, columnFilters },
177
+ })
178
+
179
+ const filterColumn =
180
+ filter !== null ? table.getColumn(filter?.columnId ?? "") : null
181
+ const pageIndex = table.getState().pagination.pageIndex
182
+ const currentPage = pageIndex + 1
183
+ const pageCount = table.getPageCount() || 1
184
+ const totalRows = table.getFilteredRowModel().rows.length
185
+ const visibleRows = table.getRowModel().rows.length
186
+ const rowFrom = totalRows === 0 ? 0 : pageIndex * table.getState().pagination.pageSize + 1
187
+ const rowTo = totalRows === 0 ? 0 : rowFrom + visibleRows - 1
188
+ const pageItems = React.useMemo(() => getPageItems(currentPage, pageCount), [currentPage, pageCount])
189
+ const visibleColumns = table.getVisibleLeafColumns()
190
+
191
+ React.useEffect(() => {
192
+ table.setPageSize(pageSize)
193
+ }, [pageSize, table])
194
+
195
+ React.useEffect(() => {
196
+ if (pageCount > 0 && pageIndex >= pageCount) {
197
+ table.setPageIndex(pageCount - 1)
198
+ }
199
+ }, [pageCount, pageIndex, table])
200
+
201
+ return (
202
+ <div className={cn("w-full space-y-4", className)}>
203
+ {filterColumn ? (
204
+ <div className="flex items-center">
205
+ <Input
206
+ placeholder={filter?.placeholder ?? labels?.filterPlaceholder ?? "Filter..."}
207
+ value={(filterColumn.getFilterValue() as string) ?? ""}
208
+ onChange={(event) =>
209
+ filterColumn.setFilterValue(event.target.value)
210
+ }
211
+ className="max-w-sm"
212
+ />
213
+ </div>
214
+ ) : null}
215
+
216
+ <div className="rounded-md border bg-muted/20 px-3 py-2">
217
+ <div className="flex items-center justify-between gap-2">
218
+ <div className="text-sm text-muted-foreground">
219
+ {labels?.paginationSummary
220
+ ? labels.paginationSummary(rowFrom, rowTo, totalRows)
221
+ : `${rowFrom} - ${rowTo} / ${totalRows}`}
222
+ </div>
223
+ <div className="flex shrink-0 items-center gap-x-3 gap-y-2">
224
+ <div className="hidden items-center gap-1 sm:flex">
225
+ <PaginationIconButton
226
+ label={labels?.firstPage ?? "First page"}
227
+ disabledLabel={labels?.firstPageUnavailable ?? "Already on the first page"}
228
+ disabled={!table.getCanPreviousPage()}
229
+ icon={ChevronLeftPipe}
230
+ onClick={() => table.setPageIndex(0)}
231
+ />
232
+ <PaginationIconButton
233
+ label={labels?.previous ?? "Previous"}
234
+ disabledLabel={labels?.previousPageUnavailable ?? "No previous page"}
235
+ disabled={!table.getCanPreviousPage()}
236
+ icon={ChevronLeft}
237
+ onClick={() => table.previousPage()}
238
+ />
239
+ <span className="min-w-20 px-2 text-center text-sm text-muted-foreground">
240
+ {labels?.pageSummary
241
+ ? labels.pageSummary(currentPage, pageCount)
242
+ : `${currentPage} / ${pageCount}`}
243
+ </span>
244
+ <PaginationIconButton
245
+ label={labels?.next ?? "Next"}
246
+ disabledLabel={labels?.nextPageUnavailable ?? "No next page"}
247
+ disabled={!table.getCanNextPage()}
248
+ icon={ChevronRight}
249
+ onClick={() => table.nextPage()}
250
+ />
251
+ <PaginationIconButton
252
+ label={labels?.lastPage ?? "Last page"}
253
+ disabledLabel={labels?.lastPageUnavailable ?? "Already on the last page"}
254
+ disabled={!table.getCanNextPage()}
255
+ icon={ChevronRightPipe}
256
+ onClick={() => table.setPageIndex(pageCount - 1)}
257
+ />
258
+ </div>
259
+ <label className="flex items-center text-sm text-muted-foreground">
260
+ <span className="sr-only">{labels?.rowsPerPage ?? "Rows per page"}</span>
261
+ <Select
262
+ aria-label={labels?.rowsPerPage ?? "Rows per page"}
263
+ value={String(table.getState().pagination.pageSize)}
264
+ onChange={(event) => {
265
+ table.setPageSize(Number(event.target.value))
266
+ table.setPageIndex(0)
267
+ }}
268
+ className="h-8 w-[88px] rounded-md py-1 text-sm"
269
+ >
270
+ {normalizedPageSizeOptions.map((option) => (
271
+ <option key={option} value={option}>
272
+ {labels?.pageSizeOption ? labels.pageSizeOption(option) : option}
273
+ </option>
274
+ ))}
275
+ </Select>
276
+ </label>
277
+ </div>
278
+ </div>
279
+ {headerActions ? (
280
+ <div className="mt-3 border-t pt-3">
281
+ {headerActions}
282
+ </div>
283
+ ) : null}
284
+ </div>
285
+
286
+ <div className="overflow-x-auto rounded-md border">
287
+ <table className="w-full min-w-[720px] table-fixed caption-bottom text-sm">
288
+ <colgroup>
289
+ {visibleColumns.map((column) => {
290
+ const width = getColumnWidth(column)
291
+ return <col key={column.id} style={width ? { width } : undefined} />
292
+ })}
293
+ </colgroup>
294
+ <thead className="border-b bg-muted/50 [&_tr]:border-b">
295
+ {table.getHeaderGroups().map((headerGroup) => (
296
+ <tr key={headerGroup.id} className="border-b">
297
+ {headerGroup.headers.map((header) => {
298
+ const canSort = header.column.getCanSort()
299
+ const sorted = header.column.getIsSorted()
300
+ const currentSortLabel =
301
+ sorted === "asc"
302
+ ? labels?.sortCurrentAscending ?? "Current: ascending"
303
+ : sorted === "desc"
304
+ ? labels?.sortCurrentDescending ?? "Current: descending"
305
+ : labels?.sortUnsorted ?? "Current: unsorted"
306
+ const nextSortLabel =
307
+ sorted === "asc"
308
+ ? labels?.sortDescending ?? "Click to sort descending"
309
+ : sorted === "desc"
310
+ ? labels?.clearSort ?? "Click to clear sorting"
311
+ : labels?.sortAscending ?? "Click to sort ascending"
312
+ const sortLabel = `${currentSortLabel}. ${nextSortLabel}`
313
+ const SortIcon =
314
+ sorted === "asc"
315
+ ? IconSortAscending
316
+ : sorted === "desc"
317
+ ? IconSortDescending
318
+ : ArrowUpDown
319
+ return (
320
+ <th
321
+ key={header.id}
322
+ className="h-10 whitespace-nowrap px-3 text-left align-middle font-medium text-muted-foreground"
323
+ >
324
+ {header.isPlaceholder ? null : canSort ? (
325
+ <Tooltip>
326
+ <TooltipTrigger asChild>
327
+ <button
328
+ type="button"
329
+ aria-label={sortLabel}
330
+ onClick={header.column.getToggleSortingHandler()}
331
+ className={cn(
332
+ "-ml-1 flex min-h-8 w-full min-w-0 items-center justify-between gap-1 rounded-md px-1 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
333
+ sorted && "bg-muted text-foreground"
334
+ )}
335
+ >
336
+ <span className="min-w-0 truncate">
337
+ {flexRender(
338
+ header.column.columnDef.header,
339
+ header.getContext()
340
+ )}
341
+ </span>
342
+ <span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
343
+ <Icon icon={SortIcon} size="xs" className={cn("opacity-50", sorted && "opacity-100")} />
344
+ </span>
345
+ </button>
346
+ </TooltipTrigger>
347
+ <TooltipContent>{sortLabel}</TooltipContent>
348
+ </Tooltip>
349
+ ) : (
350
+ flexRender(
351
+ header.column.columnDef.header,
352
+ header.getContext()
353
+ )
354
+ )}
355
+ </th>
356
+ )
357
+ })}
358
+ </tr>
359
+ ))}
360
+ </thead>
361
+ <tbody>
362
+ {table.getRowModel().rows?.length ? (
363
+ table.getRowModel().rows.map((row) => (
364
+ (() => {
365
+ const rowState = getRowState?.(row.original, row.index) ?? (row.getIsSelected() ? "selected" : undefined)
366
+ return (
367
+ <tr
368
+ key={row.id}
369
+ className="group border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted"
370
+ data-state={rowState}
371
+ >
372
+ {row.getVisibleCells().map((cell) => (
373
+ <td
374
+ key={cell.id}
375
+ className={cn(
376
+ "max-w-0 p-3 align-middle",
377
+ getActionColumnInteractionClass(cell.column.id)
378
+ )}
379
+ >
380
+ {flexRender(
381
+ cell.column.columnDef.cell,
382
+ cell.getContext()
383
+ )}
384
+ </td>
385
+ ))}
386
+ </tr>
387
+ )
388
+ })()
389
+ ))
390
+ ) : (
391
+ <tr>
392
+ <td
393
+ colSpan={columns.length}
394
+ className="h-24 px-3 text-left text-muted-foreground sm:text-center"
395
+ >
396
+ {labels?.noResults ?? "No results."}
397
+ </td>
398
+ </tr>
399
+ )}
400
+ </tbody>
401
+ </table>
402
+ </div>
403
+
404
+ <div className="grid w-full grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-2 sm:hidden">
405
+ <div className="flex items-center gap-1">
406
+ <PaginationIconButton
407
+ label={labels?.firstPage ?? "First page"}
408
+ disabledLabel={labels?.firstPageUnavailable ?? "Already on the first page"}
409
+ disabled={!table.getCanPreviousPage()}
410
+ icon={ChevronLeftPipe}
411
+ onClick={() => table.setPageIndex(0)}
412
+ />
413
+ <PaginationIconButton
414
+ label={labels?.previous ?? "Previous"}
415
+ disabledLabel={labels?.previousPageUnavailable ?? "No previous page"}
416
+ disabled={!table.getCanPreviousPage()}
417
+ icon={ChevronLeft}
418
+ onClick={() => table.previousPage()}
419
+ />
420
+ </div>
421
+ <div className="flex min-w-0 items-center justify-center sm:hidden">
422
+ {pageCount > 1 ? (
423
+ <div className="flex items-center gap-1 text-sm text-muted-foreground">
424
+ <Select
425
+ aria-label={labels?.pageSelect ?? "Go to page"}
426
+ value={String(currentPage)}
427
+ onChange={(event) => table.setPageIndex(Number(event.target.value) - 1)}
428
+ className="h-8 w-14 rounded-md py-1 text-center text-sm"
429
+ >
430
+ {Array.from({ length: pageCount }, (_, index) => index + 1).map((page) => (
431
+ <option key={page} value={page}>
432
+ {labels?.pageOption
433
+ ? labels.pageOption(page, pageCount)
434
+ : page.toLocaleString()}
435
+ </option>
436
+ ))}
437
+ </Select>
438
+ <span aria-hidden="true">/</span>
439
+ <span aria-hidden="true">{pageCount.toLocaleString()}</span>
440
+ </div>
441
+ ) : (
442
+ <span className="min-w-16 text-center text-sm text-muted-foreground">
443
+ {labels?.pageSummary
444
+ ? labels.pageSummary(currentPage, pageCount)
445
+ : `${currentPage} / ${pageCount}`}
446
+ </span>
447
+ )}
448
+ </div>
449
+ <div className="flex items-center justify-end gap-1">
450
+ <PaginationIconButton
451
+ label={labels?.next ?? "Next"}
452
+ disabledLabel={labels?.nextPageUnavailable ?? "No next page"}
453
+ disabled={!table.getCanNextPage()}
454
+ icon={ChevronRight}
455
+ onClick={() => table.nextPage()}
456
+ />
457
+ <PaginationIconButton
458
+ label={labels?.lastPage ?? "Last page"}
459
+ disabledLabel={labels?.lastPageUnavailable ?? "Already on the last page"}
460
+ disabled={!table.getCanNextPage()}
461
+ icon={ChevronRightPipe}
462
+ onClick={() => table.setPageIndex(pageCount - 1)}
463
+ />
464
+ </div>
465
+ </div>
466
+ <div className="hidden w-full items-center justify-center gap-1 sm:flex">
467
+ <PaginationIconButton
468
+ label={labels?.firstPage ?? "First page"}
469
+ disabledLabel={labels?.firstPageUnavailable ?? "Already on the first page"}
470
+ disabled={!table.getCanPreviousPage()}
471
+ icon={ChevronLeftPipe}
472
+ onClick={() => table.setPageIndex(0)}
473
+ />
474
+ <PaginationIconButton
475
+ label={labels?.previous ?? "Previous"}
476
+ disabledLabel={labels?.previousPageUnavailable ?? "No previous page"}
477
+ disabled={!table.getCanPreviousPage()}
478
+ icon={ChevronLeft}
479
+ onClick={() => table.previousPage()}
480
+ />
481
+ <div className="flex min-w-0 flex-wrap items-center justify-center gap-1 px-1">
482
+ {pageItems.map((item) => (
483
+ item === "ellipsis-start" || item === "ellipsis-end" ? (
484
+ <span key={item} className="flex h-8 w-8 items-center justify-center text-muted-foreground">
485
+ <Icon icon={Dots} size="sm" />
486
+ </span>
487
+ ) : (
488
+ <Button
489
+ key={item}
490
+ type="button"
491
+ variant={item === currentPage ? "default" : "ghost"}
492
+ size="icon"
493
+ className="h-8 w-8"
494
+ aria-label={labels?.goToPage?.(item) ?? `Go to page ${item}`}
495
+ aria-current={item === currentPage ? "page" : undefined}
496
+ onClick={() => table.setPageIndex(item - 1)}
497
+ >
498
+ {item}
499
+ </Button>
500
+ )
501
+ ))}
502
+ </div>
503
+ <PaginationIconButton
504
+ label={labels?.next ?? "Next"}
505
+ disabledLabel={labels?.nextPageUnavailable ?? "No next page"}
506
+ disabled={!table.getCanNextPage()}
507
+ icon={ChevronRight}
508
+ onClick={() => table.nextPage()}
509
+ />
510
+ <PaginationIconButton
511
+ label={labels?.lastPage ?? "Last page"}
512
+ disabledLabel={labels?.lastPageUnavailable ?? "Already on the last page"}
513
+ disabled={!table.getCanNextPage()}
514
+ icon={ChevronRightPipe}
515
+ onClick={() => table.setPageIndex(pageCount - 1)}
516
+ />
517
+ </div>
518
+ </div>
519
+ )
520
+ }
521
+
522
+ DataTable.displayName = "DataTable"
@@ -0,0 +1,102 @@
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
+ getChartColor,
11
+ normalizeChartValue,
12
+ } from "./chart-utils"
13
+ import { ChartLegend } from "./ChartLegend"
14
+ import { ChartTooltip } from "./chart-tooltip"
15
+
16
+ export interface DistributionBarProps extends React.HTMLAttributes<HTMLDivElement> {
17
+ segments: ChartDataPoint[]
18
+ showLegend?: boolean
19
+ formatValue?: (value: number) => React.ReactNode
20
+ totalLabel?: React.ReactNode
21
+ }
22
+
23
+ const baseClasses = "h-9 w-full overflow-hidden rounded-full border border-border/70 bg-muted p-0"
24
+
25
+ function getPositiveValue(value: number) {
26
+ return Number.isFinite(value) ? Math.max(value, 0) : 0
27
+ }
28
+
29
+ const DistributionBar = React.forwardRef<HTMLDivElement, DistributionBarProps>(
30
+ (
31
+ {
32
+ className,
33
+ segments,
34
+ showLegend = false,
35
+ formatValue = defaultChartValueFormatter,
36
+ totalLabel = "Total",
37
+ ...props
38
+ },
39
+ ref
40
+ ) => {
41
+ const total = Math.max(
42
+ segments.reduce((sum, segment) => sum + getPositiveValue(segment.value), 0),
43
+ 1
44
+ )
45
+ const legendItems = segments.map((segment, index) => {
46
+ const rawValue = getPositiveValue(segment.value)
47
+ const percent = normalizeChartValue(rawValue, total)
48
+
49
+ return {
50
+ label: segment.label,
51
+ value: `${defaultChartValueFormatter(percent)}%`,
52
+ color: segment.color ?? getChartColor(undefined, index),
53
+ description: [totalLabel, ": ", formatValue(segment.value)],
54
+ }
55
+ })
56
+
57
+ return (
58
+ <div ref={ref} className={cn("w-full space-y-3 p-0", className)} {...props}>
59
+ <div className={baseClasses}>
60
+ <div className="flex h-full w-full">
61
+ {segments.map((segment, index) => {
62
+ const percent = normalizeChartValue(
63
+ getPositiveValue(segment.value),
64
+ total
65
+ )
66
+ const percentText = `${defaultChartValueFormatter(percent)}%`
67
+
68
+ return (
69
+ <ChartTooltip
70
+ key={`${chartLabelToString(segment.label, "Segment")}-${index}`}
71
+ label={segment.label}
72
+ value={percentText}
73
+ description={[totalLabel, ": ", formatValue(segment.value)]}
74
+ >
75
+ <span
76
+ className="block h-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
77
+ style={{
78
+ width: `${percent}%`,
79
+ backgroundColor: getChartColor(segment.color, index),
80
+ boxShadow:
81
+ index < segments.length - 1
82
+ ? "inset -1px 0 0 hsl(var(--background) / 0.65)"
83
+ : undefined,
84
+ }}
85
+ tabIndex={0}
86
+ aria-label={`${chartLabelToString(segment.label)}: ${percentText} (${chartLabelToString(totalLabel, "Total")}: ${formatValue(segment.value)})`}
87
+ />
88
+ </ChartTooltip>
89
+ )
90
+ })}
91
+ </div>
92
+ </div>
93
+ {showLegend ? (
94
+ <ChartLegend items={legendItems} variant="horizontal" />
95
+ ) : null}
96
+ </div>
97
+ )
98
+ }
99
+ )
100
+ DistributionBar.displayName = "DistributionBar"
101
+
102
+ export { DistributionBar }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+ import { docNoteDefaultVariantKey } from "./generated/default-variant-keys"
5
+ import type { DocNoteVariantKey } from "./generated/variant-keys"
6
+
7
+ const docNoteVariantClasses: Record<DocNoteVariantKey, string> = {
8
+ default: "bg-muted/45 text-muted-foreground",
9
+ reference: "bg-secondary/55 text-muted-foreground",
10
+ }
11
+
12
+ export interface DocNoteProps extends React.HTMLAttributes<HTMLElement> {
13
+ heading?: React.ReactNode
14
+ variant?: DocNoteVariantKey
15
+ }
16
+
17
+ const DocNote = React.forwardRef<HTMLElement, DocNoteProps>(
18
+ ({ heading, children, className, variant = docNoteDefaultVariantKey, ...props }, ref) => (
19
+ <aside
20
+ ref={ref}
21
+ className={cn(
22
+ "inline-flex w-full flex-col items-center gap-1 rounded-md border-0 px-4 py-3 text-left text-sm leading-6 shadow-none [align-items:flex-start]",
23
+ "[&_a]:font-medium [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary",
24
+ docNoteVariantClasses[variant],
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {heading ? <div className="w-full font-medium text-foreground">{heading}</div> : null}
30
+ <div className="w-full">{children}</div>
31
+ </aside>
32
+ )
33
+ )
34
+ DocNote.displayName = "DocNote"
35
+
36
+ export { DocNote }