@insymetri/styleguide 0.1.77 → 0.1.79
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/IIDateInput/IIDateInput.svelte +20 -18
- package/dist/IIDateInput/IIDateInputClippingStories.svelte +26 -0
- package/dist/IIDateInput/IIDateInputClippingStories.svelte.d.ts +3 -0
- package/dist/IIDropdownInput/IIDropdownInput.svelte +26 -2
- package/dist/IIModal/IIModal.svelte +19 -7
- package/dist/IIModal/IIModal.svelte.d.ts +6 -1
- package/dist/IIModal/IIModalFocusStories.svelte +12 -11
- package/dist/style/tailwind/animations.d.ts +3 -3
- package/dist/style/tailwind/animations.js +18 -7
- package/dist/style/tailwind/preset.d.ts +3 -3
- package/dist/utils/menu/MenuSearchInput.svelte +1 -1
- package/dist/utils/menu/use-floating.svelte.d.ts +4 -0
- package/dist/utils/menu/use-floating.svelte.js +11 -5
- package/package.json +1 -1
|
@@ -117,25 +117,27 @@
|
|
|
117
117
|
</DatePicker.Trigger>
|
|
118
118
|
{/snippet}
|
|
119
119
|
</DatePicker.Input>
|
|
120
|
-
<DatePicker.
|
|
121
|
-
|
|
122
|
-
{#
|
|
123
|
-
|
|
124
|
-
<div
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
<DatePicker.Portal>
|
|
121
|
+
<DatePicker.Content forceMount sideOffset={8}>
|
|
122
|
+
{#snippet child({props, wrapperProps, open})}
|
|
123
|
+
{#if open}
|
|
124
|
+
<div {...wrapperProps}>
|
|
125
|
+
<div
|
|
126
|
+
{...props}
|
|
127
|
+
class="bg-surface border border-primary rounded-10 shadow-dropdown p-12 z-16 min-w-280"
|
|
128
|
+
transition:fly={{y: -4, duration: 150}}
|
|
129
|
+
>
|
|
130
|
+
<DatePicker.Calendar>
|
|
131
|
+
{#snippet children({months, weekdays})}
|
|
132
|
+
<IICalendarGrid {months} {weekdays} {placeholder} onNavigate={(d) => (placeholder = d)} />
|
|
133
|
+
{/snippet}
|
|
134
|
+
</DatePicker.Calendar>
|
|
135
|
+
</div>
|
|
134
136
|
</div>
|
|
135
|
-
|
|
136
|
-
{/
|
|
137
|
-
|
|
138
|
-
</DatePicker.
|
|
137
|
+
{/if}
|
|
138
|
+
{/snippet}
|
|
139
|
+
</DatePicker.Content>
|
|
140
|
+
</DatePicker.Portal>
|
|
139
141
|
</DatePicker.Root>
|
|
140
142
|
{#if showError && errorMessage}
|
|
141
143
|
<span class="text-tiny text-error mt-4" data-field-error>{errorMessage}</span>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import IIDateInput from './IIDateInput.svelte'
|
|
3
|
+
import IIButton from '../IIButton/IIButton.svelte'
|
|
4
|
+
import type {DateValue} from '@internationalized/date'
|
|
5
|
+
|
|
6
|
+
let value = $state<DateValue>()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<!--
|
|
10
|
+
Reproduces the apply-flow layout that previously broke the calendar popup:
|
|
11
|
+
a height-constrained `overflow-y-auto` scroll container (which also clips
|
|
12
|
+
overflow-x on the right) with the Continue button as a *later sibling* of the
|
|
13
|
+
date input. Before the DatePicker.Portal fix the calendar (min-w-280) was
|
|
14
|
+
clipped on the right/bottom by this container and painted *behind* the
|
|
15
|
+
Continue button. With the portal it escapes the container and layers above.
|
|
16
|
+
|
|
17
|
+
To verify: open the calendar and confirm it is fully visible and sits above
|
|
18
|
+
the Continue button.
|
|
19
|
+
-->
|
|
20
|
+
<div
|
|
21
|
+
data-testid="scroll-container"
|
|
22
|
+
style="display: flex; flex-direction: column; gap: 1rem; width: 300px; height: 200px; overflow-y: auto; border: 1px dashed var(--color-border-primary, #cbd5e1); border-radius: 10px; padding: 1rem;"
|
|
23
|
+
>
|
|
24
|
+
<IIDateInput bind:value label="Date of Birth" class="w-full" />
|
|
25
|
+
<IIButton class="w-full">Continue</IIButton>
|
|
26
|
+
</div>
|
|
@@ -111,6 +111,7 @@
|
|
|
111
111
|
placement: 'bottom-start',
|
|
112
112
|
offset: 2,
|
|
113
113
|
matchWidth: matchTriggerWidth,
|
|
114
|
+
constrainHeight: true,
|
|
114
115
|
})
|
|
115
116
|
|
|
116
117
|
function handleSelect(item: MenuItem) {
|
|
@@ -272,7 +273,7 @@
|
|
|
272
273
|
bind:this={floatingEl}
|
|
273
274
|
role="listbox"
|
|
274
275
|
data-menu-content
|
|
275
|
-
class="min-w-100 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-16 pointer-events-auto outline-none"
|
|
276
|
+
class="flex flex-col min-w-100 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-16 pointer-events-auto outline-none overflow-hidden"
|
|
276
277
|
transition:fly={{y: -4, duration: 150}}
|
|
277
278
|
>
|
|
278
279
|
{#if searchable}
|
|
@@ -283,7 +284,7 @@
|
|
|
283
284
|
onkeydown={(e) => search.handleKeydown(e, '[data-menu-content]')}
|
|
284
285
|
/>
|
|
285
286
|
{/if}
|
|
286
|
-
<div class="
|
|
287
|
+
<div class="dd-scroll min-h-0 overflow-y-auto -mr-2">
|
|
287
288
|
{#each (searchable ? search.filteredItems as MenuItem[] : items) as item, listIndex (item.value)}
|
|
288
289
|
{@const index = searchable ? search.getItemIndex(item) : listIndex}
|
|
289
290
|
{@const isHighlighted = searchable
|
|
@@ -345,3 +346,26 @@
|
|
|
345
346
|
<span class="text-tiny text-error mt-4" data-field-error>{errorMessage}</span>
|
|
346
347
|
{/if}
|
|
347
348
|
</div>
|
|
349
|
+
|
|
350
|
+
<style>
|
|
351
|
+
/* Opt back into ::-webkit-scrollbar styling so we can set a custom (narrow)
|
|
352
|
+
thumb width: the global standard scrollbar-width/color (base.css) otherwise
|
|
353
|
+
disables the WebKit pseudo-elements in Chromium. */
|
|
354
|
+
.dd-scroll {
|
|
355
|
+
scrollbar-width: auto;
|
|
356
|
+
scrollbar-color: auto;
|
|
357
|
+
}
|
|
358
|
+
.dd-scroll::-webkit-scrollbar {
|
|
359
|
+
width: 4px;
|
|
360
|
+
}
|
|
361
|
+
.dd-scroll::-webkit-scrollbar-track {
|
|
362
|
+
background: transparent;
|
|
363
|
+
}
|
|
364
|
+
.dd-scroll::-webkit-scrollbar-thumb {
|
|
365
|
+
background-color: var(--ii-gray-300);
|
|
366
|
+
border-radius: 9999px;
|
|
367
|
+
}
|
|
368
|
+
.dd-scroll::-webkit-scrollbar-thumb:hover {
|
|
369
|
+
background-color: var(--ii-gray-400);
|
|
370
|
+
}
|
|
371
|
+
</style>
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
showCloseButton?: boolean
|
|
16
16
|
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
17
17
|
overlay?: 'dark' | 'light' | 'none'
|
|
18
|
-
/**
|
|
18
|
+
/** Vertically center the modal instead of anchoring it 15% from the top. */
|
|
19
|
+
centered?: boolean
|
|
20
|
+
/** Cap the modal height (any CSS value, e.g. "60vh"). Defaults to 78vh
|
|
21
|
+
* (90vh when `centered`). Note: when anchored (not centered) the modal
|
|
22
|
+
* sits 15% from the top, so a value above ~83vh pushes the bottom edge
|
|
23
|
+
* off-screen. */
|
|
19
24
|
maxHeight?: string
|
|
20
25
|
/** Replace the body wrapper's padding/scroll defaults — e.g. for a full-height flex body. */
|
|
21
26
|
bodyClass?: string
|
|
@@ -33,6 +38,7 @@
|
|
|
33
38
|
showCloseButton = true,
|
|
34
39
|
size = 'md',
|
|
35
40
|
overlay = 'dark',
|
|
41
|
+
centered = false,
|
|
36
42
|
maxHeight,
|
|
37
43
|
bodyClass,
|
|
38
44
|
class: className,
|
|
@@ -61,7 +67,7 @@
|
|
|
61
67
|
<Dialog.Portal>
|
|
62
68
|
<Dialog.Overlay
|
|
63
69
|
class={cn(
|
|
64
|
-
'fixed inset-0 z-14 data-[state=open]:animate-
|
|
70
|
+
'fixed inset-0 z-14 data-[state=open]:animate-modal-overlay-in data-[state=closed]:animate-modal-overlay-out motion-reduce:animate-none',
|
|
65
71
|
overlay === 'dark' && 'bg-black/30',
|
|
66
72
|
overlay === 'light' && 'bg-black/10',
|
|
67
73
|
overlay === 'none' && 'pointer-events-none'
|
|
@@ -78,16 +84,22 @@
|
|
|
78
84
|
if (isInsidePortaledMenu(target)) e.preventDefault()
|
|
79
85
|
}}
|
|
80
86
|
class={cn(
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
87
|
+
// Centering is deliberately kept OFF `transform`: horizontal uses left-0/right-0 +
|
|
88
|
+
// mx-auto, and vertical centering (when `centered`) uses the standalone `translate`
|
|
89
|
+
// property — both compose with the open/close slide animation, which owns `transform`
|
|
90
|
+
// (translateY). A transform-based center would be clobbered by the animation and
|
|
91
|
+
// shift the modal for the animation's duration.
|
|
92
|
+
'fixed left-0 right-0 mx-auto bg-surface border border-strong rounded-12 w-[calc(100%-20px)] flex flex-col z-15 data-[state=open]:animate-modal-in data-[state=closed]:animate-modal-out motion-reduce:animate-none focus:outline-none',
|
|
93
|
+
// Vertical: anchored 15% from the top (default) vs. centered. When anchored, height
|
|
94
|
+
// is capped so a tall modal still clears the bottom (15% + 78vh leaves ~7vh below);
|
|
95
|
+
// centered can safely use the full 90vh since -50% keeps it within the viewport.
|
|
96
|
+
centered ? 'top-1/2 [translate:0_-50%] max-h-[90vh]' : 'top-[15%] max-h-[78vh]',
|
|
85
97
|
overlay === 'none' ? 'shadow-floating' : 'shadow-modal',
|
|
86
98
|
size === 'sm' && 'max-w-400',
|
|
87
99
|
size === 'md' && 'max-w-500',
|
|
88
100
|
size === 'lg' && 'max-w-700',
|
|
89
101
|
size === 'xl' && 'max-w-[1100px]',
|
|
90
|
-
size === 'full' && 'max-w-[95vw] h-[
|
|
102
|
+
size === 'full' && 'max-w-[95vw] h-[78vh]',
|
|
91
103
|
className
|
|
92
104
|
)}
|
|
93
105
|
style={maxHeight ? `max-height: ${maxHeight}` : undefined}
|
|
@@ -10,7 +10,12 @@ type Props = {
|
|
|
10
10
|
showCloseButton?: boolean;
|
|
11
11
|
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
12
12
|
overlay?: 'dark' | 'light' | 'none';
|
|
13
|
-
/**
|
|
13
|
+
/** Vertically center the modal instead of anchoring it 15% from the top. */
|
|
14
|
+
centered?: boolean;
|
|
15
|
+
/** Cap the modal height (any CSS value, e.g. "60vh"). Defaults to 78vh
|
|
16
|
+
* (90vh when `centered`). Note: when anchored (not centered) the modal
|
|
17
|
+
* sits 15% from the top, so a value above ~83vh pushes the bottom edge
|
|
18
|
+
* off-screen. */
|
|
14
19
|
maxHeight?: string;
|
|
15
20
|
/** Replace the body wrapper's padding/scroll defaults — e.g. for a full-height flex body. */
|
|
16
21
|
bodyClass?: string;
|
|
@@ -19,14 +19,15 @@
|
|
|
19
19
|
|
|
20
20
|
<IIButton onclick={() => (open = true)}>Open modal</IIButton>
|
|
21
21
|
|
|
22
|
-
{#if open}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
<!-- Keep IIModal mounted unconditionally (no {#if open}) so bits-ui's Dialog can
|
|
23
|
+
play its exit animation on close — wrapping it in {#if} unmounts the whole
|
|
24
|
+
dialog instantly and skips the modal-out/fade-out transitions. -->
|
|
25
|
+
<IIModal bind:open title="Add a note" onOpenChange={v => (open = v)}>
|
|
26
|
+
<IITextarea
|
|
27
|
+
bind:ref
|
|
28
|
+
bind:value
|
|
29
|
+
label="Note"
|
|
30
|
+
placeholder="The focus ring should be fully visible on the left, right, and bottom."
|
|
31
|
+
rows={4}
|
|
32
|
+
/>
|
|
33
|
+
</IIModal>
|
|
@@ -25,13 +25,11 @@ export declare const keyframes: {
|
|
|
25
25
|
transform: string;
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
|
-
|
|
28
|
+
modalSlideIn: {
|
|
29
29
|
from: {
|
|
30
|
-
opacity: string;
|
|
31
30
|
transform: string;
|
|
32
31
|
};
|
|
33
32
|
to: {
|
|
34
|
-
opacity: string;
|
|
35
33
|
transform: string;
|
|
36
34
|
};
|
|
37
35
|
};
|
|
@@ -161,6 +159,8 @@ export declare const animation: {
|
|
|
161
159
|
skeleton: string;
|
|
162
160
|
'modal-in': string;
|
|
163
161
|
'modal-out': string;
|
|
162
|
+
'modal-overlay-in': string;
|
|
163
|
+
'modal-overlay-out': string;
|
|
164
164
|
'fade-out': string;
|
|
165
165
|
'login-fade-in-up': string;
|
|
166
166
|
'login-pulse': string;
|
|
@@ -12,9 +12,13 @@ export const keyframes = {
|
|
|
12
12
|
'0%': { transform: 'translate(-50%, -50%) rotate(0deg) translateX(24px)' },
|
|
13
13
|
'100%': { transform: 'translate(-50%, -50%) rotate(360deg) translateX(24px)' },
|
|
14
14
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
modalSlideIn: {
|
|
16
|
+
// Transform-only slide into place. Opacity is handled by a separate, faster
|
|
17
|
+
// `fadeIn` animation (see modal-in) so the fade can match the overlay while
|
|
18
|
+
// the slide runs on its own long duration. (IIModal centers via mx-auto, not
|
|
19
|
+
// transform, so translateY here is free of the horizontal centering.)
|
|
20
|
+
from: { transform: 'translateY(-10px)' },
|
|
21
|
+
to: { transform: 'translateY(0)' },
|
|
18
22
|
},
|
|
19
23
|
spin: {
|
|
20
24
|
to: { transform: 'rotate(360deg)' },
|
|
@@ -60,8 +64,9 @@ export const keyframes = {
|
|
|
60
64
|
to: { opacity: '1', transform: 'translateY(0)' },
|
|
61
65
|
},
|
|
62
66
|
modalOut: {
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
// Fade out while sliding up.
|
|
68
|
+
from: { opacity: '1', transform: 'translateY(0)' },
|
|
69
|
+
to: { opacity: '0', transform: 'translateY(-6px)' },
|
|
65
70
|
},
|
|
66
71
|
fadeOut: {
|
|
67
72
|
from: { opacity: '1' },
|
|
@@ -76,8 +81,14 @@ export const animation = {
|
|
|
76
81
|
shake: 'shake 0.4s ease-out',
|
|
77
82
|
shimmer: 'shimmer 1.5s infinite',
|
|
78
83
|
skeleton: 'skeleton-loading 1.2s ease-in-out infinite',
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
// Two decoupled animations: the opacity fade matches the overlay exactly
|
|
85
|
+
// (fadeIn 130ms ease-out), while the slide glides in slowly over 2500ms.
|
|
86
|
+
'modal-in': 'fadeIn 130ms ease-out, modalSlideIn 2500ms cubic-bezier(0.1, 1, 0.1, 1)',
|
|
87
|
+
'modal-out': 'modalOut 90ms ease-in',
|
|
88
|
+
// Modal-specific overlay fades — snappy, to match the modal's timing rather
|
|
89
|
+
// than the slower shared fade-in/fade-out used by menus, tooltips, etc.
|
|
90
|
+
'modal-overlay-in': 'fadeIn 130ms ease-out',
|
|
91
|
+
'modal-overlay-out': 'fadeOut 90ms ease-in',
|
|
81
92
|
'fade-out': 'fadeOut 150ms ease-in',
|
|
82
93
|
'login-fade-in-up': 'loginFadeInUp 0.6s ease-out',
|
|
83
94
|
'login-pulse': 'loginPulse 2s ease-in-out infinite',
|
|
@@ -442,13 +442,11 @@ declare const _default: {
|
|
|
442
442
|
transform: string;
|
|
443
443
|
};
|
|
444
444
|
};
|
|
445
|
-
|
|
445
|
+
modalSlideIn: {
|
|
446
446
|
from: {
|
|
447
|
-
opacity: string;
|
|
448
447
|
transform: string;
|
|
449
448
|
};
|
|
450
449
|
to: {
|
|
451
|
-
opacity: string;
|
|
452
450
|
transform: string;
|
|
453
451
|
};
|
|
454
452
|
};
|
|
@@ -578,6 +576,8 @@ declare const _default: {
|
|
|
578
576
|
skeleton: string;
|
|
579
577
|
'modal-in': string;
|
|
580
578
|
'modal-out': string;
|
|
579
|
+
'modal-overlay-in': string;
|
|
580
|
+
'modal-overlay-out': string;
|
|
581
581
|
'fade-out': string;
|
|
582
582
|
'login-fade-in-up': string;
|
|
583
583
|
'login-pulse': string;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}: Props = $props()
|
|
15
15
|
</script>
|
|
16
16
|
|
|
17
|
-
<div class="-mx-4 -mt-4 px-14 mb-4 border-b-[0.5px] border-dropdown-border">
|
|
17
|
+
<div class="shrink-0 -mx-4 -mt-4 px-14 mb-4 border-b-[0.5px] border-dropdown-border">
|
|
18
18
|
<input
|
|
19
19
|
bind:this={inputEl}
|
|
20
20
|
bind:value={searchQuery}
|
|
@@ -9,5 +9,9 @@ export type UseFloatingOptions = {
|
|
|
9
9
|
padding?: number;
|
|
10
10
|
};
|
|
11
11
|
matchWidth?: boolean;
|
|
12
|
+
/** Constrain the floating element's height to the space available in the
|
|
13
|
+
* viewport (via the size middleware). Lets the panel grow to fit its
|
|
14
|
+
* content and only cap/scroll when it would run off-screen. */
|
|
15
|
+
constrainHeight?: boolean;
|
|
12
16
|
};
|
|
13
17
|
export declare function useFloating(opts: UseFloatingOptions): void;
|
|
@@ -13,12 +13,18 @@ export function useFloating(opts) {
|
|
|
13
13
|
const padding = typeof opts.shift === 'object' ? opts.shift.padding : 8;
|
|
14
14
|
middleware.push(shiftMiddleware({ padding }));
|
|
15
15
|
}
|
|
16
|
-
if (opts.matchWidth) {
|
|
16
|
+
if (opts.matchWidth || opts.constrainHeight) {
|
|
17
17
|
middleware.push(sizeMiddleware({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
padding: 8,
|
|
19
|
+
apply({ rects, availableHeight }) {
|
|
20
|
+
const styles = {};
|
|
21
|
+
if (opts.matchWidth) {
|
|
22
|
+
styles.minWidth = `${rects.reference.width}px`;
|
|
23
|
+
}
|
|
24
|
+
if (opts.constrainHeight) {
|
|
25
|
+
styles.maxHeight = `${availableHeight}px`;
|
|
26
|
+
}
|
|
27
|
+
Object.assign(floating.style, styles);
|
|
22
28
|
},
|
|
23
29
|
}));
|
|
24
30
|
}
|