@postxl/generators 1.1.1 → 1.2.0

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 (161) hide show
  1. package/dist/frontend-core/frontend.generator.d.ts +0 -58
  2. package/dist/frontend-core/frontend.generator.js +6 -172
  3. package/dist/frontend-core/frontend.generator.js.map +1 -1
  4. package/dist/frontend-core/template/README.md +1 -1
  5. package/dist/frontend-core/template/src/components/admin/table-filter.tsx +1 -5
  6. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +10 -4
  7. package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +2 -3
  8. package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +1 -1
  9. package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +1 -1
  10. package/dist/frontend-core/template/src/styles/styles.css +13 -1
  11. package/dist/frontend-core/template/tsconfig.json +2 -0
  12. package/dist/frontend-core/types/component.d.ts +1 -1
  13. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js +4 -6
  14. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js.map +1 -1
  15. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js +1 -1
  16. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js.map +1 -1
  17. package/dist/frontend-forms/generators/enum/inputs.generator.js +1 -1
  18. package/dist/frontend-forms/generators/enum/inputs.generator.js.map +1 -1
  19. package/dist/frontend-forms/generators/model/forms.generator.js +8 -12
  20. package/dist/frontend-forms/generators/model/forms.generator.js.map +1 -1
  21. package/dist/frontend-forms/generators/model/inputs.generator.js +2 -6
  22. package/dist/frontend-forms/generators/model/inputs.generator.js.map +1 -1
  23. package/dist/frontend-forms/template/src/components/ui/field/field.tsx +1 -4
  24. package/dist/frontend-tables/generators/model-table.generator.js +1 -5
  25. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  26. package/package.json +3 -2
  27. package/dist/frontend-core/template/src/components/ui/accordion/accordion.stories.tsx +0 -47
  28. package/dist/frontend-core/template/src/components/ui/accordion/accordion.tsx +0 -52
  29. package/dist/frontend-core/template/src/components/ui/admin-sidebar/admin-sidebar.tsx +0 -195
  30. package/dist/frontend-core/template/src/components/ui/alert/alert.stories.tsx +0 -61
  31. package/dist/frontend-core/template/src/components/ui/alert/alert.tsx +0 -45
  32. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.stories.tsx +0 -52
  33. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.tsx +0 -105
  34. package/dist/frontend-core/template/src/components/ui/avatar/avatar.stories.tsx +0 -30
  35. package/dist/frontend-core/template/src/components/ui/avatar/avatar.tsx +0 -39
  36. package/dist/frontend-core/template/src/components/ui/badge/badge.stories.tsx +0 -78
  37. package/dist/frontend-core/template/src/components/ui/badge/badge.tsx +0 -48
  38. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.stories.tsx +0 -67
  39. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.tsx +0 -85
  40. package/dist/frontend-core/template/src/components/ui/button/button.stories.tsx +0 -150
  41. package/dist/frontend-core/template/src/components/ui/button/button.tsx +0 -68
  42. package/dist/frontend-core/template/src/components/ui/calendar/calendar.stories.tsx +0 -160
  43. package/dist/frontend-core/template/src/components/ui/calendar/calendar.tsx +0 -293
  44. package/dist/frontend-core/template/src/components/ui/card/card.stories.tsx +0 -77
  45. package/dist/frontend-core/template/src/components/ui/card/card.tsx +0 -45
  46. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.stories.tsx +0 -29
  47. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.tsx +0 -28
  48. package/dist/frontend-core/template/src/components/ui/carousel/carousel.stories.tsx +0 -154
  49. package/dist/frontend-core/template/src/components/ui/carousel/carousel.tsx +0 -227
  50. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.stories.tsx +0 -106
  51. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.tsx +0 -88
  52. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.stories.tsx +0 -90
  53. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.tsx +0 -54
  54. package/dist/frontend-core/template/src/components/ui/collapse/collapse.stories.tsx +0 -52
  55. package/dist/frontend-core/template/src/components/ui/collapse/collapse.tsx +0 -9
  56. package/dist/frontend-core/template/src/components/ui/combobox/combobox.stories.tsx +0 -207
  57. package/dist/frontend-core/template/src/components/ui/combobox/combobox.tsx +0 -79
  58. package/dist/frontend-core/template/src/components/ui/command/command.stories.tsx +0 -186
  59. package/dist/frontend-core/template/src/components/ui/command/command.tsx +0 -165
  60. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.stories.tsx +0 -160
  61. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.tsx +0 -134
  62. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.stories.tsx +0 -198
  63. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.tsx +0 -100
  64. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.stories.tsx +0 -78
  65. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.tsx +0 -179
  66. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/cell-variant-types.ts +0 -11
  67. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/checkbox-cell.tsx +0 -116
  68. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/date-cell.tsx +0 -157
  69. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/gantt-cell.tsx +0 -82
  70. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/long-text-cell.tsx +0 -180
  71. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/multi-select-cell.tsx +0 -280
  72. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/number-cell.tsx +0 -169
  73. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/react-node-cell.tsx +0 -33
  74. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/select-cell.tsx +0 -175
  75. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/short-text-cell.tsx +0 -138
  76. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timeline.tsx +0 -92
  77. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timerange-picker.tsx +0 -330
  78. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell-wrapper.tsx +0 -212
  79. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell.tsx +0 -157
  80. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-column-header.tsx +0 -340
  81. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-context-menu.tsx +0 -271
  82. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-row.tsx +0 -123
  83. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-search.tsx +0 -211
  84. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-types.ts +0 -159
  85. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-utils.ts +0 -67
  86. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-view-menu.tsx +0 -360
  87. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.stories.tsx +0 -780
  88. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.tsx +0 -217
  89. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-callback-ref.ts +0 -22
  90. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-data-grid.tsx +0 -1892
  91. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-debounced-callback.ts +0 -19
  92. package/dist/frontend-core/template/src/components/ui/data-grid/styles.css +0 -3
  93. package/dist/frontend-core/template/src/components/ui/data-table/context-menu-simple.tsx +0 -141
  94. package/dist/frontend-core/template/src/components/ui/data-table/data-table.stories.tsx +0 -146
  95. package/dist/frontend-core/template/src/components/ui/data-table/data-table.tsx +0 -447
  96. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-array-cell-renderer.tsx +0 -77
  97. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-cell-renderer.tsx +0 -56
  98. package/dist/frontend-core/template/src/components/ui/data-table/renderers/favorite-cell-renderer.tsx +0 -68
  99. package/dist/frontend-core/template/src/components/ui/data-table/renderers/links-cell-renderer.tsx +0 -205
  100. package/dist/frontend-core/template/src/components/ui/data-table/utils/columns.ts +0 -351
  101. package/dist/frontend-core/template/src/components/ui/data-table/utils/data-table.utils.ts +0 -49
  102. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.stories.tsx +0 -149
  103. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.tsx +0 -30
  104. package/dist/frontend-core/template/src/components/ui/dialog/dialog.stories.tsx +0 -80
  105. package/dist/frontend-core/template/src/components/ui/dialog/dialog.tsx +0 -134
  106. package/dist/frontend-core/template/src/components/ui/drawer/drawer.stories.tsx +0 -104
  107. package/dist/frontend-core/template/src/components/ui/drawer/drawer.tsx +0 -87
  108. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.stories.tsx +0 -168
  109. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.tsx +0 -225
  110. package/dist/frontend-core/template/src/components/ui/input/input.stories.tsx +0 -141
  111. package/dist/frontend-core/template/src/components/ui/input/input.tsx +0 -47
  112. package/dist/frontend-core/template/src/components/ui/label/label.stories.tsx +0 -41
  113. package/dist/frontend-core/template/src/components/ui/label/label.tsx +0 -20
  114. package/dist/frontend-core/template/src/components/ui/loader/loader.stories.tsx +0 -45
  115. package/dist/frontend-core/template/src/components/ui/loader/loader.tsx +0 -17
  116. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.stories.tsx +0 -114
  117. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.tsx +0 -48
  118. package/dist/frontend-core/template/src/components/ui/menubar/menu.stories.tsx +0 -134
  119. package/dist/frontend-core/template/src/components/ui/menubar/menubar.tsx +0 -208
  120. package/dist/frontend-core/template/src/components/ui/modal/modal.stories.tsx +0 -297
  121. package/dist/frontend-core/template/src/components/ui/modal/modal.tsx +0 -80
  122. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.stories.tsx +0 -213
  123. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.tsx +0 -142
  124. package/dist/frontend-core/template/src/components/ui/pagination/pagination.stories.tsx +0 -49
  125. package/dist/frontend-core/template/src/components/ui/pagination/pagination.tsx +0 -84
  126. package/dist/frontend-core/template/src/components/ui/popover/popover.stories.tsx +0 -82
  127. package/dist/frontend-core/template/src/components/ui/popover/popover.tsx +0 -55
  128. package/dist/frontend-core/template/src/components/ui/progress/progress.stories.tsx +0 -80
  129. package/dist/frontend-core/template/src/components/ui/progress/progress.tsx +0 -17
  130. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.stories.tsx +0 -154
  131. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.tsx +0 -68
  132. package/dist/frontend-core/template/src/components/ui/resizable/resizable.stories.tsx +0 -73
  133. package/dist/frontend-core/template/src/components/ui/resizable/resizeable.tsx +0 -38
  134. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.stories.tsx +0 -55
  135. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.tsx +0 -39
  136. package/dist/frontend-core/template/src/components/ui/select/select.stories.tsx +0 -297
  137. package/dist/frontend-core/template/src/components/ui/select/select.tsx +0 -227
  138. package/dist/frontend-core/template/src/components/ui/separator/separator.tsx +0 -21
  139. package/dist/frontend-core/template/src/components/ui/separator/seperator.stories.tsx +0 -25
  140. package/dist/frontend-core/template/src/components/ui/sheet/sheet.stories.tsx +0 -45
  141. package/dist/frontend-core/template/src/components/ui/sheet/sheet.tsx +0 -107
  142. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.stories.tsx +0 -26
  143. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.tsx +0 -7
  144. package/dist/frontend-core/template/src/components/ui/slider/slider.stories.tsx +0 -101
  145. package/dist/frontend-core/template/src/components/ui/slider/slider.tsx +0 -98
  146. package/dist/frontend-core/template/src/components/ui/spinner/spinner.stories.tsx +0 -19
  147. package/dist/frontend-core/template/src/components/ui/spinner/spinner.tsx +0 -21
  148. package/dist/frontend-core/template/src/components/ui/switch/switch.stories.tsx +0 -33
  149. package/dist/frontend-core/template/src/components/ui/switch/switch.tsx +0 -28
  150. package/dist/frontend-core/template/src/components/ui/tabs/tabs.stories.tsx +0 -215
  151. package/dist/frontend-core/template/src/components/ui/tabs/tabs.tsx +0 -70
  152. package/dist/frontend-core/template/src/components/ui/textarea/textarea.stories.tsx +0 -138
  153. package/dist/frontend-core/template/src/components/ui/textarea/textarea.tsx +0 -40
  154. package/dist/frontend-core/template/src/components/ui/toast/toast.mdx +0 -31
  155. package/dist/frontend-core/template/src/components/ui/toast/toast.stories.tsx +0 -89
  156. package/dist/frontend-core/template/src/components/ui/toggle/toggle.stories.tsx +0 -65
  157. package/dist/frontend-core/template/src/components/ui/toggle/toggle.tsx +0 -38
  158. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.stories.tsx +0 -85
  159. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.tsx +0 -54
  160. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.stories.tsx +0 -29
  161. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.tsx +0 -29
@@ -1,340 +0,0 @@
1
- import type { ColumnSort, Header, SortDirection, SortingState, Table } from '@tanstack/react-table'
2
-
3
- import {
4
- BaselineIcon,
5
- CalendarIcon,
6
- CheckSquareIcon,
7
- ChevronDownIcon,
8
- ChevronUpIcon,
9
- EyeOffIcon,
10
- HashIcon,
11
- ListChecksIcon,
12
- ListIcon,
13
- PinIcon,
14
- PinOffIcon,
15
- TextInitialIcon,
16
- XIcon,
17
- } from 'lucide-react'
18
- import * as React from 'react'
19
-
20
- import type { Cell, ColumnMenuRendererFunction } from '@components/ui/data-grid/data-grid-types'
21
- import {
22
- DropdownMenu,
23
- DropdownMenuCheckboxItem,
24
- DropdownMenuContent,
25
- DropdownMenuItem,
26
- DropdownMenuSeparator,
27
- DropdownMenuTrigger,
28
- } from '@components/ui/dropdown-menu/dropdown-menu'
29
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@components/ui/tooltip/tooltip'
30
- import { cn } from '@lib/utils'
31
-
32
- import { GanttTimeline } from './cell-variants/utils/gantt-timeline'
33
-
34
- function getColumnVariant(variant?: Cell['variant']): {
35
- icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
36
- label: string
37
- } | null {
38
- switch (variant) {
39
- case 'short-text':
40
- return { icon: BaselineIcon, label: 'Short text' }
41
- case 'long-text':
42
- return { icon: TextInitialIcon, label: 'Long text' }
43
- case 'number':
44
- return { icon: HashIcon, label: 'Number' }
45
- case 'select':
46
- return { icon: ListIcon, label: 'Select' }
47
- case 'multi-select':
48
- return { icon: ListChecksIcon, label: 'Multi-select' }
49
- case 'checkbox':
50
- return { icon: CheckSquareIcon, label: 'Checkbox' }
51
- case 'date':
52
- return { icon: CalendarIcon, label: 'Date' }
53
- default:
54
- return null
55
- }
56
- }
57
-
58
- type DataGridColumnHeaderProps<TData, TValue> = {
59
- header: Header<TData, TValue>
60
- table: Table<TData>
61
- } & React.ComponentProps<typeof DropdownMenuTrigger>
62
-
63
- export function DataGridColumnHeader<TData, TValue>({
64
- header,
65
- table,
66
- className,
67
- onPointerDown,
68
- ...props
69
- }: DataGridColumnHeaderProps<TData, TValue>) {
70
- const [open, setOpen] = React.useState(false)
71
-
72
- const column = header.column
73
- const label = column.columnDef.meta?.label
74
- ? column.columnDef.meta.label
75
- : typeof column.columnDef.header === 'string'
76
- ? column.columnDef.header
77
- : column.id
78
-
79
- const isAnyColumnResizing = table.getState().columnSizingInfo.isResizingColumn
80
-
81
- const cellVariant = column.columnDef.meta?.cell
82
- const columnVariant = getColumnVariant(cellVariant?.variant)
83
-
84
- const pinnedPosition = column.getIsPinned()
85
- const isPinnedLeft = pinnedPosition === 'left'
86
- const isPinnedRight = pinnedPosition === 'right'
87
-
88
- const onSortingChange = React.useCallback(
89
- (direction: SortDirection) => {
90
- table.setSorting((prev: SortingState) => {
91
- const existingSortIndex = prev.findIndex((sort) => sort.id === column.id)
92
- const newSort: ColumnSort = {
93
- id: column.id,
94
- desc: direction === 'desc',
95
- }
96
-
97
- if (existingSortIndex >= 0) {
98
- const updated = [...prev]
99
- updated[existingSortIndex] = newSort
100
- return updated
101
- } else {
102
- return [...prev, newSort]
103
- }
104
- })
105
- },
106
- [column.id, table],
107
- )
108
-
109
- const onSortRemove = React.useCallback(() => {
110
- table.setSorting((prev: SortingState) => prev.filter((sort) => sort.id !== column.id))
111
- }, [column.id, table])
112
-
113
- const onLeftPin = React.useCallback(() => {
114
- column.pin('left')
115
- }, [column])
116
-
117
- const onRightPin = React.useCallback(() => {
118
- column.pin('right')
119
- }, [column])
120
-
121
- const onUnpin = React.useCallback(() => {
122
- column.pin(false)
123
- }, [column])
124
-
125
- const onTriggerPointerDown = React.useCallback(
126
- (event: React.PointerEvent<HTMLButtonElement>) => {
127
- onPointerDown?.(event)
128
- if (event.defaultPrevented) {
129
- return
130
- }
131
-
132
- if (event.button !== 0) {
133
- return
134
- }
135
- table.options.meta?.onColumnClick?.(column.id)
136
- },
137
- [table.options.meta, column.id, onPointerDown],
138
- )
139
-
140
- return (
141
- <>
142
- <DropdownMenu open={open} onOpenChange={setOpen}>
143
- <DropdownMenuTrigger
144
- className={cn(
145
- 'flex size-full items-center justify-between gap-2 p-2 text-sm bg-sidebar-accent/80 font-bold hover:bg-secondary/40 data-[state=open]:bg-secondary/40 [&_svg]:size-4',
146
- isAnyColumnResizing && 'pointer-events-none',
147
- className,
148
- )}
149
- onPointerDown={onTriggerPointerDown}
150
- {...props}
151
- >
152
- <div className="flex min-w-0 flex-1 items-center gap-1.5">
153
- {columnVariant && (
154
- <TooltipProvider>
155
- <Tooltip delayDuration={100}>
156
- <TooltipTrigger asChild>
157
- <columnVariant.icon className="size-3.5 shrink-0 text-muted-foreground" />
158
- </TooltipTrigger>
159
- <TooltipContent side="top">
160
- <p>{columnVariant.label}</p>
161
- </TooltipContent>
162
- </Tooltip>
163
- </TooltipProvider>
164
- )}
165
- <span className="truncate">{label}</span>
166
- </div>
167
- <ChevronDownIcon className="shrink-0 text-muted-foreground" />
168
- </DropdownMenuTrigger>
169
- <DropdownMenuContent align="start" sideOffset={0} className="w-60">
170
- {column.getCanSort() && (
171
- <>
172
- <DropdownMenuCheckboxItem
173
- className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground"
174
- checked={column.getIsSorted() === 'asc'}
175
- onClick={() => onSortingChange('asc')}
176
- >
177
- <ChevronUpIcon />
178
- Sort asc
179
- </DropdownMenuCheckboxItem>
180
- <DropdownMenuCheckboxItem
181
- className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground"
182
- checked={column.getIsSorted() === 'desc'}
183
- onClick={() => onSortingChange('desc')}
184
- >
185
- <ChevronDownIcon />
186
- Sort desc
187
- </DropdownMenuCheckboxItem>
188
- {column.getIsSorted() && (
189
- <DropdownMenuItem onClick={onSortRemove}>
190
- <XIcon />
191
- Remove sort
192
- </DropdownMenuItem>
193
- )}
194
- </>
195
- )}
196
- {column.getCanPin() && (
197
- <>
198
- {column.getCanSort() && <DropdownMenuSeparator />}
199
-
200
- {isPinnedLeft ? (
201
- <DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onUnpin}>
202
- <PinOffIcon />
203
- Unpin from left
204
- </DropdownMenuItem>
205
- ) : (
206
- <DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onLeftPin}>
207
- <PinIcon />
208
- Pin to left
209
- </DropdownMenuItem>
210
- )}
211
- {isPinnedRight ? (
212
- <DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onUnpin}>
213
- <PinOffIcon />
214
- Unpin from right
215
- </DropdownMenuItem>
216
- ) : (
217
- <DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onRightPin}>
218
- <PinIcon />
219
- Pin to right
220
- </DropdownMenuItem>
221
- )}
222
- </>
223
- )}
224
- {column.getCanHide() && (
225
- <>
226
- <DropdownMenuSeparator />
227
- <DropdownMenuCheckboxItem
228
- className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground"
229
- checked={!column.getIsVisible()}
230
- onClick={() => column.toggleVisibility(false)}
231
- >
232
- <EyeOffIcon />
233
- Hide column
234
- </DropdownMenuCheckboxItem>
235
- </>
236
- )}
237
-
238
- {/* Render header menu footer (custom optional component) */}
239
- {column.columnDef.meta?.headerMenuFooter && (
240
- <>
241
- <DropdownMenuSeparator />
242
- <div className="dropdown-footer">
243
- {isMenuRendererFunction(column.columnDef.meta.headerMenuFooter)
244
- ? column.columnDef.meta.headerMenuFooter({ column, open, onOpenChange: setOpen })
245
- : column.columnDef.meta.headerMenuFooter}
246
- </div>
247
- </>
248
- )}
249
- </DropdownMenuContent>
250
- </DropdownMenu>
251
- {/* Render any variant-provided header component from HeaderComponents. */}
252
- {(() => {
253
- const variant = column.columnDef.meta?.cell?.variant
254
- const variantHeader = variant ? HeaderComponents[variant] : undefined
255
- if (variantHeader) {
256
- return variantHeader({ header, table })
257
- }
258
-
259
- return null
260
- })()}
261
-
262
- {/* Render custom column header component (optional columnDef.meta) */}
263
- {column.columnDef.meta?.headerCustomComponent && (
264
- <>
265
- {isMenuRendererFunction(column.columnDef.meta.headerCustomComponent)
266
- ? column.columnDef.meta.headerCustomComponent({ column })
267
- : column.columnDef.meta.headerCustomComponent}
268
- </>
269
- )}
270
-
271
- {header.column.getCanResize() && <DataGridColumnResizer header={header} table={table} label={label} />}
272
- </>
273
- )
274
- }
275
-
276
- const DataGridColumnResizer = React.memo(DataGridColumnResizerImpl, (prev, next) => {
277
- const prevColumn = prev.header.column
278
- const nextColumn = next.header.column
279
-
280
- if (prevColumn.getIsResizing() !== nextColumn.getIsResizing() || prevColumn.getSize() !== nextColumn.getSize()) {
281
- return false
282
- }
283
-
284
- if (prev.label !== next.label) {
285
- return false
286
- }
287
-
288
- return true
289
- }) as typeof DataGridColumnResizerImpl
290
-
291
- type DataGridColumnResizerProps<TData, TValue> = {
292
- label: string
293
- } & DataGridColumnHeaderProps<TData, TValue>
294
-
295
- function DataGridColumnResizerImpl<TData, TValue>({ header, table, label }: DataGridColumnResizerProps<TData, TValue>) {
296
- const defaultColumnDef = table._getDefaultColumnDef()
297
-
298
- const onDoubleClick = React.useCallback(() => {
299
- header.column.resetSize()
300
- }, [header.column])
301
-
302
- return (
303
- <div
304
- role="separator"
305
- aria-orientation="vertical"
306
- aria-label={`Resize ${label} column`}
307
- aria-valuenow={header.column.getSize()}
308
- aria-valuemin={defaultColumnDef.minSize}
309
- aria-valuemax={defaultColumnDef.maxSize}
310
- tabIndex={0}
311
- className={cn(
312
- 'right-0 absolute top-0 z-50 h-full w-0 touch-none select-none cursor-ew-resize focus:outline-none',
313
- // visible thin line (use right positioning and a stable 1px width)
314
- "before:content-[''] before:absolute before:inset-y-0 before:right-0 before:translate-x-1/2 before:w-px before:h-full before:bg-accent-foreground/70 before:opacity-0 before:transition-opacity before:duration-150",
315
- // large invisible hit area
316
- "after:content-[''] after:absolute after:inset-y-0 after:right-0 after:translate-x-1/2 after:w-[18px] after:h-full after:bg-transparent",
317
- header.column.getIsResizing() ? 'before:opacity-100 before:bg-accent-foreground' : 'hover:before:opacity-100',
318
- )}
319
- style={{ willChange: 'transform, opacity', transform: 'translateZ(0)' }}
320
- onDoubleClick={onDoubleClick}
321
- onMouseDown={header.getResizeHandler()}
322
- onTouchStart={header.getResizeHandler()}
323
- />
324
- )
325
- }
326
-
327
- function isMenuRendererFunction<TData, TValue>(
328
- value: React.ReactNode | ColumnMenuRendererFunction<TData, TValue>,
329
- ): value is ColumnMenuRendererFunction<TData, TValue> {
330
- return typeof value === 'function'
331
- }
332
-
333
- /**
334
- * Optional header components keyed by cell variant. Components receive { header, table } and
335
- * should return a React node (or null). This allows the column header to render variant-specific
336
- * header visuals without coupling the header implementation to variants.
337
- */
338
- export const HeaderComponents: Record<string, (props: any) => React.ReactNode | null> = {
339
- gantt: (props) => <GanttTimeline {...props} />,
340
- }
@@ -1,271 +0,0 @@
1
- import { CopyIcon, EraserIcon, TrashIcon } from '@radix-ui/react-icons'
2
- import type { Table, TableMeta } from '@tanstack/react-table'
3
-
4
- import * as React from 'react'
5
- import { toast } from 'sonner'
6
-
7
- import type { UpdateCell } from '@components/ui/data-grid/data-grid-types'
8
- import { parseCellKey } from '@components/ui/data-grid/data-grid-utils'
9
- import {
10
- DropdownMenu,
11
- DropdownMenuContent,
12
- DropdownMenuItem,
13
- DropdownMenuSeparator,
14
- DropdownMenuTrigger,
15
- } from '@components/ui/dropdown-menu/dropdown-menu'
16
-
17
- type DataGridContextMenuProps<TData> = {
18
- table: Table<TData>
19
- }
20
-
21
- export function DataGridContextMenu<TData>({ table }: DataGridContextMenuProps<TData>) {
22
- const meta = table.options.meta
23
- const contextMenu = meta?.contextMenu
24
- const onContextMenuOpenChange = meta?.onContextMenuOpenChange
25
- const selectionState = meta?.selectionState
26
- const dataGridRef = meta?.dataGridRef
27
- const onDataUpdate = meta?.onDataUpdate
28
- const onRowsDelete = meta?.onRowsDelete
29
-
30
- if (!contextMenu) {
31
- return null
32
- }
33
-
34
- return (
35
- <ContextMenu
36
- table={table}
37
- dataGridRef={dataGridRef}
38
- contextMenu={contextMenu}
39
- onContextMenuOpenChange={onContextMenuOpenChange}
40
- selectionState={selectionState}
41
- onDataUpdate={onDataUpdate}
42
- onRowsDelete={onRowsDelete}
43
- />
44
- )
45
- }
46
-
47
- type ContextMenuProps<TData> = {
48
- table: Table<TData>
49
- } & Pick<
50
- TableMeta<TData>,
51
- 'dataGridRef' | 'onContextMenuOpenChange' | 'selectionState' | 'onDataUpdate' | 'onRowsDelete'
52
- > &
53
- Required<Pick<TableMeta<TData>, 'contextMenu'>>
54
-
55
- const ContextMenu = React.memo(ContextMenuImpl, (prev, next) => {
56
- if (prev.contextMenu.open !== next.contextMenu.open) {
57
- return false
58
- }
59
- if (!next.contextMenu.open) {
60
- return true
61
- }
62
- if (prev.contextMenu.x !== next.contextMenu.x) {
63
- return false
64
- }
65
- if (prev.contextMenu.y !== next.contextMenu.y) {
66
- return false
67
- }
68
-
69
- const prevSize = prev.selectionState?.selectedCells?.size ?? 0
70
- const nextSize = next.selectionState?.selectedCells?.size ?? 0
71
- if (prevSize !== nextSize) {
72
- return false
73
- }
74
-
75
- return true
76
- }) as typeof ContextMenuImpl
77
-
78
- function ContextMenuImpl<TData>({
79
- table,
80
- dataGridRef,
81
- contextMenu,
82
- onContextMenuOpenChange,
83
- selectionState,
84
- onDataUpdate,
85
- onRowsDelete,
86
- }: ContextMenuProps<TData>) {
87
- const triggerStyle = React.useMemo<React.CSSProperties>(
88
- () => ({
89
- position: 'fixed',
90
- left: `${contextMenu.x}px`,
91
- top: `${contextMenu.y}px`,
92
- width: '1px',
93
- height: '1px',
94
- padding: 0,
95
- margin: 0,
96
- border: 'none',
97
- background: 'transparent',
98
- pointerEvents: 'none',
99
- opacity: 0,
100
- }),
101
- [contextMenu.x, contextMenu.y],
102
- )
103
-
104
- const onCloseAutoFocus: NonNullable<React.ComponentProps<typeof DropdownMenuContent>['onCloseAutoFocus']> =
105
- React.useCallback(
106
- (event) => {
107
- event.preventDefault()
108
- dataGridRef?.current?.focus()
109
- },
110
- [dataGridRef],
111
- )
112
-
113
- const onCopy = React.useCallback(async () => {
114
- if (!selectionState?.selectedCells || selectionState.selectedCells.size === 0) {
115
- return
116
- }
117
-
118
- const rows = table.getRowModel().rows
119
- const columnIds: string[] = []
120
-
121
- const selectedCellsArray = Array.from(selectionState.selectedCells)
122
- for (const cellKey of selectedCellsArray) {
123
- const { columnId } = parseCellKey(cellKey)
124
- if (columnId && !columnIds.includes(columnId)) {
125
- columnIds.push(columnId)
126
- }
127
- }
128
-
129
- const cellData = new Map<string, string>()
130
- for (const cellKey of selectedCellsArray) {
131
- const { rowIndex, columnId } = parseCellKey(cellKey)
132
- const row = rows[rowIndex]
133
- if (row) {
134
- const cell = row.getVisibleCells().find((c) => c.column.id === columnId)
135
- if (cell) {
136
- const value = cell.getValue()
137
- cellData.set(cellKey, String(value ?? ''))
138
- }
139
- }
140
- }
141
-
142
- const rowIndices = new Set<number>()
143
- const colIndices = new Set<number>()
144
-
145
- for (const cellKey of selectedCellsArray) {
146
- const { rowIndex, columnId } = parseCellKey(cellKey)
147
- rowIndices.add(rowIndex)
148
- const colIndex = columnIds.indexOf(columnId)
149
- if (colIndex >= 0) {
150
- colIndices.add(colIndex)
151
- }
152
- }
153
-
154
- const sortedRowIndices = Array.from(rowIndices).sort((a, b) => a - b)
155
- const sortedColIndices = Array.from(colIndices).sort((a, b) => a - b)
156
- const sortedColumnIds = sortedColIndices.map((i) => columnIds[i])
157
-
158
- const tsvData = sortedRowIndices
159
- .map((rowIndex) =>
160
- sortedColumnIds
161
- .map((columnId) => {
162
- const cellKey = `${rowIndex}:${columnId}`
163
- return cellData.get(cellKey) ?? ''
164
- })
165
- .join('\t'),
166
- )
167
- .join('\n')
168
-
169
- await navigator.clipboard.writeText(tsvData)
170
- toast.success(
171
- `${selectionState.selectedCells.size} cell${selectionState.selectedCells.size !== 1 ? 's' : ''} copied`,
172
- )
173
- }, [table, selectionState])
174
-
175
- // Determine whether the selected cells are all editable. If any selected cell belongs to a non-editable column (meta.editable === false), disable the Clear action.
176
- const canClear = React.useMemo(() => {
177
- if (!selectionState?.selectedCells || selectionState.selectedCells.size === 0) {
178
- return false
179
- }
180
-
181
- const visibleCols = table.getVisibleLeafColumns()
182
- const rows = table.getRowModel().rows
183
-
184
- for (const cellKey of selectionState.selectedCells) {
185
- const { rowIndex, columnId } = parseCellKey(cellKey)
186
- if (!columnId) {
187
- continue
188
- }
189
- const col = visibleCols.find((c) => c.id === columnId)
190
- const editable = col?.columnDef?.meta?.editable
191
-
192
- if (editable === false) {
193
- return false
194
- }
195
-
196
- if (typeof editable === 'function') {
197
- const row = rows[rowIndex]
198
- if (row && !editable(row.original)) {
199
- return false
200
- }
201
- }
202
- }
203
-
204
- return true
205
- }, [selectionState, table])
206
-
207
- const onClear = React.useCallback(() => {
208
- if (!selectionState?.selectedCells || selectionState.selectedCells.size === 0) {
209
- return
210
- }
211
-
212
- if (!canClear) {
213
- return
214
- }
215
-
216
- const updates: UpdateCell[] = []
217
-
218
- for (const cellKey of selectionState.selectedCells) {
219
- const { rowIndex, columnId } = parseCellKey(cellKey)
220
- updates.push({ rowIndex, columnId, value: '' })
221
- }
222
-
223
- onDataUpdate?.(updates)
224
-
225
- toast.success(`${updates.length} cell${updates.length !== 1 ? 's' : ''} cleared`)
226
- }, [onDataUpdate, selectionState, canClear])
227
-
228
- const onDelete = React.useCallback(async () => {
229
- if (!selectionState?.selectedCells || selectionState.selectedCells.size === 0) {
230
- return
231
- }
232
-
233
- const rowIndices = new Set<number>()
234
- for (const cellKey of selectionState.selectedCells) {
235
- const { rowIndex } = parseCellKey(cellKey)
236
- rowIndices.add(rowIndex)
237
- }
238
-
239
- const rowIndicesArray = Array.from(rowIndices).sort((a, b) => a - b)
240
- const rowCount = rowIndicesArray.length
241
-
242
- await onRowsDelete?.(rowIndicesArray)
243
-
244
- toast.success(`${rowCount} row${rowCount !== 1 ? 's' : ''} deleted`)
245
- }, [onRowsDelete, selectionState])
246
-
247
- return (
248
- <DropdownMenu open={contextMenu.open} onOpenChange={onContextMenuOpenChange}>
249
- <DropdownMenuTrigger style={triggerStyle} />
250
- <DropdownMenuContent data-grid-popover="" align="start" className="w-48" onCloseAutoFocus={onCloseAutoFocus}>
251
- <DropdownMenuItem onSelect={onCopy}>
252
- <CopyIcon />
253
- Copy
254
- </DropdownMenuItem>
255
- <DropdownMenuItem onSelect={onClear} disabled={!canClear}>
256
- <EraserIcon />
257
- Clear
258
- </DropdownMenuItem>
259
- {onRowsDelete && (
260
- <>
261
- <DropdownMenuSeparator />
262
- <DropdownMenuItem variant="destructive" onSelect={onDelete}>
263
- <TrashIcon />
264
- Delete rows
265
- </DropdownMenuItem>
266
- </>
267
- )}
268
- </DropdownMenuContent>
269
- </DropdownMenu>
270
- )
271
- }