@spaethtech/svelte-ui 0.7.1-dev.41.d01009c → 0.7.1-dev.43.72e77ad
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.
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* bottom-anchored group (pushed to the strip's bottom edge), and hover-open
|
|
5
5
|
* popouts for parent items.
|
|
6
6
|
*
|
|
7
|
-
* Driven by data — pass `
|
|
7
|
+
* Driven by data — pass `items` / `bottomItems` arrays of {@link SideBarMenuItem}.
|
|
8
8
|
* Each item declares its own icon (Snippet), label, link, badge, and optional
|
|
9
9
|
* children. Children render in a top-layer Popup on hover or click.
|
|
10
10
|
*
|
|
@@ -33,6 +33,18 @@
|
|
|
33
33
|
*/
|
|
34
34
|
export type SideBarMenuMode = "stepped" | "drawer" | "icon" | "expanded";
|
|
35
35
|
|
|
36
|
+
/** Imperative handle exposed via `bind:api`. */
|
|
37
|
+
export type SideBarMenuApi = {
|
|
38
|
+
/** Reset all transient state — close the drawer, clear the open popout, return step level to 0. */
|
|
39
|
+
reset: () => void;
|
|
40
|
+
/** Open the drawer (drawer/stepped modes). */
|
|
41
|
+
open: () => void;
|
|
42
|
+
/** Close the drawer. */
|
|
43
|
+
close: () => void;
|
|
44
|
+
/** Set the stepped-mode step level (`0` = top). */
|
|
45
|
+
setStepLevel: (n: number) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
36
48
|
export type SideBarMenuBadgeVariant = "error" | "warning" | "info" | "success";
|
|
37
49
|
|
|
38
50
|
export type SideBarMenuBadge = {
|
|
@@ -69,6 +81,15 @@
|
|
|
69
81
|
badge?: SideBarMenuBadge;
|
|
70
82
|
/** v1 renders only the first level; deeper levels emit a DEV warning. */
|
|
71
83
|
children?: SideBarMenuItem[];
|
|
84
|
+
/** Action row (no `link`) — renders a `<button>` and calls this on click. For a "toggle"
|
|
85
|
+
* row (an AI panel, etc.) or a non-navigating action. Runs after the drawer/popout reset. */
|
|
86
|
+
onclick?: (e: MouseEvent) => void;
|
|
87
|
+
/** Custom popout content, rendered INSTEAD of `children` in the popout (separators, hotkeys,
|
|
88
|
+
* an account/identity block…). The row still needs an `icon`/`label`; this is just its popout. */
|
|
89
|
+
popout?: import("svelte").Snippet;
|
|
90
|
+
/** Opt this anchor out of client-side routing — stamps `data-sveltekit-reload` (SvelteKit) so
|
|
91
|
+
* the link forces a full-browser navigation (sign-out endpoints, downloads, external redirects). */
|
|
92
|
+
reload?: boolean;
|
|
72
93
|
};
|
|
73
94
|
</script>
|
|
74
95
|
|
|
@@ -89,7 +110,7 @@
|
|
|
89
110
|
type ModesMap = Partial<Record<Breakpoint, SideBarMenuMode>>;
|
|
90
111
|
|
|
91
112
|
let {
|
|
92
|
-
|
|
113
|
+
items = [],
|
|
93
114
|
bottomItems = [],
|
|
94
115
|
currentPath,
|
|
95
116
|
size = "md",
|
|
@@ -100,9 +121,12 @@
|
|
|
100
121
|
open = $bindable(false),
|
|
101
122
|
stepLevel = $bindable(0),
|
|
102
123
|
hamburger = true,
|
|
124
|
+
iconHoverExpand = false,
|
|
125
|
+
hoverExpandCloseMs = 200,
|
|
126
|
+
api = $bindable(),
|
|
103
127
|
class: additionalClass = "",
|
|
104
128
|
}: {
|
|
105
|
-
|
|
129
|
+
items?: SideBarMenuItem[];
|
|
106
130
|
bottomItems?: SideBarMenuItem[];
|
|
107
131
|
currentPath: string;
|
|
108
132
|
size?: Size;
|
|
@@ -149,6 +173,15 @@
|
|
|
149
173
|
* and binds to `open`.
|
|
150
174
|
*/
|
|
151
175
|
hamburger?: boolean;
|
|
176
|
+
/** In `icon` mode, expand the rail to `expanded` ON HOVER — as a floating overlay over a spacer
|
|
177
|
+
* that reserves the collapsed width, so page content never reflows. Default `false`. */
|
|
178
|
+
iconHoverExpand?: boolean;
|
|
179
|
+
/** Exit delay (ms) before a hover-expanded rail collapses on mouse-leave. Default `200`. */
|
|
180
|
+
hoverExpandCloseMs?: number;
|
|
181
|
+
/** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
|
|
182
|
+
* (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
|
|
183
|
+
* any open popout and returns a stepped drawer to the top. */
|
|
184
|
+
api?: SideBarMenuApi;
|
|
152
185
|
class?: string;
|
|
153
186
|
} = $props();
|
|
154
187
|
|
|
@@ -198,27 +231,55 @@
|
|
|
198
231
|
|
|
199
232
|
const isStepped = $derived(currentMode === "stepped");
|
|
200
233
|
const isDrawer = $derived(currentMode === "drawer" || currentMode === "stepped");
|
|
201
|
-
|
|
234
|
+
|
|
235
|
+
// ── Icon-mode hover-expand ──────────────────────────────────────
|
|
236
|
+
// When enabled + in `icon` mode, hovering the rail floats it out to the `expanded` render OVER a
|
|
237
|
+
// spacer that reserves the collapsed width — page content never reflows. Exit is delayed.
|
|
238
|
+
let hoverExpanded = $state(false);
|
|
239
|
+
let hoverExpandTimer: ReturnType<typeof setTimeout> | null = null;
|
|
240
|
+
const hoverExpandActive = $derived(iconHoverExpand && currentMode === "icon");
|
|
241
|
+
const effectivelyExpanded = $derived(hoverExpandActive && hoverExpanded);
|
|
242
|
+
function openHoverExpand() {
|
|
243
|
+
if (hoverExpandTimer) {
|
|
244
|
+
clearTimeout(hoverExpandTimer);
|
|
245
|
+
hoverExpandTimer = null;
|
|
246
|
+
}
|
|
247
|
+
if (hoverExpandActive) hoverExpanded = true;
|
|
248
|
+
}
|
|
249
|
+
function scheduleHoverCollapse() {
|
|
250
|
+
if (hoverExpandTimer) clearTimeout(hoverExpandTimer);
|
|
251
|
+
hoverExpandTimer = setTimeout(() => {
|
|
252
|
+
hoverExpanded = false;
|
|
253
|
+
openId = null;
|
|
254
|
+
hoverExpandTimer = null;
|
|
255
|
+
}, hoverExpandCloseMs);
|
|
256
|
+
}
|
|
257
|
+
$effect(() => {
|
|
258
|
+
if (!hoverExpandActive) hoverExpanded = false;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Labels show in every mode except a collapsed (not hover-expanded) icon rail.
|
|
262
|
+
const labelsVisible = $derived(currentMode !== "icon" || effectivelyExpanded);
|
|
202
263
|
|
|
203
264
|
// ── DEV depth warning ───────────────────────────────────────────
|
|
204
265
|
if (DEV) {
|
|
205
|
-
const walk = (
|
|
206
|
-
for (const it of
|
|
266
|
+
const walk = (list: SideBarMenuItem[], depth: number, path: string[]) => {
|
|
267
|
+
for (const it of list) {
|
|
268
|
+
const here = [...path, it.label];
|
|
207
269
|
if (it.children?.length) {
|
|
208
270
|
if (depth >= 1) {
|
|
209
271
|
console.warn(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'"',
|
|
272
|
+
`SideBarMenu: nesting deeper than 1 level is not yet rendered; flattening ` +
|
|
273
|
+
`grandchildren of "${here.join(" › ")}"`,
|
|
213
274
|
);
|
|
214
275
|
return;
|
|
215
276
|
}
|
|
216
|
-
walk(it.children, depth + 1);
|
|
277
|
+
walk(it.children, depth + 1, here);
|
|
217
278
|
}
|
|
218
279
|
}
|
|
219
280
|
};
|
|
220
|
-
walk(
|
|
221
|
-
walk(bottomItems, 0);
|
|
281
|
+
walk(items, 0, []);
|
|
282
|
+
walk(bottomItems, 0, []);
|
|
222
283
|
}
|
|
223
284
|
|
|
224
285
|
// ── Size axis ───────────────────────────────────────────────────
|
|
@@ -327,6 +388,15 @@
|
|
|
327
388
|
stepLevel = 1;
|
|
328
389
|
}
|
|
329
390
|
|
|
391
|
+
// Imperative handle for consumers (bound via `bind:api`). Assigned once — the closures read the
|
|
392
|
+
// reactive `open`/`stepLevel`/`openId` live, so a single assignment stays correct.
|
|
393
|
+
api = {
|
|
394
|
+
reset: closeAfterNav,
|
|
395
|
+
open: () => (open = true),
|
|
396
|
+
close: () => (open = false),
|
|
397
|
+
setStepLevel: (n: number) => (stepLevel = n),
|
|
398
|
+
};
|
|
399
|
+
|
|
330
400
|
// Label / chevron visibility are now driven by the active mode
|
|
331
401
|
// instead of by responsive class strings — the matchMedia effect
|
|
332
402
|
// above keeps `currentMode` (and therefore `labelsVisible`) in sync
|
|
@@ -503,27 +573,35 @@
|
|
|
503
573
|
`chevronVisibility` (not `labelClassResolved`) so the span
|
|
504
574
|
stays `inline-flex` and the SVG centers vertically. -->
|
|
505
575
|
<span
|
|
506
|
-
class="shrink-0 items-center justify-center opacity-60
|
|
576
|
+
class="shrink-0 items-center justify-center opacity-60 transition-transform duration-150 {isOpen
|
|
577
|
+
? 'rotate-180'
|
|
578
|
+
: ''} {sz.chevronBox} {sz.chevronSvg} {chevronClass}"
|
|
507
579
|
>
|
|
508
580
|
<IconChevronRight />
|
|
509
581
|
</span>
|
|
510
582
|
{/if}
|
|
511
583
|
{/snippet}
|
|
512
584
|
|
|
513
|
-
<!-- Single row template — used by
|
|
585
|
+
<!-- Single row template — used by items / bottomItems and the popout. -->
|
|
514
586
|
{#snippet row(item: SideBarMenuItem, opts: { id?: ItemId; isPopoutChild?: boolean })}
|
|
515
587
|
{@const active = isItemActive(item)}
|
|
588
|
+
{@const ownActive = !!item.link && currentPath.startsWith(item.link)}
|
|
516
589
|
{@const hasChildren = (item.children?.length ?? 0) > 0}
|
|
517
|
-
{@const isParent = hasChildren && !opts.isPopoutChild}
|
|
590
|
+
{@const isParent = (hasChildren || !!item.popout) && !opts.isPopoutChild}
|
|
518
591
|
{@const hasLink = !!item.link}
|
|
519
592
|
{@const isOpen = !!opts.id && openId === opts.id}
|
|
593
|
+
{@const popoutId = opts.id ? "sbm-popout-" + opts.id.replace(":", "-") : undefined}
|
|
520
594
|
{@const rowClass = `group flex items-center w-full ${sz.row} lg:pr-3 transition-colors cursor-pointer ${active ? "[background-color:var(--ui-color-primary)] text-white" : isOpen ? "[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]" : "hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"}`}
|
|
521
595
|
{#if hasLink}
|
|
522
596
|
<a
|
|
523
597
|
href={item.link}
|
|
524
598
|
use:refIntoMap={opts.id}
|
|
525
599
|
class={rowClass}
|
|
526
|
-
|
|
600
|
+
data-sveltekit-reload={item.reload ? "" : undefined}
|
|
601
|
+
aria-current={ownActive ? "page" : undefined}
|
|
602
|
+
aria-haspopup={isParent ? "menu" : undefined}
|
|
603
|
+
aria-expanded={isParent && opts.id ? isOpen : undefined}
|
|
604
|
+
aria-controls={isParent && isOpen ? popoutId : undefined}
|
|
527
605
|
use:tooltip={{
|
|
528
606
|
text: item.label,
|
|
529
607
|
side: "right",
|
|
@@ -562,6 +640,7 @@
|
|
|
562
640
|
class="{rowClass} text-left"
|
|
563
641
|
aria-haspopup="menu"
|
|
564
642
|
aria-expanded={openId === opts.id}
|
|
643
|
+
aria-controls={isOpen ? popoutId : undefined}
|
|
565
644
|
use:tooltip={{ text: item.label, side: "right", disabled: openId === opts.id }}
|
|
566
645
|
onmouseenter={!isStepped ? () => openOnHover(opts.id!) : undefined}
|
|
567
646
|
onmouseleave={!isStepped ? scheduleClose : undefined}
|
|
@@ -569,6 +648,21 @@
|
|
|
569
648
|
>
|
|
570
649
|
{@render rowBody(item, isParent, !!opts.isPopoutChild, isOpen)}
|
|
571
650
|
</button>
|
|
651
|
+
{:else if item.onclick}
|
|
652
|
+
<!-- Action row: no link, no children/popout — a `<button>` that runs `onclick`
|
|
653
|
+
(e.g. an AI-panel toggle), then resets the drawer/popout like a leaf nav. -->
|
|
654
|
+
<button
|
|
655
|
+
type="button"
|
|
656
|
+
use:refIntoMap={opts.id}
|
|
657
|
+
class="{rowClass} text-left"
|
|
658
|
+
use:tooltip={{ text: item.label, side: "right" }}
|
|
659
|
+
onclick={(e) => {
|
|
660
|
+
item.onclick!(e);
|
|
661
|
+
closeAfterNav();
|
|
662
|
+
}}
|
|
663
|
+
>
|
|
664
|
+
{@render rowBody(item, false, !!opts.isPopoutChild, false)}
|
|
665
|
+
</button>
|
|
572
666
|
{:else}
|
|
573
667
|
<!-- Malformed item: no link, no children. Render visibly so the
|
|
574
668
|
consumer notices, but with disabled-style cursor + opacity. -->
|
|
@@ -582,22 +676,27 @@
|
|
|
582
676
|
mirrors main strip styling. `borderClass` is computed per popout
|
|
583
677
|
by `geometryFor` so the visible edges get borders and the edges
|
|
584
678
|
that coincide with the sidebar's top/bottom don't. -->
|
|
585
|
-
{#snippet popout(item: SideBarMenuItem, borderClass: string)}
|
|
679
|
+
{#snippet popout(item: SideBarMenuItem, borderClass: string, popoutId: string)}
|
|
586
680
|
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
587
681
|
<div
|
|
588
682
|
role="menu"
|
|
589
683
|
tabindex="-1"
|
|
684
|
+
id={popoutId}
|
|
590
685
|
class="w-[var(--sbm-expanded-w)] overflow-hidden shadow-lg [background-color:var(--ui-color-background)] [color:var(--ui-color-text)] [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] {borderClass}"
|
|
591
686
|
onmouseenter={cancelClose}
|
|
592
687
|
onmouseleave={scheduleClose}
|
|
593
688
|
>
|
|
594
|
-
|
|
595
|
-
{
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
689
|
+
{#if item.popout}
|
|
690
|
+
{@render item.popout()}
|
|
691
|
+
{:else}
|
|
692
|
+
<ul class="flex flex-col">
|
|
693
|
+
{#each item.children ?? [] as child (child.label)}
|
|
694
|
+
<li class="list-none" role="none">
|
|
695
|
+
{@render row(child, { isPopoutChild: true })}
|
|
696
|
+
</li>
|
|
697
|
+
{/each}
|
|
698
|
+
</ul>
|
|
699
|
+
{/if}
|
|
601
700
|
</div>
|
|
602
701
|
{/snippet}
|
|
603
702
|
|
|
@@ -627,24 +726,34 @@
|
|
|
627
726
|
></div>
|
|
628
727
|
{/if}
|
|
629
728
|
|
|
630
|
-
|
|
631
|
-
|
|
729
|
+
<!-- Root wrapper. In icon hover-expand it becomes a `relative` spacer reserving the collapsed rail
|
|
730
|
+
width, so the expanded float below is positioned against the SIDEBAR's own box (container-relative,
|
|
731
|
+
never the viewport). In every other mode it's `display: contents` (no box) so layout is unchanged. -->
|
|
732
|
+
<div
|
|
733
|
+
class={hoverExpandActive ? "relative shrink-0 [width:var(--sbm-collapsed-w)]" : "contents"}
|
|
632
734
|
style="--sbm-collapsed-w: {collapsedWidth}px; --sbm-expanded-w: {expandedWidth}px;"
|
|
633
|
-
|
|
735
|
+
>
|
|
736
|
+
<aside
|
|
737
|
+
bind:this={asideRef}
|
|
738
|
+
onmouseenter={hoverExpandActive ? openHoverExpand : undefined}
|
|
739
|
+
onmouseleave={hoverExpandActive ? scheduleHoverCollapse : undefined}
|
|
740
|
+
class="
|
|
634
741
|
flex flex-col z-[60] overflow-hidden
|
|
635
742
|
[background-color:var(--ui-color-background)]
|
|
636
743
|
[color:var(--ui-color-text)]
|
|
637
744
|
[border-right:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]
|
|
638
745
|
{currentMode === 'stepped'
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
746
|
+
? `absolute inset-y-0 left-0 w-full z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : '-translate-x-full'}`
|
|
747
|
+
: currentMode === 'drawer'
|
|
748
|
+
? `absolute inset-y-0 left-0 w-[var(--sbm-expanded-w)] z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : '-translate-x-full'}`
|
|
749
|
+
: currentMode === 'icon'
|
|
750
|
+
? hoverExpandActive
|
|
751
|
+
? `absolute inset-y-0 left-0 z-[65] transition-[width] duration-200 ${effectivelyExpanded ? 'w-[var(--sbm-expanded-w)] shadow-lg' : 'w-[var(--sbm-collapsed-w)]'}`
|
|
752
|
+
: 'relative w-[var(--sbm-collapsed-w)]'
|
|
753
|
+
: 'relative w-[var(--sbm-expanded-w)]'}
|
|
645
754
|
{additionalClass}
|
|
646
755
|
"
|
|
647
|
-
>
|
|
756
|
+
>
|
|
648
757
|
{#if isStepped && stepLevel === 1 && currentParent}
|
|
649
758
|
<!-- Stepped sub-view (<2xs only): show just the children of the
|
|
650
759
|
parent the user tapped. The consumer's hamburger button is
|
|
@@ -659,15 +768,15 @@
|
|
|
659
768
|
{/each}
|
|
660
769
|
</ul>
|
|
661
770
|
{:else}
|
|
662
|
-
{#if
|
|
771
|
+
{#if items.length > 0}
|
|
663
772
|
<ul class="flex flex-col">
|
|
664
|
-
{#each
|
|
773
|
+
{#each items as item, idx (item.label)}
|
|
665
774
|
{@const id = `top:${idx}` as ItemId}
|
|
666
775
|
<li class="list-none">
|
|
667
776
|
{@render row(item, { id })}
|
|
668
777
|
</li>
|
|
669
|
-
{#if (item.children?.length ?? 0) > 0 && !isStepped}
|
|
670
|
-
{@const geom = geometryFor(id, "top", idx,
|
|
778
|
+
{#if ((item.children?.length ?? 0) > 0 || !!item.popout) && !isStepped}
|
|
779
|
+
{@const geom = geometryFor(id, "top", idx, items.length, item.children?.length ?? 1)}
|
|
671
780
|
<Popup
|
|
672
781
|
anchor={geom.anchor}
|
|
673
782
|
open={openId === id}
|
|
@@ -678,14 +787,14 @@
|
|
|
678
787
|
if (openId === id) openId = null;
|
|
679
788
|
}}
|
|
680
789
|
>
|
|
681
|
-
{@render popout(item, geom.borderClass)}
|
|
790
|
+
{@render popout(item, geom.borderClass, "sbm-popout-" + id.replace(":", "-"))}
|
|
682
791
|
</Popup>
|
|
683
792
|
{/if}
|
|
684
793
|
{/each}
|
|
685
794
|
</ul>
|
|
686
795
|
{/if}
|
|
687
796
|
|
|
688
|
-
{#if
|
|
797
|
+
{#if items.length > 0 && bottomItems.length > 0}
|
|
689
798
|
<div class="flex-1"></div>
|
|
690
799
|
{/if}
|
|
691
800
|
|
|
@@ -696,13 +805,13 @@
|
|
|
696
805
|
<li class="list-none">
|
|
697
806
|
{@render row(item, { id })}
|
|
698
807
|
</li>
|
|
699
|
-
{#if (item.children?.length ?? 0) > 0 && !isStepped}
|
|
808
|
+
{#if ((item.children?.length ?? 0) > 0 || !!item.popout) && !isStepped}
|
|
700
809
|
{@const geom = geometryFor(
|
|
701
810
|
id,
|
|
702
811
|
"bottom",
|
|
703
812
|
idx,
|
|
704
813
|
bottomItems.length,
|
|
705
|
-
item.children
|
|
814
|
+
item.children?.length ?? 1,
|
|
706
815
|
)}
|
|
707
816
|
<Popup
|
|
708
817
|
anchor={geom.anchor}
|
|
@@ -714,11 +823,12 @@
|
|
|
714
823
|
if (openId === id) openId = null;
|
|
715
824
|
}}
|
|
716
825
|
>
|
|
717
|
-
{@render popout(item, geom.borderClass)}
|
|
826
|
+
{@render popout(item, geom.borderClass, "sbm-popout-" + id.replace(":", "-"))}
|
|
718
827
|
</Popup>
|
|
719
828
|
{/if}
|
|
720
829
|
{/each}
|
|
721
830
|
</ul>
|
|
722
831
|
{/if}
|
|
723
832
|
{/if}
|
|
724
|
-
</aside>
|
|
833
|
+
</aside>
|
|
834
|
+
</div>
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
* Designed for wide desktop.
|
|
19
19
|
*/
|
|
20
20
|
export type SideBarMenuMode = "stepped" | "drawer" | "icon" | "expanded";
|
|
21
|
+
/** Imperative handle exposed via `bind:api`. */
|
|
22
|
+
export type SideBarMenuApi = {
|
|
23
|
+
/** Reset all transient state — close the drawer, clear the open popout, return step level to 0. */
|
|
24
|
+
reset: () => void;
|
|
25
|
+
/** Open the drawer (drawer/stepped modes). */
|
|
26
|
+
open: () => void;
|
|
27
|
+
/** Close the drawer. */
|
|
28
|
+
close: () => void;
|
|
29
|
+
/** Set the stepped-mode step level (`0` = top). */
|
|
30
|
+
setStepLevel: (n: number) => void;
|
|
31
|
+
};
|
|
21
32
|
export type SideBarMenuBadgeVariant = "error" | "warning" | "info" | "success";
|
|
22
33
|
export type SideBarMenuBadge = {
|
|
23
34
|
/** Color of the indicator. */
|
|
@@ -50,12 +61,21 @@ export type SideBarMenuItem = {
|
|
|
50
61
|
badge?: SideBarMenuBadge;
|
|
51
62
|
/** v1 renders only the first level; deeper levels emit a DEV warning. */
|
|
52
63
|
children?: SideBarMenuItem[];
|
|
64
|
+
/** Action row (no `link`) — renders a `<button>` and calls this on click. For a "toggle"
|
|
65
|
+
* row (an AI panel, etc.) or a non-navigating action. Runs after the drawer/popout reset. */
|
|
66
|
+
onclick?: (e: MouseEvent) => void;
|
|
67
|
+
/** Custom popout content, rendered INSTEAD of `children` in the popout (separators, hotkeys,
|
|
68
|
+
* an account/identity block…). The row still needs an `icon`/`label`; this is just its popout. */
|
|
69
|
+
popout?: import("svelte").Snippet;
|
|
70
|
+
/** Opt this anchor out of client-side routing — stamps `data-sveltekit-reload` (SvelteKit) so
|
|
71
|
+
* the link forces a full-browser navigation (sign-out endpoints, downloads, external redirects). */
|
|
72
|
+
reload?: boolean;
|
|
53
73
|
};
|
|
54
74
|
import type { Size } from "../../types/sizes.js";
|
|
55
75
|
import { type Breakpoint } from "../../types/breakpoints.js";
|
|
56
76
|
type ModesMap = Partial<Record<Breakpoint, SideBarMenuMode>>;
|
|
57
77
|
type $$ComponentProps = {
|
|
58
|
-
|
|
78
|
+
items?: SideBarMenuItem[];
|
|
59
79
|
bottomItems?: SideBarMenuItem[];
|
|
60
80
|
currentPath: string;
|
|
61
81
|
size?: Size;
|
|
@@ -102,8 +122,17 @@ type $$ComponentProps = {
|
|
|
102
122
|
* and binds to `open`.
|
|
103
123
|
*/
|
|
104
124
|
hamburger?: boolean;
|
|
125
|
+
/** In `icon` mode, expand the rail to `expanded` ON HOVER — as a floating overlay over a spacer
|
|
126
|
+
* that reserves the collapsed width, so page content never reflows. Default `false`. */
|
|
127
|
+
iconHoverExpand?: boolean;
|
|
128
|
+
/** Exit delay (ms) before a hover-expanded rail collapses on mouse-leave. Default `200`. */
|
|
129
|
+
hoverExpandCloseMs?: number;
|
|
130
|
+
/** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
|
|
131
|
+
* (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
|
|
132
|
+
* any open popout and returns a stepped drawer to the top. */
|
|
133
|
+
api?: SideBarMenuApi;
|
|
105
134
|
class?: string;
|
|
106
135
|
};
|
|
107
|
-
declare const SideBarMenu: import("svelte").Component<$$ComponentProps, {}, "open" | "stepLevel">;
|
|
136
|
+
declare const SideBarMenu: import("svelte").Component<$$ComponentProps, {}, "open" | "stepLevel" | "api">;
|
|
108
137
|
type SideBarMenu = ReturnType<typeof SideBarMenu>;
|
|
109
138
|
export default SideBarMenu;
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export type { Columns, Gap } from "./components/field-group.js";
|
|
|
41
41
|
export { default as Disclosure } from "./components/Disclosure.svelte";
|
|
42
42
|
export { default as Accordion } from "./components/Accordion.svelte";
|
|
43
43
|
export { default as SideBarMenu } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
44
|
-
export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
44
|
+
export type { SideBarMenuItem, SideBarMenuBadge, SideBarMenuBadgeVariant, SideBarMenuApi, } from "./components/SideBarMenu/SideBarMenu.svelte";
|
|
45
45
|
export { default as TabStrip } from "./components/TabStrip/TabStrip.svelte";
|
|
46
46
|
export type { TabDefinition } from "./components/TabStrip/TabStrip.svelte";
|
|
47
47
|
export { default as DataTable } from "./components/DataTable.svelte";
|