entasis 0.5.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.
@@ -533,9 +533,9 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
533
533
  disabled?: boolean | undefined;
534
534
  error?: import("../../Slot/slot.js").Slot<undefined> | undefined;
535
535
  class?: string | undefined;
536
+ prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
536
537
  size?: import("../../../types/theme.js").Sizes | undefined;
537
538
  value?: string | null | undefined;
538
- prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
539
539
  suffix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
540
540
  name?: string | undefined;
541
541
  label?: import("../../Slot/slot.js").Slot<undefined> | undefined;
@@ -1304,9 +1304,9 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
1304
1304
  disabled?: boolean | undefined;
1305
1305
  error?: import("../../Slot/slot.js").Slot<undefined> | undefined;
1306
1306
  class?: string | undefined;
1307
+ prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
1307
1308
  size?: import("../../../types/theme.js").Sizes | undefined;
1308
1309
  value?: string | null | undefined;
1309
- prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
1310
1310
  suffix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
1311
1311
  name?: string | undefined;
1312
1312
  label?: import("../../Slot/slot.js").Slot<undefined> | undefined;
@@ -2109,9 +2109,9 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
2109
2109
  disabled?: boolean | undefined;
2110
2110
  error?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2111
2111
  class?: string | undefined;
2112
+ prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2112
2113
  size?: import("../../../types/theme.js").Sizes | undefined;
2113
2114
  value?: string | null | undefined;
2114
- prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2115
2115
  suffix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2116
2116
  name?: string | undefined;
2117
2117
  label?: import("../../Slot/slot.js").Slot<undefined> | undefined;
@@ -2880,9 +2880,9 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
2880
2880
  disabled?: boolean | undefined;
2881
2881
  error?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2882
2882
  class?: string | undefined;
2883
+ prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2883
2884
  size?: import("../../../types/theme.js").Sizes | undefined;
2884
2885
  value?: string | null | undefined;
2885
- prefix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2886
2886
  suffix?: import("../../Slot/slot.js").Slot<undefined> | undefined;
2887
2887
  name?: string | undefined;
2888
2888
  label?: import("../../Slot/slot.js").Slot<undefined> | undefined;
@@ -30,6 +30,7 @@
30
30
  side = 'left',
31
31
  variant = 'admin',
32
32
  size = 'normal',
33
+ iconSize,
33
34
  activeVariant = 'soft',
34
35
  density = 'normal',
35
36
  collapsible = 'offcanvas',
@@ -268,7 +269,10 @@
268
269
  style:--sidebar-width={panelWidth}
269
270
  style:--sidebar-width-icon={widthIcon}
270
271
  style:--sidebar-width-mobile={widthMobile}
271
- class={cx('group', classes.panel({ variant, placement: 'panel', size, density, className }))}
272
+ class={cx(
273
+ 'group',
274
+ classes.panel({ variant, placement: 'panel', size, iconSize, density, className })
275
+ )}
272
276
  {...attachments}
273
277
  >
274
278
  {@render panel()}
@@ -307,6 +311,7 @@
307
311
  {widthMobile}
308
312
  {dir}
309
313
  {size}
314
+ {iconSize}
310
315
  {density}
311
316
  label={navLabel}
312
317
  {theme}
@@ -333,7 +338,7 @@
333
338
  data-sidebar="sidebar"
334
339
  data-color={resolvedColor}
335
340
  data-side={side}
336
- class={classes.panel({ variant, placement: 'static', size, density })}
341
+ class={classes.panel({ variant, placement: 'static', size, iconSize, density })}
337
342
  >
338
343
  {@render panel()}
339
344
  </div>
@@ -349,6 +354,7 @@
349
354
  {side}
350
355
  {frame}
351
356
  {size}
357
+ {iconSize}
352
358
  {density}
353
359
  {rail}
354
360
  {edgeReveal}
@@ -35,6 +35,7 @@
35
35
  side,
36
36
  frame,
37
37
  size,
38
+ iconSize,
38
39
  density,
39
40
  rail,
40
41
  edgeReveal,
@@ -60,6 +61,7 @@
60
61
  side: SidebarSide;
61
62
  frame: SidebarFrame;
62
63
  size: SidebarSize;
64
+ iconSize?: SidebarSize;
63
65
  density: SidebarDensity;
64
66
  rail: SidebarRail;
65
67
  edgeReveal: boolean;
@@ -308,6 +310,7 @@
308
310
  variant,
309
311
  placement: 'positioned',
310
312
  size,
313
+ iconSize,
311
314
  density,
312
315
  className: getSidebarPanelPeekClass(variant)
313
316
  })}
@@ -10,6 +10,7 @@ type $$ComponentProps = {
10
10
  side: SidebarSide;
11
11
  frame: SidebarFrame;
12
12
  size: SidebarSize;
13
+ iconSize?: SidebarSize;
13
14
  density: SidebarDensity;
14
15
  rail: SidebarRail;
15
16
  edgeReveal: boolean;
@@ -13,6 +13,7 @@
13
13
  widthMobile,
14
14
  dir,
15
15
  size,
16
+ iconSize,
16
17
  density,
17
18
  label,
18
19
  theme,
@@ -24,6 +25,7 @@
24
25
  widthMobile: string;
25
26
  dir?: 'ltr' | 'rtl';
26
27
  size: SidebarSize;
28
+ iconSize?: SidebarSize;
27
29
  density: SidebarDensity;
28
30
  label: string;
29
31
  theme?: SidebarThemeProps;
@@ -57,7 +59,7 @@
57
59
  data-density={density}
58
60
  style:--sidebar-width-mobile={widthMobile}
59
61
  {dir}
60
- class={classes.mobilePanel({ side, size, density })}
62
+ class={classes.mobilePanel({ side, size, iconSize, density })}
61
63
  >
62
64
  {@render children()}
63
65
  </div>
@@ -8,6 +8,7 @@ type $$ComponentProps = {
8
8
  widthMobile: string;
9
9
  dir?: 'ltr' | 'rtl';
10
10
  size: SidebarSize;
11
+ iconSize?: SidebarSize;
11
12
  density: SidebarDensity;
12
13
  label: string;
13
14
  theme?: SidebarThemeProps;
@@ -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- **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.\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";
@@ -130,6 +130,7 @@ Use for \`headerButton\`, \`footerButton\`, or direct \`<SidebarMenuButton />\`
130
130
  - **side**: 'left' | 'right' - Desktop and mobile side.
131
131
  - **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.
132
132
  - **size**: 'small' | 'normal' | 'large' (default 'normal') - Typography, icon, avatar, badge, leading-media, item-height, and search-height scale.
133
+ - **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\`.
133
134
  - **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.
134
135
  - **density**: 'compact' | 'normal' | 'comfortable' (default 'normal') - Section padding, group padding, gaps, horizontal inset, and submenu spacing.
135
136
  - **collapsible**: 'offcanvas' | 'icon' | 'none' - Collapse behavior. Icon mode requires icons on every data-driven row and otherwise resolves to offcanvas.
@@ -344,6 +344,8 @@ type SidebarOwnProps = {
344
344
  variant?: SidebarVariant;
345
345
  /** Typography, icon, and item-height scale. */
346
346
  size?: SidebarSize;
347
+ /** Icon and leading-media scale inside the panel, on its own. Defaults to `size`. */
348
+ iconSize?: SidebarSize;
347
349
  /** How active rows are painted. Defaults to 'soft', the shared selected recipe. */
348
350
  activeVariant?: SidebarActiveVariant;
349
351
  /** Spacing density for section padding, gaps, and nested navigation. */
@@ -29,6 +29,11 @@ export declare const sidebarTheme: {
29
29
  normal: string;
30
30
  large: string;
31
31
  };
32
+ iconSize: {
33
+ small: string;
34
+ normal: string;
35
+ large: string;
36
+ };
32
37
  density: {
33
38
  compact: string;
34
39
  normal: string;
@@ -412,6 +417,11 @@ export declare const sidebarTheme: {
412
417
  normal: string;
413
418
  large: string;
414
419
  };
420
+ iconSize: {
421
+ small: string;
422
+ normal: string;
423
+ large: string;
424
+ };
415
425
  density: {
416
426
  compact: string;
417
427
  normal: string;
@@ -490,6 +500,11 @@ export declare const setSidebarTheme: (theme: InferComponentTheme<{
490
500
  normal: string;
491
501
  large: string;
492
502
  };
503
+ iconSize: {
504
+ small: string;
505
+ normal: string;
506
+ large: string;
507
+ };
493
508
  density: {
494
509
  compact: string;
495
510
  normal: string;
@@ -873,6 +888,11 @@ export declare const setSidebarTheme: (theme: InferComponentTheme<{
873
888
  normal: string;
874
889
  large: string;
875
890
  };
891
+ iconSize: {
892
+ small: string;
893
+ normal: string;
894
+ large: string;
895
+ };
876
896
  density: {
877
897
  compact: string;
878
898
  normal: string;
@@ -949,6 +969,11 @@ export declare const useSidebarTheme: import("../../utils/cva/theme.js").UseComp
949
969
  normal: string;
950
970
  large: string;
951
971
  };
972
+ iconSize: {
973
+ small: string;
974
+ normal: string;
975
+ large: string;
976
+ };
952
977
  density: {
953
978
  compact: string;
954
979
  normal: string;
@@ -1332,6 +1357,11 @@ export declare const useSidebarTheme: import("../../utils/cva/theme.js").UseComp
1332
1357
  normal: string;
1333
1358
  large: string;
1334
1359
  };
1360
+ iconSize: {
1361
+ small: string;
1362
+ normal: string;
1363
+ large: string;
1364
+ };
1335
1365
  density: {
1336
1366
  compact: string;
1337
1367
  normal: string;
@@ -19,6 +19,8 @@ const activeVariants = {
19
19
  // The `--sidebar-*` properties are INTERNAL: one `size` step publishes the icon, media and
20
20
  // compact-media dimensions the rows read back, so every part scales together. A consumer
21
21
  // changes `size`, never one of these properties.
22
+ // Icon and leading-media sizes. `size` sets them by default; `iconSize`, declared after it on the
23
+ // panel parts, publishes the same variables and wins when a consumer sets it on its own.
22
24
  const sidebarSizeVariables = {
23
25
  small: '[--sidebar-icon-size:0.875rem] [--sidebar-media-size:1.75rem] [--sidebar-compact-media-size:1rem]',
24
26
  normal: '[--sidebar-icon-size:1rem] [--sidebar-media-size:2rem] [--sidebar-compact-media-size:1.25rem]',
@@ -63,6 +65,7 @@ const defaultPanel = cva({
63
65
  positioned: 'w-full'
64
66
  },
65
67
  size: sidebarSizeVariables,
68
+ iconSize: sidebarSizeVariables,
66
69
  density: sidebarDensityVariables
67
70
  },
68
71
  compoundVariants: [
@@ -662,6 +665,7 @@ const defaultMobilePanel = cva({
662
665
  right: ''
663
666
  },
664
667
  size: sidebarSizeVariables,
668
+ iconSize: sidebarSizeVariables,
665
669
  density: sidebarDensityVariables
666
670
  },
667
671
  defaultVariants: {
@@ -25,7 +25,7 @@ export declare const componentMcpRegistry: {
25
25
  readonly stack: "\n# Stack\n\nStack arranges arbitrary content along one flex axis. Use the `orientation` prop to switch\nbetween horizontal and vertical layout, and `align` / `justify` for cross-axis and main-axis\nalignment.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n import { Stack } from '../components/Stack/index.ts';\n</script>\n```\n\n## Usage\n\n```svelte\n<Stack gap=\"xl\" padding=\"xl\">\n <h2>Account</h2>\n <Stack orientation=\"horizontal\" align=\"center\" gap=\"md\" wrap=\"wrap\">\n <span>Profile</span>\n <span>Security</span>\n </Stack>\n</Stack>\n```\n\n## Responsive props\n\n`orientation`, `gap`, `align`, `justify` and `wrap` each take a plain value or a\nper-breakpoint record:\n\n```svelte\n<Stack orientation={{ md: 'horizontal' }} gap={{ xs: 'sm', lg: 'xl' }} align=\"center\">\n <span>Filters</span>\n <span>Results</span>\n</Stack>\n```\n\nThe breakpoints measure the stack's OWN width — `xs` base, `sm` 36rem, `md` 42rem,\n`lg` 56rem, `xl` 72rem — not the viewport's, so the same stack is `xs` in a narrow sidebar\nand `lg` full-bleed on the same page. The nearest defined key at or below the stack's width\nwins, and below the narrowest key the prop's default applies: `{ md: 'horizontal' }` is\nvertical at `xs` and `sm`. A prop falls back to its default only when it is `undefined`\nor `null`. A function form must be deterministic in its argument — it is called once per\nbreakpoint. There is no measurement and no JS: the five values ship as custom\nproperties and container queries pick one, so server-rendered markup is already laid out.\n\n## Props\n\n- `orientation`: `'horizontal' | 'vertical'` — flex direction (default: `'vertical'`).\n- `align`: cross-axis alignment — `start | center | end | stretch` (default: `'stretch'`).\n- `justify`: main-axis alignment — `start | center | end | between | around | evenly` (default: `'start'`).\n- `gap`, `padding`, `paddingInline`, and `paddingBlock` accept\n `none | xs | sm | md | lg | xl`.\n- `paddingInline` and `paddingBlock` override `padding` on their axis.\n- `width`, `height`, `maxWidth`, and `minHeight` accept CSS strings or pixel numbers.\n- `wrap` accepts `nowrap | wrap | wrap-reverse`.\n- `scrollable` enables native `overflow: auto`.\n- `as` changes the semantic HTML element without changing layout behavior:\n `div | span | section | article | aside | main | nav | header | footer | form | fieldset`.\n Lists are not among them — the root always wraps its children in one layout `<div>`, which\n `<ul>` and `<ol>` do not admit. Write the list yourself and put a stack inside an `<li>`.\n\n## Structure\n\nStack renders two elements: the root (`data-slot=\"stack\"`) is the box the host sizes and the\ncontainer the breakpoints are measured against — it takes `as`, `class`, `style`, the size\nprops, the padding and `scrollable` — and a single layout child (`data-slot=\"stack-layout\"`)\ncarries the flex line. The `inner` theme slot styles that child.\n\nStack forwards common semantic HTML attributes and Svelte attachments to the root element.\nPrefer parent-owned `gap` over child margins. The internal `micro` and `layout-*` tokens are\nreserved for component recipes and must not be used in generated interfaces.\n";
26
26
  readonly 'app-shell': "\n# AppShell Component\n\nConvenience wrapper for the common application layout: Sidebar owns navigation,\nresponsive drawer behavior, the application wall, and variant surfaces, while PageShell\nowns the page header, document-flow content, footer, and route-level injection. AppShell\nforwards one shared variant to Sidebar and composes PageShell inside it.\nAppShell establishes a dynamic viewport-height minimum while letting the document own\nvertical scrolling. The desktop sidebar remains sticky independently of page content.\n\nUse AppShell when every route follows the same sidebar + page shell structure. Use\nSidebar and PageShell directly when the frame needs custom composition.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { AppShell, type AppShellSidebarProps } from 'entasis/app-shell';\n\timport { houseIcon } from 'entasis/icons/house';\n\n\tconst sidebar: AppShellSidebarProps = {\n\t\tcollapsible: 'icon',\n\t\trail: true,\n\t\titems: [\n\t\t\t{\n\t\t\t\tlabel: 'Workspace',\n\t\t\t\titems: [{ label: 'Home', href: '/', icon: houseIcon, isActive: true }]\n\t\t\t}\n\t\t]\n\t};\n</script>\n\n<AppShell variant=\"framed\" {sidebar} title=\"Dashboard\" subtitle=\"Operational overview\">\n\t{#snippet children({ sidebar })}\n\t\t<button type=\"button\" onclick={sidebar.toggle}>Toggle sidebar</button>\n\t{/snippet}\n</AppShell>\n```\n\n## Route-Level Injection\n\nAppShell renders PageShell internally, so child pages can use the PageShell context API:\n\n```svelte\n<script lang=\"ts\">\n\timport { setPageShell } from 'entasis/page-shell';\n\n\tsetPageShell({\n\t\ttitle: 'Insights',\n\t\tsubtitle: 'Revenue and retention',\n\t\tfooter: pageFooter\n\t});\n</script>\n\n{#snippet pageFooter()}\n\t<span>Synced just now</span>\n{/snippet}\n```\n\n## Props\n\n- **variant**: 'admin' | 'floating' | 'inset' | 'split' | 'framed' - Shared shell treatment forwarded to Sidebar and PageShell chrome. Admin chrome uses the Sidebar canvas surface; floating chrome uses detached raised, rounded surfaces. `framed` draws one rounded card (`rounded-xl` + `raised-1`, so the border and elevation come from the elevation engine) around both the sidebar and the page; the Sidebar's own `framed` variant paints its navigation well as `surface-recessed`, an inset of that card, and the page header sits on the page surface.\n- **sidebar**: AppShellSidebarProps - Sidebar props except `children`, `mode`, `frame`, and `variant`.\n Configure Sidebar `size` and `density` independently inside this object.\n- **eyebrow**: string | Snippet<[PageShellApi]> - Small metadata above the PageShell title.\n- **breadcrumbs**: BreadcrumbItem[] | Snippet<[AppShellApi]> - PageShell breadcrumbs.\n- **breadcrumbsMaxItems**: number - Maximum visible breadcrumb items before ellipsis. Defaults to 4.\n- **back**: PageShellAction | Snippet<[AppShellApi]> - Back affordance before breadcrumbs or eyebrow.\n- **title**: string | Snippet<[PageShellApi]> - Default PageShell title.\n- **subtitle**: string | Snippet<[PageShellApi]> - Default PageShell subtitle.\n- **header**: Snippet<[PageShellApi]> - Custom PageShell header.\n- **headerActions**: Snippet<[AppShellApi]> | PageShellAction[] - Actions in the default PageShell header. Use an array for standard Button props, or a snippet when the action needs sidebar/page-shell API access.\n- **footer**: Snippet<[PageShellApi]> - PageShell footer.\n- **footerActions**: Snippet<[AppShellApi]> | PageShellAction[] - PageShell footer actions.\n- **children**: Snippet<[AppShellApi]> - Main content, with `pageShell` and `sidebar` APIs.\n- **contentPadding**: 'none' | 'small' | 'normal' | 'large' - PageShell content padding preset.\n- **contentWidth**: 'full' | 'narrow' | 'normal' | 'wide' | 'prose' - PageShell content width preset.\n- **pageShellTheme**: PageShellThemeProps - PageShell theme overrides.\n- **theme**: AppShellThemeProps - AppShell `root`, `frame` and `page` surface-token overrides. Sidebar owns the wall and shell geometry.\n\n## Accessibility\n\nAppShell delegates navigation semantics to Sidebar and page landmarks to PageShell.\nUse string `title` for the default `h1`, or preserve heading semantics when replacing\nthe PageShell header with a custom snippet.\n";
27
27
  readonly 'page-shell': "\n# PageShell Component\n\nContent shell for pages rendered inside an application frame. PageShell provides a\nsticky header and footer, document-flow content, title/subtitle props, and a context\nAPI for child routes to inject shell content. Scrolling stays on the document by default,\nso browser navigation and scroll restoration keep their native behavior.\n\nUse PageShell inside `Sidebar.children` when Sidebar owns navigation and responsive\ndrawer behavior.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { PageShell, type PageShellAction } from 'entasis/page-shell';\n\timport { downloadSimpleIcon } from 'entasis/icons/downloadSimple';\n\n\tconst headerActions = [\n\t\t{\n\t\t\tcontent: 'Export',\n\t\t\tcolor: 'primary',\n\t\t\tprefix: downloadSimpleIcon\n\t\t}\n\t] satisfies PageShellAction[];\n</script>\n\n<PageShell title=\"Insights\" subtitle=\"Live account health\" {headerActions}>\n\t{#snippet footer()}\n\t\t<span>Updated just now</span>\n\t{/snippet}\n\n\t{#snippet children()}\n\t\t<section class=\"p-6\">Page content</section>\n\t{/snippet}\n</PageShell>\n```\n\n## Route-Level Injection\n\nChild pages can set header and footer content through context. Use `setPageShell`\nduring component initialization for automatic cleanup.\n\n```svelte\n<script lang=\"ts\">\n\timport { setPageShell } from 'entasis/page-shell';\n\n\tsetPageShell({\n\t\ttitle: 'Revenue',\n\t\tsubtitle: 'Segment breakdown',\n\t\theaderActions: revenueActions,\n\t\tfooter: revenueFooter\n\t});\n</script>\n\n{#snippet revenueActions()}\n\t<button type=\"button\">Refresh</button>\n{/snippet}\n\n{#snippet revenueFooter()}\n\t<span>Synced 2 minutes ago</span>\n{/snippet}\n```\n\n## Props\n\n- **eyebrow**: string | Snippet<[PageShellApi]> - Small metadata above the title. Ignored when breadcrumbs are set.\n- **breadcrumbs**: BreadcrumbItem[] | Snippet<[PageShellApi]> - Default-header breadcrumbs.\n- **breadcrumbsMaxItems**: number - Maximum visible breadcrumb items before ellipsis. Defaults to 4.\n- **back**: PageShellAction | Snippet<[PageShellApi]> - Back affordance before breadcrumbs or eyebrow.\n- **title**: string | Snippet<[PageShellApi]> - Default header title.\n- **subtitle**: string | Snippet<[PageShellApi]> - Default header subtitle.\n- **header**: Snippet<[PageShellApi]> - Custom sticky header content.\n- **headerActions**: Snippet<[PageShellApi]> | PageShellAction[] - Actions on the right side of the default header. Use an array for standard Button props, or a snippet when the action needs shell API access.\n- **footer**: Snippet<[PageShellApi]> - Custom sticky footer content.\n- **footerActions**: Snippet<[PageShellApi]> | PageShellAction[] - Actions on the right side of the sticky footer.\n\t- **children**: Snippet<[PageShellApi]> - Page content rendered in normal document flow.\n\t- **contentPadding**: 'none' | 'small' | 'normal' | 'large' - Padding applied to the content inner wrapper.\n\t- **contentWidth**: 'full' | 'narrow' | 'normal' | 'wide' | 'prose' - Max-width preset for the content inner wrapper.\n\t- **actionOverflow**: 'auto' | 'never' - Mobile overflow behavior for action arrays.\n\t- **mobileActionCount**: 0 | 1 | 2 - Number of action-array buttons kept inline on mobile.\n\t- **label**: string - Accessible name for the page's `main` landmark, applied as aria-label.\n\t- **theme**: PageShellThemeProps - Per-instance theme overrides.\n\n## API\n\n- **usePageShell()** returns the current PageShell API and throws when no PageShell exists.\n- **setPageShell(config)** registers a scoped config override and removes it on component destroy.\n- **api.set(config)** pushes a manual override and returns a cleanup function.\n- **api.setFooterActions(actions)** pushes scoped page footer actions.\n- **api.reset()** clears all scoped overrides.\n\n## Header Action Arrays\n\n```svelte\n<script lang=\"ts\">\n\timport { PageShell, type PageShellAction } from 'entasis/page-shell';\n\timport { arrowClockwiseIcon } from 'entasis/icons/arrowClockwise';\n\n\tconst headerActions = [\n\t\t{\n\t\t\tlabel: 'Refresh',\n\t\t\tsquared: true,\n\t\t\tvariant: 'outline',\n\t\t\tprefix: arrowClockwiseIcon\n\t\t},\n\t\t{\n\t\t\tcontent: 'Create report',\n\t\t\tcolor: 'primary'\n\t\t}\n\t] satisfies PageShellAction[];\n</script>\n\n<PageShell title=\"Reports\" {headerActions}>\n\t{#snippet children()}\n\t\tPage content\n\t{/snippet}\n</PageShell>\n```\n\n## Content Presets And Footer Actions\n\n```svelte\n<PageShell\n\teyebrow=\"Settings\"\n\ttitle=\"Billing profile\"\n\tcontentPadding=\"normal\"\n\tcontentWidth=\"narrow\"\n\tfooterActions={[\n\t\t{ content: 'Cancel', variant: 'outline' },\n\t\t{ content: 'Save changes', color: 'primary' }\n\t]}\n>\n\t{#snippet footer()}\n\t\t<span>2 unsaved changes</span>\n\t{/snippet}\n\n\t{#snippet children()}\n\t\t<form>...</form>\n\t{/snippet}\n</PageShell>\n```\n\n## Accessibility\n\nPageShell renders semantic `header`, `main`, and `footer` regions. The title is an\n`h1` when provided as a string. Custom snippets are responsible for preserving\nequivalent semantics when replacing the default header.\n";
28
- readonly sidebar: "\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- **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 — '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 } }}>` → `setSidebarTheme({ motion })` →\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";
28
+ readonly sidebar: "\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 — '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 } }}>` → `setSidebarTheme({ motion })` →\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";
29
29
  readonly button: "\n# Button Component\n\nThe Button component is a flexible and customizable button element that supports various variants, sizes, colors, and interactive states.\n\n## Basic Usage\n\n```svelte\n<Button>Click me</Button>\n<Button variant=\"outline\">Outline Button</Button>\n<Button color=\"primary\" size=\"large\">Large Primary Button</Button>\n```\n\n## Props\n\n### Core Props\n- **variant**: 'solid' | 'outline' | 'soft' | 'ghost' | 'link' (default: 'solid')\n - solid: Filled background with color\n - outline: Transparent background with colored border\n - soft: Muted color background\n - ghost: Transparent background with a transient state layer on hover and press\n - link: Text-only styling with underline on hover\n\n- **color**: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' (default: 'neutral')\n - Determines the color scheme of the button\n\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - small: 24px height, smaller padding and text\n - normal: 32px height, standard padding\n - large: 36px height, larger padding and text\n\n### Layout Props\n- **fullWidth**: boolean (default: false) - Makes button take full width of container\n- **squared**: boolean - Makes button square (aspect-ratio 1:1), auto-determined if only prefix/suffix is provided\n- **disabled**: boolean (default: false) - Disables button interaction\n- **loading**: boolean (default: false) - Shows the Theme-configured loading spinner and disables interaction\n\n### State Props\nDescribe the meaning; the Button writes the ARIA. Never pass an aria-* attribute to a Button.\n- **pressed**: boolean - Toggle state of a button that stays on or off (a bold button in a toolbar, a \"show password\" eye). Rendered as aria-pressed\n- **selected**: boolean - Chosen state of a button acting as one option among several (a tab, a listbox option). Rendered as aria-selected\n- **expanded**: boolean - Whether the surface this button opens is showing. Rendered as aria-expanded. A entasis surface (Popover, PopupMenu, Select, Combobox) sets this on its own trigger, so pass it only for a surface you open yourself\n- **haspopup**: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | true - What the surface this button opens contains. Rendered as aria-haspopup, and likewise set by a entasis surface on its own trigger\n\n### Link Props\n- **href**: string - Makes button render as anchor tag\n- **target**: string - Link target (e.g., \"_blank\")\n- **rel**: string - Link relationship\n\n### Event Props\n- **onclick**: (event: MouseEvent) => void - Native click event handler\n- **onpointerenter**: (event: PointerEvent) => void - Native pointer enter event handler\n- **onpointerleave**: (event: PointerEvent) => void - Native pointer leave event handler\n\n### Content Props (Slots)\n- **children**: Snippet - Main button content\n- **prefix**: Snippet - Content before main text (typically icons)\n- **suffix**: Snippet - Content after main text (typically icons)\n\n### Advanced Props\n- **label**: string - Accessible label applied as aria-label on the root element (required for icon-only buttons)\n- **ref**: HTMLElement - Reference to the button element\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\nThe button follows this DOM structure:\n```\n<Button>\n\t<Prefix /> <!-- Optional prefix content -->\n\t<Children /> <!-- Main button content -->\n\t<Suffix /> <!-- Optional suffix content -->\n</Button>\n```\n\n## Examples\n\n### Basic Buttons\n```svelte\n<Button>Default Button</Button>\n<Button variant=\"outline\" color=\"primary\">Primary Outline</Button>\n<Button variant=\"soft\" color=\"danger\">Soft Danger</Button>\n<Button variant=\"ghost\">Ghost Button</Button>\n<Button variant=\"link\">Link Button</Button>\n```\n\n### With Icons\n```svelte\n<Button>\n\t{#snippet prefix()}\n\t\t{@render icon()}\n\t{/snippet}\n\tAdd Item\n</Button>\n\n<Button squared>\n\t{#snippet prefix()}\n\t\t{@render icon()}\n\t{/snippet}\n</Button>\n```\n\n### Interactive States\n```svelte\n<Button loading>Loading...</Button>\n<Button disabled>Disabled</Button>\n<Button fullWidth>Full Width Button</Button>\n```\n\n### As Link\n```svelte\n<Button href=\"/dashboard\" target=\"_blank\">Go to Dashboard</Button>\n```\n\n### With Event Handlers\n```svelte\n<script lang=\"ts\">\n\tfunction handleClick(event: MouseEvent) {\n\t\tconsole.log('Clicked:', event.currentTarget);\n\t}\n</script>\n\n<Button onclick={handleClick}>\n\tClick\n</Button>\n```\n\n### Custom Styling\n```svelte\n<Button class=\"lift-4 border-2\" color=\"primary\" variant=\"outline\">\n\tCustom Styled\n</Button>\n```\n\n## Accessibility\n\n- Automatically sets appropriate ARIA roles (button/link)\n- Supports keyboard navigation\n- Disabled state prevents interaction\n- Loading state provides visual feedback\n\n## Notes\n\n- When `href` is provided, renders as `<a>` tag, otherwise `<button>`\n- `squared` is automatically determined when only prefix or suffix is provided without children\n- All event handlers respect disabled state\n- Icon sizing is automatically adjusted based on button size\n\n## Theme Customization\n\nThe Button component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **root**: Main button container styles\n- **prefix**: Styles for prefix content (icons before text)\n- **suffix**: Styles for suffix content (icons after text)\n\n### Theme Type Definition\n\n```typescript\nimport type { ButtonThemeProps } from 'entasis/button';\n\n// Example theme customization\nconst customTheme: ButtonThemeProps = {\n root: {\n base: 'custom-base-classes',\n size: {\n small: 'custom-small-classes',\n normal: 'custom-normal-classes',\n large: 'custom-large-classes'\n },\n color: {\n primary: 'bg-blue-500 text-white',\n danger: 'bg-red-500 text-white'\n },\n variant: {\n solid: 'bg-color text-color-contrast',\n outline: 'border-2 border-color'\n }\n },\n prefix: {\n size: {\n small: 'w-3 h-3',\n normal: 'w-4 h-4',\n large: 'w-5 h-5'\n }\n }\n};\n```\n\n### Available Variants\n\n**root**:\n- base: Base classes applied to all buttons\n- Variants:\n - size: 'small' | 'normal' | 'large' - Controls padding, text size, and height\n - color: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' - Color scheme\n - variant: 'solid' | 'outline' | 'soft' | 'ghost' | 'link' - Visual style variant\n - loading: boolean - Loading state styling\n - disabled: boolean - Disabled state styling\n - squared: boolean - Square button styling\n - fullWidth: boolean - Full width styling\n\n**prefix**:\n- base: Base classes for prefix content\n- Variants:\n - size: 'small' | 'normal' | 'large' - Icon size based on button size\n\n**suffix**:\n- base: Base classes for suffix content\n- Variants:\n - size: 'small' | 'normal' | 'large' - Icon size based on button size\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<Button \n theme={{\n root: {\n base: 'rounded-full lift-4',\n size: {\n large: 'px-8 py-4 text-xl'\n }\n }\n }}\n>\n Custom Styled Button\n</Button>\n```\n\n**Color Variant Customization**:\n```svelte\n<Button \n color=\"primary\"\n theme={{\n root: {\n color: {\n primary: 'state-layer bg-gradient-to-r from-blue-500 to-purple-500'\n }\n }\n }}\n>\n Gradient Button\n</Button>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setButtonTheme } from '../components/Button/index.ts';\n \n setButtonTheme({\n root: {\n variant: {\n solid: 'state-layer bg-color text-color-contrast lift-3 hover:lift-4 transition-shadow',\n outline: 'state-layer border-2 border-color'\n }\n },\n prefix: {\n size: {\n normal: 'w-5 h-5'\n }\n }\n });\n</script>\n```\n";
30
30
  readonly 'button-group': "\n# ButtonGroup Component\n\nThe ButtonGroup component displays a collection of related buttons as a cohesive group with shared styling properties.\n\n## Basic Usage\n\n```svelte\n<ButtonGroup \n\titems={[\n\t\t{ children: 'First' },\n\t\t{ children: 'Second' },\n\t\t{ children: 'Third' }\n\t]}\n/>\n```\n\n## Props\n\n### Core Props\n- **items**: Array<ButtonProps> (required) - Array of button configurations\n - Each button can have all standard Button component props\n\n### Shared Button Props\n- **size**: 'small' | 'normal' | 'large' - Applied to all buttons in the group\n- **color**: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' - Shared color for all buttons\n- **variant**: 'solid' | 'outline' | 'soft' | 'ghost' | 'link' - Shared variant for all buttons\n- **disabled**: boolean - Disables all buttons in the group\n\n### Styling Props\n- **class**: string - Additional CSS classes for the group container\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<ButtonGroup>\n\t<Button />\n\t<Button />\n\t<Button />\n</ButtonGroup>\n```\n\n## Examples\n\n### Basic Button Group\n```svelte\n<ButtonGroup \n\titems={[\n\t\t{ children: 'Left' },\n\t\t{ children: 'Center' },\n\t\t{ children: 'Right' }\n\t]}\n/>\n```\n\n### With Shared Styling\n```svelte\n<ButtonGroup \n\tsize=\"large\"\n\tcolor=\"primary\"\n\tvariant=\"outline\"\n\titems={[\n\t\t{ children: 'Option 1' },\n\t\t{ children: 'Option 2' },\n\t\t{ children: 'Option 3' }\n\t]}\n/>\n```\n\n### With Icons\n```svelte\n<script lang=\"ts\">\n\timport { ButtonGroup } from 'entasis/button-group';\n\timport { textAlignLeftIcon } from 'entasis/icons/textAlignLeft';\n\timport { textAlignCenterIcon } from 'entasis/icons/textAlignCenter';\n\timport { textAlignRightIcon } from 'entasis/icons/textAlignRight';\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tprefix: textAlignLeftIcon,\n\t\t\tchildren: 'Left' \n\t\t},\n\t\t{ \n\t\t\tprefix: textAlignCenterIcon,\n\t\t\tchildren: 'Center' \n\t\t},\n\t\t{ \n\t\t\tprefix: textAlignRightIcon,\n\t\t\tchildren: 'Right' \n\t\t}\n\t]}\n/>\n```\n\n### With Individual Click Handlers\n```svelte\n<script>\n\tfunction handleOption(option) {\n\t\tconsole.log(`Selected: ${option}`);\n\t}\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tchildren: 'Save',\n\t\t\tonclick: () => handleOption('save')\n\t\t},\n\t\t{ \n\t\t\tchildren: 'Cancel',\n\t\t\tonclick: () => handleOption('cancel')\n\t\t}\n\t]}\n/>\n```\n\n### Disabled Group\n```svelte\n<ButtonGroup \n\tdisabled\n\titems={[\n\t\t{ children: 'Option 1' },\n\t\t{ children: 'Option 2' }\n\t]}\n/>\n```\n\n### Icon Only Buttons\n```svelte\n<script lang=\"ts\">\n\timport { ButtonGroup } from 'entasis/button-group';\n\timport { textBIcon } from 'entasis/icons/textB';\n\timport { textItalicIcon } from 'entasis/icons/textItalic';\n\timport { textUnderlineIcon } from 'entasis/icons/textUnderline';\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tsquared: true,\n\t\t\tprefix: textBIcon\n\t\t},\n\t\t{ \n\t\t\tsquared: true,\n\t\t\tprefix: textItalicIcon\n\t\t},\n\t\t{ \n\t\t\tsquared: true,\n\t\t\tprefix: textUnderlineIcon\n\t\t}\n\t]}\n/>\n```\n\n### Mixed Button States\n```svelte\n<ButtonGroup \n\tvariant=\"outline\"\n\titems={[\n\t\t{ children: 'Active', color: 'primary' },\n\t\t{ children: 'Default', color: 'neutral' },\n\t\t{ children: 'Disabled', disabled: true }\n\t]}\n/>\n```\n\n### Segmented Control\n```svelte\n<script>\n\tlet selected = $state('week');\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tchildren: 'Day',\n\t\t\tvariant: selected === 'day' ? 'solid' : 'ghost',\n\t\t\tonclick: () => selected = 'day'\n\t\t},\n\t\t{ \n\t\t\tchildren: 'Week',\n\t\t\tvariant: selected === 'week' ? 'solid' : 'ghost',\n\t\t\tonclick: () => selected = 'week'\n\t\t},\n\t\t{ \n\t\t\tchildren: 'Month',\n\t\t\tvariant: selected === 'month' ? 'solid' : 'ghost',\n\t\t\tonclick: () => selected = 'month'\n\t\t}\n\t]}\n/>\n```\n\n## Styling\n\nButtonGroup automatically:\n- Removes border-radius from middle buttons\n- Adjusts borders to prevent double borders\n- Creates a cohesive, connected appearance\n- Maintains consistent spacing\n\n## Accessibility\n\n- Each button maintains full keyboard accessibility\n- Focus styles are preserved\n- Disabled state cascades properly\n- Screen readers announce each button individually\n\n## Notes\n\n- Individual button props override shared props\n- Buttons are rendered in the order provided\n- The group container can be styled with the `class` prop\n- All Button component features are supported for individual buttons\n\n## Theme Customization\n\nThe ButtonGroup component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **root**: Main button group container styles\n\n### Available Variants\n\n**root**:\n- base: Base classes for button group container (handles border radius and border connections between buttons)\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<ButtonGroup \n items={buttons}\n theme={{\n root: {\n base: 'flex items-center rounded-lg overflow-hidden'\n }\n }}\n/>\n```\n\n**Custom Group Styling**:\n```svelte\n<ButtonGroup \n items={buttons}\n theme={{\n root: {\n base: 'flex items-center gap-0 border-2 border-primary rounded-lg overflow-hidden'\n }\n }}\n/>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setButtonGroupTheme } from '../components/ButtonGroup/index.ts';\n \n setButtonGroupTheme({\n root: {\n base: 'flex items-center first-child:rounded-r-none last-child:rounded-l-none'\n }\n });\n</script>\n```\n";
31
31
  readonly 'segmented-control': "\n# SegmentedControl\n\nA compact single-selection input for switching between a small set of mutually exclusive modes. It uses radiogroup/radio semantics and an animated indicator that slides and resizes to the selected item.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n import { SegmentedControl } from '../components/SegmentedControl/index.ts';\n import { gridFourIcon } from 'entasis/icons/gridFour';\n import { listIcon } from 'entasis/icons/list';\n\n const items = [\n { value: 'grid', label: 'Grid', icon: gridFourIcon },\n { value: 'list', label: 'List', icon: listIcon }\n ] as const;\n\n let value = $state<(typeof items)[number]['value']>('grid');\n</script>\n\n<SegmentedControl {items} bind:value />\n```\n\n## Props\n\n- **items**: `readonly SegmentedControlItem[]` — required options with unique `value` fields.\n- **value**: `string` — bindable selected value; defaults to the first enabled item.\n- **defaultValue**: `string` — initial selected value when value is omitted.\n- **onValueChange**: `(value: string) => void` — called once after user interaction changes the value.\n- **item**: `Snippet<[SegmentedControlItem]>` — replaces the default item renderer.\n- **size**: `'small' | 'normal' | 'large'` — defaults to `'normal'`.\n- **color**: semantic color — controls the selected pill and focus ring; defaults to `'neutral'`.\n- **variant**: `'normal' | 'pill'` — controls corner radius; defaults to the moderately rounded `'normal'` shape.\n- **disabled**: `boolean` — disables the full control.\n- **label**: `string` — accessible name for the radiogroup; defaults to the catalog's \"Segmented control\".\n- **class**: additional root classes.\n- **theme**: component theme overrides.\n\n## Item shape\n\n```ts\ntype SegmentedControlItem<Value extends string = string> = {\n value: Value;\n label?: string | Snippet;\n icon?: string | Snippet;\n disabled?: boolean;\n};\n```\n\nThe default renderer displays `icon`, then `label`. `label` is also the segment's accessible name: a string is painted and spoken, a snippet names the segment through its own content, and an icon-only segment (no `label`) falls back to its `value`.\n\n## Custom item renderer\n\n```svelte\n<SegmentedControl {items} bind:value>\n {#snippet item(option)}\n <span>{option.label}</span>\n <span>{option.count}</span>\n {/snippet}\n</SegmentedControl>\n```\n\nExtra fields on item objects remain available to the snippet through generic inference.\n\n## Shapes\n\n`variant=\"normal\"` uses a moderately rounded track and segments. Use `variant=\"pill\"` for the fully rounded stadium shape.\n\n```svelte\n<SegmentedControl {items} bind:value />\n<SegmentedControl {items} bind:value variant=\"pill\" />\n```\n\n## Keyboard behavior\n\n- Tab enters on the selected item, or the first enabled item when no value matches.\n- Arrow Left/Right moves and selects, wrapping at the ends.\n- Home/End selects the first/last enabled item.\n- Disabled items are skipped.\n\nEach item has an invisible, size-aware pointer target that extends beyond the visual track: 36px for small, 44px for normal, and 48px for large. Adjacent targets meet at the midpoint of the configured gap instead of overlapping.\n\nUse SegmentedControl for mode or value selection. Use Tabbar when changing navigational views with tab semantics.\n";
@@ -131,7 +131,7 @@ export declare const componentMcpRegistry: {
131
131
  readonly theme: "\n# Theme\n\n`Theme` owns global theme selection, runtime design tokens, shared overlay state, and theme\ntransitions. Wrap the application once and use the `ThemeState` received by the children snippet.\n\n## Runtime design tokens\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme, type ThemeDesignTokenMap } from 'entasis/theme';\n\n\tlet spacing = $state<'small' | 'normal' | 'large'>('normal');\n\tconst designTokens = $derived({\n\t\tlight: {\n\t\t\tspacing,\n\t\t\tspacingScale: { xs: 1, sm: 1.5, md: 2, lg: 3, xl: 4 },\n\t\t\tradius: 'normal',\n\t\t\ttypeScale: 'default',\n\t\t\traisedWithBorder: true,\n\t\t\tdefaultColor: 'neutral'\n\t\t},\n\t\tdark: {\n\t\t\tspacing,\n\t\t\tspacingScale: { xs: 1, sm: 1.5, md: 2, lg: 3, xl: 4 },\n\t\t\tradius: 'small',\n\t\t\ttypeScale: 'compact',\n\t\t\traisedWithBorder: false,\n\t\t\tdefaultColor: 'neutral'\n\t\t}\n\t} satisfies ThemeDesignTokenMap<readonly ['light', 'dark']>);\n</script>\n\n<Theme {designTokens} transition=\"radial-top-right\">\n\t{#snippet children(theme)}\n\t\t<button onclick={() => (spacing = spacing === 'small' ? 'large' : 'small')}>\n\t\t\tChange density\n\t\t</button>\n\t\t<button onclick={() => (theme.theme = theme.resolvedTheme === 'dark' ? 'light' : 'dark')}>\n\t\t\tToggle color scheme\n\t\t</button>\n\t{/snippet}\n</Theme>\n```\n\n`designTokens` is keyed by logical theme name and respects the `attribute` and `value` props.\nEleven presets ship as `themePresets` (`dense`, `compact`, `balanced`, `comfortable`, `spacious`,\n`sharp`, `rounded`, `display`, `editorial`, `glass`, `terminal`): `designTokens={{ light: themePresets.glass.tokens, dark: themePresets.glass.tokens }}`.\nChanging the controlled object updates already-rendered Tailwind utilities without rebuilding CSS.\n\n### ThemeDesignTokens\n\n- `spacing`: `'small' | 'normal' | 'large' | number`. Globally scales density.\n- `spacingScale`: partial overrides for the strictly increasing `xs`, `sm`, `md`, `lg`,\n and `xl` spacing multipliers. Defaults to 1/1.5/2/3/4.\n- `radius`: `'none' | 'subtile' | 'small' | 'normal' | 'large' | 'round' | number`.\n- `typeScale`: `'compact' | 'default' | 'comfortable' | 'large' | TypeScaleOptions`.\n- `raisedWithBorder`: toggles the border used by `raised-*` utilities.\n- `defaultColor`: `Colors` role kit chrome inherits when a control omits `color`.\n Defaults to `neutral`. Set `primary` to restore an accent-colored kit. Compiles the\n current-color family (`--color`, `--color-readable`, muted/contrast/light/dark variants)\n and `--default-color` onto the theme selector. Do not set `data-color` on `html`.\n- `focusColor`, `selectedColor`, `hoverColor`, `pressedColor`: the four `Colors` **state\n roles**. They pin, for the whole theme, what a focus ring, a persistent selection and the\n transient hover/pressed layer look like, independently of the role of the control the state\n lands on. They compile `--color-focus`, `--color-selected` (plus its `-contrast`,\n `-readable` and `-muted-readable` companions), `--color-hover` and\n `--color-pressed` onto the theme selector. There is no `--color-selected-muted`: the soft\n fill is a translucent tint of `--color-selected` at `--state-selected-opacity`, so it reads\n on any surface. None is declared at `:root`: every use site\n falls back to the matching current role (`ring-focus` is\n `var(--color-focus, var(--color))`, `bg-selected-muted` tints\n `var(--color-selected, var(--color))`, the state layer is\n `var(--color-hover, currentColor)` and on `:active`\n `var(--color-pressed, var(--color-hover, currentColor))`), so leaving them unset changes\n nothing and `data-color` keeps moving the states with `--color`. Theme-level only: there is\n no per-component override. An unknown role throws.\n\nComponent-level density remains a local variant. It selects utility classes whose values inherit\nthe active global spacing token.\n\nGenerated interfaces should use the public `xs | sm | md | lg | xl` vocabulary through component\nprops and named gap/padding utilities. `micro` and `layout-*` are internal recipe tokens. Prefer\nparent-owned gaps over child margins; do not emit arbitrary spacing or unsupported radius values.\n\n## Theme selection\n\nThe selection props wrap `svelte-themes`: `themes`, `defaultTheme`, `forcedTheme`,\n`systemTheme`, `syncColorScheme`, `transitionOnChange`, `storageKey`, `attribute`,\n`value`, and `colorScheme`. The default themes are light and dark, with system selection\nenabled. `systemTheme`, `syncColorScheme`, and `transitionOnChange` all default to\n`true`; they map onto the library's `enableSystem`, `enableColorScheme`, and\n`disableTransitionOnChange` options.\n\n`ThemeState` exposes `theme`, `resolvedTheme`, `themes`, and `systemTheme`. Assign\n`theme.theme` to switch themes. The optional `transition` prop applies a named view transition;\nunsupported browsers and reduced-motion users switch instantly.\n\n`spinnerVariant` sets the global default spinner animation. The children snippet is required.\n\n## Motion tokens\n\nMotion is a token scale like spacing and radius: five duration steps and four easing roles.\n`motion` retunes them app-wide; an omitted token keeps its default.\n\n| Duration | Default | | Easing role | Default |\n| ---------- | ------- | --- | ------------ | ------------- |\n| `instant` | 0ms | | `standard` | `cubicInOut` |\n| `fast` | 100ms | | `enter` | `cubicOut` |\n| `normal` | 200ms | | `exit` | `cubicIn` |\n| `slow` | 300ms | | `emphasized` | `backOut` |\n| `slower` | 500ms | | | |\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme motion={{ duration: { normal: 150, slow: 260 }, easing: { standard: 'quintOut' } }}>\n\t{@render children()}\n</Theme>\n```\n\nThe Tailwind plugin emits the same scale as CSS variables on `html` (`--duration-normal`,\n`--ease-standard`, ...) plus the matching `duration-*` / `ease-*` utilities, so CSS transitions\nand Svelte transitions read one set of numbers. The `motion` prop rewrites those variables on\n`html` at runtime and `designTokens.motion` rewrites them again per theme, layered over the prop;\n`ThemeState.motion` resolves through the same two rungs, so the utilities and the presets never\ndisagree. `ThemeState.transition` is a deprecated alias for its `normal` duration and `standard`\neasing. Reduced motion resolves every duration to 0 and collapses the `--duration-*` variables via\nthe `data-entasis-reduce-motion` attribute on `html`.\n\nComponents keep their own transition in a reserved `motion` slot on their theme, so the `theme`\nprop covers motion as well as classes:\n\n```svelte\n<script lang=\"ts\">\n\timport { Dialog } from 'entasis/dialog';\n</script>\n\n<Dialog theme={{ motion: { duration: 'fast', easing: 'emphasized' } }} title=\"Quick\">Body</Dialog>\n```\n\n## Component theme registry\n\n`components` sets app-wide component theme defaults without a wrapper component per component:\nit is keyed by theme name (`dialog`, `button`, ...) and each entry takes the same slots as that\ncomponent's `theme` prop, the `motion` slot included. A `set<Component>Theme` call in a subtree\nbeats the registry, and an instance `theme` prop beats both — per slot: each rung layers on the one\nbelow it, so a subtree that restyles one slot keeps the registry's others.\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme\n\tcomponents={{\n\t\tdialog: { motion: { duration: 'fast' }, content: { base: 'rounded-2xl' } },\n\t\tbutton: { root: { base: 'tracking-wide' } }\n\t}}\n>\n\t{@render children()}\n</Theme>\n```\n\n## Reduced motion\n\n`reduceMotion` forces reduced motion on (`true`) or off (`false`) for every entasis animation,\noverriding the OS `prefers-reduced-motion` setting; omit it to follow the OS. The live result is\nexposed as `ThemeState.preferReducesMotion` (reactive, so it updates when the OS setting changes)\nand mirrored as a `data-entasis-reduce-motion` attribute on `html` for CSS-only animations.\n\n```svelte\n<script>\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme reduceMotion>{@render children()}</Theme>\n```\n\n## Build-time boundary\n\nThe Tailwind plugin still generates color palettes and registers utility names, variants,\nkeyframes, and spinner CSS. Spacing, radius, typography scale, raised borders,\n`defaultColor` and the four state roles (`focusColor`, `selectedColor`, `hoverColor`,\n`pressedColor`) belong to `Theme.designTokens`; colors remain CSS variables and can be\noverridden directly. `ThemeState.defaultColor` exposes the active role. Kit chrome should\nresolve omitted `color` props with `useDefaultColor`.\n";
132
132
  readonly i18n: "\n# Internationalization\n\nImport from `entasis/i18n`.\n\nI18n provides locale messages to child components. setI18n and useI18n set and read the component context. en is the English message set; Messages and I18nInput describe translation inputs. locales and localeList expose the supported locale inventory with LocaleCode and LocaleMeta types.\n";
133
133
  readonly 'tailwind-plugin': "\n# Main Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin'`\n\nThis palette-agnostic plugin registers shared utilities, variants, keyframes, and spinner CSS.\nUse it when colors are defined separately instead of through the theme plugin.\n\n## Configuration\n\n`spinner`\n- **Type**: `Spinner` object\n- **Default**: Auto-generated\n- **Description**: Custom spinner configuration for the `.ui-spinner` class\n\n## Shared utilities\n\n### `.state-layer`\n- Composites `currentColor` at `--state-hover-opacity` on hover and `data-highlighted=\"true\"`\n- Composites `currentColor` at `--state-pressed-opacity` on `:active`\n- Does not activate for disabled, `data-disabled`, or `aria-disabled=\"true\"` elements\n- Theme plugin defaults the opacities to 5% and 10% in light themes, and 16% and 32% in dark themes\n\n### `bg-selected-muted` / `rounded-<step>-concentric`\n- `bg-selected-muted` composites `var(--color-selected, var(--color))` at\n `--state-selected-opacity` (0.07 light, 0.10 dark), so a persistent selection reads the same on\n `surface`, `surface-raised` and `surface-floating`. `bg-color-muted` stays opaque.\n- `rounded-<step>-concentric` (also `rounded-t-<step>-concentric` /\n `rounded-b-<step>-concentric`, with `<step>` one of `xs sm md lg xl 2xl 3xl 4xl`) keeps the\n child on its own design step but caps it at what concentricity allows inside a rounded padded\n container:\n `min(var(--radius-<step>), var(--radius-parent) - max(--pad-parent-x, --pad-parent-y))`. The\n container declares nothing: every `rounded-<step>` publishes `--radius-parent` to its\n children and `p` / `px` / `py` publish `--pad-parent-x/-y`, so the two boxes stay\n concentric at every `radius` preset. Outside any rounded container the parent radius is\n infinite, so the child is exactly its step; a negative difference clamps to 0, a square corner.\n Put it on a child that sits flush against the padding box; a floating child (an avatar, a\n Button, a Chip) keeps its own radius.\n\nConfigure spacing, radius, typography scale, and raised borders at runtime through\n`Theme.designTokens`.\n\nSemantic spacing utilities use the active theme's `xs`, `sm`, `md`, `lg`, and `xl`\nscale for gaps, padding, margins, and physical insets. For example, `gap-lg`, `p-lg`,\n`top-lg`, and `left-lg` use the same spacing value. Inset utilities support `top`,\n`right`, `bottom`, and `left`, including responsive variants.\n";
134
- readonly 'theme-tailwind-plugin': "\n# Theme Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin/theme'` generates color variables for each named theme. The declaration\nmarked `default: true` also registers the shared utility vocabulary, variants, spinner styles,\nand keyframes.\n\n```css\n@import 'tailwindcss';\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: light;\n\tdefault: true;\n\tcolorscheme: light;\n\tprimary: #5f62ef;\n\tsecondary: #e4e4e7;\n\tsurface: #fafafa;\n\tneutral: #18181b;\n}\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: dark;\n\tcolorscheme: dark;\n\tprimary: #5f62ef;\n\tsecondary: #27272a;\n\tsurface: #09090b;\n\tneutral: #fafafa;\n}\n```\n\n## Identity\n\n- `name: string` scopes variables to `html[data-theme=\"<name>\"]` and `.<name>`.\n- `default: boolean` also applies the palette to bare `html` and installs the shared engine.\n- `colorscheme: 'light' | 'dark'` controls mode-aware color defaults.\n- `prefersDark: boolean` also emits the palette under the dark system media query.\n\n## Palette inputs\n\nBase semantic colors are `primary`, `secondary`, `danger`, `success`, `warning`, `info`,\nand `neutral`. `surface` seeds the elevation ladder: `surface-recessed`,\n`surface-canvas`, `surface`, `surface-raised`, and `surface-floating`.\n\nEach semantic color supports explicit `-light`, `-lighter`, `-dark`, `-muted`,\n`-contrast`, `-readable`, and `-muted-readable` overrides. Missing variants are generated.\n`luminance` and `saturation` adjust the generated palette.\n\n`state-hover-opacity` and `state-pressed-opacity` calibrate the CSS variables consumed by\n`.state-layer`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode.\n`state-selected-opacity` (0.07 light, 0.10 dark) is the alpha `bg-selected-muted` composites the\nselected role at: the soft selection fill is a translucent tint, not an opaque colour, so it reads\nthe same on `surface`, `surface-raised` and `surface-floating`.\n\n## Runtime boundary\n\nSpacing, radius, typography scale, raised borders, `defaultColor` and the four state roles\n(`focusColor`, `selectedColor`, `hoverColor`, `pressedColor`) are not plugin options.\nConfigure them with the `designTokens` prop on `Theme`. Tailwind still discovers and compiles\nthe finite utility names; runtime theming changes the CSS variables those utilities consume.\n\nThe public spacing vocabulary is `xs | sm | md | lg | xl`, available through named gap, padding,\nand margin utilities such as `gap-md` and `px-lg`. The `micro` and `layout-*` values are\ninternal component-recipe tokens. Generated interfaces should prefer Stack/Grid gaps and must not\nemit arbitrary spacing or unsupported radius utilities.\n\nColor variables can also be overridden directly at runtime:\n\n```css\nhtml[data-theme='light'] {\n\t--color-primary: oklab(0.21 0.01 -0.03);\n}\n```\n";
134
+ readonly 'theme-tailwind-plugin': "\n# Theme Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin/theme'` generates color variables for each named theme. The declaration\nmarked `default: true` also registers the shared utility vocabulary, variants, spinner styles,\nand keyframes.\n\n```css\n@import 'tailwindcss';\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: light;\n\tdefault: true;\n\tcolorscheme: light;\n\tprimary: #5f62ef;\n\tsecondary: #e4e4e7;\n\tsurface: #fafafa;\n\tneutral: #18181b;\n}\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: dark;\n\tcolorscheme: dark;\n\tprimary: #5f62ef;\n\tsecondary: #27272a;\n\tsurface: #09090b;\n\tneutral: #fafafa;\n}\n```\n\n## Identity\n\n- `name: string` scopes variables to `html[data-theme=\"<name>\"]` and `.<name>`.\n- `default: boolean` also applies the palette to bare `html` and installs the shared engine.\n- `colorscheme: 'light' | 'dark'` controls mode-aware color defaults.\n- `prefersDark: boolean` also emits the palette under the dark system media query.\n\n## Palette inputs\n\nBase semantic colors are `primary`, `secondary`, `danger`, `success`, `warning`, `info`,\nand `neutral`. `surface` seeds the elevation ladder: `surface-recessed`,\n`surface-canvas`, `surface`, `surface-raised`, and `surface-floating`.\n\nEach semantic color supports explicit `-light`, `-lighter`, `-dark`, `-muted`,\n`-contrast`, `-readable`, and `-muted-readable` overrides. Missing variants are generated.\n`luminance` and `saturation` adjust the generated palette.\n\n`state-hover-opacity` and `state-pressed-opacity` calibrate the CSS variables consumed by\n`.state-layer`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode. `state-layer-none` switches that overlay off on one element.\n`state-selected-opacity` (0.07 light, 0.10 dark) is the alpha `bg-selected-muted` composites the\nselected role at: the soft selection fill is a translucent tint, not an opaque colour, so it reads\nthe same on `surface`, `surface-raised` and `surface-floating`.\n\n## Runtime boundary\n\nSpacing, radius, typography scale, raised borders, `defaultColor` and the four state roles\n(`focusColor`, `selectedColor`, `hoverColor`, `pressedColor`) are not plugin options.\nConfigure them with the `designTokens` prop on `Theme`. Tailwind still discovers and compiles\nthe finite utility names; runtime theming changes the CSS variables those utilities consume.\n\nThe public spacing vocabulary is `xs | sm | md | lg | xl`, available through named gap, padding,\nand margin utilities such as `gap-md` and `px-lg`. The `micro` and `layout-*` values are\ninternal component-recipe tokens. Generated interfaces should prefer Stack/Grid gaps and must not\nemit arbitrary spacing or unsupported radius utilities.\n\nColor variables can also be overridden directly at runtime:\n\n```css\nhtml[data-theme='light'] {\n\t--color-primary: oklab(0.21 0.01 -0.03);\n}\n```\n";
135
135
  readonly types: "\n# Shared theme types\n\nImport from `entasis/types`.\n\nColors names semantic palette roles. Sizes selects small, normal, or large component geometry. Density uses a separate compact/normal/comfortable scale for internal whitespace, so a density value can never be passed where a size is expected. The module also exports theme color paths, typography paths, transition easing, style types, and deepMerge for composing nested theme values.\n";
136
136
  readonly cva: "\n# Component variants\n\nImport from `entasis/cva`.\n\ncva defines class variants and defaults. cx joins class values; compose composes variants. setComponentTheme and useComponentTheme connect component theme definitions to the Svelte theme context. A resolver takes an optional second argument, the shared variant values, and then returns every class slot already bound to them, so a template calls `slots.root()` instead of passing the same props to each slot. VariantProps and InferComponentTheme derive the corresponding public types. Keep component CVA definitions beside their owner in a .theme.ts file.\n";
137
137
  readonly scheduling: "\n# Scheduling layout\n\nImport from `entasis/scheduling`.\n\npackSchedulingLanes and packSchedulingOverlaps compute placements for scheduling intervals. SchedulingInterval, SchedulingLaneInterval, the lane placement and layout types, and the overlap placement and layout types describe their inputs and results. EventCalendar and GanttChart share these layout algorithms.\n";
@@ -214,7 +214,10 @@ export const applyGlobalEngine = (api, options) => {
214
214
  'background-color': 'var(--color-pressed, var(--color-hover, currentColor))',
215
215
  opacity: 'var(--state-pressed-opacity)'
216
216
  }
217
- }
217
+ },
218
+ // The off switch for one element: the tint lives on the ::before, so a `hover:bg-*` on the
219
+ // element itself can never reach it.
220
+ '.state-layer-none': { '&::before': { display: 'none' } }
218
221
  });
219
222
  addShimmerUtilities(api);
220
223
  addScrollFadeUtilities(api);
@@ -1 +1 @@
1
- export declare const themePluginDescription = "\n# Theme Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin/theme'` generates color variables for each named theme. The declaration\nmarked `default: true` also registers the shared utility vocabulary, variants, spinner styles,\nand keyframes.\n\n```css\n@import 'tailwindcss';\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: light;\n\tdefault: true;\n\tcolorscheme: light;\n\tprimary: #5f62ef;\n\tsecondary: #e4e4e7;\n\tsurface: #fafafa;\n\tneutral: #18181b;\n}\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: dark;\n\tcolorscheme: dark;\n\tprimary: #5f62ef;\n\tsecondary: #27272a;\n\tsurface: #09090b;\n\tneutral: #fafafa;\n}\n```\n\n## Identity\n\n- `name: string` scopes variables to `html[data-theme=\"<name>\"]` and `.<name>`.\n- `default: boolean` also applies the palette to bare `html` and installs the shared engine.\n- `colorscheme: 'light' | 'dark'` controls mode-aware color defaults.\n- `prefersDark: boolean` also emits the palette under the dark system media query.\n\n## Palette inputs\n\nBase semantic colors are `primary`, `secondary`, `danger`, `success`, `warning`, `info`,\nand `neutral`. `surface` seeds the elevation ladder: `surface-recessed`,\n`surface-canvas`, `surface`, `surface-raised`, and `surface-floating`.\n\nEach semantic color supports explicit `-light`, `-lighter`, `-dark`, `-muted`,\n`-contrast`, `-readable`, and `-muted-readable` overrides. Missing variants are generated.\n`luminance` and `saturation` adjust the generated palette.\n\n`state-hover-opacity` and `state-pressed-opacity` calibrate the CSS variables consumed by\n`.state-layer`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode.\n`state-selected-opacity` (0.07 light, 0.10 dark) is the alpha `bg-selected-muted` composites the\nselected role at: the soft selection fill is a translucent tint, not an opaque colour, so it reads\nthe same on `surface`, `surface-raised` and `surface-floating`.\n\n## Runtime boundary\n\nSpacing, radius, typography scale, raised borders, `defaultColor` and the four state roles\n(`focusColor`, `selectedColor`, `hoverColor`, `pressedColor`) are not plugin options.\nConfigure them with the `designTokens` prop on `Theme`. Tailwind still discovers and compiles\nthe finite utility names; runtime theming changes the CSS variables those utilities consume.\n\nThe public spacing vocabulary is `xs | sm | md | lg | xl`, available through named gap, padding,\nand margin utilities such as `gap-md` and `px-lg`. The `micro` and `layout-*` values are\ninternal component-recipe tokens. Generated interfaces should prefer Stack/Grid gaps and must not\nemit arbitrary spacing or unsupported radius utilities.\n\nColor variables can also be overridden directly at runtime:\n\n```css\nhtml[data-theme='light'] {\n\t--color-primary: oklab(0.21 0.01 -0.03);\n}\n```\n";
1
+ export declare const themePluginDescription = "\n# Theme Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin/theme'` generates color variables for each named theme. The declaration\nmarked `default: true` also registers the shared utility vocabulary, variants, spinner styles,\nand keyframes.\n\n```css\n@import 'tailwindcss';\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: light;\n\tdefault: true;\n\tcolorscheme: light;\n\tprimary: #5f62ef;\n\tsecondary: #e4e4e7;\n\tsurface: #fafafa;\n\tneutral: #18181b;\n}\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: dark;\n\tcolorscheme: dark;\n\tprimary: #5f62ef;\n\tsecondary: #27272a;\n\tsurface: #09090b;\n\tneutral: #fafafa;\n}\n```\n\n## Identity\n\n- `name: string` scopes variables to `html[data-theme=\"<name>\"]` and `.<name>`.\n- `default: boolean` also applies the palette to bare `html` and installs the shared engine.\n- `colorscheme: 'light' | 'dark'` controls mode-aware color defaults.\n- `prefersDark: boolean` also emits the palette under the dark system media query.\n\n## Palette inputs\n\nBase semantic colors are `primary`, `secondary`, `danger`, `success`, `warning`, `info`,\nand `neutral`. `surface` seeds the elevation ladder: `surface-recessed`,\n`surface-canvas`, `surface`, `surface-raised`, and `surface-floating`.\n\nEach semantic color supports explicit `-light`, `-lighter`, `-dark`, `-muted`,\n`-contrast`, `-readable`, and `-muted-readable` overrides. Missing variants are generated.\n`luminance` and `saturation` adjust the generated palette.\n\n`state-hover-opacity` and `state-pressed-opacity` calibrate the CSS variables consumed by\n`.state-layer`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode. `state-layer-none` switches that overlay off on one element.\n`state-selected-opacity` (0.07 light, 0.10 dark) is the alpha `bg-selected-muted` composites the\nselected role at: the soft selection fill is a translucent tint, not an opaque colour, so it reads\nthe same on `surface`, `surface-raised` and `surface-floating`.\n\n## Runtime boundary\n\nSpacing, radius, typography scale, raised borders, `defaultColor` and the four state roles\n(`focusColor`, `selectedColor`, `hoverColor`, `pressedColor`) are not plugin options.\nConfigure them with the `designTokens` prop on `Theme`. Tailwind still discovers and compiles\nthe finite utility names; runtime theming changes the CSS variables those utilities consume.\n\nThe public spacing vocabulary is `xs | sm | md | lg | xl`, available through named gap, padding,\nand margin utilities such as `gap-md` and `px-lg`. The `micro` and `layout-*` values are\ninternal component-recipe tokens. Generated interfaces should prefer Stack/Grid gaps and must not\nemit arbitrary spacing or unsupported radius utilities.\n\nColor variables can also be overridden directly at runtime:\n\n```css\nhtml[data-theme='light'] {\n\t--color-primary: oklab(0.21 0.01 -0.03);\n}\n```\n";
@@ -46,7 +46,7 @@ Each semantic color supports explicit \`-light\`, \`-lighter\`, \`-dark\`, \`-mu
46
46
  \`luminance\` and \`saturation\` adjust the generated palette.
47
47
 
48
48
  \`state-hover-opacity\` and \`state-pressed-opacity\` calibrate the CSS variables consumed by
49
- \`.state-layer\`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode.
49
+ \`.state-layer\`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode. \`state-layer-none\` switches that overlay off on one element.
50
50
  \`state-selected-opacity\` (0.07 light, 0.10 dark) is the alpha \`bg-selected-muted\` composites the
51
51
  selected role at: the soft selection fill is a translucent tint, not an opaque colour, so it reads
52
52
  the same on \`surface\`, \`surface-raised\` and \`surface-floating\`.