@exxatdesignux/ui 0.3.0 → 0.4.1

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 (214) hide show
  1. package/CHANGELOG.md +701 -6
  2. package/README.md +138 -0
  3. package/bin/init.mjs +134 -31
  4. package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
  5. package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
  6. package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
  7. package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
  8. package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
  9. package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
  10. package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
  11. package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
  12. package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
  13. package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
  14. package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
  15. package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
  16. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
  17. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
  18. package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
  19. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
  20. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
  21. package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
  22. package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
  23. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
  24. package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
  25. package/consumer-extras/handbook/HANDBOOK.md +2 -0
  26. package/consumer-extras/handbook/glossary.md +2 -1
  27. package/consumer-extras/handbook/reference-implementations.md +31 -4
  28. package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
  29. package/consumer-extras/patterns/data-views-pattern.md +18 -16
  30. package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
  31. package/dist/components/data-table/index.js +2 -2
  32. package/dist/components/data-table/index.js.map +1 -1
  33. package/dist/components/data-table/pagination.js +3 -3
  34. package/dist/components/data-table/pagination.js.map +1 -1
  35. package/dist/components/data-table/use-table-state.d.ts +1 -1
  36. package/dist/components/data-table/use-table-state.js.map +1 -1
  37. package/dist/components/data-views/data-row-list.js.map +1 -1
  38. package/dist/components/data-views/finder-panel-view.d.ts +1 -1
  39. package/dist/components/data-views/finder-panel-view.js.map +1 -1
  40. package/dist/components/data-views/hub-table.d.ts +9 -3
  41. package/dist/components/data-views/hub-table.js +262 -40
  42. package/dist/components/data-views/hub-table.js.map +1 -1
  43. package/dist/components/data-views/index.js +262 -40
  44. package/dist/components/data-views/index.js.map +1 -1
  45. package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
  46. package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
  47. package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
  48. package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
  49. package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
  50. package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
  51. package/dist/components/data-views/os-folder-glyph.js.map +1 -1
  52. package/dist/components/ui/avatar.d.ts +1 -1
  53. package/dist/components/ui/key-metrics.js.map +1 -1
  54. package/dist/index.js +136 -39
  55. package/dist/index.js.map +1 -1
  56. package/package.json +3 -2
  57. package/src/components/data-table/index.tsx +2 -2
  58. package/src/components/data-table/pagination.tsx +5 -1
  59. package/src/components/data-table/use-table-state.ts +1 -1
  60. package/src/components/data-views/data-row-list.tsx +1 -1
  61. package/src/components/data-views/finder-panel-view.tsx +2 -2
  62. package/src/components/data-views/hub-table.tsx +149 -41
  63. package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
  64. package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
  65. package/src/components/data-views/os-folder-glyph.tsx +1 -1
  66. package/src/components/ui/key-metrics.tsx +1 -1
  67. package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
  68. package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
  69. package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
  70. package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
  71. package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
  72. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
  73. package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
  74. package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
  75. package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
  76. package/template/AGENTS.md +43 -37
  77. package/template/app/(app)/columns/page.tsx +11 -0
  78. package/template/app/(app)/library/all/page.tsx +11 -0
  79. package/template/app/(app)/library/find/page.tsx +12 -0
  80. package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
  81. package/template/app/(app)/library/list/page.tsx +12 -0
  82. package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
  83. package/template/app/(app)/library/page.tsx +11 -0
  84. package/template/app/(app)/tokens-themes/page.tsx +11 -0
  85. package/template/components/ask-leo-composer.tsx +2 -2
  86. package/template/components/columns-client.tsx +158 -0
  87. package/template/components/columns-showcase.tsx +541 -0
  88. package/template/components/data-views/index.ts +32 -6
  89. package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
  90. package/template/components/data-views/table-cells.tsx +673 -0
  91. package/template/components/folder-details-shell.tsx +11 -11
  92. package/template/components/hub-tree-panel-view.tsx +24 -24
  93. package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
  94. package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
  95. package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
  96. package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
  97. package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
  98. package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
  99. package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
  100. package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
  101. package/template/components/library-panel-activator.tsx +8 -0
  102. package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
  103. package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
  104. package/template/components/list-hub-status-badge.tsx +2 -2
  105. package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
  106. package/template/components/sidebar/app-sidebar.tsx +61 -5
  107. package/template/components/sidebar/secondary-panel.tsx +109 -56
  108. package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
  109. package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
  110. package/template/components/table-properties/types.ts +1 -1
  111. package/template/components/templates/discovery-hub-template.tsx +1 -1
  112. package/template/components/templates/new-focus-template.tsx +2 -2
  113. package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
  114. package/template/components/tokens-secondary-nav.tsx +192 -0
  115. package/template/components/tokens-themes-client.tsx +476 -0
  116. package/template/components/tokens-themes-section.tsx +386 -0
  117. package/template/docs/HANDBOOK.md +187 -0
  118. package/template/docs/blueprints/README.md +1 -1
  119. package/template/docs/blueprints/board-card.md +1 -1
  120. package/template/docs/blueprints/data-table.md +2 -2
  121. package/template/docs/blueprints/list-page-template.md +3 -3
  122. package/template/docs/blueprints/page-header.md +4 -4
  123. package/template/docs/collaboration-access-pattern.md +7 -7
  124. package/template/docs/component-selection-guide.md +1 -1
  125. package/template/docs/data-views-pattern.md +18 -16
  126. package/template/docs/glossary.md +58 -0
  127. package/template/docs/kpi-flat-band-pattern.md +3 -3
  128. package/template/docs/kpi-trend-pattern.md +18 -3
  129. package/template/docs/large-dataset-strategy.md +155 -0
  130. package/template/docs/library-hub-header-pattern.md +25 -0
  131. package/template/docs/migrations/_template.md +1 -1
  132. package/template/docs/reference-implementations.md +151 -0
  133. package/template/docs/token-taxonomy.md +1 -1
  134. package/template/docs/voice-and-tone.md +262 -0
  135. package/template/eslint.config.mjs +9 -39
  136. package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
  137. package/template/lib/ask-leo-route-context.ts +6 -18
  138. package/template/lib/coach-mark-registry.ts +0 -16
  139. package/template/lib/command-menu-config.ts +5 -12
  140. package/template/lib/command-menu-search-data.ts +8 -39
  141. package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
  142. package/template/lib/library-dedicated-search.ts +19 -0
  143. package/template/lib/library-hub-search.ts +90 -0
  144. package/template/lib/library-nav.ts +477 -0
  145. package/template/lib/library-recent-searches.ts +22 -0
  146. package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
  147. package/template/lib/list-status-badges.ts +16 -104
  148. package/template/lib/mock/dashboard.ts +1 -1
  149. package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
  150. package/template/lib/mock/library-header-collaborators.ts +54 -0
  151. package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
  152. package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
  153. package/template/lib/mock/library.ts +249 -0
  154. package/template/lib/mock/navigation.tsx +32 -26
  155. package/template/lib/table-state-lifecycle.ts +1 -1
  156. package/template/next.config.mjs +7 -4
  157. package/template/package.json +0 -1
  158. package/tokens/hooks-index.json +2874 -0
  159. package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
  160. package/template/app/(app)/examples/page.tsx +0 -41
  161. package/template/app/(app)/question-bank/find/page.tsx +0 -12
  162. package/template/app/(app)/question-bank/library/page.tsx +0 -11
  163. package/template/app/(app)/question-bank/list/page.tsx +0 -12
  164. package/template/app/(app)/question-bank/page.tsx +0 -11
  165. package/template/components/compliance-board-view.tsx +0 -142
  166. package/template/components/compliance-client.tsx +0 -92
  167. package/template/components/compliance-page-header.tsx +0 -89
  168. package/template/components/compliance-table.tsx +0 -468
  169. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  170. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  171. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  172. package/template/components/new-placement-back-btn.tsx +0 -28
  173. package/template/components/new-placement-form.tsx +0 -942
  174. package/template/components/placement-board-card.tsx +0 -250
  175. package/template/components/placement-detail.tsx +0 -438
  176. package/template/components/placements-board-view.tsx +0 -397
  177. package/template/components/placements-client.tsx +0 -220
  178. package/template/components/placements-list-view.tsx +0 -124
  179. package/template/components/placements-page-header.tsx +0 -166
  180. package/template/components/placements-table-cells.test.tsx +0 -22
  181. package/template/components/placements-table-cells.tsx +0 -173
  182. package/template/components/placements-table-columns.tsx +0 -210
  183. package/template/components/placements-table.tsx +0 -934
  184. package/template/components/question-bank-panel-activator.tsx +0 -8
  185. package/template/components/rotations-empty-state.tsx +0 -50
  186. package/template/components/rotations-panel-activator.tsx +0 -8
  187. package/template/components/sites-board-view.tsx +0 -67
  188. package/template/components/sites-client.tsx +0 -154
  189. package/template/components/sites-table.tsx +0 -249
  190. package/template/components/team-board-view.tsx +0 -122
  191. package/template/components/team-client.tsx +0 -100
  192. package/template/components/team-page-header.tsx +0 -92
  193. package/template/components/team-table.tsx +0 -553
  194. package/template/docs/question-bank-hub-header-pattern.md +0 -25
  195. package/template/lib/compliance-supported-views.ts +0 -10
  196. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  197. package/template/lib/mock/compliance-kpi.ts +0 -61
  198. package/template/lib/mock/compliance.ts +0 -146
  199. package/template/lib/mock/placements-kpi.ts +0 -134
  200. package/template/lib/mock/placements.ts +0 -176
  201. package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
  202. package/template/lib/mock/question-bank.ts +0 -249
  203. package/template/lib/mock/sites-directory.ts +0 -16
  204. package/template/lib/mock/sites-kpi.ts +0 -25
  205. package/template/lib/mock/team-kpi.ts +0 -60
  206. package/template/lib/mock/team.ts +0 -118
  207. package/template/lib/placement-board-card-layout.ts +0 -79
  208. package/template/lib/question-bank-dedicated-search.ts +0 -19
  209. package/template/lib/question-bank-hub-search.ts +0 -90
  210. package/template/lib/question-bank-nav.ts +0 -477
  211. package/template/lib/question-bank-recent-searches.ts +0 -22
  212. package/template/lib/question-bank-supported-views.ts +0 -12
  213. package/template/lib/sites-supported-views.ts +0 -10
  214. package/template/lib/team-supported-views.ts +0 -10
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Question bank folder tree (mock) — OS-style icon folders with appearance + hierarchy.
2
+ * Library folder tree (mock) — OS-style icon folders with appearance + hierarchy.
3
3
  * Production: replace with API + optimistic updates.
4
4
  */
5
5
 
6
- export type QuestionBankFolderColorKey =
6
+ export type LibraryFolderColorKey =
7
7
  | "brand"
8
8
  | "success"
9
9
  | "warning"
@@ -13,19 +13,19 @@ export type QuestionBankFolderColorKey =
13
13
  | "chart2"
14
14
  | "chart3"
15
15
 
16
- export interface QuestionBankFolder {
16
+ export interface LibraryFolder {
17
17
  id: string
18
18
  name: string
19
19
  /** `null` = top-level folder */
20
20
  parentId: string | null
21
21
  /** Font Awesome icon without weight prefix (e.g. `fa-folder`, `fa-flask`). */
22
22
  icon: string
23
- colorKey: QuestionBankFolderColorKey
23
+ colorKey: LibraryFolderColorKey
24
24
  }
25
25
 
26
26
  /** Tile + icon tint classes (semantic tokens). */
27
- export const QUESTION_BANK_FOLDER_COLOR_STYLES: Record<
28
- QuestionBankFolderColorKey,
27
+ export const LIBRARY_FOLDER_COLOR_STYLES: Record<
28
+ LibraryFolderColorKey,
29
29
  { tile: string; iconWrap: string; icon: string }
30
30
  > = {
31
31
  brand: {
@@ -71,7 +71,7 @@ export const QUESTION_BANK_FOLDER_COLOR_STYLES: Record<
71
71
  }
72
72
 
73
73
  /** Icon color classes using Tailwind — for use in text-based contexts (list views, panels). */
74
- export const QUESTION_BANK_FOLDER_ICON_COLORS: Record<QuestionBankFolderColorKey, string> = {
74
+ export const LIBRARY_FOLDER_ICON_COLORS: Record<LibraryFolderColorKey, string> = {
75
75
  brand: "text-orange-600 dark:text-orange-400",
76
76
  success: "text-emerald-600 dark:text-emerald-400",
77
77
  warning: "text-amber-600 dark:text-amber-400",
@@ -83,25 +83,25 @@ export const QUESTION_BANK_FOLDER_ICON_COLORS: Record<QuestionBankFolderColorKey
83
83
  }
84
84
 
85
85
  /** Preset icons for folder appearance picker. */
86
- export const QUESTION_BANK_FOLDER_ICON_OPTIONS: readonly string[] = [
86
+ export const LIBRARY_FOLDER_ICON_OPTIONS: readonly string[] = [
87
87
  "fa-folder",
88
88
  "fa-folder-open",
89
89
  "fa-book",
90
- "fa-flask",
91
- "fa-stethoscope",
92
- "fa-heart-pulse",
93
- "fa-brain",
94
- "fa-scale-balanced",
90
+ "fa-bookmark",
91
+ "fa-box",
92
+ "fa-box-archive",
93
+ "fa-tag",
94
+ "fa-flag",
95
+ "fa-star",
95
96
  "fa-file-lines",
96
97
  "fa-layer-group",
97
98
  "fa-clipboard-check",
98
- "fa-vial",
99
- "fa-user-doctor",
100
- "fa-kit-medical",
101
- "fa-notes-medical",
99
+ "fa-list-check",
100
+ "fa-grid-2",
101
+ "fa-folder-tree",
102
102
  ] as const
103
103
 
104
- export const DEFAULT_QUESTION_BANK_FOLDERS: QuestionBankFolder[] = [
104
+ export const DEFAULT_LIBRARY_FOLDERS: LibraryFolder[] = [
105
105
  {
106
106
  id: "fld-favorites",
107
107
  name: "Favorites",
@@ -111,37 +111,37 @@ export const DEFAULT_QUESTION_BANK_FOLDERS: QuestionBankFolder[] = [
111
111
  },
112
112
  {
113
113
  id: "fld-clinical",
114
- name: "Clinical",
114
+ name: "Folder 1",
115
115
  parentId: null,
116
- icon: "fa-stethoscope",
116
+ icon: "fa-folder",
117
117
  colorKey: "brand",
118
118
  },
119
119
  {
120
120
  id: "fld-science",
121
- name: "Basic science",
121
+ name: "Folder 2",
122
122
  parentId: null,
123
- icon: "fa-flask",
123
+ icon: "fa-folder",
124
124
  colorKey: "chart2",
125
125
  },
126
126
  {
127
127
  id: "fld-ops",
128
- name: "Operations",
128
+ name: "Folder 3",
129
129
  parentId: null,
130
- icon: "fa-clipboard-check",
130
+ icon: "fa-folder",
131
131
  colorKey: "warning",
132
132
  },
133
133
  {
134
134
  id: "fld-ethics",
135
- name: "Ethics & law",
135
+ name: "Folder 4",
136
136
  parentId: null,
137
- icon: "fa-scale-balanced",
137
+ icon: "fa-folder",
138
138
  colorKey: "muted",
139
139
  },
140
140
  {
141
141
  id: "fld-skills-lab",
142
- name: "Skills lab",
142
+ name: "Folder 1.A",
143
143
  parentId: "fld-clinical",
144
- icon: "fa-vial",
144
+ icon: "fa-folder",
145
145
  colorKey: "success",
146
146
  },
147
147
  ]
@@ -150,7 +150,7 @@ export function newFolderId(): string {
150
150
  return `fld-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`
151
151
  }
152
152
 
153
- export function collectFolderDescendantIds(folders: QuestionBankFolder[], rootId: string): Set<string> {
153
+ export function collectFolderDescendantIds(folders: LibraryFolder[], rootId: string): Set<string> {
154
154
  const out = new Set<string>()
155
155
  function walk(id: string) {
156
156
  out.add(id)
@@ -163,7 +163,7 @@ export function collectFolderDescendantIds(folders: QuestionBankFolder[], rootId
163
163
  }
164
164
 
165
165
  export function isValidFolderMove(
166
- folders: QuestionBankFolder[],
166
+ folders: LibraryFolder[],
167
167
  folderId: string,
168
168
  newParentId: string | null,
169
169
  ): boolean {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Demo collaborators for Library collaboration header (stacked faces).
3
+ */
4
+
5
+ import { stockPortraitUrl } from "@/lib/stock-portrait"
6
+ import type { PageHeaderCollaborator } from "@/components/page-header"
7
+
8
+ export const LIBRARY_HEADER_COLLABORATORS: PageHeaderCollaborator[] = [
9
+ {
10
+ id: "1",
11
+ name: "Owner A",
12
+ email: "owner.a@demo.exxat.io",
13
+ imageUrl: stockPortraitUrl("qb-collab-a"),
14
+ initials: "OA",
15
+ access: "owner",
16
+ roles: ["Role 1", "Role 2"],
17
+ },
18
+ {
19
+ id: "2",
20
+ name: "Owner B",
21
+ email: "owner.b@demo.exxat.io",
22
+ imageUrl: stockPortraitUrl("qb-collab-b"),
23
+ initials: "OB",
24
+ access: "editor",
25
+ roles: ["Role 3"],
26
+ },
27
+ {
28
+ id: "3",
29
+ name: "Owner C",
30
+ email: "owner.c@demo.exxat.io",
31
+ imageUrl: stockPortraitUrl("qb-collab-c"),
32
+ initials: "OC",
33
+ access: "editor",
34
+ roles: ["Role 2"],
35
+ },
36
+ {
37
+ id: "4",
38
+ name: "Owner D",
39
+ email: "owner.d@demo.exxat.io",
40
+ imageUrl: stockPortraitUrl("qb-collab-d"),
41
+ initials: "OD",
42
+ access: "commenter",
43
+ roles: ["Role 2"],
44
+ },
45
+ {
46
+ id: "5",
47
+ name: "Owner E",
48
+ email: "owner.e@demo.exxat.io",
49
+ imageUrl: stockPortraitUrl("qb-collab-e"),
50
+ initials: "OE",
51
+ access: "viewer",
52
+ roles: ["Role 3"],
53
+ },
54
+ ]
@@ -1,39 +1,39 @@
1
1
  /**
2
- * Derived labels for the question bank inspector (mock — replace with API fields).
2
+ * Derived labels for the library inspector (mock — replace with API fields).
3
3
  */
4
4
 
5
- import type { QuestionBankDifficulty, QuestionBankItem, QuestionBankType } from "@/lib/mock/question-bank"
6
- import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
7
- import { collectFolderDescendantIds } from "@/lib/mock/question-bank-folders"
5
+ import type { LibraryLevel, LibraryItem, LibraryItemType } from "@/lib/mock/library"
6
+ import type { LibraryFolder } from "@/lib/mock/library-folders"
7
+ import { collectFolderDescendantIds } from "@/lib/mock/library-folders"
8
8
 
9
- export const QUESTION_TYPE_ABBREV: Record<QuestionBankType, string> = {
10
- multiple_choice: "MCQ",
11
- true_false: "T/F",
12
- short_answer: "Short answer",
9
+ export const QUESTION_TYPE_ABBREV: Record<LibraryItemType, string> = {
10
+ multiple_choice: "Choice",
11
+ true_false: "Toggle",
12
+ short_answer: "Short text",
13
13
  }
14
14
 
15
- export function deriveQuestionItemCode(q: QuestionBankItem): string {
15
+ export function deriveQuestionItemCode(q: LibraryItem): string {
16
16
  const raw = q.itemCode?.trim()
17
17
  if (raw) return raw
18
18
  return q.questionId
19
19
  }
20
20
 
21
- export function deriveBloomLevel(q: QuestionBankItem): string {
21
+ export function deriveBloomLevel(q: LibraryItem): string {
22
22
  if (q.bloomLevel && String(q.bloomLevel).trim()) return String(q.bloomLevel)
23
23
  switch (q.difficulty) {
24
24
  case "easy":
25
- return "Remember"
25
+ return "Tier 1"
26
26
  case "medium":
27
- return "Apply"
27
+ return "Tier 2"
28
28
  case "hard":
29
- return "Analyze"
29
+ return "Tier 3"
30
30
  default:
31
- return "Apply"
31
+ return "Tier 2"
32
32
  }
33
33
  }
34
34
 
35
35
  /** Relative “last edited” clause when `lastEditedSummary` is absent. */
36
- export function deriveLastEditedLine(q: QuestionBankItem): string {
36
+ export function deriveLastEditedLine(q: LibraryItem): string {
37
37
  if (q.lastEditedSummary?.trim()) return q.lastEditedSummary.trim()
38
38
  const editor = q.lastEditedBy ?? q.author
39
39
  const d = new Date(q.updatedAt)
@@ -48,41 +48,41 @@ export function deriveLastEditedLine(q: QuestionBankItem): string {
48
48
  return `${years} year${years === 1 ? "" : "s"} ago · ${editor}`
49
49
  }
50
50
 
51
- export function deriveTags(q: QuestionBankItem): string[] {
51
+ export function deriveTags(q: LibraryItem): string[] {
52
52
  if (q.tags && q.tags.length > 0) return q.tags
53
53
  return [q.topic].filter(Boolean)
54
54
  }
55
55
 
56
- /** Bloom taxonomy row order for folder aggregate charts. */
56
+ /** Tier row order for folder aggregate charts. */
57
57
  export const BLOOM_LEVEL_ORDER = [
58
- "Remember",
59
- "Understand",
60
- "Apply",
61
- "Analyze",
62
- "Evaluate",
63
- "Create",
58
+ "Tier 1",
59
+ "Tier 2",
60
+ "Tier 3",
61
+ "Tier 4",
62
+ "Tier 5",
63
+ "Tier 6",
64
64
  ] as const
65
65
 
66
66
  export function questionsInFolderSubtree(
67
- folders: QuestionBankFolder[],
68
- questions: QuestionBankItem[],
67
+ folders: LibraryFolder[],
68
+ questions: LibraryItem[],
69
69
  folderId: string,
70
- ): QuestionBankItem[] {
70
+ ): LibraryItem[] {
71
71
  const scope = collectFolderDescendantIds(folders, folderId)
72
72
  return questions.filter(q => scope.has(q.folderId))
73
73
  }
74
74
 
75
75
  export interface FolderQuestionAggregate {
76
76
  totalQuestions: number
77
- difficulty: Record<QuestionBankDifficulty, number>
77
+ difficulty: Record<LibraryLevel, number>
78
78
  /** Counts keyed by Bloom label (includes derived levels). */
79
79
  bloom: Record<string, number>
80
80
  avgPbi: number | null
81
81
  scoredCount: number
82
82
  }
83
83
 
84
- export function aggregateFolderQuestions(rows: QuestionBankItem[]): FolderQuestionAggregate {
85
- const difficulty: Record<QuestionBankDifficulty, number> = {
84
+ export function aggregateFolderQuestions(rows: LibraryItem[]): FolderQuestionAggregate {
85
+ const difficulty: Record<LibraryLevel, number> = {
86
86
  easy: 0,
87
87
  medium: 0,
88
88
  hard: 0,
@@ -1,16 +1,16 @@
1
1
  import type { MetricInsight, MetricItem } from "@/components/key-metrics"
2
- import type { QuestionBankItem, QuestionBankType } from "@/lib/mock/question-bank"
2
+ import type { LibraryItem, LibraryItemType } from "@/lib/mock/library"
3
3
 
4
- /** Point-biserial below this mock threshold counts as a psychometric review flag. */
4
+ /** Quality score below this mock threshold counts as a review flag. */
5
5
  const PBI_REVIEW_THRESHOLD = 0.2
6
6
 
7
- const TYPE_LABEL: Record<QuestionBankType, string> = {
8
- multiple_choice: "Multiple choice",
9
- true_false: "True / false",
10
- short_answer: "Short answer",
7
+ const TYPE_LABEL: Record<LibraryItemType, string> = {
8
+ multiple_choice: "Type 1",
9
+ true_false: "Type 2",
10
+ short_answer: "Type 3",
11
11
  }
12
12
 
13
- export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
13
+ export function libraryKpiMetrics(rows: LibraryItem[]): MetricItem[] {
14
14
  const mcq = rows.filter(r => r.type === "multiple_choice").length
15
15
  const tf = rows.filter(r => r.type === "true_false").length
16
16
  const sa = rows.filter(r => r.type === "short_answer").length
@@ -20,9 +20,9 @@ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
20
20
  return [
21
21
  {
22
22
  id: "total",
23
- label: "Total questions",
23
+ label: "Total items",
24
24
  value: rows.length,
25
- delta: "",
25
+ delta: "",
26
26
  trend: "neutral",
27
27
  href: "#",
28
28
  metricVariant: "hero",
@@ -31,23 +31,23 @@ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
31
31
  id: "mcq",
32
32
  label: TYPE_LABEL.multiple_choice,
33
33
  value: mcq,
34
- delta: "",
34
+ delta: "",
35
35
  trend: "neutral",
36
36
  href: "#",
37
37
  },
38
38
  {
39
39
  id: "written",
40
- label: "True / false & short answer",
40
+ label: "Type 2 & 3",
41
41
  value: writtenTypes,
42
- delta: "",
42
+ delta: "",
43
43
  trend: "neutral",
44
44
  href: "#",
45
45
  },
46
46
  {
47
47
  id: "pbi-flags",
48
- label: "Low PBI (review)",
48
+ label: "Review flags",
49
49
  value: lowPbiFlags,
50
- delta: lowPbiFlags >= 2 ? "+1" : "",
50
+ delta: lowPbiFlags >= 2 ? "+1" : "",
51
51
  trend: lowPbiFlags >= 2 ? "up" : "neutral",
52
52
  trendPolarity: "lower_is_better",
53
53
  href: "#",
@@ -55,18 +55,18 @@ export function questionBankKpiMetrics(rows: QuestionBankItem[]): MetricItem[] {
55
55
  ]
56
56
  }
57
57
 
58
- export function questionBankKpiInsight(rows: QuestionBankItem[]): MetricInsight {
58
+ export function libraryKpiInsight(rows: LibraryItem[]): MetricInsight {
59
59
  const hard = rows.filter(r => r.difficulty === "hard").length
60
60
  const topics = new Set(rows.map(r => r.topic)).size
61
61
  return {
62
- title: "Folder library",
62
+ title: "Library snapshot",
63
63
  description:
64
64
  rows.length === 0
65
- ? "Add questions to populate metrics and charts."
65
+ ? "Add items to populate metrics and charts."
66
66
  : hard > 3
67
- ? `${hard} hard items in this view — balance with easier items where learners need quick wins.`
68
- : `${rows.length} question${rows.length === 1 ? "" : "s"} across ${topics} topic${topics === 1 ? "" : "s"} in the filtered set.`,
69
- href: "/question-bank/library",
67
+ ? `${hard} high-level items in this view — balance with lower-level items where helpful.`
68
+ : `${rows.length} item${rows.length === 1 ? "" : "s"} across ${topics} categor${topics === 1 ? "y" : "ies"} in the filtered set.`,
69
+ href: "/library/all",
70
70
  severity: hard > 3 ? "warning" : "info",
71
71
  actionLabel: "Ask Leo",
72
72
  }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Mock library items — replace with API in production.
3
+ */
4
+
5
+ export type LibraryItemType = "multiple_choice" | "true_false" | "short_answer"
6
+ export type LibraryLevel = "easy" | "medium" | "hard"
7
+
8
+ /** Tier label for inspector / analytics (optional on mock rows). */
9
+ export type LibraryTierLevel =
10
+ | "Tier 1"
11
+ | "Tier 2"
12
+ | "Tier 3"
13
+ | "Tier 4"
14
+ | "Tier 5"
15
+ | "Tier 6"
16
+
17
+ export interface LibraryItem extends Record<string, unknown> {
18
+ id: string
19
+ /** Stable human-facing identifier (catalog, search, citations). */
20
+ questionId: string
21
+ /** Short preview / stem */
22
+ stem: string
23
+ topic: string
24
+ type: LibraryItemType
25
+ difficulty: LibraryLevel
26
+ author: string
27
+ /** Work email for the primary author (demo; optional when API omits). */
28
+ authorEmail?: string
29
+ updatedAt: string
30
+ /** Folder tree id (`lib/mock/library-folders.ts`). */
31
+ folderId: string
32
+ /** Multiple choice options (only for type: "multiple_choice") */
33
+ options?: {
34
+ text: string
35
+ isCorrect?: boolean
36
+ }[]
37
+ /** Stable bank code for lists / inspector (e.g. PH-ANA-001). */
38
+ itemCode?: string
39
+ bloomLevel?: LibraryTierLevel | string
40
+ /** Topic-style tags for inspector (# displayed in UI). */
41
+ tags?: string[]
42
+ /** ISO-ish created date (inspector “Creator & history”). */
43
+ createdAt?: string
44
+ /** Display name for “Created by” when different from `author`. */
45
+ createdBy?: string
46
+ /** Last editor display name (inspector copy). */
47
+ lastEditedBy?: string
48
+ /** Precomposed “Last edited” line; overrides derived copy when set. */
49
+ lastEditedSummary?: string
50
+ /** Revision label e.g. v4 */
51
+ version?: string
52
+ examUsageCount?: number
53
+ /** Point-biserial index (psychometrics preview). */
54
+ pbi?: number
55
+ /** Mean percent correct when used in assessments. */
56
+ avgScoreCorrectPct?: number
57
+ /** Where / when the item was last used on an exam. */
58
+ lastUsedLabel?: string
59
+ /** Starred outside the Favorites folder (list landing demo). */
60
+ isStarred?: boolean
61
+ }
62
+
63
+ /** New mock rows — assign a unique `questionId` when creating client-side. */
64
+ export function newLibraryQuestionId(): string {
65
+ return `LIB-NEW-${Date.now().toString(36).toUpperCase()}`
66
+ }
67
+
68
+ export const LIBRARY_ITEMS: LibraryItem[] = [
69
+ {
70
+ id: "q1",
71
+ questionId: "LIB-2026-001",
72
+ stem: "Item 01 — sample primary entry for the demo dataset.",
73
+ topic: "Category 1",
74
+ type: "multiple_choice",
75
+ difficulty: "medium",
76
+ author: "Owner A",
77
+ authorEmail: "owner.a@demo.exxat.io",
78
+ updatedAt: "2026-03-28",
79
+ folderId: "fld-favorites",
80
+ itemCode: "LIB-2026-001",
81
+ bloomLevel: "Tier 2",
82
+ tags: ["Tag 1", "Tag 2", "Tag 3"],
83
+ createdAt: "2024-11-02",
84
+ createdBy: "Owner B",
85
+ lastEditedBy: "Owner C",
86
+ lastEditedSummary: "14 months ago · Owner C",
87
+ version: "v4",
88
+ examUsageCount: 22,
89
+ pbi: 0.48,
90
+ avgScoreCorrectPct: 88,
91
+ lastUsedLabel: "Reference set · 14 months ago",
92
+ options: [
93
+ { text: "Option A", isCorrect: true },
94
+ { text: "Option B", isCorrect: false },
95
+ { text: "Option C", isCorrect: false },
96
+ { text: "Option D", isCorrect: false },
97
+ ],
98
+ },
99
+ {
100
+ id: "q2",
101
+ questionId: "LIB-2026-002",
102
+ stem: "Item 02 — sample entry with a single inline action.",
103
+ topic: "Category 2",
104
+ type: "true_false",
105
+ difficulty: "easy",
106
+ author: "Owner B",
107
+ authorEmail: "owner.b@demo.exxat.io",
108
+ updatedAt: "2026-03-27",
109
+ folderId: "fld-skills-lab",
110
+ isStarred: true,
111
+ pbi: 0.55,
112
+ },
113
+ {
114
+ id: "q3",
115
+ questionId: "LIB-2026-003",
116
+ stem: "Item 03 — short prompt entry for the catalog demo.",
117
+ topic: "Category 3",
118
+ type: "short_answer",
119
+ difficulty: "hard",
120
+ author: "Owner C",
121
+ authorEmail: "owner.c@demo.exxat.io",
122
+ updatedAt: "2026-03-26",
123
+ folderId: "fld-science",
124
+ isStarred: true,
125
+ pbi: 0.14,
126
+ },
127
+ {
128
+ id: "q4",
129
+ questionId: "LIB-2026-004",
130
+ stem: "Item 04 — multi-option entry showing the picker pattern.",
131
+ topic: "Category 4",
132
+ type: "multiple_choice",
133
+ difficulty: "medium",
134
+ author: "Owner D",
135
+ authorEmail: "owner.d@demo.exxat.io",
136
+ updatedAt: "2026-03-25",
137
+ folderId: "fld-ethics",
138
+ isStarred: true,
139
+ pbi: 0.19,
140
+ options: [
141
+ { text: "Option A", isCorrect: true },
142
+ { text: "Option B", isCorrect: false },
143
+ { text: "Option C", isCorrect: false },
144
+ { text: "Option D", isCorrect: false },
145
+ ],
146
+ },
147
+ {
148
+ id: "q5",
149
+ questionId: "LIB-2026-005",
150
+ stem: "Item 05 — short-answer entry pinned to the Favorites folder.",
151
+ topic: "Category 1",
152
+ type: "short_answer",
153
+ difficulty: "easy",
154
+ author: "Owner A",
155
+ authorEmail: "owner.a@demo.exxat.io",
156
+ updatedAt: "2026-03-24",
157
+ folderId: "fld-favorites",
158
+ },
159
+ {
160
+ id: "q6",
161
+ questionId: "LIB-2026-006",
162
+ stem: "Item 06 — choice-list entry for the catalog demo.",
163
+ topic: "Category 3",
164
+ type: "multiple_choice",
165
+ difficulty: "medium",
166
+ author: "Owner E",
167
+ authorEmail: "owner.e@demo.exxat.io",
168
+ updatedAt: "2026-03-23",
169
+ folderId: "fld-ops",
170
+ options: [
171
+ { text: "Option A", isCorrect: true },
172
+ { text: "Option B", isCorrect: false },
173
+ { text: "Option C", isCorrect: false },
174
+ { text: "Option D", isCorrect: false },
175
+ ],
176
+ },
177
+ {
178
+ id: "q7",
179
+ questionId: "LIB-2026-007",
180
+ stem: "Item 07 — boolean entry to show toggle-style answers.",
181
+ topic: "Category 2",
182
+ type: "true_false",
183
+ difficulty: "easy",
184
+ author: "Owner B",
185
+ authorEmail: "owner.b@demo.exxat.io",
186
+ updatedAt: "2026-03-22",
187
+ folderId: "fld-clinical",
188
+ },
189
+ {
190
+ id: "q8",
191
+ questionId: "LIB-2026-008",
192
+ stem: "Item 08 — multi-option entry filed under Folder 2.",
193
+ topic: "Category 3",
194
+ type: "multiple_choice",
195
+ difficulty: "hard",
196
+ author: "Owner C",
197
+ authorEmail: "owner.c@demo.exxat.io",
198
+ updatedAt: "2026-03-21",
199
+ folderId: "fld-science",
200
+ },
201
+ {
202
+ id: "q9",
203
+ questionId: "LIB-2026-009",
204
+ stem: "Item 09 — quick true / false entry for the demo set.",
205
+ topic: "Category 4",
206
+ type: "true_false",
207
+ difficulty: "medium",
208
+ author: "Owner D",
209
+ authorEmail: "owner.d@demo.exxat.io",
210
+ updatedAt: "2026-03-20",
211
+ folderId: "fld-clinical",
212
+ },
213
+ {
214
+ id: "q10",
215
+ questionId: "LIB-2026-010",
216
+ stem: "Item 10 — short-answer entry from Owner A.",
217
+ topic: "Category 1",
218
+ type: "short_answer",
219
+ difficulty: "hard",
220
+ author: "Owner A",
221
+ authorEmail: "owner.a@demo.exxat.io",
222
+ updatedAt: "2026-03-19",
223
+ folderId: "fld-science",
224
+ },
225
+ {
226
+ id: "q11",
227
+ questionId: "LIB-2026-011",
228
+ stem: "Item 11 — true / false entry showing chip alignment.",
229
+ topic: "Category 2",
230
+ type: "true_false",
231
+ difficulty: "easy",
232
+ author: "Owner E",
233
+ authorEmail: "owner.e@demo.exxat.io",
234
+ updatedAt: "2026-03-18",
235
+ folderId: "fld-ops",
236
+ },
237
+ {
238
+ id: "q12",
239
+ questionId: "LIB-2026-012",
240
+ stem: "Item 12 — short-answer entry for the catalog demo.",
241
+ topic: "Category 3",
242
+ type: "short_answer",
243
+ difficulty: "medium",
244
+ author: "Owner B",
245
+ authorEmail: "owner.b@demo.exxat.io",
246
+ updatedAt: "2026-03-17",
247
+ folderId: "fld-ops",
248
+ },
249
+ ]