entasis 0.6.0 → 0.7.1

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 (72) hide show
  1. package/dist/components/AIAskUserQuestion/aiAskUserQuestion.theme.js +1 -1
  2. package/dist/components/AIChat/aiChat.theme.js +2 -2
  3. package/dist/components/AIComposer/aiComposer.theme.js +2 -2
  4. package/dist/components/AIContext/aiContext.theme.js +2 -2
  5. package/dist/components/AIFilePreview/aiFilePreview.theme.js +1 -1
  6. package/dist/components/AIMarker/aiMarker.theme.js +2 -2
  7. package/dist/components/AIMessage/aiMessage.theme.js +2 -2
  8. package/dist/components/AIMessageActions/aiMessageActions.theme.js +1 -1
  9. package/dist/components/AIModelSelector/aiModelSelector.theme.js +1 -1
  10. package/dist/components/AIReasoning/aiReasoning.theme.js +1 -1
  11. package/dist/components/AISuggestion/aiSuggestion.theme.js +1 -1
  12. package/dist/components/AIThread/aiThread.theme.js +2 -2
  13. package/dist/components/AIThreadToc/aiThreadToc.theme.js +1 -1
  14. package/dist/components/AITool/aiTool.theme.js +2 -2
  15. package/dist/components/AudioPlayer/audioPlayer.theme.js +2 -2
  16. package/dist/components/Avatar/avatarGroup.theme.js +1 -1
  17. package/dist/components/Button/button.mcp.d.ts +1 -1
  18. package/dist/components/Button/button.mcp.js +2 -2
  19. package/dist/components/ButtonGroup/buttonGroup.theme.js +1 -1
  20. package/dist/components/DocumentViewer/documentViewer.theme.js +1 -1
  21. package/dist/components/EventCalendar/eventCalendar.theme.js +1 -1
  22. package/dist/components/Form/ColorInput/colorInput.theme.js +2 -2
  23. package/dist/components/Form/ColorPicker/colorPicker.theme.js +2 -2
  24. package/dist/components/Form/DateInput/dateInput.theme.js +2 -2
  25. package/dist/components/Form/DateSelector/dateSelector.theme.js +1 -1
  26. package/dist/components/Form/File/fileInput.theme.js +2 -2
  27. package/dist/components/Form/KeyValueInput/keyValueInput.theme.js +2 -2
  28. package/dist/components/Form/MultiStepForm/multiStepForm.theme.js +1 -1
  29. package/dist/components/Form/NumberInput/numberInput.theme.js +2 -2
  30. package/dist/components/Form/PasswordInput/passwordInput.theme.js +2 -2
  31. package/dist/components/Form/PhoneInput/phoneInput.theme.js +2 -2
  32. package/dist/components/Form/PinInput/pinInput.theme.js +2 -2
  33. package/dist/components/Form/TagsInput/tagsInput.theme.js +2 -2
  34. package/dist/components/Form/TextArea/textArea.theme.js +2 -2
  35. package/dist/components/Form/TextInput/textInput.theme.js +2 -2
  36. package/dist/components/Form/TimeInput/timeInput.theme.js +2 -2
  37. package/dist/components/Form/VoiceInput/voiceInput.theme.js +2 -2
  38. package/dist/components/GanttChart/ganttChart.theme.js +2 -2
  39. package/dist/components/Grid/gridSpan.theme.js +2 -2
  40. package/dist/components/MediaVolume/mediaVolumeControl.theme.js +1 -1
  41. package/dist/components/MenuBar/menuBar.theme.js +2 -2
  42. package/dist/components/MenuOption/menuOption.theme.js +2 -2
  43. package/dist/components/MetadataList/metadataList.theme.js +2 -2
  44. package/dist/components/MiniCalendar/miniCalendar.theme.js +2 -2
  45. package/dist/components/NetworkIndicator/networkIndicator.mcp.d.ts +1 -1
  46. package/dist/components/NetworkIndicator/networkIndicator.mcp.js +1 -1
  47. package/dist/components/NetworkIndicator/networkIndicator.theme.js +2 -2
  48. package/dist/components/QRCode/qrCode.theme.js +2 -2
  49. package/dist/components/RichTextInput/richTextInput.theme.js +2 -2
  50. package/dist/components/ScrollArea/scrollArea.theme.js +2 -2
  51. package/dist/components/SegmentedControl/segmentedControl.theme.js +1 -1
  52. package/dist/components/SelectionMenu/selectionMenu.theme.js +1 -1
  53. package/dist/components/Sidebar/Sidebar.svelte +20 -3
  54. package/dist/components/Sidebar/SidebarActivityBar.svelte +9 -2
  55. package/dist/components/Sidebar/SidebarActivityBar.svelte.d.ts +5 -1
  56. package/dist/components/Sidebar/SidebarDesktopShell.svelte +12 -4
  57. package/dist/components/Sidebar/sidebar-layout.d.ts +7 -2
  58. package/dist/components/Sidebar/sidebar-layout.js +17 -6
  59. package/dist/components/Sidebar/sidebar.mcp.d.ts +1 -1
  60. package/dist/components/Sidebar/sidebar.mcp.js +25 -3
  61. package/dist/components/Sidebar/sidebar.theme.d.ts +33 -0
  62. package/dist/components/Sidebar/sidebar.theme.js +96 -46
  63. package/dist/components/SortableList/sortableList.theme.js +2 -2
  64. package/dist/components/SpinnerText/spinnerText.mcp.d.ts +1 -1
  65. package/dist/components/SpinnerText/spinnerText.mcp.js +1 -1
  66. package/dist/components/SpinnerText/spinnerText.theme.js +2 -2
  67. package/dist/components/ToggleButton/toggleButton.theme.js +1 -1
  68. package/dist/components/ToggleButtonGroup/toggleButtonGroup.theme.js +1 -1
  69. package/dist/components/ToggleMenu/toggleMenu.theme.js +2 -2
  70. package/dist/components/VideoPlayer/videoPlayer.theme.js +2 -2
  71. package/dist/generated/componentMcpRegistry.d.ts +4 -4
  72. package/package.json +1 -1
@@ -8,7 +8,8 @@
8
8
  SidebarActivityBarItem,
9
9
  SidebarDensity,
10
10
  SidebarSide,
11
- SidebarSize
11
+ SidebarSize,
12
+ SidebarVariant
12
13
  } from './sidebar.props.js';
13
14
  import SidebarIcon from './SidebarIcon.svelte';
14
15
  import { useSidebarTheme, type SidebarThemeProps } from './sidebar.theme.js';
@@ -19,6 +20,8 @@
19
20
  side,
20
21
  size,
21
22
  density,
23
+ variant = 'admin',
24
+ placement = 'positioned',
22
25
  orientation = 'vertical',
23
26
  label,
24
27
  class: className,
@@ -28,6 +31,10 @@
28
31
  side: SidebarSide;
29
32
  size: SidebarSize;
30
33
  density: SidebarDensity;
34
+ /** The Sidebar variant whose panel surface the rail wears beside the panel. */
35
+ variant?: SidebarVariant;
36
+ /** `static` when there is no desktop container to hold its gutters (`collapsible="none"`). */
37
+ placement?: 'positioned' | 'static';
31
38
  /** Vertical along the sidebar edge on desktop; horizontal at the top of the mobile drawer. */
32
39
  orientation?: 'vertical' | 'horizontal';
33
40
  /** Fallback accessible name when the activity bar sets no label. */
@@ -179,7 +186,7 @@
179
186
  data-side={side}
180
187
  data-orientation={orientation}
181
188
  aria-label={activityBar.label ?? label}
182
- class={classes.activityBar({ orientation, density, className })}
189
+ class={classes.activityBar({ orientation, variant, placement, density, className })}
183
190
  >
184
191
  {#if activityBar.header}
185
192
  <div
@@ -1,10 +1,14 @@
1
- import type { SidebarActivityBar, SidebarDensity, SidebarSide, SidebarSize } from './sidebar.props.js';
1
+ import type { SidebarActivityBar, SidebarDensity, SidebarSide, SidebarSize, SidebarVariant } from './sidebar.props.js';
2
2
  import { type SidebarThemeProps } from './sidebar.theme.js';
3
3
  type $$ComponentProps = {
4
4
  activityBar: SidebarActivityBar;
5
5
  side: SidebarSide;
6
6
  size: SidebarSize;
7
7
  density: SidebarDensity;
8
+ /** The Sidebar variant whose panel surface the rail wears beside the panel. */
9
+ variant?: SidebarVariant;
10
+ /** `static` when there is no desktop container to hold its gutters (`collapsible="none"`). */
11
+ placement?: 'positioned' | 'static';
8
12
  /** Vertical along the sidebar edge on desktop; horizontal at the top of the mobile drawer. */
9
13
  orientation?: 'vertical' | 'horizontal';
10
14
  /** Fallback accessible name when the activity bar sets no label. */
@@ -286,9 +286,17 @@
286
286
  <div
287
287
  data-slot="sidebar-activity-bar-container"
288
288
  data-side={side}
289
- class={getSidebarActivityBarContainerClass(side, frame)}
289
+ class={getSidebarActivityBarContainerClass(side, frame, variant)}
290
290
  >
291
- <SidebarActivityBar {activityBar} {side} {size} {density} label={activityBarLabel} {theme} />
291
+ <SidebarActivityBar
292
+ {activityBar}
293
+ {side}
294
+ {size}
295
+ {density}
296
+ {variant}
297
+ label={activityBarLabel}
298
+ {theme}
299
+ />
292
300
  </div>
293
301
  {/if}
294
302
  <!-- A hidden panel is parked off screen: `inert` keeps Tab out of it, so focus cannot land
@@ -379,8 +387,8 @@
379
387
  className:
380
388
  activityBar &&
381
389
  (side === 'left'
382
- ? '!left-[var(--sidebar-width-activity,3rem)]'
383
- : '!right-[var(--sidebar-width-activity,3rem)]')
390
+ ? '!left-[var(--sidebar-activity-offset,3rem)]'
391
+ : '!right-[var(--sidebar-activity-offset,3rem)]')
384
392
  })}
385
393
  onpointerenter={() => {
386
394
  if (!resize.isEdgeRevealSuppressed) edgeRevealed = true;
@@ -6,5 +6,10 @@ export declare function getSidebarContainerClass(side: SidebarSide, variant: Sid
6
6
  * panels already own a border and shadow, exactly as the edge-reveal path leaves them alone.
7
7
  */
8
8
  export declare function getSidebarPanelPeekClass(variant: SidebarVariant): "group-data-[peek=true]:raised-3" | undefined;
9
- /** Fixed/absolute placement for the activity bar column, pinned outside the panel. */
10
- export declare function getSidebarActivityBarContainerClass(side: SidebarSide, frame: SidebarFrame): string;
9
+ /**
10
+ * Fixed/absolute placement for the activity bar column, pinned outside the panel. The column is
11
+ * the reserved offset, and it takes the panel container's gutters so the rail lines up with the
12
+ * panel beside it: none for admin and framed, a vertical one for inset, the outer and vertical
13
+ * ones for floating and split (the panel's own gutter spaces the pair).
14
+ */
15
+ export declare function getSidebarActivityBarContainerClass(side: SidebarSide, frame: SidebarFrame, variant: SidebarVariant): string;
@@ -25,10 +25,16 @@ export function getSidebarContainerClass(side, variant, isEdgeRevealed, frame) {
25
25
  const panelOwnsShadow = variant === 'floating' || variant === 'split';
26
26
  return cx('inset-y-0 z-10 hidden w-[var(--sidebar-width)] bg-transparent transition-[left,right,width] duration-normal ease-linear group-data-[width-prehydrating=true]/sidebar-wrapper:!transition-none group-data-[resizing=true]:!transition-none md:flex', frame === 'viewport' ? 'fixed h-window' : 'absolute h-full',
27
27
  // The offcanvas offset slides the panel away relative to itself: the activity bar keeps
28
- // its own inset, so it never leaves the screen with the panel.
28
+ // its own inset, so it never leaves the screen with the panel. Floating and split park it
29
+ // fully past the edge instead: their rail stands in a gutter, and a panel parked under it
30
+ // would show through that gutter.
29
31
  side === 'left'
30
- ? 'left-[var(--sidebar-activity-offset,0px)] group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-activity-offset,0px)-var(--sidebar-width))]'
31
- : 'right-[var(--sidebar-activity-offset,0px)] group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-activity-offset,0px)-var(--sidebar-width))]', getContainerGeometryClass(variant),
32
+ ? cx('left-[var(--sidebar-activity-offset,0px)]', panelOwnsShadow
33
+ ? 'group-data-[collapsible=offcanvas]:-left-[var(--sidebar-width)]'
34
+ : 'group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-activity-offset,0px)-var(--sidebar-width))]')
35
+ : cx('right-[var(--sidebar-activity-offset,0px)]', panelOwnsShadow
36
+ ? 'group-data-[collapsible=offcanvas]:-right-[var(--sidebar-width)]'
37
+ : 'group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-activity-offset,0px)-var(--sidebar-width))]'), getContainerGeometryClass(variant),
32
38
  // Hover peek: full width over the page, lifted so it reads as a temporary drawer. The
33
39
  // elevation itself belongs to the panel (see `getSidebarPanelPeekClass`), not to this
34
40
  // transparent wrapper with its padding gutter.
@@ -46,7 +52,12 @@ export function getSidebarPanelPeekClass(variant) {
46
52
  const panelOwnsShadow = variant === 'floating' || variant === 'split';
47
53
  return panelOwnsShadow ? undefined : 'group-data-[peek=true]:raised-3';
48
54
  }
49
- /** Fixed/absolute placement for the activity bar column, pinned outside the panel. */
50
- export function getSidebarActivityBarContainerClass(side, frame) {
51
- return cx('inset-y-0 z-30 hidden w-[var(--sidebar-width-activity,3rem)] md:flex', frame === 'viewport' ? 'fixed h-window' : 'absolute h-full', side === 'left' ? 'left-0' : 'right-0');
55
+ /**
56
+ * Fixed/absolute placement for the activity bar column, pinned outside the panel. The column is
57
+ * the reserved offset, and it takes the panel container's gutters so the rail lines up with the
58
+ * panel beside it: none for admin and framed, a vertical one for inset, the outer and vertical
59
+ * ones for floating and split (the panel's own gutter spaces the pair).
60
+ */
61
+ export function getSidebarActivityBarContainerClass(side, frame, variant) {
62
+ return cx('inset-y-0 z-30 hidden w-[var(--sidebar-activity-offset,3rem)] md:flex', frame === 'viewport' ? 'fixed h-window' : 'absolute h-full', side === 'left' ? 'left-0' : 'right-0', variant === 'inset' && 'py-2', (variant === 'floating' || variant === 'split') && (side === 'left' ? 'py-2 pl-2' : 'py-2 pr-2'));
52
63
  }
@@ -1 +1 @@
1
- export declare const sidebarDescription = "\n# Sidebar Component\n\nSidebar navigation with data-driven groups, icon collapse, mobile drawer behavior,\nrecursive tree groups, header/footer rows, search, actions, and snippet escape hatches.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n</script>\n```\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n\timport { houseIcon } from 'entasis/icons/house';\n\timport { gearIcon } from 'entasis/icons/gear';\n\n\tconst items: SidebarGroup[] = [\n\t\t{\n\t\t\tlabel: 'Workspace',\n\t\t\titems: [\n\t\t\t\t{ label: 'Dashboard', href: '/', icon: houseIcon, isActive: true },\n\t\t\t\t{ label: 'Settings', href: '/settings', icon: gearIcon }\n\t\t\t]\n\t\t}\n\t];\n</script>\n\n<Sidebar items={items}>\n\t{#snippet children({ toggle })}\n\t\t<header>\n\t\t\t<button type=\"button\" onclick={toggle}>Toggle</button>\n\t\t</header>\n\t\t<main>Page content</main>\n\t{/snippet}\n</Sidebar>\n```\n\n## AI-Safe Usage Contract\n\n1. Use `items` for normal navigation. Use `content` only when data-driven rows cannot express the layout.\n2. Use local Entasis icon snippets such as `houseIcon`, not Lucide component constructors.\n3. Use `MenuItem[]` from `entasis/menu` for `menu` and action dropdowns.\n4. Do not combine `menu` with `href` or `onclick` on the same row; use `action` for a trailing row menu.\n5. Keep `children`, `header`, `content`, `footer`, `banner`, and action snippets pure; they receive `SidebarApi`.\n6. Use `collapsible=\"icon\"` for icon rail behavior, `collapsible=\"offcanvas\"` for hidden desktop panels, and `collapsible=\"none\"` for fixed sidebars. Icon collapse automatically falls back to offcanvas when any data-driven row lacks an icon.\n7. Offcanvas sidebars reveal over the content from the screen edge by default when hidden; set `edgeReveal={false}` to disable that. A revealed hidden sidebar keeps its resize handle and dismisses through a small rectangular pointer tolerance.\n8. Set `keyboardShortcut={false}` when embedding Sidebar inside another shortcut-heavy surface.\n9. Sidebar owns navigation, resize mechanics, the lower application wall, and variant surface geometry. AppShell forwards its variant and composes PageShell inside that surface.\n10. Use `size` for typography, icon scale, and item height. Use `density` independently for section padding, gaps, and submenu spacing.\n11. Use `activityBar` for a persistent icon rail outside the panel (section switching, workspaces). Every item needs an `icon` and a `label`; the label is the accessible name and the tooltip. It is layout mode only: `mode=\"panel\"` renders the navigation panel alone.\n12. Use `expandOnHover` only with `collapsible=\"icon\"`. It is a temporary peek, not a toggle: the persisted collapsed state never changes, while the peeked panel renders with expanded semantics.\n\n## Data Model\n\n### SidebarGroup\n- **label**: string - Group label, hidden in icon-collapsed mode.\n- **items**: SidebarMenuEntry[] - Menu rows.\n- **tree**: SidebarTreeNode[] - Recursive tree rows instead of menu items.\n- **action**: SidebarMenuActionDescriptor | SidebarMenuActionDescriptor[] | Snippet<[SidebarApi]> - Top-right group actions. Pass an array to pin several affordances (a `+` and a drag handle) to one group header; each renders as its own icon-only ghost button.\n- **collapsible**: boolean - Makes the group label a toggle.\n- **defaultOpen**: boolean - Initial collapsible group state.\n- **separator**: boolean - Divider before the group.\n\n### SidebarMenuEntry\n- **label**: string - Visible row label.\n- **icon**: SidebarIcon - Entasis icon snippet or string.\n- **iconColor**: Colors - Role tint for the leading icon, applied through `data-color`.\n- **iconVariant**: 'bare' | 'tile' - Leading icon treatment. `tile` paints a rounded square (`bg-color-muted text-color-muted-readable`) around the glyph, so per-project colour chips come from the role scale instead of hand-built markup.\n- **href**: string - Render as an anchor. Mutually exclusive with menu.\n- **onclick**: (event: MouseEvent) => void - Native click handler for button or anchor rows. Mutually exclusive with menu.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **disabled**: boolean - Disables button rows and marks anchor rows disabled.\n- **badge**: string | number - Trailing count/status, hidden in icon mode.\n- **tooltip**: string - Entasis Tooltip content in icon mode. Defaults to label.\n- **items**: SidebarMenuSubEntry[] - Inline nested menu.\n- **collapsible**: boolean - Set false for an always-open submenu.\n- **defaultOpen**: boolean - Initial nested menu state.\n- **menu**: MenuItem[] - Popup menu opened from the full row. Mutually exclusive with href/onclick.\n- **action**: SidebarMenuActionDescriptor | Snippet<[SidebarApi]> - Hover/focus trailing action.\n\n### SidebarActivityBar\nIcon rail pinned to the outer edge of the sidebar, visible in every display state.\n- **items**: SidebarActivityBarItem[] - Items rendered from the top.\n- **footerItems**: SidebarActivityBarItem[] - Items pinned to the end of the column.\n- **header** / **footer**: Snippet - Custom content before the first item and after the pinned ones.\n- **width**: string (default '3rem') - Column thickness, published as `--sidebar-width-activity`.\n- **label**: string - Accessible name for the column landmark. Set it whenever the panel also renders navigation.\n- **onSelect**: ({ item, index }) => void - Fires after an item is activated. `index` counts `items` then `footerItems`.\n\n### SidebarActivityBarItem\n- **icon**: SidebarIcon (required) - Icon rendered in the square.\n- **label**: string (required) - Accessible name and default tooltip; the square shows no text.\n- **id**: string - Stable render key.\n- **href** / **target** / **rel** - Render an anchor instead of a button.\n- **onclick**: (event: MouseEvent) => void - Native click handler.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **badge**: string | number | Snippet - Corner badge pinned to the outer top corner. An empty string renders a bare dot. A string or number badge joins the accessible name (`\"Alerts, 3\"`); a dot and a Snippet badge are decorative, so put their meaning in `label`.\n- **disabled**: boolean - Blocks activation and skips the item during keyboard navigation.\n- **tooltip**: string | false - Tooltip override; `false` suppresses it.\n\n### SidebarMenuButtonItem\nUse for `headerButton`, `footerButton`, or direct `<SidebarMenuButton />` rows.\n- **icon**: SidebarIcon - Leading logo/icon.\n- **avatar**: { src?: string; alt?: string; fallback?: string } - Leading avatar.\n- **variant**: 'default' | 'brand' | 'compact'.\n- **title**: string - Primary text.\n- **subtitle**: string - Secondary text.\n- **trailing**: SidebarIcon | false | SidebarMenuActionDescriptor - Trailing content. An icon is decorative; an action descriptor (`{ icon, label, onclick }`, the same shape as a group action) renders its own icon-only ghost button beside the row, so a workspace card can carry its own collapse control without a custom `header` snippet. Descriptor handlers are `onclick(event, api)`, so `api.toggle()` is reachable.\n- **href** / **onclick** / **menu** - Choose link, button, or popup behavior. `onclick(event, api)` receives the SidebarApi beside the event, so `api.toggle()` is reachable from the row itself.\n- **menuIconClass**: string - Class override for option icons inside the popup menu.\n\n## Props\n\n### State\n- **open**: boolean (bindable, default true) - Desktop expanded state.\n- **defaultOpen**: boolean (default true) - Initial desktop state when `open` is omitted.\n- **onOpenChange**: (open: boolean) => void - Called once for a library-originated desktop state change. Repeated requests and parent prop updates stay silent.\n- **onDisplayStateChange**: (state: SidebarDisplayState) => void - Called once for a library-originated semantic display-state change.\n- **api.displayState**: 'expanded' | 'collapsed' | 'hidden' - Semantic desktop state; hidden means closed offcanvas. A hover peek does not change it.\n- **api.isPeeking**: boolean - True while a hover peek renders the collapsed panel at full width. Read it alongside `displayState` when a snippet hides content in icon mode.\n- **keyboardShortcut**: string | false (default 'b') - Ctrl/Cmd shortcut key.\n\n### Layout\n- **side**: 'left' | 'right' - Desktop and mobile side.\n- **variant**: 'admin' | 'floating' | 'inset' | 'split' | 'framed' - Sidebar geometry. `framed` is the admin geometry for a sidebar hosted inside a raised card (AppShell variant framed), with a `surface-recessed` well. `admin` renders the conventional full-height navigation column; `inset` integrates navigation into the lower wall with an inset content surface; `split` renders detached sidebar and content surfaces.\n- **size**: 'small' | 'normal' | 'large' (default 'normal') - Typography, icon, avatar, badge, leading-media, item-height, and search-height scale.\n- **iconSize**: 'small' | 'normal' | 'large' - Icon and leading-media scale inside the panel on its own; defaults to `size`. Menu rows, sub rows, group labels and the header button follow it; the activity bar keeps `size`.\n- **activeVariant**: 'soft' | 'outline' | 'solid' (default 'soft') - How active rows are painted. `soft` is the shared selected recipe (`selectedSoft`: `bg-selected-muted text-selected-muted-readable`), `solid` its loud counterpart (`selectedSolid`: `bg-selected text-selected-contrast`), `outline` a bordered surface card (`bg-surface border border-neutral-muted text-neutral`) that reads as a raised card on a tinted well. Each row carries the choice as `data-active-variant`, so no descendant selector is needed to restyle selection.\n- **density**: 'compact' | 'normal' | 'comfortable' (default 'normal') - Section padding, group padding, gaps, horizontal inset, and submenu spacing.\n- **collapsible**: 'offcanvas' | 'icon' | 'none' - Collapse behavior. Icon mode requires icons on every data-driven row and otherwise resolves to offcanvas.\n- **collapseIcon**: DisclosureIndicator \u2014 'chevron' | 'plus-minus' | 'none' (default 'chevron') - Disclosure indicator drawn on collapsible menu rows. 'none' renders no indicator.\n- **mode**: 'layout' | 'panel' - Full resizing layout or only the visible navigation panel.\n- **frame**: 'viewport' | 'contained' - Standalone Sidebar positioning. Viewport mode uses Theme's dynamic window-height token; contained mode fills a positioned parent.\n- **width**: string - Expanded width.\n- **widthIcon**: string - Icon-collapsed width.\n- **widthMobile**: string - Mobile drawer width.\n- **rail**: boolean | 'line' | 'thumb' - Edge toggle rail. `true` keeps the thin line style; `thumb` renders a short visible handle with the same full-height hitbox. The appearance is preserved when the rail shares the resize control.\n- **activityBar**: SidebarActivityBar - Icon rail pinned outside the panel, in layout mode only (`mode=\"panel\"` renders the panel alone and ignores it). It never slides off screen: only the panel takes the offcanvas offset, and the reserved layout column is the panel width plus the rail width. On mobile it renders as a horizontal row at the top of the drawer.\n- **expandOnHover**: boolean (default false) - With `collapsible=\"icon\"`, hovering or focusing into the collapsed panel expands it to `width` over the page (`data-peek=\"true\"`) while the reserved column stays at `widthIcon`, so page content does not reflow. The persisted collapsed state is untouched, and the peeked panel renders exactly like an expanded one: group headers, badges, search, inline submenus, and inline tree branches all come back, and the rail or resize handle travels to its inner edge.\n- **edgeReveal**: boolean (default true) - Pointer/focus edge preview for hidden offcanvas sidebars. Hover reveal overlays content, remains resizable when configured, and re-hides after the pointer leaves its small rectangular tolerance. Dragging the sidebar closed suppresses immediate hover reopening until the pointer leaves the edge trigger; toggle/click opens persistently.\n- **resizable**: boolean | SidebarResizableOptions - Enables pointer and keyboard resizing while expanded, icon-collapsed, or temporarily edge-revealed. By default, collapse requires dragging 75% of `minWidth` beyond the minimum; override `collapseThreshold` for a custom boundary. Use `storageKey` to restore and persist the expanded width across sessions.\n - `onWidthChange({ width, isUserInteraction })` reports every expanded-width change with one named payload: continuously while the user resizes (`isUserInteraction: true`) and once when a stored width is restored (`isUserInteraction: false`).\n\n### Content\n- **items**: SidebarGroup[] - Data-driven body navigation.\n- **headerButton** / **footerButton**: SidebarMenuButtonItem - Sticky large rows.\n- **search**: SidebarSearch - Header search input; use its native `oninput` handler.\n- **headerMenu** / **footerMenu**: SidebarMenuEntry[] - Sticky quick menus.\n- Menu entries and nested entries accept `size: 'small' | 'normal' | 'large'` for row geometry.\n- **header**, **content**, **footer**, **children**, **banner**: Snippet<[SidebarApi]> - Escape hatches. The `header` snippet renders **first** in the header region, above `headerButton`, `search` and `headerMenu`.\n\n### Styling\n- **class**: string - Classes applied to the Sidebar root.\n- **theme**: SidebarThemeProps - Semantic part overrides such as `panel`, `header`, `nav`, `footer`, menu, search, rail, and mobile drawer parts.\n\n## Motion\n\n- **motion** theme slot: the y-axis slide shared by collapsible groups, inline submenus, and\n tree branches. Takes `in` / `out` slide params plus a `duration` / `easing` motion token.\n- Ladder: `<Theme components={{ sidebar: { motion } }}>` \u2192 `setSidebarTheme({ motion })` \u2192\n `theme.motion`. Reduced motion collapses it to 0.\n\n## Accessibility\n\n- The body navigation renders inside a named `<nav>` landmark (`data-sidebar=\"nav\"`), so assistive tech can jump straight to it and tell it apart from the activity bar's own landmark.\n- Active links set `aria-current=\"page\"`.\n- Collapsible rows and groups set `aria-expanded`.\n- Disabled buttons use `disabled`; disabled links omit `href`, use `aria-disabled` and `tabindex=-1`, and block activation.\n- Mobile drawer includes a backdrop button labelled \"Close Sidebar\".\n- Icon-collapsed rows keep their labels mounted and visually fade them, preserving accessible names and stable icon geometry.\n- Search, group controls, actions, and nested rows become inert before collapse can remove or hide them; focus returns to the owning visible row.\n- Nested groups, tree branches, and inline submenus use reversible height transitions for open, close, and sidebar-collapse changes.\n- Tree roots use menu-row styling and nested tree nodes use submenu-row styling. In desktop icon mode, root folders open a PopupMenu and descendants remain navigable through recursive Menu submenu popovers; root leaves retain direct navigation and tooltips.\n- When `rail` and `resizable` are both enabled, one edge control owns click-to-toggle, drag resize, and keyboard resize without overlapping hitboxes.\n- Hidden offcanvas sidebars keep that combined edge control while temporarily revealed. Resizing does not pin the sidebar open; leaving the panel, trigger, and handle tolerance re-hides it without discarding the configured width.\n- Both peeks (edge reveal and `expandOnHover`) stay open while focus is inside the panel or while an overlay opened from inside it is open, including nested submenus. They release about 120ms after the pointer, focus, and every such overlay are gone.\n- The activity bar is its own `<nav>` landmark with a `<ul>` of items, a roving tabindex, and ArrowUp/ArrowDown/Home/End navigation that loops and skips disabled items. Tab lands on the `isActive` item. Each square takes its accessible name from `label`, with a string or number `badge` appended to it, and shows `label` as a tooltip on hover and focus.\n- A hidden offcanvas panel is `inert`, so Tab never lands in a panel parked off screen; a peek makes it interactive again.\n\n## Notes\n\n- Dropdown menus use Entasis `PopupMenu` and `MenuItem[]`.\n- The component uses semantic Entasis tokens. Do not add shadcn `sidebar-*` color tokens.\n- Snippet icons from `entasis/icons/*` are the preferred icon format.\n";
1
+ export declare const sidebarDescription = "\n# Sidebar Component\n\nSidebar navigation with data-driven groups, icon collapse, mobile drawer behavior,\nrecursive tree groups, header/footer rows, search, actions, and snippet escape hatches.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n</script>\n```\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n\timport { houseIcon } from 'entasis/icons/house';\n\timport { gearIcon } from 'entasis/icons/gear';\n\n\tconst items: SidebarGroup[] = [\n\t\t{\n\t\t\tlabel: 'Workspace',\n\t\t\titems: [\n\t\t\t\t{ label: 'Dashboard', href: '/', icon: houseIcon, isActive: true },\n\t\t\t\t{ label: 'Settings', href: '/settings', icon: gearIcon }\n\t\t\t]\n\t\t}\n\t];\n</script>\n\n<Sidebar items={items}>\n\t{#snippet children({ toggle })}\n\t\t<header>\n\t\t\t<button type=\"button\" onclick={toggle}>Toggle</button>\n\t\t</header>\n\t\t<main>Page content</main>\n\t{/snippet}\n</Sidebar>\n```\n\n## AI-Safe Usage Contract\n\n1. Use `items` for normal navigation. Use `content` only when data-driven rows cannot express the layout.\n2. Use local Entasis icon snippets such as `houseIcon`, not Lucide component constructors.\n3. Use `MenuItem[]` from `entasis/menu` for `menu` and action dropdowns.\n4. Do not combine `menu` with `href` or `onclick` on the same row; use `action` for a trailing row menu.\n5. Keep `children`, `header`, `content`, `footer`, `banner`, and action snippets pure; they receive `SidebarApi`.\n6. Use `collapsible=\"icon\"` for icon rail behavior, `collapsible=\"offcanvas\"` for hidden desktop panels, and `collapsible=\"none\"` for fixed sidebars. Icon collapse automatically falls back to offcanvas when any data-driven row lacks an icon.\n7. Offcanvas sidebars reveal over the content from the screen edge by default when hidden; set `edgeReveal={false}` to disable that. A revealed hidden sidebar keeps its resize handle and dismisses through a small rectangular pointer tolerance.\n8. Set `keyboardShortcut={false}` when embedding Sidebar inside another shortcut-heavy surface.\n9. Sidebar owns navigation, resize mechanics, the lower application wall, and variant surface geometry. AppShell forwards its variant and composes PageShell inside that surface.\n10. Use `size` for typography, icon scale, and item height. Use `density` independently for section padding, gaps, and submenu spacing.\n11. Use `activityBar` for a persistent icon rail outside the panel (section switching, workspaces). Every item needs an `icon` and a `label`; the label is the accessible name and the tooltip. It is layout mode only: `mode=\"panel\"` renders the navigation panel alone. The rail does not change the panel by itself: keep the selected rail item in state from `onSelect`, mark it `isActive`, and derive the Sidebar's `items` from it, so each rail item shows its own menu.\n12. Use `expandOnHover` only with `collapsible=\"icon\"`. It is a temporary peek, not a toggle: the persisted collapsed state never changes, while the peeked panel renders with expanded semantics.\n\n## Data Model\n\n### SidebarGroup\n- **label**: string - Group label, hidden in icon-collapsed mode.\n- **items**: SidebarMenuEntry[] - Menu rows.\n- **tree**: SidebarTreeNode[] - Recursive tree rows instead of menu items.\n- **action**: SidebarMenuActionDescriptor | SidebarMenuActionDescriptor[] | Snippet<[SidebarApi]> - Top-right group actions. Pass an array to pin several affordances (a `+` and a drag handle) to one group header; each renders as its own icon-only ghost button.\n- **collapsible**: boolean - Makes the group label a toggle.\n- **defaultOpen**: boolean - Initial collapsible group state.\n- **separator**: boolean - Divider before the group.\n\n### SidebarMenuEntry\n- **label**: string - Visible row label.\n- **icon**: SidebarIcon - Entasis icon snippet or string.\n- **iconColor**: Colors - Role tint for the leading icon, applied through `data-color`.\n- **iconVariant**: 'bare' | 'tile' - Leading icon treatment. `tile` paints a rounded square (`bg-color-muted text-color-muted-readable`) around the glyph, so per-project colour chips come from the role scale instead of hand-built markup.\n- **href**: string - Render as an anchor. Mutually exclusive with menu.\n- **onclick**: (event: MouseEvent) => void - Native click handler for button or anchor rows. Mutually exclusive with menu.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **disabled**: boolean - Disables button rows and marks anchor rows disabled.\n- **badge**: string | number - Trailing count/status, hidden in icon mode.\n- **tooltip**: string - Entasis Tooltip content in icon mode. Defaults to label.\n- **items**: SidebarMenuSubEntry[] - Inline nested menu.\n- **collapsible**: boolean - Set false for an always-open submenu.\n- **defaultOpen**: boolean - Initial nested menu state.\n- **menu**: MenuItem[] - Popup menu opened from the full row. Mutually exclusive with href/onclick.\n- **action**: SidebarMenuActionDescriptor | Snippet<[SidebarApi]> - Hover/focus trailing action.\n\n### SidebarActivityBar\nIcon rail pinned to the outer edge of the sidebar, visible in every display state.\n- **items**: SidebarActivityBarItem[] - Items rendered from the top.\n- **footerItems**: SidebarActivityBarItem[] - Items pinned to the end of the column.\n- **header** / **footer**: Snippet - Custom content before the first item and after the pinned ones.\n- **width**: string (default '3rem') - Column thickness, published as `--sidebar-width-activity`.\n- **label**: string - Accessible name for the column landmark. Set it whenever the panel also renders navigation.\n- **onSelect**: ({ item, index }) => void - Fires after an item is activated. `index` counts `items` then `footerItems`.\n\n### SidebarActivityBarItem\n- **icon**: SidebarIcon (required) - Icon rendered in the square.\n- **label**: string (required) - Accessible name and default tooltip; the square shows no text.\n- **id**: string - Stable render key.\n- **href** / **target** / **rel** - Render an anchor instead of a button.\n- **onclick**: (event: MouseEvent) => void - Native click handler.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **badge**: string | number | Snippet - Corner badge pinned to the outer top corner. An empty string renders a bare dot. A string or number badge joins the accessible name (`\"Alerts, 3\"`); a dot and a Snippet badge are decorative, so put their meaning in `label`.\n- **disabled**: boolean - Blocks activation and skips the item during keyboard navigation.\n- **tooltip**: string | false - Tooltip override; `false` suppresses it.\n\n### SidebarMenuButtonItem\nUse for `headerButton`, `footerButton`, or direct `<SidebarMenuButton />` rows.\n- **icon**: SidebarIcon - Leading logo/icon.\n- **avatar**: { src?: string; alt?: string; fallback?: string } - Leading avatar.\n- **variant**: 'default' | 'brand' | 'compact'.\n- **title**: string - Primary text.\n- **subtitle**: string - Secondary text.\n- **trailing**: SidebarIcon | false | SidebarMenuActionDescriptor - Trailing content. An icon is decorative; an action descriptor (`{ icon, label, onclick }`, the same shape as a group action) renders its own icon-only ghost button beside the row, so a workspace card can carry its own collapse control without a custom `header` snippet. Descriptor handlers are `onclick(event, api)`, so `api.toggle()` is reachable.\n- **href** / **onclick** / **menu** - Choose link, button, or popup behavior. `onclick(event, api)` receives the SidebarApi beside the event, so `api.toggle()` is reachable from the row itself.\n- **menuIconClass**: string - Class override for option icons inside the popup menu.\n\n## Props\n\n### State\n- **open**: boolean (bindable, default true) - Desktop expanded state.\n- **defaultOpen**: boolean (default true) - Initial desktop state when `open` is omitted.\n- **onOpenChange**: (open: boolean) => void - Called once for a library-originated desktop state change. Repeated requests and parent prop updates stay silent.\n- **onDisplayStateChange**: (state: SidebarDisplayState) => void - Called once for a library-originated semantic display-state change.\n- **api.displayState**: 'expanded' | 'collapsed' | 'hidden' - Semantic desktop state; hidden means closed offcanvas. A hover peek does not change it.\n- **api.isPeeking**: boolean - True while a hover peek renders the collapsed panel at full width. Read it alongside `displayState` when a snippet hides content in icon mode.\n- **keyboardShortcut**: string | false (default 'b') - Ctrl/Cmd shortcut key.\n\n### Layout\n- **side**: 'left' | 'right' - Desktop and mobile side.\n- **variant**: 'admin' | 'floating' | 'inset' | 'split' | 'framed' - Sidebar geometry. `framed` is the admin geometry for a sidebar hosted inside a raised card (AppShell variant framed), with a `surface-recessed` well. `admin` renders the conventional full-height navigation column; `inset` integrates navigation into the lower wall with an inset content surface; `split` renders detached sidebar and content surfaces.\n- **size**: 'small' | 'normal' | 'large' (default 'normal') - Typography, icon, avatar, badge, leading-media, item-height, and search-height scale.\n- **iconSize**: 'small' | 'normal' | 'large' - Icon and leading-media scale inside the panel on its own; defaults to `size`. Menu rows, sub rows, group labels and the header button follow it; the activity bar keeps `size`.\n- **activeVariant**: 'soft' | 'outline' | 'solid' (default 'soft') - How active rows are painted. `soft` is the shared selected recipe (`selectedSoft`: `bg-selected-muted text-selected-muted-readable`), `solid` its loud counterpart (`selectedSolid`: `bg-selected text-selected-contrast`), `outline` a bordered surface card (`bg-surface border border-neutral-muted text-neutral`) that reads as a raised card on a tinted well. Each row carries the choice as `data-active-variant`, so no descendant selector is needed to restyle selection.\n- **density**: 'compact' | 'normal' | 'comfortable' (default 'normal') - Section padding, group padding, gaps, horizontal inset, and submenu spacing.\n- **collapsible**: 'offcanvas' | 'icon' | 'none' - Collapse behavior. Icon mode requires icons on every data-driven row and otherwise resolves to offcanvas.\n- **collapseIcon**: DisclosureIndicator \u2014 'chevron' | 'plus-minus' | 'none' (default 'chevron') - Disclosure indicator drawn on collapsible menu rows. 'none' renders no indicator.\n- **mode**: 'layout' | 'panel' - Full resizing layout or only the visible navigation panel.\n- **frame**: 'viewport' | 'contained' - Standalone Sidebar positioning. Viewport mode uses Theme's dynamic window-height token; contained mode fills a positioned parent.\n- **width**: string - Expanded width.\n- **widthIcon**: string - Icon-collapsed width.\n- **widthMobile**: string - Mobile drawer width.\n- **rail**: boolean | 'line' | 'thumb' - Edge toggle rail. `true` keeps the thin line style; `thumb` renders a short visible handle with the same full-height hitbox. The appearance is preserved when the rail shares the resize control.\n- **activityBar**: SidebarActivityBar - Icon rail pinned outside the panel, in layout mode only (`mode=\"panel\"` renders the panel alone and ignores it). It never slides off screen: only the panel takes the offcanvas offset, and the reserved layout column is the panel width plus the rail width. It wears the surface of the variant's panel: a hairline column on the canvas for `admin`, the recessed well for `framed`, borderless on the canvas for `inset`, and a card of its own (the panel's radius, edge and gutter) for `floating` and `split`. On mobile it renders as a horizontal row at the top of the drawer.\n- **expandOnHover**: boolean (default false) - With `collapsible=\"icon\"`, hovering or focusing into the collapsed panel expands it to `width` over the page (`data-peek=\"true\"`) while the reserved column stays at `widthIcon`, so page content does not reflow. The persisted collapsed state is untouched, and the peeked panel renders exactly like an expanded one: group headers, badges, search, inline submenus, and inline tree branches all come back, and the rail or resize handle travels to its inner edge.\n- **edgeReveal**: boolean (default true) - Pointer/focus edge preview for hidden offcanvas sidebars. Hover reveal overlays content, remains resizable when configured, and re-hides after the pointer leaves its small rectangular tolerance. Dragging the sidebar closed suppresses immediate hover reopening until the pointer leaves the edge trigger; toggle/click opens persistently.\n- **resizable**: boolean | SidebarResizableOptions - Enables pointer and keyboard resizing while expanded, icon-collapsed, or temporarily edge-revealed. By default, collapse requires dragging 75% of `minWidth` beyond the minimum; override `collapseThreshold` for a custom boundary. Use `storageKey` to restore and persist the expanded width across sessions.\n - `onWidthChange({ width, isUserInteraction })` reports every expanded-width change with one named payload: continuously while the user resizes (`isUserInteraction: true`) and once when a stored width is restored (`isUserInteraction: false`).\n\n### Content\n- **items**: SidebarGroup[] - Data-driven body navigation.\n- **headerButton** / **footerButton**: SidebarMenuButtonItem - Sticky large rows.\n- **search**: SidebarSearch - Header search input; use its native `oninput` handler.\n- **headerMenu** / **footerMenu**: SidebarMenuEntry[] - Sticky quick menus.\n- Menu entries and nested entries accept `size: 'small' | 'normal' | 'large'` for row geometry.\n- **header**, **content**, **footer**, **children**, **banner**: Snippet<[SidebarApi]> - Escape hatches. The `header` snippet renders **first** in the header region, above `headerButton`, `search` and `headerMenu`.\n\n### Styling\n- **class**: string - Classes applied to the Sidebar root.\n- **theme**: SidebarThemeProps - Semantic part overrides such as `panel`, `header`, `nav`, `footer`, menu, search, rail, and mobile drawer parts. Every part, with the element it lands on, its variants and their default classes, is listed in the entasis skill's `theme-parts/sidebar.md`; the registry key is `sidebar`.\n\n## Motion\n\n- **motion** theme slot: the y-axis slide shared by collapsible groups, inline submenus, and\n tree branches. Takes `in` / `out` slide params plus a `duration` / `easing` motion token.\n- Ladder: `<Theme components={{ sidebar: { motion } }}>` \u2192 `setSidebarTheme({ motion })` \u2192\n `theme.motion`. Reduced motion collapses it to 0.\n\n## Restyle recipes\n\nThe five asks that come up first, as the override to copy. Each one is rendered and asserted by\n`sidebar-recipes.svelte.test.ts`, so it cannot drift from the component. One rule behind them: a\ndefault written under a variant prefix (`data-[active-variant=solid]:data-active:bg-selected`,\n`data-[side=left]:border-r`) is only replaced by an override carrying the same prefixes; an\nunprefixed class coexists with it and loses on specificity. `theme-parts/sidebar.md` shows every\ndefault verbatim, prefixes included.\n\n- **Dark panel.** `dark` scopes the dark theme's variables to the panel, so every neutral ink inside\n flips with it (every theme also emits its variables on `.<name>`, so the class scopes the dark theme to one subtree); needs a theme named `dark`, which the documented setup declares.\n `theme={{ panel: { base: 'dark bg-slate-900' }, mobilePanel: { base: 'dark bg-slate-900' } }}`\n- **Active row in your colour.** `activeVariant=\"solid\"` plus the same prefix chain as the default:\n `theme={{ menuButton: { base: 'rounded-full', activeVariant: { solid: 'data-[active-variant=solid]:data-active:bg-indigo-600 data-[active-variant=solid]:data-active:text-white' } } }}`\n- **No hover change.** The overlay is a `::before` and the default also brightens the ink on hover:\n `theme={{ menuButton: { base: 'state-layer-none hover:text-inherit' }, subButton: { base: 'state-layer-none hover:text-neutral/70' } }}`\n- **Flat panel.** The edge is a prefixed `border-r` / `border-l` on the admin variant, the shadow a\n `raised-*` on the floating ones:\n `theme={{ panel: { base: 'raised-none', variant: { admin: 'data-[side=left]:border-r-0 data-[side=right]:border-l-0' } } }}`\n- **Row height.** A plain height beats the compound `h-control-*`: `theme={{ menuButton: { base: 'h-11' } }}`\n- **Bigger icons.** A prop, not a theme: `iconSize=\"large\"`.\n\n## Accessibility\n\n- The body navigation renders inside a named `<nav>` landmark (`data-sidebar=\"nav\"`), so assistive tech can jump straight to it and tell it apart from the activity bar's own landmark.\n- Active links set `aria-current=\"page\"`.\n- Collapsible rows and groups set `aria-expanded`.\n- Disabled buttons use `disabled`; disabled links omit `href`, use `aria-disabled` and `tabindex=-1`, and block activation.\n- Mobile drawer includes a backdrop button labelled \"Close Sidebar\".\n- Icon-collapsed rows keep their labels mounted and visually fade them, preserving accessible names and stable icon geometry.\n- Search, group controls, actions, and nested rows become inert before collapse can remove or hide them; focus returns to the owning visible row.\n- Nested groups, tree branches, and inline submenus use reversible height transitions for open, close, and sidebar-collapse changes.\n- Tree roots use menu-row styling and nested tree nodes use submenu-row styling. In desktop icon mode, root folders open a PopupMenu and descendants remain navigable through recursive Menu submenu popovers; root leaves retain direct navigation and tooltips.\n- When `rail` and `resizable` are both enabled, one edge control owns click-to-toggle, drag resize, and keyboard resize without overlapping hitboxes.\n- Hidden offcanvas sidebars keep that combined edge control while temporarily revealed. Resizing does not pin the sidebar open; leaving the panel, trigger, and handle tolerance re-hides it without discarding the configured width.\n- Both peeks (edge reveal and `expandOnHover`) stay open while focus is inside the panel or while an overlay opened from inside it is open, including nested submenus. They release about 120ms after the pointer, focus, and every such overlay are gone.\n- The activity bar is its own `<nav>` landmark with a `<ul>` of items, a roving tabindex, and ArrowUp/ArrowDown/Home/End navigation that loops and skips disabled items. Tab lands on the `isActive` item. Each square takes its accessible name from `label`, with a string or number `badge` appended to it, and shows `label` as a tooltip on hover and focus.\n- A hidden offcanvas panel is `inert`, so Tab never lands in a panel parked off screen; a peek makes it interactive again.\n\n## Notes\n\n- Dropdown menus use Entasis `PopupMenu` and `MenuItem[]`.\n- The component uses semantic Entasis tokens. Do not add shadcn `sidebar-*` color tokens.\n- Snippet icons from `entasis/icons/*` are the preferred icon format.\n";
@@ -53,7 +53,7 @@ recursive tree groups, header/footer rows, search, actions, and snippet escape h
53
53
  8. Set \`keyboardShortcut={false}\` when embedding Sidebar inside another shortcut-heavy surface.
54
54
  9. Sidebar owns navigation, resize mechanics, the lower application wall, and variant surface geometry. AppShell forwards its variant and composes PageShell inside that surface.
55
55
  10. Use \`size\` for typography, icon scale, and item height. Use \`density\` independently for section padding, gaps, and submenu spacing.
56
- 11. Use \`activityBar\` for a persistent icon rail outside the panel (section switching, workspaces). Every item needs an \`icon\` and a \`label\`; the label is the accessible name and the tooltip. It is layout mode only: \`mode="panel"\` renders the navigation panel alone.
56
+ 11. Use \`activityBar\` for a persistent icon rail outside the panel (section switching, workspaces). Every item needs an \`icon\` and a \`label\`; the label is the accessible name and the tooltip. It is layout mode only: \`mode="panel"\` renders the navigation panel alone. The rail does not change the panel by itself: keep the selected rail item in state from \`onSelect\`, mark it \`isActive\`, and derive the Sidebar's \`items\` from it, so each rail item shows its own menu.
57
57
  12. Use \`expandOnHover\` only with \`collapsible="icon"\`. It is a temporary peek, not a toggle: the persisted collapsed state never changes, while the peeked panel renders with expanded semantics.
58
58
 
59
59
  ## Data Model
@@ -141,7 +141,7 @@ Use for \`headerButton\`, \`footerButton\`, or direct \`<SidebarMenuButton />\`
141
141
  - **widthIcon**: string - Icon-collapsed width.
142
142
  - **widthMobile**: string - Mobile drawer width.
143
143
  - **rail**: boolean | 'line' | 'thumb' - Edge toggle rail. \`true\` keeps the thin line style; \`thumb\` renders a short visible handle with the same full-height hitbox. The appearance is preserved when the rail shares the resize control.
144
- - **activityBar**: SidebarActivityBar - Icon rail pinned outside the panel, in layout mode only (\`mode="panel"\` renders the panel alone and ignores it). It never slides off screen: only the panel takes the offcanvas offset, and the reserved layout column is the panel width plus the rail width. On mobile it renders as a horizontal row at the top of the drawer.
144
+ - **activityBar**: SidebarActivityBar - Icon rail pinned outside the panel, in layout mode only (\`mode="panel"\` renders the panel alone and ignores it). It never slides off screen: only the panel takes the offcanvas offset, and the reserved layout column is the panel width plus the rail width. It wears the surface of the variant's panel: a hairline column on the canvas for \`admin\`, the recessed well for \`framed\`, borderless on the canvas for \`inset\`, and a card of its own (the panel's radius, edge and gutter) for \`floating\` and \`split\`. On mobile it renders as a horizontal row at the top of the drawer.
145
145
  - **expandOnHover**: boolean (default false) - With \`collapsible="icon"\`, hovering or focusing into the collapsed panel expands it to \`width\` over the page (\`data-peek="true"\`) while the reserved column stays at \`widthIcon\`, so page content does not reflow. The persisted collapsed state is untouched, and the peeked panel renders exactly like an expanded one: group headers, badges, search, inline submenus, and inline tree branches all come back, and the rail or resize handle travels to its inner edge.
146
146
  - **edgeReveal**: boolean (default true) - Pointer/focus edge preview for hidden offcanvas sidebars. Hover reveal overlays content, remains resizable when configured, and re-hides after the pointer leaves its small rectangular tolerance. Dragging the sidebar closed suppresses immediate hover reopening until the pointer leaves the edge trigger; toggle/click opens persistently.
147
147
  - **resizable**: boolean | SidebarResizableOptions - Enables pointer and keyboard resizing while expanded, icon-collapsed, or temporarily edge-revealed. By default, collapse requires dragging 75% of \`minWidth\` beyond the minimum; override \`collapseThreshold\` for a custom boundary. Use \`storageKey\` to restore and persist the expanded width across sessions.
@@ -157,7 +157,7 @@ Use for \`headerButton\`, \`footerButton\`, or direct \`<SidebarMenuButton />\`
157
157
 
158
158
  ### Styling
159
159
  - **class**: string - Classes applied to the Sidebar root.
160
- - **theme**: SidebarThemeProps - Semantic part overrides such as \`panel\`, \`header\`, \`nav\`, \`footer\`, menu, search, rail, and mobile drawer parts.
160
+ - **theme**: SidebarThemeProps - Semantic part overrides such as \`panel\`, \`header\`, \`nav\`, \`footer\`, menu, search, rail, and mobile drawer parts. Every part, with the element it lands on, its variants and their default classes, is listed in the entasis skill's \`theme-parts/sidebar.md\`; the registry key is \`sidebar\`.
161
161
 
162
162
  ## Motion
163
163
 
@@ -166,6 +166,28 @@ Use for \`headerButton\`, \`footerButton\`, or direct \`<SidebarMenuButton />\`
166
166
  - Ladder: \`<Theme components={{ sidebar: { motion } }}>\` → \`setSidebarTheme({ motion })\` →
167
167
  \`theme.motion\`. Reduced motion collapses it to 0.
168
168
 
169
+ ## Restyle recipes
170
+
171
+ The five asks that come up first, as the override to copy. Each one is rendered and asserted by
172
+ \`sidebar-recipes.svelte.test.ts\`, so it cannot drift from the component. One rule behind them: a
173
+ default written under a variant prefix (\`data-[active-variant=solid]:data-active:bg-selected\`,
174
+ \`data-[side=left]:border-r\`) is only replaced by an override carrying the same prefixes; an
175
+ unprefixed class coexists with it and loses on specificity. \`theme-parts/sidebar.md\` shows every
176
+ default verbatim, prefixes included.
177
+
178
+ - **Dark panel.** \`dark\` scopes the dark theme's variables to the panel, so every neutral ink inside
179
+ flips with it (every theme also emits its variables on \`.<name>\`, so the class scopes the dark theme to one subtree); needs a theme named \`dark\`, which the documented setup declares.
180
+ \`theme={{ panel: { base: 'dark bg-slate-900' }, mobilePanel: { base: 'dark bg-slate-900' } }}\`
181
+ - **Active row in your colour.** \`activeVariant="solid"\` plus the same prefix chain as the default:
182
+ \`theme={{ menuButton: { base: 'rounded-full', activeVariant: { solid: 'data-[active-variant=solid]:data-active:bg-indigo-600 data-[active-variant=solid]:data-active:text-white' } } }}\`
183
+ - **No hover change.** The overlay is a \`::before\` and the default also brightens the ink on hover:
184
+ \`theme={{ menuButton: { base: 'state-layer-none hover:text-inherit' }, subButton: { base: 'state-layer-none hover:text-neutral/70' } }}\`
185
+ - **Flat panel.** The edge is a prefixed \`border-r\` / \`border-l\` on the admin variant, the shadow a
186
+ \`raised-*\` on the floating ones:
187
+ \`theme={{ panel: { base: 'raised-none', variant: { admin: 'data-[side=left]:border-r-0 data-[side=right]:border-l-0' } } }}\`
188
+ - **Row height.** A plain height beats the compound \`h-control-*\`: \`theme={{ menuButton: { base: 'h-11' } }}\`
189
+ - **Bigger icons.** A prop, not a theme: \`iconSize="large"\`.
190
+
169
191
  ## Accessibility
170
192
 
171
193
  - The body navigation renders inside a named \`<nav>\` landmark (\`data-sidebar="nav"\`), so assistive tech can jump straight to it and tell it apart from the activity bar's own landmark.
@@ -306,6 +306,17 @@ export declare const sidebarTheme: {
306
306
  vertical: string;
307
307
  horizontal: string;
308
308
  };
309
+ variant: {
310
+ admin: string;
311
+ floating: string;
312
+ inset: string;
313
+ split: string;
314
+ framed: string;
315
+ };
316
+ placement: {
317
+ positioned: string;
318
+ static: string;
319
+ };
309
320
  density: {
310
321
  compact: string;
311
322
  normal: string;
@@ -777,6 +788,17 @@ export declare const setSidebarTheme: (theme: InferComponentTheme<{
777
788
  vertical: string;
778
789
  horizontal: string;
779
790
  };
791
+ variant: {
792
+ admin: string;
793
+ floating: string;
794
+ inset: string;
795
+ split: string;
796
+ framed: string;
797
+ };
798
+ placement: {
799
+ positioned: string;
800
+ static: string;
801
+ };
780
802
  density: {
781
803
  compact: string;
782
804
  normal: string;
@@ -1246,6 +1268,17 @@ export declare const useSidebarTheme: import("../../utils/cva/theme.js").UseComp
1246
1268
  vertical: string;
1247
1269
  horizontal: string;
1248
1270
  };
1271
+ variant: {
1272
+ admin: string;
1273
+ floating: string;
1274
+ inset: string;
1275
+ split: string;
1276
+ framed: string;
1277
+ };
1278
+ placement: {
1279
+ positioned: string;
1280
+ static: string;
1281
+ };
1249
1282
  density: {
1250
1283
  compact: string;
1251
1284
  normal: string;
@@ -22,9 +22,11 @@ const activeVariants = {
22
22
  // Icon and leading-media sizes. `size` sets them by default; `iconSize`, declared after it on the
23
23
  // panel parts, publishes the same variables and wins when a consumer sets it on its own.
24
24
  const sidebarSizeVariables = {
25
- small: '[--sidebar-icon-size:0.875rem] [--sidebar-media-size:1.75rem] [--sidebar-compact-media-size:1rem]',
26
- normal: '[--sidebar-icon-size:1rem] [--sidebar-media-size:2rem] [--sidebar-compact-media-size:1.25rem]',
27
- large: '[--sidebar-icon-size:1.25rem] [--sidebar-media-size:2.25rem] [--sidebar-compact-media-size:1.5rem]'
25
+ // The same tokens the rest of the kit sizes icons and media by, so a `spacing` retune still
26
+ // reaches the sidebar; `iconSize` simply republishes them one step up or down.
27
+ small: '[--sidebar-icon-size:var(--icon-size-sm)] [--sidebar-media-size:calc(var(--spacing)*7)] [--sidebar-compact-media-size:calc(var(--spacing)*4)]',
28
+ normal: '[--sidebar-icon-size:var(--icon-size-md)] [--sidebar-media-size:calc(var(--spacing)*8)] [--sidebar-compact-media-size:calc(var(--spacing)*5)]',
29
+ large: '[--sidebar-icon-size:var(--icon-size-lg)] [--sidebar-media-size:calc(var(--spacing)*9)] [--sidebar-compact-media-size:calc(var(--spacing)*6)]'
28
30
  };
29
31
  const sidebarDensityVariables = {
30
32
  // The same spacing tokens the group's `p-sm/md/lg` resolve to, so anything positioned from
@@ -123,16 +125,16 @@ const defaultGroup = cva({
123
125
  }
124
126
  });
125
127
  const defaultGroupLabel = cva({
126
- base: 'text-neutral/70 flex shrink-0 items-center rounded-sm font-medium outline-none transition-[height,margin,padding,opacity] duration-normal ease-linear group-data-[collapsible=icon]:opacity-0 disabled:pointer-events-none [&>svg]:shrink-0',
128
+ base: 'text-neutral/70 flex shrink-0 items-center rounded-sm font-medium outline-none transition-[height,margin,padding,opacity] duration-normal ease-linear group-data-[collapsible=icon]:opacity-0 disabled:pointer-events-none [&>svg]:shrink-0 [&>svg]:size-[var(--sidebar-icon-size)]',
127
129
  variants: {
128
130
  interactive: {
129
131
  true: 'state-layer hover:text-neutral focus-visible:ring-2 focus-visible:ring-focus/50',
130
132
  false: null
131
133
  },
132
134
  size: {
133
- small: 'h-control-sm text-xs group-data-[collapsible=icon]:-mt-layout-md [&>svg]:size-icon-sm',
134
- normal: 'h-control-md text-sm group-data-[collapsible=icon]:-mt-layout-lg [&>svg]:size-icon-md',
135
- large: 'h-control-lg text-sm group-data-[collapsible=icon]:-mt-layout-lg [&>svg]:size-icon-lg'
135
+ small: 'h-control-sm text-xs group-data-[collapsible=icon]:-mt-layout-md',
136
+ normal: 'h-control-md text-sm group-data-[collapsible=icon]:-mt-layout-lg',
137
+ large: 'h-control-lg text-sm group-data-[collapsible=icon]:-mt-layout-lg'
136
138
  },
137
139
  density: {
138
140
  compact: 'px-md',
@@ -193,7 +195,7 @@ const defaultMenu = cva({
193
195
  }
194
196
  });
195
197
  const defaultMenuButton = cva({
196
- base: 'state-layer peer/menu-button group/menu-button flex w-full items-center overflow-hidden rounded-sm text-left outline-none transition-[background,color,width,height,padding,margin,border-radius] duration-normal ease-linear hover:text-neutral focus-visible:ring-2 focus-visible:ring-focus/50 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:font-medium group-has-data-[sidebar=menu-action]/menu-item:pr-layout-lg group-data-[collapsible=icon]:mx-[calc((var(--sidebar-width-icon)-var(--sidebar-icon-button-width))/2-var(--sidebar-group-padding))] group-data-[collapsible=icon]:w-[var(--sidebar-icon-button-width)] group-data-[collapsible=icon]:rounded-none group-data-[variant=admin]:group-data-[collapsible=icon]:rounded-sm group-data-[variant=framed]:group-data-[collapsible=icon]:rounded-sm group-data-[variant=inset]:group-data-[collapsible=icon]:rounded-sm group-data-[collapsible=icon]:![padding-inline:calc((var(--sidebar-icon-button-width)-var(--sidebar-icon-size))/2)] group-data-[collapsible=icon]:ring-inset [&_svg]:shrink-0',
198
+ base: 'state-layer peer/menu-button group/menu-button flex w-full items-center overflow-hidden rounded-sm text-left outline-none transition-[background,color,width,height,padding,margin,border-radius] duration-normal ease-linear hover:text-neutral focus-visible:ring-2 focus-visible:ring-focus/50 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:font-medium group-has-data-[sidebar=menu-action]/menu-item:pr-layout-lg group-data-[collapsible=icon]:mx-[calc((var(--sidebar-width-icon)-var(--sidebar-icon-button-width))/2-var(--sidebar-group-padding))] group-data-[collapsible=icon]:w-[var(--sidebar-icon-button-width)] group-data-[collapsible=icon]:rounded-none group-data-[variant=admin]:group-data-[collapsible=icon]:rounded-sm group-data-[variant=framed]:group-data-[collapsible=icon]:rounded-sm group-data-[variant=inset]:group-data-[collapsible=icon]:rounded-sm group-data-[collapsible=icon]:![padding-inline:calc((var(--sidebar-icon-button-width)-var(--sidebar-icon-size))/2)] group-data-[collapsible=icon]:ring-inset [&_svg]:shrink-0 [&_svg]:size-[var(--sidebar-icon-size)]',
197
199
  variants: {
198
200
  activeVariant: activeVariants,
199
201
  variant: {
@@ -201,9 +203,9 @@ const defaultMenuButton = cva({
201
203
  outline: 'border border-neutral-muted bg-surface'
202
204
  },
203
205
  size: {
204
- small: 'text-xs leading-4 [&_svg]:size-icon-sm',
205
- normal: 'text-sm leading-5 [&_svg]:size-icon-md',
206
- large: 'text-base leading-6 [&_svg]:size-icon-lg'
206
+ small: 'text-xs leading-4',
207
+ normal: 'text-sm leading-5',
208
+ large: 'text-base leading-6'
207
209
  },
208
210
  density: {
209
211
  compact: 'gap-sm px-sm',
@@ -253,12 +255,12 @@ const defaultMenuSecondary = cva({
253
255
  }
254
256
  });
255
257
  const defaultMenuTrailing = cva({
256
- base: 'ml-auto shrink-0 opacity-100 transition-[opacity,transform] duration-normal ease-standard group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:translate-x-1 group-data-[collapsible=icon]:opacity-0',
258
+ base: 'ml-auto shrink-0 opacity-100 transition-[opacity,transform] duration-normal ease-standard group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:translate-x-1 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-[var(--sidebar-icon-size)]',
257
259
  variants: {
258
260
  size: {
259
- small: '[&>svg]:size-icon-sm',
260
- normal: '[&>svg]:size-icon-md',
261
- large: '[&>svg]:size-icon-lg'
261
+ small: '',
262
+ normal: '',
263
+ large: ''
262
264
  }
263
265
  },
264
266
  defaultVariants: {
@@ -292,13 +294,13 @@ const defaultTreeSubMenu = cva({
292
294
  }
293
295
  });
294
296
  const defaultSubButton = cva({
295
- base: 'state-layer text-neutral/70 hover:text-neutral flex min-w-0 -translate-x-px items-center overflow-hidden rounded-sm outline-none transition-[background,color,height,padding] duration-normal ease-linear focus-visible:ring-2 focus-visible:ring-focus/50 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0',
297
+ base: 'state-layer text-neutral/70 hover:text-neutral flex min-w-0 -translate-x-px items-center overflow-hidden rounded-sm outline-none transition-[background,color,height,padding] duration-normal ease-linear focus-visible:ring-2 focus-visible:ring-focus/50 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:shrink-0 [&>svg]:size-[var(--sidebar-icon-size)]',
296
298
  variants: {
297
299
  activeVariant: activeVariants,
298
300
  size: {
299
- small: 'h-6 text-xs leading-4 [&>svg]:size-icon-sm',
300
- normal: 'h-control-sm text-sm leading-5 [&>svg]:size-icon-md',
301
- large: 'h-control-md text-base leading-6 [&>svg]:size-icon-lg'
301
+ small: 'h-6 text-xs leading-4',
302
+ normal: 'h-control-sm text-sm leading-5',
303
+ large: 'h-control-md text-base leading-6'
302
304
  },
303
305
  density: {
304
306
  compact: 'gap-sm px-md',
@@ -319,12 +321,12 @@ const defaultSubButton = cva({
319
321
  }
320
322
  });
321
323
  const defaultMenuAction = cva({
322
- base: 'state-layer text-neutral hover:text-neutral peer-hover/menu-button:text-neutral absolute top-1/2 flex aspect-square -translate-y-1/2 items-center justify-center rounded-sm p-0 opacity-100 outline-none transition group-data-[collapsible=icon]:hidden focus-visible:ring-2 focus-visible:ring-focus/50 md:opacity-0 group-focus-within/menu-row:opacity-100 group-hover/menu-row:opacity-100 has-[[aria-expanded=true]]:opacity-100 [&>svg]:shrink-0',
324
+ base: 'state-layer text-neutral hover:text-neutral peer-hover/menu-button:text-neutral absolute top-1/2 flex aspect-square -translate-y-1/2 items-center justify-center rounded-sm p-0 opacity-100 outline-none transition group-data-[collapsible=icon]:hidden focus-visible:ring-2 focus-visible:ring-focus/50 md:opacity-0 group-focus-within/menu-row:opacity-100 group-hover/menu-row:opacity-100 has-[[aria-expanded=true]]:opacity-100 [&>svg]:shrink-0 [&>svg]:size-[var(--sidebar-icon-size)]',
323
325
  variants: {
324
326
  size: {
325
- small: 'size-4.5 [&>svg]:size-icon-sm',
326
- normal: 'size-5 [&>svg]:size-icon-md',
327
- large: 'size-6 [&>svg]:size-icon-lg'
327
+ small: 'size-4.5',
328
+ normal: 'size-5',
329
+ large: 'size-6'
328
330
  },
329
331
  density: {
330
332
  compact: 'right-0.5',
@@ -340,12 +342,12 @@ const defaultMenuAction = cva({
340
342
  // One icon-only ghost button box. `size` follows the descriptor, defaulting to the Sidebar size,
341
343
  // so a group's `+` lands at row scale instead of at the group label's scale.
342
344
  const defaultActionSlot = cva({
343
- base: 'state-layer text-neutral hover:text-neutral flex aspect-square shrink-0 items-center justify-center rounded-sm p-0 outline-none transition focus-visible:ring-2 focus-visible:ring-focus/50 [&>svg]:shrink-0',
345
+ base: 'state-layer text-neutral hover:text-neutral flex aspect-square shrink-0 items-center justify-center rounded-sm p-0 outline-none transition focus-visible:ring-2 focus-visible:ring-focus/50 [&>svg]:shrink-0 [&>svg]:size-[var(--sidebar-icon-size)]',
344
346
  variants: {
345
347
  size: {
346
- small: 'h-control-sm [&>svg]:size-icon-sm',
347
- normal: 'h-control-md [&>svg]:size-icon-md',
348
- large: 'h-control-lg [&>svg]:size-icon-lg'
348
+ small: 'h-control-sm',
349
+ normal: 'h-control-md',
350
+ large: 'h-control-lg'
349
351
  }
350
352
  },
351
353
  defaultVariants: {
@@ -383,12 +385,12 @@ const defaultButtonRow = cva({
383
385
  base: 'flex w-full min-w-0 items-center gap-micro'
384
386
  });
385
387
  const defaultActionTrigger = cva({
386
- base: 'flex size-full items-center justify-center rounded-sm bg-transparent outline-none [&>svg]:shrink-0',
388
+ base: 'flex size-full items-center justify-center rounded-sm bg-transparent outline-none [&>svg]:shrink-0 [&>svg]:size-[var(--sidebar-icon-size)]',
387
389
  variants: {
388
390
  size: {
389
- small: '[&>svg]:size-icon-sm',
390
- normal: '[&>svg]:size-icon-md',
391
- large: '[&>svg]:size-icon-lg'
391
+ small: '',
392
+ normal: '',
393
+ large: ''
392
394
  }
393
395
  },
394
396
  defaultVariants: {
@@ -678,8 +680,8 @@ const defaultMedia = cva({
678
680
  base: 'bg-neutral text-neutral-contrast flex aspect-square shrink-0 items-center justify-center rounded-sm',
679
681
  variants: {
680
682
  itemSize: {
681
- normal: 'text-xs',
682
- large: ''
683
+ normal: 'text-xs size-[var(--sidebar-compact-media-size)]',
684
+ large: `size-[var(--sidebar-media-size)] [&_svg]:size-[var(--sidebar-icon-size)]`
683
685
  },
684
686
  size: {
685
687
  small: '',
@@ -688,12 +690,9 @@ const defaultMedia = cva({
688
690
  }
689
691
  },
690
692
  compoundVariants: [
691
- { itemSize: 'large', size: 'small', class: 'size-7 [&_svg]:size-icon-sm' },
692
- { itemSize: 'large', size: 'normal', class: 'size-8 [&_svg]:size-icon-md' },
693
- { itemSize: 'large', size: 'large', class: 'size-9 [&_svg]:size-icon-lg' },
694
- { itemSize: 'normal', size: 'small', class: 'size-4 [&_svg]:size-icon-xs' },
695
- { itemSize: 'normal', size: 'normal', class: 'size-5 [&_svg]:size-icon-xs' },
696
- { itemSize: 'normal', size: 'large', class: 'size-6 [&_svg]:size-icon-sm' }
693
+ { itemSize: 'normal', size: 'small', class: '[&_svg]:size-icon-xs' },
694
+ { itemSize: 'normal', size: 'normal', class: '[&_svg]:size-icon-xs' },
695
+ { itemSize: 'normal', size: 'large', class: '[&_svg]:size-icon-sm' }
697
696
  ],
698
697
  defaultVariants: {
699
698
  itemSize: 'large',
@@ -701,12 +700,12 @@ const defaultMedia = cva({
701
700
  }
702
701
  });
703
702
  const defaultAvatar = cva({
704
- base: 'bg-neutral-muted text-neutral-muted-readable flex shrink-0 items-center justify-center overflow-hidden rounded-sm font-medium',
703
+ base: 'bg-neutral-muted text-neutral-muted-readable flex shrink-0 items-center justify-center overflow-hidden rounded-sm font-medium size-[var(--sidebar-media-size)]',
705
704
  variants: {
706
705
  size: {
707
- small: 'size-7 text-xs',
708
- normal: 'size-8 text-sm',
709
- large: 'size-9 text-sm'
706
+ small: 'text-xs',
707
+ normal: 'text-sm',
708
+ large: 'text-sm'
710
709
  }
711
710
  },
712
711
  defaultVariants: {
@@ -714,11 +713,24 @@ const defaultAvatar = cva({
714
713
  }
715
714
  });
716
715
  const defaultActivityBar = cva({
717
- base: 'flex shrink-0 bg-surface-canvas text-neutral',
716
+ base: 'flex shrink-0 text-neutral',
718
717
  variants: {
719
718
  orientation: {
720
- vertical: 'h-full w-[var(--sidebar-width-activity)] flex-col border-neutral-muted data-[side=left]:border-r data-[side=right]:border-l',
721
- horizontal: 'w-full flex-row items-center border-b border-neutral-muted'
719
+ vertical: 'h-full w-[var(--sidebar-width-activity)] flex-col',
720
+ horizontal: 'w-full flex-row items-center border-b border-neutral-muted bg-surface-canvas'
721
+ },
722
+ // Beside the panel the rail wears that variant's panel surface (see the compounds); the
723
+ // mobile drawer's horizontal bar keeps one look whatever the variant.
724
+ variant: {
725
+ admin: '',
726
+ floating: '',
727
+ inset: '',
728
+ split: '',
729
+ framed: ''
730
+ },
731
+ placement: {
732
+ positioned: '',
733
+ static: ''
722
734
  },
723
735
  density: {
724
736
  compact: 'gap-xs p-xs',
@@ -726,8 +738,46 @@ const defaultActivityBar = cva({
726
738
  comfortable: 'gap-sm p-sm'
727
739
  }
728
740
  },
741
+ compoundVariants: [
742
+ // A hairline column on the canvas, beside admin's canvas panel.
743
+ {
744
+ orientation: 'vertical',
745
+ variant: 'admin',
746
+ class: 'bg-surface-canvas border-neutral-muted data-[side=left]:border-r data-[side=right]:border-l'
747
+ },
748
+ // In the card's recessed well with framed's panel, not on the canvas outside the card.
749
+ {
750
+ orientation: 'vertical',
751
+ variant: 'framed',
752
+ class: 'bg-surface-recessed border-neutral-muted data-[side=left]:border-r data-[side=right]:border-l'
753
+ },
754
+ // Borderless on the canvas, like inset's panel.
755
+ { orientation: 'vertical', variant: 'inset', class: 'bg-surface-canvas' },
756
+ // A card of its own, the same card as the panel.
757
+ {
758
+ orientation: 'vertical',
759
+ variant: ['floating', 'split'],
760
+ class: 'rounded-lg bg-surface raised-1'
761
+ },
762
+ // Without the desktop container's gutters (`collapsible="none"`), the margins the static
763
+ // panel takes.
764
+ {
765
+ orientation: 'vertical',
766
+ variant: ['floating', 'split'],
767
+ placement: 'static',
768
+ class: 'my-md data-[side=left]:ml-md data-[side=right]:mr-md'
769
+ },
770
+ {
771
+ orientation: 'vertical',
772
+ variant: 'inset',
773
+ placement: 'static',
774
+ class: 'my-md h-[calc(100%_-_1rem)]'
775
+ }
776
+ ],
729
777
  defaultVariants: {
730
778
  orientation: 'vertical',
779
+ variant: 'admin',
780
+ placement: 'positioned',
731
781
  density: 'normal'
732
782
  }
733
783
  });
@@ -85,5 +85,5 @@ export const sortableListTheme = {
85
85
  handle: defaultHandle,
86
86
  empty: defaultEmpty
87
87
  };
88
- export const setSortableListTheme = setComponentTheme('sortableList');
89
- export const useSortableListTheme = useComponentTheme('sortableList', sortableListTheme);
88
+ export const setSortableListTheme = setComponentTheme('sortable-list');
89
+ export const useSortableListTheme = useComponentTheme('sortable-list', sortableListTheme);