@postxl/generators 1.1.0 → 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 (169) hide show
  1. package/dist/backend-core/template/apps/api/src/e2e.ts +13 -4
  2. package/dist/e2e/e2e.generator.js +2 -14
  3. package/dist/e2e/e2e.generator.js.map +1 -1
  4. package/dist/e2e/generators/docker-sh.generator.d.ts +2 -0
  5. package/dist/e2e/generators/docker-sh.generator.js +25 -0
  6. package/dist/e2e/generators/docker-sh.generator.js.map +1 -0
  7. package/dist/frontend-core/frontend.generator.d.ts +0 -58
  8. package/dist/frontend-core/frontend.generator.js +6 -172
  9. package/dist/frontend-core/frontend.generator.js.map +1 -1
  10. package/dist/frontend-core/template/README.md +1 -1
  11. package/dist/frontend-core/template/src/components/admin/table-filter.tsx +1 -5
  12. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +10 -4
  13. package/dist/frontend-core/template/src/context-providers/auth-context-provider.tsx +2 -5
  14. package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +2 -3
  15. package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +1 -1
  16. package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +1 -1
  17. package/dist/frontend-core/template/src/styles/styles.css +13 -1
  18. package/dist/frontend-core/template/tsconfig.json +2 -0
  19. package/dist/frontend-core/types/component.d.ts +1 -1
  20. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js +4 -6
  21. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js.map +1 -1
  22. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js +1 -1
  23. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js.map +1 -1
  24. package/dist/frontend-forms/generators/enum/inputs.generator.js +1 -1
  25. package/dist/frontend-forms/generators/enum/inputs.generator.js.map +1 -1
  26. package/dist/frontend-forms/generators/model/forms.generator.js +8 -12
  27. package/dist/frontend-forms/generators/model/forms.generator.js.map +1 -1
  28. package/dist/frontend-forms/generators/model/inputs.generator.js +2 -6
  29. package/dist/frontend-forms/generators/model/inputs.generator.js.map +1 -1
  30. package/dist/frontend-forms/template/src/components/ui/field/field.tsx +1 -4
  31. package/dist/frontend-tables/generators/model-table.generator.js +1 -5
  32. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  33. package/package.json +3 -2
  34. package/dist/e2e/template/scripts/docker.sh +0 -17
  35. package/dist/frontend-core/template/src/components/ui/accordion/accordion.stories.tsx +0 -47
  36. package/dist/frontend-core/template/src/components/ui/accordion/accordion.tsx +0 -52
  37. package/dist/frontend-core/template/src/components/ui/admin-sidebar/admin-sidebar.tsx +0 -195
  38. package/dist/frontend-core/template/src/components/ui/alert/alert.stories.tsx +0 -61
  39. package/dist/frontend-core/template/src/components/ui/alert/alert.tsx +0 -45
  40. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.stories.tsx +0 -52
  41. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.tsx +0 -105
  42. package/dist/frontend-core/template/src/components/ui/avatar/avatar.stories.tsx +0 -30
  43. package/dist/frontend-core/template/src/components/ui/avatar/avatar.tsx +0 -39
  44. package/dist/frontend-core/template/src/components/ui/badge/badge.stories.tsx +0 -78
  45. package/dist/frontend-core/template/src/components/ui/badge/badge.tsx +0 -48
  46. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.stories.tsx +0 -67
  47. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.tsx +0 -85
  48. package/dist/frontend-core/template/src/components/ui/button/button.stories.tsx +0 -150
  49. package/dist/frontend-core/template/src/components/ui/button/button.tsx +0 -68
  50. package/dist/frontend-core/template/src/components/ui/calendar/calendar.stories.tsx +0 -160
  51. package/dist/frontend-core/template/src/components/ui/calendar/calendar.tsx +0 -293
  52. package/dist/frontend-core/template/src/components/ui/card/card.stories.tsx +0 -77
  53. package/dist/frontend-core/template/src/components/ui/card/card.tsx +0 -45
  54. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.stories.tsx +0 -29
  55. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.tsx +0 -28
  56. package/dist/frontend-core/template/src/components/ui/carousel/carousel.stories.tsx +0 -154
  57. package/dist/frontend-core/template/src/components/ui/carousel/carousel.tsx +0 -227
  58. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.stories.tsx +0 -106
  59. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.tsx +0 -88
  60. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.stories.tsx +0 -90
  61. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.tsx +0 -54
  62. package/dist/frontend-core/template/src/components/ui/collapse/collapse.stories.tsx +0 -52
  63. package/dist/frontend-core/template/src/components/ui/collapse/collapse.tsx +0 -9
  64. package/dist/frontend-core/template/src/components/ui/combobox/combobox.stories.tsx +0 -207
  65. package/dist/frontend-core/template/src/components/ui/combobox/combobox.tsx +0 -79
  66. package/dist/frontend-core/template/src/components/ui/command/command.stories.tsx +0 -186
  67. package/dist/frontend-core/template/src/components/ui/command/command.tsx +0 -165
  68. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.stories.tsx +0 -160
  69. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.tsx +0 -134
  70. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.stories.tsx +0 -198
  71. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.tsx +0 -100
  72. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.stories.tsx +0 -78
  73. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.tsx +0 -179
  74. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/cell-variant-types.ts +0 -11
  75. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/checkbox-cell.tsx +0 -116
  76. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/date-cell.tsx +0 -157
  77. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/gantt-cell.tsx +0 -82
  78. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/long-text-cell.tsx +0 -180
  79. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/multi-select-cell.tsx +0 -280
  80. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/number-cell.tsx +0 -169
  81. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/react-node-cell.tsx +0 -33
  82. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/select-cell.tsx +0 -175
  83. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/short-text-cell.tsx +0 -138
  84. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timeline.tsx +0 -92
  85. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timerange-picker.tsx +0 -330
  86. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell-wrapper.tsx +0 -212
  87. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell.tsx +0 -157
  88. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-column-header.tsx +0 -340
  89. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-context-menu.tsx +0 -271
  90. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-row.tsx +0 -123
  91. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-search.tsx +0 -211
  92. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-types.ts +0 -159
  93. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-utils.ts +0 -67
  94. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-view-menu.tsx +0 -360
  95. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.stories.tsx +0 -780
  96. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.tsx +0 -217
  97. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-callback-ref.ts +0 -22
  98. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-data-grid.tsx +0 -1892
  99. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-debounced-callback.ts +0 -19
  100. package/dist/frontend-core/template/src/components/ui/data-grid/styles.css +0 -3
  101. package/dist/frontend-core/template/src/components/ui/data-table/context-menu-simple.tsx +0 -141
  102. package/dist/frontend-core/template/src/components/ui/data-table/data-table.stories.tsx +0 -146
  103. package/dist/frontend-core/template/src/components/ui/data-table/data-table.tsx +0 -447
  104. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-array-cell-renderer.tsx +0 -77
  105. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-cell-renderer.tsx +0 -56
  106. package/dist/frontend-core/template/src/components/ui/data-table/renderers/favorite-cell-renderer.tsx +0 -68
  107. package/dist/frontend-core/template/src/components/ui/data-table/renderers/links-cell-renderer.tsx +0 -205
  108. package/dist/frontend-core/template/src/components/ui/data-table/utils/columns.ts +0 -351
  109. package/dist/frontend-core/template/src/components/ui/data-table/utils/data-table.utils.ts +0 -49
  110. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.stories.tsx +0 -149
  111. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.tsx +0 -30
  112. package/dist/frontend-core/template/src/components/ui/dialog/dialog.stories.tsx +0 -80
  113. package/dist/frontend-core/template/src/components/ui/dialog/dialog.tsx +0 -134
  114. package/dist/frontend-core/template/src/components/ui/drawer/drawer.stories.tsx +0 -104
  115. package/dist/frontend-core/template/src/components/ui/drawer/drawer.tsx +0 -87
  116. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.stories.tsx +0 -168
  117. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.tsx +0 -225
  118. package/dist/frontend-core/template/src/components/ui/input/input.stories.tsx +0 -141
  119. package/dist/frontend-core/template/src/components/ui/input/input.tsx +0 -47
  120. package/dist/frontend-core/template/src/components/ui/label/label.stories.tsx +0 -41
  121. package/dist/frontend-core/template/src/components/ui/label/label.tsx +0 -20
  122. package/dist/frontend-core/template/src/components/ui/loader/loader.stories.tsx +0 -45
  123. package/dist/frontend-core/template/src/components/ui/loader/loader.tsx +0 -17
  124. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.stories.tsx +0 -114
  125. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.tsx +0 -48
  126. package/dist/frontend-core/template/src/components/ui/menubar/menu.stories.tsx +0 -134
  127. package/dist/frontend-core/template/src/components/ui/menubar/menubar.tsx +0 -208
  128. package/dist/frontend-core/template/src/components/ui/modal/modal.stories.tsx +0 -297
  129. package/dist/frontend-core/template/src/components/ui/modal/modal.tsx +0 -80
  130. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.stories.tsx +0 -213
  131. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.tsx +0 -142
  132. package/dist/frontend-core/template/src/components/ui/pagination/pagination.stories.tsx +0 -49
  133. package/dist/frontend-core/template/src/components/ui/pagination/pagination.tsx +0 -84
  134. package/dist/frontend-core/template/src/components/ui/popover/popover.stories.tsx +0 -82
  135. package/dist/frontend-core/template/src/components/ui/popover/popover.tsx +0 -55
  136. package/dist/frontend-core/template/src/components/ui/progress/progress.stories.tsx +0 -80
  137. package/dist/frontend-core/template/src/components/ui/progress/progress.tsx +0 -17
  138. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.stories.tsx +0 -154
  139. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.tsx +0 -68
  140. package/dist/frontend-core/template/src/components/ui/resizable/resizable.stories.tsx +0 -73
  141. package/dist/frontend-core/template/src/components/ui/resizable/resizeable.tsx +0 -38
  142. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.stories.tsx +0 -55
  143. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.tsx +0 -39
  144. package/dist/frontend-core/template/src/components/ui/select/select.stories.tsx +0 -297
  145. package/dist/frontend-core/template/src/components/ui/select/select.tsx +0 -227
  146. package/dist/frontend-core/template/src/components/ui/separator/separator.tsx +0 -21
  147. package/dist/frontend-core/template/src/components/ui/separator/seperator.stories.tsx +0 -25
  148. package/dist/frontend-core/template/src/components/ui/sheet/sheet.stories.tsx +0 -45
  149. package/dist/frontend-core/template/src/components/ui/sheet/sheet.tsx +0 -107
  150. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.stories.tsx +0 -26
  151. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.tsx +0 -7
  152. package/dist/frontend-core/template/src/components/ui/slider/slider.stories.tsx +0 -101
  153. package/dist/frontend-core/template/src/components/ui/slider/slider.tsx +0 -98
  154. package/dist/frontend-core/template/src/components/ui/spinner/spinner.stories.tsx +0 -19
  155. package/dist/frontend-core/template/src/components/ui/spinner/spinner.tsx +0 -21
  156. package/dist/frontend-core/template/src/components/ui/switch/switch.stories.tsx +0 -33
  157. package/dist/frontend-core/template/src/components/ui/switch/switch.tsx +0 -28
  158. package/dist/frontend-core/template/src/components/ui/tabs/tabs.stories.tsx +0 -215
  159. package/dist/frontend-core/template/src/components/ui/tabs/tabs.tsx +0 -70
  160. package/dist/frontend-core/template/src/components/ui/textarea/textarea.stories.tsx +0 -138
  161. package/dist/frontend-core/template/src/components/ui/textarea/textarea.tsx +0 -40
  162. package/dist/frontend-core/template/src/components/ui/toast/toast.mdx +0 -31
  163. package/dist/frontend-core/template/src/components/ui/toast/toast.stories.tsx +0 -89
  164. package/dist/frontend-core/template/src/components/ui/toggle/toggle.stories.tsx +0 -65
  165. package/dist/frontend-core/template/src/components/ui/toggle/toggle.tsx +0 -38
  166. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.stories.tsx +0 -85
  167. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.tsx +0 -54
  168. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.stories.tsx +0 -29
  169. 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
- }