@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
|
@@ -7,21 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* ├─ PageHeader (title + actions; parent trail is in `SiteHeader`)
|
|
9
9
|
* │ · "New question" + "V1 · Last updated …" subtitle
|
|
10
|
-
* │ ·
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* │ │ · Question prompt (h1-style Textarea — type-aware)
|
|
15
|
-
* │ │ · Answer block — varies by question type
|
|
16
|
-
* │ │ · Explanation / rubric / model answer
|
|
17
|
-
* │ │ · References (repeatable list)
|
|
18
|
-
* │ └─ Inspector (right, bg-card panel)
|
|
19
|
-
* │ · Question format (SelectionTileGrid → compact)
|
|
20
|
-
* │ · Location (folder SelectionTileGrid)
|
|
21
|
-
* │ · Difficulty / Bloom / NBME (chips)
|
|
22
|
-
* │ · Tags (Input + Badge list)
|
|
23
|
-
* │ Sidebar-style collapse (⌘⌥]) — collapsed rail mimics
|
|
24
|
-
* │ `NestedSecondaryPanelShell` icon mode.
|
|
10
|
+
* │ · Save question (⏎) + Save as draft + ⋯ discard (⌘⌥M)
|
|
11
|
+
* └─ Single column (`FocusedWorkflowPageTemplate` — see `docs/focused-workflow-page-pattern.md`)
|
|
12
|
+
* · Details — format, folder, difficulty, Bloom, NBME, tags
|
|
13
|
+
* · Question prompt, answer block, explanation, references
|
|
25
14
|
*
|
|
26
15
|
* Composes existing primitives — `PageHeader`, `Form`/`FormField`,
|
|
27
16
|
* `Input`, `Textarea`, `Checkbox`, `Badge`, `Button`, `Tip`, `Kbd`,
|
|
@@ -776,11 +765,9 @@ export function NewQuestionComposer({
|
|
|
776
765
|
const router = useRouter()
|
|
777
766
|
const [submitting, setSubmitting] = React.useState(false)
|
|
778
767
|
const [tagDraft, setTagDraft] = React.useState("")
|
|
779
|
-
const [inspectorOpen, setInspectorOpen] = React.useState(true)
|
|
780
768
|
const [moreOpen, setMoreOpen] = React.useState(false)
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
inspector stays compact for the rest of the authoring flow. */
|
|
769
|
+
const [inspectorOpen, setInspectorOpen] = React.useState(true)
|
|
770
|
+
/** Question-type chooser — collapses to a compact row after first pick. */
|
|
784
771
|
const [typeChooserOpen, setTypeChooserOpen] = React.useState(true)
|
|
785
772
|
/** Local folder list — extended in-place when the author adds one
|
|
786
773
|
from the location picker so the new entry is selectable without
|
|
@@ -1102,11 +1089,6 @@ export function NewQuestionComposer({
|
|
|
1102
1089
|
disabled={submitting}
|
|
1103
1090
|
onInvoke={() => setMoreOpen(o => !o)}
|
|
1104
1091
|
/>
|
|
1105
|
-
<Shortcut
|
|
1106
|
-
keys="⌘⌥]"
|
|
1107
|
-
disabled={submitting}
|
|
1108
|
-
onInvoke={() => setInspectorOpen(o => !o)}
|
|
1109
|
-
/>
|
|
1110
1092
|
|
|
1111
1093
|
<form
|
|
1112
1094
|
onSubmit={form.handleSubmit(values => persist({ ...values, status: "in_review" }, "publish"))}
|
|
@@ -52,6 +52,7 @@ export function ProductSwitcher() {
|
|
|
52
52
|
<DropdownMenuTrigger asChild>
|
|
53
53
|
<SidebarMenuButton
|
|
54
54
|
size="lg"
|
|
55
|
+
tooltip={iconRail ? current.label : undefined}
|
|
55
56
|
className={cn(
|
|
56
57
|
"items-start py-2 text-sidebar-foreground data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground",
|
|
57
58
|
expandedOrMobile &&
|
|
@@ -91,8 +92,8 @@ export function ProductSwitcher() {
|
|
|
91
92
|
|
|
92
93
|
<DropdownMenuContent
|
|
93
94
|
align="start"
|
|
94
|
-
side="bottom"
|
|
95
|
-
sideOffset={4}
|
|
95
|
+
side={iconRail ? "right" : "bottom"}
|
|
96
|
+
sideOffset={iconRail ? 8 : 4}
|
|
96
97
|
>
|
|
97
98
|
<DropdownMenuLabel className="text-xs text-muted-foreground">
|
|
98
99
|
Switch product
|
|
@@ -27,6 +27,7 @@ import { QUESTION_BANK_ITEMS } from "@/lib/mock/question-bank"
|
|
|
27
27
|
import { QUESTION_BANK_HEADER_COLLABORATORS } from "@/lib/mock/question-bank-header-collaborators"
|
|
28
28
|
import { DEFAULT_QUESTION_BANK_FOLDERS, type QuestionBankFolder } from "@/lib/mock/question-bank-folders"
|
|
29
29
|
import { questionBankKpiInsight, questionBankKpiMetrics } from "@/lib/mock/question-bank-kpi"
|
|
30
|
+
import { QUESTION_BANK_SUPPORTED_VIEWS } from "@/lib/question-bank-supported-views"
|
|
30
31
|
import {
|
|
31
32
|
applyQuestionBankHubDisplayFilters,
|
|
32
33
|
isQuestionBankDefaultNav,
|
|
@@ -122,7 +123,6 @@ export function QuestionBankClient() {
|
|
|
122
123
|
|
|
123
124
|
// Stable Set of tab ids — defaults are constant so this only updates if tabs change.
|
|
124
125
|
const tabIds = React.useMemo(() => new Set(tabs.map(t => t.id)), [tabs])
|
|
125
|
-
|
|
126
126
|
// Keep the latest pathname / searchParamsKey / tabIds available to the (stable) hashchange
|
|
127
127
|
// listener via refs, so we don't re-subscribe a window listener on every URL change.
|
|
128
128
|
const navRef = React.useRef({ pathname, searchParamsKey, tabIds, hubBasePath })
|
|
@@ -314,6 +314,7 @@ export function QuestionBankClient() {
|
|
|
314
314
|
onFoldersChange={setFolders}
|
|
315
315
|
onItemsChange={setItems}
|
|
316
316
|
view={tab.viewType}
|
|
317
|
+
supportedViewTypes={QUESTION_BANK_SUPPORTED_VIEWS}
|
|
317
318
|
onViewChange={(v: DataListViewType) => updateTab({ viewType: v, icon: dataListViewIcon(v) })}
|
|
318
319
|
/>
|
|
319
320
|
)}
|
|
@@ -417,6 +418,7 @@ export function QuestionBankClient() {
|
|
|
417
418
|
/>
|
|
418
419
|
)}
|
|
419
420
|
showMetrics={showMetrics}
|
|
421
|
+
supportedViewTypes={QUESTION_BANK_SUPPORTED_VIEWS}
|
|
420
422
|
exportOpen={exportOpen}
|
|
421
423
|
onExportOpenChange={setExportOpen}
|
|
422
424
|
exportTotalRows={count}
|
|
@@ -433,6 +435,7 @@ export function QuestionBankClient() {
|
|
|
433
435
|
onFoldersChange={setFolders}
|
|
434
436
|
onItemsChange={setItems}
|
|
435
437
|
view={tab.viewType}
|
|
438
|
+
supportedViewTypes={QUESTION_BANK_SUPPORTED_VIEWS}
|
|
436
439
|
onViewChange={(v: DataListViewType) => updateTab({ viewType: v, icon: dataListViewIcon(v) })}
|
|
437
440
|
/>
|
|
438
441
|
)}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from "@/components/ui/dropdown-menu"
|
|
11
|
+
import { cn } from "@/lib/utils"
|
|
12
|
+
import { ListPageFolderColumnsPanel } from "@/components/data-views/list-page-folder-columns-panel"
|
|
13
|
+
import { FolderDetailsShell } from "@/components/folder-details-shell"
|
|
14
|
+
import type { QuestionBankItem } from "@/lib/mock/question-bank"
|
|
15
|
+
import {
|
|
16
|
+
type QuestionBankFolder,
|
|
17
|
+
QUESTION_BANK_FOLDER_COLOR_STYLES,
|
|
18
|
+
QUESTION_BANK_FOLDER_ICON_COLORS,
|
|
19
|
+
} from "@/lib/mock/question-bank-folders"
|
|
20
|
+
|
|
21
|
+
function isQuestionBankFolder(
|
|
22
|
+
item: QuestionBankFolder | QuestionBankItem,
|
|
23
|
+
): item is QuestionBankFolder {
|
|
24
|
+
return "parentId" in item && !("stem" in item)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface QuestionBankFolderColumnsPanelProps {
|
|
28
|
+
folders: QuestionBankFolder[]
|
|
29
|
+
rows: QuestionBankItem[]
|
|
30
|
+
panelRenderDetail: (row: QuestionBankItem) => React.ReactNode
|
|
31
|
+
onAddFolder: (parentId: string | null) => void
|
|
32
|
+
onAddQuestion: (parentId: string | null) => void
|
|
33
|
+
onCustomizeFolder?: (folder: QuestionBankFolder) => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Question bank **panel** view — Miller columns over folders + questions. */
|
|
37
|
+
export function QuestionBankFolderColumnsPanel({
|
|
38
|
+
folders,
|
|
39
|
+
rows,
|
|
40
|
+
panelRenderDetail,
|
|
41
|
+
onAddFolder,
|
|
42
|
+
onAddQuestion,
|
|
43
|
+
onCustomizeFolder,
|
|
44
|
+
}: QuestionBankFolderColumnsPanelProps) {
|
|
45
|
+
return (
|
|
46
|
+
<ListPageFolderColumnsPanel<QuestionBankFolder, QuestionBankItem>
|
|
47
|
+
folders={folders}
|
|
48
|
+
items={rows}
|
|
49
|
+
isFolder={isQuestionBankFolder}
|
|
50
|
+
getFolderParentId={f => f.parentId}
|
|
51
|
+
getFolderName={f => f.name}
|
|
52
|
+
getItemFolderId={i => i.folderId}
|
|
53
|
+
getItemLabel={i => i.stem}
|
|
54
|
+
renderItemDetail={panelRenderDetail}
|
|
55
|
+
onAddFolder={onAddFolder}
|
|
56
|
+
onAddItem={onAddQuestion}
|
|
57
|
+
addItemAriaLabel="Add question"
|
|
58
|
+
renderFolderDetail={(folder, { folders: allFolders, items }) => (
|
|
59
|
+
<FolderDetailsShell folder={folder} folders={allFolders} questions={items} />
|
|
60
|
+
)}
|
|
61
|
+
renderFolderRowClassName={(folder, { isSelected, depth }) =>
|
|
62
|
+
!isSelected && folder.colorKey && depth > 0
|
|
63
|
+
? QUESTION_BANK_FOLDER_COLOR_STYLES[folder.colorKey]?.tile
|
|
64
|
+
: undefined
|
|
65
|
+
}
|
|
66
|
+
renderFolderIcon={(folder, { isSelected }) => (
|
|
67
|
+
<i
|
|
68
|
+
className={cn(
|
|
69
|
+
"fa-folder shrink-0 text-sm",
|
|
70
|
+
isSelected ? "fa-solid" : "fa-light",
|
|
71
|
+
folder.colorKey && QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey],
|
|
72
|
+
)}
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
renderItemMeta={(item, { isSelected }) =>
|
|
77
|
+
item.type === "multiple_choice"
|
|
78
|
+
? "MCQ"
|
|
79
|
+
: item.difficulty?.charAt(0).toUpperCase() ?? ""
|
|
80
|
+
}
|
|
81
|
+
renderFolderActions={folder => (
|
|
82
|
+
<DropdownMenu>
|
|
83
|
+
<DropdownMenuTrigger asChild>
|
|
84
|
+
<Button
|
|
85
|
+
type="button"
|
|
86
|
+
size="icon-xs"
|
|
87
|
+
variant="ghost"
|
|
88
|
+
aria-label={`Actions for folder ${folder.name}`}
|
|
89
|
+
className="shrink-0 opacity-0 transition-opacity group-hover:opacity-100"
|
|
90
|
+
>
|
|
91
|
+
<i className="fa-light fa-ellipsis text-xs" aria-hidden="true" />
|
|
92
|
+
</Button>
|
|
93
|
+
</DropdownMenuTrigger>
|
|
94
|
+
<DropdownMenuContent align="end">
|
|
95
|
+
<DropdownMenuItem onSelect={() => onCustomizeFolder?.(folder)}>
|
|
96
|
+
<i className="fa-light fa-wand-magic-sparkles text-xs" aria-hidden="true" />
|
|
97
|
+
Customize
|
|
98
|
+
</DropdownMenuItem>
|
|
99
|
+
</DropdownMenuContent>
|
|
100
|
+
</DropdownMenu>
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|