@marianmeres/stuic 3.10.1 → 3.11.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/AGENTS.md CHANGED
@@ -23,7 +23,7 @@
23
23
 
24
24
  ```
25
25
  src/lib/
26
- ├── components/ # 37 UI components
26
+ ├── components/ # 39 UI components
27
27
  ├── actions/ # 12 Svelte actions
28
28
  ├── utils/ # 42 utility functions
29
29
  ├── themes/ # 26 theme definitions (.ts) + generated CSS (css/)
@@ -72,7 +72,7 @@ src/lib/
72
72
  - [Tasks](./docs/tasks.md) — Common procedures
73
73
 
74
74
  ### Domain Docs
75
- - [Components](./docs/domains/components.md) — 37 components, Props pattern, snippets
75
+ - [Components](./docs/domains/components.md) — 38 components, Props pattern, snippets
76
76
  - [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
77
77
  - [Actions](./docs/domains/actions.md) — 12 Svelte directives
78
78
  - [Utils](./docs/domains/utils.md) — 42 utility functions
package/API.md CHANGED
@@ -371,6 +371,223 @@ Shopping cart component with quantity controls, pricing, and summary. Supports i
371
371
  </Cart>
372
372
  ```
373
373
 
374
+ #### Checkout
375
+
376
+ Multi-step checkout flow with 13 sub-components: 9 atomic building blocks and 4 composite step pages.
377
+
378
+ **Atomic Components:**
379
+
380
+ ##### `CheckoutProgress`
381
+
382
+ Step indicator with navigation for past steps.
383
+
384
+ | Prop | Type | Default | Description |
385
+ |------|------|---------|-------------|
386
+ | `steps` | `CheckoutStep[]` | 4-step flow | Step definitions |
387
+ | `currentStep` | `string` | required | Active step ID |
388
+ | `onNavigate` | `(step: CheckoutStep) => void` | — | Past step click callback |
389
+ | `separator` | `Snippet \| string` | `"→"` | Step separator |
390
+ | `t` | `TranslateFn` | built-in | Translation function |
391
+
392
+ ##### `CheckoutOrderSummary`
393
+
394
+ Price totals display (subtotal, shipping, tax, discount, total).
395
+
396
+ | Prop | Type | Default | Description |
397
+ |------|------|---------|-------------|
398
+ | `totals` | `CheckoutOrderTotals` | required | Price totals |
399
+ | `hasShipping` | `boolean` | `true` | Show shipping row |
400
+ | `formatPrice` | `(value: number) => string` | `defaultFormatPrice` | Price formatter |
401
+ | `row` | `Snippet` | — | Custom row rendering |
402
+ | `extraRows` | `Snippet` | — | Extra content before total |
403
+
404
+ ##### `CheckoutCartReview`
405
+
406
+ Readonly cart display with summary.
407
+
408
+ | Prop | Type | Default | Description |
409
+ |------|------|---------|-------------|
410
+ | `items` | `CartComponentItem[]` | required | Cart items |
411
+ | `formatPrice` | `(value: number) => string` | `defaultFormatPrice` | Price formatter |
412
+ | `onEditCart` | `() => void` | — | Edit cart callback |
413
+ | `thumbnail` | `Snippet` | — | Custom thumbnail |
414
+ | `title` | `Snippet \| string` | `"Order Summary"` | Header title |
415
+
416
+ ##### `CheckoutGuestForm`
417
+
418
+ Guest checkout form with email, name, phone, and optional B2B fields.
419
+
420
+ | Prop | Type | Default | Description |
421
+ |------|------|---------|-------------|
422
+ | `formData` | `CheckoutCustomerFormData` | empty | Bindable form data |
423
+ | `onSubmit` | `(data: CheckoutCustomerFormData) => void` | required | Submit callback |
424
+ | `isSubmitting` | `boolean` | `false` | Disables CTA |
425
+ | `errors` | `CheckoutValidationError[]` | `[]` | Server validation errors |
426
+ | `showB2bFields` | `boolean` | `true` | Show B2B section |
427
+ | `fields` | `object` | all visible | Field visibility overrides |
428
+ | `validate` | `(data) => CheckoutValidationError[]` | — | Custom validator |
429
+
430
+ ##### `CheckoutLoginForm`
431
+
432
+ Login form with email and password.
433
+
434
+ | Prop | Type | Default | Description |
435
+ |------|------|---------|-------------|
436
+ | `formData` | `CheckoutLoginFormData` | empty | Bindable login data |
437
+ | `onSubmit` | `(data: CheckoutLoginFormData) => void` | required | Submit callback |
438
+ | `isSubmitting` | `boolean` | `false` | Disables CTA |
439
+ | `errors` | `CheckoutValidationError[]` | `[]` | Field errors |
440
+ | `error` | `string` | — | General error message |
441
+ | `onForgotPassword` | `() => void` | — | Forgot password callback |
442
+ | `footer` | `Snippet` | — | Content below form |
443
+
444
+ ##### `CheckoutAddressForm`
445
+
446
+ Address input fieldset with configurable required fields.
447
+
448
+ | Prop | Type | Default | Description |
449
+ |------|------|---------|-------------|
450
+ | `address` | `CheckoutAddressData` | empty | Bindable address data |
451
+ | `label` | `string` | `"address"` | Prefix for field IDs |
452
+ | `errors` | `CheckoutValidationError[]` | `[]` | Validation errors |
453
+ | `requiredFields` | `string[]` | name, street, city, postal_code, country | Required fields |
454
+ | `countryField` | `Snippet` | — | Custom country field (replaces text input) |
455
+
456
+ ##### `CheckoutDeliveryOptions`
457
+
458
+ Delivery method radio selection with free shipping threshold logic.
459
+
460
+ | Prop | Type | Default | Description |
461
+ |------|------|---------|-------------|
462
+ | `options` | `CheckoutDeliveryOption[]` | required | Available delivery options |
463
+ | `selectedId` | `string` | — | Bindable selected option ID |
464
+ | `onSelect` | `(optionId: string) => void` | — | Selection callback |
465
+ | `subtotal` | `number` | `0` | Order subtotal (for free shipping calc) |
466
+ | `isUpdating` | `boolean` | `false` | Reduced opacity during API calls |
467
+ | `formatPrice` | `(value: number) => string` | `defaultFormatPrice` | Price formatter |
468
+ | `option` | `Snippet` | — | Custom option card |
469
+
470
+ ##### `CheckoutOrderReview`
471
+
472
+ Full order review display with per-section edit callbacks.
473
+
474
+ | Prop | Type | Default | Description |
475
+ |------|------|---------|-------------|
476
+ | `order` | `CheckoutOrderData` | required | Order data |
477
+ | `formatPrice` | `(value: number) => string` | `defaultFormatPrice` | Price formatter |
478
+ | `onEditItems` | `() => void` | — | Edit items callback |
479
+ | `onEditShippingAddress` | `() => void` | — | Edit shipping callback |
480
+ | `onEditBillingAddress` | `() => void` | — | Edit billing callback |
481
+ | `onEditDelivery` | `() => void` | — | Edit delivery callback |
482
+
483
+ ##### `CheckoutOrderConfirmation`
484
+
485
+ Order success screen with details and continue shopping CTA.
486
+
487
+ | Prop | Type | Default | Description |
488
+ |------|------|---------|-------------|
489
+ | `order` | `CheckoutOrderData` | required | Completed order |
490
+ | `orderId` | `string` | required | Order ID for display |
491
+ | `emailSent` | `boolean` | `false` | Show email confirmation |
492
+ | `formatPrice` | `(value: number) => string` | `defaultFormatPrice` | Price formatter |
493
+ | `onContinueShopping` | `() => void` | — | Continue shopping callback |
494
+
495
+ **Composite Step Components:**
496
+
497
+ ##### `CheckoutReviewStep`
498
+
499
+ Cart review + guest/login forms in 2-column layout.
500
+
501
+ | Prop | Type | Default | Description |
502
+ |------|------|---------|-------------|
503
+ | `items` | `CartComponentItem[]` | required | Cart items |
504
+ | `guestForm` | `object` | — | Guest form config (`formData`, `onSubmit`, `isSubmitting`, `errors`) |
505
+ | `loginForm` | `object` | — | Login form config (`formData`, `onSubmit`, `isSubmitting`, `errors`, `onForgotPassword`) |
506
+ | `formMode` | `"guest-only" \| "login-only" \| "tabbed" \| "stacked"` | `"tabbed"` | Form display mode |
507
+ | `onEditCart` | `() => void` | — | Edit cart callback |
508
+ | `formatPrice` | `(v: number) => string` | `defaultFormatPrice` | Price formatter |
509
+ | `leftColumn` | `Snippet` | — | Override left column |
510
+ | `rightColumn` | `Snippet` | — | Override right column |
511
+
512
+ ##### `CheckoutShippingStep`
513
+
514
+ Shipping/billing addresses + delivery selection with sidebar summary.
515
+
516
+ | Prop | Type | Default | Description |
517
+ |------|------|---------|-------------|
518
+ | `order` | `CheckoutOrderData` | required | Order data for totals |
519
+ | `deliveryOptions` | `CheckoutDeliveryOption[]` | required | Available delivery options |
520
+ | `shippingAddress` | `CheckoutAddressData` | — | Bindable shipping address |
521
+ | `billingAddress` | `CheckoutAddressData` | — | Bindable billing address |
522
+ | `billingSameAsShipping` | `boolean` | `true` | Bindable toggle |
523
+ | `selectedDeliveryId` | `string` | — | Bindable delivery selection |
524
+ | `onContinue` | `() => void` | — | Continue callback |
525
+ | `onBack` | `() => void` | — | Back callback |
526
+ | `countryField` | `Snippet` | — | Custom country field |
527
+
528
+ ##### `CheckoutConfirmStep`
529
+
530
+ Order review + place order CTA with sidebar summary.
531
+
532
+ | Prop | Type | Default | Description |
533
+ |------|------|---------|-------------|
534
+ | `order` | `CheckoutOrderData` | required | Order to confirm |
535
+ | `validationErrors` | `CheckoutValidationError[]` | `[]` | Validation errors |
536
+ | `isValid` | `boolean` | `true` | Enable/disable place order |
537
+ | `isSubmitting` | `boolean` | `false` | Processing state |
538
+ | `onPlaceOrder` | `() => void` | — | Place order callback |
539
+ | `onBack` | `() => void` | — | Back callback |
540
+ | `onEditItems` | `() => void` | — | Edit items callback |
541
+ | `onEditShippingAddress` | `() => void` | — | Edit shipping callback |
542
+
543
+ ##### `CheckoutCompleteStep`
544
+
545
+ Order confirmation with loading/error/success states.
546
+
547
+ | Prop | Type | Default | Description |
548
+ |------|------|---------|-------------|
549
+ | `order` | `CheckoutOrderData` | required | Completed order |
550
+ | `orderId` | `string` | required | Order ID |
551
+ | `emailSent` | `boolean` | `false` | Email confirmation flag |
552
+ | `isLoading` | `boolean` | `false` | Show loading skeleton |
553
+ | `error` | `string \| null` | — | Error message |
554
+ | `onContinueShopping` | `() => void` | — | Continue shopping callback |
555
+ | `onReturnToCheckout` | `() => void` | — | Return callback (error state) |
556
+
557
+ All composite step components also accept `currentStep`, `steps`, `onStepNavigate` (for CheckoutProgress), and `t` (TranslateFn).
558
+
559
+ **Checkout Utilities:**
560
+
561
+ ```ts
562
+ import {
563
+ defaultFormatPrice, // (cents: number) => string — "12.99"
564
+ validateEmail, // (email, t) => string | null
565
+ validateAddress, // (address, prefix, t) => CheckoutValidationError[]
566
+ validateCustomerForm, // (data, t) => CheckoutValidationError[]
567
+ validateLoginForm, // (data, t) => CheckoutValidationError[]
568
+ createEmptyAddress, // () => CheckoutAddressData
569
+ createEmptyCustomerFormData, // () => CheckoutCustomerFormData
570
+ createEmptyLoginFormData, // () => CheckoutLoginFormData
571
+ } from "@marianmeres/stuic";
572
+ ```
573
+
574
+ **Checkout Types:**
575
+
576
+ ```ts
577
+ import type {
578
+ CheckoutStep,
579
+ CheckoutAddressData,
580
+ CheckoutCustomerFormData,
581
+ CheckoutLoginFormData,
582
+ CheckoutOrderLineItem,
583
+ CheckoutOrderTotals,
584
+ CheckoutDeliveryOption,
585
+ CheckoutDeliverySnapshot,
586
+ CheckoutOrderData,
587
+ CheckoutValidationError,
588
+ } from "@marianmeres/stuic";
589
+ ```
590
+
374
591
  ---
375
592
 
376
593
  ### Display
@@ -406,6 +623,32 @@ Image/content slider with navigation.
406
623
 
407
624
  Animated loading dots ("...").
408
625
 
626
+ #### `IconSwap`
627
+
628
+ Swap visibility between N states with opacity transitions. Commonly used for hamburger/X icon toggle, but supports any content.
629
+
630
+ | Prop | Type | Default | Description |
631
+ |------|------|---------|-------------|
632
+ | `states` | `Array<string \| Snippet>` | required | Array of visual states (HTML strings or Snippets) |
633
+ | `active` | `number` | `0` | Index of the currently visible state (bindable) |
634
+ | `duration` | `number` | `300` | Transition duration in ms |
635
+ | `easing` | `string` | `"ease"` | CSS transition-timing-function |
636
+ | `stateClass` | `string` | — | Additional CSS classes for each state wrapper |
637
+
638
+ **CSS Variables:** `--stuic-icon-swap-duration`, `--stuic-icon-swap-easing`
639
+
640
+ ```svelte
641
+ <script>
642
+ import { IconSwap } from "@marianmeres/stuic";
643
+ import { iconMenu, iconX } from "@marianmeres/stuic";
644
+ let isOpen = $state(false);
645
+ </script>
646
+
647
+ <button onclick={() => (isOpen = !isOpen)}>
648
+ <IconSwap active={isOpen ? 1 : 0} states={[iconMenu(), iconX()]} />
649
+ </button>
650
+ ```
651
+
409
652
  #### `DataTable`
410
653
 
411
654
  Responsive data table with paging, row selection, batch actions, and mobile card view.
@@ -970,6 +1213,7 @@ Naming pattern: `{ComponentName}Props`
970
1213
 
971
1214
  Additional exported types include:
972
1215
  - `CartComponentItem`, `CartVariant` — Cart component types
1216
+ - `CheckoutStep`, `CheckoutAddressData`, `CheckoutCustomerFormData`, `CheckoutLoginFormData`, `CheckoutOrderLineItem`, `CheckoutOrderTotals`, `CheckoutDeliveryOption`, `CheckoutDeliverySnapshot`, `CheckoutOrderData`, `CheckoutValidationError` — Checkout types
973
1217
  - `DataTableColumn` — DataTable column definition type
974
1218
  - `FieldAsset`, `FieldAssetUrlObj`, `FieldAssetWithBlobUrl` — Asset field types
975
1219
  - `FieldOption` — Option type for FieldOptions
@@ -1055,6 +1299,7 @@ Each component defines customization tokens. Override globally in `:root {}` or
1055
1299
  | Popover | `--stuic-popover-*` | `bg`, `text`, `border` |
1056
1300
  | Skeleton | `--stuic-skeleton-*` | `bg`, `bg-highlight`, `duration` |
1057
1301
  | Cart | `--stuic-cart-*` | `gap`, `item-padding`, `item-radius`, `item-border-color`, `item-bg`, `thumbnail-size`, `quantity-border-color`, `remove-color`, `summary-border-color`, `compact-max-height`, `transition` |
1302
+ | Checkout | `--stuic-checkout-*` | `input-border`, `input-bg`, `input-focus-ring`, `input-radius`, `card-border`, `card-bg`, `card-radius`, `step-gap`, `progress-*`, `summary-*`, `guest-*`, `login-*`, `address-*`, `delivery-*`, `review-*`, `confirmation-*` |
1058
1303
 
1059
1304
  ### CSS Variable Naming Convention
1060
1305
 
package/README.md CHANGED
@@ -144,7 +144,7 @@ CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
144
144
  Avatar, Carousel, AnimatedElipsis, ThemePreview, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, DataTable
145
145
 
146
146
  ### E-commerce
147
- Cart
147
+ Cart, Checkout (CheckoutProgress, CheckoutOrderSummary, CheckoutCartReview, CheckoutGuestForm, CheckoutLoginForm, CheckoutAddressForm, CheckoutDeliveryOptions, CheckoutOrderReview, CheckoutOrderConfirmation, CheckoutReviewStep, CheckoutShippingStep, CheckoutConfirmStep, CheckoutCompleteStep)
148
148
 
149
149
  ## Actions
150
150
 
@@ -7,6 +7,8 @@ export interface ResizableWidthOptions {
7
7
  min?: number;
8
8
  max?: number;
9
9
  units?: "px" | "%";
10
+ /** Reverses handle position (left instead of right) and drag direction */
11
+ reverse?: boolean;
10
12
  key?: string | number | null | undefined;
11
13
  storage?: "local" | "session";
12
14
  handleClass?: string;
@@ -21,7 +23,8 @@ export interface ResizableWidthOptions {
21
23
  /**
22
24
  * A Svelte action that makes an element's width resizable via drag handle.
23
25
  *
24
- * Adds a draggable handle to the right edge of the element. Supports mouse and touch input.
26
+ * Adds a draggable handle to the right (or left with `reverse`) edge of the element.
27
+ * Supports mouse and touch input.
25
28
  * Optionally persists the width to localStorage/sessionStorage.
26
29
  *
27
30
  * Features:
@@ -3,7 +3,8 @@ import { twMerge } from "../utils/tw-merge.js";
3
3
  /**
4
4
  * A Svelte action that makes an element's width resizable via drag handle.
5
5
  *
6
- * Adds a draggable handle to the right edge of the element. Supports mouse and touch input.
6
+ * Adds a draggable handle to the right (or left with `reverse`) edge of the element.
7
+ * Supports mouse and touch input.
7
8
  * Optionally persists the width to localStorage/sessionStorage.
8
9
  *
9
10
  * Features:
@@ -43,9 +44,9 @@ import { twMerge } from "../utils/tw-merge.js";
43
44
  * ```
44
45
  */
45
46
  export function resizableWidth(el, fn) {
46
- const DEFAULT_HANDLE_CLS = [
47
+ const HANDLE_CLS_BASE = [
47
48
  "group",
48
- "absolute top-0 right-0 bottom-0",
49
+ "absolute top-0 bottom-0",
49
50
  "w-[1px]",
50
51
  "bg-black/20 hover:bg-black/30",
51
52
  "dark:bg-white/10 dark:hover:bg-white/20",
@@ -61,7 +62,7 @@ export function resizableWidth(el, fn) {
61
62
  "transition-colors duration-200",
62
63
  "touch-none cursor-ew-resize",
63
64
  ].join(" ");
64
- function create_handle(el, handleClass, handleDragClass) {
65
+ function create_handle(el, handleClass, handleDragClass, reverse) {
65
66
  const handle = document.createElement("div");
66
67
  handle.setAttribute("data-handle", "true");
67
68
  const dragHandle = document.createElement("div");
@@ -69,11 +70,12 @@ export function resizableWidth(el, fn) {
69
70
  handle.appendChild(dragHandle);
70
71
  el.appendChild(handle);
71
72
  //
72
- handle.classList.add(...twMerge(DEFAULT_HANDLE_CLS, handleClass).split(" "));
73
+ const positionCls = reverse ? "left-0" : "right-0";
74
+ handle.classList.add(...twMerge(HANDLE_CLS_BASE, positionCls, handleClass).split(" "));
73
75
  return handle;
74
76
  }
75
77
  $effect(() => {
76
- const { enabled = true, initial: initialValue = 0, min = 0, max = 0, units = "px", key, storage = "session", handleClass = "", handleDragClass = "", onResize, debug, } = fn?.() || {};
78
+ const { enabled = true, initial: initialValue = 0, min = 0, max = 0, units = "px", reverse = false, key, storage = "session", handleClass = "", handleDragClass = "", onResize, debug, } = fn?.() || {};
77
79
  let initial = initialValue;
78
80
  const _debug = (...args) => debug?.("[resizable-width]", ...args);
79
81
  _debug("$effect");
@@ -86,7 +88,7 @@ export function resizableWidth(el, fn) {
86
88
  let startWidth = 0;
87
89
  let containerW = undefined;
88
90
  //
89
- const handle = create_handle(el, handleClass, handleDragClass);
91
+ const handle = create_handle(el, handleClass, handleDragClass, reverse);
90
92
  const container = el.parentElement;
91
93
  // do we have a storage? if so, adjust the initial value...
92
94
  const initialBackup = initial;
@@ -149,7 +151,7 @@ export function resizableWidth(el, fn) {
149
151
  //
150
152
  const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
151
153
  const deltaX = clientX - startX;
152
- const width = startWidth + deltaX;
154
+ const width = reverse ? startWidth - deltaX : startWidth + deltaX;
153
155
  set_width_px(width);
154
156
  }
155
157
  function resize_stop() {
@@ -0,0 +1,67 @@
1
+ <script lang="ts" module>
2
+ import type { Snippet } from "svelte";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ export interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
6
+ /** Array of visual states (HTML strings or Snippets) */
7
+ states: Array<string | Snippet>;
8
+ /** Index of the currently visible state (0-based) */
9
+ active?: number;
10
+ /** Transition duration in ms (default: 300). Set 0 to disable. */
11
+ duration?: number;
12
+ /** CSS transition-timing-function (default: "ease") */
13
+ easing?: string;
14
+ /** Skip all default styling */
15
+ unstyled?: boolean;
16
+ /** Additional CSS classes for the container */
17
+ class?: string;
18
+ /** Additional CSS classes for each state wrapper */
19
+ stateClass?: string;
20
+ /** Bindable root element reference */
21
+ el?: HTMLSpanElement;
22
+ }
23
+ </script>
24
+
25
+ <script lang="ts">
26
+ import { twMerge } from "../../utils/tw-merge.js";
27
+ import { prefersReducedMotion } from "../../utils/prefers-reduced-motion.svelte.js";
28
+
29
+ let {
30
+ states,
31
+ active = $bindable(0),
32
+ duration: durationProp = 300,
33
+ easing = "ease",
34
+ unstyled = false,
35
+ class: classProp,
36
+ stateClass,
37
+ el = $bindable(),
38
+ ...rest
39
+ }: Props = $props();
40
+
41
+ const prefersReduced = prefersReducedMotion();
42
+ let duration = $derived(prefersReduced.current ? 0 : durationProp);
43
+ let _active = $derived(Math.max(0, Math.min(active, states.length - 1)));
44
+ </script>
45
+
46
+ <span
47
+ bind:this={el}
48
+ class={unstyled ? classProp : twMerge("stuic-icon-swap", classProp)}
49
+ style:--stuic-icon-swap-duration="{duration}ms"
50
+ style:--stuic-icon-swap-easing={easing}
51
+ data-active={_active}
52
+ {...rest}
53
+ >
54
+ {#each states as state, i (i)}
55
+ <span
56
+ class={unstyled ? stateClass : twMerge("stuic-icon-swap-state", stateClass)}
57
+ data-visible={i === _active ? "true" : undefined}
58
+ aria-hidden={i !== _active}
59
+ >
60
+ {#if typeof state === "string"}
61
+ {@html state}
62
+ {:else}
63
+ {@render state()}
64
+ {/if}
65
+ </span>
66
+ {/each}
67
+ </span>
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { HTMLAttributes } from "svelte/elements";
3
+ export interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
4
+ /** Array of visual states (HTML strings or Snippets) */
5
+ states: Array<string | Snippet>;
6
+ /** Index of the currently visible state (0-based) */
7
+ active?: number;
8
+ /** Transition duration in ms (default: 300). Set 0 to disable. */
9
+ duration?: number;
10
+ /** CSS transition-timing-function (default: "ease") */
11
+ easing?: string;
12
+ /** Skip all default styling */
13
+ unstyled?: boolean;
14
+ /** Additional CSS classes for the container */
15
+ class?: string;
16
+ /** Additional CSS classes for each state wrapper */
17
+ stateClass?: string;
18
+ /** Bindable root element reference */
19
+ el?: HTMLSpanElement;
20
+ }
21
+ declare const IconSwap: import("svelte").Component<Props, {}, "active" | "el">;
22
+ type IconSwap = ReturnType<typeof IconSwap>;
23
+ export default IconSwap;
@@ -0,0 +1,33 @@
1
+ /* ============================================================================
2
+ ICON SWAP COMPONENT TOKENS
3
+ Override globally: :root { --stuic-icon-swap-duration: 500ms; }
4
+ Override locally: <IconSwap style="--stuic-icon-swap-duration: 500ms;">
5
+ ============================================================================ */
6
+
7
+ :root {
8
+ --stuic-icon-swap-duration: 150ms;
9
+ --stuic-icon-swap-easing: ease;
10
+ }
11
+
12
+ @layer components {
13
+ .stuic-icon-swap {
14
+ display: grid;
15
+ width: fit-content;
16
+ }
17
+
18
+ .stuic-icon-swap > .stuic-icon-swap-state {
19
+ grid-column: 1 / -1;
20
+ grid-row: 1 / -1;
21
+ display: inline-flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ opacity: 0;
25
+ pointer-events: none;
26
+ transition: opacity var(--stuic-icon-swap-duration) var(--stuic-icon-swap-easing);
27
+ }
28
+
29
+ .stuic-icon-swap > .stuic-icon-swap-state[data-visible="true"] {
30
+ opacity: 1;
31
+ pointer-events: auto;
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ export { default as IconSwap, type Props as IconSwapProps } from "./IconSwap.svelte";
@@ -0,0 +1 @@
1
+ export { default as IconSwap } from "./IconSwap.svelte";
@@ -0,0 +1,107 @@
1
+ # WithSidePanel
2
+
3
+ A flex layout component with a collapsible side panel and main content area.
4
+ Both panels consume full height with independent scrolling. The side panel
5
+ auto-hides when the container width falls below a configurable threshold
6
+ (container-based, not viewport-based), behaving like a mobile drawer.
7
+
8
+ ## Props
9
+
10
+ | Prop | Type | Default | Description |
11
+ |------|------|---------|-------------|
12
+ | `children` | `Snippet` | — | Main content |
13
+ | `side` | `Snippet` | — | Side panel content |
14
+ | `position` | `"left" \| "right"` | `"left"` | Side panel position |
15
+ | `width` | `string` | `"300px"` | Side panel width (CSS value, px or %) |
16
+ | `threshold` | `number` | `768` | Container width (px) below which side auto-hides |
17
+ | `transition` | `boolean` | `true` | Enable Svelte slide transition |
18
+ | `transitionDuration` | `number` | `200` | Transition duration in ms |
19
+ | `open` | `boolean` | `true` | Desktop visibility state (bindable) |
20
+ | `resizable` | `boolean \| Partial<ResizableWidthOptions>` | `false` | Enable drag-resizable side panel |
21
+ | `classMain` | `string` | — | Custom class for main content div |
22
+ | `classSide` | `string` | — | Custom class for side panel div |
23
+ | `class` | `string` | — | Custom class for wrapper div |
24
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
25
+ | `el` | `HTMLDivElement` | — | Bindable wrapper element reference |
26
+
27
+ ## API (via `bind:this`)
28
+
29
+ | Method | Returns | Description |
30
+ |--------|---------|-------------|
31
+ | `show()` | `void` | Show the side panel |
32
+ | `hide()` | `void` | Hide the side panel |
33
+ | `toggle()` | `void` | Toggle the side panel |
34
+ | `setWidth(w: string)` | `void` | Set side panel width dynamically |
35
+ | `current()` | `{ open: boolean, small: boolean }` | Current state |
36
+
37
+ ## Usage
38
+
39
+ ### Basic
40
+
41
+ ```svelte
42
+ <WithSidePanel>
43
+ {#snippet side()}
44
+ <nav>Navigation</nav>
45
+ {/snippet}
46
+ Main content
47
+ </WithSidePanel>
48
+ ```
49
+
50
+ ### Programmatic control
51
+
52
+ ```svelte
53
+ <script>
54
+ import { WithSidePanel } from '@marianmeres/stuic';
55
+ let panel;
56
+ </script>
57
+
58
+ <button onclick={() => panel.toggle()}>Toggle</button>
59
+
60
+ <WithSidePanel bind:this={panel} width="250px" position="right">
61
+ {#snippet side()}
62
+ Side content
63
+ {/snippet}
64
+ Main content
65
+ </WithSidePanel>
66
+ ```
67
+
68
+ ### Resizable
69
+
70
+ ```svelte
71
+ <WithSidePanel resizable={{ min: 200, max: 500, key: 'sidebar', storage: 'local' }}>
72
+ {#snippet side()}
73
+ Drag the edge to resize
74
+ {/snippet}
75
+ Main content
76
+ </WithSidePanel>
77
+ ```
78
+
79
+ ### Tracking state
80
+
81
+ ```svelte
82
+ <script>
83
+ let isOpen = $state(true);
84
+ </script>
85
+
86
+ <WithSidePanel bind:open={isOpen} threshold={600}>
87
+ {#snippet side()}
88
+ Panel is {isOpen ? 'open' : 'closed'}
89
+ {/snippet}
90
+ Main content
91
+ </WithSidePanel>
92
+ ```
93
+
94
+ ## CSS Variables
95
+
96
+ | Variable | Default | Description |
97
+ |----------|---------|-------------|
98
+ | `--stuic-with-side-panel-border-color` | `gray-200` / `gray-700` (dark) | Border between panels |
99
+ | `--stuic-with-side-panel-border-width` | `1px` | Border width |
100
+
101
+ ## Data Attributes (on wrapper)
102
+
103
+ | Attribute | Values | Description |
104
+ |-----------|--------|-------------|
105
+ | `data-position` | `"left"` \| `"right"` | Current side panel position |
106
+ | `data-small` | present/absent | Container is below threshold |
107
+ | `data-open` | present/absent | Side panel is visible |