@14ch/svelte-ui 0.0.49 → 0.0.51

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.
@@ -368,6 +368,45 @@
368
368
  --svelte-ui-nav-bottom-sheet-overlay-bg: rgba(0, 0, 0, 0.4);
369
369
  --svelte-ui-nav-bottom-sheet-border-radius: 16px 16px 0 0;
370
370
  --svelte-ui-nav-bottom-sheet-padding: 16px 8px;
371
+ /* StepNav */
372
+ /* 強調色(現在/完了)。color prop 指定時は --internal-step-nav-accent が優先。 */
373
+ --svelte-ui-step-nav-accent-color: var(--svelte-ui-primary-color);
374
+ --svelte-ui-step-nav-completed-color: var(--svelte-ui-step-nav-accent-color);
375
+ --svelte-ui-step-nav-current-color: var(--svelte-ui-step-nav-accent-color);
376
+ /* マーカー */
377
+ --svelte-ui-step-nav-marker-size-sm: 24px;
378
+ --svelte-ui-step-nav-marker-size: 32px;
379
+ --svelte-ui-step-nav-marker-size-lg: 40px;
380
+ --svelte-ui-step-nav-marker-border-width: 2px;
381
+ --svelte-ui-step-nav-marker-text-color: var(--svelte-ui-text-on-filled-color);
382
+ --svelte-ui-step-nav-upcoming-marker-color: var(--svelte-ui-border-strong-color);
383
+ --svelte-ui-step-nav-upcoming-text-color: var(--svelte-ui-text-subtle-color);
384
+ --svelte-ui-step-nav-error-color: var(--svelte-ui-error-color);
385
+ /* ラベル・説明 */
386
+ --svelte-ui-step-nav-label-color: var(--svelte-ui-text-color);
387
+ --svelte-ui-step-nav-description-color: var(--svelte-ui-text-subtle-color);
388
+ --svelte-ui-step-nav-font-size-sm: 12px;
389
+ --svelte-ui-step-nav-font-size: 14px;
390
+ --svelte-ui-step-nav-font-size-lg: 16px;
391
+ --svelte-ui-step-nav-description-font-size: var(--svelte-ui-font-size-sm);
392
+ --svelte-ui-step-nav-label-line-height: 1.3;
393
+ /* コネクター(完了区間 / 未完了区間) */
394
+ --svelte-ui-step-nav-connector-color: var(--svelte-ui-border-color);
395
+ --svelte-ui-step-nav-connector-completed-color: var(--svelte-ui-step-nav-accent-color);
396
+ --svelte-ui-step-nav-connector-thickness: 2px;
397
+ /* 間隔 */
398
+ --svelte-ui-step-nav-marker-label-gap: 8px;
399
+ --svelte-ui-step-nav-step-gap: 8px;
400
+ /* 表示中リング(フォーカス表示とは別レイヤー・別色) */
401
+ --svelte-ui-step-nav-ring-color: color-mix(
402
+ in srgb,
403
+ var(--svelte-ui-step-nav-accent-color) 40%,
404
+ transparent
405
+ );
406
+ --svelte-ui-step-nav-ring-width: 2px;
407
+ --svelte-ui-step-nav-ring-offset: 2px;
408
+ /* フォーカス表示(表示中リングと衝突しない色) */
409
+ --svelte-ui-step-nav-focus-color: var(--svelte-ui-focus-color);
371
410
  /* Input */
372
411
  --svelte-ui-input-height: var(--svelte-ui-form-height);
373
412
  --svelte-ui-input-padding: var(--svelte-ui-form-padding);
@@ -816,6 +855,18 @@
816
855
  --svelte-ui-nav-item-underline-text-color: CanvasText;
817
856
  --svelte-ui-nav-item-underline-selected-text-color: Highlight;
818
857
  --svelte-ui-nav-item-underline-bar-color: Highlight;
858
+ /* StepNav */
859
+ --svelte-ui-step-nav-accent-color: Highlight;
860
+ --svelte-ui-step-nav-marker-text-color: HighlightText;
861
+ --svelte-ui-step-nav-upcoming-marker-color: CanvasText;
862
+ --svelte-ui-step-nav-upcoming-text-color: CanvasText;
863
+ --svelte-ui-step-nav-label-color: CanvasText;
864
+ --svelte-ui-step-nav-description-color: CanvasText;
865
+ --svelte-ui-step-nav-error-color: CanvasText;
866
+ --svelte-ui-step-nav-connector-color: CanvasText;
867
+ --svelte-ui-step-nav-connector-completed-color: Highlight;
868
+ --svelte-ui-step-nav-ring-color: Highlight;
869
+ --svelte-ui-step-nav-focus-color: CanvasText;
819
870
  /* Radio */
820
871
  --svelte-ui-radio-border-color: CanvasText;
821
872
  --svelte-ui-radio-border-width: var(--svelte-ui-border-width-thick);
@@ -407,6 +407,46 @@
407
407
  --svelte-ui-nav-bottom-sheet-border-radius: 16px 16px 0 0;
408
408
  --svelte-ui-nav-bottom-sheet-padding: 16px 8px;
409
409
 
410
+ /* StepNav */
411
+ /* 強調色(現在/完了)。color prop 指定時は --internal-step-nav-accent が優先。 */
412
+ --svelte-ui-step-nav-accent-color: var(--svelte-ui-primary-color);
413
+ --svelte-ui-step-nav-completed-color: var(--svelte-ui-step-nav-accent-color);
414
+ --svelte-ui-step-nav-current-color: var(--svelte-ui-step-nav-accent-color);
415
+ /* マーカー */
416
+ --svelte-ui-step-nav-marker-size-sm: 24px;
417
+ --svelte-ui-step-nav-marker-size: 32px;
418
+ --svelte-ui-step-nav-marker-size-lg: 40px;
419
+ --svelte-ui-step-nav-marker-border-width: 2px;
420
+ --svelte-ui-step-nav-marker-text-color: var(--svelte-ui-text-on-filled-color);
421
+ --svelte-ui-step-nav-upcoming-marker-color: var(--svelte-ui-border-strong-color);
422
+ --svelte-ui-step-nav-upcoming-text-color: var(--svelte-ui-text-subtle-color);
423
+ --svelte-ui-step-nav-error-color: var(--svelte-ui-error-color);
424
+ /* ラベル・説明 */
425
+ --svelte-ui-step-nav-label-color: var(--svelte-ui-text-color);
426
+ --svelte-ui-step-nav-description-color: var(--svelte-ui-text-subtle-color);
427
+ --svelte-ui-step-nav-font-size-sm: 12px;
428
+ --svelte-ui-step-nav-font-size: 14px;
429
+ --svelte-ui-step-nav-font-size-lg: 16px;
430
+ --svelte-ui-step-nav-description-font-size: var(--svelte-ui-font-size-sm);
431
+ --svelte-ui-step-nav-label-line-height: 1.3;
432
+ /* コネクター(完了区間 / 未完了区間) */
433
+ --svelte-ui-step-nav-connector-color: var(--svelte-ui-border-color);
434
+ --svelte-ui-step-nav-connector-completed-color: var(--svelte-ui-step-nav-accent-color);
435
+ --svelte-ui-step-nav-connector-thickness: 2px;
436
+ /* 間隔 */
437
+ --svelte-ui-step-nav-marker-label-gap: 8px;
438
+ --svelte-ui-step-nav-step-gap: 8px;
439
+ /* 表示中リング(フォーカス表示とは別レイヤー・別色) */
440
+ --svelte-ui-step-nav-ring-color: color-mix(
441
+ in srgb,
442
+ var(--svelte-ui-step-nav-accent-color) 40%,
443
+ transparent
444
+ );
445
+ --svelte-ui-step-nav-ring-width: 2px;
446
+ --svelte-ui-step-nav-ring-offset: 2px;
447
+ /* フォーカス表示(表示中リングと衝突しない色) */
448
+ --svelte-ui-step-nav-focus-color: var(--svelte-ui-focus-color);
449
+
410
450
  /* Input */
411
451
  --svelte-ui-input-height: var(--svelte-ui-form-height);
412
452
  --svelte-ui-input-padding: var(--svelte-ui-form-padding);
@@ -898,6 +938,19 @@
898
938
  --svelte-ui-nav-item-underline-selected-text-color: Highlight;
899
939
  --svelte-ui-nav-item-underline-bar-color: Highlight;
900
940
 
941
+ /* StepNav */
942
+ --svelte-ui-step-nav-accent-color: Highlight;
943
+ --svelte-ui-step-nav-marker-text-color: HighlightText;
944
+ --svelte-ui-step-nav-upcoming-marker-color: CanvasText;
945
+ --svelte-ui-step-nav-upcoming-text-color: CanvasText;
946
+ --svelte-ui-step-nav-label-color: CanvasText;
947
+ --svelte-ui-step-nav-description-color: CanvasText;
948
+ --svelte-ui-step-nav-error-color: CanvasText;
949
+ --svelte-ui-step-nav-connector-color: CanvasText;
950
+ --svelte-ui-step-nav-connector-completed-color: Highlight;
951
+ --svelte-ui-step-nav-ring-color: Highlight;
952
+ --svelte-ui-step-nav-focus-color: CanvasText;
953
+
901
954
  /* Radio */
902
955
  --svelte-ui-radio-border-color: CanvasText;
903
956
  --svelte-ui-radio-border-width: var(--svelte-ui-border-width-thick);
@@ -0,0 +1,632 @@
1
+ <!-- StepNav.svelte -->
2
+
3
+ <script lang="ts">
4
+ import Icon from './Icon.svelte';
5
+ import type { StepItem } from '../types/stepItem';
6
+ import type { MenuItem } from '../types/menuItem';
7
+ import type { ComponentSize, StepNavOrientation } from '../types/propOptions';
8
+ import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
9
+ import type {
10
+ MouseHandler,
11
+ FocusHandler,
12
+ KeyboardHandler,
13
+ BivariantValueHandler
14
+ } from '../types/callbackHandlers';
15
+ import { getCurrentPath, matchPath } from '../utils/navPath';
16
+ import { subscribeUrlChange } from '../utils/urlChange';
17
+ import { t } from '../i18n';
18
+
19
+ // =========================================================================
20
+ // Props
21
+ // =========================================================================
22
+ export type StepNavProps = {
23
+ // 基本プロパティ
24
+ /** `{ label, value?, href?, description?, icon?, error?, disabled? }[]` */
25
+ items: StepItem[];
26
+ /** 表示中ステップの値(インページ方式)。`bind:value` 対応。`item.value` 省略時は配列 index の文字列('0','1',...)が暗黙の値になる。 */
27
+ value?: string;
28
+ /** 到達済みの最遠ステップの 0 始まりインデックス(進捗軸)。未指定時は表示中ステップ位置。 */
29
+ progress?: number;
30
+
31
+ // URL 方式(href 使用時)
32
+ /** 各 href のアクティブ判定に前置するパス。 */
33
+ pathPrefix?: string;
34
+ /** アクティブ判定をカスタムする関数。 */
35
+ customPathMatcher?: (currentPath: string, itemHref: string, item: StepItem) => boolean;
36
+ /** 自動検出した現在パスを上書き。 */
37
+ currentPath?: string;
38
+
39
+ // HTML属性
40
+ id?: string;
41
+
42
+ // スタイル/レイアウト
43
+ /** @default 'horizontal' */
44
+ orientation?: StepNavOrientation;
45
+ /** @default 'medium' */
46
+ size?: ComponentSize;
47
+ /** 現在/完了ステップの強調色。テーマ色より優先。 */
48
+ color?: string;
49
+ /** 番号の代わりに各ステップの icon を表示。 @default false */
50
+ showIcon?: boolean;
51
+ /** 追加インラインスタイル。 */
52
+ customStyle?: string;
53
+
54
+ // アイコン関連
55
+ iconVariant?: IconVariant;
56
+ iconWeight?: IconWeight;
57
+ iconGrade?: IconGrade;
58
+ iconOpticalSize?: IconOpticalSize;
59
+
60
+ // 状態/動作
61
+ /** インページ方式でステップのクリック遷移を許可。到達済み(progress 以下)のステップのみクリック可能。 @default false */
62
+ clickable?: boolean;
63
+ /** コンポーネント全体を無効化。 @default false */
64
+ disabled?: boolean;
65
+ /** アニメーションを抑制。 @default false */
66
+ reducedMotion?: boolean;
67
+
68
+ // ARIA/アクセシビリティ
69
+ ariaLabel?: string;
70
+ ariaLabelledby?: string;
71
+
72
+ // イベントハンドラ
73
+ /** 表示中ステップ変更(インページ方式)。 */
74
+ onchange?: BivariantValueHandler<string>;
75
+ onclick?: MouseHandler;
76
+ onfocus?: FocusHandler;
77
+ onblur?: FocusHandler;
78
+ onkeydown?: KeyboardHandler;
79
+ };
80
+
81
+ let {
82
+ // 基本プロパティ
83
+ items = [],
84
+ value = $bindable(''),
85
+ progress,
86
+
87
+ // URL 方式
88
+ pathPrefix = '',
89
+ customPathMatcher,
90
+ currentPath,
91
+
92
+ // HTML属性
93
+ id,
94
+
95
+ // スタイル/レイアウト
96
+ orientation = 'horizontal',
97
+ size = 'medium',
98
+ color,
99
+ showIcon = false,
100
+ customStyle = '',
101
+
102
+ // アイコン関連
103
+ iconVariant = 'outlined',
104
+ iconWeight = 300,
105
+ iconGrade = 0,
106
+ iconOpticalSize,
107
+
108
+ // 状態/動作
109
+ clickable = false,
110
+ disabled = false,
111
+ reducedMotion = false,
112
+
113
+ // ARIA/アクセシビリティ
114
+ ariaLabel,
115
+ ariaLabelledby,
116
+
117
+ // イベントハンドラ
118
+ onchange = () => {}, // No params for type inference
119
+ onclick = () => {}, // No params for type inference
120
+ onfocus = () => {}, // No params for type inference
121
+ onblur = () => {}, // No params for type inference
122
+ onkeydown = () => {} // No params for type inference
123
+ }: StepNavProps = $props();
124
+
125
+ // =========================================================================
126
+ // State & Effects
127
+ // =========================================================================
128
+ let rootEl: HTMLElement | undefined = $state();
129
+ let resolvedCurrentPath = $state('');
130
+
131
+ $effect(() => {
132
+ resolvedCurrentPath = getCurrentPath(currentPath);
133
+ });
134
+
135
+ $effect(() => {
136
+ return subscribeUrlChange(() => {
137
+ resolvedCurrentPath = getCurrentPath(currentPath);
138
+ });
139
+ });
140
+
141
+ // =========================================================================
142
+ // Derived state
143
+ // =========================================================================
144
+ const isLinkMode = $derived(items.some((item) => item.href != null));
145
+
146
+ const activeIndex = $derived.by(() => {
147
+ if (isLinkMode) {
148
+ return items.findIndex(
149
+ (item) =>
150
+ item.href != null &&
151
+ matchPath(
152
+ resolvedCurrentPath,
153
+ item.href,
154
+ item as unknown as MenuItem,
155
+ pathPrefix,
156
+ customPathMatcher as
157
+ | ((currentPath: string, itemHref: string, item: MenuItem) => boolean)
158
+ | undefined
159
+ )
160
+ );
161
+ }
162
+ return items.findIndex((item, index) => (item.value ?? String(index)) === value);
163
+ });
164
+
165
+ const progressIndex = $derived(progress ?? activeIndex);
166
+
167
+ const resolvedIconSize = $derived(
168
+ iconOpticalSize || (size === 'small' ? 16 : size === 'large' ? 24 : 20)
169
+ );
170
+
171
+ const rootClasses = $derived(
172
+ [
173
+ 'step-nav',
174
+ `step-nav--${orientation}`,
175
+ `step-nav--${size}`,
176
+ disabled && 'step-nav--disabled',
177
+ reducedMotion && 'step-nav--no-motion'
178
+ ]
179
+ .filter(Boolean)
180
+ .join(' ')
181
+ );
182
+
183
+ const rootStyle = $derived(
184
+ [
185
+ // color 指定時は強調色を上書き。リングは半透明の色なので同率で合成した値も注入する。
186
+ color ? `--internal-step-nav-accent:${color}` : '',
187
+ color ? `--internal-step-nav-ring-color:color-mix(in srgb, ${color} 40%, transparent)` : '',
188
+ customStyle
189
+ ]
190
+ .filter(Boolean)
191
+ .join('; ')
192
+ );
193
+
194
+ // =========================================================================
195
+ // Helpers
196
+ // =========================================================================
197
+ type StepStatus = 'completed' | 'current' | 'upcoming';
198
+
199
+ const getStatus = (index: number): StepStatus => {
200
+ if (index < progressIndex) return 'completed';
201
+ if (index === progressIndex) return 'current';
202
+ return 'upcoming';
203
+ };
204
+
205
+ const isStepDisabled = (item: StepItem): boolean => disabled || item.disabled === true;
206
+
207
+ // 対話的なステップか。到達済み(index <= progressIndex)のステップのみ対話可能。
208
+ // URL方式はリンク、インページ方式は clickable のときボタン。未到達の先のステップへは飛べない。無効なら常に非対話。
209
+ const isStepInteractive = (item: StepItem, index: number): boolean =>
210
+ !isStepDisabled(item) && index <= progressIndex && (isLinkMode || clickable);
211
+
212
+ const stepClasses = (item: StepItem, status: StepStatus, isViewing: boolean): string =>
213
+ [
214
+ 'step-nav__step',
215
+ `step-nav__step--${status}`,
216
+ isViewing && 'step-nav__step--viewing',
217
+ item.error && 'step-nav__step--error',
218
+ isStepDisabled(item) && 'step-nav__step--disabled'
219
+ ]
220
+ .filter(Boolean)
221
+ .join(' ');
222
+
223
+ // スクリーンリーダー向けの状態テキスト
224
+ const statusText = (index: number, item: StepItem): string => {
225
+ const step = t('stepNav.step', { number: index + 1 });
226
+ const state = item.error
227
+ ? t('stepNav.error')
228
+ : getStatus(index) === 'completed'
229
+ ? t('stepNav.completed')
230
+ : getStatus(index) === 'current'
231
+ ? t('stepNav.current')
232
+ : t('stepNav.upcoming');
233
+ return `${step}: ${state}`;
234
+ };
235
+
236
+ // クリック可能なインページボタンからのみ呼ばれる(未到達/無効/URL/非clickable は非対話要素で描画される)
237
+ const handleStepClick = (item: StepItem, index: number, event: MouseEvent) => {
238
+ const nextValue = item.value ?? String(index);
239
+ value = nextValue;
240
+ onchange(nextValue);
241
+ onclick(event);
242
+ };
243
+
244
+ // Arrow/Home/End はフォーカス移動の補助。Enter/Space は要素本来の活性化に委ねる。
245
+ const handleKeyDown = (event: KeyboardEvent) => {
246
+ onkeydown(event);
247
+
248
+ const focusables = Array.from(
249
+ rootEl?.querySelectorAll<HTMLElement>('[data-step-nav-interactive]') ?? []
250
+ ).filter((el) => el.tabIndex !== -1 && !(el as HTMLButtonElement).disabled);
251
+ if (focusables.length === 0) return;
252
+
253
+ const currentIndex = focusables.indexOf(event.target as HTMLElement);
254
+ if (currentIndex === -1) return;
255
+
256
+ const isVertical = orientation === 'vertical';
257
+ const prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';
258
+ const nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';
259
+
260
+ switch (event.key) {
261
+ case prevKey:
262
+ event.preventDefault();
263
+ focusables[currentIndex > 0 ? currentIndex - 1 : focusables.length - 1]?.focus();
264
+ break;
265
+ case nextKey:
266
+ event.preventDefault();
267
+ focusables[currentIndex < focusables.length - 1 ? currentIndex + 1 : 0]?.focus();
268
+ break;
269
+ case 'Home':
270
+ event.preventDefault();
271
+ focusables[0]?.focus();
272
+ break;
273
+ case 'End':
274
+ event.preventDefault();
275
+ focusables[focusables.length - 1]?.focus();
276
+ break;
277
+ default:
278
+ break;
279
+ }
280
+ };
281
+ </script>
282
+
283
+ {#snippet marker(item: StepItem, index: number, status: StepStatus)}
284
+ <span class="step-nav__marker" aria-hidden="true">
285
+ {#if item.error}
286
+ <Icon
287
+ size={resolvedIconSize}
288
+ weight={iconWeight}
289
+ grade={iconGrade}
290
+ variant={iconVariant}
291
+ filled>error</Icon
292
+ >
293
+ {:else if status === 'completed'}
294
+ <Icon size={resolvedIconSize} weight={iconWeight} grade={iconGrade} variant={iconVariant}
295
+ >check</Icon
296
+ >
297
+ {:else if showIcon && item.icon}
298
+ <Icon
299
+ size={resolvedIconSize}
300
+ weight={iconWeight}
301
+ grade={iconGrade}
302
+ opticalSize={iconOpticalSize}
303
+ variant={iconVariant}>{item.icon}</Icon
304
+ >
305
+ {:else}
306
+ {index + 1}
307
+ {/if}
308
+ </span>
309
+ {/snippet}
310
+
311
+ {#snippet stepInner(item: StepItem, index: number, status: StepStatus)}
312
+ {@render marker(item, index, status)}
313
+ <span class="step-nav__body">
314
+ <span class="step-nav__label">{item.label}</span>
315
+ {#if item.description}
316
+ <span class="step-nav__description">{item.description}</span>
317
+ {/if}
318
+ </span>
319
+ <span class="sr-only">{statusText(index, item)}</span>
320
+ {/snippet}
321
+
322
+ {#if items.length > 0}
323
+ <nav
324
+ bind:this={rootEl}
325
+ class={rootClasses}
326
+ style={rootStyle || undefined}
327
+ {id}
328
+ aria-label={ariaLabelledby ? undefined : ariaLabel}
329
+ aria-labelledby={ariaLabelledby}
330
+ data-testid="step-nav"
331
+ >
332
+ <!-- キーボード補助はランドマークを汚さないよう presentation ラッパーで受ける -->
333
+ <div style="display: contents" role="presentation" onkeydown={handleKeyDown}>
334
+ <ol class="step-nav__list" role="list">
335
+ {#each items as item, index (index)}
336
+ {@const status = getStatus(index)}
337
+ {@const isViewing = index === activeIndex}
338
+ <li class="step-nav__item" role="listitem">
339
+ {#if !isStepInteractive(item, index)}
340
+ <!-- 非対話(URL/インページを問わず共通): 表示のみ。無効ステップは aria-disabled を付与 -->
341
+ <span
342
+ class={stepClasses(item, status, isViewing)}
343
+ aria-current={isViewing ? 'step' : undefined}
344
+ aria-invalid={item.error ? 'true' : undefined}
345
+ aria-disabled={isStepDisabled(item) ? 'true' : undefined}
346
+ aria-label={item.ariaLabel ?? undefined}
347
+ >
348
+ {@render stepInner(item, index, status)}
349
+ </span>
350
+ {:else if isLinkMode}
351
+ <!-- 対話 × URL 方式: リンクとして描画し標準遷移に委ねる -->
352
+ <a
353
+ href={item.href}
354
+ class={stepClasses(item, status, isViewing)}
355
+ aria-current={isViewing ? 'step' : undefined}
356
+ aria-label={item.ariaLabel ?? undefined}
357
+ data-step-nav-interactive
358
+ {onfocus}
359
+ {onblur}
360
+ >
361
+ {@render stepInner(item, index, status)}
362
+ </a>
363
+ {:else}
364
+ <!-- 対話 × インページ方式: ボタンとして表示中を更新 -->
365
+ <button
366
+ type="button"
367
+ class={stepClasses(item, status, isViewing)}
368
+ aria-current={isViewing ? 'step' : undefined}
369
+ aria-label={item.ariaLabel ?? undefined}
370
+ data-step-nav-interactive
371
+ onclick={(event) => handleStepClick(item, index, event)}
372
+ {onfocus}
373
+ {onblur}
374
+ >
375
+ {@render stepInner(item, index, status)}
376
+ </button>
377
+ {/if}
378
+
379
+ {#if index < items.length - 1}
380
+ <span
381
+ class="step-nav__connector"
382
+ class:step-nav__connector--completed={index < progressIndex}
383
+ aria-hidden="true"
384
+ ></span>
385
+ {/if}
386
+ </li>
387
+ {/each}
388
+ </ol>
389
+ </div>
390
+ </nav>
391
+ {/if}
392
+
393
+ <style>@charset "UTF-8";
394
+ .step-nav {
395
+ --internal-step-nav-marker-size: var(--svelte-ui-step-nav-marker-size);
396
+ --internal-step-nav-font-size: var(--svelte-ui-step-nav-font-size);
397
+ display: block;
398
+ box-sizing: border-box;
399
+ max-width: 100%;
400
+ }
401
+
402
+ .step-nav--small {
403
+ --internal-step-nav-marker-size: var(--svelte-ui-step-nav-marker-size-sm);
404
+ --internal-step-nav-font-size: var(--svelte-ui-step-nav-font-size-sm);
405
+ }
406
+
407
+ .step-nav--large {
408
+ --internal-step-nav-marker-size: var(--svelte-ui-step-nav-marker-size-lg);
409
+ --internal-step-nav-font-size: var(--svelte-ui-step-nav-font-size-lg);
410
+ }
411
+
412
+ .step-nav__list {
413
+ display: flex;
414
+ margin: 0;
415
+ padding: 0;
416
+ list-style: none;
417
+ }
418
+
419
+ .step-nav__item {
420
+ display: flex;
421
+ position: relative;
422
+ box-sizing: border-box;
423
+ }
424
+
425
+ /* ---- 対話/非対話ステップ共通 ---- */
426
+ .step-nav__step {
427
+ display: flex;
428
+ align-items: center;
429
+ gap: var(--svelte-ui-step-nav-marker-label-gap);
430
+ position: relative;
431
+ border: none;
432
+ background: transparent;
433
+ padding: 0;
434
+ margin: 0;
435
+ font-family: inherit;
436
+ font-size: var(--internal-step-nav-font-size);
437
+ color: var(--svelte-ui-step-nav-label-color);
438
+ text-align: left;
439
+ text-decoration: none;
440
+ cursor: default;
441
+ box-sizing: border-box;
442
+ }
443
+
444
+ a.step-nav__step,
445
+ button.step-nav__step {
446
+ cursor: pointer;
447
+ }
448
+
449
+ .step-nav__step--disabled {
450
+ opacity: var(--svelte-ui-disabled-opacity);
451
+ cursor: not-allowed;
452
+ }
453
+
454
+ .step-nav__step:focus-visible {
455
+ outline: var(--svelte-ui-focus-outline-outer, 2px solid);
456
+ outline-color: var(--svelte-ui-step-nav-focus-color);
457
+ outline-offset: var(--svelte-ui-focus-outline-offset-outer, 2px);
458
+ border-radius: var(--svelte-ui-border-radius, 4px);
459
+ }
460
+
461
+ /* ---- マーカー ---- */
462
+ .step-nav__marker {
463
+ display: inline-flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ flex: 0 0 auto;
467
+ width: var(--internal-step-nav-marker-size);
468
+ height: var(--internal-step-nav-marker-size);
469
+ border-radius: 50%;
470
+ font-size: calc(var(--internal-step-nav-font-size) * 0.95);
471
+ font-weight: bold;
472
+ line-height: 1;
473
+ box-sizing: border-box;
474
+ position: relative;
475
+ z-index: 1;
476
+ transition-property: background-color, border-color, color;
477
+ transition-duration: var(--svelte-ui-transition-duration, 0.2s);
478
+ transition-timing-function: ease;
479
+ }
480
+
481
+ /* upcoming: 淡いアウトライン + 番号 */
482
+ .step-nav__step--upcoming .step-nav__marker {
483
+ background: var(--svelte-ui-surface-color);
484
+ color: var(--svelte-ui-step-nav-upcoming-text-color);
485
+ border: var(--svelte-ui-step-nav-marker-border-width) solid var(--svelte-ui-step-nav-upcoming-marker-color);
486
+ }
487
+
488
+ /* current: primary アウトライン + 番号 */
489
+ .step-nav__step--current .step-nav__marker {
490
+ background: var(--svelte-ui-surface-color);
491
+ color: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
492
+ border: var(--svelte-ui-step-nav-marker-border-width) solid var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
493
+ }
494
+
495
+ /* completed: primary 塗り + check */
496
+ .step-nav__step--completed .step-nav__marker {
497
+ background: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
498
+ color: var(--svelte-ui-step-nav-marker-text-color);
499
+ border: var(--svelte-ui-step-nav-marker-border-width) solid var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
500
+ }
501
+
502
+ /* error: error 色 + error アイコン(進捗軸を上書き) */
503
+ .step-nav__step--error .step-nav__marker {
504
+ background: var(--svelte-ui-surface-color);
505
+ color: var(--svelte-ui-step-nav-error-color);
506
+ border: var(--svelte-ui-step-nav-marker-border-width) solid var(--svelte-ui-step-nav-error-color);
507
+ }
508
+
509
+ /* 表示中: 外周リング(フォーカス表示とは別レイヤー・別色) */
510
+ .step-nav__step--viewing .step-nav__marker {
511
+ box-shadow: 0 0 0 var(--svelte-ui-step-nav-ring-offset) var(--svelte-ui-surface-color), 0 0 0 calc(var(--svelte-ui-step-nav-ring-offset) + var(--svelte-ui-step-nav-ring-width)) var(--internal-step-nav-ring-color, var(--svelte-ui-step-nav-ring-color));
512
+ }
513
+
514
+ /* ---- ラベル/説明 ---- */
515
+ .step-nav__body {
516
+ display: flex;
517
+ flex-direction: column;
518
+ min-width: 0;
519
+ }
520
+
521
+ .step-nav__label {
522
+ line-height: var(--svelte-ui-step-nav-label-line-height);
523
+ white-space: nowrap;
524
+ }
525
+
526
+ /* ラベル色の優先順位(同一詳細度のためソース順で解決): upcoming < viewing < error */
527
+ .step-nav__step--upcoming .step-nav__label {
528
+ color: var(--svelte-ui-step-nav-upcoming-text-color);
529
+ }
530
+
531
+ /* 表示中: 強調色 + 太字(Nav の選択中=primary 文字色と同じ規則。color prop で上書き可) */
532
+ .step-nav__step--viewing .step-nav__label {
533
+ color: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
534
+ font-weight: bold;
535
+ }
536
+
537
+ .step-nav__step--error .step-nav__label {
538
+ color: var(--svelte-ui-step-nav-error-color);
539
+ }
540
+
541
+ .step-nav__description {
542
+ font-size: var(--svelte-ui-step-nav-description-font-size);
543
+ color: var(--svelte-ui-step-nav-description-color);
544
+ line-height: var(--svelte-ui-step-nav-label-line-height);
545
+ }
546
+
547
+ /* ---- コネクター ---- */
548
+ .step-nav__connector {
549
+ position: absolute;
550
+ background: var(--svelte-ui-step-nav-connector-color);
551
+ }
552
+
553
+ .step-nav__connector--completed {
554
+ background: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-connector-completed-color));
555
+ }
556
+
557
+ /* ======================= 水平レイアウト ======================= */
558
+ .step-nav--horizontal .step-nav__list {
559
+ flex-direction: row;
560
+ overflow-x: auto;
561
+ overscroll-behavior: contain;
562
+ /* overflow-x を指定すると overflow-y も auto に計算され、マーカー上端の
563
+ 外周リング/フォーカス表示が切れる。上下に余白を確保して回避する。 */
564
+ padding-block: max(var(--svelte-ui-step-nav-ring-offset) + var(--svelte-ui-step-nav-ring-width), var(--svelte-ui-focus-outline-offset-outer, 2px) + 2px);
565
+ }
566
+
567
+ .step-nav--horizontal .step-nav__item {
568
+ /* 等幅カラムにしてマーカー中心を等間隔に揃える(コネクターの整列に必須)。
569
+ min-width をフロアにして、ステップが多いときは横スクロールで収める(Req 5.4)。 */
570
+ flex: 1 1 0;
571
+ min-width: calc(var(--internal-step-nav-marker-size) * 2.5);
572
+ flex-direction: column;
573
+ align-items: center;
574
+ text-align: center;
575
+ padding: 0 var(--svelte-ui-step-nav-step-gap);
576
+ }
577
+
578
+ .step-nav--horizontal .step-nav__step {
579
+ flex-direction: column;
580
+ align-items: center;
581
+ }
582
+
583
+ .step-nav--horizontal .step-nav__body {
584
+ align-items: center;
585
+ text-align: center;
586
+ }
587
+
588
+ /* 各ステップの右側に、次のマーカーへ向けてコネクターを描画(等幅前提で 100% 幅が隣の中心に一致) */
589
+ .step-nav--horizontal .step-nav__connector {
590
+ top: calc(var(--internal-step-nav-marker-size) / 2);
591
+ transform: translateY(-50%);
592
+ left: calc(50% + var(--internal-step-nav-marker-size) / 2);
593
+ width: calc(100% - var(--internal-step-nav-marker-size));
594
+ height: var(--svelte-ui-step-nav-connector-thickness);
595
+ }
596
+
597
+ /* ======================= 垂直レイアウト ======================= */
598
+ .step-nav--vertical .step-nav__list {
599
+ flex-direction: column;
600
+ }
601
+
602
+ .step-nav--vertical .step-nav__item {
603
+ flex-direction: row;
604
+ align-items: flex-start;
605
+ padding: var(--svelte-ui-step-nav-step-gap) 0;
606
+ }
607
+
608
+ .step-nav--vertical .step-nav__step {
609
+ flex-direction: row;
610
+ align-items: flex-start;
611
+ }
612
+
613
+ /* 各ステップの下側に、次のマーカーへ向けてコネクターを描画。
614
+ 縦 padding(step-gap)ぶんマーカーが下がっているので、開始位置に加味する(自身の高さ基準で可変高さでも整合)。 */
615
+ .step-nav--vertical .step-nav__connector {
616
+ left: calc(var(--internal-step-nav-marker-size) / 2);
617
+ transform: translateX(-50%);
618
+ top: calc(var(--internal-step-nav-marker-size) + var(--svelte-ui-step-nav-step-gap));
619
+ height: calc(100% - var(--internal-step-nav-marker-size));
620
+ width: var(--svelte-ui-step-nav-connector-thickness);
621
+ }
622
+
623
+ /* ---- モーション抑制 ---- */
624
+ .step-nav--no-motion .step-nav__marker {
625
+ transition-duration: 0.01s;
626
+ }
627
+
628
+ @media (prefers-reduced-motion: reduce) {
629
+ .step-nav__marker {
630
+ transition-duration: 0.01s;
631
+ }
632
+ }</style>
@@ -0,0 +1,50 @@
1
+ import type { StepItem } from '../types/stepItem';
2
+ import type { ComponentSize, StepNavOrientation } from '../types/propOptions';
3
+ import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
4
+ import type { MouseHandler, FocusHandler, KeyboardHandler, BivariantValueHandler } from '../types/callbackHandlers';
5
+ export type StepNavProps = {
6
+ /** `{ label, value?, href?, description?, icon?, error?, disabled? }[]` */
7
+ items: StepItem[];
8
+ /** 表示中ステップの値(インページ方式)。`bind:value` 対応。`item.value` 省略時は配列 index の文字列('0','1',...)が暗黙の値になる。 */
9
+ value?: string;
10
+ /** 到達済みの最遠ステップの 0 始まりインデックス(進捗軸)。未指定時は表示中ステップ位置。 */
11
+ progress?: number;
12
+ /** 各 href のアクティブ判定に前置するパス。 */
13
+ pathPrefix?: string;
14
+ /** アクティブ判定をカスタムする関数。 */
15
+ customPathMatcher?: (currentPath: string, itemHref: string, item: StepItem) => boolean;
16
+ /** 自動検出した現在パスを上書き。 */
17
+ currentPath?: string;
18
+ id?: string;
19
+ /** @default 'horizontal' */
20
+ orientation?: StepNavOrientation;
21
+ /** @default 'medium' */
22
+ size?: ComponentSize;
23
+ /** 現在/完了ステップの強調色。テーマ色より優先。 */
24
+ color?: string;
25
+ /** 番号の代わりに各ステップの icon を表示。 @default false */
26
+ showIcon?: boolean;
27
+ /** 追加インラインスタイル。 */
28
+ customStyle?: string;
29
+ iconVariant?: IconVariant;
30
+ iconWeight?: IconWeight;
31
+ iconGrade?: IconGrade;
32
+ iconOpticalSize?: IconOpticalSize;
33
+ /** インページ方式でステップのクリック遷移を許可。到達済み(progress 以下)のステップのみクリック可能。 @default false */
34
+ clickable?: boolean;
35
+ /** コンポーネント全体を無効化。 @default false */
36
+ disabled?: boolean;
37
+ /** アニメーションを抑制。 @default false */
38
+ reducedMotion?: boolean;
39
+ ariaLabel?: string;
40
+ ariaLabelledby?: string;
41
+ /** 表示中ステップ変更(インページ方式)。 */
42
+ onchange?: BivariantValueHandler<string>;
43
+ onclick?: MouseHandler;
44
+ onfocus?: FocusHandler;
45
+ onblur?: FocusHandler;
46
+ onkeydown?: KeyboardHandler;
47
+ };
48
+ declare const StepNav: import("svelte").Component<StepNavProps, {}, "value">;
49
+ type StepNav = ReturnType<typeof StepNav>;
50
+ export default StepNav;
@@ -58,6 +58,13 @@ export declare const TRANSLATIONS: {
58
58
  readonly goToPage: "Go to page {{page}}";
59
59
  readonly ellipsis: "Omitted pages";
60
60
  };
61
+ readonly stepNav: {
62
+ readonly step: "Step {{number}}";
63
+ readonly completed: "Completed";
64
+ readonly current: "Current step";
65
+ readonly upcoming: "Not started";
66
+ readonly error: "Error";
67
+ };
61
68
  };
62
69
  readonly ja: {
63
70
  readonly datepicker: {
@@ -117,6 +124,13 @@ export declare const TRANSLATIONS: {
117
124
  readonly goToPage: "{{page}}ページ目へ移動";
118
125
  readonly ellipsis: "省略されたページ";
119
126
  };
127
+ readonly stepNav: {
128
+ readonly step: "ステップ{{number}}";
129
+ readonly completed: "完了";
130
+ readonly current: "現在のステップ";
131
+ readonly upcoming: "未到達";
132
+ readonly error: "エラー";
133
+ };
120
134
  };
121
135
  readonly fr: {
122
136
  readonly datepicker: {
@@ -176,6 +190,13 @@ export declare const TRANSLATIONS: {
176
190
  readonly goToPage: "Aller à la page {{page}}";
177
191
  readonly ellipsis: "Pages omises";
178
192
  };
193
+ readonly stepNav: {
194
+ readonly step: "Étape {{number}}";
195
+ readonly completed: "Terminé";
196
+ readonly current: "Étape actuelle";
197
+ readonly upcoming: "Non commencé";
198
+ readonly error: "Erreur";
199
+ };
179
200
  };
180
201
  readonly de: {
181
202
  readonly datepicker: {
@@ -235,6 +256,13 @@ export declare const TRANSLATIONS: {
235
256
  readonly goToPage: "Zu Seite {{page}} gehen";
236
257
  readonly ellipsis: "Ausgelassene Seiten";
237
258
  };
259
+ readonly stepNav: {
260
+ readonly step: "Schritt {{number}}";
261
+ readonly completed: "Abgeschlossen";
262
+ readonly current: "Aktueller Schritt";
263
+ readonly upcoming: "Noch nicht begonnen";
264
+ readonly error: "Fehler";
265
+ };
238
266
  };
239
267
  readonly es: {
240
268
  readonly datepicker: {
@@ -294,6 +322,13 @@ export declare const TRANSLATIONS: {
294
322
  readonly goToPage: "Ir a la página {{page}}";
295
323
  readonly ellipsis: "Páginas omitidas";
296
324
  };
325
+ readonly stepNav: {
326
+ readonly step: "Paso {{number}}";
327
+ readonly completed: "Completado";
328
+ readonly current: "Paso actual";
329
+ readonly upcoming: "No iniciado";
330
+ readonly error: "Error";
331
+ };
297
332
  };
298
333
  readonly 'zh-cn': {
299
334
  readonly datepicker: {
@@ -353,6 +388,13 @@ export declare const TRANSLATIONS: {
353
388
  readonly goToPage: "转到第{{page}}页";
354
389
  readonly ellipsis: "省略的页面";
355
390
  };
391
+ readonly stepNav: {
392
+ readonly step: "第{{number}}步";
393
+ readonly completed: "已完成";
394
+ readonly current: "当前步骤";
395
+ readonly upcoming: "未开始";
396
+ readonly error: "错误";
397
+ };
356
398
  };
357
399
  };
358
400
  type NestedKeyOf<T> = T extends object ? {
@@ -56,4 +56,11 @@ export declare const de: {
56
56
  readonly goToPage: "Zu Seite {{page}} gehen";
57
57
  readonly ellipsis: "Ausgelassene Seiten";
58
58
  };
59
+ readonly stepNav: {
60
+ readonly step: "Schritt {{number}}";
61
+ readonly completed: "Abgeschlossen";
62
+ readonly current: "Aktueller Schritt";
63
+ readonly upcoming: "Noch nicht begonnen";
64
+ readonly error: "Fehler";
65
+ };
59
66
  };
@@ -55,5 +55,12 @@ export const de = {
55
55
  nextPage: 'Zur nächsten Seite gehen',
56
56
  goToPage: 'Zu Seite {{page}} gehen',
57
57
  ellipsis: 'Ausgelassene Seiten'
58
+ },
59
+ stepNav: {
60
+ step: 'Schritt {{number}}',
61
+ completed: 'Abgeschlossen',
62
+ current: 'Aktueller Schritt',
63
+ upcoming: 'Noch nicht begonnen',
64
+ error: 'Fehler'
58
65
  }
59
66
  };
@@ -56,4 +56,11 @@ export declare const en: {
56
56
  readonly goToPage: "Go to page {{page}}";
57
57
  readonly ellipsis: "Omitted pages";
58
58
  };
59
+ readonly stepNav: {
60
+ readonly step: "Step {{number}}";
61
+ readonly completed: "Completed";
62
+ readonly current: "Current step";
63
+ readonly upcoming: "Not started";
64
+ readonly error: "Error";
65
+ };
59
66
  };
@@ -55,5 +55,12 @@ export const en = {
55
55
  nextPage: 'Go to next page',
56
56
  goToPage: 'Go to page {{page}}',
57
57
  ellipsis: 'Omitted pages'
58
+ },
59
+ stepNav: {
60
+ step: 'Step {{number}}',
61
+ completed: 'Completed',
62
+ current: 'Current step',
63
+ upcoming: 'Not started',
64
+ error: 'Error'
58
65
  }
59
66
  };
@@ -56,4 +56,11 @@ export declare const es: {
56
56
  readonly goToPage: "Ir a la página {{page}}";
57
57
  readonly ellipsis: "Páginas omitidas";
58
58
  };
59
+ readonly stepNav: {
60
+ readonly step: "Paso {{number}}";
61
+ readonly completed: "Completado";
62
+ readonly current: "Paso actual";
63
+ readonly upcoming: "No iniciado";
64
+ readonly error: "Error";
65
+ };
59
66
  };
@@ -55,5 +55,12 @@ export const es = {
55
55
  nextPage: 'Ir a la página siguiente',
56
56
  goToPage: 'Ir a la página {{page}}',
57
57
  ellipsis: 'Páginas omitidas'
58
+ },
59
+ stepNav: {
60
+ step: 'Paso {{number}}',
61
+ completed: 'Completado',
62
+ current: 'Paso actual',
63
+ upcoming: 'No iniciado',
64
+ error: 'Error'
58
65
  }
59
66
  };
@@ -56,4 +56,11 @@ export declare const fr: {
56
56
  readonly goToPage: "Aller à la page {{page}}";
57
57
  readonly ellipsis: "Pages omises";
58
58
  };
59
+ readonly stepNav: {
60
+ readonly step: "Étape {{number}}";
61
+ readonly completed: "Terminé";
62
+ readonly current: "Étape actuelle";
63
+ readonly upcoming: "Non commencé";
64
+ readonly error: "Erreur";
65
+ };
59
66
  };
@@ -55,5 +55,12 @@ export const fr = {
55
55
  nextPage: 'Aller à la page suivante',
56
56
  goToPage: 'Aller à la page {{page}}',
57
57
  ellipsis: 'Pages omises'
58
+ },
59
+ stepNav: {
60
+ step: 'Étape {{number}}',
61
+ completed: 'Terminé',
62
+ current: 'Étape actuelle',
63
+ upcoming: 'Non commencé',
64
+ error: 'Erreur'
58
65
  }
59
66
  };
@@ -56,4 +56,11 @@ export declare const ja: {
56
56
  readonly goToPage: "{{page}}ページ目へ移動";
57
57
  readonly ellipsis: "省略されたページ";
58
58
  };
59
+ readonly stepNav: {
60
+ readonly step: "ステップ{{number}}";
61
+ readonly completed: "完了";
62
+ readonly current: "現在のステップ";
63
+ readonly upcoming: "未到達";
64
+ readonly error: "エラー";
65
+ };
59
66
  };
@@ -55,5 +55,12 @@ export const ja = {
55
55
  nextPage: '次のページへ移動',
56
56
  goToPage: '{{page}}ページ目へ移動',
57
57
  ellipsis: '省略されたページ'
58
+ },
59
+ stepNav: {
60
+ step: 'ステップ{{number}}',
61
+ completed: '完了',
62
+ current: '現在のステップ',
63
+ upcoming: '未到達',
64
+ error: 'エラー'
58
65
  }
59
66
  };
@@ -56,4 +56,11 @@ export declare const zhCn: {
56
56
  readonly goToPage: "转到第{{page}}页";
57
57
  readonly ellipsis: "省略的页面";
58
58
  };
59
+ readonly stepNav: {
60
+ readonly step: "第{{number}}步";
61
+ readonly completed: "已完成";
62
+ readonly current: "当前步骤";
63
+ readonly upcoming: "未开始";
64
+ readonly error: "错误";
65
+ };
59
66
  };
@@ -55,5 +55,12 @@ export const zhCn = {
55
55
  nextPage: '转到下一页',
56
56
  goToPage: '转到第{{page}}页',
57
57
  ellipsis: '省略的页面'
58
+ },
59
+ stepNav: {
60
+ step: '第{{number}}步',
61
+ completed: '已完成',
62
+ current: '当前步骤',
63
+ upcoming: '未开始',
64
+ error: '错误'
58
65
  }
59
66
  };
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export { default as Radio } from './components/Radio.svelte';
25
25
  export { default as RadioGroup } from './components/RadioGroup.svelte';
26
26
  export { default as Select } from './components/Select.svelte';
27
27
  export { default as SegmentedControl } from './components/SegmentedControl.svelte';
28
+ export { default as StepNav } from './components/StepNav.svelte';
28
29
  export { default as Slider } from './components/Slider.svelte';
29
30
  export { default as Snackbar } from './components/Snackbar.svelte';
30
31
  export { default as SnackbarItem } from './components/SnackbarItem.svelte';
@@ -68,6 +69,7 @@ export type { RadioProps } from './components/Radio.svelte';
68
69
  export type { RadioGroupProps } from './components/RadioGroup.svelte';
69
70
  export type { SelectProps } from './components/Select.svelte';
70
71
  export type { SegmentedControlProps } from './components/SegmentedControl.svelte';
72
+ export type { StepNavProps } from './components/StepNav.svelte';
71
73
  export type { SliderProps } from './components/Slider.svelte';
72
74
  export type { SnackbarProps } from './components/Snackbar.svelte';
73
75
  export type { SnackbarItemProps } from './components/SnackbarItem.svelte';
@@ -83,8 +85,9 @@ export type { NavProps } from './components/Nav.svelte';
83
85
  export type { NavItemProps, NavItemSelectedVariant } from './components/NavItem.svelte';
84
86
  export type { TabProps } from './components/Tab.svelte';
85
87
  export type { TextareaProps } from './components/Textarea.svelte';
86
- export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, ComponentSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant } from './types/propOptions';
88
+ export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, ComponentSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant, StepNavOrientation } from './types/propOptions';
87
89
  export type { MenuItem } from './types/menuItem';
90
+ export type { StepItem } from './types/stepItem';
88
91
  export type { SegmentedControlItem } from './types/segmentedControlItem';
89
92
  export type { Option, OptionValue } from './types/options';
90
93
  export type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from './types/icon';
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ export { default as Radio } from './components/Radio.svelte';
26
26
  export { default as RadioGroup } from './components/RadioGroup.svelte';
27
27
  export { default as Select } from './components/Select.svelte';
28
28
  export { default as SegmentedControl } from './components/SegmentedControl.svelte';
29
+ export { default as StepNav } from './components/StepNav.svelte';
29
30
  export { default as Slider } from './components/Slider.svelte';
30
31
  export { default as Snackbar } from './components/Snackbar.svelte';
31
32
  export { default as SnackbarItem } from './components/SnackbarItem.svelte';
@@ -62,6 +62,11 @@ export type FocusStyle = 'background' | 'outline' | 'none';
62
62
  * Used by Nav component
63
63
  */
64
64
  export type NavVariant = 'vertical' | 'horizontal' | 'mobile';
65
+ /**
66
+ * StepNav orientation type
67
+ * Used by StepNav component
68
+ */
69
+ export type StepNavOrientation = 'horizontal' | 'vertical';
65
70
  /**
66
71
  * Display variant for Nav hierarchical (children) menus.
67
72
  * - `popup`: child items appear in a floating panel (all variants)
@@ -0,0 +1,22 @@
1
+ export type StepItem = {
2
+ /** 表示ラベル。 */
3
+ label: string;
4
+ /** インページ方式での識別子(value 制御時に一致判定へ使用)。省略時は配列 index の文字列(`String(index)`)がフォールバックに使われる。 */
5
+ value?: string;
6
+ /** URL 方式でのリンク先。指定があると URL 方式として扱われる。 */
7
+ href?: string;
8
+ /** ラベル下の補足説明。 */
9
+ description?: string;
10
+ /** 番号の代わりに表示するアイコン名(Material Symbols)。 */
11
+ icon?: string;
12
+ /** エラー状態。進捗軸の状態に上書きしてエラー表示する。 */
13
+ error?: boolean;
14
+ /** 個別無効化。 */
15
+ disabled?: boolean;
16
+ /** URL 方式の追加マッチパス(`matchPath` 準拠)。 */
17
+ matchingPath?: string[];
18
+ /** URL 方式の完全一致指定。 */
19
+ strictMatch?: boolean;
20
+ /** アクセシブルネーム(アイコンのみ等)。 */
21
+ ariaLabel?: string;
22
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@14ch/svelte-ui",
3
3
  "description": "Modern Svelte UI components library with TypeScript support",
4
4
  "private": false,
5
- "version": "0.0.49",
5
+ "version": "0.0.51",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "svelte",