@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,180 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { DataGridCellWrapper } from '@components/ui/data-grid/data-grid-cell-wrapper'
4
- import { useDebouncedCallback } from '@components/ui/data-grid/hooks/use-debounced-callback'
5
- import { Popover, PopoverAnchor, PopoverContent } from '@components/ui/popover/popover'
6
- import { Textarea } from '@components/ui/textarea/textarea'
7
-
8
- import { CellVariantProps } from './cell-variant-types'
9
-
10
- export function LongTextCell<TData>({
11
- cell,
12
- table,
13
- rowIndex,
14
- columnId,
15
- isFocused,
16
- isEditing,
17
- isSelected,
18
- }: Readonly<CellVariantProps<TData, string>>) {
19
- const initialValue = cell.getValue()
20
- const [value, setValue] = React.useState(initialValue ?? '')
21
- const [open, setOpen] = React.useState(false)
22
- const textareaRef = React.useRef<HTMLTextAreaElement>(null)
23
- const containerRef = React.useRef<HTMLDivElement>(null)
24
- const meta = table.options.meta
25
- const sideOffset = -(containerRef.current?.clientHeight ?? 0)
26
-
27
- const prevInitialValueRef = React.useRef(initialValue)
28
- if (initialValue !== prevInitialValueRef.current) {
29
- prevInitialValueRef.current = initialValue
30
- setValue(initialValue ?? '')
31
- }
32
-
33
- // Debounced auto-save (300ms delay)
34
- const debouncedSave = useDebouncedCallback((newValue: string) => {
35
- meta?.onDataUpdate?.({ rowIndex, columnId, value: newValue })
36
- }, 300)
37
-
38
- const onSave = React.useCallback(() => {
39
- // Immediately save any pending changes and close the popover
40
- if (value !== initialValue) {
41
- meta?.onDataUpdate?.({ rowIndex, columnId, value })
42
- }
43
- setOpen(false)
44
- meta?.onCellEditingStop?.()
45
- }, [meta, value, initialValue, rowIndex, columnId])
46
-
47
- const onCancel = React.useCallback(() => {
48
- // Restore the original value
49
- setValue(initialValue ?? '')
50
- meta?.onDataUpdate?.({ rowIndex, columnId, value: initialValue })
51
- setOpen(false)
52
- meta?.onCellEditingStop?.()
53
- }, [meta, initialValue, rowIndex, columnId])
54
-
55
- const onChange = React.useCallback(
56
- (event: React.ChangeEvent<HTMLTextAreaElement>) => {
57
- const newValue = event.target.value
58
- setValue(newValue)
59
- // Debounced auto-save
60
- debouncedSave(newValue)
61
- },
62
- [debouncedSave],
63
- )
64
-
65
- const onOpenChange = React.useCallback(
66
- (isOpen: boolean) => {
67
- setOpen(isOpen)
68
- if (!isOpen) {
69
- // Immediately save any pending changes when closing
70
- if (value !== initialValue) {
71
- meta?.onDataUpdate?.({ rowIndex, columnId, value })
72
- }
73
- meta?.onCellEditingStop?.()
74
- }
75
- },
76
- [meta, value, initialValue, rowIndex, columnId],
77
- )
78
-
79
- const onOpenAutoFocus: NonNullable<React.ComponentProps<typeof PopoverContent>['onOpenAutoFocus']> =
80
- React.useCallback((event) => {
81
- event.preventDefault()
82
- if (textareaRef.current) {
83
- textareaRef.current.focus()
84
- const length = textareaRef.current.value.length
85
- textareaRef.current.setSelectionRange(length, length)
86
- }
87
- }, [])
88
-
89
- const onWrapperKeyDown = React.useCallback(
90
- (event: React.KeyboardEvent<HTMLDivElement>) => {
91
- if (isEditing && !open) {
92
- if (event.key === 'Escape') {
93
- event.preventDefault()
94
- meta?.onCellEditingStop?.()
95
- } else if (event.key === 'Tab') {
96
- event.preventDefault()
97
- // Save any pending changes
98
- if (value !== initialValue) {
99
- meta?.onDataUpdate?.({ rowIndex, columnId, value })
100
- }
101
- meta?.onCellEditingStop?.({
102
- direction: event.shiftKey ? 'left' : 'right',
103
- })
104
- }
105
- }
106
- },
107
- [isEditing, open, meta, value, initialValue, rowIndex, columnId],
108
- )
109
-
110
- const onTextareaKeyDown = React.useCallback(
111
- (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
112
- if (event.key === 'Escape') {
113
- event.preventDefault()
114
- onCancel()
115
- } else if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
116
- event.preventDefault()
117
- onSave()
118
- }
119
- // Stop propagation to prevent grid navigation
120
- event.stopPropagation()
121
- },
122
- [onCancel, onSave],
123
- )
124
-
125
- const onTextareaBlur = React.useCallback(() => {
126
- // Immediately save any pending changes on blur
127
- if (value !== initialValue) {
128
- meta?.onDataUpdate?.({ rowIndex, columnId, value })
129
- }
130
- setOpen(false)
131
- meta?.onCellEditingStop?.()
132
- }, [meta, value, initialValue, rowIndex, columnId])
133
-
134
- React.useEffect(() => {
135
- if (isEditing && !open) {
136
- setOpen(true)
137
- }
138
- if (isFocused && !isEditing && !meta?.searchOpen && !meta?.isScrolling && containerRef.current) {
139
- containerRef.current.focus()
140
- }
141
- }, [isFocused, isEditing, open, meta?.searchOpen, meta?.isScrolling])
142
-
143
- return (
144
- <Popover open={open} onOpenChange={onOpenChange}>
145
- <PopoverAnchor asChild>
146
- <DataGridCellWrapper
147
- ref={containerRef}
148
- cell={cell}
149
- table={table}
150
- rowIndex={rowIndex}
151
- columnId={columnId}
152
- isEditing={isEditing}
153
- isFocused={isFocused}
154
- isSelected={isSelected}
155
- onKeyDown={onWrapperKeyDown}
156
- >
157
- <span data-slot="grid-cell-content">{value}</span>
158
- </DataGridCellWrapper>
159
- </PopoverAnchor>
160
- <PopoverContent
161
- data-grid-cell-editor=""
162
- align="start"
163
- side="bottom"
164
- sideOffset={sideOffset}
165
- className="w-[400px] rounded-none p-0"
166
- onOpenAutoFocus={onOpenAutoFocus}
167
- >
168
- <Textarea
169
- ref={textareaRef}
170
- value={value}
171
- onChange={onChange}
172
- onKeyDown={onTextareaKeyDown}
173
- onBlur={onTextareaBlur}
174
- className="min-h-[150px] resize-none rounded-none border-0 shadow-none focus-visible:ring-0"
175
- placeholder="Enter text..."
176
- />
177
- </PopoverContent>
178
- </Popover>
179
- )
180
- }
@@ -1,280 +0,0 @@
1
- import { CheckIcon, Cross2Icon } from '@radix-ui/react-icons'
2
-
3
- import * as React from 'react'
4
-
5
- import { Badge } from '@components/ui/badge/badge'
6
- import {
7
- Command,
8
- CommandEmpty,
9
- CommandGroup,
10
- CommandInput,
11
- CommandItem,
12
- CommandList,
13
- CommandSeparator,
14
- } from '@components/ui/command/command'
15
- import { DataGridCellWrapper } from '@components/ui/data-grid/data-grid-cell-wrapper'
16
- import { getLineCount } from '@components/ui/data-grid/data-grid-utils'
17
- import { Popover, PopoverAnchor, PopoverContent } from '@components/ui/popover/popover'
18
- import { cn } from '@lib/utils'
19
-
20
- import { CellVariantProps } from './cell-variant-types'
21
-
22
- export function MultiSelectCell<TData>({
23
- cell,
24
- table,
25
- rowIndex,
26
- columnId,
27
- isFocused,
28
- isEditing,
29
- isSelected,
30
- }: Readonly<CellVariantProps<TData, string[]>>) {
31
- const cellValue = React.useMemo(() => cell.getValue() ?? [], [cell])
32
-
33
- const cellId = `${rowIndex}-${columnId}`
34
- const prevCellIdRef = React.useRef(cellId)
35
-
36
- const [selectedValues, setSelectedValues] = React.useState<string[]>(cellValue)
37
- const [open, setOpen] = React.useState(false)
38
- const [searchValue, setSearchValue] = React.useState('')
39
- const containerRef = React.useRef<HTMLDivElement>(null)
40
- const inputRef = React.useRef<HTMLInputElement>(null)
41
- const meta = table.options.meta
42
- const cellOpts = cell.column.columnDef.meta?.cell
43
- const options = cellOpts?.variant === 'multi-select' ? cellOpts.options : []
44
- const sideOffset = -(containerRef.current?.clientHeight ?? 0)
45
-
46
- if (prevCellIdRef.current !== cellId) {
47
- prevCellIdRef.current = cellId
48
- setSelectedValues(cellValue)
49
- setOpen(false)
50
- setSearchValue('')
51
- }
52
-
53
- const onValueChange = React.useCallback(
54
- (value: string) => {
55
- const newValues = selectedValues.includes(value)
56
- ? selectedValues.filter((v) => v !== value)
57
- : [...selectedValues, value]
58
-
59
- setSelectedValues(newValues)
60
- meta?.onDataUpdate?.({ rowIndex, columnId, value: newValues })
61
- // Clear search input and focus back on input after selection
62
- setSearchValue('')
63
- queueMicrotask(() => inputRef.current?.focus())
64
- },
65
- [selectedValues, meta, rowIndex, columnId],
66
- )
67
-
68
- const removeValue = React.useCallback(
69
- (valueToRemove: string, event?: React.MouseEvent) => {
70
- event?.stopPropagation()
71
- event?.preventDefault()
72
- const newValues = selectedValues.filter((v) => v !== valueToRemove)
73
- setSelectedValues(newValues)
74
- meta?.onDataUpdate?.({ rowIndex, columnId, value: newValues })
75
- // Focus back on input after removing
76
- setTimeout(() => inputRef.current?.focus(), 0)
77
- },
78
- [selectedValues, meta, rowIndex, columnId],
79
- )
80
-
81
- const clearAll = React.useCallback(() => {
82
- setSelectedValues([])
83
- meta?.onDataUpdate?.({ rowIndex, columnId, value: [] })
84
- queueMicrotask(() => inputRef.current?.focus())
85
- }, [meta, rowIndex, columnId])
86
-
87
- const onOpenChange = React.useCallback(
88
- (isOpen: boolean) => {
89
- setOpen(isOpen)
90
- if (!isOpen) {
91
- setSearchValue('')
92
- meta?.onCellEditingStop?.()
93
- }
94
- },
95
- [meta],
96
- )
97
-
98
- const onOpenAutoFocus: NonNullable<React.ComponentProps<typeof PopoverContent>['onOpenAutoFocus']> =
99
- React.useCallback((event) => {
100
- event.preventDefault()
101
- inputRef.current?.focus()
102
- }, [])
103
-
104
- const onWrapperKeyDown = React.useCallback(
105
- (event: React.KeyboardEvent<HTMLDivElement>) => {
106
- if (isEditing) {
107
- if (event.key === 'Escape') {
108
- event.preventDefault()
109
- setSelectedValues(cellValue)
110
- setSearchValue('')
111
- setOpen(false)
112
- meta?.onCellEditingStop?.()
113
- } else if (event.key === 'Tab') {
114
- event.preventDefault()
115
- setSearchValue('')
116
- setOpen(false)
117
- meta?.onCellEditingStop?.({
118
- direction: event.shiftKey ? 'left' : 'right',
119
- })
120
- }
121
- }
122
- },
123
- [isEditing, cellValue, meta],
124
- )
125
-
126
- const onInputKeyDown = React.useCallback(
127
- (event: React.KeyboardEvent<HTMLInputElement>) => {
128
- // Handle backspace when input is empty - remove last selected item
129
- if (event.key === 'Backspace' && searchValue === '' && selectedValues.length > 0) {
130
- event.preventDefault()
131
- const lastValue = selectedValues.at(-1)
132
- if (lastValue) {
133
- removeValue(lastValue)
134
- }
135
- }
136
- // Prevent escape from propagating to close the popover immediately
137
- // Let the command handle it first
138
- if (event.key === 'Escape') {
139
- event.stopPropagation()
140
- }
141
- },
142
- [searchValue, selectedValues, removeValue],
143
- )
144
-
145
- React.useEffect(() => {
146
- if (isEditing && !open) {
147
- setOpen(true)
148
- }
149
- if (isFocused && !isEditing && !meta?.searchOpen && !meta?.isScrolling && containerRef.current) {
150
- containerRef.current.focus()
151
- }
152
- }, [isFocused, isEditing, open, meta?.searchOpen, meta?.isScrolling])
153
-
154
- // Focus input when popover opens
155
- React.useEffect(() => {
156
- if (open && inputRef.current) {
157
- setTimeout(() => inputRef.current?.focus(), 0)
158
- }
159
- }, [open])
160
-
161
- const displayLabels = selectedValues
162
- .map((val) => options.find((opt) => opt.value === val)?.label ?? val)
163
- .filter(Boolean)
164
-
165
- const rowHeight = table.options.meta?.rowHeight ?? 'short'
166
-
167
- const lineCount = getLineCount(rowHeight)
168
- const maxVisibleBadgeCount = lineCount * 3
169
-
170
- const visibleLabels = displayLabels.slice(0, maxVisibleBadgeCount)
171
- const hiddenBadgeCount = Math.max(0, displayLabels.length - maxVisibleBadgeCount)
172
-
173
- return (
174
- <DataGridCellWrapper
175
- ref={containerRef}
176
- cell={cell}
177
- table={table}
178
- rowIndex={rowIndex}
179
- columnId={columnId}
180
- isEditing={isEditing}
181
- isFocused={isFocused}
182
- isSelected={isSelected}
183
- onKeyDown={onWrapperKeyDown}
184
- >
185
- {isEditing ? (
186
- <Popover open={open} onOpenChange={onOpenChange}>
187
- <PopoverAnchor asChild>
188
- <div className="absolute inset-0" />
189
- </PopoverAnchor>
190
- <PopoverContent
191
- data-grid-cell-editor=""
192
- align="start"
193
- sideOffset={sideOffset}
194
- className="w-[300px] rounded-none p-0"
195
- onOpenAutoFocus={onOpenAutoFocus}
196
- >
197
- <Command className="[&_[data-slot=command-input-wrapper]]:h-auto [&_[data-slot=command-input-wrapper]]:border-none [&_[data-slot=command-input-wrapper]]:p-0 [&_[data-slot=command-input-wrapper]_svg]:hidden">
198
- <div className="flex min-h-9 flex-wrap items-center gap-1 border-b px-3 py-1.5">
199
- {selectedValues.map((value) => {
200
- const option = options.find((opt) => opt.value === value)
201
- const label = option?.label ?? value
202
-
203
- return (
204
- <Badge key={value} variant="secondary" className="h-5 gap-1 px-1.5 text-xs">
205
- {label}
206
- <button
207
- type="button"
208
- onClick={(event) => removeValue(value, event)}
209
- onPointerDown={(event) => {
210
- event.preventDefault()
211
- event.stopPropagation()
212
- }}
213
- >
214
- <Cross2Icon className="size-3" />
215
- </button>
216
- </Badge>
217
- )
218
- })}
219
- <CommandInput
220
- ref={inputRef}
221
- value={searchValue}
222
- onValueChange={setSearchValue}
223
- onKeyDown={onInputKeyDown}
224
- placeholder="Search..."
225
- className="h-auto flex-1 p-0"
226
- />
227
- </div>
228
- <CommandList className="max-h-full">
229
- <CommandEmpty>No options found.</CommandEmpty>
230
- <CommandGroup className="max-h-[300px] scroll-py-1 overflow-y-auto overflow-x-hidden">
231
- {options.map((option) => {
232
- const isSelected = selectedValues.includes(option.value)
233
-
234
- return (
235
- <CommandItem key={option.value} value={option.label} onSelect={() => onValueChange(option.value)}>
236
- <div
237
- className={cn(
238
- 'flex size-4 items-center justify-center rounded-sm border border-border',
239
- isSelected ? 'bg-accent-foreground text-secondary' : 'opacity-50 [&_svg]:invisible',
240
- )}
241
- >
242
- <CheckIcon className="size-3" />
243
- </div>
244
- <span>{option.label}</span>
245
- </CommandItem>
246
- )
247
- })}
248
- </CommandGroup>
249
- {selectedValues.length > 0 && (
250
- <>
251
- <CommandSeparator />
252
- <CommandGroup>
253
- <CommandItem onSelect={clearAll} className="justify-center text-muted-foreground">
254
- Clear all
255
- </CommandItem>
256
- </CommandGroup>
257
- </>
258
- )}
259
- </CommandList>
260
- </Command>
261
- </PopoverContent>
262
- </Popover>
263
- ) : null}
264
- {displayLabels.length > 0 ? (
265
- <div className="flex flex-wrap items-center gap-1 overflow-hidden">
266
- {visibleLabels.map((label, index) => (
267
- <Badge key={selectedValues[index]} variant="secondary" className="h-5 shrink-0 px-1.5 text-xs">
268
- {label}
269
- </Badge>
270
- ))}
271
- {hiddenBadgeCount > 0 && (
272
- <Badge variant="outline" className="h-5 shrink-0 px-1.5 text-muted-foreground text-xs">
273
- +{hiddenBadgeCount}
274
- </Badge>
275
- )}
276
- </div>
277
- ) : null}
278
- </DataGridCellWrapper>
279
- )
280
- }
@@ -1,169 +0,0 @@
1
- import type { ColumnMeta } from '@tanstack/react-table'
2
-
3
- import * as React from 'react'
4
-
5
- import { DataGridCellWrapper } from '@components/ui/data-grid/data-grid-cell-wrapper'
6
- import { cn } from '@lib/utils'
7
-
8
- import { CellVariantProps } from './cell-variant-types'
9
-
10
- export function NumberCell<TData>({
11
- cell,
12
- table,
13
- rowIndex,
14
- columnId,
15
- isFocused,
16
- isEditing,
17
- isSelected,
18
- }: Readonly<CellVariantProps<TData, number>>) {
19
- const initialValue = cell.getValue() as number | null
20
- const inputRef = React.useRef<HTMLInputElement>(null)
21
- const containerRef = React.useRef<HTMLDivElement>(null)
22
- const meta = table.options.meta
23
- const colMeta = cell.column.columnDef.meta as ColumnMeta<TData, number>
24
- const cellOptions = colMeta?.cell
25
- // Normalize editable to a resolver function and memoize to avoid typeof checks per cell
26
- const editableResolver = React.useMemo(() => {
27
- const v = colMeta?.editable
28
- if (v === undefined) {
29
- return () => true
30
- }
31
- return typeof v === 'function' ? (v as (row: TData) => boolean) : () => Boolean(v)
32
- }, [colMeta?.editable])
33
- const isEditable = editableResolver(cell.row.original)
34
- const {
35
- min,
36
- max,
37
- step,
38
- prefix = '',
39
- suffix = '',
40
- fallbackValue = '',
41
- } = cellOptions?.variant === 'number' ? cellOptions : {}
42
-
43
- // Keep an unformatted string for editing (no grouping separators)
44
- const [editValue, setEditValue] = React.useState<string>(
45
- initialValue !== null && initialValue !== undefined ? String(initialValue) : '',
46
- )
47
-
48
- const onBlur = React.useCallback(() => {
49
- const numValue = editValue === '' ? null : Number(editValue)
50
- if (numValue !== initialValue) {
51
- meta?.onDataUpdate?.({ rowIndex, columnId, value: numValue })
52
- }
53
- meta?.onCellEditingStop?.()
54
- }, [meta, rowIndex, columnId, initialValue, editValue])
55
-
56
- const onChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
57
- setEditValue(event.target.value)
58
- }, [])
59
- const parseNumValue = React.useCallback((): number | null => {
60
- return editValue === '' ? null : Number(editValue)
61
- }, [editValue])
62
-
63
- const saveAndStop = React.useCallback(
64
- (options?: { moveToNextRow?: boolean; direction?: 'left' | 'right' }) => {
65
- const numValue = parseNumValue()
66
- if (numValue !== initialValue) {
67
- meta?.onDataUpdate?.({ rowIndex, columnId, value: numValue })
68
- }
69
- meta?.onCellEditingStop?.(options)
70
- },
71
- [parseNumValue, initialValue, meta, rowIndex, columnId],
72
- )
73
-
74
- const handleEditingKeyDown = React.useCallback(
75
- (event: React.KeyboardEvent<HTMLDivElement>) => {
76
- if (event.key === 'Enter') {
77
- event.preventDefault()
78
- saveAndStop({ moveToNextRow: true })
79
- } else if (event.key === 'Tab') {
80
- event.preventDefault()
81
- saveAndStop({ direction: event.shiftKey ? 'left' : 'right' })
82
- } else if (event.key === 'Escape') {
83
- event.preventDefault()
84
- setEditValue(initialValue !== null && initialValue !== undefined ? String(initialValue) : '')
85
- inputRef.current?.blur()
86
- }
87
- },
88
- [saveAndStop, initialValue],
89
- )
90
-
91
- const handleFocusedKeyDown = React.useCallback(
92
- (event: React.KeyboardEvent<HTMLDivElement>) => {
93
- if (!isEditable) {
94
- return
95
- }
96
-
97
- if (event.key === 'Backspace') {
98
- setEditValue('')
99
- } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
100
- setEditValue(event.key)
101
- }
102
- },
103
- [isEditable],
104
- )
105
- const onWrapperKeyDown = React.useCallback(
106
- (event: React.KeyboardEvent<HTMLDivElement>) => {
107
- if (isEditing) {
108
- handleEditingKeyDown(event)
109
- } else if (isFocused) {
110
- handleFocusedKeyDown(event)
111
- }
112
- },
113
- [isEditing, isFocused, handleEditingKeyDown, handleFocusedKeyDown],
114
- )
115
-
116
- React.useEffect(() => {
117
- setEditValue(initialValue !== null && initialValue !== undefined ? String(initialValue) : '')
118
- }, [initialValue])
119
-
120
- React.useEffect(() => {
121
- if (isEditing && inputRef.current) {
122
- inputRef.current.focus()
123
- inputRef.current.select()
124
- }
125
- if (isFocused && !isEditing && !meta?.searchOpen && !meta?.isScrolling && containerRef.current) {
126
- containerRef.current.focus()
127
- }
128
- }, [isFocused, isEditing, meta?.searchOpen, meta?.isScrolling])
129
-
130
- return (
131
- <DataGridCellWrapper
132
- ref={containerRef}
133
- cell={cell}
134
- table={table}
135
- rowIndex={rowIndex}
136
- columnId={columnId}
137
- isEditing={isEditing}
138
- isFocused={isFocused}
139
- isSelected={isSelected}
140
- onKeyDown={onWrapperKeyDown}
141
- >
142
- {isEditing ? (
143
- <input
144
- ref={inputRef}
145
- type="number"
146
- value={editValue}
147
- min={min}
148
- max={max}
149
- step={step}
150
- onBlur={onBlur}
151
- onChange={onChange}
152
- className="w-full border-none bg-transparent p-0 outline-none"
153
- />
154
- ) : (
155
- <span data-slot="grid-cell-content" className={cn('tabular-nums', colMeta?.align ?? 'text-right')}>
156
- {initialValue === null || initialValue === undefined || editValue === '' ? (
157
- fallbackValue
158
- ) : (
159
- <>
160
- {prefix}
161
- {initialValue.toLocaleString()}
162
- {suffix}
163
- </>
164
- )}
165
- </span>
166
- )}
167
- </DataGridCellWrapper>
168
- )
169
- }
@@ -1,33 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { DataGridCellWrapper } from '@components/ui/data-grid/data-grid-cell-wrapper'
4
-
5
- import { CellVariantProps } from './cell-variant-types'
6
-
7
- export function ReactNodeCell<TData>({
8
- cell,
9
- table,
10
- rowIndex,
11
- columnId,
12
- isFocused,
13
- isSelected,
14
- }: Readonly<CellVariantProps<TData, React.ReactNode>>) {
15
- const children = cell.getValue()
16
- const containerRef = React.useRef<HTMLDivElement>(null)
17
-
18
- return (
19
- <DataGridCellWrapper
20
- ref={containerRef}
21
- cell={cell}
22
- table={table}
23
- rowIndex={rowIndex}
24
- columnId={columnId}
25
- isEditing={false}
26
- isFocused={isFocused}
27
- isSelected={isSelected}
28
- className="flex size-full justify-center p-0"
29
- >
30
- {children ?? null}
31
- </DataGridCellWrapper>
32
- )
33
- }