twintrinsic 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 (212) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +150 -0
  3. package/dist/App/App.svelte +54 -0
  4. package/dist/App/App.svelte.d.ts +65 -0
  5. package/dist/Section.svelte +25 -0
  6. package/dist/Section.svelte.d.ts +34 -0
  7. package/dist/actions/clickOutside.d.ts +9 -0
  8. package/dist/actions/clickOutside.js +19 -0
  9. package/dist/actions/index.d.ts +1 -0
  10. package/dist/actions/index.js +1 -0
  11. package/dist/components/Accordion/Accordion.svelte +75 -0
  12. package/dist/components/Accordion/Accordion.svelte.d.ts +39 -0
  13. package/dist/components/Accordion/AccordionItem.svelte +150 -0
  14. package/dist/components/Accordion/AccordionItem.svelte.d.ts +30 -0
  15. package/dist/components/App/App.story.md +8 -0
  16. package/dist/components/App/App.story.svelte +170 -0
  17. package/dist/components/App/App.story.svelte.d.ts +22 -0
  18. package/dist/components/App/App.svelte +77 -0
  19. package/dist/components/App/App.svelte.d.ts +66 -0
  20. package/dist/components/App/Split.svelte +346 -0
  21. package/dist/components/App/Split.svelte.d.ts +54 -0
  22. package/dist/components/App/index.d.ts +2 -0
  23. package/dist/components/App/index.js +3 -0
  24. package/dist/components/AppHeader/AppHeader.svelte +439 -0
  25. package/dist/components/AppHeader/AppHeader.svelte.d.ts +24 -0
  26. package/dist/components/Avatar/Avatar.svelte +300 -0
  27. package/dist/components/Avatar/Avatar.svelte.d.ts +48 -0
  28. package/dist/components/Avatar/AvatarGroup.svelte +185 -0
  29. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +46 -0
  30. package/dist/components/Badge/Badge.svelte +186 -0
  31. package/dist/components/Badge/Badge.svelte.d.ts +51 -0
  32. package/dist/components/BottomBar/BottomBar.svelte +146 -0
  33. package/dist/components/BottomBar/BottomBar.svelte.d.ts +38 -0
  34. package/dist/components/Breadcrumb/Breadcrumb.svelte +77 -0
  35. package/dist/components/Breadcrumb/Breadcrumb.svelte.d.ts +42 -0
  36. package/dist/components/Breadcrumb/BreadcrumbItem.svelte +171 -0
  37. package/dist/components/Breadcrumb/BreadcrumbItem.svelte.d.ts +38 -0
  38. package/dist/components/Button/Button.svelte +252 -0
  39. package/dist/components/Button/Button.svelte.d.ts +80 -0
  40. package/dist/components/Button/ButtonGroup.svelte +127 -0
  41. package/dist/components/Button/ButtonGroup.svelte.d.ts +44 -0
  42. package/dist/components/Card/Card.svelte +152 -0
  43. package/dist/components/Card/Card.svelte.d.ts +55 -0
  44. package/dist/components/Carousel/Carousel.svelte +461 -0
  45. package/dist/components/Carousel/Carousel.svelte.d.ts +79 -0
  46. package/dist/components/Carousel/CarouselItem.svelte +149 -0
  47. package/dist/components/Carousel/CarouselItem.svelte.d.ts +35 -0
  48. package/dist/components/Chip/Chip.svelte +288 -0
  49. package/dist/components/Chip/Chip.svelte.d.ts +71 -0
  50. package/dist/components/Chip/ChipGroup.svelte +190 -0
  51. package/dist/components/Chip/ChipGroup.svelte.d.ts +71 -0
  52. package/dist/components/CodeBlock/CodeBlock.svelte +356 -0
  53. package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +44 -0
  54. package/dist/components/CodeBlock/index.d.ts +1 -0
  55. package/dist/components/CodeBlock/index.js +1 -0
  56. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte +145 -0
  57. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte.d.ts +44 -0
  58. package/dist/components/CodeEditor/CodeEditor.svelte +229 -0
  59. package/dist/components/CodeEditor/CodeEditor.svelte.d.ts +23 -0
  60. package/dist/components/Combobox/Combobox.svelte +279 -0
  61. package/dist/components/Combobox/Combobox.svelte.d.ts +34 -0
  62. package/dist/components/Container/Container.svelte +45 -0
  63. package/dist/components/Container/Container.svelte.d.ts +36 -0
  64. package/dist/components/DataTable/DataTable.svelte +879 -0
  65. package/dist/components/DataTable/DataTable.svelte.d.ts +102 -0
  66. package/dist/components/Form/AutoComplete.svelte +357 -0
  67. package/dist/components/Form/AutoComplete.svelte.d.ts +73 -0
  68. package/dist/components/Form/Calendar.svelte +429 -0
  69. package/dist/components/Form/Calendar.svelte.d.ts +53 -0
  70. package/dist/components/Form/Checkbox.svelte +196 -0
  71. package/dist/components/Form/Checkbox.svelte.d.ts +50 -0
  72. package/dist/components/Form/ColorPicker.svelte +396 -0
  73. package/dist/components/Form/ColorPicker.svelte.d.ts +43 -0
  74. package/dist/components/Form/Combobox.svelte +645 -0
  75. package/dist/components/Form/Combobox.svelte.d.ts +93 -0
  76. package/dist/components/Form/Dropdown.svelte +773 -0
  77. package/dist/components/Form/Dropdown.svelte.d.ts +81 -0
  78. package/dist/components/Form/FileUpload.svelte +796 -0
  79. package/dist/components/Form/FileUpload.svelte.d.ts +78 -0
  80. package/dist/components/Form/FloatLabel.svelte +245 -0
  81. package/dist/components/Form/FloatLabel.svelte.d.ts +44 -0
  82. package/dist/components/Form/Form.svelte +281 -0
  83. package/dist/components/Form/Form.svelte.d.ts +54 -0
  84. package/dist/components/Form/FormField.svelte +218 -0
  85. package/dist/components/Form/FormField.svelte.d.ts +47 -0
  86. package/dist/components/Form/Input.svelte +340 -0
  87. package/dist/components/Form/Input.svelte.d.ts +79 -0
  88. package/dist/components/Form/InputSwitch.svelte +189 -0
  89. package/dist/components/Form/InputSwitch.svelte.d.ts +46 -0
  90. package/dist/components/Form/InvalidState.svelte +97 -0
  91. package/dist/components/Form/InvalidState.svelte.d.ts +37 -0
  92. package/dist/components/Form/Knob.svelte +537 -0
  93. package/dist/components/Form/Knob.svelte.d.ts +78 -0
  94. package/dist/components/Form/ListInput.svelte +469 -0
  95. package/dist/components/Form/ListInput.svelte.d.ts +70 -0
  96. package/dist/components/Form/Listbox.svelte +513 -0
  97. package/dist/components/Form/Listbox.svelte.d.ts +74 -0
  98. package/dist/components/Form/NumberInput.svelte +452 -0
  99. package/dist/components/Form/NumberInput.svelte.d.ts +82 -0
  100. package/dist/components/Form/Radio.svelte +192 -0
  101. package/dist/components/Form/Radio.svelte.d.ts +53 -0
  102. package/dist/components/Form/RadioGroup.svelte +155 -0
  103. package/dist/components/Form/RadioGroup.svelte.d.ts +48 -0
  104. package/dist/components/Form/Rating.svelte +380 -0
  105. package/dist/components/Form/Rating.svelte.d.ts +64 -0
  106. package/dist/components/Form/Select.svelte +436 -0
  107. package/dist/components/Form/Select.svelte.d.ts +49 -0
  108. package/dist/components/Form/SelectGroup.svelte +34 -0
  109. package/dist/components/Form/SelectGroup.svelte.d.ts +33 -0
  110. package/dist/components/Form/Slider.svelte +622 -0
  111. package/dist/components/Form/Slider.svelte.d.ts +73 -0
  112. package/dist/components/Form/Switch.svelte +192 -0
  113. package/dist/components/Form/Switch.svelte.d.ts +46 -0
  114. package/dist/components/Form/TextInput.svelte +274 -0
  115. package/dist/components/Form/TextInput.svelte.d.ts +74 -0
  116. package/dist/components/Form/Textarea.svelte +207 -0
  117. package/dist/components/Form/Textarea.svelte.d.ts +62 -0
  118. package/dist/components/Icon/Icon.svelte +140 -0
  119. package/dist/components/Icon/Icon.svelte.d.ts +25 -0
  120. package/dist/components/Icon/index.d.ts +1 -0
  121. package/dist/components/Icon/index.js +1 -0
  122. package/dist/components/Lazy/Lazy.svelte +158 -0
  123. package/dist/components/Lazy/Lazy.svelte.d.ts +42 -0
  124. package/dist/components/Masonry/Masonry.svelte +299 -0
  125. package/dist/components/Masonry/Masonry.svelte.d.ts +55 -0
  126. package/dist/components/Menu/Menu/Menu.svelte +65 -0
  127. package/dist/components/Menu/Menu/Menu.svelte.d.ts +17 -0
  128. package/dist/components/Menu/Menu/MenuItem.svelte +90 -0
  129. package/dist/components/Menu/Menu/MenuItem.svelte.d.ts +27 -0
  130. package/dist/components/Modal/Modal.svelte +334 -0
  131. package/dist/components/Modal/Modal.svelte.d.ts +55 -0
  132. package/dist/components/Panel/Card.svelte +141 -0
  133. package/dist/components/Panel/Card.svelte.d.ts +52 -0
  134. package/dist/components/Panel/Hero/Hero.story.md +9 -0
  135. package/dist/components/Panel/Hero/Hero.story.svelte +49 -0
  136. package/dist/components/Panel/Hero/Hero.story.svelte.d.ts +21 -0
  137. package/dist/components/Panel/Hero/Hero.svelte +24 -0
  138. package/dist/components/Panel/Hero/Hero.svelte.d.ts +32 -0
  139. package/dist/components/Panel/LazyPanel.svelte +110 -0
  140. package/dist/components/Panel/LazyPanel.svelte.d.ts +46 -0
  141. package/dist/components/Panel/Panel.svelte +205 -0
  142. package/dist/components/Panel/Panel.svelte.d.ts +23 -0
  143. package/dist/components/Progress/Progress.svelte +220 -0
  144. package/dist/components/Progress/Progress.svelte.d.ts +61 -0
  145. package/dist/components/Separator/Separator.svelte +109 -0
  146. package/dist/components/Separator/Separator.svelte.d.ts +35 -0
  147. package/dist/components/Sidebar/Sidebar.svelte +213 -0
  148. package/dist/components/Sidebar/Sidebar.svelte.d.ts +60 -0
  149. package/dist/components/Skeleton/Skeleton.svelte +170 -0
  150. package/dist/components/Skeleton/Skeleton.svelte.d.ts +48 -0
  151. package/dist/components/Stepper/Stepper.svelte +111 -0
  152. package/dist/components/Stepper/Stepper.svelte.d.ts +54 -0
  153. package/dist/components/Stepper/StepperStep.svelte +369 -0
  154. package/dist/components/Stepper/StepperStep.svelte.d.ts +63 -0
  155. package/dist/components/Table/Table.svelte +167 -0
  156. package/dist/components/Table/Table.svelte.d.ts +56 -0
  157. package/dist/components/Table/TableBody.svelte +41 -0
  158. package/dist/components/Table/TableBody.svelte.d.ts +33 -0
  159. package/dist/components/Table/TableCell.svelte +76 -0
  160. package/dist/components/Table/TableCell.svelte.d.ts +36 -0
  161. package/dist/components/Table/TableHead.svelte +41 -0
  162. package/dist/components/Table/TableHead.svelte.d.ts +32 -0
  163. package/dist/components/Table/TableHeader.svelte +148 -0
  164. package/dist/components/Table/TableHeader.svelte.d.ts +42 -0
  165. package/dist/components/Table/TableRow.svelte +99 -0
  166. package/dist/components/Table/TableRow.svelte.d.ts +40 -0
  167. package/dist/components/Tabs/Tab.svelte +145 -0
  168. package/dist/components/Tabs/Tab.svelte.d.ts +36 -0
  169. package/dist/components/Tabs/TabList.svelte +60 -0
  170. package/dist/components/Tabs/TabList.svelte.d.ts +32 -0
  171. package/dist/components/Tabs/TabPanel.svelte +118 -0
  172. package/dist/components/Tabs/TabPanel.svelte.d.ts +38 -0
  173. package/dist/components/Tabs/Tabs.svelte +287 -0
  174. package/dist/components/Tabs/Tabs.svelte.d.ts +50 -0
  175. package/dist/components/Tag/Tag.svelte +260 -0
  176. package/dist/components/Tag/Tag.svelte.d.ts +54 -0
  177. package/dist/components/Tag/TagGroup.svelte +147 -0
  178. package/dist/components/Tag/TagGroup.svelte.d.ts +62 -0
  179. package/dist/components/ThemeToggle/ThemeToggle.svelte +93 -0
  180. package/dist/components/ThemeToggle/ThemeToggle.svelte.d.ts +12 -0
  181. package/dist/components/Timeline/Timeline.svelte +144 -0
  182. package/dist/components/Timeline/Timeline.svelte.d.ts +48 -0
  183. package/dist/components/Timeline/TimelineItem.svelte +391 -0
  184. package/dist/components/Timeline/TimelineItem.svelte.d.ts +63 -0
  185. package/dist/components/Toast/Toast.svelte +313 -0
  186. package/dist/components/Toast/Toast.svelte.d.ts +44 -0
  187. package/dist/components/Toast/toastStore.d.ts +40 -0
  188. package/dist/components/Toast/toastStore.js +293 -0
  189. package/dist/components/Tooltip/Tooltip.svelte +282 -0
  190. package/dist/components/Tooltip/Tooltip.svelte.d.ts +55 -0
  191. package/dist/components/Tree/Tree.svelte +129 -0
  192. package/dist/components/Tree/Tree.svelte.d.ts +61 -0
  193. package/dist/components/Tree/TreeNode.svelte +332 -0
  194. package/dist/components/Tree/TreeNode.svelte.d.ts +55 -0
  195. package/dist/components/icons/TwintrinsicLogo.svelte +73 -0
  196. package/dist/components/icons/TwintrinsicLogo.svelte.d.ts +17 -0
  197. package/dist/components/icons/twintrinsic-source.svg +73 -0
  198. package/dist/components/icons/twintrinsic.svg +38 -0
  199. package/dist/docs/EventsTable.svelte +86 -0
  200. package/dist/docs/EventsTable.svelte.d.ts +27 -0
  201. package/dist/docs/PropsTable.svelte +103 -0
  202. package/dist/docs/PropsTable.svelte.d.ts +28 -0
  203. package/dist/docs/index.d.ts +2 -0
  204. package/dist/docs/index.js +2 -0
  205. package/dist/helpers/detectLanguage.d.ts +6 -0
  206. package/dist/helpers/detectLanguage.js +60 -0
  207. package/dist/helpers/index.d.ts +1 -0
  208. package/dist/helpers/index.js +1 -0
  209. package/dist/index.d.ts +86 -0
  210. package/dist/index.js +94 -0
  211. package/dist/twintrinsic.css +347 -0
  212. package/package.json +98 -0
@@ -0,0 +1,452 @@
1
+ <!--
2
+ @component
3
+ NumberInput - A specialized input for numeric values with formatting, units, and increment controls.
4
+ Provides consistent styling, accessibility features, and integration with the Form component.
5
+
6
+ Usage:
7
+ ```svelte
8
+ <NumberInput
9
+ name="quantity"
10
+ value={1}
11
+ min={0}
12
+ max={100}
13
+ step={1}
14
+ />
15
+
16
+ <NumberInput
17
+ name="price"
18
+ value={29.99}
19
+ prefix="$"
20
+ decimalPlaces={2}
21
+ />
22
+
23
+ <NumberInput
24
+ name="percentage"
25
+ value={75}
26
+ suffix="%"
27
+ verticalButtons
28
+ />
29
+ ```
30
+ -->
31
+ <script>
32
+ import { getContext } from "svelte"
33
+
34
+ const {
35
+ /** @type {string} - Additional CSS classes */
36
+ class: className = "",
37
+
38
+ /** @type {string} - HTML id for accessibility */
39
+ id = crypto.randomUUID(),
40
+
41
+ /** @type {string} - Input name */
42
+ name,
43
+
44
+ /** @type {number} - Input value */
45
+ value = 0,
46
+
47
+ /** @type {string} - Placeholder text */
48
+ placeholder = "",
49
+
50
+ /** @type {number} - Minimum allowed value */
51
+ min,
52
+
53
+ /** @type {number} - Maximum allowed value */
54
+ max,
55
+
56
+ /** @type {number} - Step increment/decrement amount */
57
+ step = 1,
58
+
59
+ /** @type {number} - Number of decimal places to display */
60
+ decimalPlaces,
61
+
62
+ /** @type {string} - Text to display before the number */
63
+ prefix,
64
+
65
+ /** @type {string} - Text to display after the number */
66
+ suffix,
67
+
68
+ /** @type {boolean} - Whether to show increment/decrement buttons */
69
+ showButtons = true,
70
+
71
+ /** @type {boolean} - Whether to arrange buttons vertically */
72
+ verticalButtons = false,
73
+
74
+ /** @type {boolean} - Whether the input is required */
75
+ required = false,
76
+
77
+ /** @type {boolean} - Whether the input is disabled */
78
+ disabled = false,
79
+
80
+ /** @type {boolean} - Whether the input is readonly */
81
+ readonly = false,
82
+
83
+ /** @type {string} - Size of the input (sm, md, lg) */
84
+ size = "md",
85
+
86
+ /** @type {string} - ARIA label for accessibility */
87
+ ariaLabel,
88
+
89
+ /** @type {(event: CustomEvent) => void} - Change event handler */
90
+ onchange,
91
+ /** @type {(event: CustomEvent) => void} - Input event handler */
92
+ oninput,
93
+ /** @type {(event: Event) => void} - Focus event handler */
94
+ onfocus,
95
+ /** @type {(event: Event) => void} - Blur event handler */
96
+ onblur,
97
+ } = $props()
98
+
99
+ // Get form context if available
100
+ const formContext = getContext("form")
101
+
102
+ // Generate unique ID if not provided
103
+ const inputId = id || `number-input-${crypto.randomUUID()}`
104
+
105
+ // Input state
106
+ let inputValue = $state(formatValue(value))
107
+ let numericValue = $state(value)
108
+ let isFocused = $state(false)
109
+ let inputEl
110
+
111
+ // Register with form if available
112
+ let fieldApi = $state()
113
+
114
+ $effect(() => {
115
+ if (formContext && name) {
116
+ fieldApi = formContext.registerField(name, value)
117
+ }
118
+ })
119
+
120
+ // Update value when form field changes
121
+ $effect(() => {
122
+ if (fieldApi) {
123
+ const formValue = fieldApi.getValue()
124
+ if (formValue !== undefined && formValue !== numericValue) {
125
+ numericValue = formValue
126
+ inputValue = formatValue(formValue)
127
+ }
128
+ }
129
+ })
130
+
131
+ // Update internal value when prop changes
132
+ $effect(() => {
133
+ if (value !== numericValue) {
134
+ numericValue = value
135
+ inputValue = formatValue(value)
136
+ }
137
+ })
138
+
139
+ /**
140
+ * Formats a numeric value for display
141
+ * @param {number} val - Value to format
142
+ * @returns {string} - Formatted value
143
+ */
144
+ function formatValue(val) {
145
+ if (val === undefined || val === null) return ""
146
+
147
+ let formatted = decimalPlaces !== undefined ? val.toFixed(decimalPlaces) : val.toString()
148
+
149
+ return formatted
150
+ }
151
+
152
+ /**
153
+ * Parses a string value to a number
154
+ * @param {string} val - Value to parse
155
+ * @returns {number} - Parsed number
156
+ */
157
+ function parseValue(val) {
158
+ if (!val) return 0
159
+
160
+ // Remove non-numeric characters except decimal point
161
+ const numericString = val.replace(/[^\d.-]/g, "")
162
+ return parseFloat(numericString)
163
+ }
164
+
165
+ /**
166
+ * Constrains a value to min/max bounds
167
+ * @param {number} val - Value to constrain
168
+ * @returns {number} - Constrained value
169
+ */
170
+ function constrainValue(val) {
171
+ let constrained = val
172
+
173
+ if (min !== undefined && constrained < min) {
174
+ constrained = min
175
+ }
176
+
177
+ if (max !== undefined && constrained > max) {
178
+ constrained = max
179
+ }
180
+
181
+ return constrained
182
+ }
183
+
184
+ /**
185
+ * Increments the current value
186
+ */
187
+ function increment() {
188
+ if (disabled || readonly) return
189
+
190
+ const currentValue = parseValue(inputValue)
191
+ const newValue = constrainValue(currentValue + step)
192
+
193
+ numericValue = newValue
194
+ inputValue = formatValue(newValue)
195
+
196
+ // Update form field if available
197
+ if (fieldApi) {
198
+ fieldApi.setValue(newValue)
199
+ }
200
+
201
+ onchange?.(new CustomEvent("change", { detail: { value: newValue } }))
202
+ }
203
+
204
+ /**
205
+ * Decrements the current value
206
+ */
207
+ function decrement() {
208
+ if (disabled || readonly) return
209
+
210
+ const currentValue = parseValue(inputValue)
211
+ const newValue = constrainValue(currentValue - step)
212
+
213
+ numericValue = newValue
214
+ inputValue = formatValue(newValue)
215
+
216
+ // Update form field if available
217
+ if (fieldApi) {
218
+ fieldApi.setValue(newValue)
219
+ }
220
+
221
+ onchange?.(new CustomEvent("change", { detail: { value: newValue } }))
222
+ }
223
+
224
+ /**
225
+ * Handles input change
226
+ * @param {Event} event - Input event
227
+ */
228
+ function handleInput(event) {
229
+ const rawValue = event.target.value
230
+ inputValue = rawValue
231
+
232
+ // Only update numeric value if it's a valid number
233
+ const parsed = parseValue(rawValue)
234
+ if (!isNaN(parsed)) {
235
+ numericValue = parsed
236
+
237
+ // Update form field if available
238
+ if (fieldApi) {
239
+ fieldApi.setValue(parsed)
240
+ }
241
+
242
+ oninput?.(new CustomEvent("input", { detail: { value: parsed } }))
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Handles blur event
248
+ */
249
+ function handleBlur(event) {
250
+ // Format and constrain the value on blur
251
+ const parsed = parseValue(inputValue)
252
+ const constrained = constrainValue(parsed)
253
+
254
+ numericValue = constrained
255
+ inputValue = formatValue(constrained)
256
+
257
+ // Update form field if available
258
+ if (fieldApi) {
259
+ fieldApi.setValue(constrained)
260
+ }
261
+
262
+ isFocused = false
263
+ onblur?.(event)
264
+ onchange?.(new CustomEvent("change", { detail: { value: constrained } }))
265
+ }
266
+
267
+ /**
268
+ * Handles focus event
269
+ */
270
+ function handleFocus(event) {
271
+ isFocused = true
272
+ onfocus?.(event)
273
+ }
274
+
275
+ /**
276
+ * Handles keydown event
277
+ * @param {KeyboardEvent} event - Keydown event
278
+ */
279
+ function handleKeydown(event) {
280
+ if (disabled || readonly) return
281
+
282
+ if (event.key === "ArrowUp") {
283
+ event.preventDefault()
284
+ increment()
285
+ } else if (event.key === "ArrowDown") {
286
+ event.preventDefault()
287
+ decrement()
288
+ }
289
+ }
290
+
291
+ // Determine size classes
292
+ const sizeClasses = $derived(
293
+ {
294
+ sm: "h-8 text-sm",
295
+ md: "h-10 text-base",
296
+ lg: "h-12 text-lg",
297
+ }[size] || "h-10 text-base"
298
+ )
299
+
300
+ // Determine button size classes
301
+ const buttonSizeClasses = $derived(
302
+ {
303
+ sm: "w-6 h-6",
304
+ md: "w-8 h-8",
305
+ lg: "w-10 h-10",
306
+ }[size] || "w-8 h-8"
307
+ )
308
+ </script>
309
+
310
+ <div
311
+ class="
312
+ number-input
313
+ {verticalButtons ? 'number-input-vertical' : 'number-input-horizontal'}
314
+ {className}
315
+ "
316
+ >
317
+ <div class="number-input-container {sizeClasses}">
318
+ {#if prefix}
319
+ <span class="number-input-prefix">{prefix}</span>
320
+ {/if}
321
+
322
+ <input
323
+ {id}
324
+ {name}
325
+ type="text"
326
+ inputmode="decimal"
327
+ class="number-input-field"
328
+ {placeholder}
329
+ value={inputValue}
330
+ {required}
331
+ {disabled}
332
+ {readonly}
333
+ min={min !== undefined ? min : undefined}
334
+ max={max !== undefined ? max : undefined}
335
+ step={step}
336
+ aria-label={ariaLabel || name}
337
+ aria-valuemin={min !== undefined ? min : undefined}
338
+ aria-valuemax={max !== undefined ? max : undefined}
339
+ aria-valuenow={numericValue}
340
+ bind:this={inputEl}
341
+ oninput={handleInput}
342
+ onblur={handleBlur}
343
+ onfocus={handleFocus}
344
+ onkeydown={handleKeydown}
345
+ />
346
+
347
+ {#if suffix}
348
+ <span class="number-input-suffix">{suffix}</span>
349
+ {/if}
350
+
351
+ {#if showButtons}
352
+ <div class="number-input-buttons {verticalButtons ? 'number-input-buttons-vertical' : ''}">
353
+ <button
354
+ type="button"
355
+ class="number-input-button number-input-increment {buttonSizeClasses}"
356
+ aria-label="Increase value"
357
+ tabindex="-1"
358
+ disabled={disabled || readonly || (max !== undefined && numericValue >= max)}
359
+ onclick={increment}
360
+ >
361
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
362
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
363
+ </svg>
364
+ </button>
365
+
366
+ <button
367
+ type="button"
368
+ class="number-input-button number-input-decrement {buttonSizeClasses}"
369
+ aria-label="Decrease value"
370
+ tabindex="-1"
371
+ disabled={disabled || readonly || (min !== undefined && numericValue <= min)}
372
+ onclick={decrement}
373
+ >
374
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
375
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
376
+ </svg>
377
+ </button>
378
+ </div>
379
+ {/if}
380
+ </div>
381
+ </div>
382
+
383
+ <style>
384
+ @reference "../../twintrinsic.css";
385
+
386
+ .number-input {
387
+ @apply w-full;
388
+ }
389
+
390
+ .number-input-container {
391
+ @apply relative flex items-center;
392
+ @apply bg-background dark:bg-background;
393
+ @apply border border-border dark:border-border rounded-md;
394
+ @apply focus-within:ring-2 focus-within:ring-primary-500 dark:focus-within:ring-primary-400 focus-within:border-primary-500 dark:focus-within:border-primary-400;
395
+ @apply transition-colors duration-200;
396
+ }
397
+
398
+ .number-input-field {
399
+ @apply w-full h-full px-3 bg-transparent border-none outline-none;
400
+ @apply text-text dark:text-text placeholder:text-muted dark:placeholder:text-muted;
401
+ @apply disabled:opacity-50 disabled:cursor-not-allowed;
402
+ }
403
+
404
+ .number-input-prefix {
405
+ @apply pl-3 text-muted dark:text-muted;
406
+ }
407
+
408
+ .number-input-suffix {
409
+ @apply pr-3 text-muted dark:text-muted;
410
+ }
411
+
412
+ .number-input-buttons {
413
+ @apply flex;
414
+ }
415
+
416
+ .number-input-buttons-vertical {
417
+ @apply flex-col absolute right-0 top-0 h-full;
418
+ }
419
+
420
+ .number-input-horizontal .number-input-field {
421
+ @apply pr-16;
422
+ }
423
+
424
+ .number-input-horizontal .number-input-buttons {
425
+ @apply absolute right-0 h-full border-l border-border dark:border-border;
426
+ }
427
+
428
+ .number-input-button {
429
+ @apply flex items-center justify-center;
430
+ @apply text-muted dark:text-muted;
431
+ @apply hover:bg-hover dark:hover:bg-hover hover:text-text dark:hover:text-text;
432
+ @apply focus:outline-none focus:bg-hover dark:focus:bg-hover;
433
+ @apply disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent dark:disabled:hover:bg-transparent;
434
+ @apply transition-colors duration-150;
435
+ }
436
+
437
+ .number-input-vertical .number-input-increment {
438
+ @apply rounded-tr-md border-b border-border dark:border-border;
439
+ }
440
+
441
+ .number-input-vertical .number-input-decrement {
442
+ @apply rounded-br-md;
443
+ }
444
+
445
+ .number-input-horizontal .number-input-increment {
446
+ @apply border-r border-border dark:border-border;
447
+ }
448
+
449
+ .number-input-horizontal .number-input-decrement {
450
+ @apply rounded-r-md;
451
+ }
452
+ </style>
@@ -0,0 +1,82 @@
1
+ export default NumberInput;
2
+ type NumberInput = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * NumberInput - A specialized input for numeric values with formatting, units, and increment controls.
8
+ * Provides consistent styling, accessibility features, and integration with the Form component.
9
+ *
10
+ * Usage:
11
+ * ```svelte
12
+ * <NumberInput
13
+ * name="quantity"
14
+ * value={1}
15
+ * min={0}
16
+ * max={100}
17
+ * step={1}
18
+ * />
19
+ *
20
+ * <NumberInput
21
+ * name="price"
22
+ * value={29.99}
23
+ * prefix="$"
24
+ * decimalPlaces={2}
25
+ * />
26
+ *
27
+ * <NumberInput
28
+ * name="percentage"
29
+ * value={75}
30
+ * suffix="%"
31
+ * verticalButtons
32
+ * />
33
+ * ```
34
+ */
35
+ declare const NumberInput: import("svelte").Component<{
36
+ class?: string;
37
+ id?: any;
38
+ name: any;
39
+ value?: number;
40
+ placeholder?: string;
41
+ min: any;
42
+ max: any;
43
+ step?: number;
44
+ decimalPlaces: any;
45
+ prefix: any;
46
+ suffix: any;
47
+ showButtons?: boolean;
48
+ verticalButtons?: boolean;
49
+ required?: boolean;
50
+ disabled?: boolean;
51
+ readonly?: boolean;
52
+ size?: string;
53
+ ariaLabel: any;
54
+ onchange: any;
55
+ oninput: any;
56
+ onfocus: any;
57
+ onblur: any;
58
+ }, {}, "">;
59
+ type $$ComponentProps = {
60
+ class?: string;
61
+ id?: any;
62
+ name: any;
63
+ value?: number;
64
+ placeholder?: string;
65
+ min: any;
66
+ max: any;
67
+ step?: number;
68
+ decimalPlaces: any;
69
+ prefix: any;
70
+ suffix: any;
71
+ showButtons?: boolean;
72
+ verticalButtons?: boolean;
73
+ required?: boolean;
74
+ disabled?: boolean;
75
+ readonly?: boolean;
76
+ size?: string;
77
+ ariaLabel: any;
78
+ onchange: any;
79
+ oninput: any;
80
+ onfocus: any;
81
+ onblur: any;
82
+ };
@@ -0,0 +1,192 @@
1
+ <!--
2
+ @component
3
+ Radio - A styled radio button component.
4
+ Provides consistent styling, accessibility features, and integration with the Form component.
5
+
6
+ Usage:
7
+ ```svelte
8
+ <Radio
9
+ name="theme"
10
+ value="light"
11
+ label="Light theme"
12
+ checked={theme === 'light'}
13
+ />
14
+
15
+ <FormField label="Select theme">
16
+ <div class="flex gap-4">
17
+ <Radio name="theme" value="light" label="Light" />
18
+ <Radio name="theme" value="dark" label="Dark" />
19
+ <Radio name="theme" value="system" label="System" />
20
+ </div>
21
+ </FormField>
22
+ ```
23
+ -->
24
+ <script>
25
+ import { getContext } from "svelte"
26
+
27
+ const {
28
+ /** @type {string} - Additional CSS classes */
29
+ class: className = "",
30
+
31
+ /** @type {string} - HTML id for accessibility */
32
+ id = crypto.randomUUID(),
33
+
34
+ /** @type {string} - Radio name (for grouping) */
35
+ name,
36
+
37
+ /** @type {string} - Radio value */
38
+ value,
39
+
40
+ /** @type {string} - Label text */
41
+ label,
42
+
43
+ /** @type {boolean} - Whether the radio is checked */
44
+ checked = false,
45
+
46
+ /** @type {boolean} - Whether the radio is required */
47
+ required = false,
48
+
49
+ /** @type {boolean} - Whether the radio is disabled */
50
+ disabled = false,
51
+
52
+ /** @type {string} - Size of the radio (sm, md, lg) */
53
+ size = "md",
54
+
55
+ /** @type {string} - ARIA label for accessibility */
56
+ ariaLabel,
57
+ /** @type {(event: CustomEvent) => void} - Change event handler */
58
+ onchange,
59
+ ...restProps
60
+ } = $props()
61
+
62
+ // Get form context if available
63
+ const formContext = getContext("form")
64
+
65
+ // Radio state
66
+ let isChecked = $state(checked)
67
+
68
+ // Update checked state when prop changes
69
+ $effect(() => {
70
+ isChecked = checked
71
+ })
72
+
73
+ // Register with form if available
74
+ let fieldApi = $state()
75
+
76
+ $effect(() => {
77
+ if (formContext && name) {
78
+ fieldApi = formContext.registerField(name, checked ? value : undefined)
79
+ }
80
+ })
81
+
82
+ // Update checked state when form field changes
83
+ $effect(() => {
84
+ if (fieldApi) {
85
+ const formValue = fieldApi.getValue()
86
+ isChecked = formValue === value
87
+ }
88
+ })
89
+
90
+ /**
91
+ * Handles radio change
92
+ * @param {Event} event - Change event
93
+ */
94
+ function handleChange(event) {
95
+ isChecked = event.target.checked
96
+
97
+ // Update form field if available
98
+ if (fieldApi && isChecked) {
99
+ fieldApi.setValue(value)
100
+ }
101
+
102
+ onchange?.(new CustomEvent("change", { detail: { checked: isChecked, value } }))
103
+ }
104
+
105
+ // Determine radio size classes
106
+ const radioSizeClasses = $derived(
107
+ {
108
+ sm: "w-3.5 h-3.5",
109
+ md: "w-4 h-4",
110
+ lg: "w-5 h-5",
111
+ }[size] || "w-4 h-4"
112
+ )
113
+
114
+ const labelSizeClasses = $derived(
115
+ {
116
+ sm: "text-xs",
117
+ md: "text-sm",
118
+ lg: "text-base",
119
+ }[size] || "text-sm"
120
+ )
121
+ </script>
122
+
123
+ <label
124
+ class="radio-wrapper {className} {labelSizeClasses} {disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
125
+ >
126
+ <div class="radio-container">
127
+ <input
128
+ type="radio"
129
+ {id}
130
+ {name}
131
+ {value}
132
+ checked={isChecked}
133
+ {required}
134
+ disabled={disabled || (fieldApi && fieldApi.isDisabled())}
135
+ aria-label={ariaLabel || label}
136
+ class="radio-input"
137
+ onchange={handleChange}
138
+ {...restProps}
139
+ />
140
+
141
+ <span class="radio-control {radioSizeClasses}" aria-hidden="true">
142
+ <span class="radio-dot"></span>
143
+ </span>
144
+ </div>
145
+
146
+ {#if label}
147
+ <span class="radio-label">{label}</span>
148
+ {/if}
149
+ </label>
150
+
151
+ <style>
152
+ @reference "../../twintrinsic.css";
153
+
154
+ .radio-wrapper {
155
+ @apply inline-flex items-center gap-2;
156
+ }
157
+
158
+ .radio-container {
159
+ @apply relative flex items-center justify-center;
160
+ }
161
+
162
+ .radio-input {
163
+ @apply sr-only;
164
+ }
165
+
166
+ .radio-control {
167
+ @apply rounded-full border-2 border-border dark:border-border bg-background dark:bg-background;
168
+ @apply flex items-center justify-center;
169
+ @apply transition-colors duration-200;
170
+ }
171
+
172
+ .radio-input:checked + .radio-control {
173
+ @apply border-primary-500 dark:border-primary-400;
174
+ }
175
+
176
+ .radio-input:focus + .radio-control {
177
+ @apply ring-2 ring-offset-2 ring-primary-500 dark:ring-primary-400;
178
+ }
179
+
180
+ .radio-dot {
181
+ @apply rounded-full bg-primary-500 dark:bg-primary-400;
182
+ @apply w-0 h-0 opacity-0 transition-all duration-200;
183
+ }
184
+
185
+ .radio-input:checked + .radio-control .radio-dot {
186
+ @apply w-1/2 h-1/2 opacity-100;
187
+ }
188
+
189
+ .radio-label {
190
+ @apply text-text dark:text-text;
191
+ }
192
+ </style>