@makolabs/ripple 3.0.0 → 3.0.2
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/dist/elements/accordion/Accordion.svelte +1 -1
- package/dist/elements/combobox/ComboBox.svelte +59 -29
- package/dist/elements/dropdown/Select.svelte +98 -62
- package/dist/elements/dropdown/select.d.ts +3 -108
- package/dist/elements/dropdown/select.js +37 -46
- package/dist/elements/popover/Popover.svelte +59 -36
- package/dist/filters/CompactFilters.svelte +1 -1
- package/dist/forms/Checkbox.svelte +24 -9
- package/dist/forms/DateRange.svelte +236 -204
- package/dist/forms/Input.svelte +18 -18
- package/dist/forms/MarketSelector.svelte +1 -1
- package/dist/forms/NumberInput.svelte +160 -55
- package/dist/forms/RadioGroup.svelte +6 -2
- package/dist/forms/SegmentedControl.svelte +1 -1
- package/dist/forms/Tags.svelte +32 -11
- package/dist/forms/Textarea.svelte +8 -13
- package/dist/forms/Toggle.svelte +22 -14
- package/dist/forms/calendar/Calendar.svelte +107 -8
- package/dist/forms/calendar/calendar-types.d.ts +8 -0
- package/dist/forms/date-picker/DatePicker.svelte +11 -14
- package/dist/forms/form-size.d.ts +37 -0
- package/dist/forms/form-size.js +67 -0
- package/dist/forms/form-types.d.ts +33 -0
- package/dist/forms/month-picker/MonthPicker.svelte +299 -0
- package/dist/forms/month-picker/MonthPicker.svelte.d.ts +4 -0
- package/dist/forms/month-picker/month-picker-types.d.ts +22 -0
- package/dist/forms/month-picker/month-picker-types.js +1 -0
- package/dist/forms/segmented-control.d.ts +2 -2
- package/dist/forms/segmented-control.js +18 -15
- package/dist/forms/slider.js +35 -28
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/layout/activity-list/ActivityList.svelte +1 -1
- package/package.json +1 -1
|
@@ -1,6 +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 { fly } from 'svelte/transition';
|
|
5
|
+
import { quintOut } from 'svelte/easing';
|
|
6
|
+
import { onMount } from 'svelte';
|
|
4
7
|
import type { PopoverProps, PopoverPlacement } from './popover-types.js';
|
|
5
8
|
|
|
6
9
|
let {
|
|
@@ -25,13 +28,25 @@
|
|
|
25
28
|
let showTimer: ReturnType<typeof setTimeout> | undefined;
|
|
26
29
|
let hideTimer: ReturnType<typeof setTimeout> | undefined;
|
|
27
30
|
|
|
28
|
-
// Panel position in viewport coordinates — updated on open and on
|
|
29
|
-
// scroll/resize so the panel tracks the trigger. Kept reactive so the
|
|
30
|
-
// style attribute re-renders when these change.
|
|
31
31
|
let panelTop = $state(0);
|
|
32
32
|
let panelLeft = $state(0);
|
|
33
33
|
let panelTransform = $state('');
|
|
34
34
|
|
|
35
|
+
// Mobile detection — below `sm` (640px), click/manual popovers
|
|
36
|
+
// render as a bottom sheet instead of a positioned dropdown.
|
|
37
|
+
// Hover popovers are skipped on mobile (no hover).
|
|
38
|
+
let isMobile = $state(
|
|
39
|
+
typeof window !== 'undefined' && window.matchMedia('(max-width: 639.98px)').matches
|
|
40
|
+
);
|
|
41
|
+
onMount(() => {
|
|
42
|
+
const mql = window.matchMedia('(max-width: 639.98px)');
|
|
43
|
+
const handler = (e: MediaQueryListEvent) => (isMobile = e.matches);
|
|
44
|
+
mql.addEventListener('change', handler);
|
|
45
|
+
return () => mql.removeEventListener('change', handler);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const useSheet = $derived(isMobile && trigger !== 'hover');
|
|
49
|
+
|
|
35
50
|
function show() {
|
|
36
51
|
if (disabled) return;
|
|
37
52
|
clearTimeout(showTimer);
|
|
@@ -65,7 +80,6 @@
|
|
|
65
80
|
open = false;
|
|
66
81
|
}
|
|
67
82
|
|
|
68
|
-
// Clean up any in-flight timers on unmount.
|
|
69
83
|
$effect(() => {
|
|
70
84
|
return () => {
|
|
71
85
|
clearTimeout(showTimer);
|
|
@@ -79,23 +93,12 @@
|
|
|
79
93
|
|
|
80
94
|
function handleWindowClick(e: MouseEvent) {
|
|
81
95
|
if (!closeOnOutsideClick || !open) return;
|
|
96
|
+
if (useSheet) return;
|
|
82
97
|
const target = e.target as Node;
|
|
83
|
-
// The panel lives outside the trigger wrapper in the DOM, so check both.
|
|
84
98
|
if (wrapper?.contains(target) || panelEl?.contains(target)) return;
|
|
85
99
|
close();
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
/**
|
|
89
|
-
* Compute panel position from the trigger's bounding rect. Uses
|
|
90
|
-
* `position: fixed` + a very high z-index so the panel sits above
|
|
91
|
-
* sibling content (Storybook sidebars, sticky headers, etc.) instead
|
|
92
|
-
* of being clipped / layered under it.
|
|
93
|
-
*
|
|
94
|
-
* The computed position is then clamped to the viewport with an 8px
|
|
95
|
-
* gutter — this keeps panels on-screen near viewport edges on narrow
|
|
96
|
-
* devices. We measure the panel's own rect so the clamp accounts for
|
|
97
|
-
* the `translate(-50%, 0)` etc. transforms that re-anchor the panel.
|
|
98
|
-
*/
|
|
99
102
|
function updatePosition() {
|
|
100
103
|
if (!wrapper || !panelEl) return;
|
|
101
104
|
const r = wrapper.getBoundingClientRect();
|
|
@@ -124,10 +127,6 @@
|
|
|
124
127
|
break;
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
// Clamp to viewport. We re-measure the panel after applying the
|
|
128
|
-
// transform (next frame) because its true on-screen rect depends
|
|
129
|
-
// on `panelTransform`. Adjust `panelLeft`/`panelTop` so the rect
|
|
130
|
-
// ends up inside [VIEWPORT_GUTTER, viewport - VIEWPORT_GUTTER].
|
|
131
130
|
requestAnimationFrame(() => {
|
|
132
131
|
if (!panelEl) return;
|
|
133
132
|
const pr = panelEl.getBoundingClientRect();
|
|
@@ -144,13 +143,8 @@
|
|
|
144
143
|
});
|
|
145
144
|
}
|
|
146
145
|
|
|
147
|
-
/**
|
|
148
|
-
* Re-measure on every open, and while open, on scroll/resize so the
|
|
149
|
-
* panel follows its trigger when the page scrolls.
|
|
150
|
-
*/
|
|
151
146
|
$effect(() => {
|
|
152
|
-
if (!open) return;
|
|
153
|
-
// Initial placement (defer one frame so panelEl has mounted).
|
|
147
|
+
if (!open || useSheet) return;
|
|
154
148
|
requestAnimationFrame(updatePosition);
|
|
155
149
|
|
|
156
150
|
const handler = () => updatePosition();
|
|
@@ -162,6 +156,17 @@
|
|
|
162
156
|
};
|
|
163
157
|
});
|
|
164
158
|
|
|
159
|
+
$effect(() => {
|
|
160
|
+
if (!panelEl) return;
|
|
161
|
+
const el = panelEl;
|
|
162
|
+
document.body.appendChild(el);
|
|
163
|
+
return () => {
|
|
164
|
+
if (el.parentNode === document.body) {
|
|
165
|
+
document.body.removeChild(el);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
|
|
165
170
|
const arrowClass = $derived(
|
|
166
171
|
{
|
|
167
172
|
top: 'top-full left-1/2 -translate-x-1/2 border-t-white border-l-transparent border-r-transparent border-b-transparent',
|
|
@@ -182,14 +187,6 @@
|
|
|
182
187
|
data-testid={buildTestId('popover', undefined, testId)}
|
|
183
188
|
>
|
|
184
189
|
{#if trigger === 'click'}
|
|
185
|
-
<!--
|
|
186
|
-
Click-mode: forward clicks to toggle. Do NOT add role/tabindex here
|
|
187
|
-
— consumers pass an interactive child (Button, <a>, etc.) that
|
|
188
|
-
handles its own focus/keyboard; adding them here would nest
|
|
189
|
-
interactive elements (invalid HTML + a11y issue). Keyboard support
|
|
190
|
-
comes via Enter/Space on the inner button dispatching a click,
|
|
191
|
-
which bubbles to this handler.
|
|
192
|
-
-->
|
|
193
190
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
194
191
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
195
192
|
<span class="inline-flex" aria-haspopup="dialog" aria-expanded={open} onclick={toggle}>
|
|
@@ -207,12 +204,12 @@
|
|
|
207
204
|
{@render children()}
|
|
208
205
|
</span>
|
|
209
206
|
{:else}
|
|
210
|
-
<!-- manual — consumer drives `open` -->
|
|
211
207
|
<span class="inline-flex">{@render children()}</span>
|
|
212
208
|
{/if}
|
|
213
209
|
</span>
|
|
214
210
|
|
|
215
|
-
|
|
211
|
+
<!-- Desktop: positioned panel -->
|
|
212
|
+
{#if open && !useSheet}
|
|
216
213
|
<div
|
|
217
214
|
bind:this={panelEl}
|
|
218
215
|
role="dialog"
|
|
@@ -232,3 +229,29 @@
|
|
|
232
229
|
{@render content({ close })}
|
|
233
230
|
</div>
|
|
234
231
|
{/if}
|
|
232
|
+
|
|
233
|
+
<!-- Mobile: bottom sheet -->
|
|
234
|
+
{#if open && useSheet}
|
|
235
|
+
<button
|
|
236
|
+
type="button"
|
|
237
|
+
class="fixed inset-0 z-[9998] bg-black/40 backdrop-blur-sm"
|
|
238
|
+
aria-label="Close"
|
|
239
|
+
onclick={close}
|
|
240
|
+
></button>
|
|
241
|
+
<div
|
|
242
|
+
role="dialog"
|
|
243
|
+
tabindex="-1"
|
|
244
|
+
class="fixed inset-x-0 bottom-0 z-[9999] flex max-h-[85vh] min-h-48 flex-col overflow-hidden rounded-t-2xl bg-white shadow-2xl"
|
|
245
|
+
transition:fly={{ y: 300, duration: 200, easing: quintOut }}
|
|
246
|
+
data-testid={buildTestId('popover', 'sheet', testId)}
|
|
247
|
+
>
|
|
248
|
+
<!-- Drag handle -->
|
|
249
|
+
<div class="flex cursor-pointer justify-center py-2">
|
|
250
|
+
<div class="bg-default-300 h-1 w-8 rounded-full"></div>
|
|
251
|
+
</div>
|
|
252
|
+
<!-- Content -->
|
|
253
|
+
<div class="flex-1 cursor-pointer overflow-y-auto p-2">
|
|
254
|
+
{@render content({ close })}
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
{/if}
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
type="button"
|
|
315
315
|
onclick={() => handleSelect(group, tab.value)}
|
|
316
316
|
class={cn(
|
|
317
|
-
'rounded-full border px-3 py-1 text-xs font-medium whitespace-nowrap',
|
|
317
|
+
'cursor-pointer rounded-full border px-3 py-1 text-xs font-medium whitespace-nowrap',
|
|
318
318
|
active
|
|
319
319
|
? 'bg-primary-50 text-primary-700 border-primary-200'
|
|
320
320
|
: 'border-default-200 text-default-700 hover:bg-default-50'
|
|
@@ -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
|
|