banhaten 0.3.0 → 0.4.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/CHANGELOG.md +27 -0
- package/README.md +4 -4
- package/docs/design-system/component-implementation-ownership-audit.md +2 -1
- package/docs/design-system/component-quality-audit-2026-08-20.md +4 -2
- package/docs/design-system/components/alert-dialog.md +1 -1
- package/docs/design-system/components/alert.md +2 -2
- package/docs/design-system/components/attribute.md +1 -1
- package/docs/design-system/components/avatar.md +2 -2
- package/docs/design-system/components/banner.md +2 -2
- package/docs/design-system/components/breadcrumbs.md +2 -2
- package/docs/design-system/components/button-group.md +2 -2
- package/docs/design-system/components/button.md +2 -2
- package/docs/design-system/components/date-picker.md +3 -1
- package/docs/design-system/components/file-upload.md +1 -1
- package/docs/design-system/components/input.md +8 -6
- package/docs/design-system/components/item.md +3 -1
- package/docs/design-system/components/kbd.md +11 -8
- package/docs/design-system/components/menu.md +2 -2
- package/docs/design-system/components/modal.md +1 -1
- package/docs/design-system/components/onboarding-step-list-item.md +2 -2
- package/docs/design-system/components/pagination.md +2 -2
- package/docs/design-system/components/radio-card.md +1 -1
- package/docs/design-system/components/select.md +1 -1
- package/docs/design-system/components/sidebar.md +59 -19
- package/docs/design-system/components/table.md +1 -1
- package/docs/design-system/components/tag.md +3 -3
- package/docs/design-system/components/toolbar.md +1 -1
- package/docs/design-system/density-and-elevation.md +16 -12
- package/docs/design-system/icons.md +6 -1
- package/docs/design-system/installation.md +1 -1
- package/docs/design-system/token-governance.md +7 -8
- package/package.json +1 -1
- package/registry/components/alert-dialog.tsx +1 -1
- package/registry/components/alert.tsx +3 -3
- package/registry/components/attribute.tsx +8 -2
- package/registry/components/avatar.tsx +21 -17
- package/registry/components/button-group.tsx +1 -1
- package/registry/components/button.tsx +1 -1
- package/registry/components/date-picker.tsx +16 -4
- package/registry/components/expanded/OnboardingStepListItem.tsx +6 -5
- package/registry/components/expanded/banner.css +10 -5
- package/registry/components/expanded/breadcrumbs.css +4 -4
- package/registry/components/expanded/fileUpload.css +3 -3
- package/registry/components/expanded/onboardingStepListItem.css +14 -5
- package/registry/components/expanded/table.css +5 -2
- package/registry/components/input.tsx +12 -5
- package/registry/components/item.tsx +1 -1
- package/registry/components/kbd.tsx +109 -15
- package/registry/components/menu.tsx +1 -1
- package/registry/components/modal.tsx +1 -1
- package/registry/components/pagination.tsx +12 -7
- package/registry/components/radio-card.tsx +1 -2
- package/registry/components/select.tsx +1 -1
- package/registry/components/sidebar.tsx +929 -57
- package/registry/components/tag.tsx +1 -1
- package/registry/components/toolbar.tsx +1 -1
- package/registry/examples/attribute-demo.tsx +1 -1
- package/registry/examples/command-palette-demo.tsx +0 -1
- package/registry/examples/kbd-demo.tsx +2 -1
- package/registry/examples/sidebar-demo.tsx +60 -16
- package/registry/guidance/USAGE.md +1 -1
- package/registry/index.json +233 -31
- package/registry/styles/globals.css +152 -118
- package/registry.json +4 -3
- package/tokens/banhaten.tokens.json +169 -49
|
@@ -2,8 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
4
|
import { Slot } from "@radix-ui/react-slot"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Building2Icon,
|
|
7
|
+
ChevronDownIcon,
|
|
8
|
+
ChevronRightIcon,
|
|
9
|
+
ChevronsUpDownIcon,
|
|
10
|
+
FolderIcon,
|
|
11
|
+
PanelLeftIcon,
|
|
12
|
+
SearchIcon,
|
|
13
|
+
SparklesIcon,
|
|
14
|
+
UserIcon,
|
|
15
|
+
} from "lucide-react"
|
|
6
16
|
|
|
17
|
+
import {
|
|
18
|
+
Tooltip,
|
|
19
|
+
TooltipContent,
|
|
20
|
+
TooltipProvider,
|
|
21
|
+
TooltipTrigger,
|
|
22
|
+
} from "@/components/ui/tooltip"
|
|
7
23
|
import { cn } from "@/lib/utils"
|
|
8
24
|
|
|
9
25
|
type SidebarContextValue = {
|
|
@@ -23,13 +39,83 @@ type SidebarProps = React.ComponentProps<"aside"> & {
|
|
|
23
39
|
side?: "start" | "end"
|
|
24
40
|
}
|
|
25
41
|
type SidebarSectionProps = React.ComponentProps<"div">
|
|
26
|
-
type
|
|
42
|
+
type SidebarHeaderActionProps = React.ComponentProps<"button"> & {
|
|
43
|
+
label?: string
|
|
44
|
+
}
|
|
45
|
+
type SidebarHeaderBrandProps = React.ComponentProps<"div"> & {
|
|
46
|
+
brand?: React.ReactNode
|
|
47
|
+
collapseAction?: React.ReactNode | false
|
|
48
|
+
logo?: React.ReactNode
|
|
49
|
+
onSearchClick?: React.MouseEventHandler<HTMLButtonElement>
|
|
50
|
+
searchAction?: React.ReactNode | false
|
|
51
|
+
searchLabel?: string
|
|
52
|
+
}
|
|
53
|
+
type SidebarWorkspaceVariant = "actions" | "card"
|
|
54
|
+
type SidebarHeaderWorkspaceSwitchProps = React.ComponentProps<"div"> & {
|
|
55
|
+
collapseAction?: React.ReactNode | false
|
|
56
|
+
image?: React.ReactNode
|
|
57
|
+
label?: React.ReactNode
|
|
58
|
+
onSearchClick?: React.MouseEventHandler<HTMLButtonElement>
|
|
59
|
+
onWorkspaceClick?: React.MouseEventHandler<HTMLButtonElement>
|
|
60
|
+
searchAction?: React.ReactNode | false
|
|
61
|
+
searchLabel?: string
|
|
62
|
+
supportText?: React.ReactNode
|
|
63
|
+
variant?: SidebarWorkspaceVariant
|
|
64
|
+
workspaceLabel?: string
|
|
65
|
+
}
|
|
66
|
+
type SidebarFooterUserProps = React.ComponentProps<"button"> & {
|
|
67
|
+
avatar?: React.ReactNode
|
|
68
|
+
name?: React.ReactNode
|
|
69
|
+
supportText?: React.ReactNode
|
|
70
|
+
trailingIcon?: React.ReactNode
|
|
71
|
+
variant?: "default" | "supporting-text"
|
|
72
|
+
}
|
|
73
|
+
type SidebarGroupLabelProps = React.ComponentProps<"div"> & {
|
|
74
|
+
actions?: React.ReactNode
|
|
75
|
+
expandIcon?: React.ReactNode
|
|
76
|
+
expandable?: boolean
|
|
77
|
+
}
|
|
78
|
+
type SidebarGroupActionProps = React.ComponentProps<"button"> & {
|
|
79
|
+
label?: string
|
|
80
|
+
}
|
|
27
81
|
type SidebarMenuProps = React.ComponentProps<"ul">
|
|
28
82
|
type SidebarMenuItemProps = React.ComponentProps<"li">
|
|
83
|
+
type SidebarInteractionState = "default" | "hover" | "active" | "focus"
|
|
84
|
+
type SidebarMenuButtonVariant =
|
|
85
|
+
| "default"
|
|
86
|
+
| "avatar"
|
|
87
|
+
| "level2"
|
|
88
|
+
| "level2-line"
|
|
29
89
|
type SidebarMenuButtonProps = React.ComponentProps<"button"> & {
|
|
30
90
|
active?: boolean
|
|
31
91
|
asChild?: boolean
|
|
92
|
+
badge?: React.ReactNode
|
|
93
|
+
icon?: React.ReactNode
|
|
94
|
+
state?: SidebarInteractionState
|
|
95
|
+
supportText?: React.ReactNode
|
|
96
|
+
tooltip?: React.ReactNode
|
|
97
|
+
trailingIcon?: React.ReactNode
|
|
98
|
+
variant?: SidebarMenuButtonVariant
|
|
99
|
+
}
|
|
100
|
+
type SidebarMenuBadgeProps = React.ComponentProps<"span">
|
|
101
|
+
type SidebarMenuSubProps = React.ComponentProps<"ul">
|
|
102
|
+
type SidebarMenuSubButtonProps = Omit<SidebarMenuButtonProps, "variant"> & {
|
|
103
|
+
showLine?: boolean
|
|
104
|
+
}
|
|
105
|
+
type SidebarMenuTreeItemProps = React.ComponentProps<"button"> & {
|
|
106
|
+
active?: boolean
|
|
107
|
+
asChild?: boolean
|
|
108
|
+
expanded?: boolean
|
|
32
109
|
icon?: React.ReactNode
|
|
110
|
+
level?: 1 | 2 | 3 | 4
|
|
111
|
+
showChevron?: boolean
|
|
112
|
+
state?: SidebarInteractionState
|
|
113
|
+
}
|
|
114
|
+
type SidebarTreeGroupProps = SidebarSectionProps & {
|
|
115
|
+
actions?: React.ReactNode
|
|
116
|
+
expandable?: boolean
|
|
117
|
+
expandIcon?: React.ReactNode
|
|
118
|
+
label?: React.ReactNode
|
|
33
119
|
}
|
|
34
120
|
type SidebarToggleProps = React.ComponentProps<"button">
|
|
35
121
|
type SidebarTriggerProps = SidebarToggleProps
|
|
@@ -40,6 +126,7 @@ type SidebarInsetProps = React.ComponentProps<"div"> & {
|
|
|
40
126
|
|
|
41
127
|
const SidebarContext = React.createContext<SidebarContextValue | null>(null)
|
|
42
128
|
|
|
129
|
+
/** Owns the controlled or uncontrolled collapsed state for a sidebar composition. */
|
|
43
130
|
function SidebarProvider({
|
|
44
131
|
children,
|
|
45
132
|
className,
|
|
@@ -76,19 +163,22 @@ function SidebarProvider({
|
|
|
76
163
|
|
|
77
164
|
return (
|
|
78
165
|
<SidebarContext.Provider value={context}>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
166
|
+
<TooltipProvider delayDuration={0}>
|
|
167
|
+
<div
|
|
168
|
+
data-collapsed={resolvedCollapsed ? "true" : "false"}
|
|
169
|
+
data-slot="sidebar-provider"
|
|
170
|
+
dir={dir}
|
|
171
|
+
className={cn("relative min-w-0", className)}
|
|
172
|
+
{...props}
|
|
173
|
+
>
|
|
174
|
+
{children}
|
|
175
|
+
</div>
|
|
176
|
+
</TooltipProvider>
|
|
88
177
|
</SidebarContext.Provider>
|
|
89
178
|
)
|
|
90
179
|
}
|
|
91
180
|
|
|
181
|
+
/** The 280px navigation surface that contracts to the Figma 48px icon rail. */
|
|
92
182
|
const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
93
183
|
{ className, side = "start", ...props },
|
|
94
184
|
ref
|
|
@@ -99,15 +189,16 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
|
99
189
|
|
|
100
190
|
return (
|
|
101
191
|
<aside
|
|
102
|
-
data-collapsed={collapsed ? "true" :
|
|
192
|
+
data-collapsed={collapsed ? "true" : "false"}
|
|
103
193
|
data-side={side}
|
|
104
194
|
data-slot="sidebar"
|
|
105
195
|
dir={dir}
|
|
106
196
|
ref={ref}
|
|
107
197
|
className={cn(
|
|
108
|
-
"group/sidebar flex h-full min-h-[var(--bh-space-19xl-384)] w-[var(--bh-sidebar-width)] max-w-full shrink-0 flex-col overflow-hidden bg-[var(--bh-sidebar-bg)] text-[var(--bh-content-default)]
|
|
198
|
+
"group/sidebar flex h-full min-h-[var(--bh-space-19xl-384)] w-[var(--bh-sidebar-width)] max-w-full shrink-0 flex-col overflow-hidden bg-[var(--bh-sidebar-bg)] text-[var(--bh-content-default)] transition-[width,background-color]",
|
|
199
|
+
"border-e border-[var(--bh-sidebar-border)]",
|
|
109
200
|
"data-[collapsed=true]:w-[var(--bh-sidebar-width-collapsed)] data-[collapsed=true]:bg-[var(--bh-sidebar-bg-collapsed)]",
|
|
110
|
-
side === "end" && "ms-auto",
|
|
201
|
+
side === "end" && "ms-auto border-e-0 border-s",
|
|
111
202
|
className
|
|
112
203
|
)}
|
|
113
204
|
{...props}
|
|
@@ -115,12 +206,14 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
|
115
206
|
)
|
|
116
207
|
})
|
|
117
208
|
|
|
209
|
+
/** Header slot with the Figma 10px expanded and 4px/8px collapsed insets. */
|
|
118
210
|
function SidebarHeader({ className, ...props }: SidebarSectionProps) {
|
|
119
211
|
return (
|
|
120
212
|
<div
|
|
121
213
|
data-slot="sidebar-header"
|
|
122
214
|
className={cn(
|
|
123
|
-
"flex
|
|
215
|
+
"flex shrink-0 flex-col items-start gap-[var(--bh-space-lg-10)] overflow-hidden bg-[var(--bh-sidebar-bg)] p-[var(--bh-sidebar-padding)]",
|
|
216
|
+
"group-data-[collapsed=true]/sidebar:items-center group-data-[collapsed=true]/sidebar:px-[var(--bh-space-xs-4)] group-data-[collapsed=true]/sidebar:py-[var(--bh-space-md-8)]",
|
|
124
217
|
className
|
|
125
218
|
)}
|
|
126
219
|
{...props}
|
|
@@ -128,12 +221,262 @@ function SidebarHeader({ className, ...props }: SidebarSectionProps) {
|
|
|
128
221
|
)
|
|
129
222
|
}
|
|
130
223
|
|
|
224
|
+
/** Compact 28px ghost action used by Figma sidebar headers. */
|
|
225
|
+
const SidebarHeaderAction = React.forwardRef<
|
|
226
|
+
HTMLButtonElement,
|
|
227
|
+
SidebarHeaderActionProps
|
|
228
|
+
>(function SidebarHeaderAction(
|
|
229
|
+
{ "aria-label": ariaLabel, children, className, label, type = "button", ...props },
|
|
230
|
+
ref
|
|
231
|
+
) {
|
|
232
|
+
return (
|
|
233
|
+
<button
|
|
234
|
+
aria-label={ariaLabel ?? label ?? "Sidebar action"}
|
|
235
|
+
data-slot="sidebar-header-action"
|
|
236
|
+
ref={ref}
|
|
237
|
+
type={type}
|
|
238
|
+
className={cn(
|
|
239
|
+
"inline-flex size-[var(--bh-sidebar-header-action-size)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] border border-transparent bg-[var(--bh-interactive-ghost-default)] text-[var(--bh-content-default)] outline-none transition-[background-color,border-color,box-shadow,color]",
|
|
240
|
+
"hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:border-[var(--bh-border-focus)] focus-visible:shadow-[var(--shadow-button-focus)]",
|
|
241
|
+
className
|
|
242
|
+
)}
|
|
243
|
+
{...props}
|
|
244
|
+
>
|
|
245
|
+
<span
|
|
246
|
+
aria-hidden="true"
|
|
247
|
+
data-slot="sidebar-header-action-icon"
|
|
248
|
+
className="inline-flex size-[var(--bh-sidebar-icon-slot-size)] items-center justify-center [&_svg]:size-[var(--bh-sidebar-icon-size)] [&_svg]:shrink-0"
|
|
249
|
+
>
|
|
250
|
+
{children}
|
|
251
|
+
</span>
|
|
252
|
+
</button>
|
|
253
|
+
)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
/** Brand header with optional search and collapse actions; actions disappear on the icon rail. */
|
|
257
|
+
function SidebarHeaderBrand({
|
|
258
|
+
brand = "Banhaten",
|
|
259
|
+
className,
|
|
260
|
+
collapseAction,
|
|
261
|
+
logo,
|
|
262
|
+
onSearchClick,
|
|
263
|
+
searchAction,
|
|
264
|
+
searchLabel = "Search",
|
|
265
|
+
...props
|
|
266
|
+
}: SidebarHeaderBrandProps) {
|
|
267
|
+
const { collapsed } = useSidebarContext()
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<div
|
|
271
|
+
data-collapsed={collapsed ? "true" : "false"}
|
|
272
|
+
data-slot="sidebar-header-brand"
|
|
273
|
+
className={cn(
|
|
274
|
+
"flex h-[var(--bh-sidebar-brand-height)] w-full items-center justify-between py-[var(--bh-space-xs-4)] ps-[var(--bh-space-sm-6)] pe-[var(--bh-space-xs-4)]",
|
|
275
|
+
collapsed &&
|
|
276
|
+
"size-[var(--bh-sidebar-compact-control-size)] justify-center gap-[var(--bh-space-md-8)] p-0",
|
|
277
|
+
className
|
|
278
|
+
)}
|
|
279
|
+
{...props}
|
|
280
|
+
>
|
|
281
|
+
<span
|
|
282
|
+
data-slot="sidebar-brand-content"
|
|
283
|
+
className="flex min-w-0 items-center gap-[var(--bh-space-md-8)]"
|
|
284
|
+
>
|
|
285
|
+
<span
|
|
286
|
+
data-slot="sidebar-brand-logo"
|
|
287
|
+
className="inline-flex size-[var(--bh-sidebar-header-action-size)] shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] [&>*]:size-full"
|
|
288
|
+
>
|
|
289
|
+
{logo ?? (
|
|
290
|
+
<span className="inline-flex size-full items-center justify-center bg-[var(--bh-bg-brand-default)] text-[var(--bh-content-on-brand)]">
|
|
291
|
+
<SparklesIcon
|
|
292
|
+
aria-hidden="true"
|
|
293
|
+
className="size-[var(--bh-sidebar-icon-size)]"
|
|
294
|
+
/>
|
|
295
|
+
</span>
|
|
296
|
+
)}
|
|
297
|
+
</span>
|
|
298
|
+
{!collapsed ? (
|
|
299
|
+
<span
|
|
300
|
+
data-slot="sidebar-brand-name"
|
|
301
|
+
dir="auto"
|
|
302
|
+
className="truncate text-[length:var(--bh-text-body-xl-semibold-font-size)] font-[var(--bh-text-body-xl-semibold-font-weight)] leading-[var(--bh-text-body-xl-semibold-line-height)] tracking-[var(--bh-text-body-xl-semibold-letter-spacing)]"
|
|
303
|
+
>
|
|
304
|
+
{brand}
|
|
305
|
+
</span>
|
|
306
|
+
) : null}
|
|
307
|
+
</span>
|
|
308
|
+
|
|
309
|
+
{!collapsed ? (
|
|
310
|
+
<span
|
|
311
|
+
data-slot="sidebar-brand-actions"
|
|
312
|
+
className="flex shrink-0 items-center gap-[var(--bh-space-sm-6)]"
|
|
313
|
+
>
|
|
314
|
+
{searchAction === false
|
|
315
|
+
? null
|
|
316
|
+
: searchAction ?? (
|
|
317
|
+
<SidebarHeaderAction label={searchLabel} onClick={onSearchClick}>
|
|
318
|
+
<SearchIcon aria-hidden="true" />
|
|
319
|
+
</SidebarHeaderAction>
|
|
320
|
+
)}
|
|
321
|
+
{collapseAction === false ? null : collapseAction ?? <SidebarTrigger />}
|
|
322
|
+
</span>
|
|
323
|
+
) : null}
|
|
324
|
+
</div>
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Workspace switcher in either the header-actions or bordered card treatment. */
|
|
329
|
+
function SidebarHeaderWorkspaceSwitch({
|
|
330
|
+
className,
|
|
331
|
+
collapseAction,
|
|
332
|
+
image,
|
|
333
|
+
label = "Acme Inc",
|
|
334
|
+
onSearchClick,
|
|
335
|
+
onWorkspaceClick,
|
|
336
|
+
searchAction,
|
|
337
|
+
searchLabel = "Search",
|
|
338
|
+
supportText = "Pro plan",
|
|
339
|
+
variant = "actions",
|
|
340
|
+
workspaceLabel = "Switch workspace",
|
|
341
|
+
...props
|
|
342
|
+
}: SidebarHeaderWorkspaceSwitchProps) {
|
|
343
|
+
const { collapsed } = useSidebarContext()
|
|
344
|
+
const card = variant === "card"
|
|
345
|
+
|
|
346
|
+
const workspaceButton = (
|
|
347
|
+
<button
|
|
348
|
+
aria-label={workspaceLabel}
|
|
349
|
+
data-slot="sidebar-workspace-trigger"
|
|
350
|
+
onClick={onWorkspaceClick}
|
|
351
|
+
type="button"
|
|
352
|
+
className={cn(
|
|
353
|
+
"group/workspace relative flex min-w-0 items-center text-start outline-none transition-[background-color,border-color,box-shadow]",
|
|
354
|
+
card
|
|
355
|
+
? "h-[var(--bh-sidebar-workspace-card-height)] w-full gap-[var(--bh-space-md-8)] overflow-hidden rounded-[var(--bh-radius-lg-8)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-default)] p-[var(--bh-space-sm-6)] hover:bg-[var(--bh-bg-default-hover)] focus-visible:border-[length:var(--bh-border-width-strong)] focus-visible:border-[var(--bh-border-focus)]"
|
|
356
|
+
: "h-[var(--bh-sidebar-compact-control-size)] w-[var(--bh-sidebar-workspace-trigger-width)] max-w-[var(--bh-sidebar-workspace-trigger-max-width)] gap-[var(--bh-space-md-8)] rounded-[var(--bh-radius-md-6)] p-[var(--bh-space-sm-6)] hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:border-[length:var(--bh-border-width-strong)] focus-visible:border-[var(--bh-border-focus)]",
|
|
357
|
+
collapsed && card &&
|
|
358
|
+
"size-[var(--bh-sidebar-compact-control-size)] justify-center gap-0 p-0",
|
|
359
|
+
collapsed && !card &&
|
|
360
|
+
"size-[var(--bh-sidebar-item-height-collapsed)] max-w-[var(--bh-sidebar-item-height-collapsed)] justify-center gap-0 p-0"
|
|
361
|
+
)}
|
|
362
|
+
>
|
|
363
|
+
<span
|
|
364
|
+
data-slot="sidebar-workspace-image"
|
|
365
|
+
className={cn(
|
|
366
|
+
"inline-flex shrink-0 items-center justify-center overflow-hidden [&>*]:size-full",
|
|
367
|
+
card
|
|
368
|
+
? "size-[var(--bh-space-6xl-32)] rounded-[var(--bh-radius-md-6)]"
|
|
369
|
+
: "size-[var(--bh-space-5xl-24)] rounded-[var(--bh-radius-lg-8)]",
|
|
370
|
+
collapsed && !card &&
|
|
371
|
+
"size-[var(--bh-sidebar-header-action-size)] rounded-[var(--bh-radius-full)]"
|
|
372
|
+
)}
|
|
373
|
+
>
|
|
374
|
+
{image ?? (
|
|
375
|
+
<Building2Icon
|
|
376
|
+
aria-hidden="true"
|
|
377
|
+
className="size-[var(--bh-sidebar-icon-size)]"
|
|
378
|
+
/>
|
|
379
|
+
)}
|
|
380
|
+
</span>
|
|
381
|
+
|
|
382
|
+
{!collapsed ? (
|
|
383
|
+
card ? (
|
|
384
|
+
<span className="flex min-w-0 flex-1 flex-col items-start justify-center">
|
|
385
|
+
<span
|
|
386
|
+
data-slot="sidebar-workspace-label"
|
|
387
|
+
dir="auto"
|
|
388
|
+
className="w-full truncate text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)]"
|
|
389
|
+
>
|
|
390
|
+
{label}
|
|
391
|
+
</span>
|
|
392
|
+
<span
|
|
393
|
+
data-slot="sidebar-workspace-support"
|
|
394
|
+
dir="auto"
|
|
395
|
+
className="w-full truncate text-[length:var(--bh-text-body-2xs-regular-font-size)] font-[var(--bh-text-body-2xs-regular-font-weight)] leading-[var(--bh-text-body-2xs-regular-line-height)] tracking-[var(--bh-text-body-2xs-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
|
|
396
|
+
>
|
|
397
|
+
{supportText}
|
|
398
|
+
</span>
|
|
399
|
+
</span>
|
|
400
|
+
) : (
|
|
401
|
+
<span className="flex min-w-0 items-center gap-[var(--bh-space-xxs-2)]">
|
|
402
|
+
<span
|
|
403
|
+
data-slot="sidebar-workspace-label"
|
|
404
|
+
dir="auto"
|
|
405
|
+
className="truncate text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)]"
|
|
406
|
+
>
|
|
407
|
+
{label}
|
|
408
|
+
</span>
|
|
409
|
+
<ChevronDownIcon
|
|
410
|
+
aria-hidden="true"
|
|
411
|
+
className="size-[var(--bh-space-3xl-16)] shrink-0"
|
|
412
|
+
data-rtl-flip="true"
|
|
413
|
+
/>
|
|
414
|
+
</span>
|
|
415
|
+
)
|
|
416
|
+
) : !card ? (
|
|
417
|
+
<span
|
|
418
|
+
aria-hidden="true"
|
|
419
|
+
data-slot="sidebar-workspace-caret"
|
|
420
|
+
className="absolute end-0 top-1/2 inline-flex size-[var(--bh-space-3xl-16)] -translate-y-1/2 items-center justify-center rounded-[var(--bh-radius-full)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-default)]"
|
|
421
|
+
>
|
|
422
|
+
<ChevronDownIcon className="size-[var(--bh-space-xl-12)]" />
|
|
423
|
+
</span>
|
|
424
|
+
) : null}
|
|
425
|
+
|
|
426
|
+
{card && !collapsed ? (
|
|
427
|
+
<ChevronsUpDownIcon
|
|
428
|
+
aria-hidden="true"
|
|
429
|
+
className="size-[var(--bh-space-3xl-16)] shrink-0 text-[var(--bh-content-muted)]"
|
|
430
|
+
/>
|
|
431
|
+
) : null}
|
|
432
|
+
</button>
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<div
|
|
437
|
+
data-collapsed={collapsed ? "true" : "false"}
|
|
438
|
+
data-slot="sidebar-header-workspace-switch"
|
|
439
|
+
data-variant={variant}
|
|
440
|
+
className={cn(
|
|
441
|
+
"flex w-full items-center",
|
|
442
|
+
card
|
|
443
|
+
? "h-[var(--bh-sidebar-workspace-card-height)]"
|
|
444
|
+
: "h-[var(--bh-sidebar-compact-control-size)] justify-between pe-[var(--bh-space-md-8)]",
|
|
445
|
+
collapsed && card && "size-[var(--bh-sidebar-compact-control-size)]",
|
|
446
|
+
collapsed && !card &&
|
|
447
|
+
"size-[var(--bh-sidebar-item-height-collapsed)] justify-center p-0",
|
|
448
|
+
className
|
|
449
|
+
)}
|
|
450
|
+
{...props}
|
|
451
|
+
>
|
|
452
|
+
{workspaceButton}
|
|
453
|
+
{!card && !collapsed ? (
|
|
454
|
+
<span
|
|
455
|
+
data-slot="sidebar-workspace-actions"
|
|
456
|
+
className="flex shrink-0 items-center gap-[var(--bh-space-sm-6)]"
|
|
457
|
+
>
|
|
458
|
+
{searchAction === false
|
|
459
|
+
? null
|
|
460
|
+
: searchAction ?? (
|
|
461
|
+
<SidebarHeaderAction label={searchLabel} onClick={onSearchClick}>
|
|
462
|
+
<SearchIcon aria-hidden="true" />
|
|
463
|
+
</SidebarHeaderAction>
|
|
464
|
+
)}
|
|
465
|
+
{collapseAction === false ? null : collapseAction ?? <SidebarTrigger />}
|
|
466
|
+
</span>
|
|
467
|
+
) : null}
|
|
468
|
+
</div>
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** Scrollable content region with Figma's expanded and rail insets. */
|
|
131
473
|
function SidebarBody({ className, ...props }: SidebarSectionProps) {
|
|
132
474
|
return (
|
|
133
475
|
<div
|
|
134
476
|
data-slot="sidebar-body"
|
|
135
477
|
className={cn(
|
|
136
|
-
"min-h-0 flex-1 overflow-auto px-[var(--bh-
|
|
478
|
+
"min-h-0 flex-1 overflow-auto px-[var(--bh-space-xl-12)] py-0",
|
|
479
|
+
"group-data-[collapsed=true]/sidebar:px-0",
|
|
137
480
|
className
|
|
138
481
|
)}
|
|
139
482
|
{...props}
|
|
@@ -141,12 +484,14 @@ function SidebarBody({ className, ...props }: SidebarSectionProps) {
|
|
|
141
484
|
)
|
|
142
485
|
}
|
|
143
486
|
|
|
487
|
+
/** Bottom-pinned footer with the matching expanded and collapsed padding. */
|
|
144
488
|
function SidebarFooter({ className, ...props }: SidebarSectionProps) {
|
|
145
489
|
return (
|
|
146
490
|
<div
|
|
147
491
|
data-slot="sidebar-footer"
|
|
148
492
|
className={cn(
|
|
149
|
-
"shrink-0 border-t border-[var(--bh-sidebar-border)] px-[var(--bh-sidebar-padding)] py-[var(--bh-space-
|
|
493
|
+
"mt-auto shrink-0 border-t border-[var(--bh-sidebar-border)] bg-[var(--bh-sidebar-bg)] px-[var(--bh-sidebar-padding)] py-[var(--bh-space-md-8)]",
|
|
494
|
+
"group-data-[collapsed=true]/sidebar:p-[var(--bh-space-xs-4)]",
|
|
150
495
|
className
|
|
151
496
|
)}
|
|
152
497
|
{...props}
|
|
@@ -154,17 +499,132 @@ function SidebarFooter({ className, ...props }: SidebarSectionProps) {
|
|
|
154
499
|
)
|
|
155
500
|
}
|
|
156
501
|
|
|
502
|
+
/** User switcher used in the footer, with 48px supporting and 40px compact rows. */
|
|
503
|
+
const SidebarFooterUser = React.forwardRef<
|
|
504
|
+
HTMLButtonElement,
|
|
505
|
+
SidebarFooterUserProps
|
|
506
|
+
>(function SidebarFooterUser(
|
|
507
|
+
{
|
|
508
|
+
"aria-label": ariaLabel,
|
|
509
|
+
avatar,
|
|
510
|
+
className,
|
|
511
|
+
name = "Username",
|
|
512
|
+
supportText = "user@example.com",
|
|
513
|
+
trailingIcon,
|
|
514
|
+
type = "button",
|
|
515
|
+
variant = "supporting-text",
|
|
516
|
+
...props
|
|
517
|
+
},
|
|
518
|
+
ref
|
|
519
|
+
) {
|
|
520
|
+
const { collapsed } = useSidebarContext()
|
|
521
|
+
const supporting = variant === "supporting-text"
|
|
522
|
+
const resolvedLabel =
|
|
523
|
+
ariaLabel ?? (typeof name === "string" ? name : "Open user menu")
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<button
|
|
527
|
+
aria-label={resolvedLabel}
|
|
528
|
+
data-collapsed={collapsed ? "true" : "false"}
|
|
529
|
+
data-slot="sidebar-footer-user"
|
|
530
|
+
data-variant={variant}
|
|
531
|
+
ref={ref}
|
|
532
|
+
type={type}
|
|
533
|
+
className={cn(
|
|
534
|
+
"flex w-full items-center rounded-[var(--bh-radius-lg-8)] text-start outline-none transition-[background-color,border-color,box-shadow]",
|
|
535
|
+
supporting
|
|
536
|
+
? "h-[var(--bh-space-8xl-48)] gap-[var(--bh-space-xs-4)] p-[var(--bh-space-sm-6)] hover:bg-[var(--bh-interactive-ghost-hover)]"
|
|
537
|
+
: "h-[var(--bh-sidebar-item-height-collapsed)] gap-[var(--bh-space-xs-4)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-default)] px-[var(--bh-space-sm-6)] py-[var(--bh-space-md-8)] hover:bg-[var(--bh-bg-default-hover)]",
|
|
538
|
+
"focus-visible:border-[length:var(--bh-border-width-strong)] focus-visible:border-[var(--bh-border-focus)]",
|
|
539
|
+
collapsed &&
|
|
540
|
+
"mx-auto size-[var(--bh-sidebar-compact-control-size)] justify-center gap-0 rounded-[var(--bh-radius-full)] border-0 p-0 hover:bg-[var(--bh-interactive-ghost-hover)]",
|
|
541
|
+
className
|
|
542
|
+
)}
|
|
543
|
+
{...props}
|
|
544
|
+
>
|
|
545
|
+
<span
|
|
546
|
+
data-slot="sidebar-footer-user-content"
|
|
547
|
+
className={cn(
|
|
548
|
+
"flex min-w-0 flex-1 items-center gap-[var(--bh-space-md-8)] px-[var(--bh-space-xxs-2)]",
|
|
549
|
+
collapsed &&
|
|
550
|
+
"size-[var(--bh-sidebar-compact-control-size)] flex-none justify-center gap-0 p-0"
|
|
551
|
+
)}
|
|
552
|
+
>
|
|
553
|
+
<span
|
|
554
|
+
data-slot="sidebar-footer-user-avatar"
|
|
555
|
+
className={cn(
|
|
556
|
+
"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] [&>*]:size-full",
|
|
557
|
+
supporting ? "size-[var(--bh-space-6xl-32)]" : "size-[var(--bh-space-5xl-24)]",
|
|
558
|
+
collapsed && "size-[var(--bh-sidebar-compact-control-size)]"
|
|
559
|
+
)}
|
|
560
|
+
>
|
|
561
|
+
{avatar ?? (
|
|
562
|
+
<UserIcon
|
|
563
|
+
aria-hidden="true"
|
|
564
|
+
className="size-[var(--bh-sidebar-icon-size)]"
|
|
565
|
+
/>
|
|
566
|
+
)}
|
|
567
|
+
</span>
|
|
568
|
+
{!collapsed ? (
|
|
569
|
+
<span
|
|
570
|
+
data-slot="sidebar-footer-user-text"
|
|
571
|
+
className="flex min-w-0 flex-1 flex-col items-start justify-center"
|
|
572
|
+
>
|
|
573
|
+
<span
|
|
574
|
+
data-slot="sidebar-footer-user-name"
|
|
575
|
+
dir="auto"
|
|
576
|
+
className="w-full truncate text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)]"
|
|
577
|
+
>
|
|
578
|
+
{name}
|
|
579
|
+
</span>
|
|
580
|
+
{supporting ? (
|
|
581
|
+
<span
|
|
582
|
+
data-slot="sidebar-footer-user-support"
|
|
583
|
+
dir="auto"
|
|
584
|
+
className="w-full truncate text-[length:var(--bh-text-body-2xs-regular-font-size)] font-[var(--bh-text-body-2xs-regular-font-weight)] leading-[var(--bh-text-body-2xs-regular-line-height)] tracking-[var(--bh-text-body-2xs-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
|
|
585
|
+
>
|
|
586
|
+
{supportText}
|
|
587
|
+
</span>
|
|
588
|
+
) : null}
|
|
589
|
+
</span>
|
|
590
|
+
) : null}
|
|
591
|
+
</span>
|
|
592
|
+
{!collapsed ? (
|
|
593
|
+
<span
|
|
594
|
+
aria-hidden="true"
|
|
595
|
+
data-slot="sidebar-footer-user-trailing"
|
|
596
|
+
className="inline-flex size-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] [&_svg]:size-[var(--bh-space-3xl-16)]"
|
|
597
|
+
>
|
|
598
|
+
{trailingIcon ?? <ChevronsUpDownIcon />}
|
|
599
|
+
</span>
|
|
600
|
+
) : null}
|
|
601
|
+
</button>
|
|
602
|
+
)
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
/** A Figma sidebar group: 12px top padding, 6px bottom padding, and no menu gap. */
|
|
157
606
|
function SidebarGroup({ className, ...props }: SidebarSectionProps) {
|
|
158
607
|
return (
|
|
159
608
|
<div
|
|
160
609
|
data-slot="sidebar-group"
|
|
161
|
-
className={cn(
|
|
610
|
+
className={cn(
|
|
611
|
+
"flex w-full flex-col items-start pb-[var(--bh-space-sm-6)] pt-[var(--bh-space-xl-12)]",
|
|
612
|
+
className
|
|
613
|
+
)}
|
|
162
614
|
{...props}
|
|
163
615
|
/>
|
|
164
616
|
)
|
|
165
617
|
}
|
|
166
618
|
|
|
167
|
-
|
|
619
|
+
/** Group label that reveals its disclosure and actions on hover or keyboard focus. */
|
|
620
|
+
function SidebarGroupLabel({
|
|
621
|
+
actions,
|
|
622
|
+
children,
|
|
623
|
+
className,
|
|
624
|
+
expandIcon,
|
|
625
|
+
expandable = false,
|
|
626
|
+
...props
|
|
627
|
+
}: SidebarGroupLabelProps) {
|
|
168
628
|
const { collapsed } = useSidebarContext()
|
|
169
629
|
|
|
170
630
|
if (collapsed) return null
|
|
@@ -172,26 +632,84 @@ function SidebarGroupLabel({ className, ...props }: SidebarGroupLabelProps) {
|
|
|
172
632
|
return (
|
|
173
633
|
<div
|
|
174
634
|
data-slot="sidebar-group-label"
|
|
175
|
-
dir="auto"
|
|
176
635
|
className={cn(
|
|
177
|
-
"
|
|
636
|
+
"group/label flex h-[var(--bh-space-5xl-24)] w-full items-center gap-[var(--bh-space-xs-4)] rounded-[var(--bh-radius-lg-8)] px-[var(--bh-space-sm-6)] pb-[var(--bh-space-xs-4)]",
|
|
178
637
|
className
|
|
179
638
|
)}
|
|
180
639
|
{...props}
|
|
181
|
-
|
|
640
|
+
>
|
|
641
|
+
<span
|
|
642
|
+
data-slot="sidebar-group-label-content"
|
|
643
|
+
className="flex min-w-0 flex-1 items-center gap-[var(--bh-space-xxxs-1)]"
|
|
644
|
+
>
|
|
645
|
+
<span
|
|
646
|
+
data-slot="sidebar-group-label-text"
|
|
647
|
+
dir="auto"
|
|
648
|
+
className="truncate text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)] text-[var(--bh-content-muted)]"
|
|
649
|
+
>
|
|
650
|
+
{children}
|
|
651
|
+
</span>
|
|
652
|
+
{expandable ? (
|
|
653
|
+
<span
|
|
654
|
+
aria-hidden="true"
|
|
655
|
+
data-slot="sidebar-group-label-expand"
|
|
656
|
+
className="inline-flex size-[var(--bh-space-3xl-16)] shrink-0 items-center justify-center opacity-[var(--bh-opacity-0)] transition-opacity group-hover/label:opacity-[var(--bh-opacity-100)] group-focus-within/label:opacity-[var(--bh-opacity-100)] [&_svg]:size-full"
|
|
657
|
+
>
|
|
658
|
+
{expandIcon ?? <ChevronRightIcon data-rtl-flip="true" />}
|
|
659
|
+
</span>
|
|
660
|
+
) : null}
|
|
661
|
+
</span>
|
|
662
|
+
{actions ? (
|
|
663
|
+
<span
|
|
664
|
+
data-slot="sidebar-group-label-actions"
|
|
665
|
+
className="flex shrink-0 items-center gap-[var(--bh-space-xs-4)] opacity-[var(--bh-opacity-0)] transition-opacity group-hover/label:opacity-[var(--bh-opacity-100)] group-focus-within/label:opacity-[var(--bh-opacity-100)]"
|
|
666
|
+
>
|
|
667
|
+
{actions}
|
|
668
|
+
</span>
|
|
669
|
+
) : null}
|
|
670
|
+
</div>
|
|
182
671
|
)
|
|
183
672
|
}
|
|
184
673
|
|
|
674
|
+
/** 20px group-label icon slot matching the Default, Hover, and Focus variants. */
|
|
675
|
+
const SidebarGroupAction = React.forwardRef<
|
|
676
|
+
HTMLButtonElement,
|
|
677
|
+
SidebarGroupActionProps
|
|
678
|
+
>(function SidebarGroupAction(
|
|
679
|
+
{ "aria-label": ariaLabel, children, className, label, type = "button", ...props },
|
|
680
|
+
ref
|
|
681
|
+
) {
|
|
682
|
+
return (
|
|
683
|
+
<button
|
|
684
|
+
aria-label={ariaLabel ?? label ?? "Group action"}
|
|
685
|
+
data-slot="sidebar-group-action"
|
|
686
|
+
ref={ref}
|
|
687
|
+
type={type}
|
|
688
|
+
className={cn(
|
|
689
|
+
"inline-flex size-[var(--bh-sidebar-icon-slot-size)] items-center justify-center rounded-[var(--bh-control-sm)] border border-transparent bg-[var(--bh-interactive-ghost-default)] text-[var(--bh-content-muted)] outline-none transition-[background-color,border-color,color]",
|
|
690
|
+
"hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)] focus-visible:border-[var(--bh-border-focus)]",
|
|
691
|
+
"[&_svg]:size-[var(--bh-sidebar-icon-size)] [&_svg]:shrink-0",
|
|
692
|
+
className
|
|
693
|
+
)}
|
|
694
|
+
{...props}
|
|
695
|
+
>
|
|
696
|
+
{children}
|
|
697
|
+
</button>
|
|
698
|
+
)
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
/** List wrapper for contiguous 32px sidebar menu rows. */
|
|
185
702
|
function SidebarMenu({ className, ...props }: SidebarMenuProps) {
|
|
186
703
|
return (
|
|
187
704
|
<ul
|
|
188
705
|
data-slot="sidebar-menu"
|
|
189
|
-
className={cn("m-0
|
|
706
|
+
className={cn("m-0 flex w-full list-none flex-col p-0", className)}
|
|
190
707
|
{...props}
|
|
191
708
|
/>
|
|
192
709
|
)
|
|
193
710
|
}
|
|
194
711
|
|
|
712
|
+
/** Semantic list item wrapper. */
|
|
195
713
|
function SidebarMenuItem({ className, ...props }: SidebarMenuItemProps) {
|
|
196
714
|
return (
|
|
197
715
|
<li
|
|
@@ -202,50 +720,359 @@ function SidebarMenuItem({ className, ...props }: SidebarMenuItemProps) {
|
|
|
202
720
|
)
|
|
203
721
|
}
|
|
204
722
|
|
|
723
|
+
/** Native 24px neutral count badge for sidebar rows. */
|
|
724
|
+
function SidebarMenuBadge({ children, className, ...props }: SidebarMenuBadgeProps) {
|
|
725
|
+
return (
|
|
726
|
+
<span
|
|
727
|
+
data-slot="sidebar-menu-badge"
|
|
728
|
+
className={cn(
|
|
729
|
+
"inline-flex h-[var(--bh-space-5xl-24)] min-w-[var(--bh-space-5xl-24)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-subtle)] px-[var(--bh-space-xs-4)] text-center text-[length:var(--bh-text-body-3xs-semibold-font-size)] font-[var(--bh-text-body-3xs-semibold-font-weight)] leading-[var(--bh-text-body-3xs-semibold-line-height)] tracking-[var(--bh-text-body-3xs-semibold-letter-spacing)] text-[var(--bh-content-default)]",
|
|
730
|
+
className
|
|
731
|
+
)}
|
|
732
|
+
{...props}
|
|
733
|
+
>
|
|
734
|
+
{children}
|
|
735
|
+
</span>
|
|
736
|
+
)
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** Default, avatar, Level 2, and Level 2 line menu variants from the Figma set. */
|
|
205
740
|
const SidebarMenuButton = React.forwardRef<
|
|
206
741
|
HTMLButtonElement,
|
|
207
742
|
SidebarMenuButtonProps
|
|
208
743
|
>(function SidebarMenuButton(
|
|
209
|
-
{
|
|
744
|
+
{
|
|
745
|
+
"aria-label": ariaLabel,
|
|
746
|
+
active = false,
|
|
747
|
+
asChild = false,
|
|
748
|
+
badge,
|
|
749
|
+
children,
|
|
750
|
+
className,
|
|
751
|
+
icon,
|
|
752
|
+
state = "default",
|
|
753
|
+
supportText,
|
|
754
|
+
tooltip,
|
|
755
|
+
trailingIcon,
|
|
756
|
+
type = "button",
|
|
757
|
+
variant = "default",
|
|
758
|
+
...props
|
|
759
|
+
},
|
|
210
760
|
ref
|
|
211
761
|
) {
|
|
212
|
-
const { collapsed } = useSidebarContext()
|
|
213
|
-
const
|
|
762
|
+
const { collapsed, dir } = useSidebarContext()
|
|
763
|
+
const isLevel2 = variant === "level2" || variant === "level2-line"
|
|
764
|
+
const label = getSlottableLabel(children, asChild)
|
|
765
|
+
const resolvedLabel =
|
|
766
|
+
ariaLabel ?? (typeof label === "string" ? label : undefined)
|
|
767
|
+
|
|
768
|
+
if (collapsed && isLevel2) return null
|
|
769
|
+
|
|
770
|
+
const content = (
|
|
771
|
+
<>
|
|
772
|
+
{isLevel2 ? (
|
|
773
|
+
<span
|
|
774
|
+
aria-hidden="true"
|
|
775
|
+
data-slot="sidebar-menu-indent"
|
|
776
|
+
className="relative h-full w-[var(--bh-sidebar-level-2-indent-width)] shrink-0"
|
|
777
|
+
>
|
|
778
|
+
{variant === "level2-line" ? (
|
|
779
|
+
<span
|
|
780
|
+
data-slot="sidebar-menu-guide"
|
|
781
|
+
className={cn(
|
|
782
|
+
"absolute start-[var(--bh-space-md-8)]",
|
|
783
|
+
active
|
|
784
|
+
? "top-1/2 h-[var(--bh-sidebar-active-guide-height)] -translate-y-1/2 border-e-[length:var(--bh-border-width-strong)] border-[var(--bh-bg-brand-default)]"
|
|
785
|
+
: "inset-y-0 border-e-[length:var(--bh-border-width-default)] border-[var(--bh-border-subtle)]"
|
|
786
|
+
)}
|
|
787
|
+
/>
|
|
788
|
+
) : null}
|
|
789
|
+
</span>
|
|
790
|
+
) : null}
|
|
214
791
|
|
|
215
|
-
return (
|
|
216
|
-
<Comp
|
|
217
|
-
aria-current={active ? "page" : undefined}
|
|
218
|
-
data-active={active ? "true" : undefined}
|
|
219
|
-
data-slot="sidebar-menu-button"
|
|
220
|
-
ref={ref}
|
|
221
|
-
className={cn(
|
|
222
|
-
"flex h-[var(--bh-sidebar-item-height)] w-full min-w-0 items-center gap-[var(--bh-sidebar-item-gap)] rounded-[var(--bh-radius-lg-8)] px-[var(--bh-sidebar-item-padding-x)] text-start text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)] text-[var(--bh-sidebar-item-content)] outline-none transition-[background-color,box-shadow,color]",
|
|
223
|
-
"hover:bg-[var(--bh-sidebar-item-bg-hover)] focus-visible:shadow-[var(--shadow-button-focus)] data-[active=true]:bg-[var(--bh-sidebar-item-bg-active)] data-[active=true]:text-[var(--bh-sidebar-item-content-active)]",
|
|
224
|
-
collapsed && "justify-center px-[var(--bh-space-md-8)]",
|
|
225
|
-
className
|
|
226
|
-
)}
|
|
227
|
-
{...props}
|
|
228
|
-
>
|
|
229
792
|
{icon ? (
|
|
230
793
|
<span
|
|
231
794
|
aria-hidden="true"
|
|
232
795
|
data-slot="sidebar-menu-icon"
|
|
233
|
-
className=
|
|
796
|
+
className={cn(
|
|
797
|
+
"inline-flex shrink-0 items-center justify-center overflow-hidden [&>*]:size-full [&_svg]:shrink-0",
|
|
798
|
+
variant === "avatar"
|
|
799
|
+
? "size-[var(--bh-space-4xl-20)] rounded-[var(--bh-radius-md-6)]"
|
|
800
|
+
: "size-[var(--bh-sidebar-icon-slot-size)] [&_svg]:size-[var(--bh-sidebar-icon-size)]",
|
|
801
|
+
collapsed && variant === "avatar" &&
|
|
802
|
+
"size-[var(--bh-space-5xl-24)] rounded-[var(--bh-radius-lg-8)]",
|
|
803
|
+
collapsed && variant === "default" &&
|
|
804
|
+
"[&_svg]:size-[var(--bh-sidebar-icon-size-collapsed)]"
|
|
805
|
+
)}
|
|
234
806
|
>
|
|
235
807
|
{icon}
|
|
236
808
|
</span>
|
|
237
809
|
) : null}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
810
|
+
|
|
811
|
+
{!collapsed ? (
|
|
812
|
+
<span
|
|
813
|
+
data-slot="sidebar-menu-label"
|
|
814
|
+
dir="auto"
|
|
815
|
+
className="min-w-0 flex-1 truncate ps-[var(--bh-space-xs-4)]"
|
|
816
|
+
>
|
|
817
|
+
{label}
|
|
241
818
|
</span>
|
|
242
|
-
)}
|
|
243
|
-
|
|
819
|
+
) : null}
|
|
820
|
+
|
|
821
|
+
{!collapsed && supportText ? (
|
|
822
|
+
<span
|
|
823
|
+
data-slot="sidebar-menu-support"
|
|
824
|
+
dir="auto"
|
|
825
|
+
className="shrink-0 whitespace-nowrap text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-muted)]"
|
|
826
|
+
>
|
|
827
|
+
{supportText}
|
|
828
|
+
</span>
|
|
829
|
+
) : null}
|
|
830
|
+
|
|
831
|
+
{!collapsed && badge ? (
|
|
832
|
+
<span data-slot="sidebar-menu-badge-slot" className="inline-flex shrink-0">
|
|
833
|
+
{badge}
|
|
834
|
+
</span>
|
|
835
|
+
) : null}
|
|
836
|
+
|
|
837
|
+
{!collapsed && trailingIcon ? (
|
|
838
|
+
<span
|
|
839
|
+
aria-hidden="true"
|
|
840
|
+
data-slot="sidebar-menu-trailing"
|
|
841
|
+
className="inline-flex size-[var(--bh-sidebar-icon-slot-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] [&_svg]:size-[var(--bh-sidebar-icon-size)] [&_svg]:shrink-0"
|
|
842
|
+
>
|
|
843
|
+
{trailingIcon}
|
|
844
|
+
</span>
|
|
845
|
+
) : null}
|
|
846
|
+
</>
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
const buttonClassName = cn(
|
|
850
|
+
"group/menu-button relative flex w-full min-w-0 items-center gap-[var(--bh-space-sm-6)] rounded-[var(--bh-radius-lg-8)] border-[length:var(--bh-border-width-strong)] border-transparent px-[var(--bh-sidebar-item-padding-x)] text-start text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-text-body-sm-regular-font-weight)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--bh-sidebar-item-content)] outline-none transition-[background-color,border-color,color]",
|
|
851
|
+
"hover:rounded-[var(--bh-radius-md-6)] hover:bg-[var(--bh-sidebar-item-bg-hover)]",
|
|
852
|
+
"focus-visible:rounded-[var(--bh-radius-md-6)] focus-visible:border-[var(--bh-border-focus)]",
|
|
853
|
+
"data-[state=hover]:rounded-[var(--bh-radius-md-6)] data-[state=hover]:bg-[var(--bh-sidebar-item-bg-hover)]",
|
|
854
|
+
"data-[state=focus]:rounded-[var(--bh-radius-md-6)] data-[state=focus]:border-[var(--bh-border-focus)]",
|
|
855
|
+
"disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
|
|
856
|
+
variant === "avatar"
|
|
857
|
+
? "h-[var(--bh-sidebar-compact-control-size)]"
|
|
858
|
+
: "h-[var(--bh-sidebar-item-height)]",
|
|
859
|
+
(active || state === "active") &&
|
|
860
|
+
"rounded-[var(--bh-radius-md-6)] bg-[var(--bh-sidebar-item-bg-active)] font-[var(--bh-text-body-sm-medium-font-weight)] text-[var(--bh-sidebar-item-content-active)]",
|
|
861
|
+
collapsed &&
|
|
862
|
+
"mx-auto size-[var(--bh-sidebar-item-height-collapsed)] justify-center gap-0 rounded-[var(--bh-radius-lg-8)] p-0",
|
|
863
|
+
className
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
const commonProps = {
|
|
867
|
+
"aria-current": active ? ("page" as const) : undefined,
|
|
868
|
+
"aria-label": resolvedLabel,
|
|
869
|
+
"data-active": active ? "true" : "false",
|
|
870
|
+
"data-collapsed": collapsed ? "true" : "false",
|
|
871
|
+
"data-slot": "sidebar-menu-button",
|
|
872
|
+
"data-state": state,
|
|
873
|
+
"data-variant": variant,
|
|
874
|
+
className: buttonClassName,
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
const button = asChild ? (
|
|
878
|
+
<Slot {...commonProps} ref={ref} {...props}>
|
|
879
|
+
{cloneSlottableChild(children, content)}
|
|
880
|
+
</Slot>
|
|
881
|
+
) : (
|
|
882
|
+
<button {...commonProps} ref={ref} type={type} {...props}>
|
|
883
|
+
{content}
|
|
884
|
+
</button>
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
const resolvedTooltip = tooltip ?? resolvedLabel
|
|
888
|
+
if (!collapsed || !resolvedTooltip) return button
|
|
889
|
+
|
|
890
|
+
return (
|
|
891
|
+
<Tooltip>
|
|
892
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
893
|
+
<TooltipContent
|
|
894
|
+
pointerPosition={dir === "rtl" ? "center-right" : "center-left"}
|
|
895
|
+
showShortcut={false}
|
|
896
|
+
side={dir === "rtl" ? "left" : "right"}
|
|
897
|
+
>
|
|
898
|
+
{resolvedTooltip}
|
|
899
|
+
</TooltipContent>
|
|
900
|
+
</Tooltip>
|
|
244
901
|
)
|
|
245
902
|
})
|
|
246
903
|
|
|
904
|
+
/** Nested menu list that unmounts when the parent sidebar is collapsed. */
|
|
905
|
+
function SidebarMenuSub({ className, ...props }: SidebarMenuSubProps) {
|
|
906
|
+
const { collapsed } = useSidebarContext()
|
|
907
|
+
if (collapsed) return null
|
|
908
|
+
|
|
909
|
+
return (
|
|
910
|
+
<ul
|
|
911
|
+
data-slot="sidebar-menu-sub"
|
|
912
|
+
className={cn("m-0 flex w-full list-none flex-col p-0", className)}
|
|
913
|
+
{...props}
|
|
914
|
+
/>
|
|
915
|
+
)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/** Level 2 menu row with an optional vertical guide. */
|
|
919
|
+
const SidebarMenuSubButton = React.forwardRef<
|
|
920
|
+
HTMLButtonElement,
|
|
921
|
+
SidebarMenuSubButtonProps
|
|
922
|
+
>(function SidebarMenuSubButton({ showLine = false, ...props }, ref) {
|
|
923
|
+
return (
|
|
924
|
+
<SidebarMenuButton
|
|
925
|
+
ref={ref}
|
|
926
|
+
variant={showLine ? "level2-line" : "level2"}
|
|
927
|
+
{...props}
|
|
928
|
+
/>
|
|
929
|
+
)
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
const treeLevelClasses: Record<NonNullable<SidebarMenuTreeItemProps["level"]>, string> = {
|
|
933
|
+
1: "ps-[var(--bh-space-sm-6)]",
|
|
934
|
+
2: "ps-[calc(var(--bh-space-sm-6)+var(--bh-sidebar-tree-indent-step))]",
|
|
935
|
+
3: "ps-[calc(var(--bh-space-sm-6)+var(--bh-sidebar-tree-indent-step)+var(--bh-sidebar-tree-indent-step))]",
|
|
936
|
+
4: "ps-[calc(var(--bh-space-sm-6)+var(--bh-sidebar-tree-indent-step)+var(--bh-sidebar-tree-indent-step)+var(--bh-sidebar-tree-indent-step))]",
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/** 28px file-tree row with four Figma indentation levels. */
|
|
940
|
+
const SidebarMenuTreeItem = React.forwardRef<
|
|
941
|
+
HTMLButtonElement,
|
|
942
|
+
SidebarMenuTreeItemProps
|
|
943
|
+
>(function SidebarMenuTreeItem(
|
|
944
|
+
{
|
|
945
|
+
"aria-label": ariaLabel,
|
|
946
|
+
active = false,
|
|
947
|
+
asChild = false,
|
|
948
|
+
children,
|
|
949
|
+
className,
|
|
950
|
+
expanded = false,
|
|
951
|
+
icon,
|
|
952
|
+
level = 1,
|
|
953
|
+
showChevron = true,
|
|
954
|
+
state = "default",
|
|
955
|
+
type = "button",
|
|
956
|
+
...props
|
|
957
|
+
},
|
|
958
|
+
ref
|
|
959
|
+
) {
|
|
960
|
+
const { collapsed } = useSidebarContext()
|
|
961
|
+
if (collapsed) return null
|
|
962
|
+
|
|
963
|
+
const label = getSlottableLabel(children, asChild)
|
|
964
|
+
const resolvedLabel =
|
|
965
|
+
ariaLabel ?? (typeof label === "string" ? label : undefined)
|
|
966
|
+
const content = (
|
|
967
|
+
<>
|
|
968
|
+
{showChevron ? (
|
|
969
|
+
<span
|
|
970
|
+
aria-hidden="true"
|
|
971
|
+
data-slot="sidebar-tree-chevron"
|
|
972
|
+
className="inline-flex size-[var(--bh-space-3xl-16)] shrink-0 items-center justify-center [&_svg]:size-full"
|
|
973
|
+
>
|
|
974
|
+
{expanded ? (
|
|
975
|
+
<ChevronDownIcon />
|
|
976
|
+
) : (
|
|
977
|
+
<ChevronRightIcon data-rtl-flip="true" />
|
|
978
|
+
)}
|
|
979
|
+
</span>
|
|
980
|
+
) : null}
|
|
981
|
+
<span
|
|
982
|
+
aria-hidden="true"
|
|
983
|
+
data-slot="sidebar-tree-icon"
|
|
984
|
+
className="inline-flex size-[var(--bh-space-3xl-16)] shrink-0 items-center justify-center [&_svg]:size-full"
|
|
985
|
+
>
|
|
986
|
+
{icon ?? <FolderIcon />}
|
|
987
|
+
</span>
|
|
988
|
+
<span
|
|
989
|
+
data-slot="sidebar-tree-label"
|
|
990
|
+
dir="auto"
|
|
991
|
+
className="min-w-0 flex-1 truncate"
|
|
992
|
+
>
|
|
993
|
+
{label}
|
|
994
|
+
</span>
|
|
995
|
+
</>
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
const commonProps = {
|
|
999
|
+
"aria-current": active ? ("page" as const) : undefined,
|
|
1000
|
+
"aria-expanded": showChevron ? expanded : undefined,
|
|
1001
|
+
"aria-label": resolvedLabel,
|
|
1002
|
+
"data-active": active ? "true" : "false",
|
|
1003
|
+
"data-level": String(level),
|
|
1004
|
+
"data-slot": "sidebar-menu-tree-item",
|
|
1005
|
+
"data-state": state,
|
|
1006
|
+
className: cn(
|
|
1007
|
+
"flex h-[var(--bh-sidebar-tree-item-height)] w-full min-w-0 items-center gap-[var(--bh-space-sm-6)] rounded-[var(--bh-radius-lg-8)] border-[length:var(--bh-border-width-strong)] border-transparent pe-[var(--bh-space-sm-6)] text-start text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-subtle)] outline-none transition-[background-color,border-color,color]",
|
|
1008
|
+
"hover:rounded-[var(--bh-radius-md-6)] hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:rounded-[var(--bh-radius-md-6)] focus-visible:border-[var(--bh-border-focus)]",
|
|
1009
|
+
"data-[state=hover]:rounded-[var(--bh-radius-md-6)] data-[state=hover]:bg-[var(--bh-interactive-ghost-hover)] data-[state=focus]:rounded-[var(--bh-radius-md-6)] data-[state=focus]:border-[var(--bh-border-focus)]",
|
|
1010
|
+
(active || state === "active") &&
|
|
1011
|
+
"rounded-[var(--bh-radius-md-6)] bg-[var(--bh-interactive-ghost-active)] font-[var(--bh-text-body-xs-semibold-font-weight)] text-[var(--bh-content-default)]",
|
|
1012
|
+
treeLevelClasses[level],
|
|
1013
|
+
className
|
|
1014
|
+
),
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return asChild ? (
|
|
1018
|
+
<Slot {...commonProps} ref={ref} {...props}>
|
|
1019
|
+
{cloneSlottableChild(children, content)}
|
|
1020
|
+
</Slot>
|
|
1021
|
+
) : (
|
|
1022
|
+
<button {...commonProps} ref={ref} type={type} {...props}>
|
|
1023
|
+
{content}
|
|
1024
|
+
</button>
|
|
1025
|
+
)
|
|
1026
|
+
})
|
|
1027
|
+
|
|
1028
|
+
/** Labeled tree group used for file-browser and hierarchy sidebars. */
|
|
1029
|
+
function SidebarTreeGroup({
|
|
1030
|
+
actions,
|
|
1031
|
+
children,
|
|
1032
|
+
className,
|
|
1033
|
+
expandable = false,
|
|
1034
|
+
expandIcon,
|
|
1035
|
+
label,
|
|
1036
|
+
...props
|
|
1037
|
+
}: SidebarTreeGroupProps) {
|
|
1038
|
+
const { collapsed } = useSidebarContext()
|
|
1039
|
+
if (collapsed) return null
|
|
1040
|
+
|
|
1041
|
+
return (
|
|
1042
|
+
<div
|
|
1043
|
+
data-slot="sidebar-tree-group"
|
|
1044
|
+
className={cn(
|
|
1045
|
+
"flex w-full flex-col items-start gap-[var(--bh-space-sm-6)] pb-[var(--bh-space-sm-6)] pt-[var(--bh-space-xl-12)]",
|
|
1046
|
+
className
|
|
1047
|
+
)}
|
|
1048
|
+
{...props}
|
|
1049
|
+
>
|
|
1050
|
+
{label ? (
|
|
1051
|
+
<SidebarGroupLabel
|
|
1052
|
+
actions={actions}
|
|
1053
|
+
className={cn(
|
|
1054
|
+
expandable &&
|
|
1055
|
+
"[&_[data-slot=sidebar-group-label-expand]]:opacity-[var(--bh-opacity-100)]"
|
|
1056
|
+
)}
|
|
1057
|
+
expandable={expandable}
|
|
1058
|
+
expandIcon={expandIcon ?? (expandable ? <ChevronDownIcon /> : undefined)}
|
|
1059
|
+
>
|
|
1060
|
+
{label}
|
|
1061
|
+
</SidebarGroupLabel>
|
|
1062
|
+
) : null}
|
|
1063
|
+
<div data-slot="sidebar-tree-menu" className="flex w-full flex-col">
|
|
1064
|
+
{children}
|
|
1065
|
+
</div>
|
|
1066
|
+
</div>
|
|
1067
|
+
)
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/** 28px collapse control with accessible pressed state. */
|
|
247
1071
|
const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
248
|
-
function SidebarToggle(
|
|
1072
|
+
function SidebarToggle(
|
|
1073
|
+
{ children, className, onClick, type = "button", ...props },
|
|
1074
|
+
ref
|
|
1075
|
+
) {
|
|
249
1076
|
const { collapsed, toggle } = useSidebarContext()
|
|
250
1077
|
|
|
251
1078
|
return (
|
|
@@ -256,7 +1083,8 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
|
256
1083
|
ref={ref}
|
|
257
1084
|
type={type}
|
|
258
1085
|
className={cn(
|
|
259
|
-
"inline-flex size-[var(--bh-sidebar-
|
|
1086
|
+
"inline-flex size-[var(--bh-sidebar-header-action-size)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] border border-transparent bg-[var(--bh-interactive-ghost-default)] text-[var(--bh-content-default)] outline-none transition-[background-color,border-color,box-shadow,color]",
|
|
1087
|
+
"hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:border-[var(--bh-border-focus)] focus-visible:shadow-[var(--shadow-button-focus)]",
|
|
260
1088
|
className
|
|
261
1089
|
)}
|
|
262
1090
|
onClick={(event) => {
|
|
@@ -265,13 +1093,18 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
|
265
1093
|
}}
|
|
266
1094
|
{...props}
|
|
267
1095
|
>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
1096
|
+
<span
|
|
1097
|
+
aria-hidden="true"
|
|
1098
|
+
data-slot="sidebar-toggle-icon"
|
|
1099
|
+
className="inline-flex size-[var(--bh-sidebar-icon-slot-size)] items-center justify-center [&_svg]:size-[var(--bh-sidebar-icon-size)]"
|
|
1100
|
+
>
|
|
1101
|
+
{children ?? (
|
|
1102
|
+
<PanelLeftIcon
|
|
1103
|
+
className="rtl:-scale-x-100"
|
|
1104
|
+
strokeWidth="var(--bh-icon-stroke-200)"
|
|
1105
|
+
/>
|
|
1106
|
+
)}
|
|
1107
|
+
</span>
|
|
275
1108
|
</button>
|
|
276
1109
|
)
|
|
277
1110
|
}
|
|
@@ -279,6 +1112,7 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
|
279
1112
|
|
|
280
1113
|
const SidebarTrigger = SidebarToggle
|
|
281
1114
|
|
|
1115
|
+
/** Edge-hugging pointer target for toggling the sidebar from its border. */
|
|
282
1116
|
const SidebarRail = React.forwardRef<HTMLButtonElement, SidebarRailProps>(
|
|
283
1117
|
function SidebarRail({ className, onClick, type = "button", ...props }, ref) {
|
|
284
1118
|
const { toggle } = useSidebarContext()
|
|
@@ -303,6 +1137,7 @@ const SidebarRail = React.forwardRef<HTMLButtonElement, SidebarRailProps>(
|
|
|
303
1137
|
}
|
|
304
1138
|
)
|
|
305
1139
|
|
|
1140
|
+
/** Main-content wrapper paired with a sidebar shell. */
|
|
306
1141
|
const SidebarInset = React.forwardRef<HTMLDivElement, SidebarInsetProps>(
|
|
307
1142
|
function SidebarInset({ asChild = false, className, ...props }, ref) {
|
|
308
1143
|
const Comp = asChild ? Slot : "div"
|
|
@@ -318,6 +1153,20 @@ const SidebarInset = React.forwardRef<HTMLDivElement, SidebarInsetProps>(
|
|
|
318
1153
|
}
|
|
319
1154
|
)
|
|
320
1155
|
|
|
1156
|
+
function getSlottableLabel(children: React.ReactNode, asChild: boolean) {
|
|
1157
|
+
if (!asChild || !React.isValidElement<{ children?: React.ReactNode }>(children)) {
|
|
1158
|
+
return children
|
|
1159
|
+
}
|
|
1160
|
+
return children.props.children
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
function cloneSlottableChild(children: React.ReactNode, content: React.ReactNode) {
|
|
1164
|
+
if (!React.isValidElement<{ children?: React.ReactNode }>(children)) {
|
|
1165
|
+
throw new Error("Sidebar asChild expects one valid React element")
|
|
1166
|
+
}
|
|
1167
|
+
return React.cloneElement(children, undefined, content)
|
|
1168
|
+
}
|
|
1169
|
+
|
|
321
1170
|
function useSidebarContext() {
|
|
322
1171
|
const context = React.useContext(SidebarContext)
|
|
323
1172
|
if (!context) {
|
|
@@ -331,28 +1180,51 @@ export {
|
|
|
331
1180
|
Sidebar,
|
|
332
1181
|
SidebarBody,
|
|
333
1182
|
SidebarFooter,
|
|
1183
|
+
SidebarFooterUser,
|
|
334
1184
|
SidebarGroup,
|
|
1185
|
+
SidebarGroupAction,
|
|
335
1186
|
SidebarGroupLabel,
|
|
336
1187
|
SidebarHeader,
|
|
1188
|
+
SidebarHeaderAction,
|
|
1189
|
+
SidebarHeaderBrand,
|
|
1190
|
+
SidebarHeaderWorkspaceSwitch,
|
|
337
1191
|
SidebarInset,
|
|
338
1192
|
SidebarMenu,
|
|
1193
|
+
SidebarMenuBadge,
|
|
339
1194
|
SidebarMenuButton,
|
|
340
1195
|
SidebarMenuItem,
|
|
1196
|
+
SidebarMenuSub,
|
|
1197
|
+
SidebarMenuSubButton,
|
|
1198
|
+
SidebarMenuTreeItem,
|
|
341
1199
|
SidebarProvider,
|
|
342
1200
|
SidebarRail,
|
|
343
|
-
SidebarTrigger,
|
|
344
1201
|
SidebarToggle,
|
|
1202
|
+
SidebarTreeGroup,
|
|
1203
|
+
SidebarTrigger,
|
|
345
1204
|
}
|
|
346
1205
|
export type {
|
|
1206
|
+
SidebarFooterUserProps,
|
|
1207
|
+
SidebarGroupActionProps,
|
|
347
1208
|
SidebarGroupLabelProps,
|
|
1209
|
+
SidebarHeaderActionProps,
|
|
1210
|
+
SidebarHeaderBrandProps,
|
|
1211
|
+
SidebarHeaderWorkspaceSwitchProps,
|
|
348
1212
|
SidebarInsetProps,
|
|
1213
|
+
SidebarInteractionState,
|
|
1214
|
+
SidebarMenuBadgeProps,
|
|
349
1215
|
SidebarMenuButtonProps,
|
|
1216
|
+
SidebarMenuButtonVariant,
|
|
350
1217
|
SidebarMenuItemProps,
|
|
351
1218
|
SidebarMenuProps,
|
|
1219
|
+
SidebarMenuSubButtonProps,
|
|
1220
|
+
SidebarMenuSubProps,
|
|
1221
|
+
SidebarMenuTreeItemProps,
|
|
352
1222
|
SidebarProps,
|
|
353
1223
|
SidebarProviderProps,
|
|
354
1224
|
SidebarRailProps,
|
|
355
|
-
SidebarTriggerProps,
|
|
356
1225
|
SidebarSectionProps,
|
|
357
1226
|
SidebarToggleProps,
|
|
1227
|
+
SidebarTreeGroupProps,
|
|
1228
|
+
SidebarTriggerProps,
|
|
1229
|
+
SidebarWorkspaceVariant,
|
|
358
1230
|
}
|