@postxl/generators 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/frontend-core/frontend.generator.d.ts +0 -58
  2. package/dist/frontend-core/frontend.generator.js +6 -172
  3. package/dist/frontend-core/frontend.generator.js.map +1 -1
  4. package/dist/frontend-core/template/README.md +1 -1
  5. package/dist/frontend-core/template/src/components/admin/table-filter.tsx +1 -5
  6. package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +10 -4
  7. package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +2 -3
  8. package/dist/frontend-core/template/src/pages/error/default-error.page.tsx +1 -1
  9. package/dist/frontend-core/template/src/pages/error/not-found-error.page.tsx +1 -1
  10. package/dist/frontend-core/template/src/styles/styles.css +13 -1
  11. package/dist/frontend-core/template/tsconfig.json +2 -0
  12. package/dist/frontend-core/types/component.d.ts +1 -1
  13. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js +4 -6
  14. package/dist/frontend-forms/generators/discriminated-union/fields.generator.js.map +1 -1
  15. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js +1 -1
  16. package/dist/frontend-forms/generators/discriminated-union/inputs.generator.js.map +1 -1
  17. package/dist/frontend-forms/generators/enum/inputs.generator.js +1 -1
  18. package/dist/frontend-forms/generators/enum/inputs.generator.js.map +1 -1
  19. package/dist/frontend-forms/generators/model/forms.generator.js +8 -12
  20. package/dist/frontend-forms/generators/model/forms.generator.js.map +1 -1
  21. package/dist/frontend-forms/generators/model/inputs.generator.js +2 -6
  22. package/dist/frontend-forms/generators/model/inputs.generator.js.map +1 -1
  23. package/dist/frontend-forms/template/src/components/ui/field/field.tsx +1 -4
  24. package/dist/frontend-tables/generators/model-table.generator.js +1 -5
  25. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  26. package/package.json +3 -2
  27. package/dist/frontend-core/template/src/components/ui/accordion/accordion.stories.tsx +0 -47
  28. package/dist/frontend-core/template/src/components/ui/accordion/accordion.tsx +0 -52
  29. package/dist/frontend-core/template/src/components/ui/admin-sidebar/admin-sidebar.tsx +0 -195
  30. package/dist/frontend-core/template/src/components/ui/alert/alert.stories.tsx +0 -61
  31. package/dist/frontend-core/template/src/components/ui/alert/alert.tsx +0 -45
  32. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.stories.tsx +0 -52
  33. package/dist/frontend-core/template/src/components/ui/alert-dialog/alert-dialog.tsx +0 -105
  34. package/dist/frontend-core/template/src/components/ui/avatar/avatar.stories.tsx +0 -30
  35. package/dist/frontend-core/template/src/components/ui/avatar/avatar.tsx +0 -39
  36. package/dist/frontend-core/template/src/components/ui/badge/badge.stories.tsx +0 -78
  37. package/dist/frontend-core/template/src/components/ui/badge/badge.tsx +0 -48
  38. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.stories.tsx +0 -67
  39. package/dist/frontend-core/template/src/components/ui/breadcrumb/breadcrumb.tsx +0 -85
  40. package/dist/frontend-core/template/src/components/ui/button/button.stories.tsx +0 -150
  41. package/dist/frontend-core/template/src/components/ui/button/button.tsx +0 -68
  42. package/dist/frontend-core/template/src/components/ui/calendar/calendar.stories.tsx +0 -160
  43. package/dist/frontend-core/template/src/components/ui/calendar/calendar.tsx +0 -293
  44. package/dist/frontend-core/template/src/components/ui/card/card.stories.tsx +0 -77
  45. package/dist/frontend-core/template/src/components/ui/card/card.tsx +0 -45
  46. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.stories.tsx +0 -29
  47. package/dist/frontend-core/template/src/components/ui/card-hover/card-hover.tsx +0 -28
  48. package/dist/frontend-core/template/src/components/ui/carousel/carousel.stories.tsx +0 -154
  49. package/dist/frontend-core/template/src/components/ui/carousel/carousel.tsx +0 -227
  50. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.stories.tsx +0 -106
  51. package/dist/frontend-core/template/src/components/ui/checkbox/checkbox.tsx +0 -88
  52. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.stories.tsx +0 -90
  53. package/dist/frontend-core/template/src/components/ui/checkbox/shadcn-checkbox.tsx +0 -54
  54. package/dist/frontend-core/template/src/components/ui/collapse/collapse.stories.tsx +0 -52
  55. package/dist/frontend-core/template/src/components/ui/collapse/collapse.tsx +0 -9
  56. package/dist/frontend-core/template/src/components/ui/combobox/combobox.stories.tsx +0 -207
  57. package/dist/frontend-core/template/src/components/ui/combobox/combobox.tsx +0 -79
  58. package/dist/frontend-core/template/src/components/ui/command/command.stories.tsx +0 -186
  59. package/dist/frontend-core/template/src/components/ui/command/command.tsx +0 -165
  60. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.stories.tsx +0 -160
  61. package/dist/frontend-core/template/src/components/ui/command-palette/command-palette.tsx +0 -134
  62. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.stories.tsx +0 -198
  63. package/dist/frontend-core/template/src/components/ui/content-frame/content-frame.tsx +0 -100
  64. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.stories.tsx +0 -78
  65. package/dist/frontend-core/template/src/components/ui/context-menu/context-menu.tsx +0 -179
  66. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/cell-variant-types.ts +0 -11
  67. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/checkbox-cell.tsx +0 -116
  68. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/date-cell.tsx +0 -157
  69. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/gantt-cell.tsx +0 -82
  70. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/long-text-cell.tsx +0 -180
  71. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/multi-select-cell.tsx +0 -280
  72. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/number-cell.tsx +0 -169
  73. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/react-node-cell.tsx +0 -33
  74. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/select-cell.tsx +0 -175
  75. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/short-text-cell.tsx +0 -138
  76. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timeline.tsx +0 -92
  77. package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timerange-picker.tsx +0 -330
  78. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell-wrapper.tsx +0 -212
  79. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell.tsx +0 -157
  80. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-column-header.tsx +0 -340
  81. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-context-menu.tsx +0 -271
  82. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-row.tsx +0 -123
  83. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-search.tsx +0 -211
  84. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-types.ts +0 -159
  85. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-utils.ts +0 -67
  86. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-view-menu.tsx +0 -360
  87. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.stories.tsx +0 -780
  88. package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.tsx +0 -217
  89. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-callback-ref.ts +0 -22
  90. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-data-grid.tsx +0 -1892
  91. package/dist/frontend-core/template/src/components/ui/data-grid/hooks/use-debounced-callback.ts +0 -19
  92. package/dist/frontend-core/template/src/components/ui/data-grid/styles.css +0 -3
  93. package/dist/frontend-core/template/src/components/ui/data-table/context-menu-simple.tsx +0 -141
  94. package/dist/frontend-core/template/src/components/ui/data-table/data-table.stories.tsx +0 -146
  95. package/dist/frontend-core/template/src/components/ui/data-table/data-table.tsx +0 -447
  96. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-array-cell-renderer.tsx +0 -77
  97. package/dist/frontend-core/template/src/components/ui/data-table/renderers/country-cell-renderer.tsx +0 -56
  98. package/dist/frontend-core/template/src/components/ui/data-table/renderers/favorite-cell-renderer.tsx +0 -68
  99. package/dist/frontend-core/template/src/components/ui/data-table/renderers/links-cell-renderer.tsx +0 -205
  100. package/dist/frontend-core/template/src/components/ui/data-table/utils/columns.ts +0 -351
  101. package/dist/frontend-core/template/src/components/ui/data-table/utils/data-table.utils.ts +0 -49
  102. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.stories.tsx +0 -149
  103. package/dist/frontend-core/template/src/components/ui/date-picker/date-picker.tsx +0 -30
  104. package/dist/frontend-core/template/src/components/ui/dialog/dialog.stories.tsx +0 -80
  105. package/dist/frontend-core/template/src/components/ui/dialog/dialog.tsx +0 -134
  106. package/dist/frontend-core/template/src/components/ui/drawer/drawer.stories.tsx +0 -104
  107. package/dist/frontend-core/template/src/components/ui/drawer/drawer.tsx +0 -87
  108. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.stories.tsx +0 -168
  109. package/dist/frontend-core/template/src/components/ui/dropdown-menu/dropdown-menu.tsx +0 -225
  110. package/dist/frontend-core/template/src/components/ui/input/input.stories.tsx +0 -141
  111. package/dist/frontend-core/template/src/components/ui/input/input.tsx +0 -47
  112. package/dist/frontend-core/template/src/components/ui/label/label.stories.tsx +0 -41
  113. package/dist/frontend-core/template/src/components/ui/label/label.tsx +0 -20
  114. package/dist/frontend-core/template/src/components/ui/loader/loader.stories.tsx +0 -45
  115. package/dist/frontend-core/template/src/components/ui/loader/loader.tsx +0 -17
  116. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.stories.tsx +0 -114
  117. package/dist/frontend-core/template/src/components/ui/mark-value-renderer/mark-value-renderer.tsx +0 -48
  118. package/dist/frontend-core/template/src/components/ui/menubar/menu.stories.tsx +0 -134
  119. package/dist/frontend-core/template/src/components/ui/menubar/menubar.tsx +0 -208
  120. package/dist/frontend-core/template/src/components/ui/modal/modal.stories.tsx +0 -297
  121. package/dist/frontend-core/template/src/components/ui/modal/modal.tsx +0 -80
  122. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.stories.tsx +0 -213
  123. package/dist/frontend-core/template/src/components/ui/navigation-menu/navigation-menu.tsx +0 -142
  124. package/dist/frontend-core/template/src/components/ui/pagination/pagination.stories.tsx +0 -49
  125. package/dist/frontend-core/template/src/components/ui/pagination/pagination.tsx +0 -84
  126. package/dist/frontend-core/template/src/components/ui/popover/popover.stories.tsx +0 -82
  127. package/dist/frontend-core/template/src/components/ui/popover/popover.tsx +0 -55
  128. package/dist/frontend-core/template/src/components/ui/progress/progress.stories.tsx +0 -80
  129. package/dist/frontend-core/template/src/components/ui/progress/progress.tsx +0 -17
  130. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.stories.tsx +0 -154
  131. package/dist/frontend-core/template/src/components/ui/radio-group/radio-group.tsx +0 -68
  132. package/dist/frontend-core/template/src/components/ui/resizable/resizable.stories.tsx +0 -73
  133. package/dist/frontend-core/template/src/components/ui/resizable/resizeable.tsx +0 -38
  134. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.stories.tsx +0 -55
  135. package/dist/frontend-core/template/src/components/ui/scroll-area/scroll-area.tsx +0 -39
  136. package/dist/frontend-core/template/src/components/ui/select/select.stories.tsx +0 -297
  137. package/dist/frontend-core/template/src/components/ui/select/select.tsx +0 -227
  138. package/dist/frontend-core/template/src/components/ui/separator/separator.tsx +0 -21
  139. package/dist/frontend-core/template/src/components/ui/separator/seperator.stories.tsx +0 -25
  140. package/dist/frontend-core/template/src/components/ui/sheet/sheet.stories.tsx +0 -45
  141. package/dist/frontend-core/template/src/components/ui/sheet/sheet.tsx +0 -107
  142. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.stories.tsx +0 -26
  143. package/dist/frontend-core/template/src/components/ui/skeleton/skeleton.tsx +0 -7
  144. package/dist/frontend-core/template/src/components/ui/slider/slider.stories.tsx +0 -101
  145. package/dist/frontend-core/template/src/components/ui/slider/slider.tsx +0 -98
  146. package/dist/frontend-core/template/src/components/ui/spinner/spinner.stories.tsx +0 -19
  147. package/dist/frontend-core/template/src/components/ui/spinner/spinner.tsx +0 -21
  148. package/dist/frontend-core/template/src/components/ui/switch/switch.stories.tsx +0 -33
  149. package/dist/frontend-core/template/src/components/ui/switch/switch.tsx +0 -28
  150. package/dist/frontend-core/template/src/components/ui/tabs/tabs.stories.tsx +0 -215
  151. package/dist/frontend-core/template/src/components/ui/tabs/tabs.tsx +0 -70
  152. package/dist/frontend-core/template/src/components/ui/textarea/textarea.stories.tsx +0 -138
  153. package/dist/frontend-core/template/src/components/ui/textarea/textarea.tsx +0 -40
  154. package/dist/frontend-core/template/src/components/ui/toast/toast.mdx +0 -31
  155. package/dist/frontend-core/template/src/components/ui/toast/toast.stories.tsx +0 -89
  156. package/dist/frontend-core/template/src/components/ui/toggle/toggle.stories.tsx +0 -65
  157. package/dist/frontend-core/template/src/components/ui/toggle/toggle.tsx +0 -38
  158. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.stories.tsx +0 -85
  159. package/dist/frontend-core/template/src/components/ui/toggle-group/toggle-group.tsx +0 -54
  160. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.stories.tsx +0 -29
  161. package/dist/frontend-core/template/src/components/ui/tooltip/tooltip.tsx +0 -29
@@ -1,157 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { Calendar } from '@components/ui/calendar/calendar'
4
- import { DataGridCellWrapper } from '@components/ui/data-grid/data-grid-cell-wrapper'
5
- import { Popover, PopoverAnchor, PopoverContent } from '@components/ui/popover/popover'
6
- import { cn } from '@lib/utils'
7
-
8
- import { CellVariantProps } from './cell-variant-types'
9
-
10
- /**
11
- * Parse a date value (Date object or ISO string YYYY-MM-DD) as a local date, not UTC.
12
- * new Date('2025-12-01') interprets as UTC, causing timezone shifts.
13
- */
14
- function parseToLocalDate(value: string | Date | null | undefined): Date | undefined {
15
- if (!value) {
16
- return undefined
17
- }
18
- if (value instanceof Date) {
19
- return value
20
- }
21
- if (typeof value !== 'string') {
22
- return undefined
23
- }
24
-
25
- // Parse ISO date string in local timezone by explicitly setting year, month, day
26
- const regex = /^(\d{4})-(\d{2})-(\d{2})$/
27
- const match = regex.exec(value)
28
- if (!match) {
29
- return undefined
30
- }
31
- const year = Number.parseInt(match[1], 10)
32
- const month = Number.parseInt(match[2], 10) - 1 // months are 0-indexed
33
- const day = Number.parseInt(match[3], 10)
34
-
35
- return new Date(year, month, day)
36
- }
37
-
38
- function formatDateToISOString(date: Date): string {
39
- const year = date.getFullYear()
40
- const month = String(date.getMonth() + 1).padStart(2, '0')
41
- const day = String(date.getDate()).padStart(2, '0')
42
- return `${year}-${month}-${day}`
43
- }
44
-
45
- export function DateCell<TData>({
46
- cell,
47
- table,
48
- rowIndex,
49
- columnId,
50
- isFocused,
51
- isEditing,
52
- isSelected,
53
- }: Readonly<CellVariantProps<TData, string>>) {
54
- const initialValue = cell.getValue() as string | Date | null | undefined
55
- const [value, setValue] = React.useState<Date | undefined>(parseToLocalDate(initialValue))
56
- const [open, setOpen] = React.useState(false)
57
- const containerRef = React.useRef<HTMLDivElement>(null)
58
- const meta = table.options.meta
59
-
60
- const prevInitialValueRef = React.useRef(initialValue)
61
- if (initialValue !== prevInitialValueRef.current) {
62
- prevInitialValueRef.current = initialValue
63
- setValue(parseToLocalDate(initialValue))
64
- }
65
-
66
- const onDateSelect = React.useCallback(
67
- (date: Date | undefined) => {
68
- if (!date) {
69
- return
70
- }
71
-
72
- setValue(date)
73
- meta?.onDataUpdate?.({ rowIndex, columnId, value: formatDateToISOString(date) })
74
- setOpen(false)
75
- meta?.onCellEditingStop?.()
76
- },
77
- [meta, rowIndex, columnId],
78
- )
79
-
80
- const onOpenChange = React.useCallback(
81
- (isOpen: boolean) => {
82
- setOpen(isOpen)
83
- if (!isOpen && isEditing) {
84
- meta?.onCellEditingStop?.()
85
- }
86
- },
87
- [isEditing, meta],
88
- )
89
-
90
- const onWrapperKeyDown = React.useCallback(
91
- (event: React.KeyboardEvent<HTMLDivElement>) => {
92
- if (isEditing) {
93
- if (event.key === 'Escape') {
94
- event.preventDefault()
95
- setValue(parseToLocalDate(initialValue))
96
- setOpen(false)
97
- } else if (event.key === 'Tab') {
98
- event.preventDefault()
99
- setOpen(false)
100
- meta?.onCellEditingStop?.({
101
- direction: event.shiftKey ? 'left' : 'right',
102
- })
103
- }
104
- }
105
- },
106
- [isEditing, initialValue, meta],
107
- )
108
-
109
- React.useEffect(() => {
110
- setOpen(isEditing)
111
- }, [isEditing])
112
-
113
- React.useEffect(() => {
114
- if (isFocused && !isEditing && !meta?.searchOpen && !meta?.isScrolling && containerRef.current) {
115
- containerRef.current.focus()
116
- }
117
- }, [isFocused, isEditing, meta?.searchOpen, meta?.isScrolling])
118
-
119
- return (
120
- <DataGridCellWrapper
121
- ref={containerRef}
122
- cell={cell}
123
- table={table}
124
- rowIndex={rowIndex}
125
- columnId={columnId}
126
- isEditing={isEditing}
127
- isFocused={isFocused}
128
- isSelected={isSelected}
129
- onKeyDown={onWrapperKeyDown}
130
- >
131
- <Popover open={open} onOpenChange={onOpenChange}>
132
- <PopoverAnchor asChild>
133
- <span
134
- data-slot="grid-cell-content"
135
- className={cn('tabular-nums', (cell.column.columnDef.meta as any)?.align ?? 'text-right')}
136
- >
137
- {value?.toLocaleDateString() ?? ''}
138
- </span>
139
- </PopoverAnchor>
140
- {isEditing && (
141
- <PopoverContent data-grid-cell-editor="" align="start" sideOffset={10} className="w-auto p-0">
142
- <Calendar
143
- showYearNavigation={true}
144
- autoFocus
145
- captionLayout="dropdown"
146
- mode="single"
147
- className="rounded-md border border-border shadow-sm"
148
- defaultMonth={value}
149
- selected={value}
150
- onSelect={onDateSelect}
151
- />
152
- </PopoverContent>
153
- )}
154
- </Popover>
155
- </DataGridCellWrapper>
156
- )
157
- }
@@ -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
- }