@exxatdesignux/ui 0.2.17 → 0.2.19

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.
Files changed (162) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/consumer-extras/AGENTS.md +76 -0
  3. package/consumer-extras/README.md +5 -1
  4. package/consumer-extras/cursor-skills/exxat-centralized-list-dataset/SKILL.md +14 -3
  5. package/consumer-extras/cursor-skills/exxat-consumer-app/SKILL.md +37 -0
  6. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +22 -7
  7. package/consumer-extras/cursor-skills/exxat-focused-workflow-page/SKILL.md +57 -0
  8. package/consumer-extras/cursor-skills/exxat-kpi-flat-band/SKILL.md +38 -0
  9. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +10 -3
  10. package/consumer-extras/patterns/consumer-app-pattern.md +39 -0
  11. package/consumer-extras/patterns/consumer-upgrade-checklist.md +20 -0
  12. package/consumer-extras/patterns/data-views-pattern.md +42 -3
  13. package/consumer-extras/patterns/focused-workflow-page-pattern.md +84 -0
  14. package/consumer-extras/patterns/kpi-flat-band-pattern.md +57 -0
  15. package/consumer-extras/patterns/shell-surface-elevation-pattern.md +54 -0
  16. package/package.json +2 -1
  17. package/src/components/ui/button-group.tsx +81 -0
  18. package/src/components/ui/button.tsx +4 -4
  19. package/src/components/ui/sidebar.tsx +2 -2
  20. package/src/globals.css +7 -1807
  21. package/src/theme.css +10 -1126
  22. package/src/tokens/README.md +15 -0
  23. package/src/tokens/base.css +337 -0
  24. package/src/tokens/high-contrast.css +1195 -0
  25. package/src/tokens/layers.css +224 -0
  26. package/src/tokens/tailwind-bridge.css +118 -0
  27. package/src/tokens/themes.css +201 -0
  28. package/template/.cursor/rules/exxat-kbd-shortcuts.mdc +1 -1
  29. package/template/AGENTS.md +66 -21
  30. package/template/app/(app)/dashboard/loading.tsx +3 -15
  31. package/template/app/(app)/dashboard/page.tsx +2 -14
  32. package/template/app/(app)/data-list/layout.tsx +43 -0
  33. package/template/app/(app)/data-list/page.tsx +2 -2
  34. package/template/app/(app)/error.tsx +22 -6
  35. package/template/app/(app)/examples/focused-workflow/page.tsx +5 -0
  36. package/template/app/(app)/examples/page.tsx +1 -0
  37. package/template/app/(app)/layout.tsx +13 -6
  38. package/template/app/(app)/loading.tsx +1 -18
  39. package/template/app/(app)/question-bank/find/page.tsx +2 -1
  40. package/template/app/(app)/question-bank/library/page.tsx +2 -1
  41. package/template/app/(app)/question-bank/list/page.tsx +2 -1
  42. package/template/app/(app)/question-bank/new/page.tsx +15 -23
  43. package/template/app/(app)/question-bank/page.tsx +2 -1
  44. package/template/app/(app)/settings/page.tsx +4 -5
  45. package/template/app/global-error.tsx +63 -0
  46. package/template/app/globals.css +7 -1934
  47. package/template/app/layout.tsx +2 -0
  48. package/template/components/app-route-loading.tsx +14 -0
  49. package/template/components/app-sidebar.tsx +71 -55
  50. package/template/components/data-table/index.tsx +31 -67
  51. package/template/components/data-table/use-table-state.ts +33 -6
  52. package/template/components/data-views/index.ts +37 -9
  53. package/template/components/data-views/list-page-calendar-view.tsx +593 -0
  54. package/template/components/data-views/list-page-connected-view-body.tsx +66 -0
  55. package/template/components/data-views/list-page-folder-columns-panel.tsx +345 -0
  56. package/template/components/data-views/list-page-split-hub-chrome.tsx +8 -0
  57. package/template/components/dev-chunk-load-recovery.tsx +41 -0
  58. package/template/components/examples/focused-workflow-showcase.tsx +183 -0
  59. package/template/components/exxat-product-logo.tsx +2 -6
  60. package/template/components/key-metrics.tsx +54 -22
  61. package/template/components/list-hub-board-view.tsx +68 -0
  62. package/template/components/list-hub-client.tsx +186 -0
  63. package/template/components/list-hub-list-view.tsx +36 -0
  64. package/template/components/list-hub-panel-activator.tsx +8 -0
  65. package/template/components/list-hub-secondary-nav.tsx +121 -0
  66. package/template/components/list-hub-table.tsx +336 -0
  67. package/template/components/new-question-composer.tsx +6 -24
  68. package/template/components/product-switcher.tsx +5 -5
  69. package/template/components/product-wordmark.tsx +4 -7
  70. package/template/components/question-bank-client.tsx +4 -1
  71. package/template/components/question-bank-folder-columns-panel.tsx +104 -0
  72. package/template/components/question-bank-hub-client.tsx +2 -5
  73. package/template/components/question-bank-table.tsx +155 -509
  74. package/template/components/secondary-panel/nav-link-rows.tsx +83 -0
  75. package/template/components/secondary-panel.tsx +4 -44
  76. package/template/components/secondary-panels/list-hub-panel.tsx +39 -0
  77. package/template/components/secondary-panels/question-bank-panel.tsx +39 -0
  78. package/template/components/secondary-panels/registry.tsx +15 -0
  79. package/template/components/settings-appearance-card.tsx +3 -2
  80. package/template/components/settings-client.tsx +59 -15
  81. package/template/components/settings-form-row.tsx +9 -4
  82. package/template/components/sidebar-shell.tsx +2 -1
  83. package/template/components/table-properties/drawer-button.tsx +51 -20
  84. package/template/components/table-properties/drawer.tsx +81 -17
  85. package/template/components/templates/focused-workflow-layouts.tsx +448 -0
  86. package/template/components/templates/focused-workflow-page-template.tsx +69 -0
  87. package/template/components/templates/list-page.tsx +40 -13
  88. package/template/components/templates/nested-secondary-panel-shell.tsx +3 -2
  89. package/template/components/templates/page-loading-shell.tsx +262 -0
  90. package/template/components/ui/button-group.tsx +1 -0
  91. package/template/contexts/product-context.tsx +21 -2
  92. package/template/docs/consumer-app-pattern.md +39 -0
  93. package/template/docs/data-views-pattern.md +42 -3
  94. package/template/docs/drawer-vs-dialog-pattern.md +3 -1
  95. package/template/docs/focused-workflow-page-pattern.md +84 -0
  96. package/template/docs/kpi-flat-band-pattern.md +57 -0
  97. package/template/docs/kpi-strip-max-four-pattern.md +1 -0
  98. package/template/docs/shell-surface-elevation-pattern.md +54 -0
  99. package/template/lib/chunk-load-error.ts +13 -0
  100. package/template/lib/command-menu-search-data.ts +11 -27
  101. package/template/lib/conditional-rule-match.ts +87 -22
  102. package/template/lib/data-list-display-options.ts +16 -2
  103. package/template/lib/data-list-view-registry.ts +104 -0
  104. package/template/lib/data-list-view-surface.ts +15 -1
  105. package/template/lib/data-list-view.ts +16 -1
  106. package/template/lib/data-view-dashboard-storage.ts +38 -35
  107. package/template/lib/hub-connected-view-renderers.ts +58 -0
  108. package/template/lib/list-hub-nav.ts +121 -0
  109. package/template/lib/list-hub-supported-views.ts +10 -0
  110. package/template/lib/list-page-table-properties.ts +3 -7
  111. package/template/lib/list-status-badges.ts +4 -97
  112. package/template/lib/mock/list-hub-directory.ts +27 -0
  113. package/template/lib/mock/list-hub-kpi.ts +27 -0
  114. package/template/lib/mock/navigation.tsx +1 -0
  115. package/template/lib/page-loading-variant.ts +40 -0
  116. package/template/lib/question-bank-supported-views.ts +13 -0
  117. package/template/lib/sidebar-state-cookie.ts +9 -0
  118. package/template/lib/table-state-lifecycle.ts +60 -13
  119. package/template/app/(app)/data-list/[id]/page.tsx +0 -44
  120. package/template/app/(app)/data-list/new/page.tsx +0 -34
  121. package/template/components/compliance-board-view.tsx +0 -142
  122. package/template/components/compliance-client.tsx +0 -92
  123. package/template/components/compliance-list-view.tsx +0 -54
  124. package/template/components/compliance-page-header.tsx +0 -89
  125. package/template/components/compliance-table.tsx +0 -632
  126. package/template/components/data-view-dashboard-charts-compliance.tsx +0 -963
  127. package/template/components/data-view-dashboard-charts-team.tsx +0 -971
  128. package/template/components/data-view-dashboard-charts.tsx +0 -1503
  129. package/template/components/new-placement-back-btn.tsx +0 -28
  130. package/template/components/new-placement-form.tsx +0 -1068
  131. package/template/components/placement-board-card.tsx +0 -262
  132. package/template/components/placement-detail.tsx +0 -438
  133. package/template/components/placements-board-view.tsx +0 -404
  134. package/template/components/placements-client.tsx +0 -252
  135. package/template/components/placements-list-view.tsx +0 -171
  136. package/template/components/placements-page-header.tsx +0 -166
  137. package/template/components/placements-table-cells.test.tsx +0 -22
  138. package/template/components/placements-table-cells.tsx +0 -173
  139. package/template/components/placements-table-columns.tsx +0 -640
  140. package/template/components/placements-table.tsx +0 -1675
  141. package/template/components/rotations-empty-state.tsx +0 -50
  142. package/template/components/rotations-panel-activator.tsx +0 -8
  143. package/template/components/sites-all-client.tsx +0 -154
  144. package/template/components/sites-board-view.tsx +0 -67
  145. package/template/components/sites-list-view.tsx +0 -42
  146. package/template/components/sites-table.tsx +0 -402
  147. package/template/components/team-board-view.tsx +0 -122
  148. package/template/components/team-client.tsx +0 -100
  149. package/template/components/team-list-view.tsx +0 -59
  150. package/template/components/team-page-header.tsx +0 -92
  151. package/template/components/team-table.tsx +0 -714
  152. package/template/lib/data-view-dashboard-placements-layout.ts +0 -215
  153. package/template/lib/mock/compliance-kpi.ts +0 -61
  154. package/template/lib/mock/compliance.ts +0 -146
  155. package/template/lib/mock/placements-kpi.ts +0 -134
  156. package/template/lib/mock/placements.ts +0 -183
  157. package/template/lib/mock/sites-directory.ts +0 -16
  158. package/template/lib/mock/sites-kpi.ts +0 -25
  159. package/template/lib/mock/team-kpi.ts +0 -60
  160. package/template/lib/mock/team.ts +0 -118
  161. package/template/lib/placement-board-card-layout.ts +0 -79
  162. package/template/lib/placement-lifecycle.ts +0 -5
@@ -1,50 +0,0 @@
1
- "use client"
2
-
3
- /**
4
- * Rotations hub — main canvas when no rotation detail is selected.
5
- * Pairs with SecondaryPanel (nested sidebar); CTA reopens the panel if closed.
6
- */
7
-
8
- import { Button } from "@/components/ui/button"
9
- import { useSecondaryPanel } from "@/components/secondary-panel"
10
-
11
- export function RotationsEmptyState() {
12
- const { openPanel } = useSecondaryPanel()
13
-
14
- return (
15
- <section
16
- aria-labelledby="rotations-empty-title"
17
- className="flex flex-1 flex-col items-center justify-center rounded-xl border border-dashed border-border/80 bg-muted/25 px-6 py-12 text-center min-h-[min(420px,calc(100svh-var(--header-height)-6rem))]"
18
- >
19
- <div className="mb-6 w-full max-w-[min(100%,280px)] shrink-0">
20
- {/* Static SVG hero, above the fold — next/image can't optimize SVGs
21
- without `dangerouslyAllowSVG`, and lazy-loading is wrong here. */}
22
- {/* eslint-disable-next-line @next/next/no-img-element -- SVG; next/image can't optimize without dangerouslyAllowSVG */}
23
- <img
24
- src="/Illustration/Rotation.svg"
25
- alt=""
26
- width={622}
27
- height={559}
28
- decoding="async"
29
- className="h-auto w-full select-none"
30
- />
31
- </div>
32
- <h2
33
- id="rotations-empty-title"
34
- className="font-heading text-xl font-semibold tracking-tight text-foreground sm:text-2xl"
35
- >
36
- Select a rotation
37
- </h2>
38
- <p className="mt-2 max-w-md text-sm leading-relaxed text-muted-foreground">
39
- Use the rotations panel next to the sidebar to browse cycles, open a rotation for
40
- details, or review schedules and assigned students.
41
- </p>
42
- <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
43
- <Button type="button" size="lg" onClick={() => openPanel("rotations")}>
44
- <i className="fa-light fa-sidebar text-[15px]" aria-hidden="true" />
45
- Open rotations panel
46
- </Button>
47
- </div>
48
- </section>
49
- )
50
- }
@@ -1,8 +0,0 @@
1
- "use client"
2
-
3
- import { useAutoPanel } from "@/components/secondary-panel"
4
-
5
- export function RotationsPanelActivator() {
6
- useAutoPanel("rotations")
7
- return null
8
- }
@@ -1,154 +0,0 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
-
5
- import { ListPageTemplate, type ViewTab, dataListViewIcon, type DataListViewType } from "@/components/data-views"
6
- import { PageHeader } from "@/components/page-header"
7
- import { Button } from "@/components/ui/button"
8
- import { Tip } from "@/components/ui/tip"
9
- import {
10
- DropdownMenu,
11
- DropdownMenuContent,
12
- DropdownMenuItem,
13
- DropdownMenuSeparator,
14
- DropdownMenuTrigger,
15
- } from "@/components/ui/dropdown-menu"
16
- import { KeyMetrics } from "@/components/key-metrics"
17
- import { SitesTable, type SitesTableHandle } from "@/components/sites-table"
18
- import { useAskLeoPageContext } from "@/components/ask-leo-sidebar"
19
- import { SITES_DIRECTORY } from "@/lib/mock/sites-directory"
20
- import { SITES_KPI_INSIGHT, sitesKpiMetrics } from "@/lib/mock/sites-kpi"
21
-
22
- const DEFAULT_TABS: ViewTab[] = [
23
- { id: "sites", label: "Sites", viewType: "board", icon: "fa-grid-2", filterId: "all" },
24
- ]
25
-
26
- function SitesPageHeader({
27
- count,
28
- onAdd,
29
- onExport,
30
- showMetrics,
31
- onToggleMetrics,
32
- }: {
33
- count: number
34
- onAdd: () => void
35
- onExport: () => void
36
- showMetrics: boolean
37
- onToggleMetrics: () => void
38
- }) {
39
- const [moreOpen, setMoreOpen] = React.useState(false)
40
- return (
41
- <PageHeader
42
- title="Sites"
43
- subtitle={`${count} ${count === 1 ? "site" : "sites"} · Last updated now`}
44
- actions={
45
- <div className="flex items-center gap-2" role="group" aria-label="Sites actions">
46
- <Tip side="bottom" label="Add a new site">
47
- <Button type="button" size="lg" onClick={onAdd}>
48
- <i className="fa-light fa-plus" aria-hidden="true" />
49
- Add site
50
- </Button>
51
- </Tip>
52
- <DropdownMenu open={moreOpen} onOpenChange={setMoreOpen}>
53
- <Tip side="bottom" label="More actions">
54
- <DropdownMenuTrigger asChild>
55
- <Button
56
- type="button"
57
- size="lg"
58
- variant="outline"
59
- className="aspect-square px-0"
60
- aria-label="More actions"
61
- >
62
- <i className="fa-light fa-ellipsis text-base" aria-hidden="true" />
63
- </Button>
64
- </DropdownMenuTrigger>
65
- </Tip>
66
- <DropdownMenuContent align="end">
67
- <DropdownMenuItem
68
- onSelect={() => {
69
- window.setTimeout(() => onExport(), 0)
70
- }}
71
- >
72
- <i className="fa-light fa-arrow-down-to-line" aria-hidden="true" />
73
- Export
74
- </DropdownMenuItem>
75
- <DropdownMenuSeparator />
76
- <DropdownMenuItem
77
- onSelect={() => {
78
- window.setTimeout(() => onToggleMetrics(), 0)
79
- }}
80
- >
81
- <i
82
- className={`fa-light ${showMetrics ? "fa-eye-slash" : "fa-eye"}`}
83
- aria-hidden="true"
84
- />
85
- {showMetrics ? "Hide metric section" : "Show metric section"}
86
- </DropdownMenuItem>
87
- </DropdownMenuContent>
88
- </DropdownMenu>
89
- </div>
90
- }
91
- />
92
- )
93
- }
94
-
95
- export function SitesAllClient() {
96
- const [exportOpen, setExportOpen] = React.useState(false)
97
- const [showMetrics, setShowMetrics] = React.useState(true)
98
- const tableRef = React.useRef<SitesTableHandle>(null)
99
- const count = SITES_DIRECTORY.length
100
- const metrics = React.useMemo(() => sitesKpiMetrics(count), [count])
101
-
102
- useAskLeoPageContext(
103
- React.useMemo(
104
- () => ({
105
- title: "Sites",
106
- description: `${count} sites in this directory.`,
107
- suggestions: [
108
- "Which sites should we onboard next?",
109
- "Summarize capacity across affiliated sites",
110
- ],
111
- }),
112
- [count],
113
- ),
114
- )
115
-
116
- return (
117
- <ListPageTemplate
118
- defaultTabs={DEFAULT_TABS}
119
- getTabCount={() => count}
120
- showMetrics={showMetrics}
121
- tablePropertiesRef={tableRef}
122
- metrics={
123
- <KeyMetrics
124
- variant="flat"
125
- metrics={metrics}
126
- insight={SITES_KPI_INSIGHT}
127
- showHeader={false}
128
- metricsSingleRow
129
- />
130
- }
131
- exportOpen={exportOpen}
132
- onExportOpenChange={setExportOpen}
133
- exportTotalRows={count}
134
- header={
135
- <SitesPageHeader
136
- count={count}
137
- onAdd={() => {}}
138
- onExport={() => setExportOpen(true)}
139
- showMetrics={showMetrics}
140
- onToggleMetrics={() => setShowMetrics(v => !v)}
141
- />
142
- }
143
- renderContent={(tab, updateTab) => (
144
- <SitesTable
145
- key={tab.id}
146
- ref={tableRef}
147
- sites={SITES_DIRECTORY}
148
- view={tab.viewType}
149
- onViewChange={(v: DataListViewType) => updateTab({ viewType: v, icon: dataListViewIcon(v) })}
150
- />
151
- )}
152
- />
153
- )
154
- }
@@ -1,67 +0,0 @@
1
- "use client"
2
-
3
- /**
4
- * Sites hub — **grid of `ListPageBoardCard` tiles** (same card composition as Team board cards),
5
- * not the kanban `ListPageBoardTemplate` column shell. Insets match other list hubs.
6
- */
7
-
8
- import Link from "next/link"
9
- import type { SiteDirectoryRow } from "@/lib/mock/sites-directory"
10
- import { initialsFromDisplayName } from "@/lib/initials-from-name"
11
- import { BoardCardIconRow } from "@/components/data-views/board-card-primitives"
12
- import {
13
- HubRecordCard,
14
- ListPageBoardCardAvatar,
15
- ListPageBoardCardBody,
16
- ListPageBoardCardHeader,
17
- ListPageBoardCardTitleRow,
18
- } from "@/components/data-views/list-page-board-card"
19
-
20
- /** Same card building blocks as `TeamBoardView` / board tabs — without the column template. */
21
- export function SiteBoardCard({ site }: { site: SiteDirectoryRow }) {
22
- return (
23
- <Link
24
- href={site.url}
25
- className="block h-full rounded-xl text-inherit no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
26
- >
27
- <HubRecordCard interactive className="h-full w-full">
28
- <ListPageBoardCardHeader>
29
- <ListPageBoardCardTitleRow
30
- title={site.name}
31
- titleClassName="truncate"
32
- trailing={<ListPageBoardCardAvatar initials={initialsFromDisplayName(site.name)} />}
33
- />
34
- <ListPageBoardCardBody>
35
- <BoardCardIconRow iconClass="fa-hashtag">
36
- <span className="truncate">{site.id}</span>
37
- </BoardCardIconRow>
38
- <BoardCardIconRow iconClass="fa-link">
39
- <span className="truncate" title={site.url}>
40
- {site.url}
41
- </span>
42
- </BoardCardIconRow>
43
- </ListPageBoardCardBody>
44
- </ListPageBoardCardHeader>
45
- </HubRecordCard>
46
- </Link>
47
- )
48
- }
49
-
50
- /** Responsive card grid + page insets (`px-4` / `lg:px-6`) aligned with `ListPageBoardTemplate` / Team toolbar. */
51
- export function SitesCardGrid({ rows }: { rows: SiteDirectoryRow[] }) {
52
- if (rows.length === 0) {
53
- return (
54
- <div className="px-4 pb-6 pt-2 lg:px-6">
55
- <p className="py-8 text-center text-sm text-muted-foreground">No sites match your search.</p>
56
- </div>
57
- )
58
- }
59
-
60
- return (
61
- <div className="grid grid-cols-1 gap-3 px-4 pb-6 pt-2 sm:grid-cols-2 lg:grid-cols-3 lg:px-6 xl:grid-cols-4">
62
- {rows.map(site => (
63
- <SiteBoardCard key={site.id} site={site} />
64
- ))}
65
- </div>
66
- )
67
- }
@@ -1,42 +0,0 @@
1
- "use client"
2
-
3
- import Link from "next/link"
4
- import type { SiteDirectoryRow } from "@/lib/mock/sites-directory"
5
- import { ListPageBoardCard } from "@/components/data-views/list-page-board-card"
6
- import { DataRowList } from "@/components/data-views/data-row-list"
7
-
8
- export function SitesListView({ rows }: { rows: SiteDirectoryRow[] }) {
9
- return (
10
- <DataRowList<SiteDirectoryRow>
11
- rows={rows}
12
- getRowId={site => site.id}
13
- emptyState="No sites match your search."
14
- ariaLabel="Sites"
15
- renderRow={site => (
16
- <Link
17
- href={site.url}
18
- className="block rounded-xl text-inherit no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
19
- >
20
- <ListPageBoardCard
21
- layout="row"
22
- interactive
23
- rowContainerClassName="flex flex-row items-center gap-3"
24
- leading={
25
- <span className="inline-flex size-9 shrink-0 items-center justify-center rounded-md bg-brand/10 text-brand">
26
- <i className="fa-light fa-hospital text-sm" aria-hidden="true" />
27
- </span>
28
- }
29
- rowEnd={
30
- <i className="fa-light fa-chevron-right text-xs text-muted-foreground" aria-hidden="true" />
31
- }
32
- >
33
- <div className="space-y-0.5">
34
- <p className="truncate text-sm font-semibold text-foreground">{site.name}</p>
35
- <p className="truncate text-xs text-muted-foreground">{site.id}</p>
36
- </div>
37
- </ListPageBoardCard>
38
- </Link>
39
- )}
40
- />
41
- )
42
- }