@exxatdesignux/ui 0.2.14 → 0.2.16
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 +20 -0
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -1
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +3 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +3 -1
- package/consumer-extras/patterns/collaboration-access-pattern.md +2 -0
- package/package.json +1 -1
- package/src/components/ui/dropdown-menu.tsx +2 -0
- package/src/components/ui/popover.tsx +2 -2
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tooltip.tsx +7 -1
- package/src/globals.css +27 -2
- package/src/theme.css +4 -2
- package/template/AGENTS.md +6 -4
- package/template/app/(app)/question-bank/layout.tsx +11 -4
- package/template/app/globals.css +34 -2
- package/template/components/app-sidebar.tsx +89 -41
- package/template/components/ask-leo-sidebar.tsx +1 -2
- package/template/components/compliance-board-view.tsx +11 -3
- package/template/components/compliance-list-view.tsx +16 -3
- package/template/components/compliance-table.tsx +5 -1
- package/template/components/data-table/index.tsx +25 -11
- package/template/components/data-views/finder-panel-view.tsx +2 -2
- package/template/components/data-views/index.ts +19 -0
- package/template/components/data-views/list-page-split-details-placeholder.tsx +3 -3
- package/template/components/data-views/list-page-split-hub-tokens.ts +1 -1
- package/template/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/template/components/data-views/outline-tree-menu.tsx +157 -0
- package/template/components/data-views/question-bank-folder-tree-branch.tsx +210 -0
- package/template/components/exxat-product-logo.tsx +11 -72
- package/template/components/folder-details-shell.tsx +1 -1
- package/template/components/hub-tree-panel-view.tsx +88 -80
- package/template/components/key-metrics.tsx +50 -13
- package/template/components/page-header.tsx +19 -10
- package/template/components/product-switcher.tsx +1 -4
- package/template/components/question-bank-board-view.tsx +11 -2
- package/template/components/question-bank-client.tsx +111 -69
- package/template/components/question-bank-list-view.tsx +12 -1
- package/template/components/question-bank-page-header.tsx +18 -2
- package/template/components/question-bank-secondary-nav.tsx +12 -225
- package/template/components/question-bank-table.tsx +6 -1
- package/template/components/secondary-panel.tsx +1 -1
- package/template/components/site-header.tsx +21 -2
- package/template/components/team-board-view.tsx +11 -3
- package/template/components/team-list-view.tsx +16 -3
- package/template/components/team-table.tsx +6 -2
- package/template/components/templates/dedicated-search-landing-template.tsx +86 -20
- package/template/components/templates/list-page.tsx +1 -3
- package/template/components/templates/nested-secondary-panel-shell.tsx +3 -4
- package/template/docs/collaboration-access-pattern.md +2 -0
- package/template/docs/question-bank-hub-header-pattern.md +25 -0
- package/template/hooks/use-secondary-panel-hub-nav.ts +17 -1
- package/template/lib/mock/navigation.tsx +30 -1
- package/template/lib/question-bank-nav.ts +26 -0
- package/template/package.json +3 -3
- package/template/components/command-menu-01.tsx +0 -133
- package/template/components/command-menu-02.tsx +0 -386
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,26 @@ After the user bumps `@exxatdesignux/ui`, do this in order:
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## [0.2.16] - 2026-05-15
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Tokens**: `globals.css` / `theme.css` refinements and starter **`template/`** parity with the web app (layout, Question bank hub chrome, navigation).
|
|
23
|
+
- **Consumer extras**: Cursor skills + pattern docs refreshed for collaboration / Question bank hub header.
|
|
24
|
+
|
|
25
|
+
### Chore (monorepo)
|
|
26
|
+
|
|
27
|
+
- **Dependabot**: version updates for `apps/web`, `packages/ui`, workspace root, and grouped Next.js + GitHub Actions bumps (see repo `.github/dependabot.yml`).
|
|
28
|
+
- **Web app**: Next.js **16.2.6** (security patches), primary shell scroll fixes, `SiteHeader` chrome alignment.
|
|
29
|
+
|
|
30
|
+
## [0.2.15] - 2026-05-13
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **Globals**: `button` / `[role="button"]` **`cursor: pointer`** is applied **outside** `@layer base` (after utilities) so it wins over Tailwind preflight; filter bar and native controls show a hand cursor consistently.
|
|
35
|
+
- **Primitives**: **`DropdownMenuTrigger`**, **`PopoverTrigger`**, and **`TooltipTrigger`** merge **`cursor-pointer`** into **`className`**; **`SelectTrigger`** includes **`cursor-pointer`** (with **`disabled:cursor-not-allowed`**).
|
|
36
|
+
- **Starter / app parity**: `sync-template` brings **`DataTable`** filter bar **`cursor-pointer`** and list-hub table/list/board affordance updates into **`template/`**.
|
|
37
|
+
|
|
18
38
|
## [0.2.14] - 2026-05-14
|
|
19
39
|
|
|
20
40
|
### Fixed
|
|
@@ -8,7 +8,8 @@ user-invocable: true
|
|
|
8
8
|
|
|
9
9
|
**Handbook:** `apps/web/AGENTS.md` §4.7
|
|
10
10
|
**Narrative:** `apps/web/docs/collaboration-access-pattern.md`
|
|
11
|
-
**Cursor rule:** `.cursor/rules/exxat-collaboration-access.mdc`
|
|
11
|
+
**Cursor rule:** `.cursor/rules/exxat-collaboration-access.mdc`
|
|
12
|
+
**Related (Question bank folder scope + ⋯ Customize folder):** `.cursor/rules/exxat-question-bank-hub-header.mdc` · `docs/question-bank-hub-header-pattern.md`
|
|
12
13
|
|
|
13
14
|
## Wiring checklist
|
|
14
15
|
|
|
@@ -19,6 +20,7 @@ user-invocable: true
|
|
|
19
20
|
5. **Access maps** — `lib/collaborator-access.ts` for Owner / Editor / Commenter / Viewer, invite options, and **`COLLABORATION_HEADER_ADD_LABEL`**.
|
|
20
21
|
6. **Header** — empty roster → outline **Add collaborator**; non-empty → face rail; both open the invite sheet.
|
|
21
22
|
7. **Invite sheet** — `InviteCollaboratorsDrawer`: export-style **`Sheet`**, combined email + access menu, grouped roster (name → email → role tags → access badge).
|
|
23
|
+
8. **Question bank — folder URL scope** — When **`?scope=folder`**, **`QuestionBankPageHeader`** **⋯** also lists **Customize folder**; **`QuestionBankNewFolderSheet`** on **`QuestionBankClient`** — **`.cursor/rules/exxat-question-bank-hub-header.mdc`**, **`docs/question-bank-hub-header-pattern.md`**.
|
|
22
24
|
|
|
23
25
|
## MUST NOT
|
|
24
26
|
|
|
@@ -22,6 +22,7 @@ description: >
|
|
|
22
22
|
- **App root:** `exxat-ds/app/(app)/` — route group that wraps all authenticated pages
|
|
23
23
|
- **Single source of truth:** `exxat-ds/AGENTS.md` for full prose explanations; this skill is the actionable summary
|
|
24
24
|
- **Companion skills (narrow topics):** `exxat-fontawesome-icons`, `exxat-primary-nav-secondary-panel`, `exxat-centralized-list-dataset`, `exxat-list-page-view-shells`, `exxat-dedicated-search-surfaces`, `exxat-accessibility`, `exxat-board-cards`, `exxat-collaboration-access` — live under `.cursor/skills/`; vetted copies ship with **`@exxatdesignux/ui`** in `consumer-extras/cursor-skills/` after **`pnpm --filter @exxatdesignux/ui vendor:consumer-extras`**.
|
|
25
|
+
- **Question bank folder-scoped header (rule + doc):** **`.cursor/rules/exxat-question-bank-hub-header.mdc`** and **`docs/question-bank-hub-header-pattern.md`** — pair with **`exxat-primary-nav-secondary-panel`** when URL **`scope=folder`** drives the hub title.
|
|
25
26
|
- **Consumer repos (npm install of `@exxatdesignux/ui`):** After a version bump, read **`node_modules/@exxatdesignux/ui/CHANGELOG.md`**, run **`npx --package=@exxatdesignux/ui@latest exxat-ui sync-extras`** so **`docs/exxat-ds/consumer-upgrade-checklist.md`** and Cursor skills match the tarball, and diff the host app against **`node_modules/@exxatdesignux/ui/template/`** for anything new to port (routes, re-exports, AGENTS). Use **`exxat-ui changelog`**, **`exxat-ui update`**, and **`exxat-ui doctor`** for CLI guidance.
|
|
26
27
|
|
|
27
28
|
---
|
|
@@ -255,6 +256,8 @@ Use `PageHeader` from `@/components/page-header` for the content-area header (be
|
|
|
255
256
|
|
|
256
257
|
When a hub is **shared**, use **`PageHeader` `variant="collaboration"`**: **empty roster** → outline **Add collaborator**; **non-empty** → face rail (faces / **`+N`** open the invite sheet). **Invite people** also lives under the entity header **⋯ More** and opens **`InviteCollaboratorsDrawer`** via **`CollaborationAccessFlow`** when possible. Library access (Owner / Editor / Commenter / Viewer) comes from **`lib/collaborator-access.ts`**; directory tags (Faculty, Program coordinator, Director) use **`PageHeaderCollaborator.roles`**.
|
|
257
258
|
|
|
259
|
+
**Question bank library — folder URL scope:** When **`?scope=folder&folderId=`** applies, **⋯ More** must also offer **Customize folder** (**`QuestionBankPageHeader`** **`onCustomizeFolder`**) and the **`QuestionBankNewFolderSheet`** must be mounted on **`QuestionBankClient`** so it works on every **`ListPageTemplate`** view tab. **`.cursor/rules/exxat-question-bank-hub-header.mdc`** · **`docs/question-bank-hub-header-pattern.md`** (app: **`apps/web/docs/...`**).
|
|
260
|
+
|
|
258
261
|
**Handbook:** `apps/web/AGENTS.md` §4.7 · **Doc:** `docs/collaboration-access-pattern.md` · **Skill:** `.cursor/skills/exxat-collaboration-access/SKILL.md` · **Reference:** Question bank header + client.
|
|
259
262
|
|
|
260
263
|
---
|
|
@@ -15,7 +15,8 @@ user-invocable: true
|
|
|
15
15
|
2. **`components/secondary-panel.tsx`** — add **`PANELS["<id>"]`** → panel shell (title, optional search) + secondary nav component.
|
|
16
16
|
3. **Hub client** — mount **`*PanelActivator`** with **`useAutoPanel("<id>")`** (same id) for the lifetime of the route (e.g. `QuestionBankPanelActivator`).
|
|
17
17
|
4. **Data** — keep **one** **`useTableState`** / **`tableState.rows`**; drive scope from **URL** + small helpers (see **`lib/question-bank-nav.ts`**).
|
|
18
|
-
5. **
|
|
18
|
+
5. **Folder-scoped hub header (Question bank library)** — When **`scope === "folder"`** in the URL, **`QuestionBankPageHeader`** **⋯ More** includes **Customize folder**; mount **`QuestionBankNewFolderSheet`** on **`QuestionBankClient`** so it works on **all** **`ListPageTemplate`** view tabs — **`.cursor/rules/exxat-question-bank-hub-header.mdc`**, **`docs/question-bank-hub-header-pattern.md`**.
|
|
19
|
+
6. **Collapse control** — the nested rail header uses **`collapseActiveSecondaryPanel()`** (angles-left icon), not “close”, so the panel stays dismissed until **`openPanel`** runs again (nav, scope hook, or hub re-entry). Layout effects that auto-call **`openPanel`** must respect **`secondaryPanelAutoReopenSuppressed`** (see **`app/(app)/question-bank/layout.tsx`** + **`SecondaryPanelProvider`**).
|
|
19
20
|
|
|
20
21
|
## MUST NOT
|
|
21
22
|
|
|
@@ -26,3 +27,4 @@ user-invocable: true
|
|
|
26
27
|
|
|
27
28
|
- `components/app-sidebar.tsx` — `openPanel` on same-route primary click.
|
|
28
29
|
- `components/question-bank-secondary-nav.tsx` + `lib/question-bank-nav.ts`.
|
|
30
|
+
- **Folder-scoped header customize:** `components/question-bank-page-header.tsx`, `components/question-bank-client.tsx` — **`docs/question-bank-hub-header-pattern.md`**, **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Shared UI for **who can access a hub** (face stack in the header) and **inviting people** (floating sheet). **Reference:** Question bank — `QuestionBankPageHeader`, `QuestionBankClient`, `InviteCollaboratorsDrawer`.
|
|
4
4
|
|
|
5
|
+
**Folder-scoped question bank:** When the library URL selects a folder (`?scope=folder&folderId=`), the same header **⋯ More** menu also exposes **Customize folder** (name / color / icon) via **`QuestionBankNewFolderSheet`** mounted on **`QuestionBankClient`** so it works on every view tab. See **`docs/question-bank-hub-header-pattern.md`** and **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
|
|
6
|
+
|
|
5
7
|
## When to use
|
|
6
8
|
|
|
7
9
|
- A list hub or library is **shared** across people (not a private directory).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exxatdesignux/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Exxat shared design system (components, hooks, tokens). Monorepo setup: clone repo then pnpm bootstrap at workspace root — see github.com/ExxatDesign/Exxat-DS-Workspace README.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -21,12 +21,14 @@ function DropdownMenuPortal({
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function DropdownMenuTrigger({
|
|
24
|
+
className,
|
|
24
25
|
...props
|
|
25
26
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
26
27
|
return (
|
|
27
28
|
<DropdownMenuPrimitive.Trigger
|
|
28
29
|
data-slot="dropdown-menu-trigger"
|
|
29
30
|
suppressHydrationWarning
|
|
31
|
+
className={cn("cursor-pointer", className)}
|
|
30
32
|
{...props}
|
|
31
33
|
/>
|
|
32
34
|
)
|
|
@@ -8,8 +8,8 @@ function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root
|
|
|
8
8
|
return <PopoverPrimitive.Root {...props} />
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
12
|
-
return <PopoverPrimitive.Trigger {...props} />
|
|
11
|
+
function PopoverTrigger({ className, ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
12
|
+
return <PopoverPrimitive.Trigger className={cn("cursor-pointer", className)} {...props} />
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
@@ -43,7 +43,7 @@ function SelectTrigger({
|
|
|
43
43
|
data-slot="select-trigger"
|
|
44
44
|
data-size={size}
|
|
45
45
|
className={cn(
|
|
46
|
-
"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pe-2 ps-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/15 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
46
|
+
"flex w-fit cursor-pointer items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pe-2 ps-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/15 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
47
47
|
className
|
|
48
48
|
)}
|
|
49
49
|
{...props}
|
|
@@ -25,10 +25,16 @@ function Tooltip({
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function TooltipTrigger({
|
|
28
|
+
className,
|
|
28
29
|
...props
|
|
29
30
|
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
31
|
return (
|
|
31
|
-
<TooltipPrimitive.Trigger
|
|
32
|
+
<TooltipPrimitive.Trigger
|
|
33
|
+
data-slot="tooltip-trigger"
|
|
34
|
+
suppressHydrationWarning
|
|
35
|
+
className={cn("cursor-pointer", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
32
38
|
)
|
|
33
39
|
}
|
|
34
40
|
|
package/src/globals.css
CHANGED
|
@@ -165,6 +165,19 @@ html[data-text-size="large"] {
|
|
|
165
165
|
--brand-preview-one: var(--brand-tint);
|
|
166
166
|
--brand-preview-prism: oklch(0.57 0.24 342);
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Ask Leo panel tints (same mixes as the vertical wash). Use for blobs, cards, etc.
|
|
170
|
+
* `--leo-surface-gradient` is the full linear wash on `AskLeoSidebar`.
|
|
171
|
+
*/
|
|
172
|
+
--leo-surface-tint-a: color-mix(in oklch, var(--brand-color) 4%, var(--background));
|
|
173
|
+
--leo-surface-tint-b: color-mix(in oklch, var(--brand-color) 8%, var(--background));
|
|
174
|
+
--leo-surface-gradient: linear-gradient(180deg, var(--leo-surface-tint-a) 0%, var(--leo-surface-tint-b) 100%);
|
|
175
|
+
|
|
176
|
+
/* KeyMetrics `variant="flat"` — soft KPI band (lavender wash → canvas; dark: subtle brand lift) */
|
|
177
|
+
--key-metrics-flat-grad-top: color-mix(in oklch, var(--brand-tint-light) 90%, var(--background));
|
|
178
|
+
--key-metrics-flat-grad-mid: color-mix(in oklch, var(--brand-tint) 34%, var(--background));
|
|
179
|
+
--key-metrics-flat-cell-bg: color-mix(in oklch, var(--background) 80%, var(--brand-tint-light));
|
|
180
|
+
|
|
168
181
|
/* ── Surfaces ────────────────────────────────────────────────── */
|
|
169
182
|
--background: oklch(1 0 0);
|
|
170
183
|
--foreground: oklch(0.145 0 0); /* ≈ #1A1A1A — 17:1 on white ✓ */
|
|
@@ -243,7 +256,8 @@ html[data-text-size="large"] {
|
|
|
243
256
|
--sidebar-ring: oklch(0.25 0 0);
|
|
244
257
|
/* Nav section titles — ≥4.5:1 vs --sidebar (not vs page white) */
|
|
245
258
|
--sidebar-section-label-foreground: color-mix(in oklch, var(--sidebar-foreground) 58%, var(--sidebar));
|
|
246
|
-
|
|
259
|
+
/* Nested secondary rail — soft brand wash on canvas (not pure `--background`, not full `--sidebar`). */
|
|
260
|
+
--secondary-panel-bg: color-mix(in oklch, var(--brand-tint) 34%, var(--background));
|
|
247
261
|
|
|
248
262
|
/* Browser UI (meta theme-color) — aligned with --brand-tint */
|
|
249
263
|
--theme-color-chrome: #f3f2f8;
|
|
@@ -351,6 +365,10 @@ html[data-text-size="large"] {
|
|
|
351
365
|
--destructive: oklch(0.65 0.20 25); /* brighter for dark bg */
|
|
352
366
|
--destructive-foreground: oklch(0.10 0 0);
|
|
353
367
|
|
|
368
|
+
--key-metrics-flat-grad-top: color-mix(in oklch, var(--brand-color) 14%, var(--background));
|
|
369
|
+
--key-metrics-flat-grad-mid: color-mix(in oklch, var(--muted) 42%, var(--background));
|
|
370
|
+
--key-metrics-flat-cell-bg: color-mix(in oklch, var(--background) 88%, var(--brand-color));
|
|
371
|
+
|
|
354
372
|
/* Borders — visible but not washed out on dark surfaces */
|
|
355
373
|
--border: oklch(0.38 0.008 270);
|
|
356
374
|
--border-control: oklch(0.72 0.012 270);
|
|
@@ -399,7 +417,8 @@ html[data-text-size="large"] {
|
|
|
399
417
|
--sidebar-border: oklch(0.38 0.010 270);
|
|
400
418
|
--sidebar-ring: oklch(0.85 0 0);
|
|
401
419
|
--sidebar-section-label-foreground: color-mix(in oklch, var(--sidebar-foreground) 48%, var(--sidebar));
|
|
402
|
-
|
|
420
|
+
/* Nested secondary rail — dark: subtle brand lift on canvas (not flat `--background` only). */
|
|
421
|
+
--secondary-panel-bg: color-mix(in oklch, var(--background) 82%, var(--brand-color) 18%);
|
|
403
422
|
--theme-color-chrome: #2f2d36;
|
|
404
423
|
|
|
405
424
|
/* Lifted scrim on dark — white-tinted veil, not heavy black */
|
|
@@ -1792,3 +1811,9 @@ html:is([data-contrast="high"], [data-contrast="windows"]) {
|
|
|
1792
1811
|
background-color: var(--banner-prism-bg);
|
|
1793
1812
|
}
|
|
1794
1813
|
}
|
|
1814
|
+
|
|
1815
|
+
/* After all @layer rules: win over Tailwind preflight / layer merge so real controls show a pointer. */
|
|
1816
|
+
button:not(:disabled):not([disabled]),
|
|
1817
|
+
[role="button"]:not([aria-disabled="true"]):not([data-disabled]) {
|
|
1818
|
+
cursor: pointer;
|
|
1819
|
+
}
|
package/src/theme.css
CHANGED
|
@@ -219,7 +219,8 @@
|
|
|
219
219
|
--sidebar-ring: oklch(0.25 0 0);
|
|
220
220
|
/* Nav section titles — ≥4.5:1 vs --sidebar (not vs page white) */
|
|
221
221
|
--sidebar-section-label-foreground: color-mix(in oklch, var(--sidebar-foreground) 58%, var(--sidebar));
|
|
222
|
-
|
|
222
|
+
/* Nested secondary rail — soft brand wash on canvas (not pure `--background`, not full `--sidebar`). */
|
|
223
|
+
--secondary-panel-bg: color-mix(in oklch, var(--brand-tint) 34%, var(--background));
|
|
223
224
|
|
|
224
225
|
/* Browser UI (meta theme-color) — aligned with --brand-tint */
|
|
225
226
|
--theme-color-chrome: #f3f2f8;
|
|
@@ -345,7 +346,8 @@
|
|
|
345
346
|
--sidebar-border: oklch(0.38 0.010 270);
|
|
346
347
|
--sidebar-ring: oklch(0.85 0 0);
|
|
347
348
|
--sidebar-section-label-foreground: color-mix(in oklch, var(--sidebar-foreground) 48%, var(--sidebar));
|
|
348
|
-
|
|
349
|
+
/* Nested secondary rail — dark: subtle brand lift on canvas (not flat `--background` only). */
|
|
350
|
+
--secondary-panel-bg: color-mix(in oklch, var(--background) 82%, var(--brand-color) 18%);
|
|
349
351
|
--theme-color-chrome: #2f2d36;
|
|
350
352
|
|
|
351
353
|
/* Lifted scrim on dark — white-tinted veil, not heavy black */
|
package/template/AGENTS.md
CHANGED
|
@@ -162,7 +162,9 @@ Thread **`view`** and **`onViewChange`** from the **client** → **table / toolb
|
|
|
162
162
|
|
|
163
163
|
**MUST NOT:** Set **`secondaryPanel`** without **`PANELS[id]`** and **`useAutoPanel`** — users see **collapsed** main nav with **no** panel body.
|
|
164
164
|
|
|
165
|
-
**
|
|
165
|
+
**Folder-scoped library (Question bank):** When the URL is scoped to a folder (**`scope === "folder"`** + **`folderId`** via **`lib/question-bank-nav.ts`**), the hub **`QuestionBankPageHeader`** **⋯ More** menu **MUST** include **Customize folder** and open **`QuestionBankNewFolderSheet`** from the **hub client** so the action works on **every** **`ListPageTemplate`** view tab — not only inside **`QuestionBankTable`** branches that mount their own sheet. **Pattern:** **`docs/question-bank-hub-header-pattern.md`**. **Cursor rule:** **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
|
|
166
|
+
|
|
167
|
+
**Cursor rule (panel wiring):** **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. **Icons in panel:** **`.cursor/rules/exxat-fontawesome-icons.mdc`**.
|
|
166
168
|
|
|
167
169
|
### 4.7 Collaboration & access (shared hubs)
|
|
168
170
|
|
|
@@ -534,7 +536,7 @@ New pages **SHOULD** namespace keys and version JSON (`v: 1`) for future migrati
|
|
|
534
536
|
| Data view charts: **`ChartFigure`** + **`ChartDataTable`**; keyboard highlight via **`chart-keyboard-selection`** (§4.3); layout via **`data-view-dashboard-storage`** | Ad-hoc `localStorage` keys for dashboard layout; opacity-only “selection” without `activeBar`/`activeShape` |
|
|
535
537
|
| Board cards: **`ListPageBoardCard`** shell; status via **`ListHubStatusBadge`** + **`lib/list-status-badges`**; no **`uppercase`** on status chips (§4.4) | One-off board card markup; status as plain body text; duplicated status maps outside **`list-status-badges`**; **empty placeholder** primary hubs (§4.1) |
|
|
536
538
|
| **§4.5** — Non-table view bodies use **`ListPageViewFrame`** (+ **`data-views/`** shells); new grids are generic components, not route-only markup | Duplicated `mx-4` / `max-w-*` per hub; wrapping **`DataTable`** so inset **doubles** (**§5**) |
|
|
537
|
-
| **§4.6** — **`secondaryPanel`** + **`PANELS`** + **`useAutoPanel`** together for nested scope nav | **`secondaryPanel`** id with no panel component or activator |
|
|
539
|
+
| **§4.6** — **`secondaryPanel`** + **`PANELS`** + **`useAutoPanel`** together for nested scope nav; **folder URL scope** → header **⋯** **Customize folder** + client-mounted **`QuestionBankNewFolderSheet`** (**`exxat-question-bank-hub-header.mdc`**) | **`secondaryPanel`** id with no panel component or activator; folder scope with customize **only** inside a single view tab’s subtree |
|
|
538
540
|
| **§4.7** — **`PageHeader` `variant="collaboration"`** + **`CollaborationAccessFlow`** / **`InviteCollaboratorsDrawer`**; empty **Add collaborator** + non-empty face rail; roster + invite from **`collaborator-access.ts`** | Extra invite beside a populated face rail; per-person roster cards; forked access enums; toast on invite |
|
|
539
541
|
| **§4.8** — **`DedicatedSearch*`** templates + composer + recents; **no** `localStorage` in **`useState`** initial paint; hub-specific **`patchSearchParams`** only | Forked `*QuestionBank*SearchLanding*` shells for another entity; hydration mismatch on recents |
|
|
540
542
|
| **Font Awesome** — Kit in **`app/layout.tsx`**; **`fa-light` / `fa-solid`** conventions; **`aria-hidden`** on decorative **`<i>`**; run **`fa:subset-audit`** when adding glyphs (**`exxat-fontawesome-icons.mdc`**) | Parallel icon libraries for the same product chrome |
|
|
@@ -570,7 +572,7 @@ Copy and complete when implementing or reviewing:
|
|
|
570
572
|
- [ ] **Accessibility:** §8 — tablist/toolbar patterns, **≥24px** targets for icon-only controls, contrast on tinted surfaces, dialog/sheet/drawer **titles**; **every icon that communicates info has a text alternative** — adjacent label (preferred) OR `aria-label` + `Tooltip` (§8.6 Case A/B/C, covers informational icons like calendar-for-date, status dots, AND icon-only buttons); **kbd inside a button uses `<Kbd variant="bare">`** (§8.7); re-run **axe** on Placements when changing views toolbar.
|
|
571
573
|
- [ ] **Coach marks (§11):** `CoachMark` + `useCoachMark`; register in **`coach-mark-registry`**; use **`enabled`** / **`dependsOnDismissedFlowId`** when a tour must wait for another flow or a specific view (e.g. **dashboard**); customize-dashboard flows use **`lib/dashboard-customize-coach-mark.ts`**.
|
|
572
574
|
- [ ] **Application sidebar (§9.1):** **`ExxatProductLogo`** for product; **`logoDevUrl`** for schools; team switcher **`DropdownMenuContent`** keeps the explicit wide school/program surface (**`!w-max`** + min/max width); expanded switcher **`h-auto min-h-12`**; no **`CollapsibleTrigger` → `SidebarMenuButton` with `tooltip` prop**; child links **popover** on icon rail; profile **`stockPortraitUrl`** + **`referrerPolicy="no-referrer"`** on **`AvatarImage`**.
|
|
573
|
-
- [ ] **Secondary panel (§4.6):** If **`NavLinkItem.secondaryPanel`** is set — **`PANELS[id]`** in **`secondary-panel.tsx`**, hub mounts **`useAutoPanel(id)`**, scope syncs to URL + **`tableState.rows`** — **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**.
|
|
575
|
+
- [ ] **Secondary panel (§4.6):** If **`NavLinkItem.secondaryPanel`** is set — **`PANELS[id]`** in **`secondary-panel.tsx`**, hub mounts **`useAutoPanel(id)`**, scope syncs to URL + **`tableState.rows`** — **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. **Question bank folder scope:** header **⋯** → **Customize folder** + **`QuestionBankNewFolderSheet`** on **`QuestionBankClient`** — **`docs/question-bank-hub-header-pattern.md`**, **`.cursor/rules/exxat-question-bank-hub-header.mdc`**.
|
|
574
576
|
- [ ] **Collaboration & access (§4.7):** Shared hubs use **`variant="collaboration"`**, empty **Add collaborator** / non-empty face rail, **⋯ → Invite people**, **`CollaborationAccessFlow`** or **`InviteCollaboratorsDrawer`**, **`lib/collaborator-access.ts`**, roster **name → email → role tags** — **`.cursor/rules/exxat-collaboration-access.mdc`**.
|
|
575
577
|
- [ ] **Dedicated search (§4.8):** Landing uses **`DedicatedSearchLandingTemplate`**; results use **`DedicatedSearchResultsHeaderChrome`** + outer **`DEDICATED_SEARCH_RESULTS_OUTER_CONTENT_CLASSNAME`**; **`DedicatedSearchUrlComposer`** + **`DedicatedSearchRecents`** with **`createDedicatedSearchRecentsController`** — **`.cursor/rules/exxat-dedicated-search-surfaces.mdc`**.
|
|
576
578
|
- [ ] **KPI trends:** **`MetricItem.trend`** matches the delta direction; **`trendPolarity`** set for “more is worse” metrics (flags, defects, overdue) — **`docs/kpi-trend-pattern.md`**, **`.cursor/rules/exxat-kpi-trends.mdc`**.
|
|
@@ -578,4 +580,4 @@ Copy and complete when implementing or reviewing:
|
|
|
578
580
|
|
|
579
581
|
---
|
|
580
582
|
|
|
581
|
-
*Last updated: drawer vs dialog / card vs rows / KPI max-four pattern docs + rules + skills; §6.4 table; §4.8 dedicated search templates; §4.7 collaboration & access; §4.1 centralized dataset + presentation; §4.5–§4.6 view shells + secondary panel; Font Awesome rule; §9.1 application sidebar shell; §4.4 board cards; §6.5 no toast; §7.1 command palette; §13 checklist.*
|
|
583
|
+
*Last updated: question bank folder-scoped header Customize + rule/skill; drawer vs dialog / card vs rows / KPI max-four pattern docs + rules + skills; §6.4 table; §4.8 dedicated search templates; §4.7 collaboration & access; §4.1 centralized dataset + presentation; §4.5–§4.6 view shells + secondary panel; Font Awesome rule; §9.1 application sidebar shell; §4.4 board cards; §6.5 no toast; §7.1 command palette; §13 checklist.*
|
|
@@ -4,11 +4,15 @@ import * as React from "react"
|
|
|
4
4
|
import { usePathname } from "next/navigation"
|
|
5
5
|
|
|
6
6
|
import { useSecondaryPanel } from "@/components/secondary-panel"
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
QUESTION_BANK_ENTRY_PATH,
|
|
9
|
+
QUESTION_BANK_HUB_FIND_PATH,
|
|
10
|
+
QUESTION_BANK_LIST_PATH,
|
|
11
|
+
} from "@/lib/question-bank-nav"
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
|
-
* Keeps the nested secondary panel open across library
|
|
11
|
-
*
|
|
14
|
+
* Keeps the nested secondary panel open across library navigations.
|
|
15
|
+
* **Question hub** (`/question-bank`) and **Search** (`/find`, `/list`) stay full-width — no secondary rail.
|
|
12
16
|
*/
|
|
13
17
|
export default function QuestionBankLayout({ children }: { children: React.ReactNode }) {
|
|
14
18
|
const pathname = usePathname()
|
|
@@ -30,7 +34,10 @@ export default function QuestionBankLayout({ children }: { children: React.React
|
|
|
30
34
|
const isDiscoveryHubRoot =
|
|
31
35
|
pathname === QUESTION_BANK_ENTRY_PATH || pathname === `${QUESTION_BANK_ENTRY_PATH}/`
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
const isDedicatedSearchSurface =
|
|
38
|
+
pathname === QUESTION_BANK_HUB_FIND_PATH || pathname === QUESTION_BANK_LIST_PATH
|
|
39
|
+
|
|
40
|
+
if (isDiscoveryHubRoot || isDedicatedSearchSurface) {
|
|
34
41
|
closePanelRef.current({ mainSidebar: "leave" })
|
|
35
42
|
return undefined
|
|
36
43
|
}
|
package/template/app/globals.css
CHANGED
|
@@ -151,6 +151,14 @@ html[data-text-size="large"] {
|
|
|
151
151
|
via a scoped class, not globally on :root. */
|
|
152
152
|
/* Minimum readable UI copy: 11px (`text-xs` / --text-xs). Do not use smaller arbitrary sizes. */
|
|
153
153
|
|
|
154
|
+
/* ── Layout ─────────────────────────────────────────────────── */
|
|
155
|
+
/* SiteHeader / breadcrumb height. Mirrored on the SidebarProvider for
|
|
156
|
+
descendants; declared here too so JS that reads from documentElement
|
|
157
|
+
(e.g. DataTable sticky-head offset) can resolve it. Plain px so
|
|
158
|
+
`parseFloat` can read it as a number. Keep in sync with `headerHeight`
|
|
159
|
+
in components/sidebar-shell.tsx (currently `calc(var(--spacing) * 12)` = 48px). */
|
|
160
|
+
--header-height: 48px;
|
|
161
|
+
|
|
154
162
|
/* ── Typography ─────────────────────────────────────────────── */
|
|
155
163
|
/* Ivy Presto loaded via Adobe Fonts Kit wuk5wqn (use.typekit.net) */
|
|
156
164
|
--font-heading: "ivypresto-text";
|
|
@@ -171,6 +179,19 @@ html[data-text-size="large"] {
|
|
|
171
179
|
--brand-preview-one: var(--brand-tint);
|
|
172
180
|
--brand-preview-prism: oklch(0.57 0.24 342);
|
|
173
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Ask Leo panel tints (same mixes as the vertical wash). Use for blobs, cards, etc.
|
|
184
|
+
* `--leo-surface-gradient` is the full linear wash on `AskLeoSidebar`.
|
|
185
|
+
*/
|
|
186
|
+
--leo-surface-tint-a: color-mix(in oklch, var(--brand-color) 4%, var(--background));
|
|
187
|
+
--leo-surface-tint-b: color-mix(in oklch, var(--brand-color) 8%, var(--background));
|
|
188
|
+
--leo-surface-gradient: linear-gradient(180deg, var(--leo-surface-tint-a) 0%, var(--leo-surface-tint-b) 100%);
|
|
189
|
+
|
|
190
|
+
/* KeyMetrics `variant="flat"` — soft KPI band (lavender wash → canvas; dark: subtle brand lift) */
|
|
191
|
+
--key-metrics-flat-grad-top: color-mix(in oklch, var(--brand-tint-light) 90%, var(--background));
|
|
192
|
+
--key-metrics-flat-grad-mid: color-mix(in oklch, var(--brand-tint) 34%, var(--background));
|
|
193
|
+
--key-metrics-flat-cell-bg: color-mix(in oklch, var(--background) 80%, var(--brand-tint-light));
|
|
194
|
+
|
|
174
195
|
/* ── Surfaces ────────────────────────────────────────────────── */
|
|
175
196
|
--background: oklch(1 0 0);
|
|
176
197
|
--foreground: oklch(0.145 0 0); /* ≈ #1A1A1A — 17:1 on white ✓ */
|
|
@@ -249,7 +270,8 @@ html[data-text-size="large"] {
|
|
|
249
270
|
--sidebar-ring: oklch(0.25 0 0);
|
|
250
271
|
/* Nav section titles — ≥4.5:1 vs --sidebar (not vs page white) */
|
|
251
272
|
--sidebar-section-label-foreground: color-mix(in oklch, var(--sidebar-foreground) 58%, var(--sidebar));
|
|
252
|
-
|
|
273
|
+
/* Nested secondary rail — soft brand wash on canvas (not pure `--background`, not full `--sidebar`). */
|
|
274
|
+
--secondary-panel-bg: color-mix(in oklch, var(--brand-tint) 34%, var(--background));
|
|
253
275
|
|
|
254
276
|
/* Browser UI (meta theme-color) — aligned with --brand-tint */
|
|
255
277
|
--theme-color-chrome: #f3f2f8;
|
|
@@ -357,6 +379,10 @@ html[data-text-size="large"] {
|
|
|
357
379
|
--destructive: oklch(0.65 0.20 25); /* brighter for dark bg */
|
|
358
380
|
--destructive-foreground: oklch(0.10 0 0);
|
|
359
381
|
|
|
382
|
+
--key-metrics-flat-grad-top: color-mix(in oklch, var(--brand-color) 14%, var(--background));
|
|
383
|
+
--key-metrics-flat-grad-mid: color-mix(in oklch, var(--muted) 42%, var(--background));
|
|
384
|
+
--key-metrics-flat-cell-bg: color-mix(in oklch, var(--background) 88%, var(--brand-color));
|
|
385
|
+
|
|
360
386
|
/* Borders — visible but not washed out on dark surfaces */
|
|
361
387
|
--border: oklch(0.38 0.008 270);
|
|
362
388
|
--border-control: oklch(0.72 0.012 270);
|
|
@@ -405,7 +431,8 @@ html[data-text-size="large"] {
|
|
|
405
431
|
--sidebar-border: oklch(0.38 0.010 270);
|
|
406
432
|
--sidebar-ring: oklch(0.85 0 0);
|
|
407
433
|
--sidebar-section-label-foreground: color-mix(in oklch, var(--sidebar-foreground) 48%, var(--sidebar));
|
|
408
|
-
|
|
434
|
+
/* Nested secondary rail — dark: subtle brand lift on canvas (not flat `--background` only). */
|
|
435
|
+
--secondary-panel-bg: color-mix(in oklch, var(--background) 82%, var(--brand-color) 18%);
|
|
409
436
|
--theme-color-chrome: #2f2d36;
|
|
410
437
|
|
|
411
438
|
/* Lifted scrim on dark — white-tinted veil, not heavy black */
|
|
@@ -1808,3 +1835,8 @@ html:is([data-contrast="high"], [data-contrast="windows"]) [data-slot="coach-mar
|
|
|
1808
1835
|
}
|
|
1809
1836
|
}
|
|
1810
1837
|
|
|
1838
|
+
/* After all @layer rules: win over Tailwind preflight / layer merge so real controls show a pointer. */
|
|
1839
|
+
button:not(:disabled):not([disabled]),
|
|
1840
|
+
[role="button"]:not([aria-disabled="true"]):not([data-disabled]) {
|
|
1841
|
+
cursor: pointer;
|
|
1842
|
+
}
|