@exxatdesignux/ui 0.2.15 → 0.2.17
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 +23 -0
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -1
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +151 -3
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/accessibility.md +142 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/coach-marks.md +169 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/references/data-table-pattern.md +382 -0
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +56 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +17 -1
- package/consumer-extras/patterns/collaboration-access-pattern.md +2 -0
- package/package.json +3 -3
- package/src/components/ui/banner.tsx +2 -0
- package/src/components/ui/chart.tsx +57 -2
- package/src/components/ui/sidebar.tsx +1 -0
- package/src/globals.css +21 -2
- package/src/theme.css +4 -2
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +1 -1
- package/template/.cursor/rules/exxat-mono-ids.mdc +30 -0
- package/template/AGENTS.md +23 -18
- package/template/app/(app)/data-list/page.tsx +2 -2
- package/template/app/(app)/question-bank/layout.tsx +27 -7
- package/template/app/(app)/question-bank/new/page.tsx +58 -0
- package/template/app/globals.css +136 -2
- package/template/app/layout.tsx +41 -5
- package/template/components/app-sidebar.tsx +141 -59
- package/template/components/ask-leo-sidebar.tsx +1 -4
- package/template/components/brand-color-picker.tsx +344 -0
- package/template/components/compliance-list-view.tsx +33 -51
- package/template/components/compliance-table.tsx +24 -0
- package/template/components/data-table/index.tsx +68 -24
- package/template/components/data-table/pagination.tsx +0 -1
- package/template/components/data-table/types.ts +4 -1
- package/template/components/data-table/use-table-state.ts +243 -94
- package/template/components/data-views/data-row-list.tsx +183 -0
- package/template/components/data-views/finder-panel-view.tsx +2 -2
- package/template/components/data-views/index.ts +26 -3
- 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/os-folder-glyph.tsx +8 -0
- 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/export-drawer.tsx +1 -1
- package/template/components/exxat-product-logo.tsx +173 -379
- package/template/components/folder-details-shell.tsx +1 -1
- package/template/components/hub-tree-panel-view.tsx +88 -80
- package/template/components/invite-collaborators-drawer.tsx +5 -3
- package/template/components/key-metrics.tsx +116 -51
- package/template/components/new-placement-form.tsx +4 -2
- package/template/components/new-question-composer.tsx +2208 -0
- package/template/components/page-breadcrumb-trail.tsx +131 -0
- package/template/components/page-header.tsx +21 -11
- package/template/components/{data-views/placement-board-card.tsx → placement-board-card.tsx} +1 -1
- package/template/components/placement-detail.tsx +1 -1
- package/template/components/placements-board-view.tsx +1 -1
- package/template/components/{data-list-client.tsx → placements-client.tsx} +9 -7
- package/template/components/placements-list-view.tsx +18 -132
- package/template/components/{data-list-table-cells.test.tsx → placements-table-cells.test.tsx} +2 -2
- package/template/components/{data-list-table-cells.tsx → placements-table-cells.tsx} +1 -1
- package/template/components/placements-table-columns.tsx +2 -2
- package/template/components/{data-list-table.tsx → placements-table.tsx} +67 -58
- package/template/components/product-switcher.tsx +26 -11
- package/template/components/product-wordmark.tsx +285 -0
- package/template/components/question-bank-client.tsx +130 -70
- package/template/components/question-bank-hub-client.tsx +108 -115
- package/template/components/question-bank-list-view.tsx +30 -54
- package/template/components/question-bank-new-folder-sheet.tsx +1 -1
- package/template/components/question-bank-page-header.tsx +18 -2
- package/template/components/question-bank-secondary-nav.tsx +12 -228
- package/template/components/question-bank-table.tsx +30 -5
- package/template/components/rotations-empty-state.tsx +3 -0
- package/template/components/secondary-panel.tsx +24 -4
- package/template/components/settings-appearance-card.tsx +584 -141
- package/template/components/site-header.tsx +56 -32
- package/template/components/sites-list-view.tsx +31 -36
- package/template/components/sites-table.tsx +24 -0
- package/template/components/table-properties/drawer.tsx +1 -1
- package/template/components/team-client.tsx +1 -1
- package/template/components/team-list-view.tsx +34 -50
- package/template/components/team-table.tsx +29 -3
- 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 +11 -6
- package/template/components/ui/dot-pattern.tsx +50 -26
- package/template/components/ui/leo-icon.tsx +23 -3
- package/template/contexts/product-context.tsx +51 -7
- package/template/contexts/system-banner-context.tsx +112 -4
- package/template/docs/collaboration-access-pattern.md +2 -0
- package/template/docs/question-bank-hub-header-pattern.md +25 -0
- package/template/eslint.config.mjs +18 -0
- package/template/hooks/use-secondary-panel-hub-nav.ts +17 -1
- package/template/hooks/use-sidebar-reflow-zoom.ts +21 -11
- package/template/lib/data-list-persistence.ts +57 -257
- package/template/lib/dev-log.test.ts +6 -5
- package/template/lib/exxat-palette.json +1462 -0
- package/template/lib/exxat-palette.ts +136 -0
- package/template/lib/list-page-table-properties.ts +1 -1
- package/template/lib/list-status-badges.ts +1 -1
- package/template/lib/mailto.ts +29 -0
- package/template/lib/mock/navigation.tsx +30 -1
- package/template/lib/placement-board-card-layout.ts +1 -1
- package/template/lib/product-brand.ts +268 -0
- package/template/lib/question-bank-authoring.ts +308 -0
- package/template/lib/question-bank-nav.ts +70 -0
- package/template/lib/raf-throttle.ts +45 -0
- package/template/lib/table-state-lifecycle.ts +474 -0
- package/template/next.config.mjs +156 -0
- package/template/package.json +6 -6
- package/template/stores/app-store.ts +46 -1
- package/template/components/command-menu-01.tsx +0 -133
- package/template/components/command-menu-02.tsx +0 -386
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Question bank **Folders** tree — shared outline primitives (`OutlineTree*`) so the rail
|
|
5
|
+
* matches `HubTreePanelView` and shadcn sidebar file-tree rhythm.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react"
|
|
9
|
+
import Link from "next/link"
|
|
10
|
+
import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import {
|
|
13
|
+
DropdownMenu,
|
|
14
|
+
DropdownMenuContent,
|
|
15
|
+
DropdownMenuItem,
|
|
16
|
+
DropdownMenuSeparator,
|
|
17
|
+
DropdownMenuTrigger,
|
|
18
|
+
} from "@/components/ui/dropdown-menu"
|
|
19
|
+
import { Tip } from "@/components/ui/tip"
|
|
20
|
+
import { cn } from "@/lib/utils"
|
|
21
|
+
import {
|
|
22
|
+
OutlineTreeCollapsibleContentRail,
|
|
23
|
+
OutlineTreeMenuItem,
|
|
24
|
+
OutlineTreeSub,
|
|
25
|
+
} from "@/components/data-views/outline-tree-menu"
|
|
26
|
+
import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
|
|
27
|
+
import { QUESTION_BANK_FOLDER_ICON_COLORS } from "@/lib/mock/question-bank-folders"
|
|
28
|
+
import {
|
|
29
|
+
isQuestionBankNavActive,
|
|
30
|
+
questionBankHubScopeHref,
|
|
31
|
+
type QuestionBankNavState,
|
|
32
|
+
} from "@/lib/question-bank-nav"
|
|
33
|
+
|
|
34
|
+
export interface QuestionBankFolderTreeBranchProps {
|
|
35
|
+
folder: QuestionBankFolder
|
|
36
|
+
folders: QuestionBankFolder[]
|
|
37
|
+
pathname: string
|
|
38
|
+
hubSearchParams: URLSearchParams
|
|
39
|
+
nav: QuestionBankNavState
|
|
40
|
+
canManageFolders: boolean
|
|
41
|
+
canManageAccess: boolean
|
|
42
|
+
onAddSubfolder: (parentId: string) => void
|
|
43
|
+
onCustomizeFolder: (folder: QuestionBankFolder) => void
|
|
44
|
+
onManageAccess: () => void
|
|
45
|
+
onDeleteFolder: (folder: QuestionBankFolder) => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function QuestionBankFolderTreeBranch({
|
|
49
|
+
folder,
|
|
50
|
+
folders,
|
|
51
|
+
pathname,
|
|
52
|
+
hubSearchParams,
|
|
53
|
+
nav,
|
|
54
|
+
canManageFolders,
|
|
55
|
+
canManageAccess,
|
|
56
|
+
onAddSubfolder,
|
|
57
|
+
onCustomizeFolder,
|
|
58
|
+
onManageAccess,
|
|
59
|
+
onDeleteFolder,
|
|
60
|
+
}: QuestionBankFolderTreeBranchProps) {
|
|
61
|
+
const childFolders = React.useMemo(
|
|
62
|
+
() =>
|
|
63
|
+
folders
|
|
64
|
+
.filter(f => f.parentId === folder.id)
|
|
65
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
66
|
+
[folders, folder.id],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const hasSubfolders = childFolders.length > 0
|
|
70
|
+
const isRootFolder = folder.parentId === null
|
|
71
|
+
|
|
72
|
+
const folderHref = questionBankHubScopeHref(pathname, hubSearchParams, {
|
|
73
|
+
scope: "folder",
|
|
74
|
+
folderId: folder.id,
|
|
75
|
+
})
|
|
76
|
+
const folderActive = isQuestionBankNavActive(pathname, nav, "folder", folder.id)
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Collapsible defaultOpen={isRootFolder} className="group/collapsible">
|
|
80
|
+
<div
|
|
81
|
+
className={cn(
|
|
82
|
+
"group/row flex min-w-0 items-center rounded-md px-2 transition-colors",
|
|
83
|
+
folderActive
|
|
84
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground ring-1 ring-inset ring-sidebar-border/80"
|
|
85
|
+
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{hasSubfolders ? (
|
|
89
|
+
<CollapsibleTrigger asChild>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className="flex size-8 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
93
|
+
aria-label={`${folder.name} — expand or collapse`}
|
|
94
|
+
>
|
|
95
|
+
<i
|
|
96
|
+
className="fa-light fa-chevron-right text-xs transition-transform duration-150 group-data-[state=open]/collapsible:rotate-90"
|
|
97
|
+
aria-hidden
|
|
98
|
+
/>
|
|
99
|
+
</button>
|
|
100
|
+
</CollapsibleTrigger>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="size-8 shrink-0" aria-hidden />
|
|
103
|
+
)}
|
|
104
|
+
<Tip label={folder.name} side="right">
|
|
105
|
+
<Link
|
|
106
|
+
href={folderHref}
|
|
107
|
+
scroll={false}
|
|
108
|
+
aria-current={folderActive ? "page" : undefined}
|
|
109
|
+
className={cn(
|
|
110
|
+
"flex min-w-0 flex-1 items-center gap-2 py-1.5 text-left text-sm transition-colors",
|
|
111
|
+
"rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
112
|
+
!folderActive && "text-sidebar-foreground",
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
<i
|
|
116
|
+
className={cn(
|
|
117
|
+
"fa-light inline-flex size-4 shrink-0 items-center justify-center text-sm leading-none",
|
|
118
|
+
folder.icon,
|
|
119
|
+
QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey],
|
|
120
|
+
)}
|
|
121
|
+
aria-hidden
|
|
122
|
+
/>
|
|
123
|
+
<span className="min-w-0 flex-1 truncate leading-tight">{folder.name}</span>
|
|
124
|
+
{hasSubfolders ? (
|
|
125
|
+
<span className="shrink-0 text-xs tabular-nums text-muted-foreground">{childFolders.length}</span>
|
|
126
|
+
) : null}
|
|
127
|
+
</Link>
|
|
128
|
+
</Tip>
|
|
129
|
+
{canManageFolders ? (
|
|
130
|
+
<DropdownMenu>
|
|
131
|
+
<Tip label={`Folder actions for ${folder.name}`} side="right">
|
|
132
|
+
<DropdownMenuTrigger asChild>
|
|
133
|
+
<Button
|
|
134
|
+
type="button"
|
|
135
|
+
size="icon-xs"
|
|
136
|
+
variant="ghost"
|
|
137
|
+
aria-label={`Folder actions for ${folder.name}`}
|
|
138
|
+
className="shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover/row:opacity-100 group-focus-within/row:opacity-100"
|
|
139
|
+
onClick={event => event.stopPropagation()}
|
|
140
|
+
>
|
|
141
|
+
<i className="fa-light fa-ellipsis text-xs" aria-hidden="true" />
|
|
142
|
+
</Button>
|
|
143
|
+
</DropdownMenuTrigger>
|
|
144
|
+
</Tip>
|
|
145
|
+
<DropdownMenuContent align="end">
|
|
146
|
+
<DropdownMenuItem
|
|
147
|
+
onSelect={() => {
|
|
148
|
+
window.setTimeout(() => onAddSubfolder(folder.id), 0)
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<i className="fa-light fa-plus text-xs" aria-hidden="true" />
|
|
152
|
+
Add folder
|
|
153
|
+
</DropdownMenuItem>
|
|
154
|
+
<DropdownMenuItem
|
|
155
|
+
onSelect={() => {
|
|
156
|
+
window.setTimeout(() => onCustomizeFolder(folder), 0)
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<i className="fa-light fa-wand-magic-sparkles text-xs" aria-hidden="true" />
|
|
160
|
+
Customize
|
|
161
|
+
</DropdownMenuItem>
|
|
162
|
+
<DropdownMenuItem
|
|
163
|
+
disabled={!canManageAccess}
|
|
164
|
+
onSelect={() => {
|
|
165
|
+
window.setTimeout(() => onManageAccess(), 0)
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<i className="fa-light fa-user-gear text-xs" aria-hidden="true" />
|
|
169
|
+
Manage access
|
|
170
|
+
</DropdownMenuItem>
|
|
171
|
+
<DropdownMenuSeparator />
|
|
172
|
+
<DropdownMenuItem
|
|
173
|
+
variant="destructive"
|
|
174
|
+
onSelect={() => {
|
|
175
|
+
window.setTimeout(() => onDeleteFolder(folder), 0)
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<i className="fa-light fa-trash text-xs" aria-hidden="true" />
|
|
179
|
+
Delete
|
|
180
|
+
</DropdownMenuItem>
|
|
181
|
+
</DropdownMenuContent>
|
|
182
|
+
</DropdownMenu>
|
|
183
|
+
) : null}
|
|
184
|
+
</div>
|
|
185
|
+
{hasSubfolders ? (
|
|
186
|
+
<OutlineTreeCollapsibleContentRail>
|
|
187
|
+
<OutlineTreeSub surface="sidebar" guideLayout="chevronRail">
|
|
188
|
+
{childFolders.map(child => (
|
|
189
|
+
<OutlineTreeMenuItem key={child.id}>
|
|
190
|
+
<QuestionBankFolderTreeBranch
|
|
191
|
+
folder={child}
|
|
192
|
+
folders={folders}
|
|
193
|
+
pathname={pathname}
|
|
194
|
+
hubSearchParams={hubSearchParams}
|
|
195
|
+
nav={nav}
|
|
196
|
+
canManageFolders={canManageFolders}
|
|
197
|
+
canManageAccess={canManageAccess}
|
|
198
|
+
onAddSubfolder={onAddSubfolder}
|
|
199
|
+
onCustomizeFolder={onCustomizeFolder}
|
|
200
|
+
onManageAccess={onManageAccess}
|
|
201
|
+
onDeleteFolder={onDeleteFolder}
|
|
202
|
+
/>
|
|
203
|
+
</OutlineTreeMenuItem>
|
|
204
|
+
))}
|
|
205
|
+
</OutlineTreeSub>
|
|
206
|
+
</OutlineTreeCollapsibleContentRail>
|
|
207
|
+
) : null}
|
|
208
|
+
</Collapsible>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
@@ -145,7 +145,7 @@ export function ExportDrawer({
|
|
|
145
145
|
side="right"
|
|
146
146
|
showCloseButton={false}
|
|
147
147
|
showOverlay={false}
|
|
148
|
-
className="z-[
|
|
148
|
+
className="z-[80] w-80 sm:max-w-80 p-0 gap-0 flex flex-col border border-border shadow-xl rounded-xl overflow-hidden"
|
|
149
149
|
style={{ top: "0.5rem", bottom: "0.5rem", right: "0.5rem", height: "calc(100vh - 1rem)" }}
|
|
150
150
|
>
|
|
151
151
|
{/* Header */}
|