@exxatdesignux/ui 0.2.14 → 0.2.16
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 +20 -0
- package/consumer-extras/cursor-skills/exxat-collaboration-access/SKILL.md +3 -1
- package/consumer-extras/cursor-skills/exxat-ds-skill/SKILL.md +3 -0
- package/consumer-extras/cursor-skills/exxat-primary-nav-secondary-panel/SKILL.md +3 -1
- package/consumer-extras/patterns/collaboration-access-pattern.md +2 -0
- package/package.json +1 -1
- package/src/components/ui/dropdown-menu.tsx +2 -0
- package/src/components/ui/popover.tsx +2 -2
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tooltip.tsx +7 -1
- package/src/globals.css +27 -2
- package/src/theme.css +4 -2
- package/template/AGENTS.md +6 -4
- package/template/app/(app)/question-bank/layout.tsx +11 -4
- package/template/app/globals.css +34 -2
- package/template/components/app-sidebar.tsx +89 -41
- package/template/components/ask-leo-sidebar.tsx +1 -2
- package/template/components/compliance-board-view.tsx +11 -3
- package/template/components/compliance-list-view.tsx +16 -3
- package/template/components/compliance-table.tsx +5 -1
- package/template/components/data-table/index.tsx +25 -11
- package/template/components/data-views/finder-panel-view.tsx +2 -2
- package/template/components/data-views/index.ts +19 -0
- package/template/components/data-views/list-page-split-details-placeholder.tsx +3 -3
- package/template/components/data-views/list-page-split-hub-tokens.ts +1 -1
- package/template/components/data-views/list-page-tree-column-header.tsx +1 -1
- package/template/components/data-views/outline-tree-menu.tsx +157 -0
- package/template/components/data-views/question-bank-folder-tree-branch.tsx +210 -0
- package/template/components/exxat-product-logo.tsx +11 -72
- package/template/components/folder-details-shell.tsx +1 -1
- package/template/components/hub-tree-panel-view.tsx +88 -80
- package/template/components/key-metrics.tsx +50 -13
- package/template/components/page-header.tsx +19 -10
- package/template/components/product-switcher.tsx +1 -4
- package/template/components/question-bank-board-view.tsx +11 -2
- package/template/components/question-bank-client.tsx +111 -69
- package/template/components/question-bank-list-view.tsx +12 -1
- package/template/components/question-bank-page-header.tsx +18 -2
- package/template/components/question-bank-secondary-nav.tsx +12 -225
- package/template/components/question-bank-table.tsx +6 -1
- package/template/components/secondary-panel.tsx +1 -1
- package/template/components/site-header.tsx +21 -2
- package/template/components/team-board-view.tsx +11 -3
- package/template/components/team-list-view.tsx +16 -3
- package/template/components/team-table.tsx +6 -2
- package/template/components/templates/dedicated-search-landing-template.tsx +86 -20
- package/template/components/templates/list-page.tsx +1 -3
- package/template/components/templates/nested-secondary-panel-shell.tsx +3 -4
- package/template/docs/collaboration-access-pattern.md +2 -0
- package/template/docs/question-bank-hub-header-pattern.md +25 -0
- package/template/hooks/use-secondary-panel-hub-nav.ts +17 -1
- package/template/lib/mock/navigation.tsx +30 -1
- package/template/lib/question-bank-nav.ts +26 -0
- package/template/package.json +3 -3
- package/template/components/command-menu-01.tsx +0 -133
- package/template/components/command-menu-02.tsx +0 -386
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Central outline-tree chrome — mirrors shadcn/ui **Sidebar** file-tree structure
|
|
5
|
+
* (`SidebarMenu` → `SidebarMenuItem` + `Collapsible` → `SidebarMenuSub` → rows) without
|
|
6
|
+
* coupling to `useSidebar`.
|
|
7
|
+
*
|
|
8
|
+
* - **`guideLayout="inset"`** — same rhythm as `SidebarMenuSub` (`mx-3.5` + `translate-x-px`).
|
|
9
|
+
* - **`guideLayout="chevronRail"`** — use with **`OutlineTreeCollapsibleContentRail`**: a **`w-6`**
|
|
10
|
+
* spacer lines up the vertical guide with the **horizontal center** of a **`size-8`** chevron
|
|
11
|
+
* when the folder row uses **`px-2`** (8px padding + 16px half of 32px chevron hit target).
|
|
12
|
+
*
|
|
13
|
+
* @see packages/ui/src/components/ui/sidebar.tsx — `SidebarMenuSub`, `SidebarMenuSubItem`
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as React from "react"
|
|
17
|
+
import { CollapsibleContent } from "@/components/ui/collapsible"
|
|
18
|
+
import { cn } from "@/lib/utils"
|
|
19
|
+
|
|
20
|
+
export type OutlineTreeSurface = "sidebar" | "panel"
|
|
21
|
+
|
|
22
|
+
export type OutlineTreeGuideLayout = "inset" | "chevronRail"
|
|
23
|
+
|
|
24
|
+
const outlineTreeSubInsetClass: Record<OutlineTreeSurface, string> = {
|
|
25
|
+
sidebar:
|
|
26
|
+
"mx-3.5 flex min-w-0 list-none translate-x-px flex-col gap-1 border-s border-sidebar-border px-2.5 py-0.5 rtl:-translate-x-px",
|
|
27
|
+
panel:
|
|
28
|
+
"mx-3.5 flex min-w-0 list-none translate-x-px flex-col gap-1 border-s border-border/60 px-2.5 py-0.5 rtl:-translate-x-px",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const outlineTreeSubChevronRailClass: Record<OutlineTreeSurface, string> = {
|
|
32
|
+
sidebar:
|
|
33
|
+
"flex min-w-0 flex-1 list-none flex-col gap-1 border-s border-sidebar-border py-0.5 ps-2.5",
|
|
34
|
+
panel: "flex min-w-0 flex-1 list-none flex-col gap-1 border-s border-border/60 py-0.5 ps-2.5",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Pull row content onto the guide line — matches `SidebarMenuSubButton` horizontal nudge (inset layout only). */
|
|
38
|
+
export const OUTLINE_TREE_SUB_ROW_SHIFT_CLASS = "-translate-x-px rtl:translate-x-px"
|
|
39
|
+
|
|
40
|
+
/** `CollapsibleContent` row: spacer width = `px-2` (8px) + half of `size-8` chevron (16px) → guide under chevron center. */
|
|
41
|
+
export const OUTLINE_TREE_COLLAPSIBLE_CONTENT_RAIL_CLASS = "flex min-w-0 w-full"
|
|
42
|
+
|
|
43
|
+
/** Spacer column — keep in sync with folder row `px-2` + `size-8` chevron. */
|
|
44
|
+
export const OUTLINE_TREE_CHEVRON_GUIDE_SPACER_CLASS = "w-6 shrink-0"
|
|
45
|
+
|
|
46
|
+
/** Wrap `OutlineTreeSub` with `guideLayout="chevronRail"` so the vertical border meets the chevron center. */
|
|
47
|
+
export function OutlineTreeCollapsibleContentRail({
|
|
48
|
+
className,
|
|
49
|
+
children,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof CollapsibleContent>) {
|
|
52
|
+
return (
|
|
53
|
+
<CollapsibleContent
|
|
54
|
+
className={cn(OUTLINE_TREE_COLLAPSIBLE_CONTENT_RAIL_CLASS, className)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<div className={OUTLINE_TREE_CHEVRON_GUIDE_SPACER_CLASS} aria-hidden />
|
|
58
|
+
{children}
|
|
59
|
+
</CollapsibleContent>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Nested list under a folder — vertical guide + indent. */
|
|
64
|
+
export function OutlineTreeSub({
|
|
65
|
+
surface = "panel",
|
|
66
|
+
guideLayout = "inset",
|
|
67
|
+
className,
|
|
68
|
+
...props
|
|
69
|
+
}: React.ComponentProps<"ul"> & {
|
|
70
|
+
surface?: OutlineTreeSurface
|
|
71
|
+
guideLayout?: OutlineTreeGuideLayout
|
|
72
|
+
}) {
|
|
73
|
+
return (
|
|
74
|
+
<ul
|
|
75
|
+
data-slot="outline-tree-sub"
|
|
76
|
+
data-guide-layout={guideLayout}
|
|
77
|
+
className={cn(
|
|
78
|
+
guideLayout === "inset" && outlineTreeSubInsetClass[surface],
|
|
79
|
+
guideLayout === "chevronRail" && outlineTreeSubChevronRailClass[surface],
|
|
80
|
+
className,
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Root or nested branch list — matches `SidebarMenu` spacing. */
|
|
88
|
+
export function OutlineTreeMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
|
89
|
+
return (
|
|
90
|
+
<ul
|
|
91
|
+
data-slot="outline-tree-menu"
|
|
92
|
+
className={cn("flex w-full min-w-0 list-none flex-col gap-0", className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Expandable folder row wrapper — matches `SidebarMenuItem`. */
|
|
99
|
+
export function OutlineTreeMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
100
|
+
return (
|
|
101
|
+
<li
|
|
102
|
+
data-slot="outline-tree-menu-item"
|
|
103
|
+
className={cn("group/menu-item relative min-w-0 w-full list-none", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Leaf / nested row inside `OutlineTreeSub` — matches `SidebarMenuSubItem`. */
|
|
110
|
+
export function OutlineTreeSubItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
111
|
+
return (
|
|
112
|
+
<li
|
|
113
|
+
data-slot="outline-tree-sub-item"
|
|
114
|
+
className={cn("group/menu-sub-item relative min-w-0 w-full list-none", className)}
|
|
115
|
+
{...props}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface OutlineTreeLeafButtonProps extends React.ComponentProps<"button"> {
|
|
121
|
+
surface?: OutlineTreeSurface
|
|
122
|
+
isActive?: boolean
|
|
123
|
+
/** Inset `OutlineTreeSub` only — nudge like `SidebarMenuSubButton`. Ignored when parent uses `chevronRail`. */
|
|
124
|
+
subGuideAlign?: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Selectable leaf row (file / terminal row) — `SidebarMenuSubButton`–aligned rhythm. */
|
|
128
|
+
export function OutlineTreeLeafButton({
|
|
129
|
+
surface = "panel",
|
|
130
|
+
isActive = false,
|
|
131
|
+
subGuideAlign = false,
|
|
132
|
+
className,
|
|
133
|
+
...props
|
|
134
|
+
}: OutlineTreeLeafButtonProps) {
|
|
135
|
+
return (
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
data-active={isActive || undefined}
|
|
139
|
+
className={cn(
|
|
140
|
+
"flex min-h-8 w-full min-w-0 cursor-pointer select-none items-center gap-2 overflow-hidden rounded-md px-2 text-start text-sm outline-none ring-ring focus-visible:ring-2 focus-visible:ring-inset [&>svg]:size-4 [&>svg]:shrink-0",
|
|
141
|
+
subGuideAlign && OUTLINE_TREE_SUB_ROW_SHIFT_CLASS,
|
|
142
|
+
surface === "panel" &&
|
|
143
|
+
cn(
|
|
144
|
+
"text-foreground hover:bg-muted/50",
|
|
145
|
+
isActive && "bg-accent font-medium text-accent-foreground",
|
|
146
|
+
),
|
|
147
|
+
surface === "sidebar" &&
|
|
148
|
+
cn(
|
|
149
|
+
"text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
150
|
+
isActive && "bg-sidebar-accent font-medium text-sidebar-accent-foreground",
|
|
151
|
+
),
|
|
152
|
+
className,
|
|
153
|
+
)}
|
|
154
|
+
{...props}
|
|
155
|
+
/>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Question bank **Folders** tree — shared outline primitives (`OutlineTree*`) so the rail
|
|
5
|
+
* matches `HubTreePanelView` and shadcn sidebar file-tree rhythm.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as React from "react"
|
|
9
|
+
import Link from "next/link"
|
|
10
|
+
import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible"
|
|
11
|
+
import { Button } from "@/components/ui/button"
|
|
12
|
+
import {
|
|
13
|
+
DropdownMenu,
|
|
14
|
+
DropdownMenuContent,
|
|
15
|
+
DropdownMenuItem,
|
|
16
|
+
DropdownMenuSeparator,
|
|
17
|
+
DropdownMenuTrigger,
|
|
18
|
+
} from "@/components/ui/dropdown-menu"
|
|
19
|
+
import { Tip } from "@/components/ui/tip"
|
|
20
|
+
import { cn } from "@/lib/utils"
|
|
21
|
+
import {
|
|
22
|
+
OutlineTreeCollapsibleContentRail,
|
|
23
|
+
OutlineTreeMenuItem,
|
|
24
|
+
OutlineTreeSub,
|
|
25
|
+
} from "@/components/data-views/outline-tree-menu"
|
|
26
|
+
import type { QuestionBankFolder } from "@/lib/mock/question-bank-folders"
|
|
27
|
+
import { QUESTION_BANK_FOLDER_ICON_COLORS } from "@/lib/mock/question-bank-folders"
|
|
28
|
+
import {
|
|
29
|
+
isQuestionBankNavActive,
|
|
30
|
+
questionBankHubScopeHref,
|
|
31
|
+
type QuestionBankNavState,
|
|
32
|
+
} from "@/lib/question-bank-nav"
|
|
33
|
+
|
|
34
|
+
export interface QuestionBankFolderTreeBranchProps {
|
|
35
|
+
folder: QuestionBankFolder
|
|
36
|
+
folders: QuestionBankFolder[]
|
|
37
|
+
pathname: string
|
|
38
|
+
hubSearchParams: URLSearchParams
|
|
39
|
+
nav: QuestionBankNavState
|
|
40
|
+
canManageFolders: boolean
|
|
41
|
+
canManageAccess: boolean
|
|
42
|
+
onAddSubfolder: (parentId: string) => void
|
|
43
|
+
onCustomizeFolder: (folder: QuestionBankFolder) => void
|
|
44
|
+
onManageAccess: () => void
|
|
45
|
+
onDeleteFolder: (folder: QuestionBankFolder) => void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function QuestionBankFolderTreeBranch({
|
|
49
|
+
folder,
|
|
50
|
+
folders,
|
|
51
|
+
pathname,
|
|
52
|
+
hubSearchParams,
|
|
53
|
+
nav,
|
|
54
|
+
canManageFolders,
|
|
55
|
+
canManageAccess,
|
|
56
|
+
onAddSubfolder,
|
|
57
|
+
onCustomizeFolder,
|
|
58
|
+
onManageAccess,
|
|
59
|
+
onDeleteFolder,
|
|
60
|
+
}: QuestionBankFolderTreeBranchProps) {
|
|
61
|
+
const childFolders = React.useMemo(
|
|
62
|
+
() =>
|
|
63
|
+
folders
|
|
64
|
+
.filter(f => f.parentId === folder.id)
|
|
65
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
66
|
+
[folders, folder.id],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const hasSubfolders = childFolders.length > 0
|
|
70
|
+
const isRootFolder = folder.parentId === null
|
|
71
|
+
|
|
72
|
+
const folderHref = questionBankHubScopeHref(pathname, hubSearchParams, {
|
|
73
|
+
scope: "folder",
|
|
74
|
+
folderId: folder.id,
|
|
75
|
+
})
|
|
76
|
+
const folderActive = isQuestionBankNavActive(pathname, nav, "folder", folder.id)
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Collapsible defaultOpen={isRootFolder} className="group/collapsible">
|
|
80
|
+
<div
|
|
81
|
+
className={cn(
|
|
82
|
+
"group/row flex min-w-0 items-center rounded-md px-2 transition-colors",
|
|
83
|
+
folderActive
|
|
84
|
+
? "bg-sidebar-accent font-medium text-sidebar-accent-foreground ring-1 ring-inset ring-sidebar-border/80"
|
|
85
|
+
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{hasSubfolders ? (
|
|
89
|
+
<CollapsibleTrigger asChild>
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
className="flex size-8 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
93
|
+
aria-label={`${folder.name} — expand or collapse`}
|
|
94
|
+
>
|
|
95
|
+
<i
|
|
96
|
+
className="fa-light fa-chevron-right text-xs transition-transform duration-150 group-data-[state=open]/collapsible:rotate-90"
|
|
97
|
+
aria-hidden
|
|
98
|
+
/>
|
|
99
|
+
</button>
|
|
100
|
+
</CollapsibleTrigger>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="size-8 shrink-0" aria-hidden />
|
|
103
|
+
)}
|
|
104
|
+
<Tip label={folder.name} side="right">
|
|
105
|
+
<Link
|
|
106
|
+
href={folderHref}
|
|
107
|
+
scroll={false}
|
|
108
|
+
aria-current={folderActive ? "page" : undefined}
|
|
109
|
+
className={cn(
|
|
110
|
+
"flex min-w-0 flex-1 items-center gap-2 py-1.5 text-left text-sm transition-colors",
|
|
111
|
+
"rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
112
|
+
!folderActive && "text-sidebar-foreground",
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
<i
|
|
116
|
+
className={cn(
|
|
117
|
+
"fa-light inline-flex size-4 shrink-0 items-center justify-center text-sm leading-none",
|
|
118
|
+
folder.icon,
|
|
119
|
+
QUESTION_BANK_FOLDER_ICON_COLORS[folder.colorKey],
|
|
120
|
+
)}
|
|
121
|
+
aria-hidden
|
|
122
|
+
/>
|
|
123
|
+
<span className="min-w-0 flex-1 truncate leading-tight">{folder.name}</span>
|
|
124
|
+
{hasSubfolders ? (
|
|
125
|
+
<span className="shrink-0 text-xs tabular-nums text-muted-foreground">{childFolders.length}</span>
|
|
126
|
+
) : null}
|
|
127
|
+
</Link>
|
|
128
|
+
</Tip>
|
|
129
|
+
{canManageFolders ? (
|
|
130
|
+
<DropdownMenu>
|
|
131
|
+
<Tip label={`Folder actions for ${folder.name}`} side="right">
|
|
132
|
+
<DropdownMenuTrigger asChild>
|
|
133
|
+
<Button
|
|
134
|
+
type="button"
|
|
135
|
+
size="icon-xs"
|
|
136
|
+
variant="ghost"
|
|
137
|
+
aria-label={`Folder actions for ${folder.name}`}
|
|
138
|
+
className="shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover/row:opacity-100 group-focus-within/row:opacity-100"
|
|
139
|
+
onClick={event => event.stopPropagation()}
|
|
140
|
+
>
|
|
141
|
+
<i className="fa-light fa-ellipsis text-xs" aria-hidden="true" />
|
|
142
|
+
</Button>
|
|
143
|
+
</DropdownMenuTrigger>
|
|
144
|
+
</Tip>
|
|
145
|
+
<DropdownMenuContent align="end">
|
|
146
|
+
<DropdownMenuItem
|
|
147
|
+
onSelect={() => {
|
|
148
|
+
window.setTimeout(() => onAddSubfolder(folder.id), 0)
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<i className="fa-light fa-plus text-xs" aria-hidden="true" />
|
|
152
|
+
Add folder
|
|
153
|
+
</DropdownMenuItem>
|
|
154
|
+
<DropdownMenuItem
|
|
155
|
+
onSelect={() => {
|
|
156
|
+
window.setTimeout(() => onCustomizeFolder(folder), 0)
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<i className="fa-light fa-wand-magic-sparkles text-xs" aria-hidden="true" />
|
|
160
|
+
Customize
|
|
161
|
+
</DropdownMenuItem>
|
|
162
|
+
<DropdownMenuItem
|
|
163
|
+
disabled={!canManageAccess}
|
|
164
|
+
onSelect={() => {
|
|
165
|
+
window.setTimeout(() => onManageAccess(), 0)
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<i className="fa-light fa-user-gear text-xs" aria-hidden="true" />
|
|
169
|
+
Manage access
|
|
170
|
+
</DropdownMenuItem>
|
|
171
|
+
<DropdownMenuSeparator />
|
|
172
|
+
<DropdownMenuItem
|
|
173
|
+
variant="destructive"
|
|
174
|
+
onSelect={() => {
|
|
175
|
+
window.setTimeout(() => onDeleteFolder(folder), 0)
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
<i className="fa-light fa-trash text-xs" aria-hidden="true" />
|
|
179
|
+
Delete
|
|
180
|
+
</DropdownMenuItem>
|
|
181
|
+
</DropdownMenuContent>
|
|
182
|
+
</DropdownMenu>
|
|
183
|
+
) : null}
|
|
184
|
+
</div>
|
|
185
|
+
{hasSubfolders ? (
|
|
186
|
+
<OutlineTreeCollapsibleContentRail>
|
|
187
|
+
<OutlineTreeSub surface="sidebar" guideLayout="chevronRail">
|
|
188
|
+
{childFolders.map(child => (
|
|
189
|
+
<OutlineTreeMenuItem key={child.id}>
|
|
190
|
+
<QuestionBankFolderTreeBranch
|
|
191
|
+
folder={child}
|
|
192
|
+
folders={folders}
|
|
193
|
+
pathname={pathname}
|
|
194
|
+
hubSearchParams={hubSearchParams}
|
|
195
|
+
nav={nav}
|
|
196
|
+
canManageFolders={canManageFolders}
|
|
197
|
+
canManageAccess={canManageAccess}
|
|
198
|
+
onAddSubfolder={onAddSubfolder}
|
|
199
|
+
onCustomizeFolder={onCustomizeFolder}
|
|
200
|
+
onManageAccess={onManageAccess}
|
|
201
|
+
onDeleteFolder={onDeleteFolder}
|
|
202
|
+
/>
|
|
203
|
+
</OutlineTreeMenuItem>
|
|
204
|
+
))}
|
|
205
|
+
</OutlineTreeSub>
|
|
206
|
+
</OutlineTreeCollapsibleContentRail>
|
|
207
|
+
) : null}
|
|
208
|
+
</Collapsible>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Inline product wordmarks — "Exxat" letters are gray (#273441) on light,
|
|
5
5
|
* soft grey (#A8B2BA) on dark/HC; product name letters (One/Prism) use
|
|
6
|
-
* brand pink (#E31B79) by default.
|
|
6
|
+
* brand pink (#E31B79) by default.
|
|
7
|
+
*
|
|
8
|
+
* **Circular mark** (`ExxatProductMark`): always the Exxat One mark; wordmarks stay per-product.
|
|
7
9
|
*
|
|
8
10
|
* `variant="mutedSuffix"` (sidebar / switcher): in **dark** mode only, the full wordmark
|
|
9
11
|
* uses one `muted-foreground`. In **light** mode the original pink + grey wordmark is kept.
|
|
@@ -168,12 +170,16 @@ function ExxatOneLogo({
|
|
|
168
170
|
function ExxatOneMark({ className, ...props }: React.ComponentProps<"svg">) {
|
|
169
171
|
const ready = useBrowserPaintReady()
|
|
170
172
|
const paint0 = useMarkGradientId("one-mark")
|
|
171
|
-
const sharedClass = cn(
|
|
173
|
+
const sharedClass = cn(
|
|
174
|
+
"box-border block aspect-square size-7 shrink-0 flex-none object-contain",
|
|
175
|
+
className,
|
|
176
|
+
)
|
|
172
177
|
|
|
173
178
|
if (!ready) {
|
|
174
179
|
return (
|
|
175
180
|
<svg
|
|
176
181
|
viewBox="0 8.25 147 147"
|
|
182
|
+
preserveAspectRatio="xMidYMid meet"
|
|
177
183
|
fill="none"
|
|
178
184
|
xmlns="http://www.w3.org/2000/svg"
|
|
179
185
|
data-product-logo-mark
|
|
@@ -188,6 +194,7 @@ function ExxatOneMark({ className, ...props }: React.ComponentProps<"svg">) {
|
|
|
188
194
|
return (
|
|
189
195
|
<svg
|
|
190
196
|
viewBox="0 8.25 147 147"
|
|
197
|
+
preserveAspectRatio="xMidYMid meet"
|
|
191
198
|
fill="none"
|
|
192
199
|
xmlns="http://www.w3.org/2000/svg"
|
|
193
200
|
data-product-logo-mark
|
|
@@ -229,81 +236,13 @@ function ExxatOneMark({ className, ...props }: React.ComponentProps<"svg">) {
|
|
|
229
236
|
)
|
|
230
237
|
}
|
|
231
238
|
|
|
232
|
-
function ExxatPrismMark({ className, ...props }: React.ComponentProps<"svg">) {
|
|
233
|
-
const ready = useBrowserPaintReady()
|
|
234
|
-
const paint0 = useMarkGradientId("prism-mark")
|
|
235
|
-
const sharedClass = cn("block shrink-0", className)
|
|
236
|
-
|
|
237
|
-
if (!ready) {
|
|
238
|
-
return (
|
|
239
|
-
<svg
|
|
240
|
-
viewBox="0 8.25 147 147"
|
|
241
|
-
fill="none"
|
|
242
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
243
|
-
data-product-logo-mark
|
|
244
|
-
className={sharedClass}
|
|
245
|
-
aria-hidden
|
|
246
|
-
suppressHydrationWarning
|
|
247
|
-
{...props}
|
|
248
|
-
/>
|
|
249
|
-
)
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<svg
|
|
254
|
-
viewBox="0 8.25 147 147"
|
|
255
|
-
fill="none"
|
|
256
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
257
|
-
data-product-logo-mark
|
|
258
|
-
className={sharedClass}
|
|
259
|
-
aria-hidden
|
|
260
|
-
suppressHydrationWarning
|
|
261
|
-
{...props}
|
|
262
|
-
>
|
|
263
|
-
<path
|
|
264
|
-
d="M73.4939 155.238C114.084 155.238 146.988 122.334 146.988 81.7439C146.988 41.1544 114.084 8.25 73.4939 8.25C32.9044 8.25 0 41.1544 0 81.7439C0 122.334 32.9044 155.238 73.4939 155.238Z"
|
|
265
|
-
fill={`url(#${paint0})`}
|
|
266
|
-
/>
|
|
267
|
-
<path
|
|
268
|
-
d="M0.490234 89.3652C3.79023 115.675 23.9702 136.725 49.8502 141.355L84.4302 110.265V98.6852H71.5502L84.4302 87.1052V75.5252H71.5502L84.4302 63.9452V52.3652H41.6602L0.490234 89.3652Z"
|
|
269
|
-
fill="#BE1E6D"
|
|
270
|
-
/>
|
|
271
|
-
<path d="M84.4397 110.265H41.6597L48.3497 98.6851H84.4397V110.265Z" fill="white" />
|
|
272
|
-
<path d="M84.4397 63.935H48.3497L41.6597 52.355H84.4397V63.935Z" fill="white" />
|
|
273
|
-
<path d="M84.44 87.0951H55.04L58.38 81.3051L55.04 75.5151H84.44V87.0951Z" fill="white" />
|
|
274
|
-
<path d="M32.3198 75.5151H55.0398L48.3498 63.9351H32.3198V75.5151Z" fill="white" />
|
|
275
|
-
<path d="M32.3198 98.6852H48.3498L55.0398 87.0952H32.3198V98.6852Z" fill="white" />
|
|
276
|
-
<defs>
|
|
277
|
-
<linearGradient
|
|
278
|
-
id={paint0}
|
|
279
|
-
x1="23.38"
|
|
280
|
-
y1="125.015"
|
|
281
|
-
x2="96.57"
|
|
282
|
-
y2="39.8551"
|
|
283
|
-
gradientUnits="userSpaceOnUse"
|
|
284
|
-
>
|
|
285
|
-
<stop offset="0.04" stopColor="#E21C79" />
|
|
286
|
-
<stop offset="0.65" stopColor="#E21E7B" />
|
|
287
|
-
<stop offset="0.73" stopColor="#E42880" />
|
|
288
|
-
<stop offset="0.88" stopColor="#E9448E" />
|
|
289
|
-
<stop offset="1" stopColor="#EF609D" />
|
|
290
|
-
</linearGradient>
|
|
291
|
-
</defs>
|
|
292
|
-
</svg>
|
|
293
|
-
)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
239
|
export interface ExxatProductMarkProps {
|
|
297
240
|
product: Product
|
|
298
241
|
className?: string
|
|
299
242
|
}
|
|
300
243
|
|
|
301
|
-
export function ExxatProductMark({ product, className }: ExxatProductMarkProps) {
|
|
302
|
-
return
|
|
303
|
-
<ExxatPrismMark className={className} />
|
|
304
|
-
) : (
|
|
305
|
-
<ExxatOneMark className={className} />
|
|
306
|
-
)
|
|
244
|
+
export function ExxatProductMark({ product: _product, className }: ExxatProductMarkProps) {
|
|
245
|
+
return <ExxatOneMark className={className} />
|
|
307
246
|
}
|
|
308
247
|
|
|
309
248
|
function ExxatPrismLogo({
|
|
@@ -102,7 +102,7 @@ export function FolderDetailsShell({
|
|
|
102
102
|
|
|
103
103
|
return (
|
|
104
104
|
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-card">
|
|
105
|
-
<header className="shrink-0 border-b border-border/60 bg-
|
|
105
|
+
<header className="shrink-0 border-b border-border/60 bg-card px-4 pb-4 pt-4">
|
|
106
106
|
<div className="flex items-start justify-between gap-3">
|
|
107
107
|
<div className="flex min-w-0 flex-1 items-start gap-3">
|
|
108
108
|
<OsFolderGlyph
|