@exegia/corpora-ui 0.10.0 → 0.12.0
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/dist-lib/components/agents/__tests__/ai-sidebar.test.d.ts +1 -0
- package/dist-lib/components/agents/ai-sidebar.d.ts +2 -0
- package/dist-lib/components/agents/index.d.ts +12 -0
- package/dist-lib/components/agents/marquee-label.d.ts +4 -0
- package/dist-lib/components/agents/sidebar-icon.d.ts +2 -0
- package/dist-lib/components/agents/sidebar-row.d.ts +2 -0
- package/dist-lib/components/agents/type.d.ts +83 -0
- package/dist-lib/components/agents/use-ai-sidebar.d.ts +31 -0
- package/dist-lib/components/agents/utils.d.ts +16 -0
- package/dist-lib/components/beste/piece/browser-frame.d.ts +2 -0
- package/dist-lib/components/beste/piece/type.d.ts +9 -0
- package/dist-lib/components/beste/piece/utils.d.ts +3 -0
- package/dist-lib/components/blocks/auth/signup-block.d.ts +5 -5
- package/dist-lib/components/blocks/nav/sidebar-block.d.ts +3 -56
- package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +3 -0
- package/dist-lib/components/blocks/nav/types.d.ts +60 -0
- package/dist-lib/components/blocks/nav/utils.d.ts +1 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +6 -1
- package/dist-lib/components/blocks/shell/index.d.ts +1 -0
- package/dist-lib/components/blocks/shell/shell-layout.d.ts +3 -0
- package/dist-lib/components/blocks/shell/type.d.ts +26 -0
- package/dist-lib/components/blocks/shell/utils.d.ts +1 -0
- package/dist-lib/components/composed/social-providers.d.ts +9 -1
- package/dist-lib/components/docs/demo-controls.d.ts +4 -4
- package/dist-lib/components/motion/animated-sidebar.d.ts +12 -1
- package/dist-lib/components/motion/popover-morph.d.ts +35 -0
- package/dist-lib/components/motion/popover-position.d.ts +15 -0
- package/dist-lib/components/ui/badge.d.ts +7 -0
- package/dist-lib/components/ui/select.d.ts +30 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.js +644 -524
- package/dist-lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/agents/__tests__/ai-sidebar.test.tsx +93 -0
- package/src/components/agents/ai-sidebar.tsx +118 -0
- package/src/components/agents/index.ts +14 -0
- package/src/components/agents/marquee-label.tsx +59 -0
- package/src/components/agents/sidebar-icon.tsx +14 -0
- package/src/components/agents/sidebar-row.tsx +309 -0
- package/src/components/agents/type.ts +95 -0
- package/src/components/agents/use-ai-sidebar.ts +386 -0
- package/src/components/agents/utils.ts +142 -0
- package/src/components/beste/piece/browser-frame.tsx +55 -0
- package/src/components/beste/piece/type.ts +11 -0
- package/src/components/beste/piece/utils.ts +21 -0
- package/src/components/blocks/auth/linked-accounts-block.tsx +55 -57
- package/src/components/blocks/auth/passkey-manager-block.tsx +88 -81
- package/src/components/blocks/auth/signup-block.tsx +6 -6
- package/src/components/blocks/nav/sidebar-block.tsx +25 -155
- package/src/components/blocks/nav/sidebar-nav-row.tsx +79 -0
- package/src/components/blocks/nav/types.ts +65 -0
- package/src/components/blocks/nav/utils.ts +4 -0
- package/src/components/blocks/profile/profile-card-block.tsx +17 -6
- package/src/components/blocks/shell/index.ts +1 -0
- package/src/components/blocks/shell/shell-layout.tsx +115 -0
- package/src/components/blocks/shell/type.ts +50 -0
- package/src/components/blocks/shell/utils.ts +2 -0
- package/src/components/composed/social-providers.tsx +83 -27
- package/src/components/docs/component-preview.tsx +13 -12
- package/src/components/docs/demo-controls.tsx +37 -21
- package/src/components/motion/animated-sidebar.tsx +392 -364
- package/src/components/motion/popover-morph.tsx +292 -0
- package/src/components/motion/popover-position.ts +86 -0
- package/src/components/ui/badge.tsx +52 -0
- package/src/components/ui/select.tsx +257 -0
- package/src/index.css +18 -0
- package/src/index.ts +1 -0
|
@@ -12,149 +12,20 @@ import {
|
|
|
12
12
|
AnimatedSidebarGroupLabel,
|
|
13
13
|
AnimatedSidebarHeader,
|
|
14
14
|
AnimatedSidebarMenu,
|
|
15
|
-
AnimatedSidebarMenuButton,
|
|
16
|
-
AnimatedSidebarMenuItem,
|
|
17
|
-
AnimatedSidebarMenuSub,
|
|
18
|
-
AnimatedSidebarMenuSubButton,
|
|
19
|
-
AnimatedSidebarMenuSubItem,
|
|
20
15
|
AnimatedSidebarProvider,
|
|
21
16
|
AnimatedSidebarTrigger,
|
|
22
17
|
} from "@/components/motion/animated-sidebar"
|
|
23
18
|
import { cn } from "@/lib/utils"
|
|
19
|
+
import { SidebarNavRow } from "@/components/blocks/nav/sidebar-nav-row"
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
export interface SidebarNavSubItem {
|
|
27
|
-
id: string
|
|
28
|
-
label: string
|
|
29
|
-
icon?: React.ReactNode
|
|
30
|
-
/** Renders an anchor. Without one the row is a button. */
|
|
31
|
-
href?: string
|
|
32
|
-
disabled?: boolean
|
|
33
|
-
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
34
|
-
onSelect?: () => void
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** A top-level entry. With `items` it expands instead of navigating. */
|
|
38
|
-
export interface SidebarNavItem extends SidebarNavSubItem {
|
|
39
|
-
/** Trailing hint — a count, a "New" pill. Hidden while collapsed. */
|
|
40
|
-
badge?: React.ReactNode
|
|
41
|
-
items?: SidebarNavSubItem[]
|
|
42
|
-
/** Start expanded. Defaults to expanded when one of its children is active. */
|
|
43
|
-
defaultOpen?: boolean
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** A titled run of entries. The title hides while the rail is collapsed. */
|
|
47
|
-
export interface SidebarNavSection {
|
|
48
|
-
id: string
|
|
49
|
-
label?: string
|
|
50
|
-
items: SidebarNavItem[]
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface SidebarBlockProps {
|
|
54
|
-
sections: SidebarNavSection[]
|
|
55
|
-
/** `id` of the current entry — matches top-level and nested ids alike. */
|
|
56
|
-
activeId?: string
|
|
57
|
-
/** Fires for every selection, after the entry's own `onSelect`. */
|
|
58
|
-
onNavigate?: (item: SidebarNavItem | SidebarNavSubItem) => void
|
|
59
|
-
/** Brand row above the navigation. */
|
|
60
|
-
header?: React.ReactNode
|
|
61
|
-
/** Pinned below the navigation — an account card, a version note. */
|
|
62
|
-
footer?: React.ReactNode
|
|
63
|
-
/** Collapse toggle in the header row. */
|
|
64
|
-
showTrigger?: boolean
|
|
65
|
-
side?: "left" | "right"
|
|
66
|
-
variant?: "sidebar" | "floating" | "inset"
|
|
67
|
-
/** How the rail collapses: to icons, off-canvas, or not at all. */
|
|
68
|
-
collapsible?: "offcanvas" | "icon" | "none"
|
|
69
|
-
open?: boolean
|
|
70
|
-
defaultOpen?: boolean
|
|
71
|
-
onOpenChange?: (open: boolean) => void
|
|
72
|
-
/** Under 768px the panel is a drawer — drive it from your own header. */
|
|
73
|
-
openMobile?: boolean
|
|
74
|
-
defaultOpenMobile?: boolean
|
|
75
|
-
onOpenMobileChange?: (open: boolean) => void
|
|
76
|
-
/** Rail widths. CSS lengths, e.g. "16rem". */
|
|
77
|
-
width?: string
|
|
78
|
-
iconWidth?: string
|
|
79
|
-
mobileWidth?: string
|
|
80
|
-
ariaLabel?: string
|
|
81
|
-
className?: string
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function isActive(id: string, activeId?: string): boolean {
|
|
85
|
-
return activeId !== undefined && id === activeId
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
interface RowProps {
|
|
89
|
-
item: SidebarNavItem
|
|
90
|
-
activeId?: string
|
|
91
|
-
onNavigate?: (item: SidebarNavItem | SidebarNavSubItem) => void
|
|
92
|
-
}
|
|
21
|
+
import type { ISidebarBlockProps } from "@/components/blocks/nav/types"
|
|
93
22
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const [wasChildActive, setWasChildActive] = React.useState(childActive)
|
|
101
|
-
|
|
102
|
-
// Navigating into a nested route from elsewhere reveals its parent, even if
|
|
103
|
-
// the reader had collapsed it. Adjusted during render rather than in an
|
|
104
|
-
// effect, so the group never paints closed for a frame first.
|
|
105
|
-
if (childActive !== wasChildActive) {
|
|
106
|
-
setWasChildActive(childActive)
|
|
107
|
-
if (childActive) setToggled(null)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const open = toggled ?? item.defaultOpen ?? childActive
|
|
111
|
-
|
|
112
|
-
function select(entry: SidebarNavItem | SidebarNavSubItem) {
|
|
113
|
-
entry.onSelect?.()
|
|
114
|
-
onNavigate?.(entry)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<AnimatedSidebarMenuItem>
|
|
119
|
-
<AnimatedSidebarMenuButton
|
|
120
|
-
ariaExpanded={hasChildren ? open : undefined}
|
|
121
|
-
badge={item.badge}
|
|
122
|
-
disabled={item.disabled}
|
|
123
|
-
href={hasChildren ? undefined : item.href}
|
|
124
|
-
icon={item.icon}
|
|
125
|
-
isActive={isActive(item.id, activeId)}
|
|
126
|
-
onSelect={() => {
|
|
127
|
-
if (hasChildren) {
|
|
128
|
-
setToggled(!open)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
select(item)
|
|
132
|
-
}}
|
|
133
|
-
target={item.target}
|
|
134
|
-
>
|
|
135
|
-
{item.label}
|
|
136
|
-
</AnimatedSidebarMenuButton>
|
|
137
|
-
{hasChildren ? (
|
|
138
|
-
<AnimatedSidebarMenuSub open={open}>
|
|
139
|
-
{children.map((child) => (
|
|
140
|
-
<AnimatedSidebarMenuSubItem key={child.id}>
|
|
141
|
-
<AnimatedSidebarMenuSubButton
|
|
142
|
-
disabled={child.disabled}
|
|
143
|
-
href={child.href}
|
|
144
|
-
icon={child.icon}
|
|
145
|
-
isActive={isActive(child.id, activeId)}
|
|
146
|
-
onSelect={() => select(child)}
|
|
147
|
-
target={child.target}
|
|
148
|
-
>
|
|
149
|
-
{child.label}
|
|
150
|
-
</AnimatedSidebarMenuSubButton>
|
|
151
|
-
</AnimatedSidebarMenuSubItem>
|
|
152
|
-
))}
|
|
153
|
-
</AnimatedSidebarMenuSub>
|
|
154
|
-
) : null}
|
|
155
|
-
</AnimatedSidebarMenuItem>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
23
|
+
export type {
|
|
24
|
+
ISidebarBlockProps as SidebarBlockProps,
|
|
25
|
+
ISidebarNavItem as SidebarNavItem,
|
|
26
|
+
ISidebarNavSection as SidebarNavSection,
|
|
27
|
+
ISidebarNavSubItem as SidebarNavSubItem,
|
|
28
|
+
} from "@/components/blocks/nav/types"
|
|
158
29
|
|
|
159
30
|
/**
|
|
160
31
|
* Application sidebar navigation, driven by data rather than composition.
|
|
@@ -187,7 +58,7 @@ export function SidebarBlock({
|
|
|
187
58
|
mobileWidth,
|
|
188
59
|
ariaLabel = "Main",
|
|
189
60
|
className,
|
|
190
|
-
}:
|
|
61
|
+
}: ISidebarBlockProps): React.ReactElement {
|
|
191
62
|
return (
|
|
192
63
|
<AnimatedSidebarProvider
|
|
193
64
|
// The provider ships a full-page flex shell; this block is just the
|
|
@@ -220,26 +91,27 @@ export function SidebarBlock({
|
|
|
220
91
|
side={side}
|
|
221
92
|
variant={variant}
|
|
222
93
|
>
|
|
223
|
-
{header ||
|
|
224
|
-
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
94
|
+
{header ||
|
|
95
|
+
(showTrigger && (
|
|
96
|
+
<AnimatedSidebarHeader>
|
|
97
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
98
|
+
{header && <div className="min-w-0 flex-1">{header}</div>}
|
|
99
|
+
{showTrigger && (
|
|
100
|
+
<AnimatedSidebarTrigger className="text-muted-foreground hover:text-foreground">
|
|
101
|
+
<PanelLeftIcon className="size-4.5" />
|
|
102
|
+
</AnimatedSidebarTrigger>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
</AnimatedSidebarHeader>
|
|
106
|
+
))}
|
|
235
107
|
<AnimatedSidebarContent>
|
|
236
108
|
{sections.map((section) => (
|
|
237
109
|
<AnimatedSidebarGroup key={section.id}>
|
|
238
|
-
{section.label
|
|
110
|
+
{section.label && (
|
|
239
111
|
<AnimatedSidebarGroupLabel>
|
|
240
112
|
{section.label}
|
|
241
113
|
</AnimatedSidebarGroupLabel>
|
|
242
|
-
)
|
|
114
|
+
)}
|
|
243
115
|
<AnimatedSidebarGroupContent>
|
|
244
116
|
<AnimatedSidebarMenu>
|
|
245
117
|
{section.items.map((item) => (
|
|
@@ -255,9 +127,7 @@ export function SidebarBlock({
|
|
|
255
127
|
</AnimatedSidebarGroup>
|
|
256
128
|
))}
|
|
257
129
|
</AnimatedSidebarContent>
|
|
258
|
-
{footer
|
|
259
|
-
<AnimatedSidebarFooter>{footer}</AnimatedSidebarFooter>
|
|
260
|
-
) : null}
|
|
130
|
+
{footer && <AnimatedSidebarFooter>{footer}</AnimatedSidebarFooter>}
|
|
261
131
|
</AnimatedSidebar>
|
|
262
132
|
</AnimatedSidebarProvider>
|
|
263
133
|
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AnimatedSidebarMenuButton,
|
|
7
|
+
AnimatedSidebarMenuItem,
|
|
8
|
+
AnimatedSidebarMenuSub,
|
|
9
|
+
AnimatedSidebarMenuSubButton,
|
|
10
|
+
AnimatedSidebarMenuSubItem,
|
|
11
|
+
} from "@/components/motion/animated-sidebar"
|
|
12
|
+
|
|
13
|
+
import type { IRowProps, ISidebarNavItem, ISidebarNavSubItem } from "@/components/blocks/nav/types"
|
|
14
|
+
import { isActive } from "@/components/blocks/nav/utils"
|
|
15
|
+
|
|
16
|
+
export function SidebarNavRow({ item, activeId, onNavigate }: IRowProps) {
|
|
17
|
+
const children = item.items ?? []
|
|
18
|
+
const hasChildren = children.length > 0
|
|
19
|
+
const childActive = children.some((child) => isActive(child.id, activeId))
|
|
20
|
+
// null = follow the active child; a boolean = the reader has said otherwise.
|
|
21
|
+
const [toggled, setToggled] = React.useState<boolean | null>(null)
|
|
22
|
+
const [wasChildActive, setWasChildActive] = React.useState(childActive)
|
|
23
|
+
|
|
24
|
+
// Navigating into a nested route from elsewhere reveals its parent, even if
|
|
25
|
+
// the reader had collapsed it. Adjusted during render rather than in an
|
|
26
|
+
// effect, so the group never paints closed for a frame first.
|
|
27
|
+
if (childActive !== wasChildActive) {
|
|
28
|
+
setWasChildActive(childActive)
|
|
29
|
+
if (childActive) setToggled(null)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const open = toggled ?? item.defaultOpen ?? childActive
|
|
33
|
+
|
|
34
|
+
function select(entry: ISidebarNavItem | ISidebarNavSubItem) {
|
|
35
|
+
entry.onSelect?.()
|
|
36
|
+
onNavigate?.(entry)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<AnimatedSidebarMenuItem>
|
|
41
|
+
<AnimatedSidebarMenuButton
|
|
42
|
+
ariaExpanded={hasChildren ? open : undefined}
|
|
43
|
+
badge={item.badge}
|
|
44
|
+
disabled={item.disabled}
|
|
45
|
+
href={hasChildren ? undefined : item.href}
|
|
46
|
+
icon={item.icon}
|
|
47
|
+
isActive={isActive(item.id, activeId)}
|
|
48
|
+
onSelect={() => {
|
|
49
|
+
if (hasChildren) {
|
|
50
|
+
setToggled(!open)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
select(item)
|
|
54
|
+
}}
|
|
55
|
+
target={item.target}
|
|
56
|
+
>
|
|
57
|
+
{item.label}
|
|
58
|
+
</AnimatedSidebarMenuButton>
|
|
59
|
+
{hasChildren ? (
|
|
60
|
+
<AnimatedSidebarMenuSub open={open}>
|
|
61
|
+
{children.map((child) => (
|
|
62
|
+
<AnimatedSidebarMenuSubItem key={child.id}>
|
|
63
|
+
<AnimatedSidebarMenuSubButton
|
|
64
|
+
disabled={child.disabled}
|
|
65
|
+
href={child.href}
|
|
66
|
+
icon={child.icon}
|
|
67
|
+
isActive={isActive(child.id, activeId)}
|
|
68
|
+
onSelect={() => select(child)}
|
|
69
|
+
target={child.target}
|
|
70
|
+
>
|
|
71
|
+
{child.label}
|
|
72
|
+
</AnimatedSidebarMenuSubButton>
|
|
73
|
+
</AnimatedSidebarMenuSubItem>
|
|
74
|
+
))}
|
|
75
|
+
</AnimatedSidebarMenuSub>
|
|
76
|
+
) : null}
|
|
77
|
+
</AnimatedSidebarMenuItem>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
/** A nested link, one level under a top-level entry. */
|
|
3
|
+
export interface ISidebarNavSubItem {
|
|
4
|
+
id: string
|
|
5
|
+
label: string
|
|
6
|
+
icon?: React.ReactNode
|
|
7
|
+
/** Renders an anchor. Without one the row is a button. */
|
|
8
|
+
href?: string
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
11
|
+
onSelect?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A top-level entry. With `items` it expands instead of navigating. */
|
|
15
|
+
export interface ISidebarNavItem extends ISidebarNavSubItem {
|
|
16
|
+
/** Trailing hint — a count, a "New" pill. Hidden while collapsed. */
|
|
17
|
+
badge?: React.ReactNode
|
|
18
|
+
items?: ISidebarNavSubItem[]
|
|
19
|
+
/** Start expanded. Defaults to expanded when one of its children is active. */
|
|
20
|
+
defaultOpen?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A titled run of entries. The title hides while the rail is collapsed. */
|
|
24
|
+
export interface ISidebarNavSection {
|
|
25
|
+
id: string
|
|
26
|
+
label?: string
|
|
27
|
+
items: ISidebarNavItem[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ISidebarBlockProps {
|
|
31
|
+
sections: ISidebarNavSection[]
|
|
32
|
+
/** `id` of the current entry — matches top-level and nested ids alike. */
|
|
33
|
+
activeId?: string
|
|
34
|
+
/** Fires for every selection, after the entry's own `onSelect`. */
|
|
35
|
+
onNavigate?: (item: ISidebarNavItem | ISidebarNavSubItem) => void
|
|
36
|
+
/** Brand row above the navigation. */
|
|
37
|
+
header?: React.ReactNode
|
|
38
|
+
/** Pinned below the navigation — an account card, a version note. */
|
|
39
|
+
footer?: React.ReactNode
|
|
40
|
+
/** Collapse toggle in the header row. */
|
|
41
|
+
showTrigger?: boolean
|
|
42
|
+
side?: "left" | "right"
|
|
43
|
+
variant?: "sidebar" | "floating" | "inset"
|
|
44
|
+
/** How the rail collapses: to icons, off-canvas, or not at all. */
|
|
45
|
+
collapsible?: "offcanvas" | "icon" | "none"
|
|
46
|
+
open?: boolean
|
|
47
|
+
defaultOpen?: boolean
|
|
48
|
+
onOpenChange?: (open: boolean) => void
|
|
49
|
+
/** Under 768px the panel is a drawer — drive it from your own header. */
|
|
50
|
+
openMobile?: boolean
|
|
51
|
+
defaultOpenMobile?: boolean
|
|
52
|
+
onOpenMobileChange?: (open: boolean) => void
|
|
53
|
+
/** Rail widths. CSS lengths, e.g. "16rem". */
|
|
54
|
+
width?: string
|
|
55
|
+
iconWidth?: string
|
|
56
|
+
mobileWidth?: string
|
|
57
|
+
ariaLabel?: string
|
|
58
|
+
className?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface IRowProps {
|
|
62
|
+
item: ISidebarNavItem
|
|
63
|
+
activeId?: string
|
|
64
|
+
onNavigate?: (item: ISidebarNavItem | ISidebarNavSubItem) => void
|
|
65
|
+
}
|
|
@@ -109,6 +109,11 @@ export interface ProfileCardBlockProps {
|
|
|
109
109
|
open?: boolean
|
|
110
110
|
defaultOpen?: boolean
|
|
111
111
|
onOpenChange?: MenuPrimitive.Root.Props["onOpenChange"]
|
|
112
|
+
/**
|
|
113
|
+
* "content" sizes the menu to its items; "card" locks it to the trigger's
|
|
114
|
+
* width, so `align` stops mattering horizontally.
|
|
115
|
+
*/
|
|
116
|
+
menuWidth?: "content" | "card"
|
|
112
117
|
/**
|
|
113
118
|
* Emit cuelume press/release on the card and play the open/close cues.
|
|
114
119
|
* Inert unless the app opts into interaction sound via `bindSounds()`.
|
|
@@ -158,6 +163,7 @@ export function ProfileCardBlock({
|
|
|
158
163
|
align = "center",
|
|
159
164
|
side = "bottom",
|
|
160
165
|
sideOffset = 8,
|
|
166
|
+
menuWidth = "content",
|
|
161
167
|
open,
|
|
162
168
|
defaultOpen,
|
|
163
169
|
onOpenChange,
|
|
@@ -212,13 +218,15 @@ export function ProfileCardBlock({
|
|
|
212
218
|
// avatar sits inside the button and the handle is a second line.
|
|
213
219
|
aria-label={`${user.name}${user.username ? ` ${user.username}` : ""}, account menu`}
|
|
214
220
|
className={cn(
|
|
215
|
-
|
|
221
|
+
// Quiet at rest — the background only surfaces on hover or
|
|
222
|
+
// while the menu is open.
|
|
223
|
+
"h-auto w-full justify-between py-1.5 pl-1.5 data-popup-open:bg-accent sm:h-auto",
|
|
216
224
|
className
|
|
217
225
|
)}
|
|
218
226
|
data-slot="profile-card"
|
|
219
227
|
loading={busy}
|
|
220
228
|
sound={sound}
|
|
221
|
-
variant="
|
|
229
|
+
variant="ghost"
|
|
222
230
|
>
|
|
223
231
|
<span className="flex w-full min-w-0 items-center justify-start gap-2">
|
|
224
232
|
<UserAvatar
|
|
@@ -243,11 +251,14 @@ export function ProfileCardBlock({
|
|
|
243
251
|
/>
|
|
244
252
|
<DropdownMenuContent
|
|
245
253
|
align={align}
|
|
246
|
-
// Content-width
|
|
247
|
-
//
|
|
248
|
-
//
|
|
254
|
+
// Content-width by default so `align` reads: a popup locked to
|
|
255
|
+
// `--anchor-width` lands in the same place for every value. `!`
|
|
256
|
+
// because the popup's own `not-[class*='w-']:min-w-32` compiles to
|
|
249
257
|
// a higher-specificity `:not(:is())` that would otherwise win.
|
|
250
|
-
className=
|
|
258
|
+
className={cn(
|
|
259
|
+
menuWidth === "card" ? "w-(--anchor-width)" : "min-w-44!",
|
|
260
|
+
"origin-(--transform-origin) transition-[opacity,scale,translate] duration-150 ease-smooth-out data-ending-style:scale-98 data-ending-style:opacity-0 data-ending-style:duration-100 data-starting-style:scale-98 data-starting-style:opacity-0 data-[side=bottom]:data-ending-style:-translate-y-1 data-[side=bottom]:data-starting-style:-translate-y-1 data-[side=top]:data-ending-style:translate-y-1 data-[side=top]:data-starting-style:translate-y-1 motion-reduce:transition-none"
|
|
261
|
+
)}
|
|
251
262
|
side={side}
|
|
252
263
|
sideOffset={sideOffset}
|
|
253
264
|
>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./shell-layout"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { MenuIcon, PanelRightIcon } from "lucide-react"
|
|
4
|
+
import * as React from "react"
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AnimatedSidebar,
|
|
8
|
+
AnimatedSidebarInset,
|
|
9
|
+
AnimatedSidebarProvider,
|
|
10
|
+
AnimatedSidebarTrigger,
|
|
11
|
+
} from "@/components/motion/animated-sidebar"
|
|
12
|
+
import { cn } from "@/lib/utils"
|
|
13
|
+
import type { ShellLayoutProps } from "./type"
|
|
14
|
+
import { TITLE_BAR_HEIGHT } from "./utils"
|
|
15
|
+
import type { ClassNameValue } from "tailwind-merge"
|
|
16
|
+
|
|
17
|
+
export function ShellLayout({
|
|
18
|
+
children,
|
|
19
|
+
open,
|
|
20
|
+
defaultOpen,
|
|
21
|
+
onOpenChange,
|
|
22
|
+
rightDrawer,
|
|
23
|
+
rightOpen,
|
|
24
|
+
defaultRightOpen,
|
|
25
|
+
onRightOpenChange,
|
|
26
|
+
variant = "desktop",
|
|
27
|
+
className,
|
|
28
|
+
}: ShellLayoutProps): React.ReactElement {
|
|
29
|
+
|
|
30
|
+
const background: ClassNameValue = `bg-linear-to-tr/increasing from-neutral-200 via-neutral-100 to-stone-200 dark:from-neutral-900 dark:via-neutral-950 dark:to-stone-950`
|
|
31
|
+
return (
|
|
32
|
+
<AnimatedSidebarProvider
|
|
33
|
+
className={cn("block-full", className, background, 'relative pr-2')}
|
|
34
|
+
defaultOpen={defaultOpen}
|
|
35
|
+
defaultOpenRight={defaultRightOpen}
|
|
36
|
+
onOpenChange={onOpenChange}
|
|
37
|
+
onOpenRightChange={onRightOpenChange}
|
|
38
|
+
open={open}
|
|
39
|
+
openRight={rightOpen}
|
|
40
|
+
style={{
|
|
41
|
+
paddingTop: variant === "desktop" ? TITLE_BAR_HEIGHT : 0
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<AnimatedSidebar
|
|
45
|
+
ariaLabel="Primary navigation"
|
|
46
|
+
className={cn("")}
|
|
47
|
+
collapsible="icon"
|
|
48
|
+
role="navigation"
|
|
49
|
+
variant="inset"
|
|
50
|
+
></AnimatedSidebar>
|
|
51
|
+
|
|
52
|
+
<AnimatedSidebarInset
|
|
53
|
+
className={cn(
|
|
54
|
+
"min-w-24 overflow-hidden border-t border-neutral-200 dark:border-neutral-700 inset-ring-1 inset-ring-neutral-50 dark:inset-ring-black bg-white dark:bg-card rounded-lg!",
|
|
55
|
+
`mx-0!`
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
<header className="flex h-16 items-center justify-between gap-3 px-4 sm:px-6">
|
|
59
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
60
|
+
<AnimatedSidebarTrigger>
|
|
61
|
+
<MenuIcon className="size-5" />
|
|
62
|
+
</AnimatedSidebarTrigger>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex shrink-0 items-center gap-3">
|
|
65
|
+
<AnimatedSidebarTrigger aria-label="Toggle panel" side="right">
|
|
66
|
+
<PanelRightIcon className="size-5" />
|
|
67
|
+
</AnimatedSidebarTrigger>
|
|
68
|
+
</div>
|
|
69
|
+
</header>
|
|
70
|
+
<div className="min-h-24 flex-1 overflow-auto">
|
|
71
|
+
{children ?? <ShellLayoutPlaceholder />}
|
|
72
|
+
</div>
|
|
73
|
+
</AnimatedSidebarInset>
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
<AnimatedSidebar
|
|
78
|
+
ariaLabel="Secondary panel"
|
|
79
|
+
// Below md the panel is portal led over the page, so it carries the
|
|
80
|
+
// surface itself; the desktop rail keeps it on the inner panel.
|
|
81
|
+
className={cn("max-md:border-l max-md:border-neutral-200 max-md:bg-white dark:max-md:border-neutral-700 dark:max-md:bg-card")}
|
|
82
|
+
collapsible="offcanvas"
|
|
83
|
+
panelClassName={cn(
|
|
84
|
+
// The off canvas panel keeps a fixed width so it can slide out of the
|
|
85
|
+
// zero-width rail, so the inset gutter has to come out of that width
|
|
86
|
+
// rather than from a left margin.
|
|
87
|
+
"top-2 ml-0 h-[calc(100%-1rem)] min-w-[calc(var(--sidebar-width))]",
|
|
88
|
+
"rounded-lg border-t border-neutral-200 bg-white shadow-md inset-ring-1 inset-ring-neutral-50",
|
|
89
|
+
"dark:border-neutral-700 dark:bg-card dark:inset-ring-black"
|
|
90
|
+
)}
|
|
91
|
+
role="complementary"
|
|
92
|
+
side="right"
|
|
93
|
+
variant="inset"
|
|
94
|
+
>
|
|
95
|
+
{rightDrawer}
|
|
96
|
+
</AnimatedSidebar>
|
|
97
|
+
</AnimatedSidebarProvider>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function ShellLayoutPlaceholder() {
|
|
102
|
+
return (
|
|
103
|
+
<div className="grid gap-5">
|
|
104
|
+
<div className="grid gap-5 lg:grid-cols-3">
|
|
105
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
106
|
+
<div
|
|
107
|
+
className="h-32 border border-border bg-background/60"
|
|
108
|
+
key={index}
|
|
109
|
+
/>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
<div className="h-72 border border-border bg-background/60" />
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// import type {
|
|
2
|
+
// ISidebarNavItem,
|
|
3
|
+
// ISidebarNavSection,
|
|
4
|
+
// ISidebarNavSubItem,
|
|
5
|
+
// } from "@/components/blocks/nav/types"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export interface ShellAction {
|
|
9
|
+
id: string
|
|
10
|
+
label: string
|
|
11
|
+
icon: React.ReactNode
|
|
12
|
+
badge?: React.ReactNode
|
|
13
|
+
onSelect?: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ShellWorkspace {
|
|
17
|
+
name: string
|
|
18
|
+
logo?: React.ReactNode
|
|
19
|
+
meta?: React.ReactNode
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ShellLayoutProps {
|
|
23
|
+
children?: React.ReactNode
|
|
24
|
+
// sections?: ISidebarNavSection[]
|
|
25
|
+
//activeId?: string
|
|
26
|
+
// onNavigate?: (item: ISidebarNavItem | ISidebarNavSubItem) => void
|
|
27
|
+
// workspace?: ShellWorkspace
|
|
28
|
+
//sidebarFooter?: React.ReactNode
|
|
29
|
+
// topActions?: ShellAction[]
|
|
30
|
+
//title?: React.ReactNode
|
|
31
|
+
//eyebrow?: React.ReactNode
|
|
32
|
+
//searchPlaceholder?: string
|
|
33
|
+
//searchShortcut?: React.ReactNode
|
|
34
|
+
open?: boolean
|
|
35
|
+
defaultOpen?: boolean
|
|
36
|
+
onOpenChange?: (open: boolean) => void
|
|
37
|
+
/** Content of the right drawer. It has no icon rail — it is either fully
|
|
38
|
+
* shown or fully hidden. */
|
|
39
|
+
rightDrawer?: React.ReactNode
|
|
40
|
+
rightOpen?: boolean
|
|
41
|
+
defaultRightOpen?: boolean
|
|
42
|
+
onRightOpenChange?: (open: boolean) => void
|
|
43
|
+
// openMobile?: boolean
|
|
44
|
+
//defaultOpenMobile?: boolean
|
|
45
|
+
//onOpenMobileChange?: (open: boolean) => void
|
|
46
|
+
className?: string
|
|
47
|
+
variant: 'web' | 'desktop'
|
|
48
|
+
//sidebarClassName?: string
|
|
49
|
+
//contentClassName?: string
|
|
50
|
+
}
|