banhaten 0.4.0 → 0.6.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.
Files changed (214) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/CONTRIBUTING.md +6 -4
  3. package/README.md +4 -2
  4. package/docs/design-spacing.md +13 -0
  5. package/docs/design-system/README.md +2 -0
  6. package/docs/design-system/appearances/default.md +1 -1
  7. package/docs/design-system/appearances/rounded.md +1 -1
  8. package/docs/design-system/appearances/sharp.md +1 -1
  9. package/docs/design-system/component-showcase-consistency-report.md +5 -5
  10. package/docs/design-system/components/README.md +15 -0
  11. package/docs/design-system/components/accordion.md +1 -1
  12. package/docs/design-system/components/activity-feed.md +1 -1
  13. package/docs/design-system/components/activity-heatmap.md +1 -1
  14. package/docs/design-system/components/agent-input.md +85 -0
  15. package/docs/design-system/components/alert-dialog.md +1 -1
  16. package/docs/design-system/components/alert.md +10 -4
  17. package/docs/design-system/components/app-header.md +97 -0
  18. package/docs/design-system/components/approval-card.md +134 -0
  19. package/docs/design-system/components/attribute.md +14 -2
  20. package/docs/design-system/components/avatar.md +1 -1
  21. package/docs/design-system/components/badge.md +1 -1
  22. package/docs/design-system/components/banner.md +1 -1
  23. package/docs/design-system/components/breadcrumbs.md +8 -3
  24. package/docs/design-system/components/button-group.md +1 -1
  25. package/docs/design-system/components/button.md +8 -5
  26. package/docs/design-system/components/card.md +15 -1
  27. package/docs/design-system/components/catalog-components.md +1 -1
  28. package/docs/design-system/components/chart.md +95 -24
  29. package/docs/design-system/components/checkbox.md +1 -1
  30. package/docs/design-system/components/collapsible.md +20 -6
  31. package/docs/design-system/components/command.md +1 -1
  32. package/docs/design-system/components/context-menu.md +1 -1
  33. package/docs/design-system/components/date-picker.md +1 -1
  34. package/docs/design-system/components/file-upload.md +1 -1
  35. package/docs/design-system/components/hover-card.md +1 -1
  36. package/docs/design-system/components/inline-citations.md +93 -0
  37. package/docs/design-system/components/input-group.md +1 -1
  38. package/docs/design-system/components/input.md +59 -3
  39. package/docs/design-system/components/item.md +1 -1
  40. package/docs/design-system/components/kbd.md +6 -4
  41. package/docs/design-system/components/menu.md +35 -4
  42. package/docs/design-system/components/menubar.md +1 -1
  43. package/docs/design-system/components/modal.md +2 -2
  44. package/docs/design-system/components/native-select.md +1 -1
  45. package/docs/design-system/components/navigation-menu.md +1 -1
  46. package/docs/design-system/components/onboarding-step-list-item.md +1 -1
  47. package/docs/design-system/components/page-header.md +58 -17
  48. package/docs/design-system/components/pagination.md +2 -2
  49. package/docs/design-system/components/popover.md +1 -1
  50. package/docs/design-system/components/progress-slider.md +1 -1
  51. package/docs/design-system/components/radio-card.md +1 -1
  52. package/docs/design-system/components/radio-group.md +1 -1
  53. package/docs/design-system/components/resizable.md +1 -1
  54. package/docs/design-system/components/rolling-count.md +61 -0
  55. package/docs/design-system/components/scroll-area.md +1 -1
  56. package/docs/design-system/components/select.md +12 -2
  57. package/docs/design-system/components/sheet.md +1 -1
  58. package/docs/design-system/components/shimmer-text.md +67 -0
  59. package/docs/design-system/components/sidebar.md +75 -8
  60. package/docs/design-system/components/skeleton.md +2 -2
  61. package/docs/design-system/components/slideout.md +1 -1
  62. package/docs/design-system/components/slider.md +2 -2
  63. package/docs/design-system/components/social-button.md +2 -2
  64. package/docs/design-system/components/streaming-text.md +82 -0
  65. package/docs/design-system/components/table.md +103 -21
  66. package/docs/design-system/components/tabs.md +25 -6
  67. package/docs/design-system/components/tag.md +1 -1
  68. package/docs/design-system/components/task-list.md +95 -0
  69. package/docs/design-system/components/textarea.md +1 -1
  70. package/docs/design-system/components/thinking-reasoning.md +72 -0
  71. package/docs/design-system/components/thinking-state.md +67 -0
  72. package/docs/design-system/components/timeline.md +1 -1
  73. package/docs/design-system/components/toast.md +1 -1
  74. package/docs/design-system/components/toggle.md +2 -2
  75. package/docs/design-system/components/toolbar.md +11 -6
  76. package/docs/design-system/components/tooltip.md +6 -4
  77. package/docs/design-system/density-and-elevation.md +16 -16
  78. package/docs/design-system/design-system-test-missing-items.md +8 -8
  79. package/docs/design-system/installation.md +2 -0
  80. package/docs/design-system/tabs.md +2 -1
  81. package/docs/design-system/template-gap-audit.md +32 -0
  82. package/docs/design-system/templates.md +59 -0
  83. package/docs/design-system/token-governance.md +15 -2
  84. package/package.json +2 -2
  85. package/registry/components/accordion.tsx +2 -2
  86. package/registry/components/agent-input-parts.tsx +552 -0
  87. package/registry/components/agent-input-state.ts +156 -0
  88. package/registry/components/agent-input.css +307 -0
  89. package/registry/components/agent-input.tsx +382 -0
  90. package/registry/components/alert-dialog.tsx +2 -2
  91. package/registry/components/alert.tsx +23 -7
  92. package/registry/components/app-header.tsx +151 -0
  93. package/registry/components/approval-card.css +409 -0
  94. package/registry/components/approval-card.tsx +401 -0
  95. package/registry/components/approval-frame.tsx +108 -0
  96. package/registry/components/approval-questions.tsx +559 -0
  97. package/registry/components/attribute.tsx +48 -13
  98. package/registry/components/autocomplete.tsx +15 -68
  99. package/registry/components/avatar.tsx +2 -2
  100. package/registry/components/badge.tsx +8 -7
  101. package/registry/components/button-group.tsx +2 -2
  102. package/registry/components/button.tsx +19 -14
  103. package/registry/components/card.tsx +18 -5
  104. package/registry/components/chart-block-data.ts +177 -0
  105. package/registry/components/chart-block.css +171 -0
  106. package/registry/components/chart-block.tsx +260 -0
  107. package/registry/components/chart-interactions.ts +123 -0
  108. package/registry/components/chart-options.cartesian.ts +162 -0
  109. package/registry/components/chart-options.paint.ts +99 -0
  110. package/registry/components/chart-options.parts.ts +72 -0
  111. package/registry/components/chart-options.radial.ts +109 -0
  112. package/registry/components/chart-options.ts +44 -0
  113. package/registry/components/chart-options.types.ts +67 -0
  114. package/registry/components/chart.tsx +1 -1
  115. package/registry/components/checkbox.tsx +6 -6
  116. package/registry/components/collapsible.tsx +43 -3
  117. package/registry/components/command.tsx +1 -1
  118. package/registry/components/context-menu.tsx +1 -1
  119. package/registry/components/data-chart.tsx +40 -0
  120. package/registry/components/date-picker.tsx +9 -9
  121. package/registry/components/echart-theme.ts +246 -0
  122. package/registry/components/echart.css +49 -0
  123. package/registry/components/echart.tsx +178 -0
  124. package/registry/components/expanded/Breadcrumbs.tsx +5 -1
  125. package/registry/components/expanded/PageHeader.tsx +67 -24
  126. package/registry/components/expanded/Table.tsx +60 -22
  127. package/registry/components/expanded/activityFeed.css +2 -2
  128. package/registry/components/expanded/activityHeatmap.css +1 -1
  129. package/registry/components/expanded/banner.css +2 -2
  130. package/registry/components/expanded/breadcrumbs.css +19 -2
  131. package/registry/components/expanded/catalogComponentsShowcase.css +1 -1
  132. package/registry/components/expanded/fileUpload.css +7 -7
  133. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  134. package/registry/components/expanded/pageHeader.css +38 -44
  135. package/registry/components/expanded/slideout.css +4 -4
  136. package/registry/components/expanded/table.css +58 -9
  137. package/registry/components/expanded/timeline.css +1 -1
  138. package/registry/components/hover-card.tsx +1 -1
  139. package/registry/components/inline-citations.css +126 -0
  140. package/registry/components/inline-citations.tsx +292 -0
  141. package/registry/components/input-group.tsx +4 -4
  142. package/registry/components/input.tsx +150 -137
  143. package/registry/components/item.tsx +1 -1
  144. package/registry/components/kbd.tsx +3 -2
  145. package/registry/components/menu.tsx +117 -12
  146. package/registry/components/menubar.tsx +2 -2
  147. package/registry/components/modal.tsx +3 -3
  148. package/registry/components/native-select.tsx +4 -4
  149. package/registry/components/navigation-menu.tsx +2 -2
  150. package/registry/components/pagination.tsx +4 -4
  151. package/registry/components/popover.tsx +3 -3
  152. package/registry/components/progress-slider.tsx +1 -1
  153. package/registry/components/radio-card.tsx +7 -7
  154. package/registry/components/radio-group.tsx +2 -2
  155. package/registry/components/resizable.tsx +2 -2
  156. package/registry/components/rolling-count.css +37 -0
  157. package/registry/components/rolling-count.tsx +110 -0
  158. package/registry/components/scroll-area.tsx +1 -1
  159. package/registry/components/select.tsx +15 -10
  160. package/registry/components/sheet.tsx +2 -2
  161. package/registry/components/shimmer-text.css +69 -0
  162. package/registry/components/shimmer-text.tsx +43 -0
  163. package/registry/components/sidebar.tsx +528 -223
  164. package/registry/components/skeleton.tsx +1 -1
  165. package/registry/components/slider.tsx +2 -2
  166. package/registry/components/social-button.tsx +1 -1
  167. package/registry/components/streaming-text.css +45 -0
  168. package/registry/components/streaming-text.tsx +165 -0
  169. package/registry/components/table-elements.tsx +164 -5
  170. package/registry/components/tabs.css +32 -4
  171. package/registry/components/tabs.tsx +5 -1
  172. package/registry/components/tag.tsx +1 -1
  173. package/registry/components/task-list.css +328 -0
  174. package/registry/components/task-list.tsx +429 -0
  175. package/registry/components/textarea.tsx +4 -4
  176. package/registry/components/thinking-reasoning.css +178 -0
  177. package/registry/components/thinking-reasoning.tsx +253 -0
  178. package/registry/components/thinking-state.css +8 -0
  179. package/registry/components/thinking-state.tsx +46 -0
  180. package/registry/components/toast.tsx +1 -1
  181. package/registry/components/toggle.tsx +2 -2
  182. package/registry/components/toolbar.tsx +6 -6
  183. package/registry/components/tooltip.tsx +6 -4
  184. package/registry/examples/agent-input-demo.tsx +90 -0
  185. package/registry/examples/app-header-demo.tsx +40 -0
  186. package/registry/examples/approval-card-demo.tsx +83 -0
  187. package/registry/examples/button-demo.tsx +3 -0
  188. package/registry/examples/chart-block-demo.tsx +5 -0
  189. package/registry/examples/chart-demo.tsx +6 -21
  190. package/registry/examples/collection-filter-bar-demo.tsx +1 -1
  191. package/registry/examples/control-density-contract-demo.tsx +5 -1
  192. package/registry/examples/inline-citations-demo.tsx +31 -0
  193. package/registry/examples/input-actions-demo.tsx +36 -0
  194. package/registry/examples/menu-demo.tsx +1 -1
  195. package/registry/examples/popover-demo.tsx +1 -0
  196. package/registry/examples/rolling-count-demo.tsx +39 -0
  197. package/registry/examples/shimmer-text-demo.tsx +20 -0
  198. package/registry/examples/streaming-text-demo.tsx +28 -0
  199. package/registry/examples/task-list-demo.tsx +74 -0
  200. package/registry/examples/thinking-reasoning-demo.tsx +74 -0
  201. package/registry/examples/thinking-state-demo.tsx +13 -0
  202. package/registry/guidance/charts.LICENSE.txt +24 -0
  203. package/registry/hooks/use-controllable-state.ts +33 -0
  204. package/registry/hooks/use-listbox-navigation.ts +73 -0
  205. package/registry/hooks/use-motion-end.ts +55 -0
  206. package/registry/hooks/use-reduced-motion.ts +28 -0
  207. package/registry/index.json +1808 -190
  208. package/registry/styles/globals.css +992 -1088
  209. package/registry.json +385 -10
  210. package/schema/registry.schema.json +5 -1
  211. package/schema/tokens.schema.json +8 -4
  212. package/src/cli/file-transaction.js +15 -8
  213. package/src/cli/index.js +22 -29
  214. package/tokens/banhaten.tokens.json +490 -192
@@ -8,6 +8,7 @@ import {
8
8
  ChevronRightIcon,
9
9
  ChevronsUpDownIcon,
10
10
  FolderIcon,
11
+ MoreHorizontalIcon,
11
12
  PanelLeftIcon,
12
13
  SearchIcon,
13
14
  SparklesIcon,
@@ -22,10 +23,13 @@ import {
22
23
  } from "@/components/ui/tooltip"
23
24
  import { cn } from "@/lib/utils"
24
25
 
26
+ type SidebarSize = "default" | "comfortable"
27
+
25
28
  type SidebarContextValue = {
26
29
  collapsed: boolean
27
30
  dir?: "ltr" | "rtl"
28
31
  setCollapsed: (collapsed: boolean) => void
32
+ size: SidebarSize
29
33
  toggle: () => void
30
34
  }
31
35
 
@@ -34,6 +38,7 @@ type SidebarProviderProps = React.ComponentProps<"div"> & {
34
38
  defaultCollapsed?: boolean
35
39
  dir?: "ltr" | "rtl"
36
40
  onCollapsedChange?: (collapsed: boolean) => void
41
+ size?: SidebarSize
37
42
  }
38
43
  type SidebarProps = React.ComponentProps<"aside"> & {
39
44
  side?: "start" | "end"
@@ -45,10 +50,11 @@ type SidebarHeaderActionProps = React.ComponentProps<"button"> & {
45
50
  type SidebarHeaderBrandProps = React.ComponentProps<"div"> & {
46
51
  brand?: React.ReactNode
47
52
  collapseAction?: React.ReactNode | false
48
- logo?: React.ReactNode
53
+ logo?: React.ReactNode | false
49
54
  onSearchClick?: React.MouseEventHandler<HTMLButtonElement>
50
55
  searchAction?: React.ReactNode | false
51
56
  searchLabel?: string
57
+ subtitle?: React.ReactNode
52
58
  }
53
59
  type SidebarWorkspaceVariant = "actions" | "card"
54
60
  type SidebarHeaderWorkspaceSwitchProps = React.ComponentProps<"div"> & {
@@ -63,12 +69,16 @@ type SidebarHeaderWorkspaceSwitchProps = React.ComponentProps<"div"> & {
63
69
  variant?: SidebarWorkspaceVariant
64
70
  workspaceLabel?: string
65
71
  }
72
+ type SidebarFooterProps = SidebarSectionProps & {
73
+ divider?: boolean
74
+ }
66
75
  type SidebarFooterUserProps = React.ComponentProps<"button"> & {
67
- avatar?: React.ReactNode
76
+ avatar?: React.ReactNode | false
68
77
  name?: React.ReactNode
78
+ showIdentityPreview?: boolean
69
79
  supportText?: React.ReactNode
70
- trailingIcon?: React.ReactNode
71
- variant?: "default" | "supporting-text"
80
+ trailingIcon?: React.ReactNode | false
81
+ variant?: "default" | "supporting-text" | "card"
72
82
  }
73
83
  type SidebarGroupLabelProps = React.ComponentProps<"div"> & {
74
84
  actions?: React.ReactNode
@@ -134,6 +144,7 @@ function SidebarProvider({
134
144
  defaultCollapsed = false,
135
145
  dir,
136
146
  onCollapsedChange,
147
+ size = "default",
137
148
  ...props
138
149
  }: SidebarProviderProps) {
139
150
  const [uncontrolledCollapsed, setUncontrolledCollapsed] =
@@ -156,9 +167,10 @@ function SidebarProvider({
156
167
  collapsed: resolvedCollapsed,
157
168
  dir,
158
169
  setCollapsed,
170
+ size,
159
171
  toggle: () => setCollapsed(!resolvedCollapsed),
160
172
  }),
161
- [dir, resolvedCollapsed, setCollapsed]
173
+ [dir, resolvedCollapsed, setCollapsed, size]
162
174
  )
163
175
 
164
176
  return (
@@ -166,6 +178,7 @@ function SidebarProvider({
166
178
  <TooltipProvider delayDuration={0}>
167
179
  <div
168
180
  data-collapsed={resolvedCollapsed ? "true" : "false"}
181
+ data-size={size}
169
182
  data-slot="sidebar-provider"
170
183
  dir={dir}
171
184
  className={cn("relative min-w-0", className)}
@@ -178,29 +191,66 @@ function SidebarProvider({
178
191
  )
179
192
  }
180
193
 
181
- /** The 280px navigation surface that contracts to the Figma 48px icon rail. */
194
+ /** The 280px navigation surface with a 900px minimum height that contracts to the Figma 48px icon rail. */
182
195
  const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(function Sidebar(
183
- { className, side = "start", ...props },
196
+ { className, onTransitionEnd, side = "start", ...props },
184
197
  ref
185
198
  ) {
186
199
  const context = React.useContext(SidebarContext)
187
200
  const collapsed = context?.collapsed ?? false
188
201
  const dir = context?.dir
202
+ const size = context?.size ?? "default"
203
+ const [overflowVisible, setOverflowVisible] = React.useState(collapsed)
204
+ const previousCollapsed = React.useRef(collapsed)
205
+
206
+ React.useEffect(() => {
207
+ if (previousCollapsed.current === collapsed) return
208
+ previousCollapsed.current = collapsed
209
+
210
+ if (!collapsed) {
211
+ setOverflowVisible(false)
212
+ return
213
+ }
214
+
215
+ const prefersReducedMotion =
216
+ typeof window !== "undefined" &&
217
+ typeof window.matchMedia === "function" &&
218
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches
219
+
220
+ setOverflowVisible(prefersReducedMotion)
221
+ }, [collapsed])
189
222
 
190
223
  return (
191
224
  <aside
192
225
  data-collapsed={collapsed ? "true" : "false"}
226
+ data-overflow-visible={overflowVisible ? "true" : "false"}
193
227
  data-side={side}
228
+ data-size={size}
194
229
  data-slot="sidebar"
195
230
  dir={dir}
196
231
  ref={ref}
197
232
  className={cn(
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]",
233
+ "group/sidebar flex h-full min-h-[var(--bh-sidebar-min-height)] 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] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] will-change-[width] motion-reduce:transition-none",
199
234
  "border-e border-[var(--bh-sidebar-border)]",
200
235
  "data-[collapsed=true]:w-[var(--bh-sidebar-width-collapsed)] data-[collapsed=true]:bg-[var(--bh-sidebar-bg-collapsed)]",
236
+ collapsed && overflowVisible && "overflow-visible",
201
237
  side === "end" && "ms-auto border-e-0 border-s",
238
+ // Comfortable, expanded: the ATS360 shell rail's 1px inside stroke on all four sides, an outline inset by its own
239
+ // width so it takes no width and draws above the header and footer fills.
240
+ size === "comfortable" &&
241
+ "data-[collapsed=false]:border-0 data-[collapsed=false]:[outline:var(--bh-border-width-default)_solid_var(--bh-sidebar-border)] data-[collapsed=false]:[outline-offset:calc(var(--bh-border-width-default)*-1)]",
202
242
  className
203
243
  )}
244
+ onTransitionEnd={(event) => {
245
+ onTransitionEnd?.(event)
246
+ if (
247
+ !event.defaultPrevented &&
248
+ event.currentTarget === event.target &&
249
+ event.propertyName === "width"
250
+ ) {
251
+ setOverflowVisible(collapsed)
252
+ }
253
+ }}
204
254
  {...props}
205
255
  />
206
256
  )
@@ -212,8 +262,9 @@ function SidebarHeader({ className, ...props }: SidebarSectionProps) {
212
262
  <div
213
263
  data-slot="sidebar-header"
214
264
  className={cn(
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)]",
265
+ "flex shrink-0 flex-col items-center gap-[var(--bh-space-lg-10)] overflow-hidden bg-[var(--bh-sidebar-bg)] p-[var(--bh-sidebar-padding)] transition-[padding] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
266
+ "group-data-[collapsed=true]/sidebar:px-[var(--bh-space-xs-4)] group-data-[collapsed=true]/sidebar:py-[var(--bh-space-md-8)]",
267
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:px-[var(--bh-sidebar-comfortable-padding)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:pb-[var(--bh-space-none)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:pt-[var(--bh-sidebar-comfortable-padding)]",
217
268
  className
218
269
  )}
219
270
  {...props}
@@ -237,7 +288,7 @@ const SidebarHeaderAction = React.forwardRef<
237
288
  type={type}
238
289
  className={cn(
239
290
  "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)]",
291
+ "hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:border-[var(--bh-border-focus)] focus-visible:shadow-[var(--elevation-button-focus)]",
241
292
  className
242
293
  )}
243
294
  {...props}
@@ -253,7 +304,10 @@ const SidebarHeaderAction = React.forwardRef<
253
304
  )
254
305
  })
255
306
 
256
- /** Brand header with optional search and collapse actions; actions disappear on the icon rail. */
307
+ /**
308
+ * Brand header with optional search and collapse actions; actions disappear on the icon rail.
309
+ * Pass logo={false} for a text-only brand and subtitle for a stacked product or module line.
310
+ */
257
311
  function SidebarHeaderBrand({
258
312
  brand = "Banhaten",
259
313
  className,
@@ -262,63 +316,123 @@ function SidebarHeaderBrand({
262
316
  onSearchClick,
263
317
  searchAction,
264
318
  searchLabel = "Search",
319
+ subtitle,
265
320
  ...props
266
321
  }: SidebarHeaderBrandProps) {
267
322
  const { collapsed } = useSidebarContext()
323
+ const hasCollapseAction = collapseAction !== false
324
+ const showLogo = logo !== false
325
+ const stacked = subtitle !== undefined && subtitle !== null && subtitle !== false
326
+
327
+ const name = (
328
+ <span
329
+ aria-hidden={collapsed || undefined}
330
+ data-slot="sidebar-brand-name"
331
+ dir="auto"
332
+ className={cn(
333
+ "max-w-[calc(var(--bh-sidebar-width)-var(--bh-space-10xl-80))] truncate opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
334
+ "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)]",
335
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:text-[length:var(--bh-text-heading-md-semibold-font-size)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:font-[var(--bh-text-heading-md-semibold-font-weight)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:leading-[var(--bh-text-heading-md-semibold-line-height)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:tracking-[var(--bh-text-heading-md-semibold-letter-spacing)]",
336
+ collapsed &&
337
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
338
+ )}
339
+ >
340
+ {brand}
341
+ </span>
342
+ )
268
343
 
269
344
  return (
270
345
  <div
271
346
  data-collapsed={collapsed ? "true" : "false"}
272
347
  data-slot="sidebar-header-brand"
348
+ data-stacked={stacked ? "true" : "false"}
273
349
  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",
350
+ "relative h-[var(--bh-sidebar-brand-height)] w-full transition-[width,height] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
351
+ stacked && !collapsed && "h-auto",
352
+ collapsed && hasCollapseAction &&
353
+ "h-[calc(var(--bh-sidebar-header-action-size)+var(--bh-space-sm-6)+var(--bh-sidebar-header-action-size))] w-[var(--bh-sidebar-compact-control-size)]",
354
+ collapsed && !hasCollapseAction &&
355
+ "h-[var(--bh-sidebar-header-action-size)] w-[var(--bh-sidebar-compact-control-size)]",
277
356
  className
278
357
  )}
279
358
  {...props}
280
359
  >
281
360
  <span
282
361
  data-slot="sidebar-brand-content"
283
- className="flex min-w-0 items-center gap-[var(--bh-space-md-8)]"
362
+ className={cn(
363
+ "absolute start-[var(--bh-space-sm-6)] top-[var(--bh-space-xs-4)] flex min-w-0 items-center gap-[var(--bh-space-md-8)] transition-[inset-inline-start,top,gap] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
364
+ stacked && !collapsed &&
365
+ "static items-start px-[var(--bh-space-sm-6)] py-[var(--bh-space-xs-4)] group-data-[size=comfortable]/sidebar:p-[var(--bh-sidebar-comfortable-brand-padding)]",
366
+ collapsed && "start-[var(--bh-space-xs-4)] top-0 gap-0"
367
+ )}
284
368
  >
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 ? (
369
+ {showLogo ? (
299
370
  <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)]"
371
+ data-slot="sidebar-brand-logo"
372
+ 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"
303
373
  >
304
- {brand}
374
+ {logo ?? (
375
+ <span className="inline-flex size-full items-center justify-center bg-[var(--bh-bg-brand-default)] text-[var(--bh-content-on-brand)]">
376
+ <SparklesIcon
377
+ aria-hidden="true"
378
+ className="size-[var(--bh-sidebar-icon-size)]"
379
+ />
380
+ </span>
381
+ )}
305
382
  </span>
306
383
  ) : null}
384
+ {stacked ? (
385
+ <span
386
+ aria-hidden={collapsed || undefined}
387
+ data-slot="sidebar-brand-text"
388
+ className={cn(
389
+ "flex min-w-0 flex-col gap-[var(--bh-space-xs-4)] opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
390
+ collapsed &&
391
+ "pointer-events-none max-w-0 overflow-hidden opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
392
+ )}
393
+ >
394
+ {name}
395
+ <span
396
+ data-slot="sidebar-brand-subtitle"
397
+ dir="auto"
398
+ className="max-w-[calc(var(--bh-sidebar-width)-var(--bh-space-10xl-80))] truncate 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-content-subtle)]"
399
+ >
400
+ {subtitle}
401
+ </span>
402
+ </span>
403
+ ) : (
404
+ name
405
+ )}
307
406
  </span>
308
407
 
309
- {!collapsed ? (
408
+ {searchAction !== false || hasCollapseAction ? (
310
409
  <span
311
410
  data-slot="sidebar-brand-actions"
312
- className="flex shrink-0 items-center gap-[var(--bh-space-sm-6)]"
411
+ className={cn(
412
+ "absolute end-[var(--bh-space-xs-4)] top-[calc((var(--bh-sidebar-brand-height)-var(--bh-sidebar-header-action-size))/2)] flex shrink-0 items-center gap-[var(--bh-space-sm-6)] transition-[top,inset-inline-end,gap] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
413
+ collapsed && hasCollapseAction &&
414
+ "end-[var(--bh-space-xs-4)] top-[calc(var(--bh-sidebar-header-action-size)+var(--bh-space-sm-6))] gap-0"
415
+ )}
313
416
  >
314
- {searchAction === false
315
- ? null
316
- : searchAction ?? (
417
+ {searchAction === false ? null : (
418
+ <span
419
+ aria-hidden={collapsed || undefined}
420
+ data-slot="sidebar-brand-search-action"
421
+ inert={collapsed ? true : undefined}
422
+ className={cn(
423
+ "inline-flex max-w-[var(--bh-sidebar-header-action-size)] overflow-hidden opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] duration-[var(--bh-sidebar-motion-duration-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
424
+ collapsed &&
425
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)]"
426
+ )}
427
+ >
428
+ {searchAction ?? (
317
429
  <SidebarHeaderAction label={searchLabel} onClick={onSearchClick}>
318
430
  <SearchIcon aria-hidden="true" />
319
431
  </SidebarHeaderAction>
320
432
  )}
321
- {collapseAction === false ? null : collapseAction ?? <SidebarTrigger />}
433
+ </span>
434
+ )}
435
+ {hasCollapseAction ? collapseAction ?? <SidebarTrigger /> : null}
322
436
  </span>
323
437
  ) : null}
324
438
  </div>
@@ -342,6 +456,7 @@ function SidebarHeaderWorkspaceSwitch({
342
456
  }: SidebarHeaderWorkspaceSwitchProps) {
343
457
  const { collapsed } = useSidebarContext()
344
458
  const card = variant === "card"
459
+ const hasCollapseAction = collapseAction !== false
345
460
 
346
461
  const workspaceButton = (
347
462
  <button
@@ -350,20 +465,20 @@ function SidebarHeaderWorkspaceSwitch({
350
465
  onClick={onWorkspaceClick}
351
466
  type="button"
352
467
  className={cn(
353
- "group/workspace relative flex min-w-0 items-center text-start outline-none transition-[background-color,border-color,box-shadow]",
468
+ "group/workspace absolute start-0 top-0 flex min-w-0 items-center overflow-hidden text-start outline-none transition-[width,height,gap,padding,border-radius,background-color,border-color,box-shadow] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
354
469
  card
355
470
  ? "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
471
  : "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
472
  collapsed && card &&
358
- "size-[var(--bh-sidebar-compact-control-size)] justify-center gap-0 p-0",
473
+ "size-[var(--bh-sidebar-compact-control-size)] gap-0 px-[var(--bh-space-xxs-2)] py-0",
359
474
  collapsed && !card &&
360
- "size-[var(--bh-sidebar-item-height-collapsed)] max-w-[var(--bh-sidebar-item-height-collapsed)] justify-center gap-0 p-0"
475
+ "size-[var(--bh-sidebar-item-height-collapsed)] max-w-[var(--bh-sidebar-item-height-collapsed)] gap-0 px-[var(--bh-space-sm-6)] py-0"
361
476
  )}
362
477
  >
363
478
  <span
364
479
  data-slot="sidebar-workspace-image"
365
480
  className={cn(
366
- "inline-flex shrink-0 items-center justify-center overflow-hidden [&>*]:size-full",
481
+ "inline-flex shrink-0 items-center justify-center overflow-hidden transition-[width,height,border-radius] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none [&>*]:size-full",
367
482
  card
368
483
  ? "size-[var(--bh-space-6xl-32)] rounded-[var(--bh-radius-md-6)]"
369
484
  : "size-[var(--bh-space-5xl-24)] rounded-[var(--bh-radius-lg-8)]",
@@ -379,54 +494,75 @@ function SidebarHeaderWorkspaceSwitch({
379
494
  )}
380
495
  </span>
381
496
 
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>
497
+ {card ? (
498
+ <span
499
+ aria-hidden={collapsed || undefined}
500
+ className={cn(
501
+ "flex min-w-0 max-w-[var(--bh-sidebar-width)] flex-1 flex-col items-start justify-center overflow-hidden opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
502
+ collapsed &&
503
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
504
+ )}
505
+ >
506
+ <span
507
+ data-slot="sidebar-workspace-label"
508
+ dir="auto"
509
+ 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)]"
510
+ >
511
+ {label}
399
512
  </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
- />
513
+ <span
514
+ data-slot="sidebar-workspace-support"
515
+ dir="auto"
516
+ 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)]"
517
+ >
518
+ {supportText}
414
519
  </span>
415
- )
416
- ) : !card ? (
520
+ </span>
521
+ ) : (
522
+ <span
523
+ aria-hidden={collapsed || undefined}
524
+ className={cn(
525
+ "flex min-w-0 max-w-[var(--bh-sidebar-width)] items-center gap-[var(--bh-space-xxs-2)] overflow-hidden opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
526
+ collapsed &&
527
+ "pointer-events-none max-w-0 gap-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
528
+ )}
529
+ >
530
+ <span
531
+ data-slot="sidebar-workspace-label"
532
+ dir="auto"
533
+ 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)]"
534
+ >
535
+ {label}
536
+ </span>
537
+ <ChevronDownIcon
538
+ aria-hidden="true"
539
+ className="size-[var(--bh-space-3xl-16)] shrink-0"
540
+ data-rtl-flip="true"
541
+ />
542
+ </span>
543
+ )}
544
+
545
+ {!card ? (
417
546
  <span
418
547
  aria-hidden="true"
419
548
  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)]"
549
+ className={cn(
550
+ "pointer-events-none 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)] opacity-[var(--bh-opacity-0)] transition-opacity duration-[var(--bh-sidebar-motion-duration-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
551
+ collapsed &&
552
+ "opacity-[var(--bh-opacity-100)] delay-[var(--bh-sidebar-motion-delay-content)]"
553
+ )}
421
554
  >
422
555
  <ChevronDownIcon className="size-[var(--bh-space-xl-12)]" />
423
556
  </span>
424
557
  ) : null}
425
558
 
426
- {card && !collapsed ? (
559
+ {card ? (
427
560
  <ChevronsUpDownIcon
428
561
  aria-hidden="true"
429
- className="size-[var(--bh-space-3xl-16)] shrink-0 text-[var(--bh-content-muted)]"
562
+ className={cn(
563
+ "size-[var(--bh-space-3xl-16)] shrink-0 text-[var(--bh-content-muted)] opacity-[var(--bh-opacity-100)] transition-opacity duration-[var(--bh-sidebar-motion-duration-content)] delay-[var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
564
+ collapsed && "opacity-[var(--bh-opacity-0)] delay-0"
565
+ )}
430
566
  />
431
567
  ) : null}
432
568
  </button>
@@ -438,31 +574,58 @@ function SidebarHeaderWorkspaceSwitch({
438
574
  data-slot="sidebar-header-workspace-switch"
439
575
  data-variant={variant}
440
576
  className={cn(
441
- "flex w-full items-center",
577
+ "relative w-full transition-[width,height] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
442
578
  card
443
579
  ? "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",
580
+ : "h-[var(--bh-sidebar-compact-control-size)]",
581
+ collapsed && card && hasCollapseAction &&
582
+ "h-[calc(var(--bh-sidebar-compact-control-size)+var(--bh-space-sm-6)+var(--bh-sidebar-header-action-size))] w-[var(--bh-sidebar-compact-control-size)]",
583
+ collapsed && card && !hasCollapseAction &&
584
+ "h-[var(--bh-sidebar-compact-control-size)] w-[var(--bh-sidebar-compact-control-size)]",
585
+ collapsed && !card && hasCollapseAction &&
586
+ "h-[calc(var(--bh-sidebar-item-height-collapsed)+var(--bh-space-sm-6)+var(--bh-sidebar-header-action-size))] w-[var(--bh-sidebar-item-height-collapsed)]",
587
+ collapsed && !card && !hasCollapseAction &&
588
+ "h-[var(--bh-sidebar-item-height-collapsed)] w-[var(--bh-sidebar-item-height-collapsed)]",
448
589
  className
449
590
  )}
450
591
  {...props}
451
592
  >
452
593
  {workspaceButton}
453
- {!card && !collapsed ? (
594
+ {(!card && searchAction !== false) || hasCollapseAction ? (
454
595
  <span
596
+ aria-hidden={(card && !collapsed) || undefined}
455
597
  data-slot="sidebar-workspace-actions"
456
- className="flex shrink-0 items-center gap-[var(--bh-space-sm-6)]"
598
+ inert={card && !collapsed ? true : undefined}
599
+ className={cn(
600
+ "absolute flex shrink-0 items-center gap-[var(--bh-space-sm-6)] transition-[top,inset-inline-end,gap,opacity] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
601
+ card
602
+ ? "pointer-events-none end-[var(--bh-space-xs-4)] top-[calc((var(--bh-sidebar-workspace-card-height)-var(--bh-sidebar-header-action-size))/2)] opacity-[var(--bh-opacity-0)]"
603
+ : "end-[var(--bh-space-md-8)] top-[calc((var(--bh-sidebar-compact-control-size)-var(--bh-sidebar-header-action-size))/2)] opacity-[var(--bh-opacity-100)]",
604
+ collapsed && card &&
605
+ "pointer-events-auto end-[var(--bh-space-xs-4)] top-[calc(var(--bh-sidebar-compact-control-size)+var(--bh-space-sm-6))] gap-0 opacity-[var(--bh-opacity-100)]",
606
+ collapsed && !card &&
607
+ "end-[var(--bh-space-sm-6)] top-[calc(var(--bh-sidebar-item-height-collapsed)+var(--bh-space-sm-6))] gap-0"
608
+ )}
457
609
  >
458
- {searchAction === false
459
- ? null
460
- : searchAction ?? (
610
+ {!card && searchAction !== false ? (
611
+ <span
612
+ aria-hidden={collapsed || undefined}
613
+ data-slot="sidebar-workspace-search-action"
614
+ inert={collapsed ? true : undefined}
615
+ className={cn(
616
+ "inline-flex max-w-[var(--bh-sidebar-header-action-size)] overflow-hidden opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] duration-[var(--bh-sidebar-motion-duration-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
617
+ collapsed &&
618
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)]"
619
+ )}
620
+ >
621
+ {searchAction ?? (
461
622
  <SidebarHeaderAction label={searchLabel} onClick={onSearchClick}>
462
623
  <SearchIcon aria-hidden="true" />
463
624
  </SidebarHeaderAction>
464
625
  )}
465
- {collapseAction === false ? null : collapseAction ?? <SidebarTrigger />}
626
+ </span>
627
+ ) : null}
628
+ {hasCollapseAction ? collapseAction ?? <SidebarTrigger /> : null}
466
629
  </span>
467
630
  ) : null}
468
631
  </div>
@@ -475,8 +638,9 @@ function SidebarBody({ className, ...props }: SidebarSectionProps) {
475
638
  <div
476
639
  data-slot="sidebar-body"
477
640
  className={cn(
478
- "min-h-0 flex-1 overflow-auto px-[var(--bh-space-xl-12)] py-0",
641
+ "min-h-0 flex-1 overflow-auto px-[var(--bh-space-xl-12)] py-0 transition-[padding] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
479
642
  "group-data-[collapsed=true]/sidebar:px-0",
643
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:flex group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:flex-col group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:gap-[var(--bh-sidebar-comfortable-section-gap)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:px-[var(--bh-sidebar-comfortable-padding)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:pt-[var(--bh-sidebar-comfortable-section-gap)]",
480
644
  className
481
645
  )}
482
646
  {...props}
@@ -484,14 +648,17 @@ function SidebarBody({ className, ...props }: SidebarSectionProps) {
484
648
  )
485
649
  }
486
650
 
487
- /** Bottom-pinned footer with the matching expanded and collapsed padding. */
488
- function SidebarFooter({ className, ...props }: SidebarSectionProps) {
651
+ /** Bottom-pinned footer with the matching expanded and collapsed padding; divider={false} drops the top rule. */
652
+ function SidebarFooter({ className, divider = true, ...props }: SidebarFooterProps) {
489
653
  return (
490
654
  <div
655
+ data-divider={divider ? "true" : "false"}
491
656
  data-slot="sidebar-footer"
492
657
  className={cn(
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)]",
658
+ "mt-auto shrink-0 bg-[var(--bh-sidebar-bg)] px-[var(--bh-sidebar-padding)] py-[var(--bh-space-md-8)] transition-[padding] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
659
+ divider && "border-t border-[var(--bh-sidebar-border)]",
494
660
  "group-data-[collapsed=true]/sidebar:p-[var(--bh-space-xs-4)]",
661
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:p-[var(--bh-sidebar-comfortable-padding)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:pt-[var(--bh-sidebar-comfortable-section-gap)]",
495
662
  className
496
663
  )}
497
664
  {...props}
@@ -509,6 +676,7 @@ const SidebarFooterUser = React.forwardRef<
509
676
  avatar,
510
677
  className,
511
678
  name = "Username",
679
+ showIdentityPreview = true,
512
680
  supportText = "user@example.com",
513
681
  trailingIcon,
514
682
  type = "button",
@@ -518,7 +686,16 @@ const SidebarFooterUser = React.forwardRef<
518
686
  ref
519
687
  ) {
520
688
  const { collapsed } = useSidebarContext()
689
+ const card = variant === "card"
521
690
  const supporting = variant === "supporting-text"
691
+ const showSupport = supporting || card
692
+ // The card treatment is text-only unless an avatar or trailing icon is passed;
693
+ // the collapsed rail always keeps a glyph so the control stays visible.
694
+ const showAvatar =
695
+ collapsed || (avatar !== false && (!card || (avatar !== undefined && avatar !== null)))
696
+ const showTrailing =
697
+ trailingIcon !== false &&
698
+ (!card || (trailingIcon !== undefined && trailingIcon !== null))
522
699
  const resolvedLabel =
523
700
  ariaLabel ?? (typeof name === "string" ? name : "Open user menu")
524
701
 
@@ -531,13 +708,15 @@ const SidebarFooterUser = React.forwardRef<
531
708
  ref={ref}
532
709
  type={type}
533
710
  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)]",
711
+ "group/footer-user relative flex w-full items-center rounded-[var(--bh-radius-lg-8)] text-start outline-none transition-[width,height,gap,padding,border-radius,background-color,border-color,box-shadow] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
712
+ card
713
+ ? "gap-[var(--bh-space-xs-4)] bg-[var(--bh-sidebar-user-card-bg)] p-[var(--bh-space-md-8)] hover:bg-[var(--bh-sidebar-user-card-bg-hover)]"
714
+ : supporting
715
+ ? "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)]"
716
+ : "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)]",
717
+ "focus-visible:border-[length:var(--bh-border-width-strong)] focus-visible:border-[var(--bh-border-focus)] focus-visible:shadow-[var(--elevation-button-focus)]",
539
718
  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)]",
719
+ "mx-auto size-[var(--bh-sidebar-compact-control-size)] justify-center gap-0 rounded-[var(--bh-radius-full)] border-0 bg-[var(--bh-interactive-ghost-default)] p-0 hover:bg-[var(--bh-interactive-ghost-hover)]",
541
720
  className
542
721
  )}
543
722
  {...props}
@@ -545,70 +724,125 @@ const SidebarFooterUser = React.forwardRef<
545
724
  <span
546
725
  data-slot="sidebar-footer-user-content"
547
726
  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"
727
+ "flex min-w-0 flex-1 items-center gap-[var(--bh-space-md-8)] overflow-hidden px-[var(--bh-space-xxs-2)] transition-[width,height,gap,padding] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
728
+ card && "px-[var(--bh-space-none)]",
729
+ collapsed && "gap-0 p-0"
551
730
  )}
552
731
  >
732
+ {showAvatar ? (
733
+ <span
734
+ data-slot="sidebar-footer-user-avatar"
735
+ className={cn(
736
+ "inline-flex shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] transition-[width,height,border-radius] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none [&>*]:size-full",
737
+ supporting || card ? "size-[var(--bh-space-6xl-32)]" : "size-[var(--bh-space-5xl-24)]",
738
+ collapsed && "size-[var(--bh-sidebar-compact-control-size)]"
739
+ )}
740
+ >
741
+ {avatar === false || avatar === undefined || avatar === null ? (
742
+ <UserIcon
743
+ aria-hidden="true"
744
+ className="size-[var(--bh-sidebar-icon-size)]"
745
+ />
746
+ ) : (
747
+ avatar
748
+ )}
749
+ </span>
750
+ ) : null}
553
751
  <span
554
- data-slot="sidebar-footer-user-avatar"
752
+ aria-hidden={collapsed || undefined}
753
+ data-slot="sidebar-footer-user-text"
555
754
  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)]"
755
+ "flex min-w-0 max-w-[var(--bh-sidebar-width)] flex-1 flex-col items-start justify-center overflow-hidden opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none rtl:items-end rtl:text-right",
756
+ card && "gap-[var(--bh-space-xs-4)]",
757
+ collapsed &&
758
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
559
759
  )}
560
760
  >
561
- {avatar ?? (
562
- <UserIcon
563
- aria-hidden="true"
564
- className="size-[var(--bh-sidebar-icon-size)]"
565
- />
566
- )}
567
- </span>
568
- {!collapsed ? (
569
761
  <span
570
- data-slot="sidebar-footer-user-text"
571
- className="flex min-w-0 flex-1 flex-col items-start justify-center"
762
+ data-slot="sidebar-footer-user-name"
763
+ dir="auto"
764
+ className={cn(
765
+ "w-full truncate",
766
+ card
767
+ ? "text-[length:var(--bh-text-body-md-medium-font-size)] font-[var(--bh-text-body-md-medium-font-weight)] leading-[var(--bh-text-body-md-medium-line-height)] tracking-[var(--bh-text-body-md-medium-letter-spacing)] text-[var(--bh-content-default)]"
768
+ : "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)]"
769
+ )}
572
770
  >
771
+ {name}
772
+ </span>
773
+ {showSupport ? (
573
774
  <span
574
- data-slot="sidebar-footer-user-name"
775
+ data-slot="sidebar-footer-user-support"
575
776
  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)]"
777
+ className={cn(
778
+ "w-full truncate text-[var(--bh-content-subtle)]",
779
+ card
780
+ ? "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)]"
781
+ : "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)]"
782
+ )}
577
783
  >
578
- {name}
784
+ {supportText}
579
785
  </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}
786
+ ) : null}
787
+ </span>
591
788
  </span>
592
- {!collapsed ? (
789
+ {showTrailing ? (
593
790
  <span
594
791
  aria-hidden="true"
595
792
  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)]"
793
+ className={cn(
794
+ "inline-flex size-[var(--bh-space-4xl-20)] max-w-[var(--bh-space-4xl-20)] shrink-0 items-center justify-center overflow-hidden text-[var(--bh-content-muted)] opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none [&_svg]:size-[var(--bh-space-3xl-16)]",
795
+ collapsed &&
796
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
797
+ )}
597
798
  >
598
799
  {trailingIcon ?? <ChevronsUpDownIcon />}
599
800
  </span>
600
801
  ) : null}
802
+ {collapsed && showIdentityPreview ? (
803
+ <span
804
+ aria-hidden="true"
805
+ data-slot="sidebar-footer-user-preview"
806
+ className={cn(
807
+ "pointer-events-none invisible absolute bottom-0 start-[calc(100%+var(--bh-sidebar-user-preview-gap))] z-[var(--bh-z-overlay)] flex h-[var(--bh-sidebar-user-preview-height)] w-[var(--bh-sidebar-user-preview-width)] items-center gap-[var(--bh-space-md-8)] rounded-[var(--bh-radius-xl-10)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] px-[var(--bh-space-xl-12)] py-[var(--bh-space-md-8)] opacity-[var(--bh-opacity-0)] shadow-[var(--elevation-component-default)] transition-[opacity,visibility]",
808
+ "group-hover/footer-user:visible group-hover/footer-user:opacity-[var(--bh-opacity-100)] group-focus-visible/footer-user:visible group-focus-visible/footer-user:opacity-[var(--bh-opacity-100)] group-data-[state=open]/footer-user:hidden",
809
+ "group-data-[side=end]/sidebar:start-auto group-data-[side=end]/sidebar:end-[calc(100%+var(--bh-sidebar-user-preview-gap))]"
810
+ )}
811
+ >
812
+ <span className="flex min-w-0 flex-1 flex-col items-start justify-center">
813
+ <span
814
+ dir="auto"
815
+ 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)] text-[var(--bh-content-default)]"
816
+ >
817
+ {name}
818
+ </span>
819
+ <span
820
+ dir="auto"
821
+ className="w-full truncate 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)]"
822
+ >
823
+ {supportText}
824
+ </span>
825
+ </span>
826
+ <span 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)]">
827
+ {trailingIcon ?? <MoreHorizontalIcon />}
828
+ </span>
829
+ </span>
830
+ ) : null}
601
831
  </button>
602
832
  )
603
833
  })
604
834
 
605
- /** A Figma sidebar group: 12px top padding, 6px bottom padding, and no menu gap. */
835
+ /**
836
+ * A Figma sidebar group: 12px top padding, 6px bottom padding, and no menu gap.
837
+ * The comfortable size drops the padding (the body's section gap separates groups) and spaces label and rows by 8px.
838
+ */
606
839
  function SidebarGroup({ className, ...props }: SidebarSectionProps) {
607
840
  return (
608
841
  <div
609
842
  data-slot="sidebar-group"
610
843
  className={cn(
611
844
  "flex w-full flex-col items-start pb-[var(--bh-space-sm-6)] pt-[var(--bh-space-xl-12)]",
845
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:gap-[var(--bh-sidebar-comfortable-item-gap)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:py-[var(--bh-space-none)]",
612
846
  className
613
847
  )}
614
848
  {...props}
@@ -627,46 +861,56 @@ function SidebarGroupLabel({
627
861
  }: SidebarGroupLabelProps) {
628
862
  const { collapsed } = useSidebarContext()
629
863
 
630
- if (collapsed) return null
631
-
632
864
  return (
633
865
  <div
634
- data-slot="sidebar-group-label"
866
+ aria-hidden={collapsed || undefined}
867
+ data-slot="sidebar-group-label-motion"
868
+ inert={collapsed ? true : undefined}
635
869
  className={cn(
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)]",
637
- className
870
+ "grid w-full grid-rows-[1fr] opacity-[var(--bh-opacity-100)] transition-[grid-template-rows,opacity] duration-[var(--bh-sidebar-motion-duration-content)] delay-[var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
871
+ collapsed &&
872
+ "pointer-events-none grid-rows-[0fr] opacity-[var(--bh-opacity-0)] delay-0"
638
873
  )}
639
- {...props}
640
874
  >
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)]"
875
+ <div
876
+ data-slot="sidebar-group-label"
877
+ className={cn(
878
+ "group/label flex min-h-0 h-[var(--bh-space-5xl-24)] w-full items-center gap-[var(--bh-space-xs-4)] overflow-hidden rounded-[var(--bh-radius-lg-8)] px-[var(--bh-space-sm-6)] pb-[var(--bh-space-xs-4)]",
879
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:h-[var(--bh-sidebar-comfortable-group-label-height)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:px-[var(--bh-space-none)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:pb-[var(--bh-space-none)]",
880
+ className
881
+ )}
882
+ {...props}
644
883
  >
645
884
  <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)]"
885
+ data-slot="sidebar-group-label-content"
886
+ className="flex min-w-0 flex-1 items-center gap-[var(--bh-space-xxxs-1)]"
649
887
  >
650
- {children}
888
+ <span
889
+ data-slot="sidebar-group-label-text"
890
+ dir="auto"
891
+ 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-sidebar-group-label-content)]"
892
+ >
893
+ {children}
894
+ </span>
895
+ {expandable ? (
896
+ <span
897
+ aria-hidden="true"
898
+ data-slot="sidebar-group-label-expand"
899
+ 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"
900
+ >
901
+ {expandIcon ?? <ChevronRightIcon data-rtl-flip="true" />}
902
+ </span>
903
+ ) : null}
651
904
  </span>
652
- {expandable ? (
905
+ {actions ? (
653
906
  <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"
907
+ data-slot="sidebar-group-label-actions"
908
+ 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)]"
657
909
  >
658
- {expandIcon ?? <ChevronRightIcon data-rtl-flip="true" />}
910
+ {actions}
659
911
  </span>
660
912
  ) : 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}
913
+ </div>
670
914
  </div>
671
915
  )
672
916
  }
@@ -698,12 +942,16 @@ const SidebarGroupAction = React.forwardRef<
698
942
  )
699
943
  })
700
944
 
701
- /** List wrapper for contiguous 32px sidebar menu rows. */
945
+ /** List wrapper for contiguous 32px sidebar menu rows, or 40px rows 8px apart at the comfortable size. */
702
946
  function SidebarMenu({ className, ...props }: SidebarMenuProps) {
703
947
  return (
704
948
  <ul
705
949
  data-slot="sidebar-menu"
706
- className={cn("m-0 flex w-full list-none flex-col p-0", className)}
950
+ className={cn(
951
+ "m-0 flex w-full list-none flex-col p-0",
952
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:gap-[var(--bh-sidebar-comfortable-item-gap)]",
953
+ className
954
+ )}
707
955
  {...props}
708
956
  />
709
957
  )
@@ -765,8 +1013,6 @@ const SidebarMenuButton = React.forwardRef<
765
1013
  const resolvedLabel =
766
1014
  ariaLabel ?? (typeof label === "string" ? label : undefined)
767
1015
 
768
- if (collapsed && isLevel2) return null
769
-
770
1016
  const content = (
771
1017
  <>
772
1018
  {isLevel2 ? (
@@ -808,37 +1054,58 @@ const SidebarMenuButton = React.forwardRef<
808
1054
  </span>
809
1055
  ) : null}
810
1056
 
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}
818
- </span>
819
- ) : null}
1057
+ <span
1058
+ aria-hidden={collapsed || undefined}
1059
+ data-slot="sidebar-menu-label"
1060
+ dir="auto"
1061
+ className={cn(
1062
+ "min-w-0 max-w-[var(--bh-sidebar-width)] flex-1 truncate ps-[var(--bh-space-xs-4)] opacity-[var(--bh-opacity-100)] transition-[max-width,opacity,padding] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content),0ms] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content),0ms] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
1063
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:ps-[var(--bh-sidebar-comfortable-label-padding)]",
1064
+ collapsed &&
1065
+ "pointer-events-none max-w-0 p-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms,var(--bh-sidebar-motion-duration-content)]"
1066
+ )}
1067
+ >
1068
+ {label}
1069
+ </span>
820
1070
 
821
- {!collapsed && supportText ? (
1071
+ {supportText ? (
822
1072
  <span
1073
+ aria-hidden={collapsed || undefined}
823
1074
  data-slot="sidebar-menu-support"
824
1075
  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)]"
1076
+ className={cn(
1077
+ "max-w-[var(--bh-sidebar-width)] shrink-0 overflow-hidden 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)] opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
1078
+ collapsed &&
1079
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
1080
+ )}
826
1081
  >
827
1082
  {supportText}
828
1083
  </span>
829
1084
  ) : null}
830
1085
 
831
- {!collapsed && badge ? (
832
- <span data-slot="sidebar-menu-badge-slot" className="inline-flex shrink-0">
1086
+ {badge ? (
1087
+ <span
1088
+ aria-hidden={collapsed || undefined}
1089
+ data-slot="sidebar-menu-badge-slot"
1090
+ className={cn(
1091
+ "inline-flex max-w-[var(--bh-sidebar-width)] shrink-0 overflow-hidden opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
1092
+ collapsed &&
1093
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
1094
+ )}
1095
+ >
833
1096
  {badge}
834
1097
  </span>
835
1098
  ) : null}
836
1099
 
837
- {!collapsed && trailingIcon ? (
1100
+ {trailingIcon ? (
838
1101
  <span
839
1102
  aria-hidden="true"
840
1103
  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"
1104
+ className={cn(
1105
+ "inline-flex size-[var(--bh-sidebar-icon-slot-size)] max-w-[var(--bh-sidebar-icon-slot-size)] shrink-0 items-center justify-center overflow-hidden text-[var(--bh-content-muted)] opacity-[var(--bh-opacity-100)] transition-[max-width,opacity] [transition-duration:0ms,var(--bh-sidebar-motion-duration-content)] [transition-delay:0ms,var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none [&_svg]:size-[var(--bh-sidebar-icon-size)] [&_svg]:shrink-0",
1106
+ collapsed &&
1107
+ "pointer-events-none max-w-0 opacity-[var(--bh-opacity-0)] [transition-delay:var(--bh-sidebar-motion-duration-content),0ms]"
1108
+ )}
842
1109
  >
843
1110
  {trailingIcon}
844
1111
  </span>
@@ -847,7 +1114,7 @@ const SidebarMenuButton = React.forwardRef<
847
1114
  )
848
1115
 
849
1116
  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]",
1117
+ "group/menu-button relative flex w-full min-w-0 items-center gap-[var(--bh-space-sm-6)] overflow-hidden 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)] opacity-[var(--bh-opacity-100)] outline-none transition-[width,height,gap,padding,margin,opacity,background-color,border-color,color] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
851
1118
  "hover:rounded-[var(--bh-radius-md-6)] hover:bg-[var(--bh-sidebar-item-bg-hover)]",
852
1119
  "focus-visible:rounded-[var(--bh-radius-md-6)] focus-visible:border-[var(--bh-border-focus)]",
853
1120
  "data-[state=hover]:rounded-[var(--bh-radius-md-6)] data-[state=hover]:bg-[var(--bh-sidebar-item-bg-hover)]",
@@ -855,22 +1122,29 @@ const SidebarMenuButton = React.forwardRef<
855
1122
  "disabled:pointer-events-none disabled:text-[var(--bh-content-disabled)]",
856
1123
  variant === "avatar"
857
1124
  ? "h-[var(--bh-sidebar-compact-control-size)]"
858
- : "h-[var(--bh-sidebar-item-height)]",
1125
+ : "h-[var(--bh-sidebar-item-height)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:h-[var(--bh-sidebar-comfortable-item-height)]",
1126
+ "group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:text-[length:var(--bh-text-body-md-regular-font-size)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:leading-[var(--bh-text-body-md-regular-line-height)] group-data-[size=comfortable]/sidebar:group-data-[collapsed=false]/sidebar:tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
859
1127
  (active || state === "active") &&
860
1128
  "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",
1129
+ collapsed && !isLevel2 &&
1130
+ "ms-[var(--bh-space-xs-4)] size-[var(--bh-sidebar-item-height-collapsed)] gap-0 rounded-[var(--bh-radius-lg-8)] px-[var(--bh-space-md-8)] py-0",
1131
+ collapsed && !isLevel2 && variant === "avatar" &&
1132
+ "px-[var(--bh-space-sm-6)]",
1133
+ collapsed && isLevel2 &&
1134
+ "pointer-events-none h-0 w-full gap-0 border-0 p-0 opacity-[var(--bh-opacity-0)]",
863
1135
  className
864
1136
  )
865
1137
 
866
1138
  const commonProps = {
867
1139
  "aria-current": active ? ("page" as const) : undefined,
1140
+ "aria-hidden": collapsed && isLevel2 ? true : undefined,
868
1141
  "aria-label": resolvedLabel,
869
1142
  "data-active": active ? "true" : "false",
870
1143
  "data-collapsed": collapsed ? "true" : "false",
871
1144
  "data-slot": "sidebar-menu-button",
872
1145
  "data-state": state,
873
1146
  "data-variant": variant,
1147
+ inert: collapsed && isLevel2 ? true : undefined,
874
1148
  className: buttonClassName,
875
1149
  }
876
1150
 
@@ -885,33 +1159,48 @@ const SidebarMenuButton = React.forwardRef<
885
1159
  )
886
1160
 
887
1161
  const resolvedTooltip = tooltip ?? resolvedLabel
888
- if (!collapsed || !resolvedTooltip) return button
1162
+ if (!resolvedTooltip || isLevel2) return button
889
1163
 
890
1164
  return (
891
1165
  <Tooltip>
892
1166
  <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>
1167
+ {collapsed ? (
1168
+ <TooltipContent
1169
+ pointerPosition={dir === "rtl" ? "center-right" : "center-left"}
1170
+ showShortcut={false}
1171
+ side={dir === "rtl" ? "left" : "right"}
1172
+ >
1173
+ {resolvedTooltip}
1174
+ </TooltipContent>
1175
+ ) : null}
900
1176
  </Tooltip>
901
1177
  )
902
1178
  })
903
1179
 
904
- /** Nested menu list that unmounts when the parent sidebar is collapsed. */
1180
+ /** Nested menu list that contracts before the icon rail settles. */
905
1181
  function SidebarMenuSub({ className, ...props }: SidebarMenuSubProps) {
906
1182
  const { collapsed } = useSidebarContext()
907
- if (collapsed) return null
908
1183
 
909
1184
  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
- />
1185
+ <div
1186
+ aria-hidden={collapsed || undefined}
1187
+ data-slot="sidebar-menu-sub-motion"
1188
+ inert={collapsed ? true : undefined}
1189
+ className={cn(
1190
+ "grid w-full grid-rows-[1fr] opacity-[var(--bh-opacity-100)] transition-[grid-template-rows,opacity] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
1191
+ collapsed &&
1192
+ "pointer-events-none grid-rows-[0fr] opacity-[var(--bh-opacity-0)]"
1193
+ )}
1194
+ >
1195
+ <ul
1196
+ data-slot="sidebar-menu-sub"
1197
+ className={cn(
1198
+ "m-0 flex min-h-0 w-full list-none flex-col overflow-hidden p-0",
1199
+ className
1200
+ )}
1201
+ {...props}
1202
+ />
1203
+ </div>
915
1204
  )
916
1205
  }
917
1206
 
@@ -958,7 +1247,6 @@ const SidebarMenuTreeItem = React.forwardRef<
958
1247
  ref
959
1248
  ) {
960
1249
  const { collapsed } = useSidebarContext()
961
- if (collapsed) return null
962
1250
 
963
1251
  const label = getSlottableLabel(children, asChild)
964
1252
  const resolvedLabel =
@@ -998,18 +1286,23 @@ const SidebarMenuTreeItem = React.forwardRef<
998
1286
  const commonProps = {
999
1287
  "aria-current": active ? ("page" as const) : undefined,
1000
1288
  "aria-expanded": showChevron ? expanded : undefined,
1289
+ "aria-hidden": collapsed || undefined,
1001
1290
  "aria-label": resolvedLabel,
1002
1291
  "data-active": active ? "true" : "false",
1292
+ "data-collapsed": collapsed ? "true" : "false",
1003
1293
  "data-level": String(level),
1004
1294
  "data-slot": "sidebar-menu-tree-item",
1005
1295
  "data-state": state,
1296
+ inert: collapsed ? true : undefined,
1006
1297
  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]",
1298
+ "flex h-[var(--bh-sidebar-tree-item-height)] w-full min-w-0 items-center gap-[var(--bh-space-sm-6)] overflow-hidden 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)] opacity-[var(--bh-opacity-100)] outline-none transition-[height,gap,padding,opacity,background-color,border-color,color] duration-[var(--bh-sidebar-motion-duration-content)] delay-[var(--bh-sidebar-motion-delay-content)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
1008
1299
  "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
1300
  "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
1301
  (active || state === "active") &&
1011
1302
  "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
1303
  treeLevelClasses[level],
1304
+ collapsed &&
1305
+ "pointer-events-none h-0 gap-0 border-0 p-0 opacity-[var(--bh-opacity-0)] delay-0",
1013
1306
  className
1014
1307
  ),
1015
1308
  }
@@ -1036,32 +1329,42 @@ function SidebarTreeGroup({
1036
1329
  ...props
1037
1330
  }: SidebarTreeGroupProps) {
1038
1331
  const { collapsed } = useSidebarContext()
1039
- if (collapsed) return null
1040
1332
 
1041
1333
  return (
1042
1334
  <div
1043
- data-slot="sidebar-tree-group"
1335
+ aria-hidden={collapsed || undefined}
1336
+ data-slot="sidebar-tree-group-motion"
1337
+ inert={collapsed ? true : undefined}
1044
1338
  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
1339
+ "grid w-full grid-rows-[1fr] opacity-[var(--bh-opacity-100)] transition-[grid-template-rows,opacity] duration-[var(--bh-sidebar-motion-duration)] ease-[var(--bh-sidebar-motion-easing)] motion-reduce:transition-none",
1340
+ collapsed &&
1341
+ "pointer-events-none grid-rows-[0fr] opacity-[var(--bh-opacity-0)]"
1047
1342
  )}
1048
- {...props}
1049
1343
  >
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}
1344
+ <div
1345
+ data-slot="sidebar-tree-group"
1346
+ className={cn(
1347
+ "flex min-h-0 w-full flex-col items-start gap-[var(--bh-space-sm-6)] overflow-hidden pb-[var(--bh-space-sm-6)] pt-[var(--bh-space-xl-12)]",
1348
+ className
1349
+ )}
1350
+ {...props}
1351
+ >
1352
+ {label ? (
1353
+ <SidebarGroupLabel
1354
+ actions={actions}
1355
+ className={cn(
1356
+ expandable &&
1357
+ "[&_[data-slot=sidebar-group-label-expand]]:opacity-[var(--bh-opacity-100)]"
1358
+ )}
1359
+ expandable={expandable}
1360
+ expandIcon={expandIcon ?? (expandable ? <ChevronDownIcon /> : undefined)}
1361
+ >
1362
+ {label}
1363
+ </SidebarGroupLabel>
1364
+ ) : null}
1365
+ <div data-slot="sidebar-tree-menu" className="flex w-full flex-col">
1366
+ {children}
1367
+ </div>
1065
1368
  </div>
1066
1369
  </div>
1067
1370
  )
@@ -1084,7 +1387,7 @@ const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
1084
1387
  type={type}
1085
1388
  className={cn(
1086
1389
  "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)]",
1390
+ "hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:border-[var(--bh-border-focus)] focus-visible:shadow-[var(--elevation-button-focus)]",
1088
1391
  className
1089
1392
  )}
1090
1393
  onClick={(event) => {
@@ -1203,6 +1506,7 @@ export {
1203
1506
  SidebarTrigger,
1204
1507
  }
1205
1508
  export type {
1509
+ SidebarFooterProps,
1206
1510
  SidebarFooterUserProps,
1207
1511
  SidebarGroupActionProps,
1208
1512
  SidebarGroupLabelProps,
@@ -1223,6 +1527,7 @@ export type {
1223
1527
  SidebarProviderProps,
1224
1528
  SidebarRailProps,
1225
1529
  SidebarSectionProps,
1530
+ SidebarSize,
1226
1531
  SidebarToggleProps,
1227
1532
  SidebarTreeGroupProps,
1228
1533
  SidebarTriggerProps,