@exxatdesignux/ui 0.2.18 → 0.2.19
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/CHANGELOG.md +15 -0
- package/consumer-extras/AGENTS.md +76 -0
- package/consumer-extras/README.md +5 -1
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
- package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +21 -6
- package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +4 -2
- package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
- package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
- package/consumer-extras/patterns/data-views-pattern.md +40 -3
- package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
- package/consumer-extras/patterns/shell-surface-elevation-pattern.md +5 -3
- package/package.json +2 -1
- package/src/components/ui/button-group.tsx +81 -0
- package/src/components/ui/button.tsx +4 -4
- package/src/globals.css +7 -1858
- package/src/theme.css +10 -1126
- package/src/tokens/README.md +15 -0
- package/src/tokens/base.css +337 -0
- package/src/tokens/high-contrast.css +1195 -0
- package/src/tokens/layers.css +224 -0
- package/src/tokens/tailwind-bridge.css +118 -0
- package/src/tokens/themes.css +201 -0
- package/template/AGENTS.md +60 -22
- package/template/app/(app)/dashboard/loading.tsx +3 -15
- package/template/app/(app)/dashboard/page.tsx +2 -14
- package/template/app/(app)/data-list/layout.tsx +43 -0
- package/template/app/(app)/data-list/page.tsx +2 -2
- package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
- package/template/app/(app)/examples/page.tsx +1 -0
- package/template/app/(app)/loading.tsx +1 -18
- package/template/app/(app)/question-bank/find/page.tsx +2 -1
- package/template/app/(app)/question-bank/library/page.tsx +2 -1
- package/template/app/(app)/question-bank/list/page.tsx +2 -1
- package/template/app/(app)/question-bank/new/page.tsx +15 -23
- package/template/app/(app)/question-bank/page.tsx +2 -1
- package/template/app/(app)/settings/page.tsx +4 -5
- package/template/app/globals.css +7 -1964
- package/template/components/app-route-loading.tsx +14 -0
- package/template/components/app-sidebar.tsx +70 -55
- package/template/components/data-views/index.ts +37 -9
- package/template/components/data-views/list-page-calendar-view.tsx +593 -0
- package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
- package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
- package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
- package/template/components/examples/focused-workflow-showcase.tsx +183 -0
- package/template/components/list-hub-board-view.tsx +68 -0
- package/template/components/list-hub-client.tsx +186 -0
- package/template/components/list-hub-list-view.tsx +36 -0
- package/template/components/list-hub-panel-activator.tsx +8 -0
- package/template/components/list-hub-secondary-nav.tsx +121 -0
- package/template/components/list-hub-table.tsx +336 -0
- package/template/components/new-question-composer.tsx +6 -24
- package/template/components/product-switcher.tsx +3 -2
- package/template/components/question-bank-client.tsx +4 -1
- package/template/components/question-bank-folder-columns-panel.tsx +104 -0
- package/template/components/question-bank-table.tsx +143 -485
- package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
- package/template/components/secondary-panel.tsx +4 -44
- package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
- package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
- package/template/components/secondary-panels/registry.tsx +15 -0
- package/template/components/settings-appearance-card.tsx +3 -2
- package/template/components/settings-client.tsx +59 -15
- package/template/components/settings-form-row.tsx +9 -4
- package/template/components/table-properties/drawer-button.tsx +13 -0
- package/template/components/table-properties/drawer.tsx +65 -4
- package/template/components/templates/focused-workflow-layouts.tsx +448 -0
- package/template/components/templates/focused-workflow-page-template.tsx +69 -0
- package/template/components/templates/list-page.tsx +29 -5
- package/template/components/templates/nested-secondary-panel-shell.tsx +2 -1
- package/template/components/templates/page-loading-shell.tsx +262 -0
- package/template/components/ui/button-group.tsx +1 -0
- package/template/docs/consumer-app-pattern.md +39 -0
- package/template/docs/data-views-pattern.md +40 -3
- package/template/docs/drawer-vs-dialog-pattern.md +3 -1
- package/template/docs/focused-workflow-page-pattern.md +84 -0
- package/template/docs/shell-surface-elevation-pattern.md +5 -3
- package/template/lib/command-menu-search-data.ts +11 -27
- package/template/lib/data-list-display-options.ts +16 -2
- package/template/lib/data-list-view-registry.ts +104 -0
- package/template/lib/data-list-view-surface.ts +15 -1
- package/template/lib/data-list-view.ts +10 -1
- package/template/lib/data-view-dashboard-storage.ts +38 -35
- package/template/lib/hub-connected-view-renderers.ts +58 -0
- package/template/lib/list-hub-nav.ts +121 -0
- package/template/lib/list-hub-supported-views.ts +10 -0
- package/template/lib/list-page-table-properties.ts +3 -7
- package/template/lib/list-status-badges.ts +4 -97
- package/template/lib/mock/list-hub-directory.ts +27 -0
- package/template/lib/mock/list-hub-kpi.ts +27 -0
- package/template/lib/mock/navigation.tsx +1 -0
- package/template/lib/page-loading-variant.ts +40 -0
- package/template/lib/question-bank-supported-views.ts +13 -0
- package/template/lib/table-state-lifecycle.ts +2 -2
- package/template/app/(app)/data-list/[id]/page.tsx +0 -44
- package/template/app/(app)/data-list/new/page.tsx +0 -34
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-list-view.tsx +0 -54
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -612
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -1068
- package/template/components/placement-board-card.tsx +0 -262
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -404
- package/template/components/placements-client.tsx +0 -252
- package/template/components/placements-list-view.tsx +0 -171
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -640
- package/template/components/placements-table.tsx +0 -1642
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-all-client.tsx +0 -154
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-list-view.tsx +0 -42
- package/template/components/sites-table.tsx +0 -382
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-list-view.tsx +0 -59
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -693
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -183
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- package/template/lib/placement-lifecycle.ts +0 -5
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { Skeleton } from "@/components/ui/skeleton"
|
|
4
|
+
import { SidebarInset } from "@/components/ui/sidebar"
|
|
5
|
+
import {
|
|
6
|
+
FOCUSED_WORKFLOW_CONTENT_PADDING_CLASS,
|
|
7
|
+
FOCUSED_WORKFLOW_MAX_WIDTH,
|
|
8
|
+
} from "@/components/templates/focused-workflow-page-template"
|
|
9
|
+
import { FOCUSED_WORKFLOW_SIDEBAR_SECTIONS_GRID_CLASS } from "@/components/templates/focused-workflow-layouts"
|
|
10
|
+
import { PRIMARY_PAGE_MAX_WIDTH_CLASS } from "@/components/templates/primary-page-template"
|
|
11
|
+
import type { PageLoadingVariant } from "@/lib/page-loading-variant"
|
|
12
|
+
import { cn } from "@/lib/utils"
|
|
13
|
+
|
|
14
|
+
/** Breadcrumb bar placeholder — matches `SiteHeader` footprint without client hooks. */
|
|
15
|
+
function SiteHeaderSkeleton() {
|
|
16
|
+
return (
|
|
17
|
+
<div className="sticky top-0 z-30 bg-transparent">
|
|
18
|
+
<header
|
|
19
|
+
role="presentation"
|
|
20
|
+
className="flex h-(--header-height) shrink-0 items-center gap-2 rounded-t-xl bg-background ps-4 pe-2 lg:gap-2 lg:ps-6 lg:pe-2"
|
|
21
|
+
>
|
|
22
|
+
<Skeleton className="size-8 shrink-0 rounded-md" />
|
|
23
|
+
<Skeleton className="h-4 w-36 max-w-[50vw] rounded-md" />
|
|
24
|
+
<div className="ms-auto flex items-center gap-2">
|
|
25
|
+
<Skeleton className="h-8 w-16 rounded-md" />
|
|
26
|
+
<Skeleton className="size-8 rounded-md" />
|
|
27
|
+
</div>
|
|
28
|
+
</header>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface PageLoadingChromeProps {
|
|
34
|
+
children: React.ReactNode
|
|
35
|
+
maxWidthClassName?: string
|
|
36
|
+
contentClassName?: string
|
|
37
|
+
paddingClassName?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function PageLoadingChrome({
|
|
41
|
+
children,
|
|
42
|
+
maxWidthClassName = PRIMARY_PAGE_MAX_WIDTH_CLASS,
|
|
43
|
+
contentClassName,
|
|
44
|
+
paddingClassName = "px-4 pt-2 pb-32 sm:px-6 lg:px-8",
|
|
45
|
+
}: PageLoadingChromeProps) {
|
|
46
|
+
return (
|
|
47
|
+
<SidebarInset id="main-content" tabIndex={-1} aria-busy="true" aria-label="Loading page">
|
|
48
|
+
<SiteHeaderSkeleton />
|
|
49
|
+
<div className="flex min-h-0 flex-1 flex-col outline-none">
|
|
50
|
+
<div
|
|
51
|
+
className={cn(
|
|
52
|
+
"@container/main mx-auto flex min-h-0 w-full min-w-0 flex-1 flex-col",
|
|
53
|
+
maxWidthClassName,
|
|
54
|
+
paddingClassName,
|
|
55
|
+
contentClassName,
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</SidebarInset>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** List hub — header, KPI band, toolbar, table rows. */
|
|
66
|
+
export function PrimaryListHubLoadingBody() {
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex flex-col gap-6">
|
|
69
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
70
|
+
<div className="space-y-2">
|
|
71
|
+
<Skeleton className="h-9 w-56 max-w-full rounded-md" />
|
|
72
|
+
<Skeleton className="h-4 w-72 max-w-full rounded-md" />
|
|
73
|
+
</div>
|
|
74
|
+
<div className="flex shrink-0 gap-2">
|
|
75
|
+
<Skeleton className="h-9 w-28 rounded-md" />
|
|
76
|
+
<Skeleton className="h-9 w-9 rounded-md" />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-border bg-border sm:grid-cols-4">
|
|
80
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
81
|
+
<Skeleton key={i} className="h-[4.75rem] rounded-none bg-card" />
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
85
|
+
<Skeleton className="h-9 min-w-[12rem] flex-1 rounded-md sm:max-w-xs" />
|
|
86
|
+
<Skeleton className="h-9 w-24 rounded-md" />
|
|
87
|
+
<Skeleton className="h-9 w-9 rounded-md" />
|
|
88
|
+
</div>
|
|
89
|
+
<div className="space-y-2 rounded-xl border border-border bg-card p-2">
|
|
90
|
+
<Skeleton className="h-10 w-full rounded-md" />
|
|
91
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
92
|
+
<Skeleton key={i} className="h-11 w-full rounded-md" />
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function PrimaryListHubLoadingFallback() {
|
|
100
|
+
return (
|
|
101
|
+
<PageLoadingChrome>
|
|
102
|
+
<PrimaryListHubLoadingBody />
|
|
103
|
+
</PageLoadingChrome>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Question bank discovery hub — composer + folder grid. */
|
|
108
|
+
export function QuestionBankHubLoadingBody() {
|
|
109
|
+
return (
|
|
110
|
+
<div className="flex flex-col gap-10">
|
|
111
|
+
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4 py-6">
|
|
112
|
+
<Skeleton className="mx-auto h-10 w-64 max-w-full rounded-md" />
|
|
113
|
+
<Skeleton className="h-12 w-full rounded-xl" />
|
|
114
|
+
<Skeleton className="h-4 w-48 rounded-md" />
|
|
115
|
+
</div>
|
|
116
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
117
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
118
|
+
<Skeleton key={i} className="h-28 rounded-xl" />
|
|
119
|
+
))}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function QuestionBankHubLoadingFallback() {
|
|
126
|
+
return (
|
|
127
|
+
<PageLoadingChrome>
|
|
128
|
+
<QuestionBankHubLoadingBody />
|
|
129
|
+
</PageLoadingChrome>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Dedicated search landing / results (`?q=`). */
|
|
134
|
+
export function DedicatedSearchLoadingBody() {
|
|
135
|
+
return (
|
|
136
|
+
<div className="mx-auto flex min-h-[min(52vh,28rem)] w-full max-w-3xl flex-col justify-center gap-6 py-10">
|
|
137
|
+
<Skeleton className="h-10 w-72 max-w-full rounded-md" />
|
|
138
|
+
<Skeleton className="h-12 w-full rounded-xl" />
|
|
139
|
+
<div className="flex flex-wrap gap-2">
|
|
140
|
+
<Skeleton className="h-8 w-24 rounded-full" />
|
|
141
|
+
<Skeleton className="h-8 w-32 rounded-full" />
|
|
142
|
+
<Skeleton className="h-8 w-20 rounded-full" />
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function DedicatedSearchLoadingFallback() {
|
|
149
|
+
return (
|
|
150
|
+
<PageLoadingChrome maxWidthClassName="max-w-5xl">
|
|
151
|
+
<DedicatedSearchLoadingBody />
|
|
152
|
+
</PageLoadingChrome>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Dashboard tabs — KPI row + chart block. */
|
|
157
|
+
export function DashboardLoadingBody() {
|
|
158
|
+
return (
|
|
159
|
+
<div className="flex flex-col gap-6">
|
|
160
|
+
<div className="space-y-2">
|
|
161
|
+
<Skeleton className="h-9 w-56 max-w-full rounded-md" />
|
|
162
|
+
<Skeleton className="h-4 w-80 max-w-full rounded-md" />
|
|
163
|
+
</div>
|
|
164
|
+
<Skeleton className="h-11 w-full max-w-xl rounded-md" />
|
|
165
|
+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
166
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
167
|
+
<Skeleton key={i} className="h-24 rounded-xl" />
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
<Skeleton className="min-h-[320px] w-full rounded-xl" />
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function DashboardLoadingFallback() {
|
|
176
|
+
return (
|
|
177
|
+
<PageLoadingChrome maxWidthClassName={PRIMARY_PAGE_MAX_WIDTH_CLASS}>
|
|
178
|
+
<DashboardLoadingBody />
|
|
179
|
+
</PageLoadingChrome>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Focused workflow — single column form/settings sections. */
|
|
184
|
+
export function FocusedWorkflowLoadingBody({ withSidebar = false }: { withSidebar?: boolean }) {
|
|
185
|
+
if (withSidebar) {
|
|
186
|
+
return (
|
|
187
|
+
<div className={cn("flex flex-col gap-8", FOCUSED_WORKFLOW_SIDEBAR_SECTIONS_GRID_CLASS)}>
|
|
188
|
+
<div className="space-y-2 lg:col-span-2">
|
|
189
|
+
<Skeleton className="h-9 w-40 max-w-full rounded-md" />
|
|
190
|
+
<Skeleton className="h-4 w-full max-w-xl rounded-md" />
|
|
191
|
+
</div>
|
|
192
|
+
<div className="flex flex-col gap-1">
|
|
193
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
194
|
+
<Skeleton key={i} className="h-9 w-full rounded-md" />
|
|
195
|
+
))}
|
|
196
|
+
</div>
|
|
197
|
+
<div className="flex flex-col gap-8">
|
|
198
|
+
<Skeleton className="h-24 w-full rounded-xl" />
|
|
199
|
+
<Skeleton className="h-48 w-full rounded-xl" />
|
|
200
|
+
<Skeleton className="h-32 w-full rounded-xl" />
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<div className="flex flex-col gap-8">
|
|
208
|
+
<div className="space-y-2">
|
|
209
|
+
<Skeleton className="h-9 w-48 max-w-full rounded-md" />
|
|
210
|
+
<Skeleton className="h-4 w-full max-w-lg rounded-md" />
|
|
211
|
+
</div>
|
|
212
|
+
<Skeleton className="h-40 w-full rounded-xl" />
|
|
213
|
+
<Skeleton className="h-56 w-full rounded-xl" />
|
|
214
|
+
</div>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function FocusedWorkflowLoadingFallback({ withSidebar = false }: { withSidebar?: boolean }) {
|
|
219
|
+
return (
|
|
220
|
+
<PageLoadingChrome
|
|
221
|
+
maxWidthClassName={FOCUSED_WORKFLOW_MAX_WIDTH.lg}
|
|
222
|
+
paddingClassName={FOCUSED_WORKFLOW_CONTENT_PADDING_CLASS}
|
|
223
|
+
>
|
|
224
|
+
<FocusedWorkflowLoadingBody withSidebar={withSidebar} />
|
|
225
|
+
</PageLoadingChrome>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function SimplePageLoadingBody() {
|
|
230
|
+
return (
|
|
231
|
+
<div className="flex max-w-xl flex-col gap-4 py-4">
|
|
232
|
+
<Skeleton className="h-4 w-full rounded-md" />
|
|
233
|
+
<Skeleton className="h-4 w-5/6 rounded-md" />
|
|
234
|
+
<div className="flex gap-3 pt-2">
|
|
235
|
+
<Skeleton className="h-9 w-36 rounded-md" />
|
|
236
|
+
<Skeleton className="h-9 w-28 rounded-md" />
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function SimplePageLoadingFallback() {
|
|
243
|
+
return (
|
|
244
|
+
<PageLoadingChrome maxWidthClassName="max-w-3xl">
|
|
245
|
+
<SimplePageLoadingBody />
|
|
246
|
+
</PageLoadingChrome>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const FALLBACK_BY_VARIANT: Record<PageLoadingVariant, React.ReactNode> = {
|
|
251
|
+
dashboard: <DashboardLoadingFallback />,
|
|
252
|
+
"primary-list-hub": <PrimaryListHubLoadingFallback />,
|
|
253
|
+
"question-bank-hub": <QuestionBankHubLoadingFallback />,
|
|
254
|
+
"dedicated-search": <DedicatedSearchLoadingFallback />,
|
|
255
|
+
"focused-workflow": <FocusedWorkflowLoadingFallback />,
|
|
256
|
+
"focused-workflow-sidebar": <FocusedWorkflowLoadingFallback withSidebar />,
|
|
257
|
+
simple: <SimplePageLoadingFallback />,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function PageLoadingByVariant({ variant }: { variant: PageLoadingVariant }) {
|
|
261
|
+
return FALLBACK_BY_VARIANT[variant]
|
|
262
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@exxatdesignux/ui/components/button-group"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Consumer app pattern (`@exxatdesignux/ui`)
|
|
2
|
+
|
|
3
|
+
> **Audience:** Product repos that **install** the design system from npm — not the `apps/web` monorepo reference app.
|
|
4
|
+
> **Handbook:** `packages/ui/consumer-extras/AGENTS.md` · **Upgrade:** `consumer-upgrade-checklist.md` · **Skill:** `.cursor/skills/exxat-consumer-app/SKILL.md`
|
|
5
|
+
|
|
6
|
+
## Intent
|
|
7
|
+
|
|
8
|
+
A **consumer app** composes **`@exxatdesignux/ui`** primitives and copies **patterns** from the published **`template/`** tree. It does **not** fork parallel table stacks, view routers, or shell tokens.
|
|
9
|
+
|
|
10
|
+
## MUST
|
|
11
|
+
|
|
12
|
+
1. **Install** — `@exxatdesignux/ui` + peers; import DS CSS once (`@exxatdesignux/ui/globals.css` or documented entry).
|
|
13
|
+
2. **Sync extras** — After version bumps: `npx --package=@exxatdesignux/ui@latest exxat-ui sync-extras` → `.cursor/skills/exxat-*` + `docs/exxat-ds/*.md`.
|
|
14
|
+
3. **Diff template** — Compare `node_modules/@exxatdesignux/ui/template/` for new files (layouts, `ListPageConnectedViewBody`, `FocusedWorkflowPageTemplate`, mocks).
|
|
15
|
+
4. **List hubs** — One **`useTableState`** row bag; **`ListPageConnectedViewBody`** + **`defineHubViewRenderers`**; same **`supportedViewTypes`** on **`ListPageTemplate`** and **`TablePropertiesDrawer`**.
|
|
16
|
+
5. **Shell tokens** — Sidebar / secondary panel / page elevation via **`--sidebar`**, **`--secondary-panel-bg`**, **`--background`** — see **`shell-surface-elevation-pattern.md`**.
|
|
17
|
+
6. **Focused workflows** — Dedicated create/edit/settings routes use **`FocusedWorkflowPageTemplate`** — **`focused-workflow-page-pattern.md`**.
|
|
18
|
+
|
|
19
|
+
## MUST NOT
|
|
20
|
+
|
|
21
|
+
- Copy only components without porting **AGENTS**, **rules**, and **pattern docs** when behavior changes.
|
|
22
|
+
- Ship hub view tabs with long **`if (view === "table")`** chains instead of **`ListPageConnectedViewBody`**.
|
|
23
|
+
- Keep a second mock array per view while the grid uses **`useTableState`**.
|
|
24
|
+
- Set secondary panel to **`bg-sidebar`** or light **`--brand-tint`** mixes in dark mode.
|
|
25
|
+
|
|
26
|
+
## Checklist (new consumer feature)
|
|
27
|
+
|
|
28
|
+
- [ ] Read **`consumer-upgrade-checklist.md`** for the installed UI version.
|
|
29
|
+
- [ ] Run **`exxat-ui sync-extras`** if skills/patterns are stale.
|
|
30
|
+
- [ ] Find the closest **`template/`** hub or page; port file names and imports to your app paths.
|
|
31
|
+
- [ ] **List hub:** `lib/<hub>-supported-views.ts`, `lib/data-list-view-registry.ts`, `ListPageConnectedViewBody`, centralized **`tableState.rows`**.
|
|
32
|
+
- [ ] **Form route:** `FocusedWorkflowPageTemplate` + one body layout from **`focused-workflow-layouts.tsx`**.
|
|
33
|
+
- [ ] **Nav + secondary panel:** `secondaryPanel` on nav item, `PANELS` registry, `useAutoPanel` — **`exxat-primary-nav-secondary-panel`** skill.
|
|
34
|
+
- [ ] Re-run **`fa:subset-audit`** when adding Font Awesome glyphs.
|
|
35
|
+
|
|
36
|
+
## See also
|
|
37
|
+
|
|
38
|
+
- Monorepo reference: `apps/web/` (full product demo)
|
|
39
|
+
- **`.cursor/rules/exxat-consumer-app.mdc`** (when working inside a consumer repo that synced rules)
|
|
@@ -38,6 +38,41 @@ Non-table view branches (e.g. **folder** icon grid, **panel** finder, OS-style f
|
|
|
38
38
|
|
|
39
39
|
**Handbook:** **`AGENTS.md` §4.5**. **Cursor:** **`.cursor/rules/exxat-list-page-view-shells.mdc`**. **Skill:** **`.cursor/skills/exxat-list-page-view-shells/SKILL.md`**. **Do not** wrap **`DataTable`** in the frame if that stacks padding with the table toolbar (**`AGENTS.md` §5**).
|
|
40
40
|
|
|
41
|
+
## View registry and connected bodies (extensibility)
|
|
42
|
+
|
|
43
|
+
To add a **new view type** without breaking existing hubs:
|
|
44
|
+
|
|
45
|
+
1. **Register once** — Add a tile in `lib/data-list-view.ts` (`DATA_LIST_VIEW_TILES`). Capabilities (hub KPI strip, render kind) derive automatically in `lib/data-list-view-registry.ts`.
|
|
46
|
+
2. **Build the body once** — Add or extend a generic surface under `components/data-views/` (e.g. `ListPageCalendarView`). Entity-specific wiring stays in props (`getEventDate`, board `renderCard`), not a second calendar implementation per hub.
|
|
47
|
+
3. **Declare what each hub supports** — `lib/<hub>-supported-views.ts` exports a `const` array; pass the same array to **`ListPageTemplate`** (`supportedViewTypes`), the hub table (`supportedViewTypes` → `TablePropertiesDrawerButton`), and rely on defaults if omitted. **Add view**, **⌘1–9** shortcuts, and **Properties → View type** tiles all use `dataListViewTilesForHub` / `dataListViewSelectionTilesForHub` so users cannot pick a view the hub never implemented.
|
|
48
|
+
4. **Route in the hub table with `ListPageConnectedViewBody`** — Switch on **`getDataListViewRenderKind(view)`**, not raw `view === "…"` chains. Pass one renderer per kind the hub supports via **`defineHubViewRenderers(MY_HUB_SUPPORTED_VIEWS, { … })`** (`lib/hub-connected-view-renderers.ts`) so dev builds warn when a supported view has no body. **Do not** use a default branch that renders dashboard/KPIs; missing renderers show `ListPageViewNotConfigured`.
|
|
49
|
+
5. **Let the template own hub chrome** — `ListPageTemplate` hides the metrics strip on calendar/dashboard via `showsListPageHubMetricsStrip(activeTab.viewType)`. Hub clients pass `showMetrics` only; they do not reimplement per-tab KPI visibility.
|
|
50
|
+
6. **Panel / Miller columns** — Reuse **`ListPageFolderColumnsPanel`** (`components/data-views/list-page-folder-columns-panel.tsx`) for folder + record columns; hub-specific chrome (folder colors, actions) lives in a thin wrapper (e.g. `QuestionBankFolderColumnsPanel`). **Do not** export question-bank-only tree/folder nav from the generic `data-views` barrel.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// lib/my-hub-supported-views.ts
|
|
54
|
+
export const MY_HUB_SUPPORTED_VIEWS = ["table", "list", "calendar"] as const satisfies readonly DataListViewType[]
|
|
55
|
+
|
|
56
|
+
// my-hub-client.tsx
|
|
57
|
+
<ListPageTemplate supportedViewTypes={MY_HUB_SUPPORTED_VIEWS} showMetrics={showMetrics} … />
|
|
58
|
+
|
|
59
|
+
// my-hub-table.tsx
|
|
60
|
+
import { defineHubViewRenderers } from "@/lib/hub-connected-view-renderers"
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ListPageConnectedViewBody
|
|
64
|
+
view={view}
|
|
65
|
+
hubLabel="My hub"
|
|
66
|
+
renderers={defineHubViewRenderers(MY_HUB_SUPPORTED_VIEWS, {
|
|
67
|
+
"data-table": <DataTable … />,
|
|
68
|
+
"calendar-with-toolbar": toolbarShell(<ListPageCalendarView rows={tableState.rows} getEventDate={…} />),
|
|
69
|
+
})}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Checklist for a new view type:** registry tile → `data-views/` component → update each `*-supported-views.ts` that should expose it → add renderer in each `*-table.tsx` (via `defineHubViewRenderers`) → Properties drawer copy in `table-properties/drawer.tsx` if needed → `table-state-lifecycle` picks up types via `DATA_LIST_VIEW_TILES` (no separate allowlist).
|
|
75
|
+
|
|
41
76
|
## Architecture
|
|
42
77
|
|
|
43
78
|
- **Page shell** — `ListPageTemplate` owns the views toolbar (tabs), optional metrics, and export drawer. Content for the active tab is rendered via `renderContent`.
|
|
@@ -146,11 +181,13 @@ Reference: `components/placements-page-header.tsx`, `components/team-page-header
|
|
|
146
181
|
|
|
147
182
|
**When to use a new page (route):** The flow is **primary**, **long-form**, **multi-step**, or should have its **own URL** / bookmark / history **without** the parent page behind it — e.g. full create/edit, wizards, or detail that *is* the task.
|
|
148
183
|
|
|
149
|
-
**
|
|
184
|
+
**Focused workflow shell:** For dedicated create/edit, wizards, and sectioned settings, use **`FocusedWorkflowPageTemplate`** + layouts in **`focused-workflow-layouts.tsx`** — see **`docs/focused-workflow-page-pattern.md`** and **`AGENTS.md` §14**. **Not** for list hubs (**`ListPageTemplate`**) or Miller-column explorers.
|
|
185
|
+
|
|
186
|
+
**Rule of thumb:** **Context + quick** → **drawer**; **blocking short choice** → **dialog**; **primary / long / wizard / settings** → **focused workflow route** (or other dedicated page).
|
|
150
187
|
|
|
151
188
|
**Modal vs side panel (same route):** When the overlay stays on the same URL, prefer **`docs/drawer-vs-dialog-pattern.md`** and **`.cursor/rules/exxat-drawer-vs-dialog.mdc`** — drawers keep the hub visible; dialogs trap focus for confirms.
|
|
152
189
|
|
|
153
|
-
Canonical rules: **`AGENTS.md` §6.4**, root **`.cursor/rules/exxat-page-vs-drawer.mdc`**, **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**.
|
|
190
|
+
Canonical rules: **`AGENTS.md` §6.4**, **§14**, root **`.cursor/rules/exxat-page-vs-drawer.mdc`**, **`.cursor/rules/exxat-drawer-vs-dialog.mdc`**, **`.cursor/rules/exxat-focused-workflow-page.mdc`**.
|
|
154
191
|
|
|
155
192
|
---
|
|
156
193
|
|
|
@@ -170,7 +207,7 @@ When a route is a **primary** destination in nav (main hub for an entity) **and*
|
|
|
170
207
|
- [ ] **>10 items** → search, filter, sort, properties (per surface type above).
|
|
171
208
|
- [ ] **Has data to export** → **More** menu with **Export** + shared `ExportDrawer` pattern.
|
|
172
209
|
- [ ] **Primary + large / main hub** → `ListPageTemplate`-style shell where applicable.
|
|
173
|
-
- [ ] **Page vs drawer vs dialog (§6.4)** — Quick with parent **context** → drawer/sheet; **blocking** confirm → **dialog**; primary or long flows → **
|
|
210
|
+
- [ ] **Page vs drawer vs dialog (§6.4)** — Quick with parent **context** → drawer/sheet; **blocking** confirm → **dialog**; primary or long flows → **focused workflow route** (`docs/focused-workflow-page-pattern.md`, **§14**) or other dedicated page (`docs/drawer-vs-dialog-pattern.md`).
|
|
174
211
|
- [ ] **Primary button** → `Button` default variant (`size="lg"` for parity with Placements), not outline.
|
|
175
212
|
- [ ] **Dashboard view tab** → `KeyMetrics` + shared KPI helpers from **`tableState.rows`**; no duplicate one-off metric cards.
|
|
176
213
|
- [ ] **Data view charts** → `ChartFigure` + `chart-keyboard-selection`; layout persistence via **`data-view-dashboard-storage`** (see `AGENTS.md` §4.3).
|
|
@@ -30,13 +30,15 @@
|
|
|
30
30
|
|
|
31
31
|
Use when the work is **primary**, **long**, **multi-step**, or deserves its **own URL** — see **`exxat-page-vs-drawer.mdc`** and **`AGENTS.md` §6.4**.
|
|
32
32
|
|
|
33
|
+
For **large forms, wizards, and sectioned settings**, use **`FocusedWorkflowPageTemplate`** and body layouts — **`docs/focused-workflow-page-pattern.md`**, **`AGENTS.md` §14**, **`.cursor/skills/exxat-focused-workflow-page/SKILL.md`**.
|
|
34
|
+
|
|
33
35
|
## Quick matrix
|
|
34
36
|
|
|
35
37
|
| Need | Drawer | Dialog | Route |
|
|
36
38
|
| --- | --- | --- | --- |
|
|
37
39
|
| Keep hub visible | Yes | No (blocks) | No |
|
|
38
40
|
| Short confirm / alert | Rare | Yes | Overkill |
|
|
39
|
-
| Long form / wizard | Cramped | No | Yes |
|
|
41
|
+
| Long form / wizard | Cramped | No | Yes — **`FocusedWorkflowPageTemplate`** |
|
|
40
42
|
| Properties tied to a table | Yes | Too small | Optional |
|
|
41
43
|
|
|
42
44
|
## Accessibility
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Focused workflow page (dedicated routes)
|
|
2
|
+
|
|
3
|
+
> **Related:** **`AGENTS.md` §6.4** (page vs drawer vs dialog), **§14** (AI checklist), **`docs/drawer-vs-dialog-pattern.md`**, **`.cursor/rules/exxat-page-vs-drawer.mdc`**, **`.cursor/rules/exxat-focused-workflow-page.mdc`**, **`.cursor/skills/exxat-focused-workflow-page/SKILL.md`**.
|
|
4
|
+
|
|
5
|
+
## Intent
|
|
6
|
+
|
|
7
|
+
Use **`FocusedWorkflowPageTemplate`** for **large or multi-step work** on its **own route** — create/edit forms, wizards, and sectioned settings. The shell is **narrower** than list hubs and **does not** use Miller-column / split-panel explorers.
|
|
8
|
+
|
|
9
|
+
| Surface | Use instead |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Browsable record hubs (table, board, dashboard tabs) | **`PrimaryPageTemplate`** + **`ListPageTemplate`** |
|
|
12
|
+
| Finder / folder columns / split hub chrome | **`ListPageSplitHubChrome`**, **`ListPageFolderColumnsPanel`** |
|
|
13
|
+
| Quick properties or export beside a grid | **Drawer** (`TablePropertiesDrawer`, `ExportDrawer`) |
|
|
14
|
+
| Blocking confirm on the same route | **Dialog** |
|
|
15
|
+
|
|
16
|
+
## Surface matrix (§6.4)
|
|
17
|
+
|
|
18
|
+
| Need | Drawer | Dialog | **Focused workflow route** |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| Keep hub visible while acting | Yes | No | No |
|
|
21
|
+
| Own URL / bookmark / history | Rare | No | **Yes** |
|
|
22
|
+
| Multi-step wizard | Cramped | No | **Yes** |
|
|
23
|
+
| Sectioned settings (left nav) | Awkward | No | **Yes** |
|
|
24
|
+
| Short delete confirm | No | **Yes** | Overkill |
|
|
25
|
+
|
|
26
|
+
## Shell
|
|
27
|
+
|
|
28
|
+
**`FocusedWorkflowPageTemplate`** (`components/templates/focused-workflow-page-template.tsx`):
|
|
29
|
+
|
|
30
|
+
- **`SidebarInset`** + **`SiteHeader`** (breadcrumb back link + title).
|
|
31
|
+
- Centered column: **`max-w-3xl` / `max-w-4xl` / `max-w-5xl`** via **`maxWidth`** (`md` | `lg` | `xl`).
|
|
32
|
+
- Default padding: **`FOCUSED_WORKFLOW_CONTENT_PADDING_CLASS`**.
|
|
33
|
+
|
|
34
|
+
Optional **`beforeSiteHeader`** (e.g. **`SidebarAutoCollapse`** on long forms).
|
|
35
|
+
|
|
36
|
+
## Body layouts
|
|
37
|
+
|
|
38
|
+
Import from **`components/templates/focused-workflow-layouts.tsx`**:
|
|
39
|
+
|
|
40
|
+
| Layout | When |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| **`FocusedWorkflowSingleColumn`** | Default stack — header, form sections, footer actions (e.g. question authoring). |
|
|
43
|
+
| **`FocusedWorkflowStepForm`** + **`FocusedWorkflowWizardFooter`** | Multi-step wizard with progress list and sticky footer (placement-style flows). |
|
|
44
|
+
| **`FocusedWorkflowSidebarSections`** | Sectioned form with **left nav rail** (settings-style); put **`id`** on each `<section>` matching **`sections[].id`**. |
|
|
45
|
+
| **`FocusedWorkflowEmptyState`** | Placeholder / not-yet-configured route body. |
|
|
46
|
+
| **`FocusedWorkflowActionFooter`** | Single-step Cancel (Esc) + primary (Enter) without step chrome. |
|
|
47
|
+
|
|
48
|
+
Keyboard: wizard and action footers pair **`Shortcut`** with inline **`<Kbd variant="bare">`** per **`.cursor/rules/exxat-kbd-shortcuts.mdc`**.
|
|
49
|
+
|
|
50
|
+
## Golden references
|
|
51
|
+
|
|
52
|
+
| Route | Variant |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| **`/question-bank/new`** | Shell + **`FocusedWorkflowSingleColumn`** + domain composer |
|
|
55
|
+
| **`/settings`** | Shell (`maxWidth="lg"`) + **`FocusedWorkflowSidebarSections`** |
|
|
56
|
+
| **`/examples/focused-workflow`** | Showcase: empty, steps, sidebar (toggle) |
|
|
57
|
+
|
|
58
|
+
## Wiring checklist (implementers)
|
|
59
|
+
|
|
60
|
+
1. **Route** under **`app/(app)/…/page.tsx`** — thin server page; heavy UI in a **client** component.
|
|
61
|
+
2. **`siteHeader`**: **`back`** or **`breadcrumbs`** + **`title`**; avoid duplicating the trail in the body.
|
|
62
|
+
3. Pick **`maxWidth`**: **`md`** for simple forms, **`lg`** for settings / wide fields.
|
|
63
|
+
4. Choose **one** body layout; do **not** nest Miller columns or **`ListPageTemplate`** view tabs inside this shell.
|
|
64
|
+
5. Domain logic stays in **`*-composer.tsx`** / **`*-client.tsx`**; templates stay generic **`FocusedWorkflow*`**.
|
|
65
|
+
6. Run **§14** in **`AGENTS.md`** when reviewing.
|
|
66
|
+
|
|
67
|
+
## AI execution checklist (copy for PRs)
|
|
68
|
+
|
|
69
|
+
- [ ] **`FocusedWorkflowPageTemplate`** on the route — not ad-hoc **`SidebarInset`** / list-hub shell.
|
|
70
|
+
- [ ] Correct body variant: **single column** | **step form** | **sidebar sections** | **empty**.
|
|
71
|
+
- [ ] Wizard/action footers use **`Shortcut`** + bare **`Kbd`** in buttons.
|
|
72
|
+
- [ ] **No** list-hub view tabs, **no** folder-column explorer inside the page.
|
|
73
|
+
- [ ] Template/component names remain **generic** (not tied to one entity).
|
|
74
|
+
- [ ] **§6.5** — no toast for product feedback.
|
|
75
|
+
|
|
76
|
+
## Pair with
|
|
77
|
+
|
|
78
|
+
- **`exxat-page-vs-drawer.mdc`**, **`exxat-drawer-vs-dialog.mdc`**, **`exxat-kbd-shortcuts.mdc`**
|
|
79
|
+
- **`exxat-reuse-before-custom.mdc`** — extend **`focused-workflow-layouts.tsx`** before forking a second shell
|
|
80
|
+
|
|
81
|
+
## See also
|
|
82
|
+
|
|
83
|
+
- **`components/examples/focused-workflow-showcase.tsx`**
|
|
84
|
+
- **`packages/ui/consumer-extras/patterns/focused-workflow-page-pattern.md`** (npm consumers)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Shell surface elevation (sidebar · secondary panel · page)
|
|
2
2
|
|
|
3
3
|
> **Tokens:** `app/globals.css` — `--sidebar`, `--secondary-panel-bg`, `--background`, `--brand-tint*`.
|
|
4
|
-
> **Shell:** `components/templates/nested-secondary-panel-shell.tsx` — `bg-
|
|
4
|
+
> **Shell:** `components/templates/nested-secondary-panel-shell.tsx` — `bg-secondary-panel-bg` + `[data-slot="secondary-panel"]` rule in `globals.css`.
|
|
5
5
|
> **Cursor:** `.cursor/rules/exxat-primary-nav-secondary-panel.mdc` · `.cursor/skills/exxat-primary-nav-secondary-panel/SKILL.md`
|
|
6
6
|
|
|
7
7
|
## Stack (back → front)
|
|
@@ -24,14 +24,16 @@
|
|
|
24
24
|
## OKLCH formulas (dark)
|
|
25
25
|
|
|
26
26
|
```css
|
|
27
|
-
--secondary-panel-bg: color-mix(in oklch, var(--
|
|
27
|
+
--secondary-panel-bg: color-mix(in oklch, var(--background) 32%, var(--sidebar-accent) 68%);
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
**Do not** mix with light-mode **`--brand-tint`** in dark — product theme classes keep a light **`--brand-tint`** for logos/KPI glow; the secondary rail must use **`--sidebar-accent`** so the Library panel stays on-hue and dark.
|
|
31
|
+
|
|
30
32
|
Per-product **dark** theme blocks (`.theme-one.dark`, `.theme-prism.dark`, …) set **`--brand-tint-light`** where needed so mixes stay on-hue.
|
|
31
33
|
|
|
32
34
|
## Implementation
|
|
33
35
|
|
|
34
|
-
- **`NestedSecondaryPanelShell`** — `bg-
|
|
36
|
+
- **`NestedSecondaryPanelShell`** — `bg-secondary-panel-bg` (token + `[data-slot="secondary-panel"][data-state="open"]` in `globals.css`), `border-sidebar-border` (not generic `ring-border` alone).
|
|
35
37
|
- **Do not** set secondary panel to `bg-sidebar` (same as level 0 — loses elevation).
|
|
36
38
|
- **Do not** use `color-mix(… var(--sidebar) …)` without brand tokens if it drifts from active product theme.
|
|
37
39
|
|
|
@@ -3,36 +3,21 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { CommandMenuGroup, CommandMenuItem } from "@/lib/command-menu-config"
|
|
6
|
-
import {
|
|
6
|
+
import { LIST_HUB_DIRECTORY } from "@/lib/mock/list-hub-directory"
|
|
7
7
|
|
|
8
8
|
function sampleRowSearchItems(): CommandMenuItem[] {
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
p.student,
|
|
19
|
-
...nameParts,
|
|
20
|
-
p.program,
|
|
21
|
-
p.site,
|
|
22
|
-
p.internship,
|
|
23
|
-
p.specialization,
|
|
24
|
-
p.email,
|
|
25
|
-
p.supervisor,
|
|
26
|
-
p.status,
|
|
27
|
-
p.compliance,
|
|
28
|
-
]
|
|
29
|
-
.filter(Boolean)
|
|
30
|
-
.join(" "),
|
|
31
|
-
}
|
|
32
|
-
})
|
|
9
|
+
return LIST_HUB_DIRECTORY.map(row => ({
|
|
10
|
+
id: `sample-row-${row.id}`,
|
|
11
|
+
label: row.title,
|
|
12
|
+
icon: "fa-light fa-calendar-days",
|
|
13
|
+
href: "/data-list",
|
|
14
|
+
keywords: [row.id, row.title, row.category, row.eventDate, "list hub", "calendar"]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(" "),
|
|
17
|
+
}))
|
|
33
18
|
}
|
|
34
19
|
|
|
35
|
-
/** Built once at module load — avoids remapping
|
|
20
|
+
/** Built once at module load — avoids remapping rows on every layout render. */
|
|
36
21
|
export const COMMAND_MENU_SEARCH_DATA_GROUPS: CommandMenuGroup[] = [
|
|
37
22
|
{
|
|
38
23
|
id: "sample-rows",
|
|
@@ -42,7 +27,6 @@ export const COMMAND_MENU_SEARCH_DATA_GROUPS: CommandMenuGroup[] = [
|
|
|
42
27
|
},
|
|
43
28
|
]
|
|
44
29
|
|
|
45
|
-
/** Demo rows for the list hub — search-only so the palette stays lightweight on open. */
|
|
46
30
|
export function getCommandMenuSearchDataGroups(): CommandMenuGroup[] {
|
|
47
31
|
return COMMAND_MENU_SEARCH_DATA_GROUPS
|
|
48
32
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Display options for Data list (table / board / etc.) — shared across view types
|
|
2
|
+
* Display options for Data list (table / board / calendar / etc.) — shared across view types
|
|
3
3
|
* so hide/show preferences persist when switching views.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export type BoardLineCount = 1 | 2 | 3
|
|
7
7
|
|
|
8
|
+
/** Right-hand calendar body: month grid vs week rows (scroll stack uses the same months). */
|
|
9
|
+
export type CalendarMainView = "month" | "week"
|
|
10
|
+
|
|
8
11
|
export interface DataListDisplayOptions {
|
|
9
12
|
/**
|
|
10
13
|
* Board swimlanes: dataset field (table column key) used to split cards into columns.
|
|
@@ -13,7 +16,7 @@ export interface DataListDisplayOptions {
|
|
|
13
16
|
boardGroupByColumnKey: string
|
|
14
17
|
/** Max lines for primary text blocks on board cards */
|
|
15
18
|
boardLineCount: BoardLineCount
|
|
16
|
-
/** Page title block
|
|
19
|
+
/** Page title block */
|
|
17
20
|
showViewTitle: boolean
|
|
18
21
|
/** Board: phase column titles + descriptions. Table: column header row. */
|
|
19
22
|
showColumnLabels: boolean
|
|
@@ -22,6 +25,10 @@ export interface DataListDisplayOptions {
|
|
|
22
25
|
boardNewCardAbove: boolean
|
|
23
26
|
/** Toolbar search control (table view) */
|
|
24
27
|
showToolbarSearch: boolean
|
|
28
|
+
/** Calendar: left column — mini month, event list, layout tiles */
|
|
29
|
+
showCalendarSummaryPanel: boolean
|
|
30
|
+
/** Calendar: main scrollable body layout */
|
|
31
|
+
calendarMainView: CalendarMainView
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
export const DEFAULT_DATA_LIST_DISPLAY_OPTIONS: DataListDisplayOptions = {
|
|
@@ -32,4 +39,11 @@ export const DEFAULT_DATA_LIST_DISPLAY_OPTIONS: DataListDisplayOptions = {
|
|
|
32
39
|
showBoardColumnCounts: true,
|
|
33
40
|
boardNewCardAbove: true,
|
|
34
41
|
showToolbarSearch: true,
|
|
42
|
+
showCalendarSummaryPanel: false,
|
|
43
|
+
calendarMainView: "month",
|
|
35
44
|
}
|
|
45
|
+
|
|
46
|
+
export const CALENDAR_MAIN_VIEW_TILES = [
|
|
47
|
+
{ value: "month" as const, label: "Month", icon: "fa-calendar-days" },
|
|
48
|
+
{ value: "week" as const, label: "Week", icon: "fa-calendar-week" },
|
|
49
|
+
]
|