@adea-ai/ui 0.87.0 → 0.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,6 +87,12 @@ updates, navigation, and other effects remain host callbacks.
87
87
 
88
88
  `SidebarNavTitle` accepts polymorphic `as` for a host's title heading.
89
89
  Use `SidebarNavSection.headingAs` (h1 through h6) for section headings.
90
+ Sections may be uncontrolled with `defaultOpen` or controlled with `open` and
91
+ `onOpenChange`. Put disclosure-button attributes and host-owned row interactions
92
+ in `triggerProps`; its `onClick` and `onKeyDown` run before shared behavior, and
93
+ `preventDefault()` cancels the corresponding toggle or Alt+Arrow reorder callback.
94
+ `triggerProps.onReorder` receives `up` or `down`; the host remains responsible for
95
+ updating its hierarchy and preserving stable row identity.
90
96
  Disclosure buttons stay inside their heading and trailing actions stay outside
91
97
  it, preserving hierarchy and independent keyboard actions.
92
98
 
@@ -1,3 +1,3 @@
1
- export { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass, type SidebarNavItemProps, type SidebarNavProps, type SidebarNavTitleProps, } from './sidebar-nav';
1
+ export { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass, type SidebarNavItemProps, type SidebarNavProps, type SidebarNavSectionProps, type SidebarNavSectionTriggerProps, type SidebarNavTitleProps, } from './sidebar-nav';
2
2
  export { SidebarNavResizeHandle, type SidebarNavResizeHandleProps, } from './sidebar-nav-resize-handle';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,6BAA6B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,sBAAsB,EACtB,KAAK,2BAA2B,GACjC,MAAM,6BAA6B,CAAA"}
@@ -30,23 +30,39 @@ export declare function SidebarNavTitle<T extends ValidComponent = 'div'>(props:
30
30
  /** The scrolling middle of the sidebar. */
31
31
  export declare function SidebarNavContent(props: ComponentProps<'div'>): JSX.Element;
32
32
  export declare function SidebarNavFooter(props: ComponentProps<'div'>): JSX.Element;
33
+ /** Props forwarded to a collapsible section's disclosure button. */
34
+ export type SidebarNavSectionTriggerProps = Omit<ComponentProps<'button'>, 'type' | 'aria-expanded' | 'onClick' | 'onKeyDown' | 'children' | 'class' | 'style'> & {
35
+ /** Called for Alt+ArrowUp/Down; the host owns ordering and row identity. */
36
+ onReorder?: (direction: 'up' | 'down') => void;
37
+ /** Runs before the section toggle and may cancel it with `preventDefault()`. */
38
+ onClick?: ComponentProps<'button'>['onClick'];
39
+ /** Runs before shared Alt+Arrow handling and may cancel it with `preventDefault()`. */
40
+ onKeyDown?: ComponentProps<'button'>['onKeyDown'];
41
+ };
33
42
  /**
34
43
  * A titled, collapsible group. Collapsible because a list of projects
35
44
  * inevitably outgrows the column, and the user needs to put the ones they are
36
45
  * not using away *without* them disappearing into a menu.
37
46
  */
38
- export declare function SidebarNavSection(props: ComponentProps<'div'> & {
47
+ export type SidebarNavSectionProps = ComponentProps<'div'> & {
39
48
  label: string;
40
49
  /** Render a disclosure control instead of a plain heading. */
41
50
  collapsible?: boolean;
42
51
  defaultOpen?: boolean;
52
+ /** Controlled disclosure state. When supplied, the host owns changes. */
53
+ open?: boolean;
54
+ /** Called for user-initiated disclosure changes. */
55
+ onOpenChange?: (open: boolean) => void;
56
+ /** Native props and reorder callbacks for the disclosure control. */
57
+ triggerProps?: SidebarNavSectionTriggerProps;
43
58
  /** Count shown beside the label. */
44
59
  count?: number;
45
60
  /** A control at the trailing edge of the heading row. */
46
61
  action?: JSX.Element;
47
62
  /** Semantic heading element for a host's section hierarchy. */
48
63
  headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
49
- }): JSX.Element;
64
+ };
65
+ export declare function SidebarNavSection(props: SidebarNavSectionProps): JSX.Element;
50
66
  export type SidebarNavItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<T, {
51
67
  class?: string;
52
68
  active?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar-nav.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/sidebar-nav.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAGjE,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAInE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,IAAI,gBAAgB,CAC9E,CAAC,EACD;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAC1C,CAAA;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,eAgBrF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAS5D;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,IAAI,gBAAgB,CACnF,CAAC,EACD;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CACnB,CAAA;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,cAAc,GAAG,KAAK,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,eAU/F;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAS7D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,eAS5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yDAAyD;IACzD,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;CACpD,eA6DF;AAED,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,cAAc,GAAG,GAAG,IAAI,gBAAgB,CAChF,CAAC,EACD;IACE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,CACF,CAAA;AAED,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,QAKrB,CAAA;AAEX,eAAO,MAAM,wBAAwB;;;CAGpC,CAAA;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,cAAc,GAAG,GAAG,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,eA4B3F;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,eAiB/D"}
1
+ {"version":3,"file":"sidebar-nav.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/sidebar-nav/sidebar-nav.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
- import { createComponent, delegateEvents, effect, insert, memo, mergeProps, setAttribute, spread, template } from "solid-js/web";
2
+ import { createComponent, insert, memo, mergeProps, spread, template } from "solid-js/web";
3
3
  import { ChevronRight } from "lucide-solid";
4
4
  import { Show, createSignal, splitProps } from "solid-js";
5
5
  import { Polymorphic } from "@kobalte/core/polymorphic";
@@ -9,12 +9,18 @@ var _tmpl$2 = /*#__PURE__*/ template(`<div data-slot=sidebar-nav-content>`);
9
9
  var _tmpl$3 = /*#__PURE__*/ template(`<div data-slot=sidebar-nav-footer>`);
10
10
  var _tmpl$4 = /*#__PURE__*/ template(`<span class="min-w-0 flex-1 truncate">`);
11
11
  var _tmpl$5 = /*#__PURE__*/ template(`<span class="shrink-0 tabular-nums text-muted-foreground">`);
12
- var _tmpl$6 = /*#__PURE__*/ template(`<button type=button class="flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle">`);
12
+ var _tmpl$6 = /*#__PURE__*/ template(`<button>`);
13
13
  var _tmpl$7 = /*#__PURE__*/ template(`<span class="shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100">`);
14
14
  var _tmpl$8 = /*#__PURE__*/ template(`<div class="flex flex-col gap-0.5">`);
15
15
  var _tmpl$9 = /*#__PURE__*/ template(`<div data-slot=sidebar-nav-section><div class="group/section-header flex items-center gap-1 rounded-md px-2 py-1.5 hover:bg-sidebar-accent/60">`);
16
16
  var _tmpl$0 = /*#__PURE__*/ template(`<span class="ms-auto flex shrink-0 items-center gap-1">`);
17
17
  var _tmpl$1 = /*#__PURE__*/ template(`<button type=button>`);
18
+ function invokeEventHandler(handler, event) {
19
+ if (Array.isArray(handler)) {
20
+ const [callback, data] = handler;
21
+ callback(data, event);
22
+ } else if (typeof handler === "function") handler(event);
23
+ }
18
24
  function SidebarNav(props) {
19
25
  const [local, rest] = splitProps(props, [
20
26
  "class",
@@ -74,23 +80,31 @@ function SidebarNavFooter(props) {
74
80
  return _el$3;
75
81
  })();
76
82
  }
77
- /**
78
- * A titled, collapsible group. Collapsible because a list of projects
79
- * inevitably outgrows the column, and the user needs to put the ones they are
80
- * not using away *without* them disappearing into a menu.
81
- */
82
83
  function SidebarNavSection(props) {
83
84
  const [local, rest] = splitProps(props, [
84
85
  "class",
85
86
  "label",
86
87
  "collapsible",
87
88
  "defaultOpen",
89
+ "open",
90
+ "onOpenChange",
91
+ "triggerProps",
88
92
  "count",
89
93
  "action",
90
94
  "headingAs",
91
95
  "children"
92
96
  ]);
93
- const [open, setOpen] = createSignal(local.defaultOpen ?? true);
97
+ const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? true);
98
+ const open = () => local.open ?? uncontrolledOpen();
99
+ const setOpen = (next) => {
100
+ if (local.open === void 0) setUncontrolledOpen(next);
101
+ local.onOpenChange?.(next);
102
+ };
103
+ const [trigger, triggerRest] = splitProps(local.triggerProps ?? {}, [
104
+ "onClick",
105
+ "onKeyDown",
106
+ "onReorder"
107
+ ]);
94
108
  const heading = [
95
109
  createComponent(Show, {
96
110
  get when() {
@@ -142,9 +156,27 @@ function SidebarNavSection(props) {
142
156
  fallback: heading,
143
157
  get children() {
144
158
  var _el$8 = _tmpl$6();
145
- _el$8.$$click = () => setOpen((value) => !value);
159
+ spread(_el$8, mergeProps(triggerRest, {
160
+ "type": "button",
161
+ get ["aria-expanded"]() {
162
+ return open();
163
+ },
164
+ "onClick": (event) => {
165
+ invokeEventHandler(trigger.onClick, event);
166
+ if (event.defaultPrevented) return;
167
+ setOpen(!open());
168
+ },
169
+ "onKeyDown": (event) => {
170
+ invokeEventHandler(trigger.onKeyDown, event);
171
+ if (event.defaultPrevented || !event.altKey) return;
172
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
173
+ event.preventDefault();
174
+ event.stopPropagation();
175
+ trigger.onReorder?.(event.key === "ArrowUp" ? "up" : "down");
176
+ },
177
+ "class": "flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
178
+ }), false, true);
146
179
  insert(_el$8, heading);
147
- effect(() => setAttribute(_el$8, "aria-expanded", open()));
148
180
  return _el$8;
149
181
  }
150
182
  });
@@ -228,7 +260,6 @@ function SidebarNavButton(props) {
228
260
  return _el$10;
229
261
  })();
230
262
  }
231
- delegateEvents(["click"]);
232
263
  //#endregion
233
264
  export { SidebarNav, SidebarNavButton, SidebarNavContent, SidebarNavFooter, SidebarNavHeader, SidebarNavItem, SidebarNavSection, SidebarNavTitle, sidebarNavItemClass, sidebarNavItemStateClass };
234
265
 
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar-nav.js","names":["PolymorphicProps","Polymorphic","ChevronRight","ComponentProps","JSX","ValidComponent","Show","createSignal","splitProps","cn","SidebarNavProps","T","class","SidebarNav","props","local","rest","_$createComponent","_$mergeProps","as","aria-label","children","SidebarNavHeader","_el$","_tmpl$","_$spread","SidebarNavTitleProps","SidebarNavTitle","SidebarNavContent","_el$2","_tmpl$2","SidebarNavFooter","_el$3","_tmpl$3","SidebarNavSection","label","collapsible","defaultOpen","count","action","Element","headingAs","open","setOpen","heading","when","_el$4","_tmpl$4","_$insert","undefined","_el$5","_tmpl$5","_el$6","_tmpl$9","_el$7","firstChild","_$memo","fallback","_el$8","_tmpl$6","$$click","value","_$effect","_$setAttribute","_el$9","_tmpl$7","_el$0","_tmpl$8","SidebarNavItemProps","active","nested","trailing","sidebarNavItemClass","join","sidebarNavItemStateClass","idle","SidebarNavItem","aria-current","data-active","_el$1","_tmpl$0","SidebarNavButton","_el$10","_tmpl$1","_$delegateEvents","_$createComponent","_$mergeProps","_$memo","_$setAttribute","_$delegateEvents"],"sources":["../../../../src/components/layout/sidebar-nav/sidebar-nav.tsx"],"sourcesContent":["import type { PolymorphicProps } from '@kobalte/core/polymorphic'\nimport { Polymorphic } from '@kobalte/core/polymorphic'\nimport { ChevronRight } from 'lucide-solid'\nimport type { ComponentProps, JSX, ValidComponent } from 'solid-js'\nimport { Show, createSignal, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\n\n/**\n * SidebarNav.\n *\n * The secondary navigation column: the list of things inside the destination\n * the rail selected — projects, sessions, files, threads. It is a different\n * object from the rail, not a wider version of it, which is why it is a separate\n * component rather than the rail with labels on.\n *\n * Rows share one recipe with the rail's, so a row in a sidebar and a row in a\n * rail read as the same kind of thing at the same density. That recipe is\n * exactly KiroCrew's nav row: `text-sm font-medium`, `gap-2.5`, `px-3 py-2`,\n * `rounded-md`, `transition-colors`.\n *\n * The width comes from `--sidebar-width`. Sections collapse by default only when\n * the caller says so, because a section that starts collapsed and holds the\n * user's own work is a hidden default.\n */\nexport type SidebarNavProps<T extends ValidComponent = 'nav'> = PolymorphicProps<\n T,\n { class?: string; 'aria-label'?: string }\n>\n\nexport function SidebarNav<T extends ValidComponent = 'nav'>(props: SidebarNavProps<T>) {\n const [local, rest] = splitProps(props as SidebarNavProps, ['class', 'aria-label', 'children'])\n\n return (\n <Polymorphic\n as=\"nav\"\n aria-label={local['aria-label'] ?? 'Section'}\n class={cn(\n 'bg-sidebar text-sidebar-foreground flex h-full w-sidebar shrink-0 flex-col border-e border-sidebar-border',\n local.class\n )}\n {...(rest as ComponentProps<'nav'>)}\n >\n {local.children}\n </Polymorphic>\n )\n}\n\nexport function SidebarNavHeader(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"sidebar-nav-header\"\n class={cn('flex h-topbar shrink-0 items-center justify-between gap-2 px-3', local.class)}\n {...rest}\n />\n )\n}\n\nexport type SidebarNavTitleProps<T extends ValidComponent = 'div'> = PolymorphicProps<\n T,\n { class?: string }\n>\n\nexport function SidebarNavTitle<T extends ValidComponent = 'div'>(props: SidebarNavTitleProps<T>) {\n const [local, rest] = splitProps(props as SidebarNavTitleProps, ['class'])\n return (\n <Polymorphic\n as=\"div\"\n data-slot=\"sidebar-nav-title\"\n class={cn('truncate text-sm font-semibold tracking-tight', local.class)}\n {...(rest as ComponentProps<'div'>)}\n />\n )\n}\n\n/** The scrolling middle of the sidebar. */\nexport function SidebarNavContent(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"sidebar-nav-content\"\n class={cn('flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2', local.class)}\n {...rest}\n />\n )\n}\n\nexport function SidebarNavFooter(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"sidebar-nav-footer\"\n class={cn('flex shrink-0 items-center gap-1 border-t border-sidebar-border p-2', local.class)}\n {...rest}\n />\n )\n}\n\n/**\n * A titled, collapsible group. Collapsible because a list of projects\n * inevitably outgrows the column, and the user needs to put the ones they are\n * not using away *without* them disappearing into a menu.\n */\nexport function SidebarNavSection(\n props: ComponentProps<'div'> & {\n label: string\n /** Render a disclosure control instead of a plain heading. */\n collapsible?: boolean\n defaultOpen?: boolean\n /** Count shown beside the label. */\n count?: number\n /** A control at the trailing edge of the heading row. */\n action?: JSX.Element\n /** Semantic heading element for a host's section hierarchy. */\n headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n }\n) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'collapsible',\n 'defaultOpen',\n 'count',\n 'action',\n 'headingAs',\n 'children',\n ])\n const [open, setOpen] = createSignal(local.defaultOpen ?? true)\n\n const heading = (\n <>\n <Show when={local.collapsible}>\n <ChevronRight\n aria-hidden=\"true\"\n class={cn('size-3.5 shrink-0 text-muted-foreground transition-transform ease-out', {\n 'rotate-90': open(),\n })}\n />\n </Show>\n <span class=\"min-w-0 flex-1 truncate\">{local.label}</span>\n <Show when={local.count !== undefined}>\n <span class=\"shrink-0 tabular-nums text-muted-foreground\">{local.count}</span>\n </Show>\n </>\n )\n\n return (\n <div data-slot=\"sidebar-nav-section\" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>\n <div class=\"group/section-header flex items-center gap-1 rounded-md px-2 py-1.5 hover:bg-sidebar-accent/60\">\n <Polymorphic\n as={local.headingAs ?? 'div'}\n aria-label={local.headingAs ? local.label : undefined}\n class=\"flex min-w-0 flex-1 items-center gap-1.5 text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase\"\n >\n <Show when={local.collapsible} fallback={heading}>\n <button\n type=\"button\"\n aria-expanded={open()}\n onClick={() => setOpen((value) => !value)}\n class=\"flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle\"\n >\n {heading}\n </button>\n </Show>\n </Polymorphic>\n <Show when={local.action}>\n <span class=\"shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100\">\n {local.action}\n </span>\n </Show>\n </div>\n <Show when={!local.collapsible || open()}>\n <div class=\"flex flex-col gap-0.5\">{local.children}</div>\n </Show>\n </div>\n )\n}\n\nexport type SidebarNavItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<\n T,\n {\n class?: string\n active?: boolean\n /** Indent one level, for a child row such as a branch under a project. */\n nested?: boolean\n /** Trailing slot: a count, a status dot, a hover action. */\n trailing?: JSX.Element\n }\n>\n\n/** A sidebar row. Exported classes, so a caller's own element can match it. */\nexport const sidebarNavItemClass = [\n 'group/nav-item relative flex min-w-0 items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium',\n 'whitespace-nowrap transition-colors ease-out outline-none select-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n '[&_svg]:size-4 [&_svg]:shrink-0',\n].join(' ')\n\nexport const sidebarNavItemStateClass = {\n active: 'bg-primary-subtle text-foreground',\n idle: 'text-muted-foreground hover:bg-surface-hover hover:text-foreground',\n}\n\nexport function SidebarNavItem<T extends ValidComponent = 'a'>(props: SidebarNavItemProps<T>) {\n const [local, rest] = splitProps(props as SidebarNavItemProps, [\n 'class',\n 'active',\n 'nested',\n 'trailing',\n 'children',\n ])\n\n return (\n <Polymorphic\n as=\"a\"\n aria-current={local.active ? 'page' : undefined}\n data-active={local.active ? '' : undefined}\n class={cn(\n sidebarNavItemClass,\n local.active ? sidebarNavItemStateClass.active : sidebarNavItemStateClass.idle,\n { 'ps-8': local.nested },\n local.class\n )}\n {...(rest as ComponentProps<'a'>)}\n >\n {local.children}\n <Show when={local.trailing}>\n <span class=\"ms-auto flex shrink-0 items-center gap-1\">{local.trailing}</span>\n </Show>\n </Polymorphic>\n )\n}\n\n/** A row that is a button rather than a destination — \"New project\". */\nexport function SidebarNavButton(props: ComponentProps<'button'>) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n\n return (\n <button\n type=\"button\"\n class={cn(\n sidebarNavItemClass,\n sidebarNavItemStateClass.idle,\n 'w-full disabled:pointer-events-none disabled:opacity-50',\n local.class\n )}\n {...rest}\n >\n {local.children}\n </button>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sidebar-nav.js","names":["PolymorphicProps","Polymorphic","ChevronRight","ComponentProps","JSX","ValidComponent","Show","createSignal","splitProps","cn","invokeEventHandler","handler","event","Event","Array","isArray","callback","data","SidebarNavProps","T","class","SidebarNav","props","local","rest","_$createComponent","_$mergeProps","as","aria-label","children","SidebarNavHeader","_el$","_tmpl$","_$spread","SidebarNavTitleProps","SidebarNavTitle","SidebarNavContent","_el$2","_tmpl$2","SidebarNavFooter","_el$3","_tmpl$3","SidebarNavSectionTriggerProps","Omit","onReorder","direction","onClick","onKeyDown","SidebarNavSectionProps","label","collapsible","defaultOpen","open","onOpenChange","triggerProps","count","action","Element","headingAs","SidebarNavSection","uncontrolledOpen","setUncontrolledOpen","setOpen","next","undefined","trigger","triggerRest","heading","when","_el$4","_tmpl$4","_$insert","_el$5","_tmpl$5","_el$6","_tmpl$9","_el$7","firstChild","_$memo","fallback","_el$8","_tmpl$6","aria-expanded","defaultPrevented","altKey","key","preventDefault","stopPropagation","_el$9","_tmpl$7","_el$0","_tmpl$8","SidebarNavItemProps","active","nested","trailing","sidebarNavItemClass","join","sidebarNavItemStateClass","idle","SidebarNavItem","aria-current","data-active","_el$1","_tmpl$0","SidebarNavButton","_el$10","_tmpl$1","_$createComponent","_$mergeProps","_$memo"],"sources":["../../../../src/components/layout/sidebar-nav/sidebar-nav.tsx"],"sourcesContent":["import type { PolymorphicProps } from '@kobalte/core/polymorphic'\nimport { Polymorphic } from '@kobalte/core/polymorphic'\nimport { ChevronRight } from 'lucide-solid'\nimport type { ComponentProps, JSX, ValidComponent } from 'solid-js'\nimport { Show, createSignal, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\n\nfunction invokeEventHandler(handler: unknown, event: Event) {\n if (Array.isArray(handler)) {\n const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]\n callback(data, event)\n } else if (typeof handler === 'function') {\n ;(handler as (event: Event) => void)(event)\n }\n}\n\n/**\n * SidebarNav.\n *\n * The secondary navigation column: the list of things inside the destination\n * the rail selected — projects, sessions, files, threads. It is a different\n * object from the rail, not a wider version of it, which is why it is a separate\n * component rather than the rail with labels on.\n *\n * Rows share one recipe with the rail's, so a row in a sidebar and a row in a\n * rail read as the same kind of thing at the same density. That recipe is\n * exactly KiroCrew's nav row: `text-sm font-medium`, `gap-2.5`, `px-3 py-2`,\n * `rounded-md`, `transition-colors`.\n *\n * The width comes from `--sidebar-width`. Sections collapse by default only when\n * the caller says so, because a section that starts collapsed and holds the\n * user's own work is a hidden default.\n */\nexport type SidebarNavProps<T extends ValidComponent = 'nav'> = PolymorphicProps<\n T,\n { class?: string; 'aria-label'?: string }\n>\n\nexport function SidebarNav<T extends ValidComponent = 'nav'>(props: SidebarNavProps<T>) {\n const [local, rest] = splitProps(props as SidebarNavProps, ['class', 'aria-label', 'children'])\n\n return (\n <Polymorphic\n as=\"nav\"\n aria-label={local['aria-label'] ?? 'Section'}\n class={cn(\n 'bg-sidebar text-sidebar-foreground flex h-full w-sidebar shrink-0 flex-col border-e border-sidebar-border',\n local.class\n )}\n {...(rest as ComponentProps<'nav'>)}\n >\n {local.children}\n </Polymorphic>\n )\n}\n\nexport function SidebarNavHeader(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"sidebar-nav-header\"\n class={cn('flex h-topbar shrink-0 items-center justify-between gap-2 px-3', local.class)}\n {...rest}\n />\n )\n}\n\nexport type SidebarNavTitleProps<T extends ValidComponent = 'div'> = PolymorphicProps<\n T,\n { class?: string }\n>\n\nexport function SidebarNavTitle<T extends ValidComponent = 'div'>(props: SidebarNavTitleProps<T>) {\n const [local, rest] = splitProps(props as SidebarNavTitleProps, ['class'])\n return (\n <Polymorphic\n as=\"div\"\n data-slot=\"sidebar-nav-title\"\n class={cn('truncate text-sm font-semibold tracking-tight', local.class)}\n {...(rest as ComponentProps<'div'>)}\n />\n )\n}\n\n/** The scrolling middle of the sidebar. */\nexport function SidebarNavContent(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"sidebar-nav-content\"\n class={cn('flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2', local.class)}\n {...rest}\n />\n )\n}\n\nexport function SidebarNavFooter(props: ComponentProps<'div'>) {\n const [local, rest] = splitProps(props, ['class'])\n return (\n <div\n data-slot=\"sidebar-nav-footer\"\n class={cn('flex shrink-0 items-center gap-1 border-t border-sidebar-border p-2', local.class)}\n {...rest}\n />\n )\n}\n\n/** Props forwarded to a collapsible section's disclosure button. */\nexport type SidebarNavSectionTriggerProps = Omit<\n ComponentProps<'button'>,\n 'type' | 'aria-expanded' | 'onClick' | 'onKeyDown' | 'children' | 'class' | 'style'\n> & {\n /** Called for Alt+ArrowUp/Down; the host owns ordering and row identity. */\n onReorder?: (direction: 'up' | 'down') => void\n /** Runs before the section toggle and may cancel it with `preventDefault()`. */\n onClick?: ComponentProps<'button'>['onClick']\n /** Runs before shared Alt+Arrow handling and may cancel it with `preventDefault()`. */\n onKeyDown?: ComponentProps<'button'>['onKeyDown']\n}\n\n/**\n * A titled, collapsible group. Collapsible because a list of projects\n * inevitably outgrows the column, and the user needs to put the ones they are\n * not using away *without* them disappearing into a menu.\n */\nexport type SidebarNavSectionProps = ComponentProps<'div'> & {\n label: string\n /** Render a disclosure control instead of a plain heading. */\n collapsible?: boolean\n defaultOpen?: boolean\n /** Controlled disclosure state. When supplied, the host owns changes. */\n open?: boolean\n /** Called for user-initiated disclosure changes. */\n onOpenChange?: (open: boolean) => void\n /** Native props and reorder callbacks for the disclosure control. */\n triggerProps?: SidebarNavSectionTriggerProps\n /** Count shown beside the label. */\n count?: number\n /** A control at the trailing edge of the heading row. */\n action?: JSX.Element\n /** Semantic heading element for a host's section hierarchy. */\n headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'\n}\n\nexport function SidebarNavSection(props: SidebarNavSectionProps) {\n const [local, rest] = splitProps(props, [\n 'class',\n 'label',\n 'collapsible',\n 'defaultOpen',\n 'open',\n 'onOpenChange',\n 'triggerProps',\n 'count',\n 'action',\n 'headingAs',\n 'children',\n ])\n const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? true)\n const open = () => local.open ?? uncontrolledOpen()\n const setOpen = (next: boolean) => {\n if (local.open === undefined) setUncontrolledOpen(next)\n local.onOpenChange?.(next)\n }\n\n const [trigger, triggerRest] = splitProps(local.triggerProps ?? {}, [\n 'onClick',\n 'onKeyDown',\n 'onReorder',\n ])\n\n const heading = (\n <>\n <Show when={local.collapsible}>\n <ChevronRight\n aria-hidden=\"true\"\n class={cn('size-3.5 shrink-0 text-muted-foreground transition-transform ease-out', {\n 'rotate-90': open(),\n })}\n />\n </Show>\n <span class=\"min-w-0 flex-1 truncate\">{local.label}</span>\n <Show when={local.count !== undefined}>\n <span class=\"shrink-0 tabular-nums text-muted-foreground\">{local.count}</span>\n </Show>\n </>\n )\n\n return (\n <div data-slot=\"sidebar-nav-section\" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>\n <div class=\"group/section-header flex items-center gap-1 rounded-md px-2 py-1.5 hover:bg-sidebar-accent/60\">\n <Polymorphic\n as={local.headingAs ?? 'div'}\n aria-label={local.headingAs ? local.label : undefined}\n class=\"flex min-w-0 flex-1 items-center gap-1.5 text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase\"\n >\n <Show when={local.collapsible} fallback={heading}>\n <button\n {...(triggerRest as ComponentProps<'button'>)}\n type=\"button\"\n aria-expanded={open()}\n onClick={(event) => {\n invokeEventHandler(trigger.onClick, event)\n if (event.defaultPrevented) return\n setOpen(!open())\n }}\n onKeyDown={(event) => {\n invokeEventHandler(trigger.onKeyDown, event)\n if (event.defaultPrevented || !event.altKey) return\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return\n event.preventDefault()\n event.stopPropagation()\n trigger.onReorder?.(event.key === 'ArrowUp' ? 'up' : 'down')\n }}\n class=\"flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle\"\n >\n {heading}\n </button>\n </Show>\n </Polymorphic>\n <Show when={local.action}>\n <span class=\"shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100\">\n {local.action}\n </span>\n </Show>\n </div>\n <Show when={!local.collapsible || open()}>\n <div class=\"flex flex-col gap-0.5\">{local.children}</div>\n </Show>\n </div>\n )\n}\n\nexport type SidebarNavItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<\n T,\n {\n class?: string\n active?: boolean\n /** Indent one level, for a child row such as a branch under a project. */\n nested?: boolean\n /** Trailing slot: a count, a status dot, a hover action. */\n trailing?: JSX.Element\n }\n>\n\n/** A sidebar row. Exported classes, so a caller's own element can match it. */\nexport const sidebarNavItemClass = [\n 'group/nav-item relative flex min-w-0 items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium',\n 'whitespace-nowrap transition-colors ease-out outline-none select-none',\n 'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',\n '[&_svg]:size-4 [&_svg]:shrink-0',\n].join(' ')\n\nexport const sidebarNavItemStateClass = {\n active: 'bg-primary-subtle text-foreground',\n idle: 'text-muted-foreground hover:bg-surface-hover hover:text-foreground',\n}\n\nexport function SidebarNavItem<T extends ValidComponent = 'a'>(props: SidebarNavItemProps<T>) {\n const [local, rest] = splitProps(props as SidebarNavItemProps, [\n 'class',\n 'active',\n 'nested',\n 'trailing',\n 'children',\n ])\n\n return (\n <Polymorphic\n as=\"a\"\n aria-current={local.active ? 'page' : undefined}\n data-active={local.active ? '' : undefined}\n class={cn(\n sidebarNavItemClass,\n local.active ? sidebarNavItemStateClass.active : sidebarNavItemStateClass.idle,\n { 'ps-8': local.nested },\n local.class\n )}\n {...(rest as ComponentProps<'a'>)}\n >\n {local.children}\n <Show when={local.trailing}>\n <span class=\"ms-auto flex shrink-0 items-center gap-1\">{local.trailing}</span>\n </Show>\n </Polymorphic>\n )\n}\n\n/** A row that is a button rather than a destination — \"New project\". */\nexport function SidebarNavButton(props: ComponentProps<'button'>) {\n const [local, rest] = splitProps(props, ['class', 'children'])\n\n return (\n <button\n type=\"button\"\n class={cn(\n sidebarNavItemClass,\n sidebarNavItemStateClass.idle,\n 'w-full disabled:pointer-events-none disabled:opacity-50',\n local.class\n )}\n {...rest}\n >\n {local.children}\n </button>\n )\n}\n"],"mappings":"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"}
@@ -11,6 +11,8 @@ export {
11
11
  sidebarNavItemStateClass,
12
12
  type SidebarNavItemProps,
13
13
  type SidebarNavProps,
14
+ type SidebarNavSectionProps,
15
+ type SidebarNavSectionTriggerProps,
14
16
  type SidebarNavTitleProps,
15
17
  } from './sidebar-nav'
16
18
  export {
@@ -5,6 +5,15 @@ import type { ComponentProps, JSX, ValidComponent } from 'solid-js'
5
5
  import { Show, createSignal, splitProps } from 'solid-js'
6
6
  import { cn } from '../../../lib/utils'
7
7
 
8
+ function invokeEventHandler(handler: unknown, event: Event) {
9
+ if (Array.isArray(handler)) {
10
+ const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]
11
+ callback(data, event)
12
+ } else if (typeof handler === 'function') {
13
+ ;(handler as (event: Event) => void)(event)
14
+ }
15
+ }
16
+
8
17
  /**
9
18
  * SidebarNav.
10
19
  *
@@ -96,36 +105,69 @@ export function SidebarNavFooter(props: ComponentProps<'div'>) {
96
105
  )
97
106
  }
98
107
 
108
+ /** Props forwarded to a collapsible section's disclosure button. */
109
+ export type SidebarNavSectionTriggerProps = Omit<
110
+ ComponentProps<'button'>,
111
+ 'type' | 'aria-expanded' | 'onClick' | 'onKeyDown' | 'children' | 'class' | 'style'
112
+ > & {
113
+ /** Called for Alt+ArrowUp/Down; the host owns ordering and row identity. */
114
+ onReorder?: (direction: 'up' | 'down') => void
115
+ /** Runs before the section toggle and may cancel it with `preventDefault()`. */
116
+ onClick?: ComponentProps<'button'>['onClick']
117
+ /** Runs before shared Alt+Arrow handling and may cancel it with `preventDefault()`. */
118
+ onKeyDown?: ComponentProps<'button'>['onKeyDown']
119
+ }
120
+
99
121
  /**
100
122
  * A titled, collapsible group. Collapsible because a list of projects
101
123
  * inevitably outgrows the column, and the user needs to put the ones they are
102
124
  * not using away *without* them disappearing into a menu.
103
125
  */
104
- export function SidebarNavSection(
105
- props: ComponentProps<'div'> & {
106
- label: string
107
- /** Render a disclosure control instead of a plain heading. */
108
- collapsible?: boolean
109
- defaultOpen?: boolean
110
- /** Count shown beside the label. */
111
- count?: number
112
- /** A control at the trailing edge of the heading row. */
113
- action?: JSX.Element
114
- /** Semantic heading element for a host's section hierarchy. */
115
- headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
116
- }
117
- ) {
126
+ export type SidebarNavSectionProps = ComponentProps<'div'> & {
127
+ label: string
128
+ /** Render a disclosure control instead of a plain heading. */
129
+ collapsible?: boolean
130
+ defaultOpen?: boolean
131
+ /** Controlled disclosure state. When supplied, the host owns changes. */
132
+ open?: boolean
133
+ /** Called for user-initiated disclosure changes. */
134
+ onOpenChange?: (open: boolean) => void
135
+ /** Native props and reorder callbacks for the disclosure control. */
136
+ triggerProps?: SidebarNavSectionTriggerProps
137
+ /** Count shown beside the label. */
138
+ count?: number
139
+ /** A control at the trailing edge of the heading row. */
140
+ action?: JSX.Element
141
+ /** Semantic heading element for a host's section hierarchy. */
142
+ headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
143
+ }
144
+
145
+ export function SidebarNavSection(props: SidebarNavSectionProps) {
118
146
  const [local, rest] = splitProps(props, [
119
147
  'class',
120
148
  'label',
121
149
  'collapsible',
122
150
  'defaultOpen',
151
+ 'open',
152
+ 'onOpenChange',
153
+ 'triggerProps',
123
154
  'count',
124
155
  'action',
125
156
  'headingAs',
126
157
  'children',
127
158
  ])
128
- const [open, setOpen] = createSignal(local.defaultOpen ?? true)
159
+ const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? true)
160
+ const open = () => local.open ?? uncontrolledOpen()
161
+ const setOpen = (next: boolean) => {
162
+ if (local.open === undefined) setUncontrolledOpen(next)
163
+ local.onOpenChange?.(next)
164
+ }
165
+
166
+ const [trigger, triggerRest] = splitProps(local.triggerProps ?? {}, [
167
+ 'onClick',
168
+ 'onKeyDown',
169
+ 'onReorder',
170
+ ])
129
171
 
130
172
  const heading = (
131
173
  <>
@@ -154,9 +196,22 @@ export function SidebarNavSection(
154
196
  >
155
197
  <Show when={local.collapsible} fallback={heading}>
156
198
  <button
199
+ {...(triggerRest as ComponentProps<'button'>)}
157
200
  type="button"
158
201
  aria-expanded={open()}
159
- onClick={() => setOpen((value) => !value)}
202
+ onClick={(event) => {
203
+ invokeEventHandler(trigger.onClick, event)
204
+ if (event.defaultPrevented) return
205
+ setOpen(!open())
206
+ }}
207
+ onKeyDown={(event) => {
208
+ invokeEventHandler(trigger.onKeyDown, event)
209
+ if (event.defaultPrevented || !event.altKey) return
210
+ if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return
211
+ event.preventDefault()
212
+ event.stopPropagation()
213
+ trigger.onReorder?.(event.key === 'ArrowUp' ? 'up' : 'down')
214
+ }}
160
215
  class="flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
161
216
  >
162
217
  {heading}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.87.0",
3
+ "version": "0.88.0",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -11,6 +11,8 @@ export {
11
11
  sidebarNavItemStateClass,
12
12
  type SidebarNavItemProps,
13
13
  type SidebarNavProps,
14
+ type SidebarNavSectionProps,
15
+ type SidebarNavSectionTriggerProps,
14
16
  type SidebarNavTitleProps,
15
17
  } from './sidebar-nav'
16
18
  export {
@@ -5,6 +5,15 @@ import type { ComponentProps, JSX, ValidComponent } from 'solid-js'
5
5
  import { Show, createSignal, splitProps } from 'solid-js'
6
6
  import { cn } from '#lib/utils'
7
7
 
8
+ function invokeEventHandler(handler: unknown, event: Event) {
9
+ if (Array.isArray(handler)) {
10
+ const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]
11
+ callback(data, event)
12
+ } else if (typeof handler === 'function') {
13
+ ;(handler as (event: Event) => void)(event)
14
+ }
15
+ }
16
+
8
17
  /**
9
18
  * SidebarNav.
10
19
  *
@@ -96,36 +105,69 @@ export function SidebarNavFooter(props: ComponentProps<'div'>) {
96
105
  )
97
106
  }
98
107
 
108
+ /** Props forwarded to a collapsible section's disclosure button. */
109
+ export type SidebarNavSectionTriggerProps = Omit<
110
+ ComponentProps<'button'>,
111
+ 'type' | 'aria-expanded' | 'onClick' | 'onKeyDown' | 'children' | 'class' | 'style'
112
+ > & {
113
+ /** Called for Alt+ArrowUp/Down; the host owns ordering and row identity. */
114
+ onReorder?: (direction: 'up' | 'down') => void
115
+ /** Runs before the section toggle and may cancel it with `preventDefault()`. */
116
+ onClick?: ComponentProps<'button'>['onClick']
117
+ /** Runs before shared Alt+Arrow handling and may cancel it with `preventDefault()`. */
118
+ onKeyDown?: ComponentProps<'button'>['onKeyDown']
119
+ }
120
+
99
121
  /**
100
122
  * A titled, collapsible group. Collapsible because a list of projects
101
123
  * inevitably outgrows the column, and the user needs to put the ones they are
102
124
  * not using away *without* them disappearing into a menu.
103
125
  */
104
- export function SidebarNavSection(
105
- props: ComponentProps<'div'> & {
106
- label: string
107
- /** Render a disclosure control instead of a plain heading. */
108
- collapsible?: boolean
109
- defaultOpen?: boolean
110
- /** Count shown beside the label. */
111
- count?: number
112
- /** A control at the trailing edge of the heading row. */
113
- action?: JSX.Element
114
- /** Semantic heading element for a host's section hierarchy. */
115
- headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
116
- }
117
- ) {
126
+ export type SidebarNavSectionProps = ComponentProps<'div'> & {
127
+ label: string
128
+ /** Render a disclosure control instead of a plain heading. */
129
+ collapsible?: boolean
130
+ defaultOpen?: boolean
131
+ /** Controlled disclosure state. When supplied, the host owns changes. */
132
+ open?: boolean
133
+ /** Called for user-initiated disclosure changes. */
134
+ onOpenChange?: (open: boolean) => void
135
+ /** Native props and reorder callbacks for the disclosure control. */
136
+ triggerProps?: SidebarNavSectionTriggerProps
137
+ /** Count shown beside the label. */
138
+ count?: number
139
+ /** A control at the trailing edge of the heading row. */
140
+ action?: JSX.Element
141
+ /** Semantic heading element for a host's section hierarchy. */
142
+ headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
143
+ }
144
+
145
+ export function SidebarNavSection(props: SidebarNavSectionProps) {
118
146
  const [local, rest] = splitProps(props, [
119
147
  'class',
120
148
  'label',
121
149
  'collapsible',
122
150
  'defaultOpen',
151
+ 'open',
152
+ 'onOpenChange',
153
+ 'triggerProps',
123
154
  'count',
124
155
  'action',
125
156
  'headingAs',
126
157
  'children',
127
158
  ])
128
- const [open, setOpen] = createSignal(local.defaultOpen ?? true)
159
+ const [uncontrolledOpen, setUncontrolledOpen] = createSignal(local.defaultOpen ?? true)
160
+ const open = () => local.open ?? uncontrolledOpen()
161
+ const setOpen = (next: boolean) => {
162
+ if (local.open === undefined) setUncontrolledOpen(next)
163
+ local.onOpenChange?.(next)
164
+ }
165
+
166
+ const [trigger, triggerRest] = splitProps(local.triggerProps ?? {}, [
167
+ 'onClick',
168
+ 'onKeyDown',
169
+ 'onReorder',
170
+ ])
129
171
 
130
172
  const heading = (
131
173
  <>
@@ -154,9 +196,22 @@ export function SidebarNavSection(
154
196
  >
155
197
  <Show when={local.collapsible} fallback={heading}>
156
198
  <button
199
+ {...(triggerRest as ComponentProps<'button'>)}
157
200
  type="button"
158
201
  aria-expanded={open()}
159
- onClick={() => setOpen((value) => !value)}
202
+ onClick={(event) => {
203
+ invokeEventHandler(trigger.onClick, event)
204
+ if (event.defaultPrevented) return
205
+ setOpen(!open())
206
+ }}
207
+ onKeyDown={(event) => {
208
+ invokeEventHandler(trigger.onKeyDown, event)
209
+ if (event.defaultPrevented || !event.altKey) return
210
+ if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return
211
+ event.preventDefault()
212
+ event.stopPropagation()
213
+ trigger.onReorder?.(event.key === 'ArrowUp' ? 'up' : 'down')
214
+ }}
160
215
  class="flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
161
216
  >
162
217
  {heading}