@exxatdesignux/ui 0.2.6 → 0.2.8
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/package.json +2 -1
- package/template/.agents/skills/shadcn/SKILL.md +242 -0
- package/template/.agents/skills/shadcn/agents/openai.yml +5 -0
- package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
- package/template/.agents/skills/shadcn/cli.md +257 -0
- package/template/.agents/skills/shadcn/customization.md +202 -0
- package/template/.agents/skills/shadcn/evals/evals.json +47 -0
- package/template/.agents/skills/shadcn/mcp.md +94 -0
- package/template/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/template/.agents/skills/shadcn/rules/composition.md +195 -0
- package/template/.agents/skills/shadcn/rules/forms.md +192 -0
- package/template/.agents/skills/shadcn/rules/icons.md +101 -0
- package/template/.agents/skills/shadcn/rules/styling.md +162 -0
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +712 -0
- package/template/.cursor/rules/exxat-accessibility.mdc +33 -0
- package/template/.cursor/rules/exxat-command-menu.mdc +23 -0
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +53 -0
- package/template/.cursor/rules/exxat-data-tables.mdc +31 -0
- package/template/.cursor/rules/exxat-ds-agents.mdc +26 -0
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +100 -0
- package/template/.cursor/rules/exxat-list-page-connected-views.mdc +16 -0
- package/template/.cursor/rules/exxat-no-toast.mdc +26 -0
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +22 -0
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +40 -0
- package/template/AGENTS.md +52 -11
- package/template/app/(app)/dashboard/page.tsx +1 -1
- package/template/app/(app)/data-list/[id]/page.tsx +24 -8
- package/template/app/(app)/data-list/new/page.tsx +7 -4
- package/template/app/(app)/data-list/page.tsx +1 -1
- package/template/app/(app)/examples/page.tsx +41 -0
- package/template/app/(app)/question-bank/page.tsx +3 -3
- package/template/components/app-sidebar.tsx +52 -35
- package/template/components/compliance-table.tsx +79 -0
- package/template/components/data-list-client.tsx +36 -25
- package/template/components/data-list-table.tsx +797 -10
- package/template/components/data-views/finder-panel-view.tsx +405 -0
- package/template/components/data-views/folder-grid-view.tsx +86 -0
- package/template/components/data-views/index.ts +59 -0
- package/template/components/data-views/list-page-split-details-placeholder.tsx +39 -0
- package/template/components/data-views/list-page-split-hub-chrome.tsx +60 -0
- package/template/components/data-views/list-page-split-hub-tokens.ts +16 -0
- package/template/components/data-views/list-page-tree-column-header.tsx +31 -0
- package/template/components/data-views/list-page-tree-panel-shell.tsx +91 -0
- package/template/components/data-views/list-page-view-frame.tsx +53 -0
- package/template/components/data-views/os-folder-glyph.tsx +121 -0
- package/template/components/folder-details-shell.tsx +230 -0
- package/template/components/hub-tree-panel-view.tsx +672 -0
- package/template/components/list-hub-status-badge.tsx +17 -3
- package/template/components/placements-page-header.tsx +14 -8
- package/template/components/placements-table-columns.tsx +8 -8
- package/template/components/question-bank-client.tsx +157 -40
- package/template/components/question-bank-new-folder-sheet.tsx +248 -0
- package/template/components/question-bank-os-folder-view.tsx +648 -0
- package/template/components/question-bank-page-header.tsx +3 -3
- package/template/components/question-bank-panel-activator.tsx +9 -0
- package/template/components/question-bank-secondary-nav.tsx +226 -0
- package/template/components/question-bank-table.tsx +707 -22
- package/template/components/secondary-panel.tsx +41 -107
- package/template/components/sites-table.tsx +66 -0
- package/template/components/team-client.tsx +7 -0
- package/template/components/team-table.tsx +156 -1
- package/template/components/templates/list-page.tsx +2 -2
- package/template/components/ui/avatar.tsx +1 -1
- package/template/components/ui/badge.tsx +1 -1
- package/template/components/ui/banner.tsx +1 -1
- package/template/components/ui/breadcrumb.tsx +1 -1
- package/template/components/ui/button.tsx +1 -1
- package/template/components/ui/calendar.tsx +1 -1
- package/template/components/ui/card.tsx +1 -1
- package/template/components/ui/chart.tsx +1 -1
- package/template/components/ui/checkbox.tsx +1 -1
- package/template/components/ui/coach-mark.tsx +1 -1
- package/template/components/ui/collapsible.tsx +1 -1
- package/template/components/ui/command.tsx +1 -1
- package/template/components/ui/date-picker-field.tsx +1 -1
- package/template/components/ui/dialog.tsx +1 -1
- package/template/components/ui/drag-handle-grip.tsx +1 -1
- package/template/components/ui/drawer.tsx +1 -1
- package/template/components/ui/dropdown-menu.tsx +1 -1
- package/template/components/ui/field.tsx +1 -1
- package/template/components/ui/form.tsx +1 -1
- package/template/components/ui/input-group.tsx +1 -1
- package/template/components/ui/input-mask.tsx +1 -1
- package/template/components/ui/input.tsx +1 -1
- package/template/components/ui/kbd.tsx +1 -1
- package/template/components/ui/label.tsx +1 -1
- package/template/components/ui/payment-card-fields.tsx +1 -1
- package/template/components/ui/popover.tsx +1 -1
- package/template/components/ui/radio-group.tsx +1 -1
- package/template/components/ui/resizable.tsx +68 -0
- package/template/components/ui/select.tsx +1 -1
- package/template/components/ui/selection-tile-grid.tsx +1 -1
- package/template/components/ui/separator.tsx +1 -1
- package/template/components/ui/sheet.tsx +1 -1
- package/template/components/ui/sidebar.tsx +1 -1
- package/template/components/ui/skeleton.tsx +1 -1
- package/template/components/ui/sonner.tsx +1 -1
- package/template/components/ui/status-badge.tsx +1 -1
- package/template/components/ui/table.tsx +1 -1
- package/template/components/ui/tabs.tsx +1 -1
- package/template/components/ui/textarea.tsx +1 -1
- package/template/components/ui/tip.tsx +1 -1
- package/template/components/ui/toggle-group.tsx +1 -1
- package/template/components/ui/toggle-switch.tsx +1 -1
- package/template/components/ui/toggle.tsx +1 -1
- package/template/components/ui/tooltip.tsx +1 -1
- package/template/components/ui/view-segmented-control.tsx +1 -1
- package/template/docs/data-views-pattern.md +7 -0
- package/template/fontawesome-subset.manifest.json +2 -2
- package/template/hooks/use-location-hash.ts +15 -0
- package/template/hooks/use-sidebar-reflow-zoom.ts +40 -0
- package/template/lib/ask-leo-route-context.ts +25 -57
- package/template/lib/coach-mark-registry.ts +13 -13
- package/template/lib/command-menu-config.ts +28 -23
- package/template/lib/command-menu-search-data.ts +10 -9
- package/template/lib/data-list-view-surface.ts +12 -1
- package/template/lib/data-list-view.ts +6 -3
- package/template/lib/mock/dashboard.ts +11 -11
- package/template/lib/mock/navigation.tsx +22 -63
- package/template/lib/mock/placements-kpi.ts +19 -19
- package/template/lib/mock/question-bank-folders.ts +167 -0
- package/template/lib/mock/question-bank-inspector.ts +109 -0
- package/template/lib/mock/question-bank-kpi.ts +1 -1
- package/template/lib/mock/question-bank.ts +80 -0
- package/template/lib/question-bank-nav.ts +91 -0
- package/template/next.config.mjs +8 -0
- package/template/package.json +1 -0
- package/template/public/folders/icons8-folder-windows-11.svg +1 -0
- package/template/scripts/fontawesome-subset-audit.mjs +2 -3
- package/template/app/(app)/compliance/page.tsx +0 -10
- package/template/app/(app)/rotations/page.tsx +0 -15
- package/template/app/(app)/sites/all/page.tsx +0 -13
- package/template/app/(app)/team/page.tsx +0 -10
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Question bank secondary sidebar — All / My / folder tree (Font Awesome only).
|
|
5
|
+
* Scope syncs to the main hub via `?scope=` + optional `folderId=` (`lib/question-bank-nav.ts`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react"
|
|
9
|
+
import Link from "next/link"
|
|
10
|
+
import { usePathname, useSearchParams } from "next/navigation"
|
|
11
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
|
12
|
+
import { Tip } from "@/components/ui/tip"
|
|
13
|
+
import { cn } from "@/lib/utils"
|
|
14
|
+
import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
|
|
15
|
+
import { QUESTION_BANK_FOLDER_ICON_COLORS } from "@/lib/mock/question-bank-folders"
|
|
16
|
+
import { DEFAULT_QUESTION_BANK_FOLDERS } from "@/lib/mock/question-bank-folders"
|
|
17
|
+
import { useSecondaryPanel } from "@/components/secondary-panel"
|
|
18
|
+
import {
|
|
19
|
+
parseQuestionBankNav,
|
|
20
|
+
questionBankNavHref,
|
|
21
|
+
type QuestionBankNavScope,
|
|
22
|
+
} from "@/lib/question-bank-nav"
|
|
23
|
+
|
|
24
|
+
function matchesQuery(q: string, ...parts: (string | undefined)[]) {
|
|
25
|
+
if (!q) return true
|
|
26
|
+
return parts.some(p => p && p.toLowerCase().includes(q))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isNavActive(
|
|
30
|
+
pathname: string,
|
|
31
|
+
nav: ReturnType<typeof parseQuestionBankNav>,
|
|
32
|
+
scope: QuestionBankNavScope,
|
|
33
|
+
folderId?: string | null,
|
|
34
|
+
) {
|
|
35
|
+
if (pathname !== "/question-bank") return false
|
|
36
|
+
if (scope === "all") return nav.scope === "all"
|
|
37
|
+
if (scope === "my") return nav.scope === "my"
|
|
38
|
+
if (scope === "folder" && folderId) {
|
|
39
|
+
return nav.scope === "folder" && nav.folderId === folderId
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function NavRow({
|
|
45
|
+
href,
|
|
46
|
+
active,
|
|
47
|
+
iconClass,
|
|
48
|
+
label,
|
|
49
|
+
onClick,
|
|
50
|
+
}: {
|
|
51
|
+
href: string
|
|
52
|
+
active: boolean
|
|
53
|
+
iconClass: string
|
|
54
|
+
label: string
|
|
55
|
+
/** e.g. reopen secondary panel on same-route “All questions” */
|
|
56
|
+
onClick?: () => void
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<li className="min-w-0">
|
|
60
|
+
<Tip label={label} side="right">
|
|
61
|
+
<Link
|
|
62
|
+
href={href}
|
|
63
|
+
onClick={() => onClick?.()}
|
|
64
|
+
aria-current={active ? "page" : undefined}
|
|
65
|
+
className={cn(
|
|
66
|
+
"flex w-full min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors",
|
|
67
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
68
|
+
active
|
|
69
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
70
|
+
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<span className="size-4 shrink-0 text-center text-[13px] leading-none" aria-hidden>
|
|
74
|
+
<i className={cn(active ? "fa-solid" : "fa-light", iconClass)} aria-hidden />
|
|
75
|
+
</span>
|
|
76
|
+
<span className="min-w-0 flex-1 truncate">{label}</span>
|
|
77
|
+
</Link>
|
|
78
|
+
</Tip>
|
|
79
|
+
</li>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function PanelFolderBranch({
|
|
84
|
+
folder,
|
|
85
|
+
folders,
|
|
86
|
+
query,
|
|
87
|
+
depth,
|
|
88
|
+
pathname,
|
|
89
|
+
nav,
|
|
90
|
+
}: {
|
|
91
|
+
folder: QuestionBankFolder
|
|
92
|
+
folders: QuestionBankFolder[]
|
|
93
|
+
query: string
|
|
94
|
+
depth: number
|
|
95
|
+
pathname: string
|
|
96
|
+
nav: ReturnType<typeof parseQuestionBankNav>
|
|
97
|
+
}) {
|
|
98
|
+
const childFolders = folders
|
|
99
|
+
.filter(f => f.parentId === folder.id)
|
|
100
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
101
|
+
|
|
102
|
+
const visibleFolders = React.useMemo(
|
|
103
|
+
() => childFolders.filter(f => matchesQuery(query, f.name)),
|
|
104
|
+
[childFolders, query],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const hasSubfolders = childFolders.length > 0
|
|
108
|
+
const indent = depth * 10
|
|
109
|
+
|
|
110
|
+
if (query && !matchesQuery(query, folder.name) && visibleFolders.length === 0) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const folderHref = questionBankNavHref({ scope: "folder", folderId: folder.id })
|
|
115
|
+
const folderActive = isNavActive(pathname, nav, "folder", folder.id)
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Collapsible defaultOpen={depth < 1} className="group">
|
|
119
|
+
<div className="flex min-w-0 items-center rounded-md hover:bg-sidebar-accent/40">
|
|
120
|
+
<div style={{ width: indent }} className="shrink-0" aria-hidden />
|
|
121
|
+
{hasSubfolders ? (
|
|
122
|
+
<CollapsibleTrigger asChild>
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
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"
|
|
126
|
+
aria-label={`${folder.name} — expand or collapse`}
|
|
127
|
+
>
|
|
128
|
+
<i
|
|
129
|
+
className="fa-light fa-chevron-right text-xs transition-transform duration-150 group-data-[state=open]:rotate-90"
|
|
130
|
+
aria-hidden
|
|
131
|
+
/>
|
|
132
|
+
</button>
|
|
133
|
+
</CollapsibleTrigger>
|
|
134
|
+
) : (
|
|
135
|
+
<div className="size-8 shrink-0" aria-hidden />
|
|
136
|
+
)}
|
|
137
|
+
<Tip label={folder.name} side="right">
|
|
138
|
+
<Link
|
|
139
|
+
href={folderHref}
|
|
140
|
+
aria-current={folderActive ? "page" : undefined}
|
|
141
|
+
className={cn(
|
|
142
|
+
"flex min-w-0 flex-1 items-center gap-2 py-1.5 pr-2 text-left text-sm transition-colors",
|
|
143
|
+
"rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
144
|
+
folderActive
|
|
145
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
146
|
+
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
|
147
|
+
)}
|
|
148
|
+
>
|
|
149
|
+
<i
|
|
150
|
+
className={cn("fa-light shrink-0 text-sm", folder.icon, QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey])}
|
|
151
|
+
aria-hidden
|
|
152
|
+
/>
|
|
153
|
+
<span className="min-w-0 flex-1 truncate leading-tight">{folder.name}</span>
|
|
154
|
+
{hasSubfolders ? (
|
|
155
|
+
<span className="shrink-0 text-xs tabular-nums text-muted-foreground">{childFolders.length}</span>
|
|
156
|
+
) : null}
|
|
157
|
+
</Link>
|
|
158
|
+
</Tip>
|
|
159
|
+
</div>
|
|
160
|
+
{hasSubfolders ? (
|
|
161
|
+
<CollapsibleContent>
|
|
162
|
+
{visibleFolders.map(child => (
|
|
163
|
+
<PanelFolderBranch
|
|
164
|
+
key={child.id}
|
|
165
|
+
folder={child}
|
|
166
|
+
folders={folders}
|
|
167
|
+
query={query}
|
|
168
|
+
depth={depth + 1}
|
|
169
|
+
pathname={pathname}
|
|
170
|
+
nav={nav}
|
|
171
|
+
/>
|
|
172
|
+
))}
|
|
173
|
+
</CollapsibleContent>
|
|
174
|
+
) : null}
|
|
175
|
+
</Collapsible>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function QuestionBankSecondaryNav({ query }: { query: string }) {
|
|
180
|
+
const pathname = usePathname()
|
|
181
|
+
const searchParams = useSearchParams()
|
|
182
|
+
const searchParamsKey = searchParams.toString()
|
|
183
|
+
const { openPanel } = useSecondaryPanel()
|
|
184
|
+
const nav = React.useMemo(
|
|
185
|
+
() => parseQuestionBankNav(new URLSearchParams(searchParamsKey)),
|
|
186
|
+
[searchParamsKey],
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const folders = DEFAULT_QUESTION_BANK_FOLDERS
|
|
190
|
+
const q = query.trim().toLowerCase()
|
|
191
|
+
|
|
192
|
+
const roots = React.useMemo(
|
|
193
|
+
() => folders.filter(f => f.parentId === null).sort((a, b) => a.name.localeCompare(b.name)),
|
|
194
|
+
[folders],
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-3 pb-4" role="navigation" aria-label="Question bank">
|
|
199
|
+
<ul className="space-y-0.5" role="list">
|
|
200
|
+
<NavRow
|
|
201
|
+
href={questionBankNavHref({ scope: "all" })}
|
|
202
|
+
active={isNavActive(pathname, nav, "all")}
|
|
203
|
+
iconClass="fa-table-list"
|
|
204
|
+
label="All questions"
|
|
205
|
+
onClick={() => openPanel("question-bank")}
|
|
206
|
+
/>
|
|
207
|
+
<NavRow
|
|
208
|
+
href={questionBankNavHref({ scope: "my" })}
|
|
209
|
+
active={isNavActive(pathname, nav, "my")}
|
|
210
|
+
iconClass="fa-user"
|
|
211
|
+
label="My questions"
|
|
212
|
+
/>
|
|
213
|
+
<li role="presentation" className="select-none">
|
|
214
|
+
<span className="block px-2 pt-3 pb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground/70">
|
|
215
|
+
Folders
|
|
216
|
+
</span>
|
|
217
|
+
</li>
|
|
218
|
+
{roots.map(folder => (
|
|
219
|
+
<li key={folder.id} className="min-w-0">
|
|
220
|
+
<PanelFolderBranch folder={folder} folders={folders} query={q} depth={0} pathname={pathname} nav={nav} />
|
|
221
|
+
</li>
|
|
222
|
+
))}
|
|
223
|
+
</ul>
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
226
|
+
}
|