@exxatdesignux/ui 0.1.0 → 0.2.7
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/bin/cli.mjs +176 -0
- package/bin/init.mjs +15 -1
- package/bin/sync-extras.mjs +65 -0
- package/consumer-extras/README.md +21 -0
- package/consumer-extras/cursor-skills/exxat-accessibility/SKILL.md +282 -0
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +68 -0
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +99 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +713 -0
- package/consumer-extras/cursor-skills/exxat-fontawesome-icons/SKILL.md +31 -0
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +36 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +27 -0
- package/consumer-extras/patterns/command-menu-pattern.md +45 -0
- package/consumer-extras/patterns/data-views-pattern.md +167 -0
- package/package.json +7 -3
- package/src/components/ui/sidebar.tsx +7 -2
- 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/app/globals.css +1 -1
- 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/page-header.tsx +149 -7
- 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 -39
- 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 +31 -2
- 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/hooks/use-app-theme.ts +1 -1
- package/template/hooks/use-coach-mark.ts +1 -1
- package/template/hooks/use-location-hash.ts +15 -0
- package/template/hooks/use-mobile.ts +1 -1
- package/template/hooks/use-mod-key-label.ts +1 -1
- 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/date-filter.ts +1 -1
- 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-header-collaborators.ts +14 -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/lib/utils.ts +1 -1
- 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/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
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { Button } from "@/components/ui/button"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
PageHeader,
|
|
7
|
+
type PageHeaderCollaborator,
|
|
8
|
+
type PageHeaderVariant,
|
|
9
|
+
} from "@/components/page-header"
|
|
6
10
|
import {
|
|
7
11
|
DropdownMenu,
|
|
8
12
|
DropdownMenuContent,
|
|
@@ -11,31 +15,56 @@ import {
|
|
|
11
15
|
DropdownMenuTrigger,
|
|
12
16
|
} from "@/components/ui/dropdown-menu"
|
|
13
17
|
import { Tip } from "@/components/ui/tip"
|
|
18
|
+
import { QUESTION_BANK_HEADER_COLLABORATORS } from "@/lib/mock/question-bank-header-collaborators"
|
|
14
19
|
|
|
15
20
|
export interface QuestionBankPageHeaderProps {
|
|
21
|
+
/** Scoped hub title (All / My / folder name) — keep in sync with `SiteHeader`. */
|
|
22
|
+
title: string
|
|
16
23
|
questionCount: number
|
|
17
24
|
onNewQuestion: () => void
|
|
18
25
|
onExport: () => void
|
|
19
26
|
showMetrics: boolean
|
|
20
27
|
onToggleMetrics: () => void
|
|
21
28
|
showTitleBlock?: boolean
|
|
29
|
+
/** `collaboration` adds access line + collaborator stack + invite before CTAs. */
|
|
30
|
+
variant?: PageHeaderVariant
|
|
31
|
+
/** Optional role / access row when `variant="collaboration"` (badge + copy). */
|
|
32
|
+
accessInfo?: React.ReactNode
|
|
33
|
+
collaborators?: PageHeaderCollaborator[]
|
|
34
|
+
collaboratorDisplayLimit?: number
|
|
35
|
+
onAddCollaborator?: () => void
|
|
36
|
+
addCollaboratorLabel?: string
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
export function QuestionBankPageHeader({
|
|
40
|
+
title,
|
|
25
41
|
questionCount,
|
|
26
42
|
onNewQuestion,
|
|
27
43
|
onExport,
|
|
28
44
|
showMetrics,
|
|
29
45
|
onToggleMetrics,
|
|
30
46
|
showTitleBlock = true,
|
|
47
|
+
variant = "default",
|
|
48
|
+
accessInfo,
|
|
49
|
+
collaborators = QUESTION_BANK_HEADER_COLLABORATORS,
|
|
50
|
+
collaboratorDisplayLimit = 4,
|
|
51
|
+
onAddCollaborator = () => {},
|
|
52
|
+
addCollaboratorLabel = "Invite people",
|
|
31
53
|
}: QuestionBankPageHeaderProps) {
|
|
32
54
|
const [moreOpen, setMoreOpen] = React.useState(false)
|
|
33
55
|
const countLine = `${questionCount} ${questionCount === 1 ? "question" : "questions"} · Last updated now`
|
|
56
|
+
const resolvedAccess = variant === "collaboration" ? accessInfo : undefined
|
|
34
57
|
|
|
35
58
|
return (
|
|
36
59
|
<PageHeader
|
|
37
|
-
title=
|
|
60
|
+
title={title}
|
|
38
61
|
subtitle={countLine}
|
|
62
|
+
variant={variant}
|
|
63
|
+
accessInfo={resolvedAccess}
|
|
64
|
+
collaborators={variant === "collaboration" ? collaborators : undefined}
|
|
65
|
+
collaboratorDisplayLimit={collaboratorDisplayLimit}
|
|
66
|
+
onAddCollaborator={variant === "collaboration" ? onAddCollaborator : undefined}
|
|
67
|
+
addCollaboratorLabel={addCollaboratorLabel}
|
|
39
68
|
showTitleBlock={showTitleBlock}
|
|
40
69
|
actions={(
|
|
41
70
|
<div className="flex items-center gap-2" role="group" aria-label="Question bank actions">
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useAutoPanel } from "@/components/secondary-panel"
|
|
4
|
+
|
|
5
|
+
/** Opens the Question bank secondary panel while this route is mounted (same pattern as rotations). */
|
|
6
|
+
export function QuestionBankPanelActivator() {
|
|
7
|
+
useAutoPanel("question-bank")
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
@@ -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
|
+
}
|