@14ch/svelte-ui 0.0.1

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.
Files changed (109) hide show
  1. package/README.md +359 -0
  2. package/dist/assets/styles/README.md +144 -0
  3. package/dist/assets/styles/core.scss +61 -0
  4. package/dist/assets/styles/import.scss +11 -0
  5. package/dist/assets/styles/optional/fonts.scss +23 -0
  6. package/dist/assets/styles/optional/reset.scss +230 -0
  7. package/dist/assets/styles/variables.scss +805 -0
  8. package/dist/components/Button.svelte +574 -0
  9. package/dist/components/Button.svelte.d.ts +56 -0
  10. package/dist/components/COMPONENT_DESIGN_GUIDELINES.md +127 -0
  11. package/dist/components/Checkbox.svelte +523 -0
  12. package/dist/components/Checkbox.svelte.d.ts +42 -0
  13. package/dist/components/CheckboxGroup.svelte +82 -0
  14. package/dist/components/CheckboxGroup.svelte.d.ts +13 -0
  15. package/dist/components/ColorPicker.svelte +496 -0
  16. package/dist/components/ColorPicker.svelte.d.ts +45 -0
  17. package/dist/components/Combobox.svelte +576 -0
  18. package/dist/components/Combobox.svelte.d.ts +52 -0
  19. package/dist/components/ConfirmDialog.svelte +116 -0
  20. package/dist/components/ConfirmDialog.svelte.d.ts +20 -0
  21. package/dist/components/Datepicker.svelte +578 -0
  22. package/dist/components/Datepicker.svelte.d.ts +72 -0
  23. package/dist/components/DatepickerCalendar.svelte +925 -0
  24. package/dist/components/DatepickerCalendar.svelte.d.ts +31 -0
  25. package/dist/components/Dialog.svelte +245 -0
  26. package/dist/components/Dialog.svelte.d.ts +38 -0
  27. package/dist/components/Drawer.svelte +383 -0
  28. package/dist/components/Drawer.svelte.d.ts +39 -0
  29. package/dist/components/Fab.svelte +486 -0
  30. package/dist/components/Fab.svelte.d.ts +51 -0
  31. package/dist/components/FileUploader.svelte +456 -0
  32. package/dist/components/FileUploader.svelte.d.ts +36 -0
  33. package/dist/components/Icon.svelte +167 -0
  34. package/dist/components/Icon.svelte.d.ts +21 -0
  35. package/dist/components/IconButton.svelte +557 -0
  36. package/dist/components/IconButton.svelte.d.ts +60 -0
  37. package/dist/components/ImageUploader.svelte +516 -0
  38. package/dist/components/ImageUploader.svelte.d.ts +37 -0
  39. package/dist/components/ImageUploaderPreview.svelte +157 -0
  40. package/dist/components/ImageUploaderPreview.svelte.d.ts +13 -0
  41. package/dist/components/Input.svelte +885 -0
  42. package/dist/components/Input.svelte.d.ts +75 -0
  43. package/dist/components/LoadingSpinner.svelte +116 -0
  44. package/dist/components/LoadingSpinner.svelte.d.ts +10 -0
  45. package/dist/components/Modal.svelte +313 -0
  46. package/dist/components/Modal.svelte.d.ts +34 -0
  47. package/dist/components/Pagination.svelte +276 -0
  48. package/dist/components/Pagination.svelte.d.ts +14 -0
  49. package/dist/components/Popup.svelte +676 -0
  50. package/dist/components/Popup.svelte.d.ts +40 -0
  51. package/dist/components/PopupMenu.svelte +421 -0
  52. package/dist/components/PopupMenu.svelte.d.ts +24 -0
  53. package/dist/components/PopupMenuButton.svelte +365 -0
  54. package/dist/components/PopupMenuButton.svelte.d.ts +42 -0
  55. package/dist/components/Radio.svelte +548 -0
  56. package/dist/components/Radio.svelte.d.ts +42 -0
  57. package/dist/components/RadioGroup.svelte +74 -0
  58. package/dist/components/RadioGroup.svelte.d.ts +14 -0
  59. package/dist/components/Select.svelte +479 -0
  60. package/dist/components/Select.svelte.d.ts +47 -0
  61. package/dist/components/Slider.svelte +473 -0
  62. package/dist/components/Slider.svelte.d.ts +46 -0
  63. package/dist/components/Snackbar.svelte +124 -0
  64. package/dist/components/Snackbar.svelte.d.ts +9 -0
  65. package/dist/components/SnackbarItem.svelte +423 -0
  66. package/dist/components/SnackbarItem.svelte.d.ts +21 -0
  67. package/dist/components/Switch.svelte +454 -0
  68. package/dist/components/Switch.svelte.d.ts +40 -0
  69. package/dist/components/Tab.svelte +193 -0
  70. package/dist/components/Tab.svelte.d.ts +14 -0
  71. package/dist/components/TabItem.svelte +140 -0
  72. package/dist/components/TabItem.svelte.d.ts +17 -0
  73. package/dist/components/Textarea.svelte +702 -0
  74. package/dist/components/Textarea.svelte.d.ts +64 -0
  75. package/dist/components/skeleton/Skeleton.svelte +235 -0
  76. package/dist/components/skeleton/Skeleton.svelte.d.ts +13 -0
  77. package/dist/components/skeleton/SkeletonAvatar.svelte +97 -0
  78. package/dist/components/skeleton/SkeletonAvatar.svelte.d.ts +8 -0
  79. package/dist/components/skeleton/SkeletonBox.svelte +105 -0
  80. package/dist/components/skeleton/SkeletonBox.svelte.d.ts +12 -0
  81. package/dist/components/skeleton/SkeletonButton.svelte +71 -0
  82. package/dist/components/skeleton/SkeletonButton.svelte.d.ts +8 -0
  83. package/dist/components/skeleton/SkeletonHeading.svelte +49 -0
  84. package/dist/components/skeleton/SkeletonHeading.svelte.d.ts +8 -0
  85. package/dist/components/skeleton/SkeletonMedia.svelte +115 -0
  86. package/dist/components/skeleton/SkeletonMedia.svelte.d.ts +9 -0
  87. package/dist/components/skeleton/SkeletonText.svelte +75 -0
  88. package/dist/components/skeleton/SkeletonText.svelte.d.ts +8 -0
  89. package/dist/index.d.ts +42 -0
  90. package/dist/index.js +43 -0
  91. package/dist/types/icon.d.ts +4 -0
  92. package/dist/types/icon.js +2 -0
  93. package/dist/types/menuItem.d.ts +8 -0
  94. package/dist/types/menuItem.js +1 -0
  95. package/dist/types/options.d.ts +6 -0
  96. package/dist/types/options.js +4 -0
  97. package/dist/types/skeleton.d.ts +77 -0
  98. package/dist/types/skeleton.js +19 -0
  99. package/dist/utils/accessibility.d.ts +48 -0
  100. package/dist/utils/accessibility.js +87 -0
  101. package/dist/utils/formatText.d.ts +4 -0
  102. package/dist/utils/formatText.js +44 -0
  103. package/dist/utils/mobile.d.ts +9 -0
  104. package/dist/utils/mobile.js +47 -0
  105. package/dist/utils/snackbar.svelte.d.ts +51 -0
  106. package/dist/utils/snackbar.svelte.js +107 -0
  107. package/dist/utils/style.d.ts +17 -0
  108. package/dist/utils/style.js +22 -0
  109. package/package.json +102 -0
@@ -0,0 +1,454 @@
1
+ <!-- Switch.svelte -->
2
+
3
+ <script lang="ts">
4
+ import type { Snippet } from 'svelte';
5
+ import type { HTMLInputAttributes } from 'svelte/elements';
6
+
7
+ // =========================================================================
8
+ // Props, States & Constants
9
+ // =========================================================================
10
+ let {
11
+ // Snippet
12
+ children,
13
+
14
+ // 基本プロパティ
15
+ value = $bindable(false),
16
+
17
+ // HTML属性系
18
+ id = `switch-${Math.random().toString(36).substring(2, 15)}`,
19
+ inputAttributes,
20
+
21
+ // スタイル/レイアウト
22
+ size = 'medium',
23
+
24
+ // 状態/動作
25
+ disabled = false,
26
+ required = false,
27
+ reducedMotion = false,
28
+
29
+ // フォーカスイベント
30
+ onfocus = () => {}, // No params for type inference
31
+ onblur = () => {}, // No params for type inference
32
+
33
+ // キーボードイベント
34
+ onkeydown = () => {}, // No params for type inference
35
+ onkeyup = () => {}, // No params for type inference
36
+
37
+ // マウスイベント
38
+ onclick = () => {}, // No params for type inference
39
+ onmousedown = () => {}, // No params for type inference
40
+ onmouseup = () => {}, // No params for type inference
41
+ onmouseenter = () => {}, // No params for type inference
42
+ onmouseleave = () => {}, // No params for type inference
43
+ onmouseover = () => {}, // No params for type inference
44
+ onmouseout = () => {}, // No params for type inference
45
+ oncontextmenu = () => {}, // No params for type inference
46
+ onauxclick = () => {}, // No params for type inference
47
+
48
+ // タッチイベント
49
+ ontouchstart = () => {}, // No params for type inference
50
+ ontouchend = () => {}, // No params for type inference
51
+ ontouchmove = () => {}, // No params for type inference
52
+ ontouchcancel = () => {}, // No params for type inference
53
+
54
+ // ポインターイベント
55
+ onpointerdown = () => {}, // No params for type inference
56
+ onpointerup = () => {}, // No params for type inference
57
+ onpointerenter = () => {}, // No params for type inference
58
+ onpointerleave = () => {}, // No params for type inference
59
+ onpointermove = () => {}, // No params for type inference
60
+ onpointercancel = () => {}, // No params for type inference
61
+
62
+ // 変更イベント
63
+ onchange = (value: boolean) => {},
64
+
65
+ // その他
66
+ ...restProps
67
+ }: {
68
+ // 基本プロパティ
69
+ value?: boolean;
70
+
71
+ // ラベル
72
+ children?: Snippet;
73
+
74
+ // HTML属性系
75
+ id?: string;
76
+ inputAttributes?: HTMLInputAttributes | undefined;
77
+
78
+ // サイズ
79
+ size?: 'small' | 'medium' | 'large';
80
+
81
+ // 無効状態
82
+ disabled?: boolean;
83
+
84
+ // 必須項目
85
+ required?: boolean;
86
+
87
+ // アニメーション無効化
88
+ reducedMotion?: boolean;
89
+
90
+ // フォーカスイベント
91
+ onfocus?: Function; // No params for type inference
92
+ onblur?: Function; // No params for type inference
93
+
94
+ // キーボードイベント
95
+ onkeydown?: Function; // No params for type inference
96
+ onkeyup?: Function; // No params for type inference
97
+
98
+ // マウスイベント
99
+ onclick?: Function; // No params for type inference
100
+ onmousedown?: Function; // No params for type inference
101
+ onmouseup?: Function; // No params for type inference
102
+ onmouseenter?: Function; // No params for type inference
103
+ onmouseleave?: Function; // No params for type inference
104
+ onmouseover?: Function; // No params for type inference
105
+ onmouseout?: Function; // No params for type inference
106
+ oncontextmenu?: Function; // No params for type inference
107
+ onauxclick?: Function; // No params for type inference
108
+
109
+ // タッチイベント
110
+ ontouchstart?: Function; // No params for type inference
111
+ ontouchend?: Function; // No params for type inference
112
+ ontouchmove?: Function; // No params for type inference
113
+ ontouchcancel?: Function; // No params for type inference
114
+
115
+ // ポインターイベント
116
+ onpointerdown?: Function; // No params for type inference
117
+ onpointerup?: Function; // No params for type inference
118
+ onpointerenter?: Function; // No params for type inference
119
+ onpointerleave?: Function; // No params for type inference
120
+ onpointermove?: Function; // No params for type inference
121
+ onpointercancel?: Function; // No params for type inference
122
+
123
+ // 変更イベント
124
+ onchange?: (value: boolean) => void;
125
+
126
+ // その他
127
+ [key: string]: any;
128
+ } = $props();
129
+
130
+ let inputRef: HTMLInputElement | undefined = $state();
131
+ let errorId = `switch-error-${Math.random().toString(36).substring(2, 15)}`;
132
+
133
+ // =========================================================================
134
+
135
+ // =========================================================================
136
+ // Methods
137
+ // =========================================================================
138
+ const handleFocus = (event: FocusEvent) => {
139
+ if (disabled) return;
140
+ onfocus?.(event);
141
+ };
142
+
143
+ const handleBlur = (event: FocusEvent) => {
144
+ if (disabled) return;
145
+ onblur?.(event);
146
+ };
147
+
148
+ const handleKeyDown = (event: KeyboardEvent) => {
149
+ if (disabled) return;
150
+ onkeydown?.(event);
151
+ if (event.key === 'Enter' || event.key === ' ') {
152
+ event.preventDefault();
153
+ value = !value;
154
+ onchange?.(value);
155
+ }
156
+ };
157
+
158
+ const handleKeyUp = (event: KeyboardEvent) => {
159
+ if (disabled) return;
160
+ onkeyup?.(event);
161
+ };
162
+
163
+ const handleClick = (event: MouseEvent) => {
164
+ if (disabled) return;
165
+ onclick?.(event);
166
+ // スイッチの状態を切り替え
167
+ value = !value;
168
+ onchange?.(value);
169
+ };
170
+
171
+ const handleMouseDown = (event: MouseEvent) => {
172
+ if (disabled) return;
173
+ onmousedown?.(event);
174
+ };
175
+
176
+ const handleMouseUp = (event: MouseEvent) => {
177
+ if (disabled) return;
178
+ onmouseup?.(event);
179
+ };
180
+
181
+ const handleMouseEnter = (event: MouseEvent) => {
182
+ if (disabled) return;
183
+ onmouseenter?.(event);
184
+ };
185
+
186
+ const handleMouseLeave = (event: MouseEvent) => {
187
+ if (disabled) return;
188
+ onmouseleave?.(event);
189
+ };
190
+
191
+ const handleMouseOver = (event: MouseEvent) => {
192
+ if (disabled) return;
193
+ onmouseover?.(event);
194
+ };
195
+
196
+ const handleMouseOut = (event: MouseEvent) => {
197
+ if (disabled) return;
198
+ onmouseout?.(event);
199
+ };
200
+
201
+ const handleContextMenu = (event: MouseEvent) => {
202
+ if (disabled) return;
203
+ oncontextmenu?.(event);
204
+ };
205
+
206
+ const handleAuxClick = (event: MouseEvent) => {
207
+ if (disabled) return;
208
+ onauxclick?.(event);
209
+ };
210
+
211
+ const handleTouchStart = (event: TouchEvent) => {
212
+ if (disabled) return;
213
+ ontouchstart?.(event);
214
+ };
215
+
216
+ const handleTouchEnd = (event: TouchEvent) => {
217
+ if (disabled) return;
218
+ ontouchend?.(event);
219
+ };
220
+
221
+ const handleTouchMove = (event: TouchEvent) => {
222
+ if (disabled) return;
223
+ ontouchmove?.(event);
224
+ };
225
+
226
+ const handleTouchCancel = (event: TouchEvent) => {
227
+ if (disabled) return;
228
+ ontouchcancel?.(event);
229
+ };
230
+
231
+ const handlePointerDown = (event: PointerEvent) => {
232
+ if (disabled) return;
233
+ onpointerdown?.(event);
234
+ };
235
+
236
+ const handlePointerUp = (event: PointerEvent) => {
237
+ if (disabled) return;
238
+ onpointerup?.(event);
239
+ };
240
+
241
+ const handlePointerEnter = (event: PointerEvent) => {
242
+ if (disabled) return;
243
+ onpointerenter?.(event);
244
+ };
245
+
246
+ const handlePointerLeave = (event: PointerEvent) => {
247
+ if (disabled) return;
248
+ onpointerleave?.(event);
249
+ };
250
+
251
+ const handlePointerMove = (event: PointerEvent) => {
252
+ if (disabled) return;
253
+ onpointermove?.(event);
254
+ };
255
+
256
+ const handlePointerCancel = (event: PointerEvent) => {
257
+ if (disabled) return;
258
+ onpointercancel?.(event);
259
+ };
260
+
261
+ const handleChange = (event: Event) => {
262
+ if (disabled) return;
263
+ const target = event.target as HTMLInputElement;
264
+ value = target.checked;
265
+ onchange?.(value);
266
+ };
267
+ </script>
268
+
269
+ <div
270
+ class="switch"
271
+ class:switch--small={size === 'small'}
272
+ class:switch--medium={size === 'medium'}
273
+ class:switch--large={size === 'large'}
274
+ class:switch--disabled={disabled}
275
+ class:switch--checked={value}
276
+ class:switch--reduced-motion={reducedMotion}
277
+ data-testid="switch"
278
+ >
279
+ <input
280
+ bind:this={inputRef}
281
+ bind:checked={value}
282
+ type="checkbox"
283
+ class="switch-input"
284
+ {disabled}
285
+ {required}
286
+ {id}
287
+ {...inputAttributes}
288
+ onchange={handleChange}
289
+ onfocus={handleFocus}
290
+ onblur={handleBlur}
291
+ onkeydown={handleKeyDown}
292
+ onkeyup={handleKeyUp}
293
+ onclick={handleClick}
294
+ onmousedown={handleMouseDown}
295
+ onmouseup={handleMouseUp}
296
+ onmouseenter={handleMouseEnter}
297
+ onmouseleave={handleMouseLeave}
298
+ onmouseover={handleMouseOver}
299
+ onmouseout={handleMouseOut}
300
+ oncontextmenu={handleContextMenu}
301
+ onauxclick={handleAuxClick}
302
+ ontouchstart={handleTouchStart}
303
+ ontouchend={handleTouchEnd}
304
+ ontouchmove={handleTouchMove}
305
+ ontouchcancel={handleTouchCancel}
306
+ onpointerdown={handlePointerDown}
307
+ onpointerup={handlePointerUp}
308
+ onpointerenter={handlePointerEnter}
309
+ onpointerleave={handlePointerLeave}
310
+ onpointermove={handlePointerMove}
311
+ onpointercancel={handlePointerCancel}
312
+ {...restProps}
313
+ />
314
+
315
+ <label for={id} class="switch__track">
316
+ <span class="switch-thumb"></span>
317
+ </label>
318
+
319
+ <label for={id} class="switch__label" class:switch__label--disabled={disabled}>
320
+ {#if children}
321
+ {@render children()}
322
+ {/if}
323
+ </label>
324
+ </div>
325
+
326
+ <style>@charset "UTF-8";
327
+ /* =============================================
328
+ * Base Styles
329
+ * ============================================= */
330
+ .switch {
331
+ display: inline-flex;
332
+ align-items: center;
333
+ font-family: var(--svelte-ui-font-family);
334
+ font-size: inherit;
335
+ line-height: var(--svelte-ui-line-height);
336
+ color: var(--svelte-ui-text-color);
337
+ }
338
+
339
+ .switch-input {
340
+ position: absolute;
341
+ opacity: 0;
342
+ width: 1px;
343
+ height: 1px;
344
+ margin: -1px;
345
+ padding: 0;
346
+ overflow: hidden;
347
+ clip: rect(0, 0, 0, 0);
348
+ white-space: nowrap;
349
+ border: 0;
350
+ }
351
+
352
+ .switch__label {
353
+ display: inline-flex;
354
+ align-items: center;
355
+ padding-left: var(--svelte-ui-switch-gap);
356
+ cursor: pointer;
357
+ user-select: none;
358
+ }
359
+ .switch__label--disabled {
360
+ cursor: not-allowed;
361
+ opacity: 0.5;
362
+ }
363
+
364
+ .switch__track {
365
+ position: relative;
366
+ width: var(--switch-width);
367
+ height: var(--switch-height);
368
+ background-color: var(--svelte-ui-switch-inactive-color);
369
+ border-radius: var(--switch-border-radius);
370
+ transition: background-color var(--svelte-ui-transition-duration) ease, filter var(--svelte-ui-transition-duration) ease;
371
+ flex-shrink: 0;
372
+ cursor: pointer;
373
+ }
374
+ .switch--checked .switch__track {
375
+ background-color: var(--switch-active-color, var(--svelte-ui-switch-active-color));
376
+ }
377
+ .switch--disabled .switch__track {
378
+ opacity: var(--svelte-ui-switch-disabled-opacity);
379
+ }
380
+
381
+ .switch-thumb {
382
+ position: absolute;
383
+ top: var(--switch-thumb-margin);
384
+ left: var(--switch-thumb-margin);
385
+ width: var(--switch-thumb-size);
386
+ height: var(--switch-thumb-size);
387
+ background-color: var(--svelte-ui-switch-thumb-color);
388
+ border-radius: var(--switch-thumb-border-radius);
389
+ transition: transform var(--svelte-ui-transition-duration) ease;
390
+ }
391
+ .switch--checked .switch-thumb {
392
+ transform: translateX(calc(var(--switch-width) - var(--switch-thumb-size) - var(--switch-thumb-margin) * 2));
393
+ }
394
+ .switch--disabled .switch-thumb {
395
+ opacity: var(--svelte-ui-switch-disabled-opacity);
396
+ }
397
+
398
+ /* =========================================================================
399
+ * Size Variants
400
+ * ========================================================================= */
401
+ .switch--small {
402
+ --switch-width: var(--svelte-ui-switch-width-sm);
403
+ --switch-height: var(--svelte-ui-switch-height-sm);
404
+ --switch-thumb-size: var(--svelte-ui-switch-thumb-size-sm);
405
+ --switch-thumb-margin: var(--svelte-ui-switch-thumb-margin);
406
+ --switch-border-radius: var(--svelte-ui-switch-border-radius);
407
+ --switch-thumb-border-radius: var(--svelte-ui-switch-thumb-border-radius);
408
+ }
409
+
410
+ .switch--medium {
411
+ --switch-width: var(--svelte-ui-switch-width);
412
+ --switch-height: var(--svelte-ui-switch-height);
413
+ --switch-thumb-size: var(--svelte-ui-switch-thumb-size);
414
+ --switch-thumb-margin: var(--svelte-ui-switch-thumb-margin);
415
+ --switch-border-radius: var(--svelte-ui-switch-border-radius);
416
+ --switch-thumb-border-radius: var(--svelte-ui-switch-thumb-border-radius);
417
+ }
418
+
419
+ .switch--large {
420
+ --switch-width: var(--svelte-ui-switch-width-lg);
421
+ --switch-height: var(--svelte-ui-switch-height-lg);
422
+ --switch-thumb-size: var(--svelte-ui-switch-thumb-size-lg);
423
+ --switch-thumb-margin: var(--svelte-ui-switch-thumb-margin);
424
+ --switch-border-radius: var(--svelte-ui-switch-border-radius);
425
+ --switch-thumb-border-radius: var(--svelte-ui-switch-thumb-border-radius);
426
+ }
427
+
428
+ /* =============================================
429
+ * フォーカス状態
430
+ * ============================================= */
431
+ .switch-input:focus-visible + .switch__track {
432
+ outline: 2px solid var(--svelte-ui-focus-color);
433
+ outline-offset: 2px;
434
+ }
435
+
436
+ /* =========================================================================
437
+ * Motion & Media Queries
438
+ * ========================================================================= */
439
+ /* Mobile touch targets */
440
+ @media (hover: none) and (pointer: coarse) {
441
+ .switch__label {
442
+ min-height: var(--svelte-ui-touch-target);
443
+ }
444
+ .switch--small .switch__label {
445
+ min-height: var(--svelte-ui-touch-target-sm);
446
+ }
447
+ .switch--large .switch__label {
448
+ min-height: var(--svelte-ui-touch-target-lg);
449
+ }
450
+ }
451
+ .switch--reduced-motion * {
452
+ transition: none !important;
453
+ animation: none !important;
454
+ }</style>
@@ -0,0 +1,40 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = {
4
+ value?: boolean;
5
+ children?: Snippet;
6
+ id?: string;
7
+ inputAttributes?: HTMLInputAttributes | undefined;
8
+ size?: 'small' | 'medium' | 'large';
9
+ disabled?: boolean;
10
+ required?: boolean;
11
+ reducedMotion?: boolean;
12
+ onfocus?: Function;
13
+ onblur?: Function;
14
+ onkeydown?: Function;
15
+ onkeyup?: Function;
16
+ onclick?: Function;
17
+ onmousedown?: Function;
18
+ onmouseup?: Function;
19
+ onmouseenter?: Function;
20
+ onmouseleave?: Function;
21
+ onmouseover?: Function;
22
+ onmouseout?: Function;
23
+ oncontextmenu?: Function;
24
+ onauxclick?: Function;
25
+ ontouchstart?: Function;
26
+ ontouchend?: Function;
27
+ ontouchmove?: Function;
28
+ ontouchcancel?: Function;
29
+ onpointerdown?: Function;
30
+ onpointerup?: Function;
31
+ onpointerenter?: Function;
32
+ onpointerleave?: Function;
33
+ onpointermove?: Function;
34
+ onpointercancel?: Function;
35
+ onchange?: (value: boolean) => void;
36
+ [key: string]: any;
37
+ };
38
+ declare const Switch: import("svelte").Component<$$ComponentProps, {}, "value">;
39
+ type Switch = ReturnType<typeof Switch>;
40
+ export default Switch;
@@ -0,0 +1,193 @@
1
+ <!-- Tab.svelte -->
2
+
3
+ <script lang="ts">
4
+ import TabItem from './TabItem.svelte';
5
+ import type { MenuItem } from '../types/menuItem';
6
+
7
+ // =========================================================================
8
+ // Props, States & Constants
9
+ // =========================================================================
10
+
11
+ let {
12
+ // 基本プロパティ
13
+ tabItems = [],
14
+ pathPrefix = '',
15
+ customPathMatcher,
16
+
17
+ // スタイル/レイアウト
18
+ textColor = 'var(--svelte-ui-text-subtle-color)',
19
+ selectedTextColor = 'var(--svelte-ui-primary-color)',
20
+ selectedBarColor = 'var(--svelte-ui-primary-color)',
21
+
22
+ // ARIA/アクセシビリティ
23
+ ariaLabel = 'Tabs',
24
+ ariaLabelledby
25
+ }: {
26
+ // 基本プロパティ
27
+ tabItems: MenuItem[];
28
+ pathPrefix?: string;
29
+ customPathMatcher?: (currentPath: string, itemHref: string, item: MenuItem) => boolean;
30
+
31
+ // スタイル/レイアウト
32
+ textColor?: string;
33
+ selectedTextColor?: string;
34
+ selectedBarColor?: string;
35
+
36
+ // ARIA/アクセシビリティ
37
+ ariaLabel?: string;
38
+ ariaLabelledby?: string;
39
+ } = $props();
40
+
41
+ // =========================================================================
42
+ // Methods
43
+ // =========================================================================
44
+
45
+ // ブラウザ標準APIを使用した現在パス取得
46
+ const getCurrentPath = () => {
47
+ if (typeof window !== 'undefined') {
48
+ return window.location.pathname;
49
+ }
50
+ return '';
51
+ };
52
+
53
+ // パスの正規化処理
54
+ const normalizePath = (path: string): string => {
55
+ if (!pathPrefix) return path;
56
+
57
+ // pathPrefixが設定されている場合、それを除去
58
+ if (path.startsWith(pathPrefix)) {
59
+ const normalized = path.substring(pathPrefix.length);
60
+ return normalized.startsWith('/') ? normalized : '/' + normalized;
61
+ }
62
+
63
+ return path;
64
+ };
65
+
66
+ // パスマッチング関数
67
+ const matchPath = (currentPath: string, itemHref: string, item: MenuItem): boolean => {
68
+ if (customPathMatcher) {
69
+ return customPathMatcher(currentPath, itemHref, item);
70
+ }
71
+
72
+ const normalizedCurrentPath = normalizePath(currentPath);
73
+
74
+ // matchingPathのチェック
75
+ if (item.matchingPath?.some((href) => normalizedCurrentPath.startsWith(href))) {
76
+ return true;
77
+ }
78
+
79
+ // strictMatchの場合
80
+ if (item.strictMatch) {
81
+ return normalizedCurrentPath === itemHref;
82
+ }
83
+
84
+ // ルートパス (/) の特別な処理
85
+ if (itemHref === '/') {
86
+ return normalizedCurrentPath === '/';
87
+ }
88
+
89
+ // その他のパス
90
+ return normalizedCurrentPath !== '' && normalizedCurrentPath.startsWith(itemHref);
91
+ };
92
+
93
+ // シンプルなキーボードナビゲーション
94
+ const handleKeyDown = (event: KeyboardEvent) => {
95
+ if (tabItems.length === 0) return;
96
+
97
+ const tabList = event.currentTarget as HTMLElement;
98
+ const tabs = Array.from(tabList.querySelectorAll('a[role="tab"]')) as HTMLElement[];
99
+ const currentTab = event.target as HTMLElement;
100
+ const currentIndex = tabs.indexOf(currentTab);
101
+
102
+ if (currentIndex === -1) return;
103
+
104
+ let nextIndex = currentIndex;
105
+
106
+ switch (event.key) {
107
+ case 'ArrowLeft':
108
+ event.preventDefault();
109
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
110
+ break;
111
+ case 'ArrowRight':
112
+ event.preventDefault();
113
+ nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
114
+ break;
115
+ case 'Home':
116
+ event.preventDefault();
117
+ nextIndex = 0;
118
+ break;
119
+ case 'End':
120
+ event.preventDefault();
121
+ nextIndex = tabs.length - 1;
122
+ break;
123
+ default:
124
+ return;
125
+ }
126
+
127
+ tabs[nextIndex]?.focus();
128
+ };
129
+
130
+ // =========================================================================
131
+ // $derived
132
+ // =========================================================================
133
+
134
+ // 現在のパスを取得
135
+ const currentPath = $derived(getCurrentPath());
136
+
137
+ // アクティブなタブのインデックスを現在のパスに基づいて計算
138
+ const selectedTabIndex = $derived.by(() => {
139
+ for (let i = 0; i < tabItems.length; i++) {
140
+ const item = tabItems[i];
141
+ if (!item.href) continue;
142
+
143
+ if (matchPath(currentPath, item.href, item)) {
144
+ return i;
145
+ }
146
+ }
147
+ return -1;
148
+ });
149
+ </script>
150
+
151
+ <div
152
+ class="tab"
153
+ role="tablist"
154
+ aria-label={ariaLabelledby ? undefined : ariaLabel}
155
+ aria-labelledby={ariaLabelledby}
156
+ tabindex="-1"
157
+ onkeydown={handleKeyDown}
158
+ data-testid="tab"
159
+ >
160
+ {#each tabItems as tabItem, index}
161
+ <div class="tab__item">
162
+ <TabItem
163
+ {tabItem}
164
+ isSelected={index === selectedTabIndex}
165
+ {textColor}
166
+ {selectedTextColor}
167
+ {selectedBarColor}
168
+ />
169
+ </div>
170
+ {/each}
171
+ </div>
172
+
173
+ <style>.tab {
174
+ display: flex;
175
+ justify-content: start;
176
+ width: 100%;
177
+ height: 100%;
178
+ overflow-x: auto;
179
+ overflow-y: visible;
180
+ -ms-overflow-style: none;
181
+ overscroll-behavior: contain;
182
+ box-sizing: border-box;
183
+ }
184
+
185
+ .tab::-webkit-scrollbar {
186
+ display: none;
187
+ }
188
+
189
+ .tab__item {
190
+ display: block;
191
+ height: 100%;
192
+ flex-shrink: 0;
193
+ }</style>
@@ -0,0 +1,14 @@
1
+ import type { MenuItem } from '../types/menuItem';
2
+ type $$ComponentProps = {
3
+ tabItems: MenuItem[];
4
+ pathPrefix?: string;
5
+ customPathMatcher?: (currentPath: string, itemHref: string, item: MenuItem) => boolean;
6
+ textColor?: string;
7
+ selectedTextColor?: string;
8
+ selectedBarColor?: string;
9
+ ariaLabel?: string;
10
+ ariaLabelledby?: string;
11
+ };
12
+ declare const Tab: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type Tab = ReturnType<typeof Tab>;
14
+ export default Tab;