@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -1
  3. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +3 -0
  4. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +3 -1
  5. package/consumer-extras/patterns/collaboration-access-pattern.md +2 -0
  6. package/package.json +1 -1
  7. package/src/components/ui/dropdown-menu.tsx +2 -0
  8. package/src/components/ui/popover.tsx +2 -2
  9. package/src/components/ui/select.tsx +1 -1
  10. package/src/components/ui/tooltip.tsx +7 -1
  11. package/src/globals.css +27 -2
  12. package/src/theme.css +4 -2
  13. package/template/AGENTS.md +6 -4
  14. package/template/app/(app)/question-bank/layout.tsx +11 -4
  15. package/template/app/globals.css +34 -2
  16. package/template/components/app-sidebar.tsx +89 -41
  17. package/template/components/ask-leo-sidebar.tsx +1 -2
  18. package/template/components/compliance-board-view.tsx +11 -3
  19. package/template/components/compliance-list-view.tsx +16 -3
  20. package/template/components/compliance-table.tsx +5 -1
  21. package/template/components/data-table/index.tsx +25 -11
  22. package/template/components/data-views/finder-panel-view.tsx +2 -2
  23. package/template/components/data-views/index.ts +19 -0
  24. package/template/components/data-views/list-page-split-details-placeholder.tsx +3 -3
  25. package/template/components/data-views/list-page-split-hub-tokens.ts +1 -1
  26. package/template/components/data-views/list-page-tree-column-header.tsx +1 -1
  27. package/template/components/data-views/outline-tree-menu.tsx +157 -0
  28. package/template/components/data-views/question-bank-folder-tree-branch.tsx +210 -0
  29. package/template/components/exxat-product-logo.tsx +11 -72
  30. package/template/components/folder-details-shell.tsx +1 -1
  31. package/template/components/hub-tree-panel-view.tsx +88 -80
  32. package/template/components/key-metrics.tsx +50 -13
  33. package/template/components/page-header.tsx +19 -10
  34. package/template/components/product-switcher.tsx +1 -4
  35. package/template/components/question-bank-board-view.tsx +11 -2
  36. package/template/components/question-bank-client.tsx +111 -69
  37. package/template/components/question-bank-list-view.tsx +12 -1
  38. package/template/components/question-bank-page-header.tsx +18 -2
  39. package/template/components/question-bank-secondary-nav.tsx +12 -225
  40. package/template/components/question-bank-table.tsx +6 -1
  41. package/template/components/secondary-panel.tsx +1 -1
  42. package/template/components/site-header.tsx +21 -2
  43. package/template/components/team-board-view.tsx +11 -3
  44. package/template/components/team-list-view.tsx +16 -3
  45. package/template/components/team-table.tsx +6 -2
  46. package/template/components/templates/dedicated-search-landing-template.tsx +86 -20
  47. package/template/components/templates/list-page.tsx +1 -3
  48. package/template/components/templates/nested-secondary-panel-shell.tsx +3 -4
  49. package/template/docs/collaboration-access-pattern.md +2 -0
  50. package/template/docs/question-bank-hub-header-pattern.md +25 -0
  51. package/template/hooks/use-secondary-panel-hub-nav.ts +17 -1
  52. package/template/lib/mock/navigation.tsx +30 -1
  53. package/template/lib/question-bank-nav.ts +26 -0
  54. package/template/package.json +3 -3
  55. package/template/components/command-menu-01.tsx +0 -133
  56. 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. **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`**).
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.14",
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 data-slot="tooltip-trigger" suppressHydrationWarning {...props} />
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
- --secondary-panel-bg: oklch(0.99 0.008 286.1);
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
- --secondary-panel-bg: oklch(0.23 0.02 270);
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
- --secondary-panel-bg: oklch(0.99 0.008 286.1);
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
- --secondary-panel-bg: oklch(0.23 0.02 270);
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 */
@@ -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
- **Cursor rule:** **`.cursor/rules/exxat-primary-nav-secondary-panel.mdc`**. **Icons in panel:** **`.cursor/rules/exxat-fontawesome-icons.mdc`**.
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 { QUESTION_BANK_ENTRY_PATH } from "@/lib/question-bank-nav"
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 / list / find navigations.
11
- * The discovery hub (`/question-bank`) is full-width — no secondary bar there.
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
- if (isDiscoveryHubRoot) {
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
  }
@@ -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
- --secondary-panel-bg: oklch(0.99 0.008 286.1);
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
- --secondary-panel-bg: oklch(0.23 0.02 270);
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
+ }