banhaten 0.2.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 +55 -0
- package/README.md +5 -5
- package/docs/design-system/component-implementation-ownership-audit.md +245 -0
- package/docs/design-system/component-quality-audit-2026-08-20.md +225 -0
- package/docs/design-system/component-showcase-consistency-report.md +2 -0
- package/docs/design-system/components/README.md +1 -0
- package/docs/design-system/components/activity-feed.md +2 -0
- package/docs/design-system/components/activity-heatmap.md +73 -0
- package/docs/design-system/components/alert-dialog.md +1 -1
- package/docs/design-system/components/alert.md +3 -3
- package/docs/design-system/components/attribute.md +1 -1
- package/docs/design-system/components/avatar.md +2 -2
- package/docs/design-system/components/badge.md +1 -1
- package/docs/design-system/components/banner.md +6 -2
- package/docs/design-system/components/breadcrumbs.md +5 -2
- package/docs/design-system/components/button-group.md +3 -2
- package/docs/design-system/components/button.md +2 -2
- package/docs/design-system/components/card.md +11 -3
- package/docs/design-system/components/catalog-components.md +3 -0
- package/docs/design-system/components/date-picker.md +3 -1
- package/docs/design-system/components/file-upload.md +5 -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/navigation-menu.md +1 -1
- package/docs/design-system/components/onboarding-step-list-item.md +3 -2
- package/docs/design-system/components/page-header.md +3 -1
- package/docs/design-system/components/pagination.md +3 -2
- package/docs/design-system/components/radio-card.md +1 -1
- package/docs/design-system/components/resizable.md +1 -1
- package/docs/design-system/components/scroll-area.md +7 -3
- package/docs/design-system/components/select.md +1 -1
- package/docs/design-system/components/sidebar.md +60 -18
- package/docs/design-system/components/slideout.md +4 -1
- package/docs/design-system/components/steps.md +7 -2
- package/docs/design-system/components/table.md +25 -7
- package/docs/design-system/components/tag.md +3 -3
- package/docs/design-system/components/timeline.md +4 -1
- package/docs/design-system/components/toggle.md +1 -1
- package/docs/design-system/components/toolbar.md +1 -1
- package/docs/design-system/components/tooltip.md +1 -1
- package/docs/design-system/density-and-elevation.md +23 -2
- package/docs/design-system/gap-promotion-report.md +1 -67
- package/docs/design-system/icons.md +6 -1
- package/docs/design-system/installation.md +1 -1
- package/docs/design-system/launch-comparison-shadcn.md +10 -10
- package/docs/design-system/token-governance.md +7 -8
- package/package.json +3 -2
- package/registry/components/alert-dialog.tsx +1 -1
- package/registry/components/alert.tsx +17 -13
- package/registry/components/attribute.tsx +8 -2
- package/registry/components/avatar.tsx +28 -20
- package/registry/components/badge.tsx +17 -17
- package/registry/components/button-group.tsx +4 -2
- package/registry/components/button.tsx +3 -3
- package/registry/components/card.tsx +19 -3
- package/registry/components/carousel.tsx +14 -0
- package/registry/components/command.tsx +37 -3
- package/registry/components/date-picker.tsx +83 -35
- package/registry/components/expanded/ActivityFeed.tsx +3 -7
- package/registry/components/expanded/ActivityHeatmap.tsx +64 -0
- package/registry/components/expanded/Banner.tsx +0 -1
- package/registry/components/expanded/Breadcrumbs.tsx +3 -0
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +3 -4
- package/registry/components/expanded/CatalogDivider.tsx +3 -2
- package/registry/components/expanded/FileUpload.tsx +7 -5
- package/registry/components/expanded/OnboardingStepListItem.tsx +6 -7
- package/registry/components/expanded/PageHeader.tsx +35 -44
- package/registry/components/expanded/Slideout.tsx +18 -13
- package/registry/components/expanded/Steps.tsx +147 -9
- package/registry/components/expanded/Table.tsx +50 -18
- package/registry/components/expanded/Timeline.tsx +11 -24
- package/registry/components/expanded/activityFeed.css +8 -8
- package/registry/components/expanded/activityHeatmap.css +66 -0
- package/registry/components/expanded/banner.css +30 -15
- package/registry/components/expanded/breadcrumbs.css +5 -5
- package/registry/components/expanded/fileUpload.css +4 -4
- package/registry/components/expanded/onboardingStepListItem.css +16 -7
- package/registry/components/expanded/pageHeader.css +14 -11
- package/registry/components/expanded/slideout.css +3 -7
- package/registry/components/expanded/steps.css +75 -12
- package/registry/components/expanded/table.css +63 -69
- package/registry/components/expanded/timeline.css +17 -16
- package/registry/components/field.tsx +61 -8
- 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 +57 -7
- package/registry/components/modal.tsx +1 -1
- package/registry/components/navigation-menu.tsx +1 -1
- package/registry/components/pagination.css +36 -0
- package/registry/components/pagination.tsx +20 -13
- package/registry/components/progress-slider.tsx +1 -1
- package/registry/components/radio-card.tsx +1 -2
- package/registry/components/resizable.tsx +36 -2
- package/registry/components/scroll-area.tsx +30 -6
- package/registry/components/select.tsx +35 -12
- package/registry/components/sidebar.tsx +940 -62
- package/registry/components/social-button.tsx +12 -5
- package/registry/components/table-elements.tsx +128 -0
- package/registry/components/tabs.tsx +5 -10
- package/registry/components/tag.tsx +2 -2
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +6 -6
- package/registry/components/tooltip.tsx +1 -1
- package/registry/examples/attribute-demo.tsx +1 -1
- package/registry/examples/avatar-demo.tsx +7 -4
- package/registry/examples/command-palette-demo.tsx +0 -1
- package/registry/examples/expanded/activity-heatmap-demo.tsx +29 -0
- package/registry/examples/expanded/breadcrumbs-demo.tsx +3 -3
- package/registry/examples/kbd-demo.tsx +2 -1
- package/registry/examples/pagination-demo.tsx +5 -5
- package/registry/examples/sidebar-demo.tsx +59 -13
- package/registry/guidance/USAGE.md +1 -1
- package/registry/index.json +621 -60
- package/registry/styles/globals.css +211 -151
- package/registry.json +44 -6
- package/tokens/banhaten.tokens.json +325 -95
|
@@ -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,21 +39,94 @@ 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
|
|
32
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
|
|
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
|
|
36
122
|
type SidebarRailProps = React.ComponentProps<"button">
|
|
37
|
-
type SidebarInsetProps = React.ComponentProps<"
|
|
123
|
+
type SidebarInsetProps = React.ComponentProps<"div"> & {
|
|
124
|
+
asChild?: boolean
|
|
125
|
+
}
|
|
38
126
|
|
|
39
127
|
const SidebarContext = React.createContext<SidebarContextValue | null>(null)
|
|
40
128
|
|
|
129
|
+
/** Owns the controlled or uncontrolled collapsed state for a sidebar composition. */
|
|
41
130
|
function SidebarProvider({
|
|
42
131
|
children,
|
|
43
132
|
className,
|
|
@@ -74,36 +163,42 @@ function SidebarProvider({
|
|
|
74
163
|
|
|
75
164
|
return (
|
|
76
165
|
<SidebarContext.Provider value={context}>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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>
|
|
86
177
|
</SidebarContext.Provider>
|
|
87
178
|
)
|
|
88
179
|
}
|
|
89
180
|
|
|
181
|
+
/** The 280px navigation surface that contracts to the Figma 48px icon rail. */
|
|
90
182
|
const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
91
183
|
{ className, side = "start", ...props },
|
|
92
184
|
ref
|
|
93
185
|
) {
|
|
94
|
-
const
|
|
186
|
+
const context = React.useContext(SidebarContext)
|
|
187
|
+
const collapsed = context?.collapsed ?? false
|
|
188
|
+
const dir = context?.dir
|
|
95
189
|
|
|
96
190
|
return (
|
|
97
191
|
<aside
|
|
98
|
-
data-collapsed={collapsed ? "true" :
|
|
192
|
+
data-collapsed={collapsed ? "true" : "false"}
|
|
99
193
|
data-side={side}
|
|
100
194
|
data-slot="sidebar"
|
|
101
195
|
dir={dir}
|
|
102
196
|
ref={ref}
|
|
103
197
|
className={cn(
|
|
104
|
-
"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)]",
|
|
105
200
|
"data-[collapsed=true]:w-[var(--bh-sidebar-width-collapsed)] data-[collapsed=true]:bg-[var(--bh-sidebar-bg-collapsed)]",
|
|
106
|
-
side === "end" && "ms-auto",
|
|
201
|
+
side === "end" && "ms-auto border-e-0 border-s",
|
|
107
202
|
className
|
|
108
203
|
)}
|
|
109
204
|
{...props}
|
|
@@ -111,12 +206,14 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
|
|
|
111
206
|
)
|
|
112
207
|
})
|
|
113
208
|
|
|
209
|
+
/** Header slot with the Figma 10px expanded and 4px/8px collapsed insets. */
|
|
114
210
|
function SidebarHeader({ className, ...props }: SidebarSectionProps) {
|
|
115
211
|
return (
|
|
116
212
|
<div
|
|
117
213
|
data-slot="sidebar-header"
|
|
118
214
|
className={cn(
|
|
119
|
-
"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)]",
|
|
120
217
|
className
|
|
121
218
|
)}
|
|
122
219
|
{...props}
|
|
@@ -124,12 +221,262 @@ function SidebarHeader({ className, ...props }: SidebarSectionProps) {
|
|
|
124
221
|
)
|
|
125
222
|
}
|
|
126
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. */
|
|
127
473
|
function SidebarBody({ className, ...props }: SidebarSectionProps) {
|
|
128
474
|
return (
|
|
129
475
|
<div
|
|
130
476
|
data-slot="sidebar-body"
|
|
131
477
|
className={cn(
|
|
132
|
-
"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",
|
|
133
480
|
className
|
|
134
481
|
)}
|
|
135
482
|
{...props}
|
|
@@ -137,12 +484,14 @@ function SidebarBody({ className, ...props }: SidebarSectionProps) {
|
|
|
137
484
|
)
|
|
138
485
|
}
|
|
139
486
|
|
|
487
|
+
/** Bottom-pinned footer with the matching expanded and collapsed padding. */
|
|
140
488
|
function SidebarFooter({ className, ...props }: SidebarSectionProps) {
|
|
141
489
|
return (
|
|
142
490
|
<div
|
|
143
491
|
data-slot="sidebar-footer"
|
|
144
492
|
className={cn(
|
|
145
|
-
"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)]",
|
|
146
495
|
className
|
|
147
496
|
)}
|
|
148
497
|
{...props}
|
|
@@ -150,17 +499,132 @@ function SidebarFooter({ className, ...props }: SidebarSectionProps) {
|
|
|
150
499
|
)
|
|
151
500
|
}
|
|
152
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. */
|
|
153
606
|
function SidebarGroup({ className, ...props }: SidebarSectionProps) {
|
|
154
607
|
return (
|
|
155
608
|
<div
|
|
156
609
|
data-slot="sidebar-group"
|
|
157
|
-
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
|
+
)}
|
|
158
614
|
{...props}
|
|
159
615
|
/>
|
|
160
616
|
)
|
|
161
617
|
}
|
|
162
618
|
|
|
163
|
-
|
|
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) {
|
|
164
628
|
const { collapsed } = useSidebarContext()
|
|
165
629
|
|
|
166
630
|
if (collapsed) return null
|
|
@@ -168,26 +632,84 @@ function SidebarGroupLabel({ className, ...props }: SidebarGroupLabelProps) {
|
|
|
168
632
|
return (
|
|
169
633
|
<div
|
|
170
634
|
data-slot="sidebar-group-label"
|
|
171
|
-
dir="auto"
|
|
172
635
|
className={cn(
|
|
173
|
-
"
|
|
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)]",
|
|
174
637
|
className
|
|
175
638
|
)}
|
|
176
639
|
{...props}
|
|
177
|
-
|
|
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>
|
|
178
671
|
)
|
|
179
672
|
}
|
|
180
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. */
|
|
181
702
|
function SidebarMenu({ className, ...props }: SidebarMenuProps) {
|
|
182
703
|
return (
|
|
183
704
|
<ul
|
|
184
705
|
data-slot="sidebar-menu"
|
|
185
|
-
className={cn("m-0
|
|
706
|
+
className={cn("m-0 flex w-full list-none flex-col p-0", className)}
|
|
186
707
|
{...props}
|
|
187
708
|
/>
|
|
188
709
|
)
|
|
189
710
|
}
|
|
190
711
|
|
|
712
|
+
/** Semantic list item wrapper. */
|
|
191
713
|
function SidebarMenuItem({ className, ...props }: SidebarMenuItemProps) {
|
|
192
714
|
return (
|
|
193
715
|
<li
|
|
@@ -198,50 +720,359 @@ function SidebarMenuItem({ className, ...props }: SidebarMenuItemProps) {
|
|
|
198
720
|
)
|
|
199
721
|
}
|
|
200
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. */
|
|
201
740
|
const SidebarMenuButton = React.forwardRef<
|
|
202
741
|
HTMLButtonElement,
|
|
203
742
|
SidebarMenuButtonProps
|
|
204
743
|
>(function SidebarMenuButton(
|
|
205
|
-
{
|
|
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
|
+
},
|
|
206
760
|
ref
|
|
207
761
|
) {
|
|
208
|
-
const { collapsed } = useSidebarContext()
|
|
209
|
-
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}
|
|
210
791
|
|
|
211
|
-
return (
|
|
212
|
-
<Comp
|
|
213
|
-
aria-current={active ? "page" : undefined}
|
|
214
|
-
data-active={active ? "true" : undefined}
|
|
215
|
-
data-slot="sidebar-menu-button"
|
|
216
|
-
ref={ref}
|
|
217
|
-
className={cn(
|
|
218
|
-
"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]",
|
|
219
|
-
"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)]",
|
|
220
|
-
collapsed && "justify-center px-[var(--bh-space-md-8)]",
|
|
221
|
-
className
|
|
222
|
-
)}
|
|
223
|
-
{...props}
|
|
224
|
-
>
|
|
225
792
|
{icon ? (
|
|
226
793
|
<span
|
|
227
794
|
aria-hidden="true"
|
|
228
795
|
data-slot="sidebar-menu-icon"
|
|
229
|
-
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
|
+
)}
|
|
230
806
|
>
|
|
231
807
|
{icon}
|
|
232
808
|
</span>
|
|
233
809
|
) : null}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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}
|
|
237
818
|
</span>
|
|
238
|
-
)}
|
|
239
|
-
|
|
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>
|
|
901
|
+
)
|
|
902
|
+
})
|
|
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>
|
|
240
1025
|
)
|
|
241
1026
|
})
|
|
242
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. */
|
|
243
1071
|
const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
244
|
-
function SidebarToggle(
|
|
1072
|
+
function SidebarToggle(
|
|
1073
|
+
{ children, className, onClick, type = "button", ...props },
|
|
1074
|
+
ref
|
|
1075
|
+
) {
|
|
245
1076
|
const { collapsed, toggle } = useSidebarContext()
|
|
246
1077
|
|
|
247
1078
|
return (
|
|
@@ -252,7 +1083,8 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
|
252
1083
|
ref={ref}
|
|
253
1084
|
type={type}
|
|
254
1085
|
className={cn(
|
|
255
|
-
"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)]",
|
|
256
1088
|
className
|
|
257
1089
|
)}
|
|
258
1090
|
onClick={(event) => {
|
|
@@ -261,13 +1093,18 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
|
261
1093
|
}}
|
|
262
1094
|
{...props}
|
|
263
1095
|
>
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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>
|
|
271
1108
|
</button>
|
|
272
1109
|
)
|
|
273
1110
|
}
|
|
@@ -275,6 +1112,7 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
|
275
1112
|
|
|
276
1113
|
const SidebarTrigger = SidebarToggle
|
|
277
1114
|
|
|
1115
|
+
/** Edge-hugging pointer target for toggling the sidebar from its border. */
|
|
278
1116
|
const SidebarRail = React.forwardRef<HTMLButtonElement, SidebarRailProps>(
|
|
279
1117
|
function SidebarRail({ className, onClick, type = "button", ...props }, ref) {
|
|
280
1118
|
const { toggle } = useSidebarContext()
|
|
@@ -299,10 +1137,13 @@ const SidebarRail = React.forwardRef<HTMLButtonElement, SidebarRailProps>(
|
|
|
299
1137
|
}
|
|
300
1138
|
)
|
|
301
1139
|
|
|
302
|
-
|
|
303
|
-
|
|
1140
|
+
/** Main-content wrapper paired with a sidebar shell. */
|
|
1141
|
+
const SidebarInset = React.forwardRef<HTMLDivElement, SidebarInsetProps>(
|
|
1142
|
+
function SidebarInset({ asChild = false, className, ...props }, ref) {
|
|
1143
|
+
const Comp = asChild ? Slot : "div"
|
|
1144
|
+
|
|
304
1145
|
return (
|
|
305
|
-
<
|
|
1146
|
+
<Comp
|
|
306
1147
|
data-slot="sidebar-inset"
|
|
307
1148
|
ref={ref}
|
|
308
1149
|
className={cn("min-w-0 flex-1", className)}
|
|
@@ -312,6 +1153,20 @@ const SidebarInset = React.forwardRef<HTMLElement, SidebarInsetProps>(
|
|
|
312
1153
|
}
|
|
313
1154
|
)
|
|
314
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
|
+
|
|
315
1170
|
function useSidebarContext() {
|
|
316
1171
|
const context = React.useContext(SidebarContext)
|
|
317
1172
|
if (!context) {
|
|
@@ -325,28 +1180,51 @@ export {
|
|
|
325
1180
|
Sidebar,
|
|
326
1181
|
SidebarBody,
|
|
327
1182
|
SidebarFooter,
|
|
1183
|
+
SidebarFooterUser,
|
|
328
1184
|
SidebarGroup,
|
|
1185
|
+
SidebarGroupAction,
|
|
329
1186
|
SidebarGroupLabel,
|
|
330
1187
|
SidebarHeader,
|
|
1188
|
+
SidebarHeaderAction,
|
|
1189
|
+
SidebarHeaderBrand,
|
|
1190
|
+
SidebarHeaderWorkspaceSwitch,
|
|
331
1191
|
SidebarInset,
|
|
332
1192
|
SidebarMenu,
|
|
1193
|
+
SidebarMenuBadge,
|
|
333
1194
|
SidebarMenuButton,
|
|
334
1195
|
SidebarMenuItem,
|
|
1196
|
+
SidebarMenuSub,
|
|
1197
|
+
SidebarMenuSubButton,
|
|
1198
|
+
SidebarMenuTreeItem,
|
|
335
1199
|
SidebarProvider,
|
|
336
1200
|
SidebarRail,
|
|
337
|
-
SidebarTrigger,
|
|
338
1201
|
SidebarToggle,
|
|
1202
|
+
SidebarTreeGroup,
|
|
1203
|
+
SidebarTrigger,
|
|
339
1204
|
}
|
|
340
1205
|
export type {
|
|
1206
|
+
SidebarFooterUserProps,
|
|
1207
|
+
SidebarGroupActionProps,
|
|
341
1208
|
SidebarGroupLabelProps,
|
|
1209
|
+
SidebarHeaderActionProps,
|
|
1210
|
+
SidebarHeaderBrandProps,
|
|
1211
|
+
SidebarHeaderWorkspaceSwitchProps,
|
|
342
1212
|
SidebarInsetProps,
|
|
1213
|
+
SidebarInteractionState,
|
|
1214
|
+
SidebarMenuBadgeProps,
|
|
343
1215
|
SidebarMenuButtonProps,
|
|
1216
|
+
SidebarMenuButtonVariant,
|
|
344
1217
|
SidebarMenuItemProps,
|
|
345
1218
|
SidebarMenuProps,
|
|
1219
|
+
SidebarMenuSubButtonProps,
|
|
1220
|
+
SidebarMenuSubProps,
|
|
1221
|
+
SidebarMenuTreeItemProps,
|
|
346
1222
|
SidebarProps,
|
|
347
1223
|
SidebarProviderProps,
|
|
348
1224
|
SidebarRailProps,
|
|
349
|
-
SidebarTriggerProps,
|
|
350
1225
|
SidebarSectionProps,
|
|
351
1226
|
SidebarToggleProps,
|
|
1227
|
+
SidebarTreeGroupProps,
|
|
1228
|
+
SidebarTriggerProps,
|
|
1229
|
+
SidebarWorkspaceVariant,
|
|
352
1230
|
}
|