@postxl/generators 1.0.10 → 1.0.13
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.
- package/dist/backend-authentication/authentication.generator.js +1 -1
- package/dist/backend-core/backend.generator.js +20 -21
- package/dist/backend-core/backend.generator.js.map +1 -1
- package/dist/backend-core/generators/main.generator.js +10 -2
- package/dist/backend-core/generators/main.generator.js.map +1 -1
- package/dist/backend-core/generators/tsconfig.generator.js +1 -0
- package/dist/backend-core/generators/tsconfig.generator.js.map +1 -1
- package/dist/backend-core/template/jest.config.ts +2 -2
- package/dist/backend-core/template/libs/utils/src/pagination.test.ts +1 -1
- package/dist/backend-database-prisma/generators/prisma-schema.generator.js +0 -1
- package/dist/backend-database-prisma/generators/prisma-schema.generator.js.map +1 -1
- package/dist/backend-database-prisma/prisma.generator.js +4 -4
- package/dist/backend-database-prisma/prisma.generator.js.map +1 -1
- package/dist/backend-database-prisma/template/prisma.config.ts +0 -1
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.js +2 -2
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-s3/s3.generator.js +1 -1
- package/dist/base/base.generator.js +14 -16
- package/dist/base/base.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-sidebar.generator.js +7 -12
- package/dist/frontend-admin/generators/admin-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/audit-log-sidebar.generator.js +36 -53
- package/dist/frontend-admin/generators/audit-log-sidebar.generator.js.map +1 -1
- package/dist/frontend-core/frontend.generator.js +1 -0
- package/dist/frontend-core/frontend.generator.js.map +1 -1
- package/dist/frontend-core/template/src/components/ui/color-mode-toggle/color-mode-toggle.tsx +93 -0
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/checkbox-cell.tsx +3 -3
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/date-cell.tsx +1 -2
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/long-text-cell.tsx +1 -0
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/multi-select-cell.tsx +5 -9
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/number-cell.tsx +5 -3
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/react-node-cell.tsx +2 -1
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/select-cell.tsx +4 -4
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/short-text-cell.tsx +4 -3
- package/dist/frontend-core/template/src/components/ui/data-grid/cell-variants/utils/gantt-timeline.tsx +6 -5
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell-wrapper.tsx +2 -2
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-cell.tsx +7 -6
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-column-header.tsx +13 -20
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-row.tsx +2 -2
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-search.tsx +2 -2
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-utils.ts +3 -14
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid-view-menu.tsx +3 -3
- package/dist/frontend-core/template/src/components/ui/data-grid/data-grid.tsx +6 -6
- package/dist/frontend-core/template/src/components/ui/data-grid/styles.css +3 -0
- package/dist/frontend-core/template/src/components/ui/data-table/data-table.tsx +11 -12
- package/dist/frontend-core/template/src/context-providers/auth-context-provider.tsx +1 -1
- package/dist/frontend-core/template/src/context-providers/theme-context-provider.tsx +52 -67
- package/dist/frontend-core/template/src/main.tsx +1 -1
- package/dist/frontend-core/template/src/pages/dashboard/dashboard.page.tsx +11 -14
- package/dist/frontend-core/template/src/styles/styles.css +155 -12
- package/dist/frontend-core/template/src/styles/theme-default.css +125 -177
- package/dist/frontend-core/types/component.d.ts +1 -1
- package/dist/frontend-trpc-client/trpc-client.generator.js +1 -1
- package/dist/frontend-trpc-client/trpc-client.generator.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useRef } from 'react'
|
|
2
|
+
|
|
2
3
|
import { GanttTimerangePicker } from './gantt-timerange-picker'
|
|
3
4
|
|
|
4
5
|
// Gantt header component rendering the timeline scale
|
|
@@ -49,7 +50,7 @@ export const GanttTimeline = ({ header }: { header: any }) => {
|
|
|
49
50
|
/* distribute space between years with flex-grow except for first and last as they might only be partial */
|
|
50
51
|
<div
|
|
51
52
|
key={year}
|
|
52
|
-
className="h-full relative flex grow border-l border-
|
|
53
|
+
className="h-full relative flex grow border-l border-muted-foreground/30 first:border-none first:grow-0 last:grow-0 overflow-hidden"
|
|
53
54
|
style={
|
|
54
55
|
// index == 0: first year, index == totalYears -1: last year
|
|
55
56
|
index == 0 || index == totalYears - 1
|
|
@@ -62,7 +63,7 @@ export const GanttTimeline = ({ header }: { header: any }) => {
|
|
|
62
63
|
<div className="absolute bottom-0 left-0 right-0 flex h-1.5 w-full">
|
|
63
64
|
{index == 0 && ts.getMonth() < 11 && (
|
|
64
65
|
<div
|
|
65
|
-
className="h-full border-r border-dashed border-
|
|
66
|
+
className="h-full border-r border-dashed border-muted-foreground/30"
|
|
66
67
|
style={{ width: `${(firstMonthPartialMs / firstYearPartialMs) * 100}%` }}
|
|
67
68
|
/>
|
|
68
69
|
)}
|
|
@@ -71,8 +72,8 @@ export const GanttTimeline = ({ header }: { header: any }) => {
|
|
|
71
72
|
length: (index == totalYears - 1 ? te.getMonth() : 12) - (index == 0 ? ts.getMonth() + 1 : 0),
|
|
72
73
|
}).map((_, monthIndex) => (
|
|
73
74
|
<div
|
|
74
|
-
key={`${year}-${
|
|
75
|
-
className="grow h-full border-r border-dashed border-
|
|
75
|
+
key={`${year}-${monthIndex}`}
|
|
76
|
+
className="grow h-full border-r border-dashed border-muted-foreground/30 last:border-0"
|
|
76
77
|
/>
|
|
77
78
|
))}
|
|
78
79
|
{/* last month with calculated width as it might only be partial */}
|
|
@@ -82,7 +83,7 @@ export const GanttTimeline = ({ header }: { header: any }) => {
|
|
|
82
83
|
</div>
|
|
83
84
|
)}
|
|
84
85
|
|
|
85
|
-
<span className="ps-0.5 -mt-px text-xs text-
|
|
86
|
+
<span className="ps-0.5 -mt-px text-xs text-muted-foreground select-none z-1">{year}</span>
|
|
86
87
|
</div>
|
|
87
88
|
))}
|
|
88
89
|
</div>
|
|
@@ -186,10 +186,10 @@ export function DataGridCellWrapper<TData>({
|
|
|
186
186
|
'text-center': align === 'center',
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
|
-
'ring-1 ring-
|
|
189
|
+
'ring-1 ring-foreground ring-inset dark:ring-secondary-foreground': isFocused,
|
|
190
190
|
'bg-yellow-100 dark:bg-yellow-900/30': isSearchMatch && !isActiveSearchMatch,
|
|
191
191
|
'bg-orange-200 dark:bg-orange-900/50': isActiveSearchMatch,
|
|
192
|
-
'bg-
|
|
192
|
+
'bg-accent-foreground/10': isSelected && !isEditing,
|
|
193
193
|
'cursor-default': !isEditing,
|
|
194
194
|
'**:data-[slot=grid-cell-content]:line-clamp-1': !isEditing && rowHeight === 'short',
|
|
195
195
|
'**:data-[slot=grid-cell-content]:line-clamp-2': !isEditing && rowHeight === 'medium',
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Cell, Table } from '@tanstack/react-table'
|
|
2
|
-
|
|
3
|
-
import { LongTextCell } from './cell-variants/long-text-cell'
|
|
4
|
-
import { NumberCell } from './cell-variants/number-cell'
|
|
5
|
-
import { SelectCell } from './cell-variants/select-cell'
|
|
6
|
-
import { MultiSelectCell } from './cell-variants/multi-select-cell'
|
|
2
|
+
|
|
7
3
|
import { CheckboxCell } from './cell-variants/checkbox-cell'
|
|
8
4
|
import { DateCell } from './cell-variants/date-cell'
|
|
9
|
-
import { ReactNodeCell } from './cell-variants/react-node-cell'
|
|
10
5
|
import { GanttCell } from './cell-variants/gantt-cell'
|
|
6
|
+
import { LongTextCell } from './cell-variants/long-text-cell'
|
|
7
|
+
import { MultiSelectCell } from './cell-variants/multi-select-cell'
|
|
8
|
+
import { NumberCell } from './cell-variants/number-cell'
|
|
9
|
+
import { ReactNodeCell } from './cell-variants/react-node-cell'
|
|
10
|
+
import { SelectCell } from './cell-variants/select-cell'
|
|
11
|
+
import { ShortTextCell } from './cell-variants/short-text-cell'
|
|
11
12
|
|
|
12
13
|
type DataGridCellProps<TData> = {
|
|
13
14
|
cell: Cell<TData, unknown>
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '@components/ui/dropdown-menu/dropdown-menu'
|
|
29
29
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@components/ui/tooltip/tooltip'
|
|
30
30
|
import { cn } from '@lib/utils'
|
|
31
|
+
|
|
31
32
|
import { GanttTimeline } from './cell-variants/utils/gantt-timeline'
|
|
32
33
|
|
|
33
34
|
function getColumnVariant(variant?: Cell['variant']): {
|
|
@@ -139,7 +140,7 @@ export function DataGridColumnHeader<TData, TValue>({
|
|
|
139
140
|
<DropdownMenu>
|
|
140
141
|
<DropdownMenuTrigger
|
|
141
142
|
className={cn(
|
|
142
|
-
'flex size-full items-center justify-between gap-2 p-2 text-sm bg-sidebar-accent/80 font-bold hover:bg-
|
|
143
|
+
'flex size-full items-center justify-between gap-2 p-2 text-sm bg-sidebar-accent/80 font-bold hover:bg-secondary/40 data-[state=open]:bg-secondary/40 [&_svg]:size-4',
|
|
143
144
|
isAnyColumnResizing && 'pointer-events-none',
|
|
144
145
|
className,
|
|
145
146
|
)}
|
|
@@ -151,7 +152,7 @@ export function DataGridColumnHeader<TData, TValue>({
|
|
|
151
152
|
<TooltipProvider>
|
|
152
153
|
<Tooltip delayDuration={100}>
|
|
153
154
|
<TooltipTrigger asChild>
|
|
154
|
-
<columnVariant.icon className="size-3.5 shrink-0 text-
|
|
155
|
+
<columnVariant.icon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
155
156
|
</TooltipTrigger>
|
|
156
157
|
<TooltipContent side="top">
|
|
157
158
|
<p>{columnVariant.label}</p>
|
|
@@ -161,13 +162,13 @@ export function DataGridColumnHeader<TData, TValue>({
|
|
|
161
162
|
)}
|
|
162
163
|
<span className="truncate">{label}</span>
|
|
163
164
|
</div>
|
|
164
|
-
<ChevronDownIcon className="shrink-0 text-
|
|
165
|
+
<ChevronDownIcon className="shrink-0 text-muted-foreground" />
|
|
165
166
|
</DropdownMenuTrigger>
|
|
166
167
|
<DropdownMenuContent align="start" sideOffset={0} className="w-60">
|
|
167
168
|
{column.getCanSort() && (
|
|
168
169
|
<>
|
|
169
170
|
<DropdownMenuCheckboxItem
|
|
170
|
-
className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-
|
|
171
|
+
className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground"
|
|
171
172
|
checked={column.getIsSorted() === 'asc'}
|
|
172
173
|
onClick={() => onSortingChange('asc')}
|
|
173
174
|
>
|
|
@@ -175,7 +176,7 @@ export function DataGridColumnHeader<TData, TValue>({
|
|
|
175
176
|
Sort asc
|
|
176
177
|
</DropdownMenuCheckboxItem>
|
|
177
178
|
<DropdownMenuCheckboxItem
|
|
178
|
-
className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-
|
|
179
|
+
className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground"
|
|
179
180
|
checked={column.getIsSorted() === 'desc'}
|
|
180
181
|
onClick={() => onSortingChange('desc')}
|
|
181
182
|
>
|
|
@@ -195,29 +196,23 @@ export function DataGridColumnHeader<TData, TValue>({
|
|
|
195
196
|
{column.getCanSort() && <DropdownMenuSeparator />}
|
|
196
197
|
|
|
197
198
|
{isPinnedLeft ? (
|
|
198
|
-
<DropdownMenuItem className="[&_svg]:text-
|
|
199
|
+
<DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onUnpin}>
|
|
199
200
|
<PinOffIcon />
|
|
200
201
|
Unpin from left
|
|
201
202
|
</DropdownMenuItem>
|
|
202
203
|
) : (
|
|
203
|
-
<DropdownMenuItem
|
|
204
|
-
className="[&_svg]:text-neutral-500 dark:[&_svg]:text-neutral-400"
|
|
205
|
-
onClick={onLeftPin}
|
|
206
|
-
>
|
|
204
|
+
<DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onLeftPin}>
|
|
207
205
|
<PinIcon />
|
|
208
206
|
Pin to left
|
|
209
207
|
</DropdownMenuItem>
|
|
210
208
|
)}
|
|
211
209
|
{isPinnedRight ? (
|
|
212
|
-
<DropdownMenuItem className="[&_svg]:text-
|
|
210
|
+
<DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onUnpin}>
|
|
213
211
|
<PinOffIcon />
|
|
214
212
|
Unpin from right
|
|
215
213
|
</DropdownMenuItem>
|
|
216
214
|
) : (
|
|
217
|
-
<DropdownMenuItem
|
|
218
|
-
className="[&_svg]:text-neutral-500 dark:[&_svg]:text-neutral-400"
|
|
219
|
-
onClick={onRightPin}
|
|
220
|
-
>
|
|
215
|
+
<DropdownMenuItem className="[&_svg]:text-muted-foreground" onClick={onRightPin}>
|
|
221
216
|
<PinIcon />
|
|
222
217
|
Pin to right
|
|
223
218
|
</DropdownMenuItem>
|
|
@@ -228,7 +223,7 @@ export function DataGridColumnHeader<TData, TValue>({
|
|
|
228
223
|
<>
|
|
229
224
|
<DropdownMenuSeparator />
|
|
230
225
|
<DropdownMenuCheckboxItem
|
|
231
|
-
className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-
|
|
226
|
+
className="relative pr-8 pl-2 [&>span:first-child]:right-2 [&>span:first-child]:left-auto [&_svg]:text-muted-foreground"
|
|
232
227
|
checked={!column.getIsVisible()}
|
|
233
228
|
onClick={() => column.toggleVisibility(false)}
|
|
234
229
|
>
|
|
@@ -292,12 +287,10 @@ function DataGridColumnResizerImpl<TData, TValue>({ header, table, label }: Data
|
|
|
292
287
|
className={cn(
|
|
293
288
|
'right-0 absolute top-0 z-50 h-full w-0 touch-none select-none cursor-ew-resize focus:outline-none',
|
|
294
289
|
// visible thin line (use right positioning and a stable 1px width)
|
|
295
|
-
"before:content-[''] before:absolute before:inset-y-0 before:right-0 before:translate-x-1/2 before:w-px before:h-full before:bg-
|
|
290
|
+
"before:content-[''] before:absolute before:inset-y-0 before:right-0 before:translate-x-1/2 before:w-px before:h-full before:bg-accent-foreground/70 before:opacity-0 before:transition-opacity before:duration-150",
|
|
296
291
|
// large invisible hit area
|
|
297
292
|
"after:content-[''] after:absolute after:inset-y-0 after:right-0 after:translate-x-1/2 after:w-[18px] after:h-full after:bg-transparent",
|
|
298
|
-
header.column.getIsResizing()
|
|
299
|
-
? 'before:opacity-100 before:bg-neutral-900 dark:before:bg-neutral-50'
|
|
300
|
-
: 'hover:before:opacity-100',
|
|
293
|
+
header.column.getIsResizing() ? 'before:opacity-100 before:bg-accent-foreground' : 'hover:before:opacity-100',
|
|
301
294
|
)}
|
|
302
295
|
style={{ willChange: 'transform, opacity', transform: 'translateZ(0)' }}
|
|
303
296
|
onDoubleClick={onDoubleClick}
|
|
@@ -79,7 +79,7 @@ function DataGridRowImpl<TData>({
|
|
|
79
79
|
ref={rowRef}
|
|
80
80
|
tabIndex={-1}
|
|
81
81
|
className={cn(
|
|
82
|
-
'absolute flex w-full border-b h-[calc(var(--line-height
|
|
82
|
+
'absolute flex w-full border-b h-[calc(var(--data-grid-line-height)*(var(--line-count))+12px)]',
|
|
83
83
|
className,
|
|
84
84
|
)}
|
|
85
85
|
style={{ '--line-count': `${getLineCount(rowHeight)}` } as React.CSSProperties}
|
|
@@ -107,7 +107,7 @@ function DataGridRowImpl<TData>({
|
|
|
107
107
|
{typeof cell.column.columnDef.header === 'function' ? (
|
|
108
108
|
<div
|
|
109
109
|
className={cn('size-full px-3 py-1.5', {
|
|
110
|
-
'bg-
|
|
110
|
+
'bg-accent-foreground/10': isRowSelected,
|
|
111
111
|
})}
|
|
112
112
|
>
|
|
113
113
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
@@ -152,7 +152,7 @@ function DataGridSearchImpl({
|
|
|
152
152
|
<div
|
|
153
153
|
role="search"
|
|
154
154
|
data-slot="grid-search"
|
|
155
|
-
className="fade-in-0 slide-in-from-top-2 absolute top-4 right-4 z-50 flex animate-in flex-col gap-2 rounded-lg border border-
|
|
155
|
+
className="fade-in-0 slide-in-from-top-2 absolute top-4 right-4 z-50 flex animate-in flex-col gap-2 rounded-lg border border-border bg-background p-2 shadow-lg"
|
|
156
156
|
>
|
|
157
157
|
<div className="flex items-center gap-2">
|
|
158
158
|
<Input
|
|
@@ -195,7 +195,7 @@ function DataGridSearchImpl({
|
|
|
195
195
|
</Button>
|
|
196
196
|
</div>
|
|
197
197
|
</div>
|
|
198
|
-
<div className="flex items-center gap-1 whitespace-nowrap text-
|
|
198
|
+
<div className="flex items-center gap-1 whitespace-nowrap text-muted-foreground">
|
|
199
199
|
{searchMatches.length > 0 ? (
|
|
200
200
|
<span>
|
|
201
201
|
{matchIndex + 1} of {searchMatches.length}
|
|
@@ -20,21 +20,10 @@ export function parseCellKey(cellKey: string): Required<CellPosition> {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function getRowHeightValue(rowHeight: RowHeightValue): number {
|
|
23
|
-
// TODO:
|
|
24
|
-
const lineHeight =
|
|
25
|
-
getComputedStyle(document.documentElement)
|
|
26
|
-
.getPropertyValue('--line-height-sm')
|
|
27
|
-
.trim()
|
|
28
|
-
.replaceAll(/[^\d.-]/g, ''),
|
|
29
|
-
) */ 16
|
|
30
|
-
/* const rowHeightMap: Record<RowHeightValue, number> = {
|
|
31
|
-
short: lineHeight * getLineCount(rowHeight) + 12,
|
|
32
|
-
medium: lineHeight * getLineCount(rowHeight) + 12,
|
|
33
|
-
tall: lineHeight * getLineCount(rowHeight) + 12,
|
|
34
|
-
'extra-tall': lineHeight * getLineCount(rowHeight) + 12,
|
|
35
|
-
} */
|
|
23
|
+
// TODO: Make lineHeight configurable / use css variable: --data-grid-line-height
|
|
24
|
+
const lineHeight = 16
|
|
36
25
|
|
|
37
|
-
return
|
|
26
|
+
return lineHeight * getLineCount(rowHeight) + 12
|
|
38
27
|
}
|
|
39
28
|
|
|
40
29
|
export function getLineCount(rowHeight: RowHeightValue): number {
|
|
@@ -251,7 +251,7 @@ export function DataGridViewMenu<TData>({ table, ...props }: DataGridViewMenuPro
|
|
|
251
251
|
size="sm"
|
|
252
252
|
className="ml-auto hidden h-8 font-normal lg:flex"
|
|
253
253
|
>
|
|
254
|
-
<MixerHorizontalIcon className="text-
|
|
254
|
+
<MixerHorizontalIcon className="text-muted-foreground" />
|
|
255
255
|
View
|
|
256
256
|
</Button>
|
|
257
257
|
</PopoverTrigger>
|
|
@@ -310,7 +310,7 @@ export function DataGridViewMenu<TData>({ table, ...props }: DataGridViewMenuPro
|
|
|
310
310
|
startPointerDrag(e, column.id)
|
|
311
311
|
}}
|
|
312
312
|
className={cn(
|
|
313
|
-
'size-4 text-
|
|
313
|
+
'size-4 text-muted-foreground',
|
|
314
314
|
searchQuery.trim() !== '' && 'opacity-10',
|
|
315
315
|
searchQuery.trim() === '' && (isDragging ? 'cursor-grabbing' : 'cursor-grab'),
|
|
316
316
|
)}
|
|
@@ -326,7 +326,7 @@ export function DataGridViewMenu<TData>({ table, ...props }: DataGridViewMenuPro
|
|
|
326
326
|
? column.getIsVisible()
|
|
327
327
|
? 'opacity-100'
|
|
328
328
|
: 'opacity-0'
|
|
329
|
-
: 'opacity-50 text-
|
|
329
|
+
: 'opacity-50 text-muted-foreground',
|
|
330
330
|
)}
|
|
331
331
|
/>
|
|
332
332
|
</CommandItem>
|
|
@@ -82,7 +82,7 @@ export function DataGrid<TData>({
|
|
|
82
82
|
data-slot="grid"
|
|
83
83
|
tabIndex={0}
|
|
84
84
|
ref={dataGridRef}
|
|
85
|
-
className="relative grid select-none overflow-auto rounded-md border border-
|
|
85
|
+
className="relative grid select-none overflow-auto rounded-md border border-border focus:outline-none"
|
|
86
86
|
style={{
|
|
87
87
|
...columnSizeVars,
|
|
88
88
|
// dynamically include measured header/footer heights (falling back to previous magic numbers)
|
|
@@ -98,7 +98,7 @@ export function DataGrid<TData>({
|
|
|
98
98
|
role="rowgroup"
|
|
99
99
|
data-slot="grid-header"
|
|
100
100
|
ref={headerRef}
|
|
101
|
-
className="sticky top-0 z-10 grid border-b bg-
|
|
101
|
+
className="sticky top-0 z-10 grid border-b bg-background"
|
|
102
102
|
>
|
|
103
103
|
{table.getHeaderGroups().map((headerGroup, rowIndex) => (
|
|
104
104
|
<div
|
|
@@ -139,7 +139,7 @@ export function DataGrid<TData>({
|
|
|
139
139
|
}}
|
|
140
140
|
>
|
|
141
141
|
{header.isPlaceholder ? null : typeof header.column.columnDef.header === 'function' ? (
|
|
142
|
-
<div className="size-full px-3 py-1.5 bg-
|
|
142
|
+
<div className="size-full px-3 py-1.5 bg-secondary/80 hover:bg-secondary/40">
|
|
143
143
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
144
144
|
</div>
|
|
145
145
|
) : (
|
|
@@ -183,7 +183,7 @@ export function DataGrid<TData>({
|
|
|
183
183
|
role="rowgroup"
|
|
184
184
|
data-slot="grid-footer"
|
|
185
185
|
ref={footerRef}
|
|
186
|
-
className="sticky bottom-0 z-10 grid border-t bg-
|
|
186
|
+
className="sticky bottom-0 z-10 grid border-t bg-background"
|
|
187
187
|
>
|
|
188
188
|
<div
|
|
189
189
|
role="row"
|
|
@@ -195,7 +195,7 @@ export function DataGrid<TData>({
|
|
|
195
195
|
<div
|
|
196
196
|
role="gridcell"
|
|
197
197
|
tabIndex={0}
|
|
198
|
-
className="relative flex h-9 grow items-center bg-
|
|
198
|
+
className="relative flex h-9 grow items-center bg-secondary/30 transition-colors hover:bg-secondary/50 focus:bg-secondary/50 focus:outline-none"
|
|
199
199
|
style={{
|
|
200
200
|
width: table.getTotalSize(),
|
|
201
201
|
minWidth: table.getTotalSize(),
|
|
@@ -203,7 +203,7 @@ export function DataGrid<TData>({
|
|
|
203
203
|
onClick={onRowAdd}
|
|
204
204
|
onKeyDown={onAddRowKeyDown}
|
|
205
205
|
>
|
|
206
|
-
<div className="sticky left-0 flex items-center gap-2 px-3 text-
|
|
206
|
+
<div className="sticky left-0 flex items-center gap-2 px-3 text-muted-foreground">
|
|
207
207
|
<PlusIcon className="size-3.5" />
|
|
208
208
|
<span className="text-sm">Add row</span>
|
|
209
209
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '@glideapps/glide-data-grid/dist/index.css'
|
|
2
2
|
|
|
3
|
-
import { useTheme } from '@context-providers/theme-context-provider'
|
|
3
|
+
import { type Theme as ContextTheme, useTheme } from '@context-providers/theme-context-provider'
|
|
4
4
|
import {
|
|
5
5
|
type CellClickedEventArgs,
|
|
6
6
|
DataEditor,
|
|
@@ -125,20 +125,19 @@ export function DataTable<Row extends object, ColumnId extends string = string>(
|
|
|
125
125
|
}: DataTableProps<Row, ColumnId>) {
|
|
126
126
|
const sortOrder = currentSort?.order === 'asc' ? 'sortAscending' : 'sortDescending'
|
|
127
127
|
const [managedColumns, setManagedColumns] = useState<ManagedDataTableColumn<Row, ColumnId>[]>([])
|
|
128
|
-
const {
|
|
128
|
+
const { brand, colorMode } = useTheme()
|
|
129
|
+
const theme: ContextTheme = `${brand} ${colorMode}`
|
|
129
130
|
const [tableTheme, setTableTheme] = useState<Partial<Theme> | undefined>()
|
|
130
131
|
|
|
131
|
-
const getTheme = useCallback(() => getTableTheme(theme), [theme])
|
|
132
|
-
|
|
133
132
|
useEffect(() => {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}, [
|
|
133
|
+
const observer = new MutationObserver(() => {
|
|
134
|
+
setTableTheme(getTableTheme(theme))
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
|
|
138
|
+
|
|
139
|
+
return () => observer.disconnect()
|
|
140
|
+
}, [theme])
|
|
142
141
|
|
|
143
142
|
useEffect(() => {
|
|
144
143
|
setManagedColumns(
|
|
@@ -154,7 +154,7 @@ export function AuthProvider({ children }: Readonly<{ children: React.ReactNode
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
if (userData && isUserSuccessfullyLoaded) {
|
|
157
|
-
const userDataResult: ViewerResult = Result.fromObject(userData as unknown as any)
|
|
157
|
+
const userDataResult: ViewerResult = Result.fromObject(userData as unknown as any) as unknown as ViewerResult
|
|
158
158
|
if (userDataResult.isOk()) {
|
|
159
159
|
const value = userDataResult.unwrap()
|
|
160
160
|
console.log('User data loaded successfully:', value)
|
|
@@ -1,100 +1,85 @@
|
|
|
1
|
-
import { createContext, useEffect, useMemo
|
|
1
|
+
import { createContext, useEffect, useMemo } from 'react'
|
|
2
2
|
import useLocalStorageState from 'use-local-storage-state'
|
|
3
3
|
|
|
4
4
|
import { createUseContext } from '@lib/react'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const ALLOWED_BRANDS = ['client', 'default'] as const
|
|
7
|
+
|
|
8
|
+
export type ColorMode = 'dark' | 'light' | 'system'
|
|
9
|
+
export type Brand = (typeof ALLOWED_BRANDS)[number]
|
|
10
|
+
export type Theme = `${Brand} ${ColorMode}`
|
|
11
|
+
type ResolvedColorMode = 'dark' | 'light'
|
|
8
12
|
|
|
9
13
|
type ThemeProviderProps = {
|
|
10
14
|
children: React.ReactNode
|
|
11
|
-
|
|
15
|
+
defaultBrand?: Brand
|
|
16
|
+
defaultColorMode?: ColorMode
|
|
12
17
|
storageKey?: string
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
type ThemeProviderState = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const initialState: ThemeProviderState = {
|
|
22
|
-
theme: 'system',
|
|
23
|
-
setTheme: () => null,
|
|
24
|
-
isThemeReady: false,
|
|
21
|
+
brand: Brand
|
|
22
|
+
colorMode: ColorMode
|
|
23
|
+
setColorMode: (colorMode: ColorMode) => void
|
|
24
|
+
setBrand: (brand: Brand) => void
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
function nextFrame(): Promise<void> {
|
|
31
|
-
return new Promise((r) => requestAnimationFrame(() => r()))
|
|
27
|
+
type ThemeStorage = {
|
|
28
|
+
brand: Brand
|
|
29
|
+
colorMode: ColorMode
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async function waitForThemeApplied(expected: Theme, timeoutMs = 500): Promise<boolean> {
|
|
40
|
-
const start = performance.now()
|
|
41
|
-
// Give the browser a paint opportunity
|
|
42
|
-
await nextFrame()
|
|
43
|
-
|
|
44
|
-
while (performance.now() - start < timeoutMs) {
|
|
45
|
-
const value = getComputedStyle(document.documentElement)
|
|
46
|
-
.getPropertyValue('--theme-ready-flag')
|
|
47
|
-
.replaceAll(/['"\s]/g, '')
|
|
48
|
-
if (value === expected) {
|
|
49
|
-
return true
|
|
50
|
-
}
|
|
51
|
-
await nextFrame()
|
|
52
|
-
}
|
|
53
|
-
return false
|
|
32
|
+
const initialState: ThemeProviderState = {
|
|
33
|
+
brand: 'default',
|
|
34
|
+
colorMode: 'system',
|
|
35
|
+
setColorMode: () => null,
|
|
36
|
+
setBrand: () => null,
|
|
54
37
|
}
|
|
55
38
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
const ThemeContext = createContext<ThemeProviderState>(initialState)
|
|
40
|
+
const STORAGE_KEY = 'PXL-theme-mode'
|
|
41
|
+
|
|
42
|
+
export function ThemeProvider({
|
|
43
|
+
children,
|
|
44
|
+
defaultBrand = 'default',
|
|
45
|
+
defaultColorMode = 'system',
|
|
46
|
+
...props
|
|
47
|
+
}: Readonly<ThemeProviderProps>) {
|
|
48
|
+
const [themeStorage, setThemeStorage] = useLocalStorageState<ThemeStorage>(STORAGE_KEY, {
|
|
49
|
+
defaultValue: { brand: defaultBrand, colorMode: defaultColorMode },
|
|
59
50
|
})
|
|
60
|
-
const
|
|
51
|
+
const { brand, colorMode } = themeStorage
|
|
61
52
|
|
|
62
53
|
useEffect(() => {
|
|
63
54
|
const root = globalThis.document.documentElement
|
|
64
55
|
|
|
65
|
-
root.classList.remove('light', 'dark')
|
|
56
|
+
root.classList.remove('light', 'dark', ...ALLOWED_BRANDS)
|
|
57
|
+
root.classList.add(brand)
|
|
66
58
|
|
|
67
|
-
let
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
let resolvedColorMode: ResolvedColorMode
|
|
60
|
+
if (colorMode === 'system') {
|
|
61
|
+
resolvedColorMode = globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
62
|
+
setThemeStorage({ brand, colorMode: resolvedColorMode })
|
|
63
|
+
root.classList.add(resolvedColorMode)
|
|
71
64
|
return
|
|
72
65
|
}
|
|
73
|
-
applied = theme
|
|
74
|
-
root.classList.add(theme)
|
|
75
66
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Option A: wait for computed variable
|
|
80
|
-
const ok = await waitForThemeApplied(applied)
|
|
81
|
-
if (!cancelled) {
|
|
82
|
-
setIsThemeReady(ok)
|
|
83
|
-
}
|
|
84
|
-
})()
|
|
85
|
-
|
|
86
|
-
return () => {
|
|
87
|
-
cancelled = true
|
|
88
|
-
}
|
|
89
|
-
}, [theme])
|
|
67
|
+
resolvedColorMode = colorMode
|
|
68
|
+
root.classList.add(resolvedColorMode)
|
|
69
|
+
}, [brand, colorMode, setThemeStorage])
|
|
90
70
|
|
|
91
71
|
const value = useMemo(
|
|
92
72
|
() => ({
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
73
|
+
brand,
|
|
74
|
+
colorMode,
|
|
75
|
+
setColorMode: (newColorMode: ColorMode) => {
|
|
76
|
+
setThemeStorage({ brand, colorMode: newColorMode })
|
|
77
|
+
},
|
|
78
|
+
setBrand: (newBrand: Brand) => {
|
|
79
|
+
setThemeStorage({ brand: newBrand, colorMode })
|
|
80
|
+
},
|
|
96
81
|
}),
|
|
97
|
-
[
|
|
82
|
+
[brand, colorMode, setThemeStorage],
|
|
98
83
|
)
|
|
99
84
|
|
|
100
85
|
return (
|
|
@@ -32,7 +32,7 @@ function App() {
|
|
|
32
32
|
<QueryClientProvider client={queryClient}>
|
|
33
33
|
<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>
|
|
34
34
|
<AuthProvider>
|
|
35
|
-
<ThemeProvider
|
|
35
|
+
<ThemeProvider>
|
|
36
36
|
<AppRouter />
|
|
37
37
|
</ThemeProvider>
|
|
38
38
|
</AuthProvider>
|
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import { useAuth } from '@context-providers/auth-context-provider'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { ColorMode } from '@context-providers/theme-context-provider'
|
|
3
|
+
import { PersonIcon } from '@radix-ui/react-icons'
|
|
4
|
+
import { Post } from '@types'
|
|
4
5
|
|
|
6
|
+
import { useState } from 'react'
|
|
5
7
|
import { toast } from 'sonner'
|
|
6
8
|
|
|
9
|
+
import { UpdatePostModal } from '@components/forms/post/update'
|
|
7
10
|
import { Button } from '@components/ui/button/button'
|
|
11
|
+
import { ColorModeToggle } from '@components/ui/color-mode-toggle/color-mode-toggle'
|
|
8
12
|
import { ContentFrame } from '@components/ui/content-frame/content-frame'
|
|
9
|
-
import { APP_CONFIG } from '@lib/config'
|
|
10
|
-
import { useState } from 'react'
|
|
11
|
-
import { Post } from '@types'
|
|
12
|
-
import { usePosts } from '@hooks/use-posts'
|
|
13
|
-
import { UpdatePostModal } from '@components/forms/post/update'
|
|
14
13
|
import { Spreadsheet } from '@components/ui/spreadsheet'
|
|
15
14
|
import model from '@components/ui/spreadsheet/model.json'
|
|
16
15
|
import type { Model } from '@components/ui/spreadsheet/types'
|
|
16
|
+
import { usePosts } from '@hooks/use-posts'
|
|
17
|
+
import { APP_CONFIG } from '@lib/config'
|
|
17
18
|
|
|
18
19
|
export const DashboardPage = () => {
|
|
19
20
|
const { logout, user } = useAuth()
|
|
20
|
-
const { theme, setTheme } = useTheme()
|
|
21
21
|
|
|
22
22
|
const [selectedPost, setSelectedPost] = useState<Post | null>(null)
|
|
23
23
|
const [isUpdateModalOpen, setIsUpdateModalOpen] = useState(false)
|
|
24
24
|
|
|
25
|
-
const onToggleTheme = () => {
|
|
26
|
-
|
|
27
|
-
toast.info("As you may see now it's " + (theme === 'light' ? 'dark' : 'light') + ' theme')
|
|
25
|
+
const onToggleTheme = (nextMode: ColorMode) => {
|
|
26
|
+
toast.info("As you may see now it's " + nextMode + ' color mode')
|
|
28
27
|
}
|
|
29
28
|
const { list } = usePosts()
|
|
30
29
|
|
|
@@ -42,9 +41,7 @@ export const DashboardPage = () => {
|
|
|
42
41
|
<ContentFrame
|
|
43
42
|
title={APP_CONFIG.AUTH ? 'Dashboard (authorized route)' : 'Dashboard'}
|
|
44
43
|
controls={[
|
|
45
|
-
<
|
|
46
|
-
{theme === 'light' ? <MoonIcon /> : <SunIcon />}
|
|
47
|
-
</Button>,
|
|
44
|
+
<ColorModeToggle key="btn-color-mode" onClick={onToggleTheme} />,
|
|
48
45
|
APP_CONFIG.AUTH && (
|
|
49
46
|
<Button key="btn-logout" onClick={logout}>
|
|
50
47
|
<PersonIcon /> Logout
|