@exxatdesignux/ui 0.2.8 → 0.2.10
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/consumer-extras/cursor-skills/exxat-card-vs-list-rows/SKILL.md +20 -0
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +33 -0
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
- package/consumer-extras/cursor-skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +31 -5
- package/consumer-extras/cursor-skills/exxat-kpi-max-four/SKILL.md +19 -0
- package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +27 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +1 -0
- package/consumer-extras/patterns/collaboration-access-pattern.md +114 -0
- package/consumer-extras/patterns/data-views-pattern.md +12 -4
- package/package.json +17 -4
- package/src/components/ui/banner.tsx +20 -7
- package/src/components/ui/date-picker-field.tsx +3 -3
- package/src/components/ui/dropdown-menu.tsx +17 -6
- package/src/components/ui/input-group.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/separator.tsx +2 -2
- package/src/components/ui/sidebar.tsx +31 -3
- package/src/components/ui/textarea.tsx +1 -1
- package/src/globals.css +0 -1
- package/src/index.ts +1 -0
- package/src/lib/date-filter.ts +13 -4
- package/src/lib/dropdown-menu-surface.ts +13 -0
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +27 -9
- package/template/.cursor/rules/exxat-data-tables.mdc +1 -0
- package/template/AGENTS.md +82 -27
- package/template/app/(app)/examples/page.tsx +2 -1
- package/template/app/(app)/help/page.tsx +6 -0
- package/template/app/(app)/layout.tsx +7 -4
- package/template/app/(app)/question-bank/find/page.tsx +12 -0
- package/template/app/(app)/question-bank/layout.tsx +46 -0
- package/template/app/(app)/question-bank/library/page.tsx +11 -0
- package/template/app/(app)/question-bank/list/page.tsx +12 -0
- package/template/app/(app)/question-bank/page.tsx +4 -3
- package/template/app/globals.css +1 -2
- package/template/components/app-sidebar.tsx +51 -13
- package/template/components/ask-leo-composer.tsx +173 -45
- package/template/components/ask-leo-sidebar.tsx +9 -1
- package/template/components/chart-area-interactive.tsx +3 -13
- package/template/components/charts-overview.tsx +33 -6
- package/template/components/collaboration-access-flow.tsx +144 -0
- package/template/components/compliance-page-header.tsx +1 -1
- package/template/components/compliance-table.tsx +2 -2
- package/template/components/dashboard-tabs.tsx +4 -3
- package/template/components/data-list-table-cells.tsx +1 -1
- package/template/components/data-list-table.tsx +1 -1
- package/template/components/data-table/index.tsx +5 -5
- package/template/components/data-table/use-table-state.ts +18 -2
- package/template/components/data-view-dashboard-charts-compliance.tsx +8 -5
- package/template/components/data-view-dashboard-charts-team.tsx +8 -5
- package/template/components/data-view-dashboard-charts.tsx +62 -227
- package/template/components/dedicated-search-recents.tsx +96 -0
- package/template/components/dedicated-search-url-composer.tsx +112 -0
- package/template/components/getting-started.tsx +1 -1
- package/template/components/hub-tree-panel-view.tsx +10 -26
- package/template/components/invite-collaborators-drawer.tsx +453 -0
- package/template/components/key-metrics.tsx +54 -8
- package/template/components/nav-documents.tsx +1 -1
- package/template/components/new-placement-form.tsx +3 -3
- package/template/components/page-header.tsx +76 -59
- package/template/components/placements-board-view.tsx +3 -3
- package/template/components/placements-page-header.tsx +1 -1
- package/template/components/placements-table-columns.tsx +3 -2
- package/template/components/product-switcher.tsx +0 -1
- package/template/components/question-bank-board-view.tsx +35 -47
- package/template/components/question-bank-client.tsx +293 -81
- package/template/components/question-bank-dashboard-charts.tsx +174 -0
- package/template/components/question-bank-favorite-button.tsx +46 -0
- package/template/components/question-bank-hub-client.tsx +436 -0
- package/template/components/question-bank-list-view.tsx +26 -19
- package/template/components/question-bank-new-folder-sheet.tsx +56 -42
- package/template/components/question-bank-os-folder-view.tsx +3 -14
- package/template/components/question-bank-page-header.tsx +85 -53
- package/template/components/question-bank-panel-activator.tsx +3 -4
- package/template/components/question-bank-secondary-nav.tsx +523 -65
- package/template/components/question-bank-table.tsx +125 -343
- package/template/components/secondary-panel.tsx +130 -63
- package/template/components/settings-client.tsx +3 -1
- package/template/components/sidebar-shell.tsx +2 -0
- package/template/components/sites-all-client.tsx +1 -1
- package/template/components/sites-table.tsx +1 -1
- package/template/components/system-banner-slot.tsx +2 -1
- package/template/components/table-properties/drawer.tsx +3 -3
- package/template/components/table-properties/sort-card.tsx +1 -1
- package/template/components/team-page-header.tsx +1 -1
- package/template/components/team-table.tsx +8 -4
- package/template/components/templates/dedicated-search-landing-template.tsx +58 -0
- package/template/components/templates/dedicated-search-results-template.tsx +19 -0
- package/template/components/templates/discovery-hub-template.tsx +273 -0
- package/template/components/templates/list-page.tsx +11 -4
- package/template/components/templates/nested-secondary-panel-shell.tsx +57 -0
- package/template/components/templates/secondary-panel-hub-template.tsx +54 -0
- package/template/docs/card-vs-rows-pattern.md +36 -0
- package/template/docs/collaboration-access-pattern.md +114 -0
- package/template/docs/data-views-pattern.md +12 -4
- package/template/docs/drawer-vs-dialog-pattern.md +50 -0
- package/template/docs/kpi-strip-max-four-pattern.md +29 -0
- package/template/docs/kpi-trend-pattern.md +43 -0
- package/template/fontawesome-subset.manifest.json +2 -2
- package/template/hooks/use-location-hash.ts +14 -8
- package/template/hooks/use-secondary-panel-hub-nav.ts +98 -0
- package/template/lib/ask-leo-route-context.ts +24 -0
- package/template/lib/collaborator-access.ts +92 -0
- package/template/lib/command-menu-config.ts +8 -1
- package/template/lib/command-menu-search-data.ts +11 -8
- package/template/lib/data-list-display-options.ts +1 -1
- package/template/lib/data-view-dashboard-placements-layout.ts +215 -0
- package/template/lib/date-filter.ts +1 -0
- package/template/lib/dedicated-search-recents.ts +76 -0
- package/template/lib/dedicated-search-url.ts +23 -0
- package/template/lib/discovery-hub.ts +15 -0
- package/template/lib/list-status-badges.ts +1 -21
- package/template/lib/mock/navigation.tsx +4 -2
- package/template/lib/mock/placements.ts +9 -9
- package/template/lib/mock/question-bank-folders.ts +7 -0
- package/template/lib/mock/question-bank-header-collaborators.ts +45 -5
- package/template/lib/mock/question-bank-inspector.ts +1 -2
- package/template/lib/mock/question-bank-kpi.ts +38 -26
- package/template/lib/mock/question-bank.ts +43 -16
- package/template/lib/question-bank-dedicated-search.ts +19 -0
- package/template/lib/question-bank-hub-search.ts +90 -0
- package/template/lib/question-bank-nav.ts +322 -6
- package/template/lib/question-bank-recent-searches.ts +22 -0
- package/template/package.json +0 -1
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
saveDashboardLayout,
|
|
27
27
|
type ChartType,
|
|
28
28
|
type DashboardLayout,
|
|
29
|
-
} from "@/
|
|
29
|
+
} from "@/lib/data-view-dashboard-placements-layout"
|
|
30
30
|
import { CoachMark } from "@/components/ui/coach-mark"
|
|
31
31
|
import { useCoachMark } from "@/hooks/use-coach-mark"
|
|
32
32
|
import { DASHBOARD_CUSTOMIZE_COACH_STEPS } from "@/lib/dashboard-customize-coach-mark"
|
|
@@ -502,7 +502,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
502
502
|
Add filter
|
|
503
503
|
</button>
|
|
504
504
|
</DropdownMenuTrigger>
|
|
505
|
-
<DropdownMenuContent align="start"
|
|
505
|
+
<DropdownMenuContent align="start">
|
|
506
506
|
<DropdownMenuLabel className="text-xs">Filter by field</DropdownMenuLabel>
|
|
507
507
|
<DropdownMenuSeparator />
|
|
508
508
|
{filterableCols.map(c => (
|
|
@@ -615,7 +615,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
615
615
|
<i className="fa-light fa-filter text-[13px]" aria-hidden="true" />
|
|
616
616
|
</button>
|
|
617
617
|
</DropdownMenuTrigger>
|
|
618
|
-
<DropdownMenuContent align="end"
|
|
618
|
+
<DropdownMenuContent align="end">
|
|
619
619
|
<DropdownMenuLabel className="text-xs">Filter by field</DropdownMenuLabel>
|
|
620
620
|
<DropdownMenuSeparator />
|
|
621
621
|
{filterableCols.map(c => (
|
|
@@ -1173,7 +1173,7 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1173
1173
|
</button>
|
|
1174
1174
|
</DropdownMenuTrigger>
|
|
1175
1175
|
</Tip>
|
|
1176
|
-
<DropdownMenuContent align="start"
|
|
1176
|
+
<DropdownMenuContent align="start">
|
|
1177
1177
|
|
|
1178
1178
|
{/* Column quick-search */}
|
|
1179
1179
|
<div className="px-2 pt-2 pb-1">
|
|
@@ -1234,14 +1234,14 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1234
1234
|
const filtered = prev.filter(r => r.fieldKey !== col.key)
|
|
1235
1235
|
return [{ id: `sort-${Date.now()}`, fieldKey: col.key, direction: "asc" as const }, ...filtered]
|
|
1236
1236
|
})}>
|
|
1237
|
-
<i className="fa-light fa-arrow-up-
|
|
1237
|
+
<i className="fa-light fa-arrow-up-a-z text-xs shrink-0" aria-hidden="true" />
|
|
1238
1238
|
Sort Ascending
|
|
1239
1239
|
</DropdownMenuItem>
|
|
1240
1240
|
<DropdownMenuItem onClick={() => setSortRules(prev => {
|
|
1241
1241
|
const filtered = prev.filter(r => r.fieldKey !== col.key)
|
|
1242
1242
|
return [{ id: `sort-${Date.now()}`, fieldKey: col.key, direction: "desc" as const }, ...filtered]
|
|
1243
1243
|
})}>
|
|
1244
|
-
<i className="fa-light fa-arrow-down-
|
|
1244
|
+
<i className="fa-light fa-arrow-down-a-z text-xs shrink-0" aria-hidden="true" />
|
|
1245
1245
|
Sort Descending
|
|
1246
1246
|
</DropdownMenuItem>
|
|
1247
1247
|
<DropdownMenuSeparator />
|
|
@@ -91,6 +91,11 @@ export function useTableState<TData extends Record<string, unknown>>(
|
|
|
91
91
|
columns: ColumnDef<TData>[],
|
|
92
92
|
defaultSort?: { key: string; dir: SortDir },
|
|
93
93
|
paginationOverride?: { page: number; pageSize: number },
|
|
94
|
+
/**
|
|
95
|
+
* When defined (including `""`), toolbar search is synced from the URL (`?q=`).
|
|
96
|
+
* Use `searchParams.get("q") ?? ""` on question bank list routes; omit for other hubs.
|
|
97
|
+
*/
|
|
98
|
+
syncedSearchFromUrl?: string,
|
|
94
99
|
) {
|
|
95
100
|
// ── Sort ──────────────────────────────────────────────────────────────────
|
|
96
101
|
const [sortRules, setSortRules] = React.useState<SortRule[]>(() => {
|
|
@@ -133,8 +138,12 @@ export function useTableState<TData extends Record<string, unknown>>(
|
|
|
133
138
|
}, [setSortRules])
|
|
134
139
|
|
|
135
140
|
// ── Filters ───────────────────────────────────────────────────────────────
|
|
136
|
-
const [search, setSearch] = React.useState(
|
|
137
|
-
|
|
141
|
+
const [search, setSearch] = React.useState(() =>
|
|
142
|
+
syncedSearchFromUrl !== undefined ? syncedSearchFromUrl.trim() : "",
|
|
143
|
+
)
|
|
144
|
+
const [searchOpen, setSearchOpen] = React.useState(() =>
|
|
145
|
+
syncedSearchFromUrl !== undefined && Boolean(syncedSearchFromUrl.trim()),
|
|
146
|
+
)
|
|
138
147
|
const searchRef = React.useRef<HTMLInputElement>(null)
|
|
139
148
|
const [activeFilters, setActiveFilters] = React.useState<ActiveFilter[]>([])
|
|
140
149
|
const [filterConnectors, setFilterConnectors] = React.useState<Record<string, "and" | "or">>({})
|
|
@@ -142,6 +151,13 @@ export function useTableState<TData extends Record<string, unknown>>(
|
|
|
142
151
|
const [filterBarVisible, setFilterBarVisible] = React.useState(true)
|
|
143
152
|
const [drawerExpandedFilters, setDrawerExpandedFilters] = React.useState<Set<string>>(new Set())
|
|
144
153
|
|
|
154
|
+
React.useEffect(() => {
|
|
155
|
+
if (syncedSearchFromUrl === undefined) return
|
|
156
|
+
const next = syncedSearchFromUrl.trim()
|
|
157
|
+
setSearch(next)
|
|
158
|
+
setSearchOpen(next.length > 0)
|
|
159
|
+
}, [syncedSearchFromUrl])
|
|
160
|
+
|
|
145
161
|
const toggleConnector = React.useCallback((leftId: string) => {
|
|
146
162
|
setFilterConnectors(prev => ({ ...prev, [leftId]: prev[leftId] === "or" ? "and" : "or" }))
|
|
147
163
|
}, [setFilterConnectors])
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
applyVisibleReorder,
|
|
57
57
|
type ChartType,
|
|
58
58
|
type DashboardLayout,
|
|
59
|
-
} from "@/
|
|
59
|
+
} from "@/lib/data-view-dashboard-placements-layout"
|
|
60
60
|
import {
|
|
61
61
|
CHART_KBD_ACTIVE_BAR,
|
|
62
62
|
CHART_KBD_ACTIVE_PIE_SHAPE,
|
|
@@ -74,6 +74,9 @@ const CAT_CFG: ChartConfig = {
|
|
|
74
74
|
value: { label: "Items", color: "var(--primary)" },
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
const CHART_MARGIN = { top: 8, right: 8, left: 0, bottom: 0 } as const
|
|
78
|
+
const CHART_MARGIN_HORIZONTAL = { top: 8, right: 8, left: 4, bottom: 0 } as const
|
|
79
|
+
|
|
77
80
|
interface ComplianceDashboardCardDef {
|
|
78
81
|
id: string
|
|
79
82
|
title: string
|
|
@@ -254,7 +257,7 @@ function ComplianceByStatusChart({ rows, chartType }: { rows: ComplianceItem[];
|
|
|
254
257
|
{(activeIndex) => (
|
|
255
258
|
<>
|
|
256
259
|
<ChartContainer config={STATUS_CFG} className="h-[220px] w-full">
|
|
257
|
-
<BarChart data={byStatus} layout="vertical" margin={
|
|
260
|
+
<BarChart data={byStatus} layout="vertical" margin={CHART_MARGIN}>
|
|
258
261
|
<CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
|
|
259
262
|
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
260
263
|
<YAxis type="category" dataKey="name" width={88} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
@@ -289,7 +292,7 @@ function ComplianceByStatusChart({ rows, chartType }: { rows: ComplianceItem[];
|
|
|
289
292
|
{(activeIndex) => (
|
|
290
293
|
<>
|
|
291
294
|
<ChartContainer config={STATUS_CFG} className="h-[220px] w-full">
|
|
292
|
-
<BarChart data={byStatus} margin={
|
|
295
|
+
<BarChart data={byStatus} margin={CHART_MARGIN}>
|
|
293
296
|
<CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
|
|
294
297
|
<XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
295
298
|
<YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
@@ -374,7 +377,7 @@ function ComplianceByCategoryChart({ rows, chartType }: { rows: ComplianceItem[]
|
|
|
374
377
|
{(activeIndex) => (
|
|
375
378
|
<>
|
|
376
379
|
<ChartContainer config={CAT_CFG} className="h-[220px] w-full">
|
|
377
|
-
<BarChart data={byCategory} margin={
|
|
380
|
+
<BarChart data={byCategory} margin={CHART_MARGIN}>
|
|
378
381
|
<CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
|
|
379
382
|
<XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
380
383
|
<YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
@@ -409,7 +412,7 @@ function ComplianceByCategoryChart({ rows, chartType }: { rows: ComplianceItem[]
|
|
|
409
412
|
{(activeIndex) => (
|
|
410
413
|
<>
|
|
411
414
|
<ChartContainer config={CAT_CFG} className="h-[220px] w-full">
|
|
412
|
-
<BarChart data={byCategory} layout="vertical" margin={
|
|
415
|
+
<BarChart data={byCategory} layout="vertical" margin={CHART_MARGIN_HORIZONTAL}>
|
|
413
416
|
<CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
|
|
414
417
|
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
415
418
|
<YAxis type="category" dataKey="name" width={100} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
applyVisibleReorder,
|
|
58
58
|
type ChartType,
|
|
59
59
|
type DashboardLayout,
|
|
60
|
-
} from "@/
|
|
60
|
+
} from "@/lib/data-view-dashboard-placements-layout"
|
|
61
61
|
import {
|
|
62
62
|
CHART_KBD_ACTIVE_BAR,
|
|
63
63
|
CHART_KBD_ACTIVE_PIE_SHAPE,
|
|
@@ -75,6 +75,9 @@ const ROLE_CHART_CFG: ChartConfig = {
|
|
|
75
75
|
value: { label: "Members", color: "var(--primary)" },
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
const CHART_MARGIN = { top: 8, right: 8, left: 0, bottom: 0 } as const
|
|
79
|
+
const CHART_MARGIN_HORIZONTAL = { top: 8, right: 8, left: 4, bottom: 0 } as const
|
|
80
|
+
|
|
78
81
|
interface TeamDashboardCardDef {
|
|
79
82
|
id: string
|
|
80
83
|
title: string
|
|
@@ -255,7 +258,7 @@ function TeamByStatusChart({ members, chartType }: { members: TeamMember[]; char
|
|
|
255
258
|
{(activeIndex) => (
|
|
256
259
|
<>
|
|
257
260
|
<ChartContainer config={STATUS_CHART_CFG} className="h-[220px] w-full">
|
|
258
|
-
<BarChart data={byStatus} layout="vertical" margin={
|
|
261
|
+
<BarChart data={byStatus} layout="vertical" margin={CHART_MARGIN}>
|
|
259
262
|
<CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
|
|
260
263
|
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
261
264
|
<YAxis type="category" dataKey="name" width={72} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
@@ -290,7 +293,7 @@ function TeamByStatusChart({ members, chartType }: { members: TeamMember[]; char
|
|
|
290
293
|
{(activeIndex) => (
|
|
291
294
|
<>
|
|
292
295
|
<ChartContainer config={STATUS_CHART_CFG} className="h-[220px] w-full">
|
|
293
|
-
<BarChart data={byStatus} margin={
|
|
296
|
+
<BarChart data={byStatus} margin={CHART_MARGIN}>
|
|
294
297
|
<CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
|
|
295
298
|
<XAxis dataKey="name" tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
296
299
|
<YAxis allowDecimals={false} width={36} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
@@ -382,7 +385,7 @@ function TeamByRoleChart({ members, chartType }: { members: TeamMember[]; chartT
|
|
|
382
385
|
{(activeIndex) => (
|
|
383
386
|
<>
|
|
384
387
|
<ChartContainer config={ROLE_CHART_CFG} className="h-[220px] w-full">
|
|
385
|
-
<BarChart data={byRole} margin={
|
|
388
|
+
<BarChart data={byRole} margin={CHART_MARGIN}>
|
|
386
389
|
<CartesianGrid vertical={false} strokeDasharray="3 3" className="stroke-border" />
|
|
387
390
|
<XAxis dataKey="name" tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|
|
388
391
|
<YAxis allowDecimals={false} width={32} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
@@ -417,7 +420,7 @@ function TeamByRoleChart({ members, chartType }: { members: TeamMember[]; chartT
|
|
|
417
420
|
{(activeIndex) => (
|
|
418
421
|
<>
|
|
419
422
|
<ChartContainer config={ROLE_CHART_CFG} className="h-[220px] w-full">
|
|
420
|
-
<BarChart data={byRole} layout="vertical" margin={
|
|
423
|
+
<BarChart data={byRole} layout="vertical" margin={CHART_MARGIN_HORIZONTAL}>
|
|
421
424
|
<CartesianGrid horizontal={false} strokeDasharray="3 3" className="stroke-border" />
|
|
422
425
|
<XAxis type="number" allowDecimals={false} tick={{ fontSize: 12 }} tickLine={false} axisLine={false} />
|
|
423
426
|
<YAxis type="category" dataKey="name" width={120} tick={{ fontSize: 11 }} tickLine={false} axisLine={false} />
|