@makolabs/ripple 2.5.9 → 3.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.
- package/README.md +403 -497
- package/dist/adapters/storage/S3Adapter.d.ts +49 -1
- package/dist/adapters/storage/S3Adapter.js +38 -1
- package/dist/adapters/storage/types.d.ts +20 -0
- package/dist/ai/AIChatInterface.svelte +2 -1
- package/dist/ai/AIChatInterface.svelte.d.ts +2 -1
- package/dist/ai/CodeRenderer.svelte +7 -2
- package/dist/ai/CodeRenderer.svelte.d.ts +2 -1
- package/dist/ai/ComposeDropdown.svelte +1 -1
- package/dist/ai/MessageBox.svelte +3 -3
- package/dist/ai/MessageBox.svelte.d.ts +3 -2
- package/dist/ai/ThinkingDisplay.svelte +4 -3
- package/dist/ai/ThinkingDisplay.svelte.d.ts +2 -1
- package/dist/ai/ai-types.d.ts +55 -1
- package/dist/button/Button.svelte +5 -5
- package/dist/button/button-types.d.ts +49 -4
- package/dist/button/button.d.ts +9 -9
- package/dist/button/button.js +6 -6
- package/dist/charts/Chart.svelte +8 -16
- package/dist/charts/chart-types.d.ts +78 -1
- package/dist/drawer/Drawer.svelte +6 -26
- package/dist/drawer/drawer-types.d.ts +33 -12
- package/dist/drawer/drawer.d.ts +3 -3
- package/dist/drawer/drawer.js +1 -1
- package/dist/elements/accordion/Accordion.svelte +6 -17
- package/dist/elements/accordion/accordion-types.d.ts +53 -6
- package/dist/elements/alert/Alert.svelte +3 -0
- package/dist/elements/badge/Badge.svelte +1 -1
- package/dist/elements/badge/badge-types.d.ts +22 -0
- package/dist/elements/badge/badge.d.ts +3 -3
- package/dist/elements/badge/badge.js +1 -1
- package/dist/elements/combobox/ComboBox.svelte +244 -0
- package/dist/elements/combobox/ComboBox.svelte.d.ts +4 -0
- package/dist/elements/combobox/combobox-types.d.ts +41 -0
- package/dist/elements/combobox/combobox-types.js +1 -0
- package/dist/elements/context-menu/ContextMenu.svelte +137 -0
- package/dist/elements/context-menu/ContextMenu.svelte.d.ts +4 -0
- package/dist/elements/context-menu/context-menu-types.d.ts +40 -0
- package/dist/elements/context-menu/context-menu-types.js +1 -0
- package/dist/elements/dropdown/Dropdown.svelte +1 -1
- package/dist/elements/dropdown/Select.svelte +4 -1
- package/dist/elements/dropdown/dropdown-types.d.ts +114 -0
- package/dist/elements/dropdown/dropdown.d.ts +3 -3
- package/dist/elements/dropdown/dropdown.js +2 -2
- package/dist/elements/dropdown/select.d.ts +3 -108
- package/dist/elements/dropdown/select.js +38 -47
- package/dist/elements/empty-state/EmptyState.svelte +1 -1
- package/dist/elements/empty-state/empty-state-types.d.ts +32 -1
- package/dist/elements/empty-state/empty-state.d.ts +3 -3
- package/dist/elements/empty-state/empty-state.js +2 -2
- package/dist/elements/file-upload/FileUpload.svelte +5 -0
- package/dist/elements/file-upload/file-upload-types.d.ts +59 -0
- package/dist/elements/pagination/Pagination.svelte +53 -21
- package/dist/elements/pagination/Pagination.svelte.d.ts +33 -5
- package/dist/elements/popover/Popover.svelte +254 -0
- package/dist/elements/popover/Popover.svelte.d.ts +4 -0
- package/dist/elements/popover/index.d.ts +2 -0
- package/dist/elements/popover/index.js +1 -0
- package/dist/elements/popover/popover-types.d.ts +60 -0
- package/dist/elements/popover/popover-types.js +1 -0
- package/dist/elements/progress/Progress.svelte +32 -7
- package/dist/elements/progress/progress-types.d.ts +48 -1
- package/dist/elements/skeleton/Skeleton.svelte +56 -0
- package/dist/elements/skeleton/Skeleton.svelte.d.ts +4 -0
- package/dist/elements/skeleton/index.d.ts +2 -0
- package/dist/elements/skeleton/index.js +1 -0
- package/dist/elements/skeleton/skeleton-types.d.ts +50 -0
- package/dist/elements/skeleton/skeleton-types.js +1 -0
- package/dist/elements/spinner/Spinner.svelte +1 -1
- package/dist/elements/spinner/spinner-types.d.ts +20 -0
- package/dist/elements/spinner/spinner.d.ts +3 -3
- package/dist/elements/spinner/spinner.js +2 -2
- package/dist/elements/tooltip/Tooltip.svelte +108 -11
- package/dist/elements/tooltip/tooltip-types.d.ts +49 -1
- package/dist/file-browser/FileBrowser.svelte +21 -12
- package/dist/filters/CompactFilters.svelte +221 -33
- package/dist/filters/CompactFilters.svelte.d.ts +1 -1
- package/dist/filters/FilterBar.svelte +184 -0
- package/dist/filters/FilterBar.svelte.d.ts +4 -0
- package/dist/filters/FilterPopover.svelte +346 -0
- package/dist/filters/FilterPopover.svelte.d.ts +4 -0
- package/dist/filters/date-presets.d.ts +15 -0
- package/dist/filters/date-presets.js +107 -0
- package/dist/filters/filter-types.d.ts +69 -3
- package/dist/filters/index.d.ts +5 -0
- package/dist/filters/index.js +4 -0
- package/dist/filters/sync-filters-to-url.svelte.d.ts +37 -0
- package/dist/filters/sync-filters-to-url.svelte.js +114 -0
- package/dist/forms/Checkbox.svelte +24 -9
- package/dist/forms/DateRange.svelte +23 -6
- package/dist/forms/Input.svelte +19 -19
- package/dist/forms/MarketSelector.svelte +9 -4
- package/dist/forms/NumberInput.svelte +14 -18
- package/dist/forms/RadioGroup.svelte +127 -0
- package/dist/forms/RadioGroup.svelte.d.ts +4 -0
- package/dist/forms/SegmentedControl.svelte +11 -4
- package/dist/forms/Slider.svelte +72 -3
- package/dist/forms/Tags.svelte +44 -14
- package/dist/forms/Textarea.svelte +121 -0
- package/dist/forms/Textarea.svelte.d.ts +4 -0
- package/dist/forms/Toggle.svelte +30 -22
- package/dist/forms/calendar/Calendar.svelte +315 -0
- package/dist/forms/calendar/Calendar.svelte.d.ts +4 -0
- package/dist/forms/calendar/calendar-types.d.ts +54 -0
- package/dist/forms/calendar/calendar-types.js +1 -0
- package/dist/forms/calendar/index.d.ts +2 -0
- package/dist/forms/calendar/index.js +1 -0
- package/dist/forms/date-picker/DatePicker.svelte +141 -0
- package/dist/forms/date-picker/DatePicker.svelte.d.ts +4 -0
- package/dist/forms/date-picker/date-picker-types.d.ts +29 -0
- package/dist/forms/date-picker/date-picker-types.js +1 -0
- package/dist/forms/form-size.d.ts +37 -0
- package/dist/forms/form-size.js +67 -0
- package/dist/forms/form-types.d.ts +430 -6
- package/dist/forms/market/market-selector-types.d.ts +52 -1
- package/dist/forms/segmented-control.d.ts +5 -2
- package/dist/forms/segmented-control.js +25 -13
- package/dist/forms/slider.d.ts +3 -3
- package/dist/forms/slider.js +37 -30
- package/dist/funcs/user-management.remote.js +1 -1
- package/dist/header/Breadcrumbs.svelte +4 -20
- package/dist/header/PageHeader.svelte +6 -14
- package/dist/header/breadcrumbs.d.ts +3 -11
- package/dist/header/breadcrumbs.js +10 -5
- package/dist/header/header-types.d.ts +62 -11
- package/dist/index.d.ts +35 -9
- package/dist/index.js +24 -4
- package/dist/layout/activity-list/ActivityList.svelte +13 -7
- package/dist/layout/activity-list/activity-list-types.d.ts +46 -7
- package/dist/layout/card/Card.svelte +12 -15
- package/dist/layout/card/MetricCard.svelte +50 -32
- package/dist/layout/card/card-types.d.ts +114 -4
- package/dist/layout/navbar/navbar-types.d.ts +48 -0
- package/dist/layout/navbar/navbar.d.ts +3 -3
- package/dist/layout/navbar/navbar.js +2 -2
- package/dist/layout/sidebar/Sidebar.svelte +87 -11
- package/dist/layout/sidebar/sidebar-types.d.ts +60 -1
- package/dist/layout/stepper/Stepper.svelte +288 -0
- package/dist/layout/stepper/Stepper.svelte.d.ts +4 -0
- package/dist/layout/stepper/stepper-types.d.ts +80 -0
- package/dist/layout/stepper/stepper-types.js +1 -0
- package/dist/layout/table/Table.svelte +91 -85
- package/dist/layout/table/table-types.d.ts +148 -24
- package/dist/layout/table/table.d.ts +3 -3
- package/dist/layout/table/table.js +2 -2
- package/dist/layout/tabs/Tab.svelte +6 -2
- package/dist/layout/tabs/Tab.svelte.d.ts +4 -1
- package/dist/layout/tabs/TabGroup.svelte +9 -2
- package/dist/layout/tabs/tabs-types.d.ts +63 -0
- package/dist/layout/tabs/tabs.d.ts +3 -3
- package/dist/layout/tabs/tabs.js +12 -6
- package/dist/modal/ConfirmDialog.svelte +65 -0
- package/dist/modal/ConfirmDialog.svelte.d.ts +4 -0
- package/dist/modal/Modal.svelte +6 -26
- package/dist/modal/confirm-dialog-types.d.ts +39 -0
- package/dist/modal/confirm-dialog-types.js +1 -0
- package/dist/modal/modal-types.d.ts +51 -12
- package/dist/modal/modal.d.ts +3 -3
- package/dist/modal/modal.js +3 -3
- package/dist/pipeline/Pipeline.svelte +8 -3
- package/dist/pipeline/pipeline-types.d.ts +55 -3
- package/dist/pipeline/pipeline.d.ts +18 -3
- package/dist/pipeline/pipeline.js +7 -2
- package/dist/server/s3.d.ts +35 -3
- package/dist/sonner/Toaster.svelte +29 -0
- package/dist/sonner/Toaster.svelte.d.ts +4 -0
- package/dist/sonner/index.d.ts +21 -0
- package/dist/sonner/index.js +20 -0
- package/dist/user-management/UserManagement.svelte +22 -16
- package/dist/user-management/UserModal.svelte +10 -7
- package/dist/user-management/UserTable.svelte +16 -17
- package/dist/user-management/UserViewModal.svelte +11 -11
- package/dist/user-management/user-management-types.d.ts +118 -31
- package/dist/variants.d.ts +1 -1
- package/dist/variants.js +1 -1
- package/package.json +7 -4
- package/dist/config/ai.d.ts +0 -13
- package/dist/config/ai.js +0 -44
- package/dist/elements/empty-state/EmptyStateTestWrapper.svelte +0 -25
- package/dist/elements/empty-state/EmptyStateTestWrapper.svelte.d.ts +0 -8
- package/dist/elements/tooltip/TooltipTestWrapper.svelte +0 -14
- package/dist/elements/tooltip/TooltipTestWrapper.svelte.d.ts +0 -7
- package/dist/helper/deprecation.d.ts +0 -14
- package/dist/helper/deprecation.js +0 -24
- package/dist/modal/ModalFooterTestWrapper.svelte +0 -17
- package/dist/modal/ModalFooterTestWrapper.svelte.d.ts +0 -8
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
function isDateRange(v) {
|
|
2
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Sync a reactive `selections` object to/from URL query params.
|
|
6
|
+
*
|
|
7
|
+
* - On first call: reads current URL params into the setter.
|
|
8
|
+
* - On every subsequent change to `getter()`: writes back to the URL.
|
|
9
|
+
*
|
|
10
|
+
* Designed for use inside a Svelte 5 component with `$effect` semantics — call
|
|
11
|
+
* it at the top level of `<script>` and it will register its own `$effect`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```svelte
|
|
15
|
+
* <script>
|
|
16
|
+
* import { CompactFilters, syncFiltersToUrl } from '@makolabs/ripple';
|
|
17
|
+
* let selections = $state({ status: 'all' });
|
|
18
|
+
* syncFiltersToUrl(() => selections, (v) => (selections = v));
|
|
19
|
+
* </script>
|
|
20
|
+
*
|
|
21
|
+
* <CompactFilters {filterGroups} bind:selections />
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Environment:
|
|
25
|
+
* - Uses `window.location` and `history.replaceState` — no-ops on the server.
|
|
26
|
+
* - Multi-select values (`string[]`) are serialized as comma-joined strings.
|
|
27
|
+
* - Removes a key from the URL when its value is empty / empty array.
|
|
28
|
+
* - Leaves unrelated URL params untouched.
|
|
29
|
+
*/
|
|
30
|
+
export function syncFiltersToUrl(getter, setter, options = {}) {
|
|
31
|
+
if (typeof window === 'undefined')
|
|
32
|
+
return;
|
|
33
|
+
const { keys, debounceMs = 150, arrayKeys = [], dateRangeKeys = [] } = options;
|
|
34
|
+
// Initial read from URL — plain imperative parse, no reactivity needed.
|
|
35
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
36
|
+
const params = new URLSearchParams(window.location.search);
|
|
37
|
+
const initial = {};
|
|
38
|
+
const managedKeys = keys ?? Object.keys(getter());
|
|
39
|
+
let hasAny = false;
|
|
40
|
+
for (const key of managedKeys) {
|
|
41
|
+
if (dateRangeKeys.includes(key)) {
|
|
42
|
+
const from = params.get(`${key}_from`);
|
|
43
|
+
const to = params.get(`${key}_to`);
|
|
44
|
+
if (from && to) {
|
|
45
|
+
const preset = params.get(`${key}_preset`) ?? undefined;
|
|
46
|
+
initial[key] = preset ? { from, to, preset } : { from, to };
|
|
47
|
+
hasAny = true;
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const raw = params.get(key);
|
|
52
|
+
if (raw !== null) {
|
|
53
|
+
initial[key] = arrayKeys.includes(key) ? raw.split(',').filter(Boolean) : raw;
|
|
54
|
+
hasAny = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (hasAny) {
|
|
58
|
+
setter({ ...getter(), ...initial });
|
|
59
|
+
}
|
|
60
|
+
// Write on change
|
|
61
|
+
let timer;
|
|
62
|
+
$effect(() => {
|
|
63
|
+
const current = getter();
|
|
64
|
+
// Touch each managed value to register reactive deps
|
|
65
|
+
for (const key of keys ?? Object.keys(current)) {
|
|
66
|
+
void current[key];
|
|
67
|
+
}
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
timer = setTimeout(() => {
|
|
70
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity
|
|
71
|
+
const url = new URL(window.location.href);
|
|
72
|
+
const managed = keys ?? Object.keys(current);
|
|
73
|
+
for (const key of managed) {
|
|
74
|
+
const value = current[key];
|
|
75
|
+
if (dateRangeKeys.includes(key)) {
|
|
76
|
+
url.searchParams.delete(`${key}_from`);
|
|
77
|
+
url.searchParams.delete(`${key}_to`);
|
|
78
|
+
url.searchParams.delete(`${key}_preset`);
|
|
79
|
+
if (isDateRange(value)) {
|
|
80
|
+
url.searchParams.set(`${key}_from`, value.from);
|
|
81
|
+
url.searchParams.set(`${key}_to`, value.to);
|
|
82
|
+
if (value.preset)
|
|
83
|
+
url.searchParams.set(`${key}_preset`, value.preset);
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (value === undefined ||
|
|
88
|
+
value === null ||
|
|
89
|
+
value === '' ||
|
|
90
|
+
(Array.isArray(value) && value.length === 0)) {
|
|
91
|
+
url.searchParams.delete(key);
|
|
92
|
+
}
|
|
93
|
+
else if (Array.isArray(value)) {
|
|
94
|
+
url.searchParams.set(key, value.join(','));
|
|
95
|
+
}
|
|
96
|
+
else if (typeof value === 'string') {
|
|
97
|
+
url.searchParams.set(key, value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const next = url.pathname + (url.search ? url.search : '') + url.hash;
|
|
101
|
+
const currentPath = window.location.pathname + window.location.search + window.location.hash;
|
|
102
|
+
if (next !== currentPath) {
|
|
103
|
+
// Use SvelteKit's replaceState so the router stays in sync. Fall
|
|
104
|
+
// back to history.replaceState for non-SvelteKit consumers.
|
|
105
|
+
import('$app/navigation')
|
|
106
|
+
.then(({ replaceState }) => replaceState(next, {}))
|
|
107
|
+
.catch(() => {
|
|
108
|
+
window.history.replaceState({}, '', next);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}, debounceMs);
|
|
112
|
+
return () => clearTimeout(timer);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
|
-
import
|
|
4
|
+
import { Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
6
|
+
import type { CheckboxProps, VariantSizes } from '../index.js';
|
|
5
7
|
|
|
6
8
|
let {
|
|
7
9
|
name,
|
|
@@ -10,21 +12,34 @@
|
|
|
10
12
|
disabled = false,
|
|
11
13
|
errors = [],
|
|
12
14
|
required = false,
|
|
15
|
+
size = Size.MD,
|
|
13
16
|
testId
|
|
14
17
|
}: CheckboxProps = $props();
|
|
15
18
|
|
|
19
|
+
// Checkbox box dimension uses Tailwind's `size-*` shorthand, scaling
|
|
20
|
+
// from size-3 (XS) up to size-7 (XXL) so it stays visually proportional
|
|
21
|
+
// to adjacent form controls at the same size.
|
|
22
|
+
const boxSize: Record<VariantSizes, string> = {
|
|
23
|
+
[Size.XS]: 'size-3',
|
|
24
|
+
[Size.SM]: 'size-3.5',
|
|
25
|
+
[Size.MD]: 'size-4',
|
|
26
|
+
[Size.LG]: 'size-5',
|
|
27
|
+
[Size.XL]: 'size-6',
|
|
28
|
+
[Size.XXL]: 'size-7'
|
|
29
|
+
};
|
|
30
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
31
|
+
|
|
16
32
|
const checkboxClass = $derived(
|
|
17
|
-
cn(
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
cn(
|
|
34
|
+
'rounded text-primary-600 border-default-300 focus:ring-primary-500',
|
|
35
|
+
boxSize[size],
|
|
36
|
+
disabled && 'opacity-50 cursor-not-allowed',
|
|
37
|
+
errors.length && 'accent-danger-500'
|
|
38
|
+
)
|
|
21
39
|
);
|
|
22
40
|
|
|
23
41
|
const labelClass = $derived(
|
|
24
|
-
cn('
|
|
25
|
-
'text-default-700': !errors.length,
|
|
26
|
-
'text-danger-600': errors.length
|
|
27
|
-
})
|
|
42
|
+
cn('font-medium', tokens.text, errors.length ? 'text-danger-600' : 'text-default-700')
|
|
28
43
|
);
|
|
29
44
|
</script>
|
|
30
45
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
|
+
import { Size } from '../variants.js';
|
|
4
|
+
import { formSizeTokens } from './form-size.js';
|
|
3
5
|
import type { DateRangeProps } from '../index.js';
|
|
4
6
|
import Portal from '../utils/Portal.svelte';
|
|
7
|
+
import { fly } from 'svelte/transition';
|
|
8
|
+
import { quintOut } from 'svelte/easing';
|
|
5
9
|
|
|
6
10
|
let {
|
|
7
11
|
startDate = $bindable(),
|
|
@@ -15,11 +19,14 @@
|
|
|
15
19
|
endLabel = 'End date',
|
|
16
20
|
format = 'MM/dd/yyyy',
|
|
17
21
|
errors = [],
|
|
22
|
+
size = Size.MD,
|
|
18
23
|
id,
|
|
19
24
|
name,
|
|
20
25
|
onselect
|
|
21
26
|
}: DateRangeProps = $props();
|
|
22
27
|
|
|
28
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
29
|
+
|
|
23
30
|
let isOpen = $state(false);
|
|
24
31
|
let hoveredDate = $state<Date | null>(null);
|
|
25
32
|
let datePickerRef = $state<HTMLDivElement | null>(null);
|
|
@@ -237,7 +244,12 @@
|
|
|
237
244
|
{id}
|
|
238
245
|
type="button"
|
|
239
246
|
class={cn(
|
|
240
|
-
'border-default-300 flex w-full items-center justify-between
|
|
247
|
+
'border-default-300 flex w-full items-center justify-between border bg-white',
|
|
248
|
+
tokens.height,
|
|
249
|
+
tokens.padX,
|
|
250
|
+
tokens.text,
|
|
251
|
+
tokens.radius,
|
|
252
|
+
tokens.shadow,
|
|
241
253
|
disabled
|
|
242
254
|
? 'bg-default-100 text-default-400 cursor-not-allowed opacity-50'
|
|
243
255
|
: errors?.length
|
|
@@ -247,7 +259,6 @@
|
|
|
247
259
|
onclick={toggleDatepicker}
|
|
248
260
|
aria-haspopup="true"
|
|
249
261
|
aria-expanded={isOpen}
|
|
250
|
-
aria-invalid={errors?.length ? 'true' : undefined}
|
|
251
262
|
aria-describedby={errors?.length ? `${id}-errors` : undefined}
|
|
252
263
|
{disabled}
|
|
253
264
|
>
|
|
@@ -258,7 +269,7 @@
|
|
|
258
269
|
? `${formatDate(startDate)} - Select end date`
|
|
259
270
|
: placeholder}
|
|
260
271
|
</span>
|
|
261
|
-
<svg class=
|
|
272
|
+
<svg class={cn('text-default-400', tokens.iconSize)} viewBox="0 0 20 20" fill="currentColor">
|
|
262
273
|
<path
|
|
263
274
|
fill-rule="evenodd"
|
|
264
275
|
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
|
|
@@ -270,11 +281,16 @@
|
|
|
270
281
|
{#if startDate || endDate}
|
|
271
282
|
<button
|
|
272
283
|
type="button"
|
|
273
|
-
class=
|
|
284
|
+
class={cn(
|
|
285
|
+
'text-default-400 hover:text-default-500 absolute top-1/2 -translate-y-1/2',
|
|
286
|
+
// Sit just left of the calendar icon; use the token gap
|
|
287
|
+
// so the clear button stays visually tied to the icon.
|
|
288
|
+
'right-8'
|
|
289
|
+
)}
|
|
274
290
|
onclick={clearDates}
|
|
275
291
|
aria-label="Clear dates"
|
|
276
292
|
>
|
|
277
|
-
<svg class=
|
|
293
|
+
<svg class={cn(tokens.iconSize)} viewBox="0 0 20 20" fill="currentColor">
|
|
278
294
|
<path
|
|
279
295
|
fill-rule="evenodd"
|
|
280
296
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
@@ -298,6 +314,7 @@
|
|
|
298
314
|
<div
|
|
299
315
|
bind:this={calendarRef}
|
|
300
316
|
class="ring-opacity-5 ring-default-300 absolute z-10 mt-1 w-full origin-top-left rounded-md bg-white p-4 shadow-lg ring-1 focus:outline-none"
|
|
317
|
+
transition:fly={{ y: -8, duration: 300, easing: quintOut }}
|
|
301
318
|
>
|
|
302
319
|
<div class="mb-2 flex items-center justify-between">
|
|
303
320
|
{#if viewMode === 'days'}
|
|
@@ -488,7 +505,7 @@
|
|
|
488
505
|
|
|
489
506
|
{#if startDate || endDate}
|
|
490
507
|
<div
|
|
491
|
-
class="border-default-200 text-default-500 mt-4 flex justify-between border-t pt-3 text-xs"
|
|
508
|
+
class="border-default-200 text-default-500 mt-4 flex flex-wrap justify-between gap-x-4 gap-y-1 border-t pt-3 text-xs"
|
|
492
509
|
>
|
|
493
510
|
<div>
|
|
494
511
|
{startDate ? `${startLabel}: ${formatDate(startDate)}` : ''}
|
package/dist/forms/Input.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
4
|
import { Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { InputProps } from '../index.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
placeholder,
|
|
13
14
|
disabled = false,
|
|
14
15
|
class: className = '',
|
|
15
|
-
size = Size.
|
|
16
|
+
size = Size.MD,
|
|
16
17
|
value = $bindable(),
|
|
17
18
|
errors = [],
|
|
18
19
|
testId,
|
|
@@ -20,26 +21,25 @@
|
|
|
20
21
|
}: InputProps = $props();
|
|
21
22
|
|
|
22
23
|
const BASIC_TYPES = ['text', 'email', 'password', 'number', 'tel', 'url', 'date', 'textarea'];
|
|
24
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
23
25
|
const inputClasses = $derived(
|
|
24
26
|
cn(
|
|
25
|
-
'transition-colors placeholder:text-default-400',
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'border-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
!errors.length
|
|
42
|
-
},
|
|
27
|
+
'w-full bg-white transition-colors placeholder:text-default-400',
|
|
28
|
+
tokens.padX,
|
|
29
|
+
tokens.text,
|
|
30
|
+
// All basic types (including textarea) get the bordered look.
|
|
31
|
+
BASIC_TYPES.includes(type) && [
|
|
32
|
+
'border focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2',
|
|
33
|
+
tokens.radius,
|
|
34
|
+
tokens.shadow
|
|
35
|
+
],
|
|
36
|
+
// Single-line types get a fixed height; textarea is content-driven.
|
|
37
|
+
type !== 'textarea' && tokens.height,
|
|
38
|
+
type === 'textarea' && ['resize-y min-h-[100px]', tokens.padY],
|
|
39
|
+
errors.length
|
|
40
|
+
? 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500'
|
|
41
|
+
: 'border-default-300 focus-within:border-primary-500 focus-within:ring-primary-500',
|
|
42
|
+
disabled && 'opacity-50 cursor-not-allowed',
|
|
43
43
|
className
|
|
44
44
|
)
|
|
45
45
|
);
|
|
@@ -15,24 +15,29 @@
|
|
|
15
15
|
showFlags = true,
|
|
16
16
|
label = '',
|
|
17
17
|
appearance = 'surface',
|
|
18
|
-
orientation = '
|
|
18
|
+
orientation = 'auto',
|
|
19
19
|
labelLayout = 'inline',
|
|
20
20
|
labelClass = undefined,
|
|
21
21
|
color = Color.PRIMARY,
|
|
22
|
-
size = Size.
|
|
22
|
+
size = Size.MD,
|
|
23
23
|
compact = false,
|
|
24
|
+
flagsOnly = false,
|
|
24
25
|
disabled = false,
|
|
25
26
|
class: className = '',
|
|
26
27
|
onchange = undefined,
|
|
27
28
|
testId
|
|
28
29
|
}: MarketSelectorProps = $props();
|
|
29
30
|
|
|
31
|
+
// flagsOnly implies showFlags + compact (which sr-onlys the label)
|
|
32
|
+
const effectiveShowFlags = $derived(showFlags || flagsOnly);
|
|
33
|
+
const effectiveCompact = $derived(compact || flagsOnly);
|
|
34
|
+
|
|
30
35
|
const options = $derived.by((): SegmentedOption[] => {
|
|
31
36
|
return markets.map((code) => ({
|
|
32
37
|
value: code,
|
|
33
38
|
label: code,
|
|
34
39
|
title: COUNTRY_NAMES[code],
|
|
35
|
-
prefix:
|
|
40
|
+
prefix: effectiveShowFlags ? countryCodeToFlagEmoji(code) : undefined
|
|
36
41
|
}));
|
|
37
42
|
});
|
|
38
43
|
|
|
@@ -63,7 +68,7 @@
|
|
|
63
68
|
{labelClass}
|
|
64
69
|
{color}
|
|
65
70
|
{size}
|
|
66
|
-
{
|
|
71
|
+
compact={effectiveCompact}
|
|
67
72
|
{disabled}
|
|
68
73
|
{testId}
|
|
69
74
|
onchange={handleChange}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
4
|
import { Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { NumberInputProps } from '../index.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -10,12 +11,12 @@
|
|
|
10
11
|
name,
|
|
11
12
|
label,
|
|
12
13
|
placeholder = 'Enter a number',
|
|
13
|
-
size = Size.
|
|
14
|
+
size = Size.MD,
|
|
14
15
|
class: className = '',
|
|
15
16
|
units = [],
|
|
16
17
|
errors,
|
|
17
18
|
disabled = false,
|
|
18
|
-
|
|
19
|
+
dropdownIcon: DropdownIcon,
|
|
19
20
|
onunitchange: onUnitChange,
|
|
20
21
|
testId,
|
|
21
22
|
...restProps
|
|
@@ -26,18 +27,16 @@
|
|
|
26
27
|
|
|
27
28
|
const selectedOption = $derived(units.find((u) => u.value === unit));
|
|
28
29
|
|
|
30
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
31
|
+
|
|
29
32
|
const containerClass = $derived(
|
|
30
33
|
cn(
|
|
31
|
-
'relative flex items-center gap-1
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'h-8': size === Size.SM,
|
|
38
|
-
'h-10': size === Size.BASE,
|
|
39
|
-
'h-12': size === Size.LG
|
|
40
|
-
},
|
|
34
|
+
'relative flex items-center gap-1 border bg-white',
|
|
35
|
+
tokens.radius,
|
|
36
|
+
tokens.shadow,
|
|
37
|
+
tokens.height,
|
|
38
|
+
errors?.length && 'border-danger-300',
|
|
39
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
41
40
|
'border-default-300 focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',
|
|
42
41
|
className
|
|
43
42
|
)
|
|
@@ -45,12 +44,9 @@
|
|
|
45
44
|
|
|
46
45
|
const inputClass = $derived(
|
|
47
46
|
cn(
|
|
48
|
-
'w-full bg-transparent outline-none disabled:cursor-not-allowed
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'text-base': size === Size.BASE,
|
|
52
|
-
'text-lg': size === Size.LG
|
|
53
|
-
}
|
|
47
|
+
'w-full bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-default-400',
|
|
48
|
+
tokens.padX,
|
|
49
|
+
tokens.text
|
|
54
50
|
)
|
|
55
51
|
);
|
|
56
52
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../helper/testid.js';
|
|
4
|
+
import { Color, Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
6
|
+
import type { RadioGroupProps } from '../index.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
name,
|
|
10
|
+
label,
|
|
11
|
+
options,
|
|
12
|
+
value = $bindable<string | undefined>(undefined),
|
|
13
|
+
disabled = false,
|
|
14
|
+
required = false,
|
|
15
|
+
orientation = 'vertical',
|
|
16
|
+
size = Size.MD,
|
|
17
|
+
color = Color.PRIMARY,
|
|
18
|
+
errors = [],
|
|
19
|
+
class: className = '',
|
|
20
|
+
onchange,
|
|
21
|
+
testId
|
|
22
|
+
}: RadioGroupProps = $props();
|
|
23
|
+
|
|
24
|
+
const hasErrors = $derived(errors.length > 0);
|
|
25
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
26
|
+
|
|
27
|
+
// Radio circle scales with the form ladder — `size-3` at xs through
|
|
28
|
+
// `size-6` at xl. 2xl aliases xl.
|
|
29
|
+
const dotSize = $derived(
|
|
30
|
+
{
|
|
31
|
+
[Size.XS]: 'size-3',
|
|
32
|
+
[Size.SM]: 'size-3.5',
|
|
33
|
+
[Size.MD]: 'size-4',
|
|
34
|
+
[Size.LG]: 'size-5',
|
|
35
|
+
[Size.XL]: 'size-6',
|
|
36
|
+
[Size.XXL]: 'size-6'
|
|
37
|
+
}[size]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const accentBg = $derived(
|
|
41
|
+
{
|
|
42
|
+
[Color.DEFAULT]: 'bg-default-600',
|
|
43
|
+
[Color.PRIMARY]: 'bg-primary-500',
|
|
44
|
+
[Color.SECONDARY]: 'bg-secondary-500',
|
|
45
|
+
[Color.INFO]: 'bg-info-500',
|
|
46
|
+
[Color.SUCCESS]: 'bg-success-500',
|
|
47
|
+
[Color.WARNING]: 'bg-warning-500',
|
|
48
|
+
[Color.DANGER]: 'bg-danger-500'
|
|
49
|
+
}[color]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
function handlePick(next: string, optionDisabled: boolean) {
|
|
53
|
+
if (disabled || optionDisabled) return;
|
|
54
|
+
value = next;
|
|
55
|
+
onchange?.(next);
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<fieldset
|
|
60
|
+
class={cn('w-full', className)}
|
|
61
|
+
{disabled}
|
|
62
|
+
aria-invalid={hasErrors}
|
|
63
|
+
data-testid={buildTestId('radio-group', undefined, testId)}
|
|
64
|
+
>
|
|
65
|
+
{#if label}
|
|
66
|
+
<legend class="text-default-700 mb-2 block text-sm font-medium">
|
|
67
|
+
{label}
|
|
68
|
+
{#if required}<span class="text-danger-500" aria-hidden="true">*</span>{/if}
|
|
69
|
+
</legend>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
72
|
+
<div
|
|
73
|
+
class={cn('flex gap-3', orientation === 'vertical' ? 'flex-col' : 'flex-wrap items-center')}
|
|
74
|
+
role="radiogroup"
|
|
75
|
+
aria-label={label}
|
|
76
|
+
>
|
|
77
|
+
{#each options as option (option.value)}
|
|
78
|
+
{@const checked = value === option.value}
|
|
79
|
+
{@const optionDisabled = disabled || option.disabled === true}
|
|
80
|
+
<label
|
|
81
|
+
class={cn(
|
|
82
|
+
'group inline-flex cursor-pointer items-start gap-2 select-none',
|
|
83
|
+
optionDisabled && 'cursor-not-allowed opacity-60'
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<input
|
|
87
|
+
type="radio"
|
|
88
|
+
{name}
|
|
89
|
+
value={option.value}
|
|
90
|
+
{checked}
|
|
91
|
+
disabled={optionDisabled}
|
|
92
|
+
required={required && !value}
|
|
93
|
+
onchange={() => handlePick(option.value, optionDisabled)}
|
|
94
|
+
class="sr-only"
|
|
95
|
+
data-testid={buildTestId('radio-group', option.value, testId)}
|
|
96
|
+
/>
|
|
97
|
+
<span
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
class={cn(
|
|
100
|
+
'relative mt-0.5 flex shrink-0 items-center justify-center rounded-full border transition-colors',
|
|
101
|
+
dotSize,
|
|
102
|
+
checked ? `${accentBg} border-transparent` : 'border-default-300 bg-white',
|
|
103
|
+
hasErrors && !checked && 'border-danger-300'
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{#if checked}
|
|
107
|
+
<span class="size-1/3 rounded-full bg-white"></span>
|
|
108
|
+
{/if}
|
|
109
|
+
</span>
|
|
110
|
+
<span class="flex flex-col">
|
|
111
|
+
<span class={cn('text-default-800', tokens.text)}>{option.label}</span>
|
|
112
|
+
{#if option.description}
|
|
113
|
+
<span class="text-default-500 text-xs">{option.description}</span>
|
|
114
|
+
{/if}
|
|
115
|
+
</span>
|
|
116
|
+
</label>
|
|
117
|
+
{/each}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{#if hasErrors}
|
|
121
|
+
<ul class="mt-2 space-y-0.5" role="alert">
|
|
122
|
+
{#each errors as error (error)}
|
|
123
|
+
<li class="text-danger-600 text-xs">{error}</li>
|
|
124
|
+
{/each}
|
|
125
|
+
</ul>
|
|
126
|
+
{/if}
|
|
127
|
+
</fieldset>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
appearance = 'surface',
|
|
18
18
|
orientation = 'horizontal',
|
|
19
19
|
color = Color.PRIMARY,
|
|
20
|
-
size = Size.
|
|
20
|
+
size = Size.MD,
|
|
21
21
|
compact = false,
|
|
22
22
|
labelLayout = 'above',
|
|
23
23
|
labelClass = '',
|
|
@@ -41,8 +41,11 @@
|
|
|
41
41
|
|
|
42
42
|
const rootClass = $derived(
|
|
43
43
|
cn(
|
|
44
|
-
'w-fit',
|
|
44
|
+
orientation === 'auto' ? '@container w-full' : 'w-fit',
|
|
45
45
|
labelLayout === 'inline' ? 'flex flex-row items-center gap-2' : 'flex flex-col gap-2',
|
|
46
|
+
orientation === 'auto' &&
|
|
47
|
+
labelLayout === 'inline' &&
|
|
48
|
+
'@max-[250px]:flex-col @max-[250px]:items-stretch',
|
|
46
49
|
className
|
|
47
50
|
)
|
|
48
51
|
);
|
|
@@ -84,8 +87,12 @@
|
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
function handleSegmentKeydown(e: KeyboardEvent, index: number) {
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
// `'auto'` defaults to horizontal layout (flex-row) and only flips
|
|
91
|
+
// to vertical via a container query below 250px — treat it as
|
|
92
|
+
// horizontal for keyboard nav since that matches the default render.
|
|
93
|
+
const isHorizontal = orientation === 'horizontal' || orientation === 'auto';
|
|
94
|
+
const forward = isHorizontal ? e.key === 'ArrowRight' : e.key === 'ArrowDown';
|
|
95
|
+
const backward = isHorizontal ? e.key === 'ArrowLeft' : e.key === 'ArrowUp';
|
|
89
96
|
if (forward) {
|
|
90
97
|
e.preventDefault();
|
|
91
98
|
moveSelection(index, 1);
|