@spaethtech/svelte-ui 0.7.1-dev.43.72e77ad → 0.7.1-dev.45.41387ea

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.
@@ -35,6 +35,7 @@
35
35
  align = "start",
36
36
  boundary = "viewport",
37
37
  offset = 4,
38
+ margin = 8,
38
39
  matchWidth = false,
39
40
  surface = false,
40
41
  variant,
@@ -51,6 +52,9 @@
51
52
  /** Default the viewport; pass a container element or CSS selector to bound flipping to it. */
52
53
  boundary?: Boundary;
53
54
  offset?: number;
55
+ /** Minimum gap kept from the boundary edges, in px (default 8). Set `0` to let the box sit flush
56
+ * against an edge — e.g. a sidebar popout that must align to the sidebar's (viewport) edge. */
57
+ margin?: number;
54
58
  matchWidth?: boolean;
55
59
  /** Render a themed floating PANEL around the children (background, border, padding, shadow) instead
56
60
  * of the default bare positioner. `variant`/`size`/`borderless` configure that panel. Consumers
@@ -132,6 +136,7 @@
132
136
  align,
133
137
  boundary,
134
138
  offset,
139
+ margin,
135
140
  matchWidth,
136
141
  onplaced: (p) => (placed = p),
137
142
  }}
@@ -12,6 +12,9 @@ type $$ComponentProps = {
12
12
  /** Default the viewport; pass a container element or CSS selector to bound flipping to it. */
13
13
  boundary?: Boundary;
14
14
  offset?: number;
15
+ /** Minimum gap kept from the boundary edges, in px (default 8). Set `0` to let the box sit flush
16
+ * against an edge — e.g. a sidebar popout that must align to the sidebar's (viewport) edge. */
17
+ margin?: number;
15
18
  matchWidth?: boolean;
16
19
  /** Render a themed floating PANEL around the children (background, border, padding, shadow) instead
17
20
  * of the default bare positioner. `variant`/`size`/`borderless` configure that panel. Consumers
@@ -123,6 +123,7 @@
123
123
  hamburger = true,
124
124
  iconHoverExpand = false,
125
125
  hoverExpandCloseMs = 200,
126
+ side = "left",
126
127
  api = $bindable(),
127
128
  class: additionalClass = "",
128
129
  }: {
@@ -178,6 +179,10 @@
178
179
  iconHoverExpand?: boolean;
179
180
  /** Exit delay (ms) before a hover-expanded rail collapses on mouse-leave. Default `200`. */
180
181
  hoverExpandCloseMs?: number;
182
+ /** Which edge the sidebar docks to. `"right"` mirrors everything: the frame border, the drawer
183
+ * slide direction, the hamburger corner, popout side + border, and item layout (icon on the
184
+ * right, labels right-aligned). Default `"left"`. */
185
+ side?: "left" | "right";
181
186
  /** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
182
187
  * (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
183
188
  * any open popout and returns a stepped drawer to the top. */
@@ -231,6 +236,16 @@
231
236
 
232
237
  const isStepped = $derived(currentMode === "stepped");
233
238
  const isDrawer = $derived(currentMode === "drawer" || currentMode === "stepped");
239
+ // Dock side. `right` mirrors the frame, drawer slide, hamburger corner, popout side, and item layout.
240
+ const dockRight = $derived(side === "right");
241
+ const popoutSide = $derived<"left" | "right">(dockRight ? "left" : "right");
242
+ const dockEdge = $derived(dockRight ? "right-0" : "left-0");
243
+ const dockOff = $derived(dockRight ? "translate-x-full" : "-translate-x-full");
244
+ const dockBorder = $derived(
245
+ dockRight
246
+ ? "[border-left:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]"
247
+ : "[border-right:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]",
248
+ );
234
249
 
235
250
  // ── Icon-mode hover-expand ──────────────────────────────────────
236
251
  // When enabled + in `icon` mode, hovering the rail floats it out to the `expanded` render OVER a
@@ -261,6 +276,15 @@
261
276
  // Labels show in every mode except a collapsed (not hover-expanded) icon rail.
262
277
  const labelsVisible = $derived(currentMode !== "icon" || effectivelyExpanded);
263
278
 
279
+ // Aside width in px, driven by mode (inline style — robust, no Tailwind arbitrary-var class).
280
+ const asideWidthCss = $derived(
281
+ currentMode === "stepped"
282
+ ? "100%"
283
+ : currentMode === "icon" && !effectivelyExpanded
284
+ ? `${collapsedWidth}px`
285
+ : `${expandedWidth}px`,
286
+ );
287
+
264
288
  // ── DEV depth warning ───────────────────────────────────────────
265
289
  if (DEV) {
266
290
  const walk = (list: SideBarMenuItem[], depth: number, path: string[]) => {
@@ -505,7 +529,7 @@
505
529
  // height get clamped to the viewport by the `anchored` engine
506
530
  // boundary. That case also needs in-popout scroll (TODO) and
507
531
  // the border rule may need to account for viewport-edge cases.
508
- let borderClass = "border-r";
532
+ let borderClass = dockRight ? "border-l" : "border-r";
509
533
  if (group === "bottom") {
510
534
  borderClass += spans ? " border-t" : " border-t border-b";
511
535
  } else {
@@ -564,7 +588,9 @@
564
588
  what `truncate` (overflow-hidden + text-ellipsis) needs to
565
589
  actually clip overflow. Without `min-w-0` the label refuses
566
590
  to shrink past its natural width and the row overflows. -->
567
- <span class="flex-1 min-w-0 truncate {labelClassResolved} {sz.text}">{item.label}</span>
591
+ <span class="flex-1 min-w-0 truncate {dockRight ? 'text-right' : ''} {labelClassResolved} {sz.text}"
592
+ >{item.label}</span
593
+ >
568
594
  {#if isParent}
569
595
  <!-- Chevron — visible only when the label is visible (no point
570
596
  dangling a `>` next to an icon-only icon). Rotates 180° to
@@ -591,7 +617,7 @@
591
617
  {@const hasLink = !!item.link}
592
618
  {@const isOpen = !!opts.id && openId === opts.id}
593
619
  {@const popoutId = opts.id ? "sbm-popout-" + opts.id.replace(":", "-") : undefined}
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)]"}`}
620
+ {@const rowClass = `group flex items-center w-full ${sz.row} ${dockRight ? "flex-row-reverse lg:pl-3" : "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)]"}`}
595
621
  {#if hasLink}
596
622
  <a
597
623
  href={item.link}
@@ -682,7 +708,8 @@
682
708
  role="menu"
683
709
  tabindex="-1"
684
710
  id={popoutId}
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}"
711
+ style="width: {expandedWidth}px;"
712
+ class="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}"
686
713
  onmouseenter={cancelClose}
687
714
  onmouseleave={scheduleClose}
688
715
  >
@@ -706,7 +733,9 @@
706
733
  to suppress this when the consumer provides its own trigger. -->
707
734
  <button
708
735
  type="button"
709
- class="fixed top-3 left-3 z-[80] inline-flex w-10 h-10 items-center justify-center cursor-pointer [background-color:var(--ui-color-background)] border [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] shadow-md [&_svg]:w-5 [&_svg]:h-5"
736
+ class="fixed top-3 {dockRight
737
+ ? 'right-3'
738
+ : 'left-3'} z-[80] inline-flex w-10 h-10 items-center justify-center cursor-pointer [background-color:var(--ui-color-background)] border [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] shadow-md [&_svg]:w-5 [&_svg]:h-5"
710
739
  aria-label={open ? "Close menu" : "Open menu"}
711
740
  aria-expanded={open}
712
741
  onclick={() => (open = !open)}
@@ -730,27 +759,28 @@
730
759
  width, so the expanded float below is positioned against the SIDEBAR's own box (container-relative,
731
760
  never the viewport). In every other mode it's `display: contents` (no box) so layout is unchanged. -->
732
761
  <div
733
- class={hoverExpandActive ? "relative shrink-0 [width:var(--sbm-collapsed-w)]" : "contents"}
734
- style="--sbm-collapsed-w: {collapsedWidth}px; --sbm-expanded-w: {expandedWidth}px;"
762
+ class={hoverExpandActive ? "relative shrink-0" : "contents"}
763
+ style="{hoverExpandActive ? `width: ${collapsedWidth}px;` : ''}"
735
764
  >
736
765
  <aside
737
766
  bind:this={asideRef}
767
+ style="width: {asideWidthCss};"
738
768
  onmouseenter={hoverExpandActive ? openHoverExpand : undefined}
739
769
  onmouseleave={hoverExpandActive ? scheduleHoverCollapse : undefined}
740
770
  class="
741
- flex flex-col z-[60] overflow-hidden
771
+ flex flex-col z-[60] overflow-hidden shrink-0
742
772
  [background-color:var(--ui-color-background)]
743
773
  [color:var(--ui-color-text)]
744
- [border-right:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]
774
+ {dockBorder}
745
775
  {currentMode === 'stepped'
746
- ? `absolute inset-y-0 left-0 w-full z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : '-translate-x-full'}`
776
+ ? `absolute inset-y-0 ${dockEdge} z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : dockOff}`
747
777
  : 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'}`
778
+ ? `absolute inset-y-0 ${dockEdge} z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : dockOff}`
749
779
  : currentMode === 'icon'
750
780
  ? 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)]'}
781
+ ? `absolute inset-y-0 ${dockEdge} z-[65] transition-[width] duration-200 ${effectivelyExpanded ? 'shadow-lg' : ''}`
782
+ : 'relative'
783
+ : 'relative'}
754
784
  {additionalClass}
755
785
  "
756
786
  >
@@ -780,9 +810,10 @@
780
810
  <Popup
781
811
  anchor={geom.anchor}
782
812
  open={openId === id}
783
- side="right"
813
+ side={popoutSide}
784
814
  align={geom.align}
785
815
  offset={0}
816
+ margin={0}
786
817
  onclose={() => {
787
818
  if (openId === id) openId = null;
788
819
  }}
@@ -816,9 +847,10 @@
816
847
  <Popup
817
848
  anchor={geom.anchor}
818
849
  open={openId === id}
819
- side="right"
850
+ side={popoutSide}
820
851
  align={geom.align}
821
852
  offset={0}
853
+ margin={0}
822
854
  onclose={() => {
823
855
  if (openId === id) openId = null;
824
856
  }}
@@ -127,6 +127,10 @@ type $$ComponentProps = {
127
127
  iconHoverExpand?: boolean;
128
128
  /** Exit delay (ms) before a hover-expanded rail collapses on mouse-leave. Default `200`. */
129
129
  hoverExpandCloseMs?: number;
130
+ /** Which edge the sidebar docks to. `"right"` mirrors everything: the frame border, the drawer
131
+ * slide direction, the hamburger corner, popout side + border, and item layout (icon on the
132
+ * right, labels right-aligned). Default `"left"`. */
133
+ side?: "left" | "right";
130
134
  /** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
131
135
  * (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
132
136
  * any open popout and returns a stepped drawer to the top. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.7.1-dev.43.72e77ad",
3
+ "version": "0.7.1-dev.45.41387ea",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"