@streamscloud/kit 0.9.3 → 0.9.5

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.
@@ -1,30 +1,48 @@
1
1
  <script lang="ts">import { Image } from '../image';
2
- let { src = null, name = '', size = 'md', status = null } = $props();
3
- const initials = $derived(name
2
+ let { src = null, name = '', size = 'md', status = null, badge = null } = $props();
3
+ const deriveInitials = (value) => value
4
4
  .split(/\s+/)
5
5
  .filter(Boolean)
6
6
  .slice(0, 2)
7
7
  .map((p) => p[0])
8
8
  .join('')
9
- .toUpperCase());
9
+ .toUpperCase();
10
+ const initials = $derived(deriveInitials(name));
11
+ const badgeInitials = $derived(deriveInitials(badge?.name ?? ''));
10
12
  </script>
11
13
 
14
+ {#snippet personIcon()}
15
+ <span class="avatar__fallback" aria-hidden="true">
16
+ <svg class="avatar__fallback-icon" viewBox="0 0 24 24">
17
+ <circle cx="12" cy="9" r="4" fill="currentColor" opacity="0.6"></circle>
18
+ <path d="M4 21c0-4 4-7 8-7s8 3 8 7" fill="currentColor" opacity="0.6"></path>
19
+ </svg>
20
+ </span>
21
+ {/snippet}
22
+
12
23
  {#snippet fallback()}
13
24
  {#if name && initials}
14
25
  <span class="avatar__initials">{initials}</span>
15
26
  {:else}
16
- <span class="avatar__fallback" aria-hidden="true">
17
- <svg class="avatar__fallback-icon" viewBox="0 0 24 24">
18
- <circle cx="12" cy="9" r="4" fill="currentColor" opacity="0.6"></circle>
19
- <path d="M4 21c0-4 4-7 8-7s8 3 8 7" fill="currentColor" opacity="0.6"></path>
20
- </svg>
21
- </span>
27
+ {@render personIcon()}
22
28
  {/if}
23
29
  {/snippet}
24
30
 
25
- <span class="avatar avatar--{size}" class:avatar--with-status={!!status} role="img" aria-label={name || 'Avatar'}>
31
+ {#snippet badgeFallback()}
32
+ {#if badge?.name && badgeInitials}
33
+ <span class="avatar__initials avatar__initials--badge">{badgeInitials}</span>
34
+ {:else}
35
+ {@render personIcon()}
36
+ {/if}
37
+ {/snippet}
38
+
39
+ <span class="avatar avatar--{size}" class:avatar--with-status={!!status && !badge} role="img" aria-label={name || 'Avatar'}>
26
40
  <Image src={src} alt={name} showStubOnError stub={fallback} />
27
- {#if status}
41
+ {#if badge}
42
+ <span class="avatar__badge" role={badge.name ? 'img' : undefined} aria-label={badge.name || undefined} aria-hidden={badge.name ? undefined : true}>
43
+ <Image src={badge.src ?? null} alt={badge.name ?? ''} showStubOnError stub={badgeFallback} />
44
+ </span>
45
+ {:else if status}
28
46
  <span class="avatar__dot avatar__dot--{status}" aria-hidden="true"></span>
29
47
  {/if}
30
48
  </span>
@@ -33,13 +51,16 @@ const initials = $derived(name
33
51
  @component
34
52
  A circular avatar with three render modes (priority order): image → initials (derived from `name`)
35
53
  → generic person icon. If `src` fails to load, automatically falls back to initials/icon. Optional
36
- status dot in the bottom-right corner. Built on top of the kit `Image` component, which handles
37
- the load / error / stub state machine.
54
+ status dot in the bottom-right corner, or a square `badge` mini-avatar in the same corner (the
55
+ workspace/org-with-user pattern, same fallback chain at badge scale) — `badge` and `status` are
56
+ mutually exclusive, `badge` wins. Built on top of the kit `Image` component, which handles the
57
+ load / error / stub state machine.
38
58
 
39
59
  ### CSS Custom Properties
40
60
  | Property | Description | Default |
41
61
  |---|---|---|
42
62
  | `--sc-kit--avatar--size` | Diameter (overrides size preset) | per `size` preset (20 / 24 / 32 / 40 / 56 px) |
63
+ | `--sc-kit--avatar--border-radius` | Avatar shape (set e.g. `--sc-kit--radius--sm` for a rounded square) | `50%` |
43
64
  | `--sc-kit--avatar--background` | Fallback container background (initials / icon mode) | `--sc-kit--color--bg--active` |
44
65
  | `--sc-kit--avatar--color` | Fallback foreground (generic icon tint) | `--sc-kit--color--text--muted` |
45
66
  | `--sc-kit--avatar--initials--background` | Initials chip background | `--sc-kit--color--accent--soft` |
@@ -48,6 +69,9 @@ the load / error / stub state machine.
48
69
  | `--sc-kit--avatar--dot--color-online` | Online dot color | `--sc-kit--color--success` |
49
70
  | `--sc-kit--avatar--dot--color-busy` | Busy dot color | `--sc-kit--color--danger` |
50
71
  | `--sc-kit--avatar--dot--color-offline` | Offline dot color | `--sc-kit--color--text--muted` |
72
+ | `--sc-kit--avatar--badge--size` | Badge mini-avatar box | 45% of the avatar diameter |
73
+ | `--sc-kit--avatar--badge--ring-color` | Badge separation ring (override on active/hover row backgrounds to blend) | `--sc-kit--color--bg--panel` |
74
+ | `--sc-kit--avatar--badge--ring-width` | Badge separation ring thickness | `1.5px` |
51
75
  -->
52
76
 
53
77
  <style>.avatar {
@@ -59,7 +83,11 @@ the load / error / stub state machine.
59
83
  --_avatar--dot-online: var(--sc-kit--avatar--dot--color-online, var(--sc-kit--color--success));
60
84
  --_avatar--dot-busy: var(--sc-kit--avatar--dot--color-busy, var(--sc-kit--color--danger));
61
85
  --_avatar--dot-offline: var(--sc-kit--avatar--dot--color-offline, var(--sc-kit--color--text--muted));
62
- --sc-kit--image--border-radius: 50%;
86
+ --_avatar--badge-size: var(--sc-kit--avatar--badge--size, calc(var(--_avatar--size) * 0.45));
87
+ --_avatar--badge-ring-color: var(--sc-kit--avatar--badge--ring-color, var(--sc-kit--color--bg--panel));
88
+ --_avatar--badge-ring-width: var(--sc-kit--avatar--badge--ring-width, 1.5px);
89
+ --_avatar--border-radius: var(--sc-kit--avatar--border-radius, 50%);
90
+ --sc-kit--image--border-radius: var(--_avatar--border-radius);
63
91
  --sc-kit--image--background: var(--_avatar--background);
64
92
  position: relative;
65
93
  display: flex;
@@ -67,7 +95,7 @@ the load / error / stub state machine.
67
95
  justify-content: center;
68
96
  width: var(--_avatar--size);
69
97
  height: var(--_avatar--size);
70
- border-radius: 50%;
98
+ border-radius: var(--_avatar--border-radius);
71
99
  flex-shrink: 0;
72
100
  background: var(--_avatar--background);
73
101
  color: var(--_avatar--color);
@@ -91,7 +119,7 @@ the load / error / stub state machine.
91
119
  .avatar__initials {
92
120
  width: 100%;
93
121
  height: 100%;
94
- border-radius: 50%;
122
+ border-radius: var(--_avatar--border-radius);
95
123
  display: flex;
96
124
  align-items: center;
97
125
  justify-content: center;
@@ -102,6 +130,25 @@ the load / error / stub state machine.
102
130
  line-height: var(--sc-kit--leading--tight);
103
131
  user-select: none;
104
132
  }
133
+ .avatar__initials--badge {
134
+ border-radius: var(--sc-kit--radius--sm);
135
+ font-size: calc(var(--_avatar--badge-size) * 0.5);
136
+ }
137
+ .avatar__badge {
138
+ position: absolute;
139
+ right: -0.125rem;
140
+ bottom: -0.125rem;
141
+ width: var(--_avatar--badge-size);
142
+ height: var(--_avatar--badge-size);
143
+ border-radius: var(--sc-kit--radius--sm);
144
+ box-shadow: 0 0 0 var(--_avatar--badge-ring-width) var(--_avatar--badge-ring-color);
145
+ overflow: hidden;
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ background: var(--_avatar--background);
150
+ --sc-kit--image--border-radius: var(--sc-kit--radius--sm);
151
+ }
105
152
  .avatar__fallback {
106
153
  width: 100%;
107
154
  height: 100%;
@@ -1,5 +1,4 @@
1
- type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
2
- type AvatarStatus = 'online' | 'busy' | 'offline';
1
+ import type { AvatarData, AvatarSize, AvatarStatus } from './types';
3
2
  type Props = {
4
3
  /** Image URL. If omitted or fails to load, falls back to initials, then to a generic icon. */
5
4
  src?: string | null;
@@ -7,19 +6,24 @@ type Props = {
7
6
  name?: string;
8
7
  /** Size preset. @default 'md' */
9
8
  size?: AvatarSize;
10
- /** Optional status dot rendered in the bottom-right corner. */
9
+ /** Optional status dot rendered in the bottom-right corner. Ignored while `badge` is set — both occupy the same corner. */
11
10
  status?: AvatarStatus | null;
11
+ /** Secondary mini-avatar layered on the bottom-right corner — the workspace/org-with-user pattern. Reuses the image → initials → icon fallback chain at badge scale. Takes the corner over `status`. */
12
+ badge?: AvatarData | null;
12
13
  };
13
14
  /**
14
15
  * A circular avatar with three render modes (priority order): image → initials (derived from `name`)
15
16
  * → generic person icon. If `src` fails to load, automatically falls back to initials/icon. Optional
16
- * status dot in the bottom-right corner. Built on top of the kit `Image` component, which handles
17
- * the load / error / stub state machine.
17
+ * status dot in the bottom-right corner, or a square `badge` mini-avatar in the same corner (the
18
+ * workspace/org-with-user pattern, same fallback chain at badge scale) — `badge` and `status` are
19
+ * mutually exclusive, `badge` wins. Built on top of the kit `Image` component, which handles the
20
+ * load / error / stub state machine.
18
21
  *
19
22
  * ### CSS Custom Properties
20
23
  * | Property | Description | Default |
21
24
  * |---|---|---|
22
25
  * | `--sc-kit--avatar--size` | Diameter (overrides size preset) | per `size` preset (20 / 24 / 32 / 40 / 56 px) |
26
+ * | `--sc-kit--avatar--border-radius` | Avatar shape (set e.g. `--sc-kit--radius--sm` for a rounded square) | `50%` |
23
27
  * | `--sc-kit--avatar--background` | Fallback container background (initials / icon mode) | `--sc-kit--color--bg--active` |
24
28
  * | `--sc-kit--avatar--color` | Fallback foreground (generic icon tint) | `--sc-kit--color--text--muted` |
25
29
  * | `--sc-kit--avatar--initials--background` | Initials chip background | `--sc-kit--color--accent--soft` |
@@ -28,6 +32,9 @@ type Props = {
28
32
  * | `--sc-kit--avatar--dot--color-online` | Online dot color | `--sc-kit--color--success` |
29
33
  * | `--sc-kit--avatar--dot--color-busy` | Busy dot color | `--sc-kit--color--danger` |
30
34
  * | `--sc-kit--avatar--dot--color-offline` | Offline dot color | `--sc-kit--color--text--muted` |
35
+ * | `--sc-kit--avatar--badge--size` | Badge mini-avatar box | 45% of the avatar diameter |
36
+ * | `--sc-kit--avatar--badge--ring-color` | Badge separation ring (override on active/hover row backgrounds to blend) | `--sc-kit--color--bg--panel` |
37
+ * | `--sc-kit--avatar--badge--ring-width` | Badge separation ring thickness | `1.5px` |
31
38
  */
32
39
  declare const Cmp: import("svelte").Component<Props, {}, "">;
33
40
  type Cmp = ReturnType<typeof Cmp>;
@@ -1 +1,2 @@
1
1
  export { default as Avatar } from './cmp.avatar.svelte';
2
+ export type { AvatarData, AvatarSize, AvatarStatus } from './types';
@@ -0,0 +1,8 @@
1
+ export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
2
+ export type AvatarStatus = 'online' | 'busy' | 'offline';
3
+ export type AvatarData = {
4
+ /** Image URL. `null` / `undefined` skips to the initials → icon fallback chain. */
5
+ src?: string | null;
6
+ /** Display name — drives the initials fallback and the accessible label. */
7
+ name?: string;
8
+ };
@@ -1,18 +1,12 @@
1
- <script lang="ts">import { Icon } from '../icon';
1
+ <script lang="ts">import { Avatar } from '../avatar';
2
+ import { Icon } from '../icon';
2
3
  import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_20_regular.svg?raw';
3
- const { name, subtitle, avatar, secondaryAvatar, position = 'top' } = $props();
4
+ const { name, subtitle, avatar, badge = null, position = 'top' } = $props();
4
5
  </script>
5
6
 
6
- <div class="nav-menu-account-row" class:nav-menu-account-row--dual={secondaryAvatar} class:nav-menu-account-row--top={position === 'top'}>
7
- <span class="nav-menu-account-row__avatar-stack">
8
- <span class="nav-menu-account-row__avatar nav-menu-account-row__avatar--primary">
9
- {@render avatar()}
10
- </span>
11
- {#if secondaryAvatar}
12
- <span class="nav-menu-account-row__avatar nav-menu-account-row__avatar--secondary">
13
- {@render secondaryAvatar()}
14
- </span>
15
- {/if}
7
+ <div class="nav-menu-account-row" class:nav-menu-account-row--top={position === 'top'}>
8
+ <span class="nav-menu-account-row__avatar" class:nav-menu-account-row__avatar--dual={!!badge}>
9
+ <Avatar src={avatar.src} name={avatar.name} badge={badge} />
16
10
  </span>
17
11
  <span class="nav-menu-account-row__info">
18
12
  <span class="nav-menu-account-row__name">{name}</span>
@@ -29,18 +23,13 @@ const { name, subtitle, avatar, secondaryAvatar, position = 'top' } = $props();
29
23
  @component
30
24
  NavMenuAccountRow — workspace / account trigger row for the top of a `NavMenu` panel. Purely
31
25
  presentational: render it inside a `Popover` `trigger` snippet to make it a real switcher. Layout:
32
- 32px avatar on the left, 2-line text (name + subtitle), chevron-down on the right. It owns its own
33
- padding + hover; for a full-width trigger set `--sc-kit--popover--width: 100%` on the `Popover`.
34
-
35
- ### CSS Custom Properties
36
- | Property | Description | Default |
37
- |---|---|---|
38
- | `--sc-kit--nav-menu-account-row--avatar-size` | Primary avatar box | `32px` |
39
- | `--sc-kit--nav-menu-account-row--secondary-avatar-size` | Secondary badge box | `14px` |
26
+ kit `Avatar` on the left at the `md` preset (rounded square; circle + corner badge in
27
+ org-with-user mode when `badge` is set), 2-line text (name + subtitle), chevron-down on the
28
+ right. No public CSS vars of its own — avatar sizing/colors are tuned via the
29
+ `--sc-kit--avatar--*` vars directly. It owns its own padding + hover; for a full-width trigger
30
+ set `--sc-kit--popover--width: 100%` on the `Popover`.
40
31
  -->
41
32
  <style>.nav-menu-account-row {
42
- --_avatar-size: var(--sc-kit--nav-menu-account-row--avatar-size, 2rem);
43
- --_secondary-size: var(--sc-kit--nav-menu-account-row--secondary-avatar-size, 0.875rem);
44
33
  display: flex;
45
34
  align-items: center;
46
35
  gap: var(--sc-kit--space--2);
@@ -53,56 +42,17 @@ padding + hover; for a full-width trigger set `--sc-kit--popover--width: 100%` o
53
42
  .nav-menu-account-row:hover {
54
43
  background: var(--sc-kit--color--bg--hover);
55
44
  }
56
-
57
45
  .nav-menu-account-row--top {
58
46
  margin-top: var(--sc-kit--space--4);
59
47
  }
60
-
61
- .nav-menu-account-row__avatar-stack {
62
- position: relative;
63
- display: inline-flex;
64
- width: var(--_avatar-size);
65
- height: var(--_avatar-size);
66
- flex-shrink: 0;
67
- }
68
-
69
48
  .nav-menu-account-row__avatar {
49
+ --sc-kit--avatar--border-radius: var(--sc-kit--radius--sm);
70
50
  display: inline-flex;
71
- align-items: center;
72
- justify-content: center;
73
- overflow: hidden;
74
- background: var(--sc-kit--color--bg--field-alt);
75
- color: var(--sc-kit--color--text--on-accent);
76
- font-size: var(--sc-kit--font-size--sm);
77
- font-weight: var(--sc-kit--font-weight--semibold);
78
- }
79
- .nav-menu-account-row__avatar :global(img) {
80
- width: 100%;
81
- height: 100%;
82
- object-fit: cover;
83
- }
84
-
85
- .nav-menu-account-row__avatar--primary {
86
- width: 100%;
87
- height: 100%;
88
- border-radius: var(--sc-kit--radius--sm);
89
- }
90
-
91
- .nav-menu-account-row--dual .nav-menu-account-row__avatar--primary {
92
- border-radius: var(--sc-kit--radius--circle);
51
+ flex-shrink: 0;
93
52
  }
94
-
95
- .nav-menu-account-row__avatar--secondary {
96
- position: absolute;
97
- right: -2px;
98
- bottom: -2px;
99
- width: var(--_secondary-size);
100
- height: var(--_secondary-size);
101
- border-radius: var(--sc-kit--radius--sm);
102
- box-shadow: 0 0 0 1.5px var(--sc-kit--color--bg--panel);
103
- font-size: 0.5rem;
53
+ .nav-menu-account-row__avatar--dual {
54
+ --sc-kit--avatar--border-radius: 50%;
104
55
  }
105
-
106
56
  .nav-menu-account-row__info {
107
57
  display: flex;
108
58
  flex-direction: column;
@@ -110,7 +60,6 @@ padding + hover; for a full-width trigger set `--sc-kit--popover--width: 100%` o
110
60
  min-width: 0;
111
61
  gap: 0.125rem;
112
62
  }
113
-
114
63
  .nav-menu-account-row__name {
115
64
  font-size: var(--sc-kit--font-size--md);
116
65
  font-weight: var(--sc-kit--font-weight--semibold);
@@ -121,7 +70,6 @@ padding + hover; for a full-width trigger set `--sc-kit--popover--width: 100%` o
121
70
  white-space: nowrap;
122
71
  overflow: hidden;
123
72
  }
124
-
125
73
  .nav-menu-account-row__subtitle {
126
74
  font-size: 0.625rem;
127
75
  color: var(--sc-kit--color--text--secondary);
@@ -131,16 +79,11 @@ padding + hover; for a full-width trigger set `--sc-kit--popover--width: 100%` o
131
79
  white-space: nowrap;
132
80
  overflow: hidden;
133
81
  }
134
-
135
82
  .nav-menu-account-row__chevron {
83
+ --sc-kit--icon--size: 0.75rem;
136
84
  flex-shrink: 0;
137
85
  display: inline-flex;
138
86
  align-items: center;
139
87
  justify-content: center;
140
88
  color: var(--sc-kit--color--text--secondary);
141
- }
142
- .nav-menu-account-row__chevron :global(svg) {
143
- width: 0.75rem;
144
- height: 0.75rem;
145
- fill: currentColor;
146
89
  }</style>
@@ -1,27 +1,24 @@
1
- import type { Snippet } from 'svelte';
1
+ import { type AvatarData } from '../avatar';
2
2
  type Props = {
3
3
  /** Primary line — workspace / account name. Truncates with ellipsis. */
4
4
  name: string;
5
5
  /** Secondary line — workspace type, role, email, etc. Truncates. */
6
6
  subtitle?: string;
7
- /** Primary avatar slot (32 × 32px). Renders as a rounded square by default; becomes a circle when `secondaryAvatar` is also provided (org-with-user pattern). */
8
- avatar: Snippet;
7
+ /** Primary avatar data (image initials → icon fallback). Renders as a rounded square by default; becomes a circle when `badge` is also provided (org-with-user pattern). */
8
+ avatar: AvatarData;
9
9
  /** Optional secondary avatar — square badge layered on the bottom-right of the primary. Use it to show the active user when the primary is the workspace / organization. */
10
- secondaryAvatar?: Snippet;
10
+ badge?: AvatarData | null;
11
11
  /** Placement within the menu — `top` adds spacing above so the row clears the panel's top edge; `bottom` leaves it flush (footer use). @default 'top' */
12
12
  position?: 'top' | 'bottom';
13
13
  };
14
14
  /**
15
15
  * NavMenuAccountRow — workspace / account trigger row for the top of a `NavMenu` panel. Purely
16
16
  * presentational: render it inside a `Popover` `trigger` snippet to make it a real switcher. Layout:
17
- * 32px avatar on the left, 2-line text (name + subtitle), chevron-down on the right. It owns its own
18
- * padding + hover; for a full-width trigger set `--sc-kit--popover--width: 100%` on the `Popover`.
19
- *
20
- * ### CSS Custom Properties
21
- * | Property | Description | Default |
22
- * |---|---|---|
23
- * | `--sc-kit--nav-menu-account-row--avatar-size` | Primary avatar box | `32px` |
24
- * | `--sc-kit--nav-menu-account-row--secondary-avatar-size` | Secondary badge box | `14px` |
17
+ * kit `Avatar` on the left at the `md` preset (rounded square; circle + corner badge in
18
+ * org-with-user mode when `badge` is set), 2-line text (name + subtitle), chevron-down on the
19
+ * right. No public CSS vars of its own — avatar sizing/colors are tuned via the
20
+ * `--sc-kit--avatar--*` vars directly. It owns its own padding + hover; for a full-width trigger
21
+ * set `--sc-kit--popover--width: 100%` on the `Popover`.
25
22
  */
26
23
  declare const Cmp: import("svelte").Component<Props, {}, "">;
27
24
  type Cmp = ReturnType<typeof Cmp>;
@@ -0,0 +1,197 @@
1
+ <script lang="ts" generics="TResult = void, TCancelResult = void">import { Button } from '../button';
2
+ import { Dialog, DialogCloseButton } from '../dialog';
3
+ import { IconSlot } from '../icon';
4
+ import { Stepper } from '../stepper';
5
+ import { StepperDialogLayoutLocalization } from './stepper-dialog-layout-localization';
6
+ let { controller, icon, title, subtitle, steps, canAdvance = () => true, labels, children, on } = $props();
7
+ const localization = new StepperDialogLayoutLocalization();
8
+ let step = $state(0);
9
+ let submitting = $state(false);
10
+ const isFirstStep = $derived(step === 0);
11
+ const isLastStep = $derived(step === steps.length - 1);
12
+ const stepAllowed = $derived(canAdvance(step));
13
+ const showCloseButton = $derived(!controller.settings.nonCancelable);
14
+ const setStep = (next) => {
15
+ step = next;
16
+ on.stepChange?.(next);
17
+ };
18
+ const handleNext = async () => {
19
+ if (!stepAllowed) {
20
+ return;
21
+ }
22
+ if (on.beforeNext && (await on.beforeNext(step)) === false) {
23
+ return;
24
+ }
25
+ setStep(step + 1);
26
+ };
27
+ const handleBack = () => {
28
+ if (step > 0) {
29
+ setStep(step - 1);
30
+ }
31
+ };
32
+ const handleFinish = async () => {
33
+ if (!stepAllowed || submitting) {
34
+ return;
35
+ }
36
+ submitting = true;
37
+ try {
38
+ await on.finish();
39
+ }
40
+ catch (error) {
41
+ console.error('StepperDialogLayout: on.finish threw — surface errors inside the handler', error);
42
+ }
43
+ finally {
44
+ submitting = false;
45
+ }
46
+ };
47
+ const handleStepClick = (clicked) => {
48
+ if (clicked < step) {
49
+ setStep(clicked);
50
+ }
51
+ };
52
+ </script>
53
+
54
+ <div class="stepper-dialog-layout">
55
+ <Dialog controller={controller}>
56
+ {#snippet headerSection()}
57
+ <div class="stepper-dialog-layout__header">
58
+ {#if icon}
59
+ <span class="stepper-dialog-layout__icon" aria-hidden="true"><IconSlot icon={icon} /></span>
60
+ {/if}
61
+ <div class="stepper-dialog-layout__title-block">
62
+ <div class="stepper-dialog-layout__title">{title}</div>
63
+ {#if subtitle}
64
+ <div class="stepper-dialog-layout__subtitle">{subtitle}</div>
65
+ {/if}
66
+ </div>
67
+ {#if showCloseButton}
68
+ <DialogCloseButton controller={controller} />
69
+ {/if}
70
+ </div>
71
+ {/snippet}
72
+
73
+ {#snippet bodySection()}
74
+ <div class="stepper-dialog-layout__layout">
75
+ <div class="stepper-dialog-layout__sidebar">
76
+ <Stepper orientation="vertical" completed="checkmark" current={step} steps={steps} on={{ stepClick: handleStepClick }} />
77
+ </div>
78
+ <div class="stepper-dialog-layout__main">
79
+ <div class="stepper-dialog-layout__body">{@render children(step)}</div>
80
+ </div>
81
+ </div>
82
+ {/snippet}
83
+
84
+ {#snippet footer()}
85
+ <Button type="button" variant="secondary" on={{ click: () => controller.cancel() }}>{labels?.cancel ?? localization.cancel}</Button>
86
+ {#if !isFirstStep}
87
+ <Button type="button" variant="ghost" on={{ click: handleBack }}>{labels?.back ?? localization.back}</Button>
88
+ {/if}
89
+ {#if isLastStep}
90
+ <Button type="button" variant="primary" loading={submitting} disabled={!stepAllowed || submitting} on={{ click: () => void handleFinish() }}
91
+ >{labels?.finish ?? localization.finish}</Button>
92
+ {:else}
93
+ <Button type="button" variant="primary" disabled={!stepAllowed} on={{ click: () => void handleNext() }}>{labels?.next ?? localization.next}</Button>
94
+ {/if}
95
+ {/snippet}
96
+ </Dialog>
97
+ </div>
98
+
99
+ <!--
100
+ @component
101
+ StepperDialogLayout — layout scaffold for multi-step wizard dialog views: header (icon / title /
102
+ subtitle / close), vertical Stepper sidebar, scrollable body, and a Cancel / Back / Next / Finish
103
+ footer. Render it inside your own dialog view opened via `Dialogs.open()` — the layout owns step
104
+ navigation and the submitting state; your `on.finish` owns closing (`controller.ok(...)`).
105
+ Completed sidebar steps are clickable to go back; `canAdvance(step)` gates Next / Finish.
106
+
107
+ ### CSS Custom Properties
108
+ | Property | Description | Default |
109
+ |---|---|---|
110
+ | `--sc-kit--stepper-dialog-layout--sidebar--width` | Stepper sidebar width | `20rem` (320px) |
111
+ | `--sc-kit--stepper-dialog-layout--sidebar--background` | Sidebar background | `--sc-kit--color--bg--field-alt` |
112
+ | `--sc-kit--stepper-dialog-layout--height` | Dialog height (forwarded to `--sc-kit--dialog--height`) | `auto` |
113
+ -->
114
+
115
+ <style>.stepper-dialog-layout {
116
+ --_sdl--sidebar-width: var(--sc-kit--stepper-dialog-layout--sidebar--width, functions.to-rem(320));
117
+ --_sdl--sidebar-background: var(--sc-kit--stepper-dialog-layout--sidebar--background, var(--sc-kit--color--bg--field-alt));
118
+ --sc-kit--dialog--height: var(--sc-kit--stepper-dialog-layout--height, auto);
119
+ --sc-kit--dialog--body--overflow-y: auto;
120
+ display: contents;
121
+ }
122
+ .stepper-dialog-layout__header {
123
+ display: flex;
124
+ align-items: flex-start;
125
+ gap: var(--sc-kit--space--3);
126
+ padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
127
+ border-block-end: 1px solid var(--sc-kit--color--border);
128
+ }
129
+ .stepper-dialog-layout__icon {
130
+ --sc-kit--icon--size: 1.5rem;
131
+ display: inline-flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ flex-shrink: 0;
135
+ inline-size: 2.5rem;
136
+ block-size: 2.5rem;
137
+ background: var(--sc-kit--color--bg--field-alt);
138
+ color: var(--sc-kit--color--text--primary);
139
+ border-radius: var(--sc-kit--radius--md);
140
+ }
141
+ .stepper-dialog-layout__title-block {
142
+ flex: 1;
143
+ min-inline-size: 0;
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 0.125rem;
147
+ }
148
+ .stepper-dialog-layout__title {
149
+ font-size: var(--sc-kit--font-size--lg);
150
+ font-weight: var(--sc-kit--font-weight--semibold);
151
+ line-height: var(--sc-kit--leading--tight);
152
+ color: var(--sc-kit--color--text--primary);
153
+ }
154
+ .stepper-dialog-layout__subtitle {
155
+ font-size: var(--sc-kit--font-size--sm);
156
+ color: var(--sc-kit--color--text--muted);
157
+ line-height: var(--sc-kit--leading--normal);
158
+ }
159
+ .stepper-dialog-layout__layout {
160
+ display: flex;
161
+ flex: 1;
162
+ min-block-size: 0;
163
+ block-size: 100%;
164
+ }
165
+ @media screen and (max-width: 768px) {
166
+ .stepper-dialog-layout__layout {
167
+ flex-direction: column;
168
+ }
169
+ }
170
+ .stepper-dialog-layout__sidebar {
171
+ flex: 0 0 var(--_sdl--sidebar-width);
172
+ padding: var(--sc-kit--space--5) var(--sc-kit--space--4);
173
+ background: var(--_sdl--sidebar-background);
174
+ border-inline-end: 1px solid var(--sc-kit--color--border);
175
+ overflow-y: auto;
176
+ min-block-size: 0;
177
+ }
178
+ @media screen and (max-width: 768px) {
179
+ .stepper-dialog-layout__sidebar {
180
+ flex: 0 0 auto;
181
+ border-inline-end: none;
182
+ border-block-end: 1px solid var(--sc-kit--color--border);
183
+ }
184
+ }
185
+ .stepper-dialog-layout__main {
186
+ flex: 1 1 auto;
187
+ display: flex;
188
+ flex-direction: column;
189
+ min-inline-size: 0;
190
+ min-block-size: 0;
191
+ }
192
+ .stepper-dialog-layout__body {
193
+ flex: 1;
194
+ min-block-size: 0;
195
+ overflow-y: auto;
196
+ padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
197
+ }</style>
@@ -0,0 +1,66 @@
1
+ import { type DialogController } from '../dialog';
2
+ import { type IconProp } from '../icon';
3
+ import { type Step } from '../stepper';
4
+ import type { Snippet } from 'svelte';
5
+ declare function $$render<TResult = void, TCancelResult = void>(): {
6
+ props: {
7
+ controller: DialogController<TResult, TCancelResult>;
8
+ /** Leading header icon — string SVG source, `{ src, color?, size? }` object, or custom snippet. */
9
+ icon?: IconProp;
10
+ title: string;
11
+ subtitle?: string;
12
+ steps: Step[];
13
+ /** Reactive gate for Next / Finish per step index. @default () => true */
14
+ canAdvance?: (step: number) => boolean;
15
+ /** Per-button overrides of the localized footer labels. */
16
+ labels?: {
17
+ cancel?: string;
18
+ back?: string;
19
+ next?: string;
20
+ finish?: string;
21
+ };
22
+ /** Body content; receives the current step index. */
23
+ children: Snippet<[number]>;
24
+ on: {
25
+ /** Owns closing: call `controller.ok(...)` on success; return without closing to keep the dialog open. Surface errors inside the handler — rejections are absorbed (logged), not rethrown. */
26
+ finish: () => void | Promise<void>;
27
+ beforeNext?: (step: number) => boolean | void | Promise<boolean | void>;
28
+ stepChange?: (step: number) => void;
29
+ };
30
+ };
31
+ exports: {};
32
+ bindings: "";
33
+ slots: {};
34
+ events: {};
35
+ };
36
+ declare class __sveltets_Render<TResult = void, TCancelResult = void> {
37
+ props(): ReturnType<typeof $$render<TResult, TCancelResult>>['props'];
38
+ events(): ReturnType<typeof $$render<TResult, TCancelResult>>['events'];
39
+ slots(): ReturnType<typeof $$render<TResult, TCancelResult>>['slots'];
40
+ bindings(): "";
41
+ exports(): {};
42
+ }
43
+ interface $$IsomorphicComponent {
44
+ new <TResult = void, TCancelResult = void>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TResult, TCancelResult>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TResult, TCancelResult>['props']>, ReturnType<__sveltets_Render<TResult, TCancelResult>['events']>, ReturnType<__sveltets_Render<TResult, TCancelResult>['slots']>> & {
45
+ $$bindings?: ReturnType<__sveltets_Render<TResult, TCancelResult>['bindings']>;
46
+ } & ReturnType<__sveltets_Render<TResult, TCancelResult>['exports']>;
47
+ <TResult = void, TCancelResult = void>(internal: unknown, props: ReturnType<__sveltets_Render<TResult, TCancelResult>['props']> & {}): ReturnType<__sveltets_Render<TResult, TCancelResult>['exports']>;
48
+ z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
49
+ }
50
+ /**
51
+ * StepperDialogLayout — layout scaffold for multi-step wizard dialog views: header (icon / title /
52
+ * subtitle / close), vertical Stepper sidebar, scrollable body, and a Cancel / Back / Next / Finish
53
+ * footer. Render it inside your own dialog view opened via `Dialogs.open()` — the layout owns step
54
+ * navigation and the submitting state; your `on.finish` owns closing (`controller.ok(...)`).
55
+ * Completed sidebar steps are clickable to go back; `canAdvance(step)` gates Next / Finish.
56
+ *
57
+ * ### CSS Custom Properties
58
+ * | Property | Description | Default |
59
+ * |---|---|---|
60
+ * | `--sc-kit--stepper-dialog-layout--sidebar--width` | Stepper sidebar width | `20rem` (320px) |
61
+ * | `--sc-kit--stepper-dialog-layout--sidebar--background` | Sidebar background | `--sc-kit--color--bg--field-alt` |
62
+ * | `--sc-kit--stepper-dialog-layout--height` | Dialog height (forwarded to `--sc-kit--dialog--height`) | `auto` |
63
+ */
64
+ declare const Cmp: $$IsomorphicComponent;
65
+ type Cmp<TResult = void, TCancelResult = void> = InstanceType<typeof Cmp<TResult, TCancelResult>>;
66
+ export default Cmp;
@@ -0,0 +1 @@
1
+ export { default as StepperDialogLayout } from './cmp.stepper-dialog-layout.svelte';
@@ -0,0 +1 @@
1
+ export { default as StepperDialogLayout } from './cmp.stepper-dialog-layout.svelte';
@@ -1,4 +1,4 @@
1
- export declare class StepperDialogLocalization {
1
+ export declare class StepperDialogLayoutLocalization {
2
2
  get cancel(): string;
3
3
  get back(): string;
4
4
  get next(): string;
@@ -1,18 +1,4 @@
1
1
  import { AppLocale } from '../../core/locale';
2
- export class StepperDialogLocalization {
3
- get cancel() {
4
- return loc.cancel[AppLocale.current];
5
- }
6
- get back() {
7
- return loc.back[AppLocale.current];
8
- }
9
- get next() {
10
- return loc.next[AppLocale.current];
11
- }
12
- get finish() {
13
- return loc.finish[AppLocale.current];
14
- }
15
- }
16
2
  const loc = {
17
3
  cancel: {
18
4
  en: 'Cancel',
@@ -31,3 +17,17 @@ const loc = {
31
17
  no: 'Fullfør'
32
18
  }
33
19
  };
20
+ export class StepperDialogLayoutLocalization {
21
+ get cancel() {
22
+ return loc.cancel[AppLocale.current];
23
+ }
24
+ get back() {
25
+ return loc.back[AppLocale.current];
26
+ }
27
+ get next() {
28
+ return loc.next[AppLocale.current];
29
+ }
30
+ get finish() {
31
+ return loc.finish[AppLocale.current];
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -359,9 +359,9 @@
359
359
  "types": "./dist/ui/stepper/index.d.ts",
360
360
  "svelte": "./dist/ui/stepper/index.js"
361
361
  },
362
- "./ui/stepper-dialog": {
363
- "types": "./dist/ui/stepper-dialog/index.d.ts",
364
- "svelte": "./dist/ui/stepper-dialog/index.js"
362
+ "./ui/stepper-dialog-layout": {
363
+ "types": "./dist/ui/stepper-dialog-layout/index.d.ts",
364
+ "svelte": "./dist/ui/stepper-dialog-layout/index.js"
365
365
  },
366
366
  "./ui/swipe-indicator": {
367
367
  "types": "./dist/ui/swipe-indicator/index.d.ts",
@@ -1,221 +0,0 @@
1
- <script lang="ts">import { Button } from '../button';
2
- import { Dialog, DialogCloseButton } from '../dialog';
3
- import { Icon } from '../icon';
4
- import { Stepper } from '../stepper';
5
- import { StepperDialogLocalization } from './stepper-dialog-localization';
6
- let { controller, data } = $props();
7
- const localization = new StepperDialogLocalization();
8
- let step = $state(0);
9
- let submitting = $state(false);
10
- const isFirstStep = $derived(step === 0);
11
- const isLastStep = $derived(step === data.steps.length - 1);
12
- const canAdvance = $derived(data.canAdvance(step));
13
- const showCloseButton = $derived(!controller.settings.nonCancelable);
14
- const setStep = (i) => {
15
- step = i;
16
- data.on.stepChange?.(i);
17
- };
18
- const handleNext = async () => {
19
- if (!canAdvance) {
20
- return;
21
- }
22
- if (data.on.beforeNext) {
23
- const result = await data.on.beforeNext(step);
24
- if (result === false) {
25
- return;
26
- }
27
- }
28
- setStep(step + 1);
29
- };
30
- const handleBack = () => {
31
- if (step > 0) {
32
- setStep(step - 1);
33
- }
34
- };
35
- const handleFinish = async () => {
36
- if (!canAdvance || submitting) {
37
- return;
38
- }
39
- submitting = true;
40
- try {
41
- await data.on.finish();
42
- controller.ok();
43
- }
44
- finally {
45
- submitting = false;
46
- }
47
- };
48
- const handleStepClick = (i) => {
49
- if (i < step) {
50
- setStep(i);
51
- }
52
- };
53
- </script>
54
-
55
- {#snippet footerButtons()}
56
- <Button type="button" variant="secondary" on={{ click: () => controller.cancel() }}>{data.cancelLabel ?? localization.cancel}</Button>
57
- {#if !isFirstStep}
58
- <Button type="button" variant="ghost" on={{ click: handleBack }}>{data.backLabel ?? localization.back}</Button>
59
- {/if}
60
- {#if isLastStep}
61
- <Button type="button" variant="primary" loading={submitting} disabled={!canAdvance || submitting} on={{ click: handleFinish }}
62
- >{data.finishLabel ?? localization.finish}</Button>
63
- {:else}
64
- <Button type="button" variant="primary" disabled={!canAdvance} on={{ click: handleNext }}>{data.nextLabel ?? localization.next}</Button>
65
- {/if}
66
- {/snippet}
67
-
68
- {#snippet titleRow()}
69
- <div class="stepper-dialog__title-row">
70
- {#if data.icon}<span class="stepper-dialog__icon" aria-hidden="true"><Icon src={data.icon} /></span>{/if}
71
- <div class="stepper-dialog__title-block">
72
- <div class="stepper-dialog__title">{data.title}</div>
73
- {#if data.subtitle}<div class="stepper-dialog__subtitle">{data.subtitle}</div>{/if}
74
- </div>
75
- {#if showCloseButton}<DialogCloseButton controller={controller} />{/if}
76
- </div>
77
- {/snippet}
78
-
79
- <div
80
- class="stepper-dialog stepper-dialog--{data.layout}"
81
- style:--sc-kit--dialog--height={data.height}
82
- style:--sc-kit--dialog--body--overflow-y={data.height ? 'auto' : null}
83
- style:--_stepper-dialog--sidebar-background={data.sidebarBackground}>
84
- {#if data.layout === 'side'}
85
- <Dialog controller={controller}>
86
- {#snippet headerSection()}
87
- <div class="stepper-dialog__header stepper-dialog__header--side">
88
- {@render titleRow()}
89
- </div>
90
- {/snippet}
91
- {#snippet bodySection()}
92
- <div class="stepper-dialog__layout">
93
- <div class="stepper-dialog__sidebar">
94
- <Stepper orientation="vertical" completed="checkmark" current={step} steps={data.steps} on={{ stepClick: handleStepClick }} />
95
- </div>
96
- <div class="stepper-dialog__main">
97
- <div class="stepper-dialog__body">{@render data.content(step)}</div>
98
- </div>
99
- </div>
100
- {/snippet}
101
- {#snippet footer()}{@render footerButtons()}{/snippet}
102
- </Dialog>
103
- {:else}
104
- <Dialog controller={controller}>
105
- {#snippet headerSection()}
106
- <div class="stepper-dialog__header stepper-dialog__header--top">
107
- {@render titleRow()}
108
- <div class="stepper-dialog__stepper">
109
- <Stepper orientation="horizontal-inline" size="sm" completed="checkmark" current={step} steps={data.steps} on={{ stepClick: handleStepClick }} />
110
- </div>
111
- </div>
112
- {/snippet}
113
- {#snippet body()}{@render data.content(step)}{/snippet}
114
- {#snippet footer()}{@render footerButtons()}{/snippet}
115
- </Dialog>
116
- {/if}
117
- </div>
118
-
119
- <!--
120
- @component
121
- Internal view for `openStepperDialog`. Renders a multi-step dialog: header (icon + title + subtitle + close),
122
- a stepper band (top layout) or vertical sidebar (side layout), the current step's body, and a Cancel / Back /
123
- Next / Finish footer. The dialog owns step navigation and the submitting spinner; the consumer owns step
124
- content (via `content` snippet) and the finish handler.
125
-
126
- Never render this view directly — use the imperative `openStepperDialog(...)` from the module barrel.
127
- -->
128
-
129
- <style>.stepper-dialog {
130
- --_stepper-dialog--sidebar-width: 20rem;
131
- --_stepper-dialog--sidebar-background: var(--sc-kit--color--bg--field-alt);
132
- --_stepper-dialog--header-background: var(--sc-kit--color--bg--field-alt);
133
- --_stepper-dialog--header-border-color: var(--sc-kit--color--border);
134
- display: contents;
135
- }
136
- .stepper-dialog__header {
137
- display: flex;
138
- flex-direction: column;
139
- }
140
- .stepper-dialog__header--side {
141
- border-bottom: 1px solid var(--_stepper-dialog--header-border-color);
142
- }
143
- .stepper-dialog__title-row {
144
- display: flex;
145
- align-items: flex-start;
146
- gap: var(--sc-kit--space--3);
147
- padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
148
- }
149
- .stepper-dialog__icon {
150
- display: inline-flex;
151
- align-items: center;
152
- justify-content: center;
153
- flex-shrink: 0;
154
- width: 2.5rem;
155
- height: 2.5rem;
156
- background: var(--sc-kit--color--bg--field-alt);
157
- color: var(--sc-kit--color--text--primary);
158
- border-radius: var(--sc-kit--radius--md);
159
- --sc-kit--icon--size: 1.5rem;
160
- }
161
- .stepper-dialog__title-block {
162
- flex: 1;
163
- min-width: 0;
164
- display: flex;
165
- flex-direction: column;
166
- gap: 0.125rem;
167
- }
168
- .stepper-dialog__title {
169
- font-size: var(--sc-kit--font-size--lg);
170
- font-weight: var(--sc-kit--font-weight--semibold);
171
- line-height: var(--sc-kit--leading--tight);
172
- color: var(--sc-kit--color--text--primary);
173
- }
174
- .stepper-dialog__subtitle {
175
- font-size: var(--sc-kit--font-size--sm);
176
- color: var(--sc-kit--color--text--muted);
177
- line-height: var(--sc-kit--leading--snug);
178
- }
179
- .stepper-dialog__stepper {
180
- padding: var(--sc-kit--space--2) var(--sc-kit--space--6);
181
- background: var(--_stepper-dialog--header-background);
182
- }
183
- .stepper-dialog__layout {
184
- display: flex;
185
- flex: 1;
186
- min-height: 0;
187
- height: 100%;
188
- }
189
- @media screen and (max-width: 768px) {
190
- .stepper-dialog__layout {
191
- flex-direction: column;
192
- }
193
- }
194
- .stepper-dialog__sidebar {
195
- flex: 0 0 var(--_stepper-dialog--sidebar-width);
196
- padding: var(--sc-kit--space--5) var(--sc-kit--space--4);
197
- background: var(--_stepper-dialog--sidebar-background);
198
- border-right: 1px solid var(--sc-kit--color--border);
199
- overflow-y: auto;
200
- min-height: 0;
201
- }
202
- @media screen and (max-width: 768px) {
203
- .stepper-dialog__sidebar {
204
- flex: 0 0 auto;
205
- border-right: none;
206
- border-bottom: 1px solid var(--sc-kit--color--border);
207
- }
208
- }
209
- .stepper-dialog__main {
210
- flex: 1 1 auto;
211
- display: flex;
212
- flex-direction: column;
213
- min-width: 0;
214
- min-height: 0;
215
- }
216
- .stepper-dialog__body {
217
- flex: 1;
218
- min-height: 0;
219
- overflow-y: auto;
220
- padding: var(--sc-kit--space--5) var(--sc-kit--space--6);
221
- }</style>
@@ -1,17 +0,0 @@
1
- import { type DialogController } from '../dialog';
2
- import type { StepperDialogData } from './types';
3
- type Props = {
4
- controller: DialogController;
5
- data: StepperDialogData;
6
- };
7
- /**
8
- * Internal view for `openStepperDialog`. Renders a multi-step dialog: header (icon + title + subtitle + close),
9
- * a stepper band (top layout) or vertical sidebar (side layout), the current step's body, and a Cancel / Back /
10
- * Next / Finish footer. The dialog owns step navigation and the submitting spinner; the consumer owns step
11
- * content (via `content` snippet) and the finish handler.
12
- *
13
- * Never render this view directly — use the imperative `openStepperDialog(...)` from the module barrel.
14
- */
15
- declare const Cmp: import("svelte").Component<Props, {}, "">;
16
- type Cmp = ReturnType<typeof Cmp>;
17
- export default Cmp;
@@ -1,2 +0,0 @@
1
- export { openStepperDialog } from './stepper-dialog';
2
- export type { StepperDialogData, StepperDialogLayout } from './types';
@@ -1 +0,0 @@
1
- export { openStepperDialog } from './stepper-dialog';
@@ -1,22 +0,0 @@
1
- import { type DialogResult } from '../dialog';
2
- import type { Step } from '../stepper';
3
- import type { StepperDialogData, StepperDialogLayout } from './types';
4
- import type { Snippet } from 'svelte';
5
- type StepperDialogInit = {
6
- icon?: string;
7
- title: string;
8
- subtitle?: string;
9
- steps: Step[];
10
- layout?: StepperDialogLayout;
11
- sidebarBackground?: string;
12
- canAdvance?: (step: number) => boolean;
13
- cancelLabel?: string;
14
- backLabel?: string;
15
- nextLabel?: string;
16
- finishLabel?: string;
17
- height?: string;
18
- content: Snippet<[number]>;
19
- on: StepperDialogData['on'];
20
- };
21
- export declare const openStepperDialog: (init: StepperDialogInit) => Promise<DialogResult<void>>;
22
- export {};
@@ -1,26 +0,0 @@
1
- import { Dialogs } from '../dialog';
2
- import StepperDialogView from './cmp.stepper-dialog.svelte';
3
- export const openStepperDialog = (init) => {
4
- const data = {
5
- icon: init.icon,
6
- title: init.title,
7
- subtitle: init.subtitle,
8
- steps: init.steps,
9
- layout: init.layout ?? 'top',
10
- sidebarBackground: init.sidebarBackground,
11
- canAdvance: init.canAdvance ?? (() => true),
12
- cancelLabel: init.cancelLabel,
13
- backLabel: init.backLabel,
14
- nextLabel: init.nextLabel,
15
- finishLabel: init.finishLabel,
16
- height: init.height,
17
- content: init.content,
18
- on: init.on
19
- };
20
- return Dialogs.open({
21
- view: StepperDialogView,
22
- data,
23
- explicitSettings: { closeOnEsc: true, closeOnClickOutside: false },
24
- containerSettings: { size: data.layout === 'side' ? 'large' : 'medium' }
25
- });
26
- };
@@ -1,34 +0,0 @@
1
- import type { Step } from '../stepper';
2
- import type { Snippet } from 'svelte';
3
- export type StepperDialogLayout = 'top' | 'side';
4
- export type StepperDialogData = {
5
- icon?: string;
6
- title: string;
7
- subtitle?: string;
8
- steps: Step[];
9
- layout: StepperDialogLayout;
10
- /** Side-layout sidebar background — any valid CSS `background` shorthand. No-op in `'top'` layout. */
11
- sidebarBackground?: string;
12
- /** Reactive: close over reactive state to enable/disable Next / Finish per step. @default () => true */
13
- canAdvance: (step: number) => boolean;
14
- /** Override the localized Cancel label. */
15
- cancelLabel?: string;
16
- /** Override the localized Back label. */
17
- backLabel?: string;
18
- /** Override the localized Next label. */
19
- nextLabel?: string;
20
- /** Override the localized Finish label. */
21
- finishLabel?: string;
22
- /** Fixed dialog height (any CSS length). When set, body scrolls inside the fixed frame. */
23
- height?: string;
24
- /** Body content — receives the current step index. */
25
- content: Snippet<[number]>;
26
- on: {
27
- /** Called when the user clicks Finish from the last step. Dialog auto-closes with `controller.ok()` after this resolves; throw to keep it open. */
28
- finish: () => void | Promise<void>;
29
- /** Pre-advance hook. Return `false` to block Next. */
30
- beforeNext?: (step: number) => boolean | void | Promise<boolean | void>;
31
- /** Fires whenever the internal step index changes. */
32
- stepChange?: (step: number) => void;
33
- };
34
- };
File without changes