@exxatdesignux/ui 0.2.9 → 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 +1 -1
- 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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SecondaryPanel — nested
|
|
5
|
-
*
|
|
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
|
-
*
|
|
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(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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 {
|
|
82
|
-
|
|
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="
|
|
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={
|
|
99
|
-
aria-label="
|
|
170
|
+
onClick={() => collapseActiveSecondaryPanel()}
|
|
171
|
+
aria-label="Use icon-only navigation"
|
|
100
172
|
>
|
|
101
|
-
<i className="fa-light fa-
|
|
173
|
+
<i className="fa-light fa-angles-left" aria-hidden="true" />
|
|
102
174
|
</Button>
|
|
103
175
|
</Tip>
|
|
104
176
|
</div>
|
|
105
|
-
<
|
|
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
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
<
|
|
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">
|
|
@@ -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"
|
|
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"
|
|
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"
|
|
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"
|
|
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-
|
|
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>
|
|
@@ -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 "@/
|
|
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"
|
|
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={
|
|
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
|
+
}
|