svelte-comp 1.2.7 → 1.3.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.
Files changed (139) hide show
  1. package/README.md +12 -11
  2. package/dist/App.svelte +497 -2
  3. package/dist/app.css +2 -3
  4. package/dist/app.d.ts +10 -0
  5. package/dist/lang.d.ts +3 -3
  6. package/dist/lang.js +3 -3
  7. package/dist/lib/Accordion.svelte +14 -14
  8. package/dist/lib/Badge.svelte +44 -0
  9. package/dist/lib/Badge.svelte.d.ts +10 -0
  10. package/dist/lib/Button.svelte +23 -8
  11. package/dist/lib/Calendar.svelte +384 -377
  12. package/dist/lib/Card.svelte +6 -6
  13. package/dist/lib/Carousel.svelte +16 -16
  14. package/dist/lib/Carousel.svelte.d.ts +1 -1
  15. package/dist/lib/CheckBox.svelte +2 -2
  16. package/dist/lib/CodeView.svelte +6 -5
  17. package/dist/lib/ColorPicker.svelte +6 -6
  18. package/dist/lib/ContextMenu.svelte +328 -0
  19. package/dist/lib/ContextMenu.svelte.d.ts +14 -0
  20. package/dist/lib/DatePicker.svelte +161 -161
  21. package/dist/lib/Dialog.svelte +10 -10
  22. package/dist/lib/Field.svelte +1 -1
  23. package/dist/lib/FilePicker.svelte +127 -74
  24. package/dist/lib/FilePicker.svelte.d.ts +6 -3
  25. package/dist/lib/Hamburger.svelte +27 -21
  26. package/dist/lib/InstallPWA.svelte +94 -0
  27. package/dist/lib/InstallPWA.svelte.d.ts +8 -0
  28. package/dist/lib/Menu.svelte +18 -18
  29. package/dist/lib/NoticeBase.svelte +140 -0
  30. package/dist/lib/NoticeBase.svelte.d.ts +43 -0
  31. package/dist/lib/PrimaryColorSelect.svelte +6 -6
  32. package/dist/lib/ProgressCircle.svelte +7 -9
  33. package/dist/lib/ProgressCircle.svelte.d.ts +7 -9
  34. package/dist/lib/SearchInput.svelte +6 -6
  35. package/dist/lib/Select.svelte +2 -2
  36. package/dist/lib/Slider.svelte +1 -1
  37. package/dist/lib/Splitter.svelte +15 -6
  38. package/dist/lib/Switch.svelte +5 -4
  39. package/dist/lib/Tabs.svelte +6 -6
  40. package/dist/lib/ThemeToggle.svelte +1 -0
  41. package/dist/lib/TimePicker.svelte +103 -95
  42. package/dist/lib/TimePickerNew.svelte +634 -0
  43. package/dist/lib/TimePickerNew.svelte.d.ts +49 -0
  44. package/dist/lib/Toast.svelte +17 -120
  45. package/dist/lib/Tooltip.svelte +7 -7
  46. package/dist/lib/Topbar.svelte +112 -0
  47. package/dist/lib/Topbar.svelte.d.ts +44 -0
  48. package/dist/lib/__tests__/Accordion.test.d.ts +1 -0
  49. package/dist/lib/__tests__/Accordion.test.js +171 -0
  50. package/dist/lib/__tests__/Badge.test.d.ts +1 -0
  51. package/dist/lib/__tests__/Badge.test.js +41 -0
  52. package/dist/lib/__tests__/Button.test.d.ts +1 -0
  53. package/dist/lib/__tests__/Button.test.js +269 -0
  54. package/dist/lib/__tests__/Calendar.test.d.ts +1 -0
  55. package/dist/lib/__tests__/Calendar.test.js +171 -0
  56. package/dist/lib/__tests__/Card.test.d.ts +1 -0
  57. package/dist/lib/__tests__/Card.test.js +148 -0
  58. package/dist/lib/__tests__/Carousel.test.d.ts +1 -0
  59. package/dist/lib/__tests__/Carousel.test.js +439 -0
  60. package/dist/lib/__tests__/CheckBox.test.d.ts +1 -0
  61. package/dist/lib/__tests__/CheckBox.test.js +152 -0
  62. package/dist/lib/__tests__/CodeView.test.d.ts +1 -0
  63. package/dist/lib/__tests__/CodeView.test.js +157 -0
  64. package/dist/lib/__tests__/ColorPicker.test.d.ts +1 -0
  65. package/dist/lib/__tests__/ColorPicker.test.js +93 -0
  66. package/dist/lib/__tests__/ContextMenu.test.d.ts +1 -0
  67. package/dist/lib/__tests__/ContextMenu.test.js +67 -0
  68. package/dist/lib/__tests__/DatePicker.test.d.ts +1 -0
  69. package/dist/lib/__tests__/DatePicker.test.js +108 -0
  70. package/dist/lib/__tests__/Dialog.test.d.ts +1 -0
  71. package/dist/lib/__tests__/Dialog.test.js +183 -0
  72. package/dist/lib/__tests__/Field.test.d.ts +1 -0
  73. package/dist/lib/__tests__/Field.test.js +190 -0
  74. package/dist/lib/__tests__/FilePicker.test.d.ts +1 -0
  75. package/dist/lib/__tests__/FilePicker.test.js +179 -0
  76. package/dist/lib/__tests__/Form.integration.test.d.ts +1 -0
  77. package/dist/lib/__tests__/Form.integration.test.js +158 -0
  78. package/dist/lib/__tests__/Form.test.d.ts +1 -0
  79. package/dist/lib/__tests__/Form.test.js +463 -0
  80. package/dist/lib/__tests__/Hamburger.test.d.ts +1 -0
  81. package/dist/lib/__tests__/Hamburger.test.js +161 -0
  82. package/dist/lib/__tests__/InstallPWA.test.d.ts +1 -0
  83. package/dist/lib/__tests__/InstallPWA.test.js +15 -0
  84. package/dist/lib/__tests__/Menu.test.d.ts +1 -0
  85. package/dist/lib/__tests__/Menu.test.js +285 -0
  86. package/dist/lib/__tests__/NoticeBase.test.d.ts +1 -0
  87. package/dist/lib/__tests__/NoticeBase.test.js +60 -0
  88. package/dist/lib/__tests__/PaginatedCard.test.d.ts +1 -0
  89. package/dist/lib/__tests__/PaginatedCard.test.js +89 -0
  90. package/dist/lib/__tests__/Pagination.test.d.ts +1 -0
  91. package/dist/lib/__tests__/Pagination.test.js +168 -0
  92. package/dist/lib/__tests__/PrimaryColorSelect.test.d.ts +1 -0
  93. package/dist/lib/__tests__/PrimaryColorSelect.test.js +92 -0
  94. package/dist/lib/__tests__/ProgressBar.test.d.ts +1 -0
  95. package/dist/lib/__tests__/ProgressBar.test.js +69 -0
  96. package/dist/lib/__tests__/ProgressCircle.test.d.ts +1 -0
  97. package/dist/lib/__tests__/ProgressCircle.test.js +71 -0
  98. package/dist/lib/__tests__/Radio.test.d.ts +1 -0
  99. package/dist/lib/__tests__/Radio.test.js +127 -0
  100. package/dist/lib/__tests__/SearchInput.test.d.ts +1 -0
  101. package/dist/lib/__tests__/SearchInput.test.js +80 -0
  102. package/dist/lib/__tests__/Select.test.d.ts +1 -0
  103. package/dist/lib/__tests__/Select.test.js +408 -0
  104. package/dist/lib/__tests__/Slider.test.d.ts +1 -0
  105. package/dist/lib/__tests__/Slider.test.js +213 -0
  106. package/dist/lib/__tests__/Splitter.test.d.ts +1 -0
  107. package/dist/lib/__tests__/Splitter.test.js +87 -0
  108. package/dist/lib/__tests__/Switch.test.d.ts +1 -0
  109. package/dist/lib/__tests__/Switch.test.js +97 -0
  110. package/dist/lib/__tests__/Table.test.d.ts +1 -0
  111. package/dist/lib/__tests__/Table.test.js +349 -0
  112. package/dist/lib/__tests__/Tabs.test.d.ts +1 -0
  113. package/dist/lib/__tests__/Tabs.test.js +262 -0
  114. package/dist/lib/__tests__/ThemeToggle.test.d.ts +1 -0
  115. package/dist/lib/__tests__/ThemeToggle.test.js +84 -0
  116. package/dist/lib/__tests__/TimePicker.test.d.ts +1 -0
  117. package/dist/lib/__tests__/TimePicker.test.js +146 -0
  118. package/dist/lib/__tests__/TimePickerNew.test.d.ts +1 -0
  119. package/dist/lib/__tests__/TimePickerNew.test.js +322 -0
  120. package/dist/lib/__tests__/Toast.test.d.ts +1 -0
  121. package/dist/lib/__tests__/Toast.test.js +135 -0
  122. package/dist/lib/__tests__/Tooltip.test.d.ts +1 -0
  123. package/dist/lib/__tests__/Tooltip.test.js +171 -0
  124. package/dist/lib/__tests__/Topbar.test.d.ts +1 -0
  125. package/dist/lib/__tests__/Topbar.test.js +25 -0
  126. package/dist/lib/__tests__/setupLangContext.d.ts +1 -0
  127. package/dist/lib/__tests__/setupLangContext.js +65 -0
  128. package/dist/lib/__tests__/storage.test.d.ts +1 -0
  129. package/dist/lib/__tests__/storage.test.js +124 -0
  130. package/dist/lib/__tests__/utils.test.d.ts +1 -0
  131. package/dist/lib/__tests__/utils.test.js +11 -0
  132. package/dist/lib/index.d.ts +5 -0
  133. package/dist/lib/index.js +5 -0
  134. package/dist/lib/lang.d.ts +64 -0
  135. package/dist/lib/lang.js +64 -0
  136. package/dist/lib/types/index.d.ts +1 -0
  137. package/dist/styles.css +2 -0
  138. package/dist/utils/index.js +15 -4
  139. package/package.json +12 -12
@@ -16,6 +16,10 @@
16
16
  * @prop clearable {boolean} - Shows a clear button to reset selection
17
17
  * @default true
18
18
  *
19
+ * @prop maxBytes {number} - Maximum allowed file size in bytes
20
+ *
21
+ * @prop onError {(error: string) => void} - Fired when selected files are rejected
22
+ *
19
23
  * @prop placeholder {string} - Placeholder text for the drop zone
20
24
  *
21
25
  * @prop value {FileList | null} - Controlled selected files (bindable)
@@ -23,14 +27,12 @@
23
27
  *
24
28
  * @prop onFilesSelected {(files: FileList | null) => void} - Fired when files are chosen
25
29
  *
26
- * @prop onError {(error: string) => void} - Fired on validation errors
27
- *
28
30
  * @prop class {string} - Additional classes for the wrapper
29
31
  * @default ""
30
32
  *
31
33
  * @note The entire area is clickable and supports drag-and-drop.
32
34
  * @note After a selection, the underlying input resets its value, so choosing the same file twice still triggers updates.
33
- * @note `accept` does not apply to dropped files, only to the picker UI; validate files inside `onFilesSelected`.
35
+ * @note `accept` and `maxBytes` are enforced for both input and dropped files.
34
36
  * @note When `clearable=true`, the user can clear selected files and the callback receives `null`.
35
37
  * @note When `disabled=true`, clicks, drag events, focus, and keyboard input are blocked.
36
38
  */
@@ -43,6 +45,7 @@ type Props = HTMLAttributes<HTMLDivElement> & {
43
45
  clearable?: boolean;
44
46
  placeholder?: string;
45
47
  value?: FileList | null;
48
+ maxBytes?: number;
46
49
  onFilesSelected?: (files: FileList | null) => void;
47
50
  onError?: (error: string) => void;
48
51
  class?: string;
@@ -117,7 +117,7 @@
117
117
  });
118
118
 
119
119
  const triggerBase =
120
- "fixed top-4 left-4 inline-flex items-center justify-center h-8 w-8 rounded-md border border-[var(--border-color-default)] bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] transition-colors z-[var(--z-modal)]";
120
+ "fixed top-4 left-4 inline-flex items-center justify-center h-8 w-8 rounded-[var(--radius-md)] [@media(pointer:coarse)]:min-h-11 [@media(pointer:coarse)]:min-w-11 border border-[var(--border-color-default)] bg-[var(--color-bg-secondary)] hover:bg-[var(--color-bg-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-color-focus)] transition-colors z-[var(--z-modal)]";
121
121
 
122
122
  const triggerClass = $derived(cx(triggerBase, externalClass));
123
123
  </script>
@@ -133,19 +133,19 @@
133
133
  <span class="relative block w-5 h-3.5">
134
134
  <span
135
135
  class={cx(
136
- "absolute left-0 top-1/2 h-[2px] w-full bg-current transition-transform duration-200",
136
+ "absolute left-0 top-1/2 h-[2px] w-full bg-current transition-transform duration-[var(--transition-fast)]",
137
137
  open ? "translate-y-[-50%] rotate-45" : "translate-y-[calc(-50%_-_6px)]"
138
138
  )}
139
139
  ></span>
140
140
  <span
141
141
  class={cx(
142
- "absolute left-0 top-1/2 h-[2px] w-full bg-current transition-opacity duration-200 translate-y-[-50%]",
142
+ "absolute left-0 top-1/2 h-[2px] w-full bg-current transition-opacity duration-[var(--transition-fast)] translate-y-[-50%]",
143
143
  open ? "opacity-0" : "opacity-100"
144
144
  )}
145
145
  ></span>
146
146
  <span
147
147
  class={cx(
148
- "absolute left-0 top-1/2 h-[2px] w-full bg-current transition-transform duration-200",
148
+ "absolute left-0 top-1/2 h-[2px] w-full bg-current transition-transform duration-[var(--transition-fast)]",
149
149
  open
150
150
  ? "translate-y-[-50%] -rotate-45"
151
151
  : "translate-y-[calc(-50%_+_6px)]"
@@ -165,37 +165,43 @@
165
165
  style={`width:${typeof width === "number" ? `${width}px` : width}`}
166
166
  >
167
167
  {#if header}
168
- <div class="p-4 border-b border-[var(--border-color-default)]">
168
+ <div class="p-[var(--spacing-md)] border-b border-[var(--border-color-default)]">
169
169
  {@render header?.()}
170
170
  </div>
171
171
  {/if}
172
172
 
173
173
  <div class="flex-1 overflow-y-auto" tabindex="-1">
174
174
  {#if menuItems.length === 0}
175
- <div class="text-xs opacity-70 px-3 py-2 text-center">No items</div>
175
+ <div class="[font-size:var(--text-xs)] opacity-70 px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[var(--spacing-sm)] text-center">No items</div>
176
176
  {:else}
177
- <ul class="grid gap-2 p-4">
177
+ <ul class="grid gap-[var(--spacing-sm)] p-[var(--spacing-md)]">
178
178
  {#each menuItems as it (it.id)}
179
- <li>
180
- <button
181
- type="button"
182
- class="w-full text-left px-3 py-2 rounded-md hover:bg-[var(--color-bg-hover)] focus:outline-[var(--border-color-focus)] focus:outline-2 transition-colors"
183
- aria-current={activeItem === it.id ? "page" : undefined}
184
- onclick={() => {
185
- onSelect?.(it.id);
186
- if (closeOnSelect) closeMenu();
187
- }}
188
- >
179
+ {#if it.type === "section"}
180
+ <li class="px-[calc(var(--spacing-sm)+var(--spacing-xs))] pt-[var(--spacing-sm)] mt-[calc(var(--spacing-sm)+var(--spacing-xs))] text-[var(--color-text-muted)] [font-size:var(--text-xs)] lowercase tracking-wide opacity-70">
189
181
  {it.label}
190
- </button>
191
- </li>
182
+ </li>
183
+ {:else}
184
+ <li>
185
+ <button
186
+ type="button"
187
+ class="w-full text-left px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[var(--spacing-sm)] rounded-[var(--radius-md)] hover:bg-[var(--color-bg-hover)] focus:outline-[var(--border-color-focus)] focus:outline-2 transition-colors"
188
+ aria-current={activeItem === it.id ? "page" : undefined}
189
+ onclick={() => {
190
+ onSelect?.(it.id);
191
+ if (closeOnSelect) closeMenu();
192
+ }}
193
+ >
194
+ {it.label}
195
+ </button>
196
+ </li>
197
+ {/if}
192
198
  {/each}
193
199
  </ul>
194
200
  {/if}
195
201
  </div>
196
202
 
197
203
  {#if footer}
198
- <div class="p-4 border-t border-[var(--border-color-default)]">
204
+ <div class="p-[var(--spacing-md)] border-t border-[var(--border-color-default)]">
199
205
  {@render footer?.()}
200
206
  </div>
201
207
  {/if}
@@ -203,7 +209,7 @@
203
209
 
204
210
  <button
205
211
  type="button"
206
- class="flex-1 bg-black/40"
212
+ class="flex-1 bg-[oklch(0_0_0/0.4)]"
207
213
  aria-hidden="true"
208
214
  onclick={closeMenu}
209
215
  ></button>
@@ -0,0 +1,94 @@
1
+ <!-- src/lib/InstallPWA.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * @component InstallPWA
5
+ * @description Installs the app using the browser PWA prompt.
6
+ *
7
+ * @prop alwaysShow {boolean} - Forces the install button to be visible
8
+ * @default false
9
+ *
10
+ * @prop inline {boolean} - Renders the button inline instead of fixed
11
+ * @default false
12
+ *
13
+ * @prop class {string} - Additional button classes
14
+ * @default ""
15
+ *
16
+ * @note Uses the `beforeinstallprompt` event and defers the prompt until user action.
17
+ */
18
+ import Button from "./Button.svelte";
19
+
20
+ interface BeforeInstallPromptEvent extends Event {
21
+ prompt: () => Promise<void>;
22
+ userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>;
23
+ }
24
+
25
+ type Props = {
26
+ alwaysShow?: boolean;
27
+ inline?: boolean;
28
+ class?: string;
29
+ };
30
+
31
+ let {
32
+ alwaysShow = false,
33
+ inline = false,
34
+ class: externalClass = "",
35
+ }: Props = $props();
36
+
37
+ let deferredPrompt = $state<BeforeInstallPromptEvent | null>(null);
38
+ let showButton = $state(false);
39
+
40
+ $effect(() => {
41
+ const handler = (e: Event) => {
42
+ const bipEvent = e as BeforeInstallPromptEvent;
43
+ bipEvent.preventDefault();
44
+ deferredPrompt = bipEvent;
45
+ showButton = true;
46
+ };
47
+
48
+ window.addEventListener("beforeinstallprompt", handler);
49
+ return () => window.removeEventListener("beforeinstallprompt", handler);
50
+ });
51
+
52
+ function installPWA() {
53
+ if (deferredPrompt) {
54
+ deferredPrompt.prompt();
55
+ deferredPrompt.userChoice.then(() => {
56
+ showButton = false;
57
+ });
58
+ }
59
+ }
60
+ </script>
61
+
62
+ {#if showButton || alwaysShow}
63
+ <Button
64
+ onClick={installPWA}
65
+ sz="sm"
66
+ class={inline
67
+ ? `z-[1000] flex items-center gap-2.5 ${externalClass}`
68
+ : `fixed bottom-5 right-5 z-[10] flex items-center gap-2.5 ${externalClass}`}
69
+ variant="pill"
70
+ >
71
+ <span class="flex items-center gap-2">
72
+ <svg
73
+ xmlns="http://www.w3.org/2000/svg"
74
+ width="24"
75
+ height="24"
76
+ viewBox="0 0 24 24"
77
+ fill="none"
78
+ stroke="currentColor"
79
+ stroke-width="2"
80
+ stroke-linecap="round"
81
+ stroke-linejoin="round"
82
+ class="w-4 h-4"
83
+ aria-hidden="true"
84
+ >
85
+ <path d="M12 15V3" />
86
+ <path
87
+ d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"
88
+ />
89
+ <path d="m7 10 5 5 5-5" />
90
+ </svg>
91
+ <span>Install App</span>
92
+ </span>
93
+ </Button>
94
+ {/if}
@@ -0,0 +1,8 @@
1
+ type Props = {
2
+ alwaysShow?: boolean;
3
+ inline?: boolean;
4
+ class?: string;
5
+ };
6
+ declare const InstallPWA: import("svelte").Component<Props, {}, "">;
7
+ type InstallPWA = ReturnType<typeof InstallPWA>;
8
+ export default InstallPWA;
@@ -61,20 +61,20 @@
61
61
  let subMenuLeft = $state(0);
62
62
 
63
63
  const sizes: Record<SizeKey, string> = {
64
- xs: "h-7 px-3",
65
- sm: "h-8 px-3",
66
- md: "h-9 px-4",
67
- lg: "h-10 px-4",
68
- xl: "h-11 px-5",
64
+ xs: "h-[calc(var(--spacing-md)+var(--spacing-sm)+var(--spacing-xs))] px-[calc(var(--spacing-sm)+var(--spacing-xs))]",
65
+ sm: "h-[var(--spacing-xl)] px-[calc(var(--spacing-sm)+var(--spacing-xs))]",
66
+ md: "h-[calc(var(--spacing-xl)+var(--spacing-xs))] px-[var(--spacing-md)]",
67
+ lg: "h-[calc(var(--spacing-xl)+var(--spacing-sm))] px-[var(--spacing-md)]",
68
+ xl: "h-[calc(var(--spacing-xl)+var(--spacing-sm)+var(--spacing-xs))] px-[calc(var(--spacing-md)+var(--spacing-xs))]",
69
69
  };
70
70
 
71
71
  const navBase =
72
- "flex items-stretch pl-2 gap-1 border-b relative z-10 bg-[var(--color-bg-surface)] text-[var(--color-text-default)] border-[var(--border-color-default)]";
72
+ "flex items-stretch pl-[var(--spacing-sm)] gap-[var(--spacing-xs)] border-b relative z-10 bg-[var(--color-bg-surface)] text-[var(--color-text-default)] border-[var(--border-color-default)]";
73
73
 
74
74
  const subMenuGutter = 8;
75
75
 
76
76
  const topButtonBase =
77
- "px-4 rounded-xs leading-none transition-colors outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]";
77
+ "px-[var(--spacing-md)] rounded-[var(--radius-sm)] leading-none transition-colors outline-none [@media(pointer:coarse)]:min-h-11 focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]";
78
78
 
79
79
  const topButtonActive =
80
80
  "bg-[var(--color-bg-muted)] text-[var(--color-text-default)]";
@@ -480,7 +480,7 @@
480
480
  <div
481
481
  bind:this={menuRefs[menuItem.name]}
482
482
  class={cx(
483
- "fixed z-50 min-w-44 p-2 rounded-xs shadow-[0_2px_4px_var(--shadow-color)] ",
483
+ "fixed z-50 min-w-44 p-[var(--spacing-sm)] rounded-[var(--radius-sm)] shadow-[0_2px_4px_var(--shadow-color)] ",
484
484
  "border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]"
485
485
  )}
486
486
  style={menuStyle}
@@ -491,7 +491,7 @@
491
491
  {#each menuItem.actions as action, i (actionKey(action, i))}
492
492
  {#if isSeparator(action)}
493
493
  <div
494
- class="my-1 mx-1 border-t border-[var(--border-color-default)]"
494
+ class="my-[var(--spacing-xs)] mx-[var(--spacing-xs)] border-t border-[var(--border-color-default)]"
495
495
  role="separator"
496
496
  ></div>
497
497
  {:else}
@@ -501,8 +501,8 @@
501
501
  type="button"
502
502
  role="menuitem"
503
503
  class={cx(
504
- "relative text-left rounded-xs transition-colors outline-none px-1.5 py-0.5 my-1 mr-1 min-w-full flex items-center",
505
- "gap-3 hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
504
+ "relative text-left rounded-[var(--radius-sm)] transition-colors outline-none px-[calc(var(--spacing-sm)+var(--spacing-xs)/2)] py-[calc(var(--spacing-xs)/2)] my-[var(--spacing-xs)] mr-[var(--spacing-xs)] min-w-full flex items-center",
505
+ "gap-[calc(var(--spacing-sm)+var(--spacing-xs))] hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
506
506
  "focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]",
507
507
  textCls
508
508
  )}
@@ -528,11 +528,11 @@
528
528
  focusMenuAction(menuItem, i);
529
529
  }}
530
530
  >
531
- <span class="flex items-center gap-2 flex-1 min-w-0">
531
+ <span class="flex items-center gap-[var(--spacing-sm)] flex-1 min-w-0">
532
532
  <span class="truncate">{actionText(action)}</span>
533
533
  </span>
534
534
 
535
- <span class="flex items-center shrink-0 ml-auto gap-1">
535
+ <span class="flex items-center shrink-0 ml-auto gap-[var(--spacing-xs)]">
536
536
  {#if actionShortcut(action)}
537
537
  <span
538
538
  class={cx(
@@ -562,7 +562,7 @@
562
562
  <div
563
563
  bind:this={subMenuRefs[actionId(action)]}
564
564
  class={cx(
565
- "fixed z-50 min-w-44 p-2 rounded-xs shadow-[0_2px_4px_var(--shadow-color)]",
565
+ "fixed z-50 min-w-44 p-[var(--spacing-sm)] rounded-[var(--radius-sm)] shadow-[0_2px_4px_var(--shadow-color)]",
566
566
  "border border-[var(--border-color-default)] bg-[var(--color-bg-surface)]"
567
567
  )}
568
568
  style={subMenuStyle}
@@ -574,7 +574,7 @@
574
574
  {#each action.submenu as sub, j (actionKey(sub, j))}
575
575
  {#if isSeparator(sub)}
576
576
  <div
577
- class="my-1 mx-1 border-t border-[var(--border-color-default)]"
577
+ class="my-[var(--spacing-xs)] mx-[var(--spacing-xs)] border-t border-[var(--border-color-default)]"
578
578
  role="separator"
579
579
  ></div>
580
580
  {:else}
@@ -583,8 +583,8 @@
583
583
  type="button"
584
584
  role="menuitem"
585
585
  class={cx(
586
- "relative text-left rounded-xs transition-colors outline-none px-1.5 py-0.5",
587
- "my-1 mr-1 w-full flex items-center justify-between gap-3",
586
+ "relative text-left rounded-[var(--radius-sm)] transition-colors outline-none px-[calc(var(--spacing-sm)+var(--spacing-xs)/2)] py-[calc(var(--spacing-xs)/2)]",
587
+ "my-[var(--spacing-xs)] mr-[var(--spacing-xs)] w-full flex items-center justify-between gap-[calc(var(--spacing-sm)+var(--spacing-xs))]",
588
588
  "hover:bg-[var(--color-bg-muted)] focus-visible:bg-[var(--color-bg-muted)]",
589
589
  "focus-visible:shadow-[inset_0_0_0_2px_var(--border-color-focus)]",
590
590
  "decoration-[var(--color-text-default)]",
@@ -595,7 +595,7 @@
595
595
  onmouseenter={() => (activeSubIndex = j)}
596
596
  onfocus={() => (activeSubIndex = j)}
597
597
  >
598
- <span class="flex items-center gap-2 flex-1 min-w-0">
598
+ <span class="flex items-center gap-[var(--spacing-sm)] flex-1 min-w-0">
599
599
  <span class="truncate">{actionText(sub)}</span>
600
600
  </span>
601
601
 
@@ -0,0 +1,140 @@
1
+ <!-- src/lib/NoticeBase.svelte -->
2
+ <script lang="ts">
3
+ /**
4
+ * @component NoticeBase
5
+ * @description Shared base for Toast and Badge visuals.
6
+ *
7
+ * @prop title {string} - Optional title displayed above the message
8
+ *
9
+ * @prop message {string} - Notice text content
10
+ *
11
+ * @prop variant {ToastVariant} - Visual style
12
+ * @options success|danger|warning|info
13
+ * @default info
14
+ *
15
+ * @prop showIcon {boolean} - Shows an icon matching the variant
16
+ * @default true
17
+ *
18
+ * @prop inline {boolean} - Inline layout without overlay styling
19
+ * @default false
20
+ *
21
+ * @prop size {"sm" | "md"} - Size preset for spacing and typography
22
+ * @default "sm"
23
+ *
24
+ * @prop end {Snippet} - Trailing content (e.g. close button)
25
+ *
26
+ * @prop class {string} - Additional wrapper classes
27
+ * @default ""
28
+ *
29
+ * @note Used by Toast and Badge to keep styles consistent.
30
+ */
31
+ import type { Snippet } from "svelte";
32
+ import type { ToastVariant } from "./types";
33
+ import { cx } from "../utils";
34
+
35
+ type Props = {
36
+ title?: string;
37
+ message: string;
38
+ variant?: ToastVariant;
39
+ showIcon?: boolean;
40
+ class?: string;
41
+ inline?: boolean;
42
+ size?: "sm" | "md";
43
+ end?: Snippet;
44
+ };
45
+
46
+ let {
47
+ title,
48
+ message,
49
+ variant = "info",
50
+ showIcon = true,
51
+ inline = false,
52
+ size = "sm",
53
+ end,
54
+ class: externalClass = "",
55
+ }: Props = $props();
56
+
57
+ function variantClasses(v: ToastVariant) {
58
+ switch (v) {
59
+ case "success":
60
+ return "bg-[var(--color-bg-success)] text-[var(--color-text-success)]";
61
+ case "danger":
62
+ return "bg-[var(--color-bg-danger)] text-[var(--color-text-danger)]";
63
+ case "warning":
64
+ return "bg-[var(--color-bg-warning)] text-[var(--color-text-warning)]";
65
+ default:
66
+ return "bg-[var(--color-bg-page)] text-[var(--color-text-default)]";
67
+ }
68
+ }
69
+
70
+ const sizeClasses = $derived(
71
+ size === "md"
72
+ ? "gap-[calc(var(--spacing-sm)+var(--spacing-xs))] px-[var(--spacing-md)] py-[calc(var(--spacing-sm)+var(--spacing-xs))] rounded-[var(--radius-lg)]"
73
+ : "gap-[var(--spacing-sm)] px-[calc(var(--spacing-sm)+var(--spacing-xs))] py-[calc(var(--spacing-sm)+var(--spacing-xs)/2)] rounded-[var(--radius-md)]"
74
+ );
75
+
76
+ const iconClass = $derived(size === "md" ? "w-5 h-5" : "w-4 h-4");
77
+
78
+ const titleClass = $derived(
79
+ size === "md"
80
+ ? "font-[var(--font-weight-medium)] truncate [font-size:var(--text-md)] max-sm:[font-size:var(--text-sm)]"
81
+ : "font-[var(--font-weight-medium)] truncate [font-size:var(--text-sm)]"
82
+ );
83
+
84
+ const messageClass = $derived(
85
+ size === "md"
86
+ ? "line-clamp-3 [font-size:var(--text-sm)] max-sm:[font-size:var(--text-xs)]"
87
+ : "truncate [font-size:var(--text-xs)]"
88
+ );
89
+
90
+ const rootClass = $derived(
91
+ cx(
92
+ "flex items-center border border-[var(--border-color-default)]",
93
+ sizeClasses,
94
+ !inline && "shadow-[0_8px_16px_var(--shadow-color)] backdrop-blur-sm",
95
+ variantClasses(variant),
96
+ externalClass
97
+ )
98
+ );
99
+ </script>
100
+
101
+ <div class={rootClass} role="status" aria-live="polite">
102
+ {#if showIcon}
103
+ <span class={cx(iconClass, "flex-shrink-0")} aria-hidden="true">
104
+ {#if variant === "success"}
105
+ <svg fill="none" viewBox="0 0 26 26">
106
+ <path d="M8.5 14L11.1 16.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
107
+ <path d="M18.2 10L11.6 16.6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
108
+ <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
109
+ </svg>
110
+ {:else if variant === "danger"}
111
+ <svg fill="none" viewBox="0 0 26 26">
112
+ <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
113
+ <path d="M9 9.5L16.7 17.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
114
+ <path d="M16.7 9.5L9 17.3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
115
+ </svg>
116
+ {:else if variant === "warning"}
117
+ <svg fill="none" viewBox="0 0 27 27">
118
+ <path d="M4.6 25.9H22.5C25.2 25.9 26.8 23 25.6 20.6L16.6 3.8C15.3 1.3 11.8 1.3 10.5 3.8L1.5 20.6C0.3 23 1.9 25.9 4.6 25.9Z" stroke="currentColor" stroke-width="2"/>
119
+ <path d="M13.9 18H13.2L11.9 9.3C11.9 8.6 12.5 8 13.2 8H13.9C14.6 8 15.2 8.6 15.2 9.3L13.9 18Z" fill="currentColor"/>
120
+ <circle cx="13.5" cy="20.6" r="1.3" fill="currentColor"/>
121
+ </svg>
122
+ {:else}
123
+ <svg fill="none" viewBox="0 0 26 26">
124
+ <path d="M13 25C19.6 25 25 19.6 25 13C25 6.4 19.6 1 13 1C6.4 1 1 6.4 1 13C1 19.6 6.4 25 13 25Z" stroke="currentColor" stroke-width="2"/>
125
+ <circle cx="13" cy="7.7" r="1.3" fill="currentColor"/>
126
+ <rect x="11.6" y="10.3" width="2.7" height="9.4" rx="1.3" fill="currentColor"/>
127
+ </svg>
128
+ {/if}
129
+ </span>
130
+ {/if}
131
+
132
+ <div class="flex-1 min-w-0">
133
+ {#if title}
134
+ <div class={titleClass}>{title}</div>
135
+ {/if}
136
+ <div class={messageClass} title={message}>{message}</div>
137
+ </div>
138
+
139
+ {@render end?.()}
140
+ </div>
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @component NoticeBase
3
+ * @description Shared base for Toast and Badge visuals.
4
+ *
5
+ * @prop title {string} - Optional title displayed above the message
6
+ *
7
+ * @prop message {string} - Notice text content
8
+ *
9
+ * @prop variant {ToastVariant} - Visual style
10
+ * @options success|danger|warning|info
11
+ * @default info
12
+ *
13
+ * @prop showIcon {boolean} - Shows an icon matching the variant
14
+ * @default true
15
+ *
16
+ * @prop inline {boolean} - Inline layout without overlay styling
17
+ * @default false
18
+ *
19
+ * @prop size {"sm" | "md"} - Size preset for spacing and typography
20
+ * @default "sm"
21
+ *
22
+ * @prop end {Snippet} - Trailing content (e.g. close button)
23
+ *
24
+ * @prop class {string} - Additional wrapper classes
25
+ * @default ""
26
+ *
27
+ * @note Used by Toast and Badge to keep styles consistent.
28
+ */
29
+ import type { Snippet } from "svelte";
30
+ import type { ToastVariant } from "./types";
31
+ type Props = {
32
+ title?: string;
33
+ message: string;
34
+ variant?: ToastVariant;
35
+ showIcon?: boolean;
36
+ class?: string;
37
+ inline?: boolean;
38
+ size?: "sm" | "md";
39
+ end?: Snippet;
40
+ };
41
+ declare const NoticeBase: import("svelte").Component<Props, {}, "">;
42
+ type NoticeBase = ReturnType<typeof NoticeBase>;
43
+ export default NoticeBase;
@@ -19,9 +19,9 @@
19
19
  * @note The `html` element receives `data-primary="{value}"` for theme styling.
20
20
  * @note Uses the same onChange contract as Select and forwards palette options as-is.
21
21
  */
22
- import Select from "./Select.svelte";
23
- import type { PrimaryKey, PaletteOption, SizeKey } from "./types";
24
- import { getComponentText, getLangContext, getLangKey } from "./lang-context";
22
+ import Select from "./Select.svelte";
23
+ import type { PrimaryKey, PaletteOption, SizeKey } from "./types";
24
+ import { getComponentText, getLangContext, getLangKey } from "./lang-context";
25
25
 
26
26
  type Props = {
27
27
  sz?: SizeKey;
@@ -31,9 +31,9 @@
31
31
 
32
32
  let { sz = "sm", label, class: externalClass = "" }: Props = $props();
33
33
 
34
- const langCtx = getLangContext();
35
- const langKey = $derived(getLangKey(langCtx));
36
- const L = $derived(getComponentText("primaryColorSelect", langKey));
34
+ const langCtx = getLangContext();
35
+ const langKey = $derived(getLangKey(langCtx));
36
+ const L = $derived(getComponentText("primaryColorSelect", langKey));
37
37
 
38
38
  const labelFinal = $derived(label ?? L.text);
39
39
 
@@ -7,20 +7,18 @@
7
7
  * @default 0
8
8
  * @prop indeterminate {boolean} - Enables spinning infinite mode
9
9
  * @default false
10
- * @prop size {number} - Diameter in px
11
- * @default 48
12
- * @prop stroke {number} - Stroke width in px
13
- * @default 4
10
+ * @prop sz {SizeKey} - Size preset (xs|sm|md|lg|xl)
11
+ * @default md
14
12
  * @prop variant {ComponentVariant} - Color/style variant
15
- * @options default|neutral|success|warning|error
13
+ * @options default|neutral
16
14
  * @default default
17
- * @prop label {string} - Optional text shown in center
15
+ * @prop label {string} - Optional text shown above the circle
18
16
  * @default ""
19
- * @prop max {number} - Max progress value for normalization
20
- * @default 100
17
+ * @prop disabled {boolean} - Apply disabled styles
18
+ * @default false
21
19
  * @prop class {string} - Extra wrapper classes
22
20
  * @default ""
23
- * @note Clamps value between 0-max
21
+ * @note Clamps value between 0-100
24
22
  * @note Uses SVG stroke-dashoffset animation
25
23
  * @note Accessible role=progressbar with aria-valuenow
26
24
  * @note Works in both determinate/indeterminate modes
@@ -5,20 +5,18 @@
5
5
  * @default 0
6
6
  * @prop indeterminate {boolean} - Enables spinning infinite mode
7
7
  * @default false
8
- * @prop size {number} - Diameter in px
9
- * @default 48
10
- * @prop stroke {number} - Stroke width in px
11
- * @default 4
8
+ * @prop sz {SizeKey} - Size preset (xs|sm|md|lg|xl)
9
+ * @default md
12
10
  * @prop variant {ComponentVariant} - Color/style variant
13
- * @options default|neutral|success|warning|error
11
+ * @options default|neutral
14
12
  * @default default
15
- * @prop label {string} - Optional text shown in center
13
+ * @prop label {string} - Optional text shown above the circle
16
14
  * @default ""
17
- * @prop max {number} - Max progress value for normalization
18
- * @default 100
15
+ * @prop disabled {boolean} - Apply disabled styles
16
+ * @default false
19
17
  * @prop class {string} - Extra wrapper classes
20
18
  * @default ""
21
- * @note Clamps value between 0-max
19
+ * @note Clamps value between 0-100
22
20
  * @note Uses SVG stroke-dashoffset animation
23
21
  * @note Accessible role=progressbar with aria-valuenow
24
22
  * @note Works in both determinate/indeterminate modes
@@ -23,9 +23,9 @@
23
23
  *
24
24
  * @note Renders a leading search icon and uses `Field` with `type="search"` and `clearable`.
25
25
  */
26
- import Field from "./Field.svelte";
27
- import type { FieldVariant, SizeKey } from "./types";
28
- import { getComponentText, getLangContext, getLangKey } from "./lang-context";
26
+ import Field from "./Field.svelte";
27
+ import type { FieldVariant, SizeKey } from "./types";
28
+ import { getComponentText, getLangContext, getLangKey } from "./lang-context";
29
29
 
30
30
  type Props = {
31
31
  label?: string;
@@ -47,9 +47,9 @@
47
47
  ...rest
48
48
  }: Props = $props();
49
49
 
50
- const langCtx = getLangContext();
51
- const langKey = $derived(getLangKey(langCtx));
52
- const L = $derived(getComponentText("searchInput", langKey));
50
+ const langCtx = getLangContext();
51
+ const langKey = $derived(getLangKey(langCtx));
52
+ const L = $derived(getComponentText("searchInput", langKey));
53
53
 
54
54
  const placeholderFinal = $derived(placeholder ?? L.placeholder);
55
55
  </script>
@@ -102,7 +102,7 @@
102
102
  let menuPosition = $state<"top" | "bottom">("bottom");
103
103
 
104
104
  const base =
105
- "relative w-full outline-none appearance-none cursor-pointer transition-colors duration-[var(--transition-fast)] ease-[var(--timing-default)] box-border rounded-[var(--radius-md)] border focus:border-[var(--border-color-focus)] focus:ring-2 focus:ring-[var(--border-color-focus)] [color:var(--color-text-default)] disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed";
105
+ "relative w-full outline-none appearance-none cursor-pointer transition-colors duration-[var(--transition-fast)] ease-[var(--timing-default)] box-border rounded-[var(--radius-md)] border focus:border-[var(--border-color-focus)] focus:ring-2 focus:ring-[var(--border-color-focus)] [color:var(--color-text-default)] disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed [@media(pointer:coarse)]:min-h-11";
106
106
 
107
107
  const sizes: Record<SizeKey, string> = {
108
108
  xs: "px-2 pr-6 h-6",
@@ -152,7 +152,7 @@
152
152
 
153
153
  const itemBaseClass = $derived(
154
154
  cx(
155
- "w-full text-left px-4 py-2 transition-colors duration-[var(--transition-fast)] cursor-pointer",
155
+ "w-full text-left px-4 py-2 transition-colors duration-[var(--transition-fast)] cursor-pointer [@media(pointer:coarse)]:min-h-11",
156
156
  TEXT[sz]
157
157
  )
158
158
  );