@postxl/generators 1.1.1 → 1.2.1

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 (181) hide show
  1. package/dist/backend-core/backend.generator.js +4 -1
  2. package/dist/backend-core/backend.generator.js.map +1 -1
  3. package/dist/backend-core/generators/jest.config.generator.d.ts +2 -0
  4. package/dist/backend-core/{template/jest.config.ts → generators/jest.config.generator.js} +12 -8
  5. package/dist/backend-core/generators/jest.config.generator.js.map +1 -0
  6. package/dist/backend-core/generators/tsconfig.generator.d.ts +1 -1
  7. package/dist/backend-core/generators/tsconfig.generator.js +1 -9
  8. package/dist/backend-core/generators/tsconfig.generator.js.map +1 -1
  9. package/dist/backend-core/types.d.ts +2 -0
  10. package/dist/base/base.generator.js +12 -8
  11. package/dist/base/base.generator.js.map +1 -1
  12. package/dist/e2e/template/e2e/package.json +1 -1
  13. package/dist/e2e/template/e2e/specs/example.spec.ts +1 -1
  14. package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
  15. package/dist/frontend-core/frontend.generator.d.ts +0 -58
  16. package/dist/frontend-core/frontend.generator.js +9 -173
  17. package/dist/frontend-core/frontend.generator.js.map +1 -1
  18. package/dist/frontend-core/generators/tsconfig.generator.js +2 -0
  19. package/dist/frontend-core/generators/tsconfig.generator.js.map +1 -1
  20. package/dist/frontend-core/template/README.md +1 -1
  21. package/dist/frontend-core/template/src/components/admin/table-filter.tsx +1 -5
  22. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +10 -4
  23. package/dist/frontend-core/template/src/lib/query-client.ts +45 -4
  24. package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +2 -3
  25. package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +45 -12
  26. package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +1 -1
  27. package/dist/frontend-core/template/src/styles/styles.css +13 -1
  28. package/dist/frontend-core/template/tsconfig.json +2 -0
  29. package/dist/frontend-core/types/component.d.ts +1 -1
  30. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js +4 -6
  31. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js.map +1 -1
  32. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js +1 -1
  33. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js.map +1 -1
  34. package/dist/frontend-forms/generators/enum/inputs.generator.js +1 -1
  35. package/dist/frontend-forms/generators/enum/inputs.generator.js.map +1 -1
  36. package/dist/frontend-forms/generators/model/forms.generator.js +8 -12
  37. package/dist/frontend-forms/generators/model/forms.generator.js.map +1 -1
  38. package/dist/frontend-forms/generators/model/inputs.generator.js +2 -6
  39. package/dist/frontend-forms/generators/model/inputs.generator.js.map +1 -1
  40. package/dist/frontend-forms/template/src/components/ui/field/field.tsx +1 -4
  41. package/dist/frontend-tables/generators/model-table.generator.js +1 -5
  42. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  43. package/package.json +4 -3
  44. package/dist/e2e/generators/package-json.generator.d.ts +0 -2
  45. package/dist/e2e/generators/package-json.generator.js +0 -29
  46. package/dist/e2e/generators/package-json.generator.js.map +0 -1
  47. package/dist/frontend-core/template/src/components/ui/accordion/accordion.stories.tsx +0 -47
  48. package/dist/frontend-core/template/src/components/ui/accordion/accordion.tsx +0 -52
  49. package/dist/frontend-core/template/src/components/ui/admin-sidebar/admin-sidebar.tsx +0 -195
  50. package/dist/frontend-core/template/src/components/ui/alert/alert.stories.tsx +0 -61
  51. package/dist/frontend-core/template/src/components/ui/alert/alert.tsx +0 -45
  52. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.stories.tsx +0 -52
  53. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.tsx +0 -105
  54. package/dist/frontend-core/template/src/components/ui/avatar/avatar.stories.tsx +0 -30
  55. package/dist/frontend-core/template/src/components/ui/avatar/avatar.tsx +0 -39
  56. package/dist/frontend-core/template/src/components/ui/badge/badge.stories.tsx +0 -78
  57. package/dist/frontend-core/template/src/components/ui/badge/badge.tsx +0 -48
  58. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.stories.tsx +0 -67
  59. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.tsx +0 -85
  60. package/dist/frontend-core/template/src/components/ui/button/button.stories.tsx +0 -150
  61. package/dist/frontend-core/template/src/components/ui/button/button.tsx +0 -68
  62. package/dist/frontend-core/template/src/components/ui/calendar/calendar.stories.tsx +0 -160
  63. package/dist/frontend-core/template/src/components/ui/calendar/calendar.tsx +0 -293
  64. package/dist/frontend-core/template/src/components/ui/card/card.stories.tsx +0 -77
  65. package/dist/frontend-core/template/src/components/ui/card/card.tsx +0 -45
  66. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.stories.tsx +0 -29
  67. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.tsx +0 -28
  68. package/dist/frontend-core/template/src/components/ui/carousel/carousel.stories.tsx +0 -154
  69. package/dist/frontend-core/template/src/components/ui/carousel/carousel.tsx +0 -227
  70. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.stories.tsx +0 -106
  71. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.tsx +0 -88
  72. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.stories.tsx +0 -90
  73. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.tsx +0 -54
  74. package/dist/frontend-core/template/src/components/ui/collapse/collapse.stories.tsx +0 -52
  75. package/dist/frontend-core/template/src/components/ui/collapse/collapse.tsx +0 -9
  76. package/dist/frontend-core/template/src/components/ui/combobox/combobox.stories.tsx +0 -207
  77. package/dist/frontend-core/template/src/components/ui/combobox/combobox.tsx +0 -79
  78. package/dist/frontend-core/template/src/components/ui/command/command.stories.tsx +0 -186
  79. package/dist/frontend-core/template/src/components/ui/command/command.tsx +0 -165
  80. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.stories.tsx +0 -160
  81. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.tsx +0 -134
  82. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.stories.tsx +0 -198
  83. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.tsx +0 -100
  84. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.stories.tsx +0 -78
  85. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.tsx +0 -179
  86. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/cell-variant-types.ts +0 -11
  87. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/checkbox-cell.tsx +0 -116
  88. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/date-cell.tsx +0 -157
  89. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/gantt-cell.tsx +0 -82
  90. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/long-text-cell.tsx +0 -180
  91. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/multi-select-cell.tsx +0 -280
  92. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/number-cell.tsx +0 -169
  93. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/react-node-cell.tsx +0 -33
  94. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/select-cell.tsx +0 -175
  95. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/short-text-cell.tsx +0 -138
  96. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timeline.tsx +0 -92
  97. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timerange-picker.tsx +0 -330
  98. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell-wrapper.tsx +0 -212
  99. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell.tsx +0 -157
  100. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-column-header.tsx +0 -340
  101. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-context-menu.tsx +0 -271
  102. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-row.tsx +0 -123
  103. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-search.tsx +0 -211
  104. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-types.ts +0 -159
  105. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-utils.ts +0 -67
  106. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-view-menu.tsx +0 -360
  107. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.stories.tsx +0 -780
  108. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.tsx +0 -217
  109. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-callback-ref.ts +0 -22
  110. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-data-grid.tsx +0 -1892
  111. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-debounced-callback.ts +0 -19
  112. package/dist/frontend-core/template/src/components/ui/data-grid/styles.css +0 -3
  113. package/dist/frontend-core/template/src/components/ui/data-table/context-menu-simple.tsx +0 -141
  114. package/dist/frontend-core/template/src/components/ui/data-table/data-table.stories.tsx +0 -146
  115. package/dist/frontend-core/template/src/components/ui/data-table/data-table.tsx +0 -447
  116. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-array-cell-renderer.tsx +0 -77
  117. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-cell-renderer.tsx +0 -56
  118. package/dist/frontend-core/template/src/components/ui/data-table/renderers/favorite-cell-renderer.tsx +0 -68
  119. package/dist/frontend-core/template/src/components/ui/data-table/renderers/links-cell-renderer.tsx +0 -205
  120. package/dist/frontend-core/template/src/components/ui/data-table/utils/columns.ts +0 -351
  121. package/dist/frontend-core/template/src/components/ui/data-table/utils/data-table.utils.ts +0 -49
  122. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.stories.tsx +0 -149
  123. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.tsx +0 -30
  124. package/dist/frontend-core/template/src/components/ui/dialog/dialog.stories.tsx +0 -80
  125. package/dist/frontend-core/template/src/components/ui/dialog/dialog.tsx +0 -134
  126. package/dist/frontend-core/template/src/components/ui/drawer/drawer.stories.tsx +0 -104
  127. package/dist/frontend-core/template/src/components/ui/drawer/drawer.tsx +0 -87
  128. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.stories.tsx +0 -168
  129. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.tsx +0 -225
  130. package/dist/frontend-core/template/src/components/ui/input/input.stories.tsx +0 -141
  131. package/dist/frontend-core/template/src/components/ui/input/input.tsx +0 -47
  132. package/dist/frontend-core/template/src/components/ui/label/label.stories.tsx +0 -41
  133. package/dist/frontend-core/template/src/components/ui/label/label.tsx +0 -20
  134. package/dist/frontend-core/template/src/components/ui/loader/loader.stories.tsx +0 -45
  135. package/dist/frontend-core/template/src/components/ui/loader/loader.tsx +0 -17
  136. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.stories.tsx +0 -114
  137. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.tsx +0 -48
  138. package/dist/frontend-core/template/src/components/ui/menubar/menu.stories.tsx +0 -134
  139. package/dist/frontend-core/template/src/components/ui/menubar/menubar.tsx +0 -208
  140. package/dist/frontend-core/template/src/components/ui/modal/modal.stories.tsx +0 -297
  141. package/dist/frontend-core/template/src/components/ui/modal/modal.tsx +0 -80
  142. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.stories.tsx +0 -213
  143. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.tsx +0 -142
  144. package/dist/frontend-core/template/src/components/ui/pagination/pagination.stories.tsx +0 -49
  145. package/dist/frontend-core/template/src/components/ui/pagination/pagination.tsx +0 -84
  146. package/dist/frontend-core/template/src/components/ui/popover/popover.stories.tsx +0 -82
  147. package/dist/frontend-core/template/src/components/ui/popover/popover.tsx +0 -55
  148. package/dist/frontend-core/template/src/components/ui/progress/progress.stories.tsx +0 -80
  149. package/dist/frontend-core/template/src/components/ui/progress/progress.tsx +0 -17
  150. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.stories.tsx +0 -154
  151. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.tsx +0 -68
  152. package/dist/frontend-core/template/src/components/ui/resizable/resizable.stories.tsx +0 -73
  153. package/dist/frontend-core/template/src/components/ui/resizable/resizeable.tsx +0 -38
  154. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.stories.tsx +0 -55
  155. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.tsx +0 -39
  156. package/dist/frontend-core/template/src/components/ui/select/select.stories.tsx +0 -297
  157. package/dist/frontend-core/template/src/components/ui/select/select.tsx +0 -227
  158. package/dist/frontend-core/template/src/components/ui/separator/separator.tsx +0 -21
  159. package/dist/frontend-core/template/src/components/ui/separator/seperator.stories.tsx +0 -25
  160. package/dist/frontend-core/template/src/components/ui/sheet/sheet.stories.tsx +0 -45
  161. package/dist/frontend-core/template/src/components/ui/sheet/sheet.tsx +0 -107
  162. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.stories.tsx +0 -26
  163. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.tsx +0 -7
  164. package/dist/frontend-core/template/src/components/ui/slider/slider.stories.tsx +0 -101
  165. package/dist/frontend-core/template/src/components/ui/slider/slider.tsx +0 -98
  166. package/dist/frontend-core/template/src/components/ui/spinner/spinner.stories.tsx +0 -19
  167. package/dist/frontend-core/template/src/components/ui/spinner/spinner.tsx +0 -21
  168. package/dist/frontend-core/template/src/components/ui/switch/switch.stories.tsx +0 -33
  169. package/dist/frontend-core/template/src/components/ui/switch/switch.tsx +0 -28
  170. package/dist/frontend-core/template/src/components/ui/tabs/tabs.stories.tsx +0 -215
  171. package/dist/frontend-core/template/src/components/ui/tabs/tabs.tsx +0 -70
  172. package/dist/frontend-core/template/src/components/ui/textarea/textarea.stories.tsx +0 -138
  173. package/dist/frontend-core/template/src/components/ui/textarea/textarea.tsx +0 -40
  174. package/dist/frontend-core/template/src/components/ui/toast/toast.mdx +0 -31
  175. package/dist/frontend-core/template/src/components/ui/toast/toast.stories.tsx +0 -89
  176. package/dist/frontend-core/template/src/components/ui/toggle/toggle.stories.tsx +0 -65
  177. package/dist/frontend-core/template/src/components/ui/toggle/toggle.tsx +0 -38
  178. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.stories.tsx +0 -85
  179. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.tsx +0 -54
  180. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.stories.tsx +0 -29
  181. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.tsx +0 -29
@@ -1,82 +0,0 @@
1
- import type { Cell, Table } 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
- export type GanttCellProps<TData> = {
9
- cell: Cell<TData, { start: Date; end: Date; barClassName?: string } | null>
10
- table: Table<TData>
11
- rowIndex: number
12
- columnId: string
13
- isEditing: boolean
14
- isFocused: boolean
15
- isSelected: boolean
16
- }
17
-
18
- export function GanttCell<TData>({
19
- cell,
20
- table,
21
- rowIndex,
22
- columnId,
23
- isFocused,
24
- isEditing,
25
- isSelected,
26
- }: Readonly<GanttCellProps<TData>>) {
27
- const initialValue = cell.getValue() as { start: Date; end: Date; barClassName?: string } | null
28
- const containerRef = React.useRef<HTMLDivElement>(null)
29
- const cellOpts = (cell.column.columnDef.meta as any)?.cell
30
-
31
- const ts = cellOpts?.dateRangeFrom ?? cellOpts?.timelineStart
32
- const te = cellOpts?.dateRangeTo ?? cellOpts?.timelineEnd
33
-
34
- const timelineStartMs = ts instanceof Date ? ts.getTime() : undefined
35
- const timelineEndMs = te instanceof Date ? te.getTime() : undefined
36
-
37
- if (!timelineStartMs || !timelineEndMs) {
38
- return null
39
- }
40
-
41
- const timelineDurationMs = timelineEndMs - timelineStartMs
42
- const hasValidDates = initialValue && initialValue.start instanceof Date && initialValue.end instanceof Date
43
-
44
- const msUntilStart = hasValidDates ? initialValue.start.getTime() - timelineStartMs : 0
45
- // end - start time considering timeline bounds
46
- const barWidthMs = hasValidDates
47
- ? Math.min(initialValue.end.getTime(), timelineEndMs) - Math.max(initialValue.start.getTime(), timelineStartMs)
48
- : 0
49
-
50
- return (
51
- <DataGridCellWrapper
52
- ref={containerRef}
53
- cell={cell}
54
- table={table}
55
- rowIndex={rowIndex}
56
- columnId={columnId}
57
- isEditing={isEditing}
58
- isFocused={isFocused}
59
- isSelected={isSelected}
60
- className="px-1"
61
- >
62
- <div className="size-full flex overflow-hidden">
63
- {hasValidDates && (
64
- <>
65
- <div
66
- className="shrink-0"
67
- style={{
68
- width: `${Math.max(0, (msUntilStart / timelineDurationMs) * 100)}%`,
69
- }}
70
- />
71
- <div
72
- className={cn('shrink-0 h-full rounded-sm bg-primary', initialValue.barClassName)}
73
- style={{
74
- width: `${(barWidthMs / timelineDurationMs) * 100}%`,
75
- }}
76
- />
77
- </>
78
- )}
79
- </div>
80
- </DataGridCellWrapper>
81
- )
82
- }
@@ -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
- }