@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.
Files changed (125) hide show
  1. package/consumer-extras/cursor-skills/exxat-card-vs-list-rows/SKILL.md +20 -0
  2. package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +33 -0
  3. package/consumer-extras/cursor-skills/exxat-dedicated-search-surfaces/SKILL.md +45 -0
  4. package/consumer-extras/cursor-skills/exxat-drawer-vs-dialog/SKILL.md +20 -0
  5. package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +31 -5
  6. package/consumer-extras/cursor-skills/exxat-kpi-max-four/SKILL.md +19 -0
  7. package/consumer-extras/cursor-skills/exxat-kpi-trends/SKILL.md +27 -0
  8. package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +1 -0
  9. package/consumer-extras/patterns/collaboration-access-pattern.md +114 -0
  10. package/consumer-extras/patterns/data-views-pattern.md +12 -4
  11. package/package.json +17 -4
  12. package/src/components/ui/banner.tsx +20 -7
  13. package/src/components/ui/date-picker-field.tsx +3 -3
  14. package/src/components/ui/dropdown-menu.tsx +17 -6
  15. package/src/components/ui/input-group.tsx +1 -1
  16. package/src/components/ui/input.tsx +1 -1
  17. package/src/components/ui/select.tsx +1 -1
  18. package/src/components/ui/separator.tsx +2 -2
  19. package/src/components/ui/sidebar.tsx +31 -3
  20. package/src/components/ui/textarea.tsx +1 -1
  21. package/src/globals.css +0 -1
  22. package/src/index.ts +1 -0
  23. package/src/lib/date-filter.ts +13 -4
  24. package/src/lib/dropdown-menu-surface.ts +13 -0
  25. package/template/.claude/skills/exxat-ds-skill/SKILL.md +27 -9
  26. package/template/.cursor/rules/exxat-data-tables.mdc +1 -0
  27. package/template/AGENTS.md +82 -27
  28. package/template/app/(app)/examples/page.tsx +2 -1
  29. package/template/app/(app)/help/page.tsx +6 -0
  30. package/template/app/(app)/layout.tsx +7 -4
  31. package/template/app/(app)/question-bank/find/page.tsx +12 -0
  32. package/template/app/(app)/question-bank/layout.tsx +46 -0
  33. package/template/app/(app)/question-bank/library/page.tsx +11 -0
  34. package/template/app/(app)/question-bank/list/page.tsx +12 -0
  35. package/template/app/(app)/question-bank/page.tsx +4 -3
  36. package/template/app/globals.css +1 -2
  37. package/template/components/app-sidebar.tsx +51 -13
  38. package/template/components/ask-leo-composer.tsx +173 -45
  39. package/template/components/ask-leo-sidebar.tsx +9 -1
  40. package/template/components/chart-area-interactive.tsx +3 -13
  41. package/template/components/charts-overview.tsx +33 -6
  42. package/template/components/collaboration-access-flow.tsx +144 -0
  43. package/template/components/compliance-page-header.tsx +1 -1
  44. package/template/components/compliance-table.tsx +2 -2
  45. package/template/components/dashboard-tabs.tsx +4 -3
  46. package/template/components/data-list-table-cells.tsx +1 -1
  47. package/template/components/data-list-table.tsx +1 -1
  48. package/template/components/data-table/index.tsx +5 -5
  49. package/template/components/data-table/use-table-state.ts +18 -2
  50. package/template/components/data-view-dashboard-charts-compliance.tsx +8 -5
  51. package/template/components/data-view-dashboard-charts-team.tsx +8 -5
  52. package/template/components/data-view-dashboard-charts.tsx +62 -227
  53. package/template/components/dedicated-search-recents.tsx +96 -0
  54. package/template/components/dedicated-search-url-composer.tsx +112 -0
  55. package/template/components/getting-started.tsx +1 -1
  56. package/template/components/hub-tree-panel-view.tsx +10 -26
  57. package/template/components/invite-collaborators-drawer.tsx +453 -0
  58. package/template/components/key-metrics.tsx +54 -8
  59. package/template/components/nav-documents.tsx +1 -1
  60. package/template/components/new-placement-form.tsx +3 -3
  61. package/template/components/page-header.tsx +76 -59
  62. package/template/components/placements-board-view.tsx +3 -3
  63. package/template/components/placements-page-header.tsx +1 -1
  64. package/template/components/placements-table-columns.tsx +3 -2
  65. package/template/components/product-switcher.tsx +0 -1
  66. package/template/components/question-bank-board-view.tsx +35 -47
  67. package/template/components/question-bank-client.tsx +293 -81
  68. package/template/components/question-bank-dashboard-charts.tsx +174 -0
  69. package/template/components/question-bank-favorite-button.tsx +46 -0
  70. package/template/components/question-bank-hub-client.tsx +436 -0
  71. package/template/components/question-bank-list-view.tsx +26 -19
  72. package/template/components/question-bank-new-folder-sheet.tsx +56 -42
  73. package/template/components/question-bank-os-folder-view.tsx +3 -14
  74. package/template/components/question-bank-page-header.tsx +85 -53
  75. package/template/components/question-bank-panel-activator.tsx +3 -4
  76. package/template/components/question-bank-secondary-nav.tsx +523 -65
  77. package/template/components/question-bank-table.tsx +125 -343
  78. package/template/components/secondary-panel.tsx +130 -63
  79. package/template/components/settings-client.tsx +3 -1
  80. package/template/components/sidebar-shell.tsx +2 -0
  81. package/template/components/sites-all-client.tsx +1 -1
  82. package/template/components/sites-table.tsx +1 -1
  83. package/template/components/system-banner-slot.tsx +2 -1
  84. package/template/components/table-properties/drawer.tsx +3 -3
  85. package/template/components/table-properties/sort-card.tsx +1 -1
  86. package/template/components/team-page-header.tsx +1 -1
  87. package/template/components/team-table.tsx +8 -4
  88. package/template/components/templates/dedicated-search-landing-template.tsx +58 -0
  89. package/template/components/templates/dedicated-search-results-template.tsx +19 -0
  90. package/template/components/templates/discovery-hub-template.tsx +273 -0
  91. package/template/components/templates/list-page.tsx +11 -4
  92. package/template/components/templates/nested-secondary-panel-shell.tsx +57 -0
  93. package/template/components/templates/secondary-panel-hub-template.tsx +54 -0
  94. package/template/docs/card-vs-rows-pattern.md +36 -0
  95. package/template/docs/collaboration-access-pattern.md +114 -0
  96. package/template/docs/data-views-pattern.md +12 -4
  97. package/template/docs/drawer-vs-dialog-pattern.md +50 -0
  98. package/template/docs/kpi-strip-max-four-pattern.md +29 -0
  99. package/template/docs/kpi-trend-pattern.md +43 -0
  100. package/template/fontawesome-subset.manifest.json +2 -2
  101. package/template/hooks/use-location-hash.ts +14 -8
  102. package/template/hooks/use-secondary-panel-hub-nav.ts +98 -0
  103. package/template/lib/ask-leo-route-context.ts +24 -0
  104. package/template/lib/collaborator-access.ts +92 -0
  105. package/template/lib/command-menu-config.ts +8 -1
  106. package/template/lib/command-menu-search-data.ts +11 -8
  107. package/template/lib/data-list-display-options.ts +1 -1
  108. package/template/lib/data-view-dashboard-placements-layout.ts +215 -0
  109. package/template/lib/date-filter.ts +1 -0
  110. package/template/lib/dedicated-search-recents.ts +76 -0
  111. package/template/lib/dedicated-search-url.ts +23 -0
  112. package/template/lib/discovery-hub.ts +15 -0
  113. package/template/lib/list-status-badges.ts +1 -21
  114. package/template/lib/mock/navigation.tsx +4 -2
  115. package/template/lib/mock/placements.ts +9 -9
  116. package/template/lib/mock/question-bank-folders.ts +7 -0
  117. package/template/lib/mock/question-bank-header-collaborators.ts +45 -5
  118. package/template/lib/mock/question-bank-inspector.ts +1 -2
  119. package/template/lib/mock/question-bank-kpi.ts +38 -26
  120. package/template/lib/mock/question-bank.ts +43 -16
  121. package/template/lib/question-bank-dedicated-search.ts +19 -0
  122. package/template/lib/question-bank-hub-search.ts +90 -0
  123. package/template/lib/question-bank-nav.ts +322 -6
  124. package/template/lib/question-bank-recent-searches.ts +22 -0
  125. package/template/package.json +0 -1
@@ -1,11 +1,11 @@
1
1
  "use client"
2
2
 
3
3
  /**
4
- * SecondaryPanel — nested sidebar panel that appears between the icon-rail
5
- * sidebar and the main content. When active, the main sidebar collapses to
6
- * icon-only mode; when dismissed, the sidebar expands back.
4
+ * SecondaryPanel — nested rail between the primary icon sidebar and content.
5
+ * Full width shows hub scope nav; **compact** matches the primary sidebar icon rail (`w-12`).
7
6
  *
8
- * Pattern: VS Code / Figma icon-rail + detail panel.
7
+ * Chrome uses {@link NestedSecondaryPanelShell}. Question bank body stays in
8
+ * `question-bank-secondary-nav.tsx` (domain-specific), not duplicated here.
9
9
  */
10
10
 
11
11
  import * as React from "react"
@@ -13,30 +13,65 @@ import { cn } from "@/lib/utils"
13
13
  import { useSidebar } from "@/components/ui/sidebar"
14
14
  import { Tip } from "@/components/ui/tip"
15
15
  import { Button } from "@/components/ui/button"
16
- import {
17
- InputGroup,
18
- InputGroupAddon,
19
- InputGroupInput,
20
- } from "@/components/ui/input-group"
21
16
  import { QuestionBankSecondaryNav } from "@/components/question-bank-secondary-nav"
17
+ import { NestedSecondaryPanelShell } from "@/components/templates/nested-secondary-panel-shell"
18
+ import type { QuestionBankItem } from "@/lib/mock/question-bank"
19
+ import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
20
+
21
+ export type QuestionBankFolderBridge = {
22
+ folders: QuestionBankFolder[]
23
+ onFoldersChange: React.Dispatch<React.SetStateAction<QuestionBankFolder[]>>
24
+ items: QuestionBankItem[]
25
+ onItemsChange: React.Dispatch<React.SetStateAction<QuestionBankItem[]>>
26
+ }
27
+
28
+ export type QuestionBankAccessBridge = {
29
+ openManageAccess: () => void
30
+ }
22
31
 
23
32
  // ─────────────────────────────────────────────────────────────────────────────
24
33
  // Context
25
34
  // ─────────────────────────────────────────────────────────────────────────────
26
35
 
36
+ export type ClosePanelOptions = {
37
+ /**
38
+ * Main app sidebar after the secondary panel closes.
39
+ * - `leave` (default): only clear the active panel — preserves expanded/collapsed primary rail (⌘B, cookie).
40
+ * - `expand`: full primary rail — use only when product explicitly restores the wide rail after dismiss.
41
+ * - `collapse`: icon rail — matches routes that keep the primary rail compact with the panel closed.
42
+ */
43
+ mainSidebar?: "expand" | "collapse" | "leave"
44
+ }
45
+
27
46
  interface SecondaryPanelContextValue {
28
47
  /** Currently active panel id, or null if none */
29
48
  activePanel: string | null
30
- /** Open a panel by id */
49
+ /** Open a panel by id (always exits compact / full-width). */
31
50
  openPanel: (id: string) => void
32
- /** Close the panel */
33
- closePanel: () => void
51
+ /** Close the panel (programmatic / route cleanup). */
52
+ closePanel: (opts?: ClosePanelOptions) => void
53
+ /** Icon-only nested rail while the panel stays “open”. Cleared by {@link openPanel} / {@link closePanel}. */
54
+ secondaryPanelCompact: boolean
55
+ /** Narrow icon rail (primary-sidebar-style); keeps {@link activePanel} mounted. */
56
+ collapseActiveSecondaryPanel: () => void
57
+ /** Question bank folder tree shared with the secondary nav while the hub is mounted. */
58
+ questionBankFolderBridge: QuestionBankFolderBridge | null
59
+ setQuestionBankFolderBridge: (bridge: QuestionBankFolderBridge | null) => void
60
+ /** Opens the hub collaborators sheet from the secondary nav. */
61
+ questionBankAccessBridge: QuestionBankAccessBridge | null
62
+ setQuestionBankAccessBridge: (bridge: QuestionBankAccessBridge | null) => void
34
63
  }
35
64
 
36
65
  const SecondaryPanelContext = React.createContext<SecondaryPanelContextValue>({
37
66
  activePanel: null,
38
67
  openPanel: () => {},
39
68
  closePanel: () => {},
69
+ secondaryPanelCompact: false,
70
+ collapseActiveSecondaryPanel: () => {},
71
+ questionBankFolderBridge: null,
72
+ setQuestionBankFolderBridge: () => {},
73
+ questionBankAccessBridge: null,
74
+ setQuestionBankAccessBridge: () => {},
40
75
  })
41
76
 
42
77
  export function useSecondaryPanel() {
@@ -45,25 +80,59 @@ export function useSecondaryPanel() {
45
80
 
46
81
  export function SecondaryPanelProvider({ children }: { children: React.ReactNode }) {
47
82
  const [activePanel, setActivePanel] = React.useState<string | null>(null)
83
+ const [secondaryPanelCompact, setSecondaryPanelCompact] = React.useState(false)
84
+ const [questionBankFolderBridge, setQuestionBankFolderBridge] =
85
+ React.useState<QuestionBankFolderBridge | null>(null)
86
+ const [questionBankAccessBridge, setQuestionBankAccessBridge] =
87
+ React.useState<QuestionBankAccessBridge | null>(null)
48
88
  const { setOpen } = useSidebar()
49
89
 
50
- const openPanel = React.useCallback((id: string) => {
51
- setActivePanel(id)
52
- setOpen(false) // collapse main sidebar to icon rail
53
- }, [setOpen])
90
+ const openPanel = React.useCallback(
91
+ (id: string) => {
92
+ setSecondaryPanelCompact(false)
93
+ setActivePanel(id)
94
+ setOpen(false) // collapse main sidebar to icon rail
95
+ },
96
+ [setOpen],
97
+ )
54
98
 
55
- const closePanel = React.useCallback(() => {
99
+ const closePanel = React.useCallback((opts?: ClosePanelOptions) => {
100
+ setSecondaryPanelCompact(false)
56
101
  setActivePanel(null)
57
- setOpen(true) // expand main sidebar back
102
+ const mainSidebar = opts?.mainSidebar ?? "leave"
103
+ if (mainSidebar === "leave") return
104
+ if (mainSidebar === "collapse") {
105
+ setOpen(false)
106
+ } else {
107
+ setOpen(true) // expand main sidebar back
108
+ }
58
109
  }, [setOpen])
59
110
 
111
+ const collapseActiveSecondaryPanel = React.useCallback(() => {
112
+ setSecondaryPanelCompact(true)
113
+ }, [])
114
+
60
115
  const value = React.useMemo(
61
116
  () => ({
62
117
  activePanel,
63
118
  openPanel,
64
119
  closePanel,
120
+ secondaryPanelCompact,
121
+ collapseActiveSecondaryPanel,
122
+ questionBankFolderBridge,
123
+ setQuestionBankFolderBridge,
124
+ questionBankAccessBridge,
125
+ setQuestionBankAccessBridge,
65
126
  }),
66
- [activePanel, openPanel, closePanel],
127
+ [
128
+ activePanel,
129
+ openPanel,
130
+ closePanel,
131
+ secondaryPanelCompact,
132
+ collapseActiveSecondaryPanel,
133
+ questionBankFolderBridge,
134
+ questionBankAccessBridge,
135
+ ],
67
136
  )
68
137
 
69
138
  return (
@@ -78,8 +147,11 @@ export function SecondaryPanelProvider({ children }: { children: React.ReactNode
78
147
  // ─────────────────────────────────────────────────────────────────────────────
79
148
 
80
149
  function QuestionBankPanel() {
81
- const { closePanel } = useSecondaryPanel()
82
- const [query, setQuery] = React.useState("")
150
+ const { collapseActiveSecondaryPanel, secondaryPanelCompact } = useSecondaryPanel()
151
+
152
+ if (secondaryPanelCompact) {
153
+ return <QuestionBankSecondaryNav />
154
+ }
83
155
 
84
156
  return (
85
157
  <>
@@ -90,34 +162,19 @@ function QuestionBankPanel() {
90
162
  >
91
163
  Question bank
92
164
  </h2>
93
- <Tip label="Close panel" side="bottom">
165
+ <Tip label="Use icon-only navigation" side="bottom">
94
166
  <Button
95
167
  type="button"
96
168
  size="icon"
97
169
  variant="ghost"
98
- onClick={closePanel}
99
- aria-label="Close panel"
170
+ onClick={() => collapseActiveSecondaryPanel()}
171
+ aria-label="Use icon-only navigation"
100
172
  >
101
- <i className="fa-light fa-xmark" aria-hidden="true" />
173
+ <i className="fa-light fa-angles-left" aria-hidden="true" />
102
174
  </Button>
103
175
  </Tip>
104
176
  </div>
105
- <div className="px-4 pb-2">
106
- <InputGroup className="h-8 min-h-8">
107
- <InputGroupAddon>
108
- <i className="fa-light fa-magnifying-glass size-4 text-[13px]" aria-hidden="true" />
109
- </InputGroupAddon>
110
- <InputGroupInput
111
- type="search"
112
- value={query}
113
- onChange={e => setQuery(e.target.value)}
114
- placeholder="Search"
115
- aria-label="Search"
116
- className="text-sm pe-3"
117
- />
118
- </InputGroup>
119
- </div>
120
- <QuestionBankSecondaryNav query={query} />
177
+ <QuestionBankSecondaryNav />
121
178
  </>
122
179
  )
123
180
  }
@@ -128,31 +185,13 @@ const PANELS: Record<string, React.FC> = {
128
185
  }
129
186
 
130
187
  export function SecondaryPanel() {
131
- const { activePanel } = useSecondaryPanel()
188
+ const { activePanel, secondaryPanelCompact } = useSecondaryPanel()
132
189
  const PanelContent = activePanel ? PANELS[activePanel] : null
133
190
 
134
191
  return (
135
- <nav
136
- aria-label="Secondary navigation"
137
- data-state={activePanel ? "open" : "closed"}
138
- className={cn(
139
- "flex flex-col overflow-hidden",
140
- "transition-[width,margin,opacity] duration-200 ease-linear",
141
- activePanel
142
- ? "w-64 shrink-0 m-2 mx-2 rounded-xl ring-1 ring-sidebar-border shadow-sm relative h-[min(calc(100svh-2rem),800px)] md:sticky md:top-2 md:h-[min(calc(100svh-1rem),800px)]"
143
- : "h-0 min-h-0 shrink overflow-hidden border-0 p-0 m-0 min-w-0 w-0 max-w-0 opacity-0 pointer-events-none",
144
- )}
145
- style={activePanel ? { backgroundColor: "var(--secondary-panel-bg, #FAFBFF)" } : undefined}
146
- >
147
- <div
148
- className={cn(
149
- "flex flex-1 flex-col overflow-y-auto",
150
- activePanel ? "min-w-0" : "hidden min-w-0 w-0 p-0",
151
- )}
152
- >
153
- {PanelContent ? <PanelContent /> : null}
154
- </div>
155
- </nav>
192
+ <NestedSecondaryPanelShell open={Boolean(activePanel)} compact={secondaryPanelCompact}>
193
+ {PanelContent ? <PanelContent /> : null}
194
+ </NestedSecondaryPanelShell>
156
195
  )
157
196
  }
158
197
 
@@ -171,3 +210,31 @@ export function useAutoPanel(panelId: string) {
171
210
  // eslint-disable-next-line react-hooks/exhaustive-deps
172
211
  }, [panelId])
173
212
  }
213
+
214
+ /** Sync hub folder state into the question bank secondary nav while the route is mounted. */
215
+ export function QuestionBankFolderBridge({
216
+ folders,
217
+ onFoldersChange,
218
+ items,
219
+ onItemsChange,
220
+ }: QuestionBankFolderBridge) {
221
+ const { setQuestionBankFolderBridge } = useSecondaryPanel()
222
+
223
+ React.useEffect(() => {
224
+ setQuestionBankFolderBridge({ folders, onFoldersChange, items, onItemsChange })
225
+ return () => setQuestionBankFolderBridge(null)
226
+ }, [folders, onFoldersChange, items, onItemsChange, setQuestionBankFolderBridge])
227
+
228
+ return null
229
+ }
230
+
231
+ export function QuestionBankAccessBridge({ openManageAccess }: QuestionBankAccessBridge) {
232
+ const { setQuestionBankAccessBridge } = useSecondaryPanel()
233
+
234
+ React.useEffect(() => {
235
+ setQuestionBankAccessBridge({ openManageAccess })
236
+ return () => setQuestionBankAccessBridge(null)
237
+ }, [openManageAccess, setQuestionBankAccessBridge])
238
+
239
+ return null
240
+ }
@@ -430,7 +430,9 @@ export function SettingsClient() {
430
430
  </p>
431
431
  </section>
432
432
 
433
- <SettingsAppearanceCard />
433
+ <section id="appearance" className="scroll-mt-20">
434
+ <SettingsAppearanceCard />
435
+ </section>
434
436
 
435
437
  <section id="input-formats" className="scroll-mt-20">
436
438
  <header className="mb-6 space-y-1">
@@ -2,6 +2,8 @@
2
2
 
3
3
  /**
4
4
  * SidebarShell — SidebarProvider with layout-aware widths.
5
+ * Desktop expanded/collapsed is persisted in the `sidebar_state` cookie by `@exxatdesignux/ui`
6
+ * `SidebarProvider` (read on mount + write on toggle).
5
7
  */
6
8
 
7
9
  import * as React from "react"
@@ -63,7 +63,7 @@ function SitesPageHeader({
63
63
  </Button>
64
64
  </DropdownMenuTrigger>
65
65
  </Tip>
66
- <DropdownMenuContent align="end" className="w-52">
66
+ <DropdownMenuContent align="end">
67
67
  <DropdownMenuItem
68
68
  onSelect={() => {
69
69
  window.setTimeout(() => onExport(), 0)
@@ -137,7 +137,7 @@ function buildSitesColumns(): ColumnDef<SiteDirectoryRow>[] {
137
137
  <i className="fa-light fa-ellipsis text-sm" aria-hidden="true" />
138
138
  </Button>
139
139
  </DropdownMenuTrigger>
140
- <DropdownMenuContent align="end" className="w-40">
140
+ <DropdownMenuContent align="end">
141
141
  <DropdownMenuItem asChild>
142
142
  <Link href={row.url} className="flex cursor-pointer items-center gap-2">
143
143
  <i className="fa-light fa-arrow-up-right-from-square" aria-hidden="true" />
@@ -23,8 +23,9 @@ export function SystemBannerSlot() {
23
23
  // collapsed: gap = icon + spacing(4), inset ms-2 → match packages/ui sidebar gap + 0.5rem.
24
24
  // Below md: px-2 matches SidebarInset horizontal inset (high zoom / narrow viewports).
25
25
  // md+: left padding follows the sidebar gap so the banner aligns with the main content area.
26
+ // `z-20` keeps the slot (and promo glow) above the fixed sidebar rail (`z-10`) so paint order does not flatten the shadow.
26
27
  return (
27
- <div className="shrink-0 px-2 pt-1.5 transition-[padding] duration-200 ease-linear md:ps-[var(--sidebar-width)] md:pe-2 md:pt-2 md:group-data-[state=collapsed]/sidebar-wrapper:ps-[calc(var(--sidebar-width-icon)+1rem+0.5rem)]">
28
+ <div className="relative z-20 shrink-0 px-2 pt-1.5 transition-[padding] duration-200 ease-linear md:ps-[var(--sidebar-width)] md:pe-2 md:pt-2 md:group-data-[state=collapsed]/sidebar-wrapper:ps-[calc(var(--sidebar-width-icon)+1rem+0.5rem)]">
28
29
  <SystemBanner
29
30
  variant={config.variant}
30
31
  emphasis={config.emphasis}
@@ -737,7 +737,7 @@ export function TablePropertiesDrawer({
737
737
  Add filter
738
738
  </Button>
739
739
  </DropdownMenuTrigger>
740
- <DropdownMenuContent align="start" className="w-48">
740
+ <DropdownMenuContent align="start">
741
741
  <DropdownMenuLabel className="text-xs">Filter by field</DropdownMenuLabel>
742
742
  <DropdownMenuSeparator />
743
743
  {filterFields.map(f => (
@@ -844,7 +844,7 @@ export function TablePropertiesDrawer({
844
844
  Add sort
845
845
  </Button>
846
846
  </DropdownMenuTrigger>
847
- <DropdownMenuContent align="start" className="w-48">
847
+ <DropdownMenuContent align="start">
848
848
  <DropdownMenuLabel className="text-xs">Sort by field</DropdownMenuLabel>
849
849
  <DropdownMenuSeparator />
850
850
  {sortFieldList.filter(f => !sortRules.some(r => r.fieldKey === f.key)).map(col => (
@@ -1063,7 +1063,7 @@ function ConditionalRulesPanel({
1063
1063
  Add rule
1064
1064
  </Button>
1065
1065
  </DropdownMenuTrigger>
1066
- <DropdownMenuContent align="start" className="w-48">
1066
+ <DropdownMenuContent align="start">
1067
1067
  <DropdownMenuLabel className="text-xs">Rule for column</DropdownMenuLabel>
1068
1068
  <DropdownMenuSeparator />
1069
1069
  {filterFields.map(f => (
@@ -38,7 +38,7 @@ export function DrawerSortCard(props: DrawerSortCardProps) {
38
38
  onClick={onToggleDir}
39
39
  className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-interactive-hover-foreground transition-colors mt-0.5"
40
40
  >
41
- <i className={`fa-light ${rule.direction === "asc" ? "fa-arrow-up-az" : "fa-arrow-down-az"} text-xs`} aria-hidden="true" />
41
+ <i className={`fa-light ${rule.direction === "asc" ? "fa-arrow-up-a-z" : "fa-arrow-down-a-z"} text-xs`} aria-hidden="true" />
42
42
  {rule.direction === "asc" ? "Ascending" : "Descending"}
43
43
  <i className="fa-light fa-chevron-down text-xs" aria-hidden="true" />
44
44
  </button>
@@ -62,7 +62,7 @@ export function TeamPageHeader({
62
62
  </Button>
63
63
  </DropdownMenuTrigger>
64
64
  </Tip>
65
- <DropdownMenuContent align="end" className="w-52">
65
+ <DropdownMenuContent align="end">
66
66
  <DropdownMenuItem
67
67
  onSelect={() => {
68
68
  window.setTimeout(() => onExport(), 0)
@@ -25,7 +25,7 @@ import {
25
25
  saveTeamDashboardLayout,
26
26
  } from "@/components/data-view-dashboard-charts-team"
27
27
  import { KEY_METRICS_KPI_COUNT_DEFAULT } from "@/lib/dashboard-layout-merge"
28
- import type { ChartType, DashboardLayout } from "@/components/data-view-dashboard-charts"
28
+ import type { ChartType, DashboardLayout } from "@/lib/data-view-dashboard-placements-layout"
29
29
  import { TeamListView } from "@/components/team-list-view"
30
30
  import { TeamBoardView, TEAM_BOARD_GROUP_OPTIONS } from "@/components/team-board-view"
31
31
  import { FinderPanelView, type FinderGroup } from "@/components/data-views/finder-panel-view"
@@ -340,7 +340,7 @@ function buildTeamColumns(members: TeamMember[]): ColumnDef<TeamMember>[] {
340
340
  <i className="fa-light fa-ellipsis text-sm" aria-hidden="true" />
341
341
  </Button>
342
342
  </DropdownMenuTrigger>
343
- <DropdownMenuContent align="end" className="w-40">
343
+ <DropdownMenuContent align="end">
344
344
  <DropdownMenuItem onClick={() => window.open(`mailto:${row.email}`)}>
345
345
  <i className="fa-light fa-envelope" aria-hidden="true" />
346
346
  Email
@@ -503,6 +503,11 @@ export const TeamTable = React.forwardRef<
503
503
  },
504
504
  }), [tableState.setSheetOpen])
505
505
 
506
+ const teamPanelFinderGroups = React.useMemo(
507
+ () => buildTeamStatusGroups(tableState.rows),
508
+ [tableState.rows],
509
+ )
510
+
506
511
  const teamBoardGroupKey = TEAM_BOARD_GROUP_OPTIONS.some(
507
512
  o => o.key === displayOptions.boardGroupByColumnKey,
508
513
  )
@@ -596,7 +601,6 @@ export const TeamTable = React.forwardRef<
596
601
  }
597
602
 
598
603
  if (view === "panel") {
599
- const groups = React.useMemo(() => buildTeamStatusGroups(tableState.rows), [tableState.rows])
600
604
  return (
601
605
  <div className="flex min-h-0 flex-1 flex-col">
602
606
  {sharedToolbar}
@@ -604,7 +608,7 @@ export const TeamTable = React.forwardRef<
604
608
  <FinderPanelView<TeamMember>
605
609
  embedded
606
610
  groupsColumnTitle="Status"
607
- groups={groups}
611
+ groups={teamPanelFinderGroups}
608
612
  rows={tableState.rows}
609
613
  getRowId={r => r.id}
610
614
  getRowGroupId={r => r.status}
@@ -0,0 +1,58 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { ListPageViewFrame } from "@/components/data-views"
6
+
7
+ export interface DedicatedSearchLandingTemplateProps {
8
+ /** Page title — string or rich node (e.g. styled heading). */
9
+ title: React.ReactNode
10
+ /** Primary search control (typically {@link DedicatedSearchUrlComposer}). */
11
+ composer: React.ReactNode
12
+ /** Optional block below composer (e.g. {@link DedicatedSearchRecents}). */
13
+ trailing?: React.ReactNode
14
+ /** Forwarded to {@link ListPageViewFrame}. */
15
+ maxWidthClassName?: string
16
+ frameClassName?: string
17
+ gutterClassName?: string
18
+ }
19
+
20
+ const DEFAULT_GUTTER =
21
+ "mx-auto flex min-h-[min(72vh,36rem)] w-full min-w-0 flex-col justify-center gap-0 px-6 py-8 sm:px-8 sm:py-10 md:px-12 md:py-12 lg:px-16"
22
+
23
+ /**
24
+ * Centered dedicated-search landing — empty `?q=` shell (hero title + composer + optional trailing).
25
+ */
26
+ export function DedicatedSearchLandingTemplate({
27
+ title,
28
+ composer,
29
+ trailing,
30
+ maxWidthClassName = "max-w-5xl",
31
+ frameClassName = "min-w-0",
32
+ gutterClassName = DEFAULT_GUTTER,
33
+ }: DedicatedSearchLandingTemplateProps) {
34
+ return (
35
+ <ListPageViewFrame
36
+ maxWidthClassName={maxWidthClassName}
37
+ className={frameClassName}
38
+ gutterClassName={gutterClassName}
39
+ >
40
+ <header className="min-w-0">
41
+ {typeof title === "string" ? (
42
+ <h1
43
+ className="text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl"
44
+ style={{ fontFamily: "var(--font-heading)" }}
45
+ >
46
+ {title}
47
+ </h1>
48
+ ) : (
49
+ title
50
+ )}
51
+ </header>
52
+
53
+ <div className="min-w-0 mt-6 sm:mt-8">{composer}</div>
54
+
55
+ {trailing ? <div className="min-w-0 mt-10 sm:mt-12 md:mt-14 lg:mt-16">{trailing}</div> : null}
56
+ </ListPageViewFrame>
57
+ )
58
+ }
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ /** Apply to the hub content wrapper when showing results (list surface under composer). */
6
+ export const DEDICATED_SEARCH_RESULTS_OUTER_CONTENT_CLASSNAME =
7
+ "border-t border-border/40 bg-gradient-to-b from-muted/25 via-background to-background"
8
+
9
+ export interface DedicatedSearchResultsHeaderChromeProps {
10
+ children: ReactNode
11
+ className?: string
12
+ }
13
+
14
+ /**
15
+ * Muted strip wrapping page chrome + composer on the results branch (below site header).
16
+ */
17
+ export function DedicatedSearchResultsHeaderChrome({ children, className }: DedicatedSearchResultsHeaderChromeProps) {
18
+ return <div className={cn("border-b border-border/50 bg-muted/15", className)}>{children}</div>
19
+ }