sveltacular 1.0.0 → 1.0.4
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.
- package/README.md +44 -2
- package/dist/forms/check-box/check-box-group.svelte +16 -12
- package/dist/forms/combo-box/combo-box.svelte +31 -43
- package/dist/forms/combo-box/combo-box.svelte.d.ts +2 -22
- package/dist/forms/date-box/date-box.svelte +7 -7
- package/dist/forms/file-box/file-box.svelte +1 -5
- package/dist/forms/form-field.svelte +58 -0
- package/dist/forms/form-field.svelte.d.ts +7 -0
- package/dist/forms/form-row.svelte +29 -0
- package/dist/forms/form-row.svelte.d.ts +7 -0
- package/dist/forms/form.svelte +6 -0
- package/dist/forms/info-box/info-box.svelte +1 -5
- package/dist/forms/list-box/list-box.svelte +1 -5
- package/dist/forms/money-box/money-box.svelte +6 -7
- package/dist/forms/number-box/number-box.svelte +1 -5
- package/dist/forms/phone-box/phone-box.svelte +9 -7
- package/dist/forms/radio-group/radio-group.svelte +1 -5
- package/dist/forms/slider/slider.svelte +1 -5
- package/dist/forms/text-area/text-area.svelte +18 -15
- package/dist/forms/text-box/text-box.svelte +40 -53
- package/dist/forms/time-box/time-box.svelte +1 -5
- package/dist/generic/avatar/avatar.svelte +2 -0
- package/dist/generic/chip/chip.svelte +2 -0
- package/dist/navigation/context-menu/README.md +2 -0
- package/dist/navigation/context-menu/context-menu-divider.svelte +2 -0
- package/dist/sveltacular.css +518 -0
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -12,17 +12,23 @@ npm i sveltacular
|
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
+
First, import the default styles to get all CSS variables and base styles:
|
|
16
|
+
|
|
15
17
|
```svelte
|
|
16
18
|
<script lang="ts">
|
|
19
|
+
import 'sveltacular/styles.css';
|
|
17
20
|
import { Button } from 'sveltacular';
|
|
18
21
|
</script>
|
|
19
22
|
|
|
20
23
|
<Button variant="primary" label="Hello World" />
|
|
21
24
|
```
|
|
22
25
|
|
|
26
|
+
**Note**: The styles import is required for components to render with default styling. If you prefer to provide your own theme, you can skip this import and define your own CSS variables (see [Theming](#theming) below).
|
|
27
|
+
|
|
23
28
|
## Component Catalog
|
|
24
29
|
|
|
25
30
|
### Forms
|
|
31
|
+
|
|
26
32
|
- **Button** - Multiple variants (primary, secondary, positive, danger, outline)
|
|
27
33
|
- **TextBox** - Text input with validation and formatting options
|
|
28
34
|
- **NumberBox** - Number input with min/max/decimals
|
|
@@ -38,6 +44,7 @@ npm i sveltacular
|
|
|
38
44
|
- **Form** - Form container with validation
|
|
39
45
|
|
|
40
46
|
### Generic Components
|
|
47
|
+
|
|
41
48
|
- **Card** - Card container
|
|
42
49
|
- **Pill** - Badge/pill component
|
|
43
50
|
- **Badge** - Notification badge
|
|
@@ -52,6 +59,7 @@ npm i sveltacular
|
|
|
52
59
|
- **List** - Styled list component
|
|
53
60
|
|
|
54
61
|
### Navigation
|
|
62
|
+
|
|
55
63
|
- **AppBar** - Application bar
|
|
56
64
|
- **SideBar** - Side navigation
|
|
57
65
|
- **Breadcrumbs** - Breadcrumb navigation
|
|
@@ -62,16 +70,19 @@ npm i sveltacular
|
|
|
62
70
|
- **Drawer** - Slide-out drawer
|
|
63
71
|
|
|
64
72
|
### Modals
|
|
73
|
+
|
|
65
74
|
- **Modal** - Generic modal dialog
|
|
66
75
|
- **Alert** - Alert dialog
|
|
67
76
|
- **Confirm** - Confirmation dialog
|
|
68
77
|
- **Prompt** - Input prompt dialog
|
|
69
78
|
|
|
70
79
|
### Tables
|
|
80
|
+
|
|
71
81
|
- **Table** - Table component with header/body/footer
|
|
72
82
|
- **DataGrid** - Advanced data grid
|
|
73
83
|
|
|
74
84
|
### Typography
|
|
85
|
+
|
|
75
86
|
- **Headline** - Heading component
|
|
76
87
|
- **Subtitle** - Subtitle component
|
|
77
88
|
- **Text** - Text component
|
|
@@ -79,6 +90,7 @@ npm i sveltacular
|
|
|
79
90
|
- **CodeBlock** - Code block
|
|
80
91
|
|
|
81
92
|
### Layout
|
|
93
|
+
|
|
82
94
|
- **FlexRow** / **FlexCol** - Flexbox layout
|
|
83
95
|
- **Grid** - Grid layout
|
|
84
96
|
|
|
@@ -102,11 +114,19 @@ import { CheckBox, CheckBoxGroup } from 'sveltacular/forms/check-box';
|
|
|
102
114
|
|
|
103
115
|
## Theming
|
|
104
116
|
|
|
105
|
-
Sveltacular uses CSS variables for theming.
|
|
117
|
+
Sveltacular uses CSS variables for theming. When you import `sveltacular/styles.css`, all default CSS variables are included. You can override any of these variables to customize the appearance of components.
|
|
118
|
+
|
|
119
|
+
See [THEMING.md](./THEMING.md) for a complete list of available CSS variables.
|
|
120
|
+
|
|
121
|
+
### Customizing the Theme
|
|
106
122
|
|
|
107
|
-
|
|
123
|
+
Override CSS variables in your own stylesheet (after importing the default styles):
|
|
108
124
|
|
|
109
125
|
```css
|
|
126
|
+
/* Import default styles first */
|
|
127
|
+
@import 'sveltacular/styles.css';
|
|
128
|
+
|
|
129
|
+
/* Then override variables as needed */
|
|
110
130
|
:root {
|
|
111
131
|
--button-primary-bg: #1e88e5;
|
|
112
132
|
--form-input-border: #e0e0e0;
|
|
@@ -114,6 +134,27 @@ Example:
|
|
|
114
134
|
}
|
|
115
135
|
```
|
|
116
136
|
|
|
137
|
+
Or in a Svelte component:
|
|
138
|
+
|
|
139
|
+
```svelte
|
|
140
|
+
<script>
|
|
141
|
+
import 'sveltacular/styles.css';
|
|
142
|
+
import { Button } from 'sveltacular';
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<style>
|
|
146
|
+
:global(:root) {
|
|
147
|
+
--button-primary-bg: #1e88e5;
|
|
148
|
+
--form-input-border: #e0e0e0;
|
|
149
|
+
--base-color-bg: #ffffff;
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Providing Your Own Theme
|
|
155
|
+
|
|
156
|
+
If you prefer not to use the default stylesheet, you can define all CSS variables yourself. See [THEMING.md](./THEMING.md) for the complete list of required variables.
|
|
157
|
+
|
|
117
158
|
## Form Validation
|
|
118
159
|
|
|
119
160
|
Sveltacular includes a validation system:
|
|
@@ -136,6 +177,7 @@ if (!result.isValid) {
|
|
|
136
177
|
## Accessibility
|
|
137
178
|
|
|
138
179
|
Sveltacular components include:
|
|
180
|
+
|
|
139
181
|
- ARIA attributes for screen readers
|
|
140
182
|
- Keyboard navigation support
|
|
141
183
|
- Focus management utilities
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
|
|
3
4
|
import FormField from '../form-field.svelte';
|
|
4
|
-
import FormLabel from '../form-label.svelte';
|
|
5
5
|
import CheckBox from './check-box.svelte';
|
|
6
6
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
7
7
|
|
|
@@ -31,13 +31,20 @@
|
|
|
31
31
|
// Sync itemsWithState when items or group changes (one-way: items/group -> itemsWithState)
|
|
32
32
|
// Reassign the entire array to avoid reading itemsWithState in the effect
|
|
33
33
|
$effect(() => {
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
// Track items and group as dependencies
|
|
35
|
+
const currentItems = items;
|
|
36
|
+
const currentGroup = group;
|
|
37
|
+
|
|
38
|
+
// Use untrack to prevent writing to itemsWithState from triggering this effect again
|
|
39
|
+
untrack(() => {
|
|
40
|
+
// Rebuild itemsWithState from items, using group to determine checked state
|
|
41
|
+
// Reassign instead of mutate to avoid circular dependency
|
|
42
|
+
const newItems = currentItems.map((item) => ({
|
|
43
|
+
...item,
|
|
44
|
+
isChecked: currentGroup.includes(item.value ?? '')
|
|
45
|
+
}));
|
|
46
|
+
itemsWithState = newItems;
|
|
47
|
+
});
|
|
41
48
|
});
|
|
42
49
|
|
|
43
50
|
const handleCheckboxChange = (data: { isChecked: boolean; value: string }) => {
|
|
@@ -53,10 +60,7 @@
|
|
|
53
60
|
};
|
|
54
61
|
</script>
|
|
55
62
|
|
|
56
|
-
<FormField {size}>
|
|
57
|
-
{#if label}
|
|
58
|
-
<FormLabel {id} {required} {label} />
|
|
59
|
-
{/if}
|
|
63
|
+
<FormField {size} {label} {id} {required} {disabled}>
|
|
60
64
|
<div>
|
|
61
65
|
{#each itemsWithState as item}
|
|
62
66
|
<CheckBox
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
* />
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
|
+
import { untrack } from 'svelte';
|
|
22
23
|
import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
|
|
23
24
|
import FormField from '../form-field.svelte';
|
|
24
|
-
import FormLabel from '../form-label.svelte';
|
|
25
25
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
26
26
|
import Menu from '../../generic/menu/menu.svelte';
|
|
27
27
|
import AngleUpIcon from '../../icons/angle-up-icon.svelte';
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
multiSelect = false,
|
|
41
41
|
placeholder = '',
|
|
42
42
|
label = undefined,
|
|
43
|
-
|
|
43
|
+
helperText = undefined,
|
|
44
44
|
errorText = undefined,
|
|
45
45
|
successText = undefined,
|
|
46
46
|
maxSelections = undefined as number | undefined,
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
placeholder?: string;
|
|
69
69
|
/** Label text for the combobox */
|
|
70
70
|
label?: string;
|
|
71
|
-
/**
|
|
72
|
-
|
|
71
|
+
/** Helper text displayed below the input */
|
|
72
|
+
helperText?: string;
|
|
73
73
|
/** Error message to display */
|
|
74
74
|
errorText?: string;
|
|
75
75
|
/** Success message to display */
|
|
@@ -368,9 +368,28 @@
|
|
|
368
368
|
}
|
|
369
369
|
};
|
|
370
370
|
|
|
371
|
-
//
|
|
371
|
+
// Filter items when items or searchQuery changes
|
|
372
|
+
// Use untrack to prevent reading filteredItems/highlightIndex from triggering the effect
|
|
372
373
|
$effect(() => {
|
|
373
|
-
|
|
374
|
+
// Track only the dependencies we care about
|
|
375
|
+
const query = searchQuery.trim().toLowerCase();
|
|
376
|
+
const currentItems = items;
|
|
377
|
+
|
|
378
|
+
// Use untrack to write to state without triggering this effect again
|
|
379
|
+
untrack(() => {
|
|
380
|
+
if (query && searchable) {
|
|
381
|
+
filteredItems = currentItems.filter((item) =>
|
|
382
|
+
item.name.toLowerCase().includes(query)
|
|
383
|
+
);
|
|
384
|
+
} else {
|
|
385
|
+
filteredItems = [...currentItems];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Reset highlight if out of bounds
|
|
389
|
+
if (highlightIndex >= filteredItems.length) {
|
|
390
|
+
highlightIndex = Math.max(0, filteredItems.length - 1);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
374
393
|
});
|
|
375
394
|
|
|
376
395
|
// Derived state for open/closed
|
|
@@ -379,16 +398,15 @@
|
|
|
379
398
|
// Clear search query when menu closes in single-select mode
|
|
380
399
|
$effect(() => {
|
|
381
400
|
if (!isMenuOpen && !multiSelect) {
|
|
382
|
-
|
|
401
|
+
// Use untrack to prevent triggering other effects
|
|
402
|
+
untrack(() => {
|
|
403
|
+
searchQuery = '';
|
|
404
|
+
});
|
|
383
405
|
}
|
|
384
406
|
});
|
|
385
407
|
</script>
|
|
386
408
|
|
|
387
|
-
<FormField {size}>
|
|
388
|
-
{#if label}
|
|
389
|
-
<FormLabel {id} {required} {disabled} {label} />
|
|
390
|
-
{/if}
|
|
391
|
-
|
|
409
|
+
<FormField {size} {label} {id} {required} {disabled} {helperText} {errorText} {successText}>
|
|
392
410
|
<div class="combobox-wrapper {open ? 'open' : 'closed'} {disabled ? 'disabled' : 'enabled'}">
|
|
393
411
|
<!-- Multi-select chip display -->
|
|
394
412
|
{#if multiSelect && selectedItems.length > 0}
|
|
@@ -423,7 +441,7 @@
|
|
|
423
441
|
aria-activedescendant={activeDescendant}
|
|
424
442
|
aria-haspopup="listbox"
|
|
425
443
|
aria-label={label}
|
|
426
|
-
aria-describedby={
|
|
444
|
+
aria-describedby={helperText || errorText || successText ? `${id}-helper ${id}-error ${id}-success` : undefined}
|
|
427
445
|
aria-invalid={errorText ? 'true' : undefined}
|
|
428
446
|
onfocus={() => {
|
|
429
447
|
if (!disabled) {
|
|
@@ -489,19 +507,6 @@
|
|
|
489
507
|
</div>
|
|
490
508
|
</div>
|
|
491
509
|
|
|
492
|
-
<!-- Help/Error/Success text -->
|
|
493
|
-
{#if helpText || errorText || successText}
|
|
494
|
-
<div id="{id}-description" class="description">
|
|
495
|
-
{#if errorText}
|
|
496
|
-
<span class="error-text" role="alert">{errorText}</span>
|
|
497
|
-
{:else if successText}
|
|
498
|
-
<span class="success-text" role="status">{successText}</span>
|
|
499
|
-
{:else if helpText}
|
|
500
|
-
<span class="help-text">{helpText}</span>
|
|
501
|
-
{/if}
|
|
502
|
-
</div>
|
|
503
|
-
{/if}
|
|
504
|
-
|
|
505
510
|
<!-- Max selections indicator -->
|
|
506
511
|
{#if multiSelect && maxSelections !== undefined}
|
|
507
512
|
<div class="max-selections-indicator">
|
|
@@ -629,23 +634,6 @@ button:focus-visible {
|
|
|
629
634
|
margin-top: 0.25rem;
|
|
630
635
|
}
|
|
631
636
|
|
|
632
|
-
.description {
|
|
633
|
-
margin-top: 0.5rem;
|
|
634
|
-
font-size: var(--font-sm);
|
|
635
|
-
line-height: 1.4;
|
|
636
|
-
}
|
|
637
|
-
.description .help-text {
|
|
638
|
-
color: var(--gray-600);
|
|
639
|
-
}
|
|
640
|
-
.description .error-text {
|
|
641
|
-
color: var(--danger);
|
|
642
|
-
font-weight: 500;
|
|
643
|
-
}
|
|
644
|
-
.description .success-text {
|
|
645
|
-
color: var(--success);
|
|
646
|
-
font-weight: 500;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
637
|
.max-selections-indicator {
|
|
650
638
|
margin-top: 0.5rem;
|
|
651
639
|
font-size: var(--font-sm);
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Combobox Component
|
|
3
|
-
*
|
|
4
|
-
* A searchable select component with typeahead, multi-select support, and virtual scrolling.
|
|
5
|
-
* Follows the ARIA 1.2 Combobox pattern for full accessibility.
|
|
6
|
-
*
|
|
7
|
-
* @component
|
|
8
|
-
* @example
|
|
9
|
-
* ```svelte
|
|
10
|
-
* <ComboBox
|
|
11
|
-
* bind:value
|
|
12
|
-
* items={[
|
|
13
|
-
* { value: '1', name: 'Option 1' },
|
|
14
|
-
* { value: '2', name: 'Option 2' }
|
|
15
|
-
* ]}
|
|
16
|
-
* label="Select an option"
|
|
17
|
-
* searchable
|
|
18
|
-
* />
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
1
|
import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
|
|
22
2
|
type $$ComponentProps = {
|
|
23
3
|
/** Current selected value(s) - string for single-select, string[] for multi-select */
|
|
@@ -38,8 +18,8 @@ type $$ComponentProps = {
|
|
|
38
18
|
placeholder?: string;
|
|
39
19
|
/** Label text for the combobox */
|
|
40
20
|
label?: string;
|
|
41
|
-
/**
|
|
42
|
-
|
|
21
|
+
/** Helper text displayed below the input */
|
|
22
|
+
helperText?: string;
|
|
43
23
|
/** Error message to display */
|
|
44
24
|
errorText?: string;
|
|
45
25
|
/** Success message to display */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import { addUnits, currentDateTime, isDateString, isDateOrDateTimeString, isDateTimeString } from '../../helpers/date.js';
|
|
3
4
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
4
5
|
import FormField from '../form-field.svelte';
|
|
5
|
-
import FormLabel from '../form-label.svelte';
|
|
6
6
|
import type { DateUnit, FormFieldSizeOptions } from '../../index.js';
|
|
7
7
|
import Button from '../button/button.svelte';
|
|
8
8
|
|
|
@@ -68,17 +68,17 @@
|
|
|
68
68
|
|
|
69
69
|
$effect(() => {
|
|
70
70
|
if (!value) {
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
// Use untrack to prevent writes to enabled/value from triggering this effect again
|
|
72
|
+
untrack(() => {
|
|
73
|
+
if (nullable) enabled = false;
|
|
74
|
+
else value = getDefaultValue();
|
|
75
|
+
});
|
|
73
76
|
}
|
|
74
77
|
});
|
|
75
78
|
let disabled = $derived(!enabled);
|
|
76
79
|
</script>
|
|
77
80
|
|
|
78
|
-
<FormField {size}>
|
|
79
|
-
{#if label}
|
|
80
|
-
<FormLabel {id} {required} {label} />
|
|
81
|
-
{/if}
|
|
81
|
+
<FormField {size} {label} {id} {required} {disabled}>
|
|
82
82
|
<div class:nullable class:disabled>
|
|
83
83
|
<span class="input">
|
|
84
84
|
<input {...{ type }} {id} {placeholder} {disabled} bind:value {required} oninput={onInput} />
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
4
4
|
import FormField from '../form-field.svelte';
|
|
5
|
-
import FormLabel from '../form-label.svelte';
|
|
6
5
|
import type { FormFieldSizeOptions } from '../../types/form.js';
|
|
7
6
|
|
|
8
7
|
const id = uniqueId();
|
|
@@ -32,10 +31,7 @@
|
|
|
32
31
|
} = $props();
|
|
33
32
|
</script>
|
|
34
33
|
|
|
35
|
-
<FormField {size}>
|
|
36
|
-
{#if label}
|
|
37
|
-
<FormLabel {id} {required} {label} />
|
|
38
|
-
{/if}
|
|
34
|
+
<FormField {size} {label} {id} {required} {disabled}>
|
|
39
35
|
<div>
|
|
40
36
|
<input
|
|
41
37
|
{id}
|
|
@@ -2,24 +2,82 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { ComponentSize } from '../types/size.js';
|
|
4
4
|
import { getMaxWidth, getDisplayType } from '../types/size.js';
|
|
5
|
+
import FormLabel from './form-label.svelte';
|
|
5
6
|
|
|
6
7
|
let {
|
|
7
8
|
size = 'full',
|
|
9
|
+
label = undefined,
|
|
10
|
+
id = undefined,
|
|
11
|
+
required = false,
|
|
12
|
+
disabled = false,
|
|
13
|
+
helperText = undefined,
|
|
14
|
+
errorText = undefined,
|
|
15
|
+
successText = undefined,
|
|
8
16
|
children
|
|
9
17
|
}: {
|
|
10
18
|
size?: ComponentSize;
|
|
19
|
+
label?: string | undefined;
|
|
20
|
+
id?: string | undefined;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
helperText?: string | undefined;
|
|
24
|
+
errorText?: string | undefined;
|
|
25
|
+
successText?: string | undefined;
|
|
11
26
|
children: Snippet;
|
|
12
27
|
} = $props();
|
|
13
28
|
|
|
14
29
|
let displayType = $derived(getDisplayType(size));
|
|
15
30
|
let maxWidth = $derived(getMaxWidth(size));
|
|
31
|
+
|
|
32
|
+
let showHelperText = $derived(!!helperText && !errorText && !successText);
|
|
33
|
+
let showSuccessText = $derived(!!successText && !errorText);
|
|
34
|
+
let showErrorText = $derived(!!errorText);
|
|
16
35
|
</script>
|
|
17
36
|
|
|
18
37
|
<div style={`display: ${displayType}; width: 100%; min-width: 10rem; max-width: ${maxWidth}`}>
|
|
38
|
+
{#if label}
|
|
39
|
+
<FormLabel {id} {required} {disabled} {label} />
|
|
40
|
+
{/if}
|
|
19
41
|
{@render children?.()}
|
|
42
|
+
{#if showHelperText}
|
|
43
|
+
<div class="helper-text" id="{id}-helper">{helperText}</div>
|
|
44
|
+
{/if}
|
|
45
|
+
{#if showSuccessText}
|
|
46
|
+
<div class="success-text" id="{id}-success" role="status" aria-live="polite">
|
|
47
|
+
{successText}
|
|
48
|
+
</div>
|
|
49
|
+
{/if}
|
|
50
|
+
{#if showErrorText}
|
|
51
|
+
<div class="error-text" id="{id}-error" role="alert" aria-live="assertive">
|
|
52
|
+
{errorText}
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
20
55
|
</div>
|
|
21
56
|
|
|
22
57
|
<style>div {
|
|
23
58
|
margin-bottom: var(--spacing-base);
|
|
24
59
|
margin-right: var(--spacing-base);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.helper-text {
|
|
63
|
+
font-size: var(--font-sm);
|
|
64
|
+
line-height: 1.25rem;
|
|
65
|
+
padding: var(--spacing-xs);
|
|
66
|
+
color: var(--body-fg);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.success-text {
|
|
70
|
+
font-size: var(--font-sm);
|
|
71
|
+
line-height: 1.25rem;
|
|
72
|
+
padding: var(--spacing-xs);
|
|
73
|
+
color: var(--success, #28a745);
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.error-text {
|
|
78
|
+
font-size: var(--font-sm);
|
|
79
|
+
line-height: 1.25rem;
|
|
80
|
+
padding: var(--spacing-xs);
|
|
81
|
+
color: var(--danger, #dc3545);
|
|
82
|
+
font-weight: 500;
|
|
25
83
|
}</style>
|
|
@@ -2,6 +2,13 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { ComponentSize } from '../types/size.js';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
size?: ComponentSize;
|
|
5
|
+
label?: string | undefined;
|
|
6
|
+
id?: string | undefined;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
helperText?: string | undefined;
|
|
10
|
+
errorText?: string | undefined;
|
|
11
|
+
successText?: string | undefined;
|
|
5
12
|
children: Snippet;
|
|
6
13
|
};
|
|
7
14
|
declare const FormField: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { children }: Props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div class="form-row">
|
|
12
|
+
{@render children()}
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<style>/* ============================================
|
|
16
|
+
BREAKPOINTS - Responsive Design
|
|
17
|
+
============================================ */
|
|
18
|
+
.form-row {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: row;
|
|
21
|
+
gap: var(--spacing-base);
|
|
22
|
+
align-items: flex-start;
|
|
23
|
+
}
|
|
24
|
+
@media (max-width: 479.98px) {
|
|
25
|
+
.form-row {
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
align-items: stretch;
|
|
28
|
+
}
|
|
29
|
+
}</style>
|
package/dist/forms/form.svelte
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import LinkIcon from '../../icons/link-icon.svelte';
|
|
3
3
|
import type { FormFieldSizeOptions } from '../../index.js';
|
|
4
4
|
import FormField from '../form-field.svelte';
|
|
5
|
-
import FormLabel from '../form-label.svelte';
|
|
6
5
|
|
|
7
6
|
let {
|
|
8
7
|
size = 'md' as FormFieldSizeOptions,
|
|
@@ -17,10 +16,7 @@
|
|
|
17
16
|
} = $props();
|
|
18
17
|
</script>
|
|
19
18
|
|
|
20
|
-
<FormField {size}>
|
|
21
|
-
{#if label}
|
|
22
|
-
<FormLabel {label} />
|
|
23
|
-
{/if}
|
|
19
|
+
<FormField {size} {label}>
|
|
24
20
|
<div class="input">
|
|
25
21
|
{#if href}
|
|
26
22
|
<span class="icon">
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { DropdownOption, FormFieldSizeOptions, MenuOption } from '../../types/form.js';
|
|
3
3
|
import FormField from '../form-field.svelte';
|
|
4
|
-
import FormLabel from '../form-label.svelte';
|
|
5
4
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
6
5
|
import Menu from '../../generic/menu/menu.svelte';
|
|
7
6
|
import AngleUpIcon from '../../icons/angle-up-icon.svelte';
|
|
@@ -151,10 +150,7 @@
|
|
|
151
150
|
let open = $derived(isMenuOpen && !disabled);
|
|
152
151
|
</script>
|
|
153
152
|
|
|
154
|
-
<FormField {size}>
|
|
155
|
-
{#if label}
|
|
156
|
-
<FormLabel {id} {required} {disabled} {label} />
|
|
157
|
-
{/if}
|
|
153
|
+
<FormField {size} {label} {id} {required} {disabled}>
|
|
158
154
|
<div class="{open ? 'open' : 'closed'} {disabled ? 'disabled' : 'enabled'}">
|
|
159
155
|
<input
|
|
160
156
|
type="text"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { uniqueId, type FormFieldSizeOptions } from '../../index.js';
|
|
3
3
|
import FormField from '../form-field.svelte';
|
|
4
|
-
import FormLabel from '../form-label.svelte';
|
|
5
4
|
import { untrack } from 'svelte';
|
|
6
5
|
|
|
7
6
|
const id = uniqueId();
|
|
@@ -58,8 +57,11 @@
|
|
|
58
57
|
]);
|
|
59
58
|
$effect(() => {
|
|
60
59
|
if (value !== null && value >= 0) {
|
|
61
|
-
dollars
|
|
62
|
-
|
|
60
|
+
// Use untrack to prevent writes to dollars/cents from triggering this effect again
|
|
61
|
+
untrack(() => {
|
|
62
|
+
dollars = getDollarsFromValue();
|
|
63
|
+
cents = getCentsFromValue();
|
|
64
|
+
});
|
|
63
65
|
}
|
|
64
66
|
});
|
|
65
67
|
|
|
@@ -207,10 +209,7 @@
|
|
|
207
209
|
|
|
208
210
|
</script>
|
|
209
211
|
|
|
210
|
-
<FormField {size}>
|
|
211
|
-
{#if label}
|
|
212
|
-
<FormLabel {id} {label} />
|
|
213
|
-
{/if}
|
|
212
|
+
<FormField {size} {label} {id}>
|
|
214
213
|
<div class="input {currency}" class:allowCents {id}>
|
|
215
214
|
{#if prefix}
|
|
216
215
|
<span class="prefix">{prefix}</span>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { roundToDecimals } from '../../helpers/round-to-decimals.js';
|
|
3
3
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
4
4
|
import FormField from '../form-field.svelte';
|
|
5
|
-
import FormLabel from '../form-label.svelte';
|
|
6
5
|
import type { FormFieldSizeOptions } from '../../types/form.js';
|
|
7
6
|
const id = uniqueId();
|
|
8
7
|
|
|
@@ -64,10 +63,7 @@
|
|
|
64
63
|
};
|
|
65
64
|
</script>
|
|
66
65
|
|
|
67
|
-
<FormField {size}>
|
|
68
|
-
{#if label}
|
|
69
|
-
<FormLabel {id} {label} />
|
|
70
|
-
{/if}
|
|
66
|
+
<FormField {size} {label} {id}>
|
|
71
67
|
<div class="input {type}">
|
|
72
68
|
{#if prefix}
|
|
73
69
|
<span class="prefix">{prefix}</span>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import { uniqueId, type FormFieldSizeOptions } from '../../index.js';
|
|
3
4
|
import FormField from '../form-field.svelte';
|
|
4
|
-
import FormLabel from '../form-label.svelte';
|
|
5
5
|
|
|
6
6
|
let {
|
|
7
7
|
value = $bindable('' as string | null),
|
|
@@ -135,16 +135,18 @@
|
|
|
135
135
|
setValue(value ?? '');
|
|
136
136
|
|
|
137
137
|
$effect(() => {
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
// Only trigger when the phone number parts change
|
|
139
|
+
const hasValue = areaCode || localExt || lastFour;
|
|
140
|
+
if (hasValue) {
|
|
141
|
+
// Use untrack to prevent value changes from triggering this effect again
|
|
142
|
+
untrack(() => {
|
|
143
|
+
publishChange();
|
|
144
|
+
});
|
|
140
145
|
}
|
|
141
146
|
});
|
|
142
147
|
</script>
|
|
143
148
|
|
|
144
|
-
<FormField {size}>
|
|
145
|
-
{#if label}
|
|
146
|
-
<FormLabel id="{id}-areaCode" {label} />
|
|
147
|
-
{/if}
|
|
149
|
+
<FormField {size} {label} id="{id}-areaCode">
|
|
148
150
|
<div class="input">
|
|
149
151
|
<span class="areaCode segment">
|
|
150
152
|
<span>(</span>
|