@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.
- package/CHANGELOG.md +701 -6
- package/README.md +138 -0
- package/bin/init.mjs +134 -31
- package/consumer-extras/cursor-rules/exxat-board-cards.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-centralized-list-dataset.mdc +2 -2
- package/consumer-extras/cursor-rules/exxat-collaboration-access.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-data-tables.mdc +2 -0
- package/consumer-extras/cursor-rules/exxat-dedicated-search-surfaces.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-ds-agents.mdc +3 -3
- package/consumer-extras/cursor-rules/exxat-library-hub-header.mdc +28 -0
- package/consumer-extras/cursor-rules/exxat-mono-ids.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-person-identity-display.mdc +1 -1
- package/consumer-extras/cursor-rules/exxat-primary-nav-secondary-panel.mdc +6 -6
- package/consumer-extras/cursor-rules/exxat-reuse-before-custom.mdc +1 -1
- package/consumer-extras/cursor-skills/exxat-board-cards/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -3
- package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +2 -2
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +7 -7
- package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-list-page-view-shells/SKILL.md +1 -1
- package/consumer-extras/cursor-skills/exxat-mono-ids/SKILL.md +4 -4
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +8 -8
- package/consumer-extras/cursor-skills/exxat-token-economy/SKILL.md +277 -0
- package/consumer-extras/handbook/HANDBOOK.md +2 -0
- package/consumer-extras/handbook/glossary.md +2 -1
- package/consumer-extras/handbook/reference-implementations.md +31 -4
- package/consumer-extras/patterns/collaboration-access-pattern.md +7 -7
- package/consumer-extras/patterns/data-views-pattern.md +18 -16
- package/consumer-extras/patterns/kpi-flat-band-pattern.md +2 -2
- package/dist/components/data-table/index.js +2 -2
- package/dist/components/data-table/index.js.map +1 -1
- package/dist/components/data-table/pagination.js +3 -3
- package/dist/components/data-table/pagination.js.map +1 -1
- package/dist/components/data-table/use-table-state.d.ts +1 -1
- package/dist/components/data-table/use-table-state.js.map +1 -1
- package/dist/components/data-views/data-row-list.js.map +1 -1
- package/dist/components/data-views/finder-panel-view.d.ts +1 -1
- package/dist/components/data-views/finder-panel-view.js.map +1 -1
- package/dist/components/data-views/hub-table.d.ts +9 -3
- package/dist/components/data-views/hub-table.js +262 -40
- package/dist/components/data-views/hub-table.js.map +1 -1
- package/dist/components/data-views/index.js +262 -40
- package/dist/components/data-views/index.js.map +1 -1
- package/dist/components/data-views/list-page-split-hub-tokens.d.ts +2 -2
- package/dist/components/data-views/list-page-split-hub-tokens.js.map +1 -1
- package/dist/components/data-views/list-page-tree-column-header.d.ts +1 -1
- package/dist/components/data-views/list-page-tree-column-header.js.map +1 -1
- package/dist/components/data-views/list-page-tree-panel-shell.js.map +1 -1
- package/dist/components/data-views/os-folder-glyph.d.ts +1 -1
- package/dist/components/data-views/os-folder-glyph.js.map +1 -1
- package/dist/components/ui/avatar.d.ts +1 -1
- package/dist/components/ui/key-metrics.js.map +1 -1
- package/dist/index.js +136 -39
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/components/data-table/index.tsx +2 -2
- package/src/components/data-table/pagination.tsx +5 -1
- package/src/components/data-table/use-table-state.ts +1 -1
- package/src/components/data-views/data-row-list.tsx +1 -1
- package/src/components/data-views/finder-panel-view.tsx +2 -2
- package/src/components/data-views/hub-table.tsx +149 -41
- package/src/components/data-views/list-page-split-hub-tokens.ts +2 -2
- package/src/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/src/components/data-views/os-folder-glyph.tsx +1 -1
- package/src/components/ui/key-metrics.tsx +1 -1
- package/template/.claude/skills/exxat-ds-skill/SKILL.md +8 -7
- package/template/.cursor/rules/exxat-accessibility.mdc +1 -1
- package/template/.cursor/rules/exxat-command-menu.mdc +1 -1
- package/template/.cursor/rules/exxat-dashboard-view-charts.mdc +6 -6
- package/template/.cursor/rules/exxat-data-tables.mdc +3 -3
- package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +5 -5
- package/template/.cursor/rules/exxat-mono-ids.mdc +1 -1
- package/template/.cursor/rules/exxat-page-vs-drawer.mdc +1 -1
- package/template/.cursor/rules/exxat-table-properties-drawer.mdc +1 -1
- package/template/AGENTS.md +43 -37
- package/template/app/(app)/columns/page.tsx +11 -0
- package/template/app/(app)/library/all/page.tsx +11 -0
- package/template/app/(app)/library/find/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/layout.tsx +16 -16
- package/template/app/(app)/library/list/page.tsx +12 -0
- package/template/app/(app)/{question-bank → library}/new/page.tsx +10 -10
- package/template/app/(app)/library/page.tsx +11 -0
- package/template/app/(app)/tokens-themes/page.tsx +11 -0
- package/template/components/ask-leo-composer.tsx +2 -2
- package/template/components/columns-client.tsx +158 -0
- package/template/components/columns-showcase.tsx +541 -0
- package/template/components/data-views/index.ts +32 -6
- package/template/components/data-views/{question-bank-folder-tree-branch.tsx → library-folder-tree-branch.tsx} +19 -19
- package/template/components/data-views/table-cells.tsx +673 -0
- package/template/components/folder-details-shell.tsx +11 -11
- package/template/components/hub-tree-panel-view.tsx +24 -24
- package/template/components/{question-bank-board-view.tsx → library-board-view.tsx} +44 -44
- package/template/components/{question-bank-client.tsx → library-client.tsx} +82 -82
- package/template/components/{question-bank-dashboard-charts.tsx → library-dashboard-charts.tsx} +14 -14
- package/template/components/{question-bank-favorite-button.tsx → library-favorite-button.tsx} +7 -7
- package/template/components/{question-bank-hub-client.tsx → library-hub-client.tsx} +43 -43
- package/template/components/{question-bank-new-folder-sheet.tsx → library-new-folder-sheet.tsx} +14 -14
- package/template/components/{question-bank-os-folder-view.tsx → library-os-folder-view.tsx} +31 -31
- package/template/components/{question-bank-page-header.tsx → library-page-header.tsx} +6 -6
- package/template/components/library-panel-activator.tsx +8 -0
- package/template/components/{question-bank-secondary-nav.tsx → library-secondary-nav.tsx} +60 -60
- package/template/components/{question-bank-table.tsx → library-table.tsx} +97 -97
- package/template/components/list-hub-status-badge.tsx +2 -2
- package/template/components/{new-question-composer.tsx → new-library-item-form.tsx} +37 -37
- package/template/components/sidebar/app-sidebar.tsx +61 -5
- package/template/components/sidebar/secondary-panel.tsx +109 -56
- package/template/components/sidebar/sidebar-auto-collapse.tsx +2 -2
- package/template/components/sidebar/sidebar-auto-open.tsx +2 -1
- package/template/components/table-properties/types.ts +1 -1
- package/template/components/templates/discovery-hub-template.tsx +1 -1
- package/template/components/templates/new-focus-template.tsx +2 -2
- package/template/components/templates/secondary-panel-hub-template.tsx +1 -1
- package/template/components/tokens-secondary-nav.tsx +192 -0
- package/template/components/tokens-themes-client.tsx +476 -0
- package/template/components/tokens-themes-section.tsx +386 -0
- package/template/docs/HANDBOOK.md +187 -0
- package/template/docs/blueprints/README.md +1 -1
- package/template/docs/blueprints/board-card.md +1 -1
- package/template/docs/blueprints/data-table.md +2 -2
- package/template/docs/blueprints/list-page-template.md +3 -3
- package/template/docs/blueprints/page-header.md +4 -4
- package/template/docs/collaboration-access-pattern.md +7 -7
- package/template/docs/component-selection-guide.md +1 -1
- package/template/docs/data-views-pattern.md +18 -16
- package/template/docs/glossary.md +58 -0
- package/template/docs/kpi-flat-band-pattern.md +3 -3
- package/template/docs/kpi-trend-pattern.md +18 -3
- package/template/docs/large-dataset-strategy.md +155 -0
- package/template/docs/library-hub-header-pattern.md +25 -0
- package/template/docs/migrations/_template.md +1 -1
- package/template/docs/reference-implementations.md +151 -0
- package/template/docs/token-taxonomy.md +1 -1
- package/template/docs/voice-and-tone.md +262 -0
- package/template/eslint.config.mjs +9 -39
- package/template/hooks/use-secondary-panel-hub-nav.ts +10 -10
- package/template/lib/ask-leo-route-context.ts +6 -18
- package/template/lib/coach-mark-registry.ts +0 -16
- package/template/lib/command-menu-config.ts +5 -12
- package/template/lib/command-menu-search-data.ts +8 -39
- package/template/lib/{question-bank-authoring.ts → library-authoring.ts} +89 -88
- package/template/lib/library-dedicated-search.ts +19 -0
- package/template/lib/library-hub-search.ts +90 -0
- package/template/lib/library-nav.ts +477 -0
- package/template/lib/library-recent-searches.ts +22 -0
- package/template/lib/{placements-supported-views.ts → library-supported-views.ts} +2 -2
- package/template/lib/list-status-badges.ts +16 -104
- package/template/lib/mock/dashboard.ts +1 -1
- package/template/lib/mock/{question-bank-folders.ts → library-folders.ts} +30 -30
- package/template/lib/mock/library-header-collaborators.ts +54 -0
- package/template/lib/mock/{question-bank-inspector.ts → library-inspector.ts} +29 -29
- package/template/lib/mock/{question-bank-kpi.ts → library-kpi.ts} +20 -20
- package/template/lib/mock/library.ts +249 -0
- package/template/lib/mock/navigation.tsx +32 -26
- package/template/lib/table-state-lifecycle.ts +1 -1
- package/template/next.config.mjs +7 -4
- package/template/package.json +0 -1
- package/tokens/hooks-index.json +2874 -0
- package/consumer-extras/cursor-rules/exxat-question-bank-hub-header.mdc +0 -28
- package/template/app/(app)/examples/page.tsx +0 -41
- package/template/app/(app)/question-bank/find/page.tsx +0 -12
- package/template/app/(app)/question-bank/library/page.tsx +0 -11
- package/template/app/(app)/question-bank/list/page.tsx +0 -12
- package/template/app/(app)/question-bank/page.tsx +0 -11
- package/template/components/compliance-board-view.tsx +0 -142
- package/template/components/compliance-client.tsx +0 -92
- package/template/components/compliance-page-header.tsx +0 -89
- package/template/components/compliance-table.tsx +0 -468
- package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
- package/template/components/data-view-dashboard-charts-team.tsx +0 -971
- package/template/components/data-view-dashboard-charts.tsx +0 -1503
- package/template/components/new-placement-back-btn.tsx +0 -28
- package/template/components/new-placement-form.tsx +0 -942
- package/template/components/placement-board-card.tsx +0 -250
- package/template/components/placement-detail.tsx +0 -438
- package/template/components/placements-board-view.tsx +0 -397
- package/template/components/placements-client.tsx +0 -220
- package/template/components/placements-list-view.tsx +0 -124
- package/template/components/placements-page-header.tsx +0 -166
- package/template/components/placements-table-cells.test.tsx +0 -22
- package/template/components/placements-table-cells.tsx +0 -173
- package/template/components/placements-table-columns.tsx +0 -210
- package/template/components/placements-table.tsx +0 -934
- package/template/components/question-bank-panel-activator.tsx +0 -8
- package/template/components/rotations-empty-state.tsx +0 -50
- package/template/components/rotations-panel-activator.tsx +0 -8
- package/template/components/sites-board-view.tsx +0 -67
- package/template/components/sites-client.tsx +0 -154
- package/template/components/sites-table.tsx +0 -249
- package/template/components/team-board-view.tsx +0 -122
- package/template/components/team-client.tsx +0 -100
- package/template/components/team-page-header.tsx +0 -92
- package/template/components/team-table.tsx +0 -553
- package/template/docs/question-bank-hub-header-pattern.md +0 -25
- package/template/lib/compliance-supported-views.ts +0 -10
- package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
- package/template/lib/mock/compliance-kpi.ts +0 -61
- package/template/lib/mock/compliance.ts +0 -146
- package/template/lib/mock/placements-kpi.ts +0 -134
- package/template/lib/mock/placements.ts +0 -176
- package/template/lib/mock/question-bank-header-collaborators.ts +0 -54
- package/template/lib/mock/question-bank.ts +0 -249
- package/template/lib/mock/sites-directory.ts +0 -16
- package/template/lib/mock/sites-kpi.ts +0 -25
- package/template/lib/mock/team-kpi.ts +0 -60
- package/template/lib/mock/team.ts +0 -118
- package/template/lib/placement-board-card-layout.ts +0 -79
- package/template/lib/question-bank-dedicated-search.ts +0 -19
- package/template/lib/question-bank-hub-search.ts +0 -90
- package/template/lib/question-bank-nav.ts +0 -477
- package/template/lib/question-bank-recent-searches.ts +0 -22
- package/template/lib/question-bank-supported-views.ts +0 -12
- package/template/lib/sites-supported-views.ts +0 -10
- package/template/lib/team-supported-views.ts +0 -10
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tokens secondary navigation — the body of the `tokens` panel.
|
|
5
|
+
*
|
|
6
|
+
* Same shape as `LibrarySecondaryNav` but simpler — flat list of
|
|
7
|
+
* categories. URL scope: `?category=color|gradient|radius|…` (default = `all`).
|
|
8
|
+
*
|
|
9
|
+
* Compact mode (icon rail) and expanded mode share the same active-state
|
|
10
|
+
* logic. Clicking the active row while already on the same href reopens the
|
|
11
|
+
* panel (in case it was collapsed) — matching the Library "All
|
|
12
|
+
* questions" reopen behavior.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as React from "react"
|
|
16
|
+
import Link from "next/link"
|
|
17
|
+
import { usePathname, useSearchParams } from "next/navigation"
|
|
18
|
+
|
|
19
|
+
import { Tip } from "@/components/ui/tip"
|
|
20
|
+
import { cn } from "@/lib/utils"
|
|
21
|
+
import { useSecondaryPanel } from "@/components/sidebar"
|
|
22
|
+
import {
|
|
23
|
+
CATEGORY_TABS,
|
|
24
|
+
CATEGORY_COUNTS,
|
|
25
|
+
TOKENS_INDEX,
|
|
26
|
+
type TokenCategory,
|
|
27
|
+
} from "@/components/tokens-themes-section"
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* URL value for "show everything". Centralized so consumers (panel + client +
|
|
31
|
+
* page header subtitle) all agree on the canonical default.
|
|
32
|
+
*/
|
|
33
|
+
export const TOKENS_ALL_CATEGORY = "all" as const
|
|
34
|
+
|
|
35
|
+
export type TokensCategoryParam = "all" | TokenCategory
|
|
36
|
+
|
|
37
|
+
/** Read the active category from a `URLSearchParams`. Falls back to `"all"`. */
|
|
38
|
+
export function readTokensCategory(params: URLSearchParams | null): TokensCategoryParam {
|
|
39
|
+
const raw = (params?.get("category") ?? "").toLowerCase()
|
|
40
|
+
if (raw === TOKENS_ALL_CATEGORY) return TOKENS_ALL_CATEGORY
|
|
41
|
+
const match = CATEGORY_TABS.find((c) => c.id === raw)
|
|
42
|
+
return match ? (match.id as TokenCategory) : TOKENS_ALL_CATEGORY
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface CategoryEntry {
|
|
46
|
+
id: TokensCategoryParam
|
|
47
|
+
label: string
|
|
48
|
+
icon: string
|
|
49
|
+
count: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const CATEGORY_ENTRIES: CategoryEntry[] = [
|
|
53
|
+
{
|
|
54
|
+
id: TOKENS_ALL_CATEGORY,
|
|
55
|
+
label: "All tokens",
|
|
56
|
+
icon: "fa-grid-2",
|
|
57
|
+
count: TOKENS_INDEX.tokenCount,
|
|
58
|
+
},
|
|
59
|
+
...CATEGORY_TABS.filter((c) => CATEGORY_COUNTS[c.id] > 0).map((c) => ({
|
|
60
|
+
id: c.id as TokensCategoryParam,
|
|
61
|
+
label: c.label,
|
|
62
|
+
icon: c.icon,
|
|
63
|
+
count: CATEGORY_COUNTS[c.id],
|
|
64
|
+
})),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
/** Build `?category=…` URL preserving the current pathname (tokens hub). */
|
|
68
|
+
function tokensCategoryHref(pathname: string, id: TokensCategoryParam): string {
|
|
69
|
+
if (id === TOKENS_ALL_CATEGORY) return pathname
|
|
70
|
+
return `${pathname}?category=${encodeURIComponent(id)}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function CategoryRow({
|
|
74
|
+
entry,
|
|
75
|
+
active,
|
|
76
|
+
onActiveClick,
|
|
77
|
+
}: {
|
|
78
|
+
entry: CategoryEntry
|
|
79
|
+
active: boolean
|
|
80
|
+
onActiveClick: () => void
|
|
81
|
+
}) {
|
|
82
|
+
const pathname = usePathname()
|
|
83
|
+
const href = tokensCategoryHref(pathname, entry.id)
|
|
84
|
+
return (
|
|
85
|
+
<li className="min-w-0">
|
|
86
|
+
<Link
|
|
87
|
+
href={href}
|
|
88
|
+
scroll={false}
|
|
89
|
+
onClick={() => { if (active) onActiveClick() }}
|
|
90
|
+
aria-current={active ? "page" : undefined}
|
|
91
|
+
className={cn(
|
|
92
|
+
"flex w-full min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors",
|
|
93
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
94
|
+
active
|
|
95
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
|
|
96
|
+
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<span className="size-4 shrink-0 text-center text-[13px] leading-none" aria-hidden="true">
|
|
100
|
+
<i className={cn(active ? "fa-solid" : "fa-light", entry.icon)} aria-hidden="true" />
|
|
101
|
+
</span>
|
|
102
|
+
<span className="min-w-0 flex-1 truncate">{entry.label}</span>
|
|
103
|
+
<span
|
|
104
|
+
className={cn(
|
|
105
|
+
"shrink-0 rounded-sm px-1.5 py-0.5 text-[10px] font-medium tabular-nums",
|
|
106
|
+
active ? "bg-foreground/10 text-sidebar-accent-foreground" : "bg-muted/60 text-muted-foreground",
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
{entry.count}
|
|
110
|
+
</span>
|
|
111
|
+
</Link>
|
|
112
|
+
</li>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function IconCategoryRow({
|
|
117
|
+
entry,
|
|
118
|
+
active,
|
|
119
|
+
onActiveClick,
|
|
120
|
+
}: {
|
|
121
|
+
entry: CategoryEntry
|
|
122
|
+
active: boolean
|
|
123
|
+
onActiveClick: () => void
|
|
124
|
+
}) {
|
|
125
|
+
const pathname = usePathname()
|
|
126
|
+
const href = tokensCategoryHref(pathname, entry.id)
|
|
127
|
+
return (
|
|
128
|
+
<li className="flex w-full justify-center" role="none">
|
|
129
|
+
<Tip label={`${entry.label} (${entry.count})`} side="right">
|
|
130
|
+
<Link
|
|
131
|
+
href={href}
|
|
132
|
+
scroll={false}
|
|
133
|
+
onClick={() => { if (active) onActiveClick() }}
|
|
134
|
+
aria-current={active ? "page" : undefined}
|
|
135
|
+
aria-label={entry.label}
|
|
136
|
+
className={cn(
|
|
137
|
+
"flex size-9 shrink-0 items-center justify-center rounded-md transition-colors",
|
|
138
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
139
|
+
active
|
|
140
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
141
|
+
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
<span className="text-center text-[15px] leading-none" aria-hidden="true">
|
|
145
|
+
<i className={cn(active ? "fa-solid" : "fa-light", entry.icon)} aria-hidden="true" />
|
|
146
|
+
</span>
|
|
147
|
+
</Link>
|
|
148
|
+
</Tip>
|
|
149
|
+
</li>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function TokensSecondaryNav() {
|
|
154
|
+
const searchParams = useSearchParams()
|
|
155
|
+
const searchParamsKey = searchParams.toString()
|
|
156
|
+
const { openPanel, secondaryPanelCompact } = useSecondaryPanel()
|
|
157
|
+
|
|
158
|
+
const active = React.useMemo(
|
|
159
|
+
() => readTokensCategory(new URLSearchParams(searchParamsKey)),
|
|
160
|
+
[searchParamsKey],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
const onActiveClick = React.useCallback(() => openPanel("tokens"), [openPanel])
|
|
164
|
+
|
|
165
|
+
if (secondaryPanelCompact) {
|
|
166
|
+
return (
|
|
167
|
+
<ul className="flex flex-col gap-1 px-1 py-3" role="list">
|
|
168
|
+
{CATEGORY_ENTRIES.map((entry) => (
|
|
169
|
+
<IconCategoryRow
|
|
170
|
+
key={entry.id}
|
|
171
|
+
entry={entry}
|
|
172
|
+
active={entry.id === active}
|
|
173
|
+
onActiveClick={onActiveClick}
|
|
174
|
+
/>
|
|
175
|
+
))}
|
|
176
|
+
</ul>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<ul className="flex flex-col gap-0.5 px-3 pb-4" role="list">
|
|
182
|
+
{CATEGORY_ENTRIES.map((entry) => (
|
|
183
|
+
<CategoryRow
|
|
184
|
+
key={entry.id}
|
|
185
|
+
entry={entry}
|
|
186
|
+
active={entry.id === active}
|
|
187
|
+
onActiveClick={onActiveClick}
|
|
188
|
+
/>
|
|
189
|
+
))}
|
|
190
|
+
</ul>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tokens & themes — hub client.
|
|
5
|
+
*
|
|
6
|
+
* Composition mirrors `Library → Library` and matches every other
|
|
7
|
+
* primary hub (Placements / Team / Sites / Compliance):
|
|
8
|
+
* - **Secondary panel** (`tokens`) — category scope lives in the rail
|
|
9
|
+
* (Colors, Radius, Motion, …) via `TokensSecondaryNav`. Opening the panel
|
|
10
|
+
* also collapses the main sidebar via `secondary-panel.tsx#openPanel`.
|
|
11
|
+
* - `PrimaryPageTemplate` + `ListPageTemplate` — same hub frame as
|
|
12
|
+
* Placements / Library.
|
|
13
|
+
* - **`HubTable`** (NOT raw `<DataTable>`) — the canonical primitive that
|
|
14
|
+
* wires `useTableState`, the toolbar (search + filter chips + filter
|
|
15
|
+
* dropdown + sort), `TablePropertiesDrawerButton`, view-type tiles,
|
|
16
|
+
* bulk-actions, and conditional rules. Hubs that drop down to raw
|
|
17
|
+
* `<DataTable>` silently lose filters and Properties; do not do that.
|
|
18
|
+
* - One view tab (`viewType: "table"`) — category scope is the panel's
|
|
19
|
+
* job, not the view tabs'.
|
|
20
|
+
*
|
|
21
|
+
* Token index (`packages/ui/tokens/hooks-index.json`) is the single source of
|
|
22
|
+
* truth; visualizers live in `tokens-themes-section.tsx`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import * as React from "react"
|
|
26
|
+
import { useRouter, useSearchParams } from "next/navigation"
|
|
27
|
+
|
|
28
|
+
import { PrimaryPageTemplate } from "@/components/templates/primary-page-template"
|
|
29
|
+
import { PageHeader } from "@/components/page-header"
|
|
30
|
+
import {
|
|
31
|
+
KeyMetrics,
|
|
32
|
+
type MetricInsight,
|
|
33
|
+
type MetricItem,
|
|
34
|
+
} from "@/components/key-metrics"
|
|
35
|
+
import {
|
|
36
|
+
HubTable,
|
|
37
|
+
ListPageTemplate,
|
|
38
|
+
type ViewTab,
|
|
39
|
+
} from "@/components/data-views"
|
|
40
|
+
import type { ColumnDef } from "@/components/data-table/types"
|
|
41
|
+
import type { DataListViewType } from "@/lib/data-list-view"
|
|
42
|
+
import { Button } from "@/components/ui/button"
|
|
43
|
+
import { Tip } from "@/components/ui/tip"
|
|
44
|
+
import { Badge } from "@/components/ui/badge"
|
|
45
|
+
import { useAutoPanel } from "@/components/sidebar"
|
|
46
|
+
import {
|
|
47
|
+
CATEGORY_COUNTS,
|
|
48
|
+
CATEGORY_TABS,
|
|
49
|
+
DEPRECATED_COUNT,
|
|
50
|
+
TOKENS_INDEX,
|
|
51
|
+
categoryPreview,
|
|
52
|
+
primaryValueText,
|
|
53
|
+
type TokenCategory,
|
|
54
|
+
type TokenRecord,
|
|
55
|
+
} from "@/components/tokens-themes-section"
|
|
56
|
+
import {
|
|
57
|
+
readTokensCategory,
|
|
58
|
+
TOKENS_ALL_CATEGORY,
|
|
59
|
+
type TokensCategoryParam,
|
|
60
|
+
} from "@/components/tokens-secondary-nav"
|
|
61
|
+
|
|
62
|
+
/** Row shape consumed by `DataTable` — flat fields make built-in search work out of the box. */
|
|
63
|
+
interface TokenRow extends Record<string, unknown> {
|
|
64
|
+
id: string // == name, unique
|
|
65
|
+
name: string // var(--…)
|
|
66
|
+
namespace: string
|
|
67
|
+
category: TokenCategory | string
|
|
68
|
+
value: string
|
|
69
|
+
deprecated: boolean
|
|
70
|
+
/** The original token index entry — used by the Preview cell renderer. */
|
|
71
|
+
record: TokenRecord
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Build all token rows once at module load (token index is static at runtime). */
|
|
75
|
+
const TOKEN_ROWS: TokenRow[] = (() => {
|
|
76
|
+
const out: TokenRow[] = []
|
|
77
|
+
for (const [name, record] of Object.entries(TOKENS_INDEX.tokens)) {
|
|
78
|
+
out.push({
|
|
79
|
+
id: name,
|
|
80
|
+
name,
|
|
81
|
+
namespace: record.namespace,
|
|
82
|
+
category: record.category,
|
|
83
|
+
value: primaryValueText(record),
|
|
84
|
+
deprecated: Boolean(record.deprecated),
|
|
85
|
+
record,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
return out.sort((a, b) => a.name.localeCompare(b.name))
|
|
89
|
+
})()
|
|
90
|
+
|
|
91
|
+
/** Pre-bucket rows by category so each panel selection slices in O(1). */
|
|
92
|
+
const ROWS_BY_CATEGORY = (() => {
|
|
93
|
+
const map = new Map<TokensCategoryParam, TokenRow[]>()
|
|
94
|
+
map.set(TOKENS_ALL_CATEGORY, TOKEN_ROWS)
|
|
95
|
+
for (const tab of CATEGORY_TABS) {
|
|
96
|
+
map.set(
|
|
97
|
+
tab.id as TokensCategoryParam,
|
|
98
|
+
TOKEN_ROWS.filter((r) => tab.matches(String(r.category))),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
return map
|
|
102
|
+
})()
|
|
103
|
+
|
|
104
|
+
/** Namespace select-filter options — built once from the index. */
|
|
105
|
+
const NAMESPACE_OPTIONS = TOKENS_INDEX.namespaces
|
|
106
|
+
.slice()
|
|
107
|
+
.sort()
|
|
108
|
+
.map((ns) => ({ value: ns, label: ns }))
|
|
109
|
+
|
|
110
|
+
const STATUS_OPTIONS = [
|
|
111
|
+
{ value: "active", label: "Active" },
|
|
112
|
+
{ value: "deprecated", label: "Deprecated" },
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
const TOKENS_HEADER_SUBTITLE = `${TOKENS_INDEX.tokenCount} tokens · ${TOKENS_INDEX.namespaces.length} namespaces · v${TOKENS_INDEX.version}`
|
|
116
|
+
|
|
117
|
+
const TOKENS_VIEW_TABS: ViewTab[] = [
|
|
118
|
+
{
|
|
119
|
+
id: "tokens-table",
|
|
120
|
+
label: "Tokens",
|
|
121
|
+
viewType: "table",
|
|
122
|
+
icon: "fa-table",
|
|
123
|
+
filterId: "all",
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
/** Tokens hub only supports the table view — Properties drawer hides everything else. */
|
|
128
|
+
const TOKENS_SUPPORTED_VIEWS: readonly DataListViewType[] = ["table"] as const
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Canonical KPI shape (matches `placement-kpi.ts` precedent):
|
|
132
|
+
* - every `MetricItem` is clickable — tiles drive the secondary-panel
|
|
133
|
+
* category by pushing `?category=…`, the same URL the panel rail uses,
|
|
134
|
+
* - a `MetricInsight` summarizes the current scope on the right.
|
|
135
|
+
* See `apps/web/docs/kpi-flat-band-pattern.md` + `exxat-kpi-trends.mdc`.
|
|
136
|
+
*/
|
|
137
|
+
function buildMetrics(
|
|
138
|
+
navigate: (category: TokensCategoryParam) => void,
|
|
139
|
+
): MetricItem[] {
|
|
140
|
+
return [
|
|
141
|
+
{
|
|
142
|
+
id: "total",
|
|
143
|
+
label: "Total tokens",
|
|
144
|
+
value: TOKENS_INDEX.tokenCount,
|
|
145
|
+
delta: "",
|
|
146
|
+
trend: "neutral",
|
|
147
|
+
trendPolarity: "informational",
|
|
148
|
+
metricVariant: "hero",
|
|
149
|
+
description: `${TOKENS_INDEX.namespaces.length} namespaces`,
|
|
150
|
+
onClick: () => navigate(TOKENS_ALL_CATEGORY),
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "color",
|
|
154
|
+
label: "Color tokens",
|
|
155
|
+
value: CATEGORY_COUNTS.color ?? 0,
|
|
156
|
+
delta: "",
|
|
157
|
+
trend: "neutral",
|
|
158
|
+
trendPolarity: "informational",
|
|
159
|
+
description: "semantic + alias",
|
|
160
|
+
onClick: () => navigate("color" as TokensCategoryParam),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "motion",
|
|
164
|
+
label: "Motion tokens",
|
|
165
|
+
value: CATEGORY_COUNTS.transition ?? 0,
|
|
166
|
+
delta: "",
|
|
167
|
+
trend: "neutral",
|
|
168
|
+
trendPolarity: "informational",
|
|
169
|
+
description: "easings + durations",
|
|
170
|
+
onClick: () => navigate("motion" as TokensCategoryParam),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "deprecated",
|
|
174
|
+
label: "Deprecated",
|
|
175
|
+
value: DEPRECATED_COUNT,
|
|
176
|
+
delta: "",
|
|
177
|
+
trend: "neutral",
|
|
178
|
+
trendPolarity: "lower_is_better",
|
|
179
|
+
description:
|
|
180
|
+
DEPRECATED_COUNT > 0
|
|
181
|
+
? "scheduled for removal"
|
|
182
|
+
: "none scheduled for removal",
|
|
183
|
+
onClick: () => navigate("deprecated" as TokensCategoryParam),
|
|
184
|
+
},
|
|
185
|
+
]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildInsight(activeCategory: TokensCategoryParam, rowCount: number): MetricInsight {
|
|
189
|
+
const label = categoryDisplayLabel(activeCategory)
|
|
190
|
+
return {
|
|
191
|
+
title:
|
|
192
|
+
activeCategory === TOKENS_ALL_CATEGORY
|
|
193
|
+
? "Token index"
|
|
194
|
+
: `${label} in scope`,
|
|
195
|
+
description:
|
|
196
|
+
activeCategory === TOKENS_ALL_CATEGORY
|
|
197
|
+
? `${rowCount.toLocaleString()} tokens across ${TOKENS_INDEX.namespaces.length} namespaces. Click any KPI above to scope by category, or use the rail on the left for finer slicing.`
|
|
198
|
+
: `${rowCount.toLocaleString()} ${label.toLowerCase()}. Filter by namespace or status from the table toolbar, or jump back to the full index from the rail.`,
|
|
199
|
+
severity: "info",
|
|
200
|
+
actionLabel: "Ask Leo",
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Friendly display label for the category currently scoped from the panel. */
|
|
205
|
+
function categoryDisplayLabel(category: TokensCategoryParam): string {
|
|
206
|
+
if (category === TOKENS_ALL_CATEGORY) return "All tokens"
|
|
207
|
+
return CATEGORY_TABS.find((c) => c.id === category)?.label ?? "Tokens"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ── Cell renderers ───────────────────────────────────────────────────── */
|
|
211
|
+
|
|
212
|
+
function useClipboard() {
|
|
213
|
+
const [copied, setCopied] = React.useState<string | null>(null)
|
|
214
|
+
const copy = React.useCallback((text: string) => {
|
|
215
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) return
|
|
216
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
217
|
+
setCopied(text)
|
|
218
|
+
window.setTimeout(() => setCopied((c) => (c === text ? null : c)), 1200)
|
|
219
|
+
}).catch(() => {})
|
|
220
|
+
}, [])
|
|
221
|
+
return { copied, copy }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function TokenNameCell({
|
|
225
|
+
row,
|
|
226
|
+
onCopy,
|
|
227
|
+
copiedNow,
|
|
228
|
+
}: {
|
|
229
|
+
row: TokenRow
|
|
230
|
+
onCopy: (text: string) => void
|
|
231
|
+
copiedNow: boolean
|
|
232
|
+
}) {
|
|
233
|
+
const cssRef = `var(${row.name})`
|
|
234
|
+
return (
|
|
235
|
+
<div className="group/token-name flex min-w-0 items-center gap-2">
|
|
236
|
+
<code className="truncate font-mono text-xs text-foreground tabular-nums">{row.name}</code>
|
|
237
|
+
<Tip side="top" label={copiedNow ? `Copied ${cssRef}` : `Copy ${cssRef}`}>
|
|
238
|
+
<Button
|
|
239
|
+
type="button"
|
|
240
|
+
size="icon"
|
|
241
|
+
variant="ghost"
|
|
242
|
+
className="size-6 shrink-0 opacity-0 transition-opacity group-hover/token-name:opacity-100 focus-visible:opacity-100"
|
|
243
|
+
onClick={(e) => {
|
|
244
|
+
e.stopPropagation()
|
|
245
|
+
onCopy(cssRef)
|
|
246
|
+
}}
|
|
247
|
+
aria-label={`Copy ${cssRef}`}
|
|
248
|
+
>
|
|
249
|
+
<i
|
|
250
|
+
className={
|
|
251
|
+
copiedNow
|
|
252
|
+
? "fa-light fa-check text-xs"
|
|
253
|
+
: "fa-light fa-copy text-xs"
|
|
254
|
+
}
|
|
255
|
+
aria-hidden="true"
|
|
256
|
+
/>
|
|
257
|
+
</Button>
|
|
258
|
+
</Tip>
|
|
259
|
+
</div>
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function StatusCell({ row }: { row: TokenRow }) {
|
|
264
|
+
if (row.deprecated) {
|
|
265
|
+
return (
|
|
266
|
+
<Badge variant="destructive" className="h-5 px-1.5 text-[10px]">
|
|
267
|
+
deprecated
|
|
268
|
+
</Badge>
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
return <span className="text-xs text-muted-foreground">—</span>
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* ── Public ───────────────────────────────────────────────────────────── */
|
|
275
|
+
|
|
276
|
+
export function TokensThemesClient() {
|
|
277
|
+
// Opens the `tokens` secondary panel on mount and closes it on unmount.
|
|
278
|
+
// `openPanel` also collapses the main sidebar (`persist: false` — does not
|
|
279
|
+
// overwrite the user's saved rail preference). See
|
|
280
|
+
// `apps/web/components/sidebar/secondary-panel.tsx#openPanel`.
|
|
281
|
+
useAutoPanel("tokens")
|
|
282
|
+
|
|
283
|
+
const router = useRouter()
|
|
284
|
+
const searchParams = useSearchParams()
|
|
285
|
+
const searchParamsKey = searchParams.toString()
|
|
286
|
+
const activeCategory = React.useMemo(
|
|
287
|
+
() => readTokensCategory(new URLSearchParams(searchParamsKey)),
|
|
288
|
+
[searchParamsKey],
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
const [tabs, setTabs] = React.useState<ViewTab[]>(TOKENS_VIEW_TABS)
|
|
292
|
+
const [activeTabId, setActiveTabId] = React.useState<string>(TOKENS_VIEW_TABS[0]!.id)
|
|
293
|
+
const [pagination, setPagination] = React.useState(false)
|
|
294
|
+
const { copied, copy } = useClipboard()
|
|
295
|
+
|
|
296
|
+
const navigateToCategory = React.useCallback(
|
|
297
|
+
(category: TokensCategoryParam) => {
|
|
298
|
+
const params = new URLSearchParams(searchParamsKey)
|
|
299
|
+
if (category === TOKENS_ALL_CATEGORY) {
|
|
300
|
+
params.delete("category")
|
|
301
|
+
} else {
|
|
302
|
+
params.set("category", String(category))
|
|
303
|
+
}
|
|
304
|
+
const next = params.toString()
|
|
305
|
+
router.push(next ? `/tokens-themes?${next}` : "/tokens-themes", { scroll: false })
|
|
306
|
+
},
|
|
307
|
+
[router, searchParamsKey],
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
const metrics = React.useMemo(
|
|
311
|
+
() => buildMetrics(navigateToCategory),
|
|
312
|
+
[navigateToCategory],
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
const rows = React.useMemo(
|
|
316
|
+
() => ROWS_BY_CATEGORY.get(activeCategory) ?? TOKEN_ROWS,
|
|
317
|
+
[activeCategory],
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
const insight = React.useMemo(
|
|
321
|
+
() => buildInsight(activeCategory, rows.length),
|
|
322
|
+
[activeCategory, rows.length],
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
const getTabCount = React.useCallback(() => rows.length, [rows.length])
|
|
326
|
+
|
|
327
|
+
const columns: ColumnDef<TokenRow>[] = React.useMemo(() => [
|
|
328
|
+
{
|
|
329
|
+
key: "preview",
|
|
330
|
+
label: "Preview",
|
|
331
|
+
width: 110,
|
|
332
|
+
minWidth: 90,
|
|
333
|
+
cell: (row) => (
|
|
334
|
+
<div className="group flex w-full items-center justify-center">
|
|
335
|
+
{categoryPreview(row.name, row.record)}
|
|
336
|
+
</div>
|
|
337
|
+
),
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
key: "name",
|
|
341
|
+
label: "Token",
|
|
342
|
+
width: 320,
|
|
343
|
+
minWidth: 220,
|
|
344
|
+
defaultPin: "left",
|
|
345
|
+
sortable: true,
|
|
346
|
+
sortKey: "name",
|
|
347
|
+
filter: { type: "text", icon: "fa-font" },
|
|
348
|
+
cell: (row) => (
|
|
349
|
+
<TokenNameCell
|
|
350
|
+
row={row}
|
|
351
|
+
onCopy={copy}
|
|
352
|
+
copiedNow={copied === `var(${row.name})`}
|
|
353
|
+
/>
|
|
354
|
+
),
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
key: "namespace",
|
|
358
|
+
label: "Namespace",
|
|
359
|
+
width: 200,
|
|
360
|
+
minWidth: 140,
|
|
361
|
+
sortable: true,
|
|
362
|
+
sortKey: "namespace",
|
|
363
|
+
filter: {
|
|
364
|
+
type: "select",
|
|
365
|
+
icon: "fa-tag",
|
|
366
|
+
options: NAMESPACE_OPTIONS,
|
|
367
|
+
},
|
|
368
|
+
cell: (row) => (
|
|
369
|
+
<span className="rounded-sm bg-muted/60 px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground">
|
|
370
|
+
{row.namespace}
|
|
371
|
+
</span>
|
|
372
|
+
),
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
key: "value",
|
|
376
|
+
label: "Value",
|
|
377
|
+
width: 340,
|
|
378
|
+
minWidth: 200,
|
|
379
|
+
sortable: true,
|
|
380
|
+
sortKey: "value",
|
|
381
|
+
filter: { type: "text", icon: "fa-magnifying-glass" },
|
|
382
|
+
cell: (row) => (
|
|
383
|
+
<code
|
|
384
|
+
className="block truncate font-mono text-[11px] text-muted-foreground"
|
|
385
|
+
title={row.value}
|
|
386
|
+
>
|
|
387
|
+
{row.value || "—"}
|
|
388
|
+
</code>
|
|
389
|
+
),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
key: "status",
|
|
393
|
+
label: "Status",
|
|
394
|
+
width: 120,
|
|
395
|
+
minWidth: 100,
|
|
396
|
+
sortable: true,
|
|
397
|
+
sortKey: "deprecated",
|
|
398
|
+
filter: {
|
|
399
|
+
type: "select",
|
|
400
|
+
icon: "fa-circle-check",
|
|
401
|
+
options: STATUS_OPTIONS,
|
|
402
|
+
},
|
|
403
|
+
cell: (row) => <StatusCell row={row} />,
|
|
404
|
+
},
|
|
405
|
+
], [copy, copied])
|
|
406
|
+
|
|
407
|
+
const headerTitle =
|
|
408
|
+
activeCategory === TOKENS_ALL_CATEGORY
|
|
409
|
+
? "Tokens & themes"
|
|
410
|
+
: `${categoryDisplayLabel(activeCategory)} tokens`
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<PrimaryPageTemplate
|
|
414
|
+
siteHeader={{
|
|
415
|
+
breadcrumbs: [
|
|
416
|
+
{ label: "Dashboard", href: "/dashboard" },
|
|
417
|
+
{ label: "Tokens & themes", href: "/tokens-themes" },
|
|
418
|
+
],
|
|
419
|
+
title: headerTitle,
|
|
420
|
+
}}
|
|
421
|
+
>
|
|
422
|
+
<ListPageTemplate
|
|
423
|
+
defaultTabs={TOKENS_VIEW_TABS}
|
|
424
|
+
tabs={tabs}
|
|
425
|
+
onTabsChange={setTabs}
|
|
426
|
+
activeTabId={activeTabId}
|
|
427
|
+
onActiveTabChange={setActiveTabId}
|
|
428
|
+
supportedViewTypes={["table"]}
|
|
429
|
+
getTabCount={getTabCount}
|
|
430
|
+
header={
|
|
431
|
+
<PageHeader
|
|
432
|
+
title={headerTitle}
|
|
433
|
+
subtitle={TOKENS_HEADER_SUBTITLE}
|
|
434
|
+
/>
|
|
435
|
+
}
|
|
436
|
+
metrics={
|
|
437
|
+
<KeyMetrics
|
|
438
|
+
variant="flat"
|
|
439
|
+
metrics={metrics}
|
|
440
|
+
insight={insight}
|
|
441
|
+
showHeader={false}
|
|
442
|
+
metricsSingleRow
|
|
443
|
+
/>
|
|
444
|
+
}
|
|
445
|
+
renderContent={(tab, updateTab) => (
|
|
446
|
+
<HubTable<TokenRow>
|
|
447
|
+
rows={rows}
|
|
448
|
+
columns={columns}
|
|
449
|
+
view={tab.viewType}
|
|
450
|
+
onViewChange={(v) =>
|
|
451
|
+
updateTab({ viewType: v })
|
|
452
|
+
}
|
|
453
|
+
supportedViewTypes={TOKENS_SUPPORTED_VIEWS}
|
|
454
|
+
hubLabel="Tokens"
|
|
455
|
+
lifecycleTabLabel="Tokens & themes"
|
|
456
|
+
searchAriaLabel="Search tokens"
|
|
457
|
+
getRowId={(r) => r.id}
|
|
458
|
+
getRowSelectionLabel={(r) => r.name}
|
|
459
|
+
defaultSort={{ key: "name", dir: "asc" }}
|
|
460
|
+
selectable={false}
|
|
461
|
+
pagination={pagination}
|
|
462
|
+
onPaginationChange={setPagination}
|
|
463
|
+
paginationInitialPageSize={25}
|
|
464
|
+
paginationPageSizeOptions={[10, 25, 50, 100]}
|
|
465
|
+
emptyState={
|
|
466
|
+
<p className="text-sm text-muted-foreground">
|
|
467
|
+
No tokens match your filters.
|
|
468
|
+
</p>
|
|
469
|
+
}
|
|
470
|
+
renderers={{}}
|
|
471
|
+
/>
|
|
472
|
+
)}
|
|
473
|
+
/>
|
|
474
|
+
</PrimaryPageTemplate>
|
|
475
|
+
)
|
|
476
|
+
}
|