@getmicdrop/svelte-components 5.12.0 → 5.14.0
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/calendar/OrderSummary/OrderSummary.svelte +67 -7
- package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts +2 -0
- package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts.map +1 -1
- package/dist/index.spec.js +0 -1
- package/dist/patterns/navigation/Header.svelte +23 -27
- package/dist/patterns/navigation/Header.svelte.d.ts.map +1 -1
- package/dist/primitives/AvatarButton/AvatarButton.svelte +57 -0
- package/dist/primitives/AvatarButton/AvatarButton.svelte.d.ts +18 -0
- package/dist/primitives/AvatarButton/AvatarButton.svelte.d.ts.map +1 -0
- package/dist/primitives/BottomSheet/BottomSheet.spec.js +19 -19
- package/dist/primitives/BottomSheet/BottomSheet.svelte +5 -5
- package/dist/primitives/BottomSheet/BottomSheet.svelte.d.ts +2 -2
- package/dist/primitives/BottomSheet/BottomSheet.svelte.d.ts.map +1 -1
- package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte +3 -3
- package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts +1 -1
- package/dist/primitives/Button/Button.spec.js +16 -14
- package/dist/primitives/Button/Button.svelte +9 -45
- package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
- package/dist/primitives/CardAction/CardAction.svelte +68 -0
- package/dist/primitives/CardAction/CardAction.svelte.d.ts +20 -0
- package/dist/primitives/CardAction/CardAction.svelte.d.ts.map +1 -0
- package/dist/primitives/Drawer/Drawer.spec.js +33 -33
- package/dist/primitives/Drawer/Drawer.svelte +5 -9
- package/dist/primitives/Drawer/Drawer.svelte.d.ts +2 -3
- package/dist/primitives/Drawer/Drawer.svelte.d.ts.map +1 -1
- package/dist/primitives/Input/Input.svelte +1 -1
- package/dist/primitives/LandingButton/LandingButton.svelte +92 -0
- package/dist/primitives/LandingButton/LandingButton.svelte.d.ts +22 -0
- package/dist/primitives/LandingButton/LandingButton.svelte.d.ts.map +1 -0
- package/dist/primitives/MenuItem/MenuItem.svelte +85 -0
- package/dist/primitives/MenuItem/MenuItem.svelte.d.ts +24 -0
- package/dist/primitives/MenuItem/MenuItem.svelte.d.ts.map +1 -0
- package/dist/primitives/Modal/Modal.spec.js +7 -7
- package/dist/primitives/Modal/Modal.stories.svelte +3 -3
- package/dist/primitives/Modal/Modal.svelte +25 -18
- package/dist/primitives/Modal/Modal.svelte.d.ts +5 -5
- package/dist/primitives/Modal/Modal.svelte.d.ts.map +1 -1
- package/dist/primitives/Modal/ModalTestWrapper.svelte +3 -3
- package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts +2 -2
- package/dist/primitives/NavItem/NavItem.svelte +75 -0
- package/dist/primitives/NavItem/NavItem.svelte.d.ts +20 -0
- package/dist/primitives/NavItem/NavItem.svelte.d.ts.map +1 -0
- package/dist/primitives/SearchResultItem/SearchResultItem.svelte +109 -0
- package/dist/primitives/SearchResultItem/SearchResultItem.svelte.d.ts +26 -0
- package/dist/primitives/SearchResultItem/SearchResultItem.svelte.d.ts.map +1 -0
- package/dist/primitives/SidebarToggle/SidebarToggle.svelte +55 -0
- package/dist/primitives/SidebarToggle/SidebarToggle.svelte.d.ts +18 -0
- package/dist/primitives/SidebarToggle/SidebarToggle.svelte.d.ts.map +1 -0
- package/dist/primitives/index.d.ts +7 -0
- package/dist/primitives/index.js +21 -0
- package/dist/recipes/SuperLogin/SuperLogin.svelte +3 -3
- package/dist/recipes/SuperLogin/SuperLogin.svelte.d.ts.map +1 -1
- package/dist/recipes/inputs/index.d.ts +0 -1
- package/dist/recipes/inputs/index.js +0 -1
- package/dist/recipes/modals/AlertModal.spec.js +2 -2
- package/dist/recipes/modals/AlertModal.svelte +6 -6
- package/dist/recipes/modals/AlertModal.svelte.d.ts +3 -3
- package/dist/recipes/modals/ConfirmationModal.spec.js +2 -2
- package/dist/recipes/modals/ConfirmationModal.svelte +5 -5
- package/dist/recipes/modals/ConfirmationModal.svelte.d.ts +3 -3
- package/dist/recipes/modals/InputModal.spec.js +2 -2
- package/dist/recipes/modals/InputModal.svelte +4 -4
- package/dist/recipes/modals/InputModal.svelte.d.ts +3 -3
- package/dist/recipes/modals/ModalTestWrapper.spec.js +49 -49
- package/dist/recipes/modals/ModalTestWrapper.svelte +3 -3
- package/dist/recipes/modals/ModalTestWrapper.svelte.d.ts +2 -2
- package/dist/recipes/modals/StatusModal.spec.js +2 -2
- package/dist/recipes/modals/StatusModal.svelte +4 -4
- package/dist/recipes/modals/StatusModal.svelte.d.ts +3 -3
- package/dist/stories/ComponentConsolidation.stories.svelte +10 -10
- package/dist/stories/PrimitivesGallery.svelte +25 -21
- package/dist/stories/PrimitivesGallery.svelte.d.ts.map +1 -1
- package/dist/stories/RecipesGallery.spec.js +9 -18
- package/dist/stories/RecipesGallery.svelte +5 -22
- package/dist/stories/RecipesGallery.svelte.d.ts.map +1 -1
- package/dist/tokens/__tests__/sizing.test.js +5 -7
- package/dist/tokens/sizing.d.ts +20 -19
- package/dist/tokens/sizing.d.ts.map +1 -1
- package/dist/tokens/sizing.js +20 -19
- package/package.json +1 -1
- package/dist/recipes/inputs/SelectDropdown.spec.d.ts +0 -2
- package/dist/recipes/inputs/SelectDropdown.spec.d.ts.map +0 -1
- package/dist/recipes/inputs/SelectDropdown.spec.js +0 -518
- package/dist/recipes/inputs/SelectDropdown.svelte +0 -171
- package/dist/recipes/inputs/SelectDropdown.svelte.d.ts +0 -16
- package/dist/recipes/inputs/SelectDropdown.svelte.d.ts.map +0 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LandingButton Component
|
|
4
|
+
* Hero/landing page CTA buttons with shadow and prominent styling.
|
|
5
|
+
*
|
|
6
|
+
* Replaces: Button variant="landing" and variant="landing-secondary"
|
|
7
|
+
*/
|
|
8
|
+
import { twMerge } from 'tailwind-merge';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
import { buttonSizes } from '../../tokens/sizing.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Visual style: primary (blue) or secondary (outline) */
|
|
14
|
+
variant?: 'primary' | 'secondary';
|
|
15
|
+
/** Disabled state */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Loading state */
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
/** Link href (renders as <a> if provided) */
|
|
20
|
+
href?: string | null;
|
|
21
|
+
/** Content */
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
/** Additional classes */
|
|
24
|
+
class?: string;
|
|
25
|
+
/** Click handler */
|
|
26
|
+
onclick?: (e: MouseEvent) => void;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let {
|
|
31
|
+
variant = 'primary',
|
|
32
|
+
disabled = false,
|
|
33
|
+
loading = false,
|
|
34
|
+
href = null,
|
|
35
|
+
children,
|
|
36
|
+
class: className = '',
|
|
37
|
+
onclick,
|
|
38
|
+
...restProps
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
let effectiveDisabled = $derived(disabled || loading);
|
|
42
|
+
|
|
43
|
+
const baseClasses = 'inline-flex items-center justify-center rounded-xl font-medium leading-none focus:outline-hidden transition-all duration-150 ease-out select-none no-underline hover:no-underline shadow hover:shadow-md';
|
|
44
|
+
|
|
45
|
+
const variantClasses = {
|
|
46
|
+
primary: 'text-white bg-blue-600 border border-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:border-blue-600 dark:hover:bg-blue-700',
|
|
47
|
+
secondary: 'text-gray-700 bg-white border border-gray-200 hover:border-gray-400 hover:text-gray-900 dark:text-gray-300 dark:bg-gray-800 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:text-gray-100',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const disabledClasses = 'bg-gray-200 border-gray-200 text-gray-400 cursor-not-allowed shadow-none dark:bg-gray-700 dark:border-gray-700 dark:text-gray-500';
|
|
51
|
+
|
|
52
|
+
// Landing size from tokens
|
|
53
|
+
let sizeClass = $derived(buttonSizes.landing);
|
|
54
|
+
|
|
55
|
+
let variantClass = $derived(() => {
|
|
56
|
+
if (effectiveDisabled) return disabledClasses;
|
|
57
|
+
return variantClasses[variant] || variantClasses.primary;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
let classes = $derived(twMerge(
|
|
61
|
+
baseClasses,
|
|
62
|
+
sizeClass,
|
|
63
|
+
variantClass(),
|
|
64
|
+
effectiveDisabled ? 'cursor-not-allowed' : 'cursor-pointer active:scale-[0.97] active:opacity-90',
|
|
65
|
+
className
|
|
66
|
+
));
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
{#if href}
|
|
70
|
+
<a
|
|
71
|
+
{href}
|
|
72
|
+
class={classes}
|
|
73
|
+
{onclick}
|
|
74
|
+
{...restProps}
|
|
75
|
+
>
|
|
76
|
+
<span class="inline-flex items-center gap-1.5" class:invisible={loading}>
|
|
77
|
+
{#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
|
|
78
|
+
</span>
|
|
79
|
+
</a>
|
|
80
|
+
{:else}
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
class={classes}
|
|
84
|
+
disabled={effectiveDisabled}
|
|
85
|
+
{onclick}
|
|
86
|
+
{...restProps}
|
|
87
|
+
>
|
|
88
|
+
<span class="inline-flex items-center gap-1.5" class:invisible={loading}>
|
|
89
|
+
{#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
|
|
90
|
+
</span>
|
|
91
|
+
</button>
|
|
92
|
+
{/if}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Visual style: primary (blue) or secondary (outline) */
|
|
4
|
+
variant?: 'primary' | 'secondary';
|
|
5
|
+
/** Disabled state */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Loading state */
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
/** Link href (renders as <a> if provided) */
|
|
10
|
+
href?: string | null;
|
|
11
|
+
/** Content */
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
/** Additional classes */
|
|
14
|
+
class?: string;
|
|
15
|
+
/** Click handler */
|
|
16
|
+
onclick?: (e: MouseEvent) => void;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
declare const LandingButton: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type LandingButton = ReturnType<typeof LandingButton>;
|
|
21
|
+
export default LandingButton;
|
|
22
|
+
//# sourceMappingURL=LandingButton.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LandingButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/LandingButton/LandingButton.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAIpC,UAAU,KAAK;IACb,0DAA0D;IAC1D,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC;IAClC,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,cAAc;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AA+DH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* MenuItem Component
|
|
4
|
+
* For dropdown menus, action sheets, and sidebar navigation items.
|
|
5
|
+
*
|
|
6
|
+
* Replaces: Button variant="menu-item" and variant="menu-item-danger"
|
|
7
|
+
*/
|
|
8
|
+
import { twMerge } from 'tailwind-merge';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
import { buttonMenuItemSizes } from '../../tokens/sizing.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Destructive/danger styling (red text) */
|
|
14
|
+
danger?: boolean;
|
|
15
|
+
/** Size variant */
|
|
16
|
+
size?: 'sm' | 'md' | 'lg' | 'nav';
|
|
17
|
+
/** Disabled state */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** Active/selected state */
|
|
20
|
+
active?: boolean;
|
|
21
|
+
/** Content */
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
/** Trailing content (icons, badges) */
|
|
24
|
+
trailing?: Snippet;
|
|
25
|
+
/** Additional classes */
|
|
26
|
+
class?: string;
|
|
27
|
+
/** Click handler */
|
|
28
|
+
onclick?: (e: MouseEvent) => void;
|
|
29
|
+
[key: string]: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
danger = false,
|
|
34
|
+
size = 'md',
|
|
35
|
+
disabled = false,
|
|
36
|
+
active = false,
|
|
37
|
+
children,
|
|
38
|
+
trailing,
|
|
39
|
+
class: className = '',
|
|
40
|
+
onclick,
|
|
41
|
+
...restProps
|
|
42
|
+
}: Props = $props();
|
|
43
|
+
|
|
44
|
+
const baseClasses = 'w-full text-left whitespace-nowrap bg-transparent border-transparent rounded-lg font-medium leading-none focus:outline-hidden transition-all duration-150 ease-out select-none';
|
|
45
|
+
|
|
46
|
+
const variantClasses = {
|
|
47
|
+
default: 'text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600',
|
|
48
|
+
danger: 'text-red-600 hover:bg-red-50 dark:text-red-500 dark:hover:bg-gray-600',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const activeClasses = 'bg-blue-50 dark:bg-gray-700';
|
|
52
|
+
const disabledClasses = 'text-gray-400 cursor-not-allowed dark:text-gray-500';
|
|
53
|
+
|
|
54
|
+
let sizeClass = $derived(buttonMenuItemSizes[size] || buttonMenuItemSizes.md);
|
|
55
|
+
|
|
56
|
+
let variantClass = $derived(() => {
|
|
57
|
+
if (disabled) return disabledClasses;
|
|
58
|
+
if (active) return `${variantClasses[danger ? 'danger' : 'default']} ${activeClasses}`;
|
|
59
|
+
return variantClasses[danger ? 'danger' : 'default'];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let hasTrailing = $derived(typeof trailing === 'function' || trailing);
|
|
63
|
+
|
|
64
|
+
let classes = $derived(twMerge(
|
|
65
|
+
baseClasses,
|
|
66
|
+
hasTrailing ? 'flex items-center justify-between' : 'flex items-center justify-start',
|
|
67
|
+
sizeClass,
|
|
68
|
+
variantClass(),
|
|
69
|
+
disabled ? 'cursor-not-allowed' : 'cursor-pointer active:scale-[0.97] active:opacity-90',
|
|
70
|
+
className
|
|
71
|
+
));
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
class={classes}
|
|
77
|
+
{disabled}
|
|
78
|
+
{onclick}
|
|
79
|
+
{...restProps}
|
|
80
|
+
>
|
|
81
|
+
<span class="inline-flex items-center gap-1.5">
|
|
82
|
+
{#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
|
|
83
|
+
</span>
|
|
84
|
+
{#if typeof trailing === 'function'}{@render trailing()}{:else if trailing}{trailing}{/if}
|
|
85
|
+
</button>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Destructive/danger styling (red text) */
|
|
4
|
+
danger?: boolean;
|
|
5
|
+
/** Size variant */
|
|
6
|
+
size?: 'sm' | 'md' | 'lg' | 'nav';
|
|
7
|
+
/** Disabled state */
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
/** Active/selected state */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
/** Content */
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
/** Trailing content (icons, badges) */
|
|
14
|
+
trailing?: Snippet;
|
|
15
|
+
/** Additional classes */
|
|
16
|
+
class?: string;
|
|
17
|
+
/** Click handler */
|
|
18
|
+
onclick?: (e: MouseEvent) => void;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
declare const MenuItem: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type MenuItem = ReturnType<typeof MenuItem>;
|
|
23
|
+
export default MenuItem;
|
|
24
|
+
//# sourceMappingURL=MenuItem.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MenuItem.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/MenuItem/MenuItem.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAIpC,UAAU,KAAK;IACb,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;IAClC,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AA2DH,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -14,9 +14,9 @@ describe('Modal Component Tests', () => {
|
|
|
14
14
|
// CSS hides one based on viewport, but in tests both are in DOM
|
|
15
15
|
// Use getAllByText and check that at least one exists
|
|
16
16
|
|
|
17
|
-
test('Renders Modal when
|
|
17
|
+
test('Renders Modal when open is true', () => {
|
|
18
18
|
setupTest({
|
|
19
|
-
|
|
19
|
+
open: true,
|
|
20
20
|
title: 'Test Title',
|
|
21
21
|
description: 'Test Description',
|
|
22
22
|
warningText: 'Test Warning'
|
|
@@ -29,9 +29,9 @@ describe('Modal Component Tests', () => {
|
|
|
29
29
|
expect(screen.getAllByRole('button', { name: /Confirm/i }).length).toBeGreaterThan(0);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
test('Does not render Modal when
|
|
32
|
+
test('Does not render Modal when open is false', () => {
|
|
33
33
|
setupTest({
|
|
34
|
-
|
|
34
|
+
open: false,
|
|
35
35
|
title: 'Test Title',
|
|
36
36
|
description: 'Test Description',
|
|
37
37
|
warningText: 'Test Warning'
|
|
@@ -46,7 +46,7 @@ describe('Modal Component Tests', () => {
|
|
|
46
46
|
const description = 'Correct Description';
|
|
47
47
|
const warningText = 'Correct Warning';
|
|
48
48
|
setupTest({
|
|
49
|
-
|
|
49
|
+
open: true,
|
|
50
50
|
title,
|
|
51
51
|
description,
|
|
52
52
|
warningText,
|
|
@@ -62,7 +62,7 @@ describe('Modal Component Tests', () => {
|
|
|
62
62
|
const oncancel = vi.fn();
|
|
63
63
|
|
|
64
64
|
const { user } = setupTest({
|
|
65
|
-
|
|
65
|
+
open: true,
|
|
66
66
|
title: 'Test Title',
|
|
67
67
|
oncancel
|
|
68
68
|
});
|
|
@@ -79,7 +79,7 @@ describe('Modal Component Tests', () => {
|
|
|
79
79
|
|
|
80
80
|
test('Prevents propagation of click events within modal', async () => {
|
|
81
81
|
const { user } = setupTest({
|
|
82
|
-
|
|
82
|
+
open: true,
|
|
83
83
|
title: 'Test Title'
|
|
84
84
|
});
|
|
85
85
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
>
|
|
23
23
|
Open Basic Modal
|
|
24
24
|
</button>
|
|
25
|
-
<ModalProgress bind:
|
|
25
|
+
<ModalProgress bind:open={show1}>
|
|
26
26
|
{#snippet header()}
|
|
27
27
|
<h2 class="text-xl font-semibold mb-2">Modal Title</h2>
|
|
28
28
|
{/snippet}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
>
|
|
48
48
|
Open Processing Modal
|
|
49
49
|
</button>
|
|
50
|
-
<ModalProgress bind:
|
|
50
|
+
<ModalProgress bind:open={show2} isProcessing={true}>
|
|
51
51
|
{#snippet body()}
|
|
52
52
|
<div class="flex flex-col items-center gap-4">
|
|
53
53
|
<div
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
>
|
|
67
67
|
Open Success Modal
|
|
68
68
|
</button>
|
|
69
|
-
<ModalProgress bind:
|
|
69
|
+
<ModalProgress bind:open={show3} isSuccess={true}>
|
|
70
70
|
{#snippet body()}
|
|
71
71
|
<div class="flex flex-col items-center gap-4 text-center">
|
|
72
72
|
<div class="text-green-600 dark:text-green-400 text-5xl">✓</div>
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
import { triggerHaptic } from "../../utils/haptic.js";
|
|
14
14
|
|
|
15
15
|
/** @type {{
|
|
16
|
-
|
|
16
|
+
open?: boolean,
|
|
17
17
|
isProcessing?: boolean,
|
|
18
18
|
isSuccess?: boolean,
|
|
19
|
-
size?: '
|
|
19
|
+
size?: 'sm' | 'md' | 'lg' | 'xl',
|
|
20
20
|
persistent?: boolean,
|
|
21
21
|
haptic?: boolean,
|
|
22
22
|
oncancel?: () => void,
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
class?: string,
|
|
27
27
|
}} */
|
|
28
28
|
let {
|
|
29
|
-
|
|
29
|
+
open = $bindable(false),
|
|
30
30
|
isProcessing = false,
|
|
31
31
|
isSuccess = false,
|
|
32
|
-
size = "
|
|
32
|
+
size = "md",
|
|
33
33
|
persistent = false,
|
|
34
34
|
haptic = true,
|
|
35
35
|
oncancel,
|
|
@@ -43,55 +43,61 @@
|
|
|
43
43
|
// Store scroll position for iOS scroll lock
|
|
44
44
|
let scrollY = $state(0);
|
|
45
45
|
|
|
46
|
-
// Track previous
|
|
47
|
-
let
|
|
46
|
+
// Track previous open state to detect open transitions
|
|
47
|
+
let prevOpen = $state(false);
|
|
48
48
|
|
|
49
49
|
// Trigger haptic on modal open (QOL Bible)
|
|
50
50
|
$effect(() => {
|
|
51
|
-
if (
|
|
51
|
+
if (open && !prevOpen && haptic) {
|
|
52
52
|
triggerHaptic('light');
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
prevOpen = open;
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
// Handle escape key
|
|
58
58
|
function handleKeydown(event) {
|
|
59
|
-
if (event.key === "Escape" &&
|
|
59
|
+
if (event.key === "Escape" && open && !persistent) {
|
|
60
60
|
resetModal();
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const resetModal = () => {
|
|
65
65
|
oncancel?.();
|
|
66
|
-
|
|
66
|
+
open = false;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
// Size classes for desktop modal (
|
|
69
|
+
// Size classes for desktop modal (xs/sm/md/lg/xl scale)
|
|
70
70
|
const sizeClasses = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
sm: "max-w-sm",
|
|
72
|
+
md: "max-w-md",
|
|
73
|
+
lg: "max-w-xl",
|
|
74
|
+
xl: "max-w-2xl"
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
let modalSizeClass = $derived(sizeClasses[size] || sizeClasses.
|
|
77
|
+
let modalSizeClass = $derived(sizeClasses[size] || sizeClasses.md);
|
|
78
78
|
|
|
79
79
|
// iOS-compatible scroll lock using position:fixed approach
|
|
80
|
+
// Also compensates for scrollbar width to prevent layout shift
|
|
80
81
|
$effect(() => {
|
|
81
82
|
if (typeof document !== "undefined") {
|
|
82
|
-
if (
|
|
83
|
+
if (open) {
|
|
83
84
|
scrollY = window.scrollY;
|
|
85
|
+
// Calculate scrollbar width before hiding it
|
|
86
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
84
87
|
document.body.style.position = "fixed";
|
|
85
88
|
document.body.style.top = `-${scrollY}px`;
|
|
86
89
|
document.body.style.left = "0";
|
|
87
90
|
document.body.style.right = "0";
|
|
88
91
|
document.body.style.overflow = "hidden";
|
|
92
|
+
// Add padding to compensate for scrollbar disappearing
|
|
93
|
+
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
|
89
94
|
} else {
|
|
90
95
|
document.body.style.position = "";
|
|
91
96
|
document.body.style.top = "";
|
|
92
97
|
document.body.style.left = "";
|
|
93
98
|
document.body.style.right = "";
|
|
94
99
|
document.body.style.overflow = "";
|
|
100
|
+
document.body.style.paddingRight = "";
|
|
95
101
|
window.scrollTo(0, scrollY);
|
|
96
102
|
}
|
|
97
103
|
}
|
|
@@ -104,13 +110,14 @@
|
|
|
104
110
|
document.body.style.left = "";
|
|
105
111
|
document.body.style.right = "";
|
|
106
112
|
document.body.style.overflow = "";
|
|
113
|
+
document.body.style.paddingRight = "";
|
|
107
114
|
}
|
|
108
115
|
});
|
|
109
116
|
</script>
|
|
110
117
|
|
|
111
118
|
<svelte:window onkeydown={handleKeydown} />
|
|
112
119
|
|
|
113
|
-
{#if
|
|
120
|
+
{#if open}
|
|
114
121
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
115
122
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
116
123
|
<div
|
|
@@ -2,10 +2,10 @@ export default Modal;
|
|
|
2
2
|
type Modal = {
|
|
3
3
|
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
4
|
$set?(props: Partial<{
|
|
5
|
-
|
|
5
|
+
open?: boolean | undefined;
|
|
6
6
|
isProcessing?: boolean | undefined;
|
|
7
7
|
isSuccess?: boolean | undefined;
|
|
8
|
-
size?: "
|
|
8
|
+
size?: "sm" | "md" | "lg" | "xl" | undefined;
|
|
9
9
|
persistent?: boolean | undefined;
|
|
10
10
|
haptic?: boolean | undefined;
|
|
11
11
|
oncancel?: (() => void) | undefined;
|
|
@@ -16,10 +16,10 @@ type Modal = {
|
|
|
16
16
|
}>): void;
|
|
17
17
|
};
|
|
18
18
|
declare const Modal: import("svelte").Component<{
|
|
19
|
-
|
|
19
|
+
open?: boolean;
|
|
20
20
|
isProcessing?: boolean;
|
|
21
21
|
isSuccess?: boolean;
|
|
22
|
-
size?: "
|
|
22
|
+
size?: "sm" | "md" | "lg" | "xl";
|
|
23
23
|
persistent?: boolean;
|
|
24
24
|
haptic?: boolean;
|
|
25
25
|
oncancel?: () => void;
|
|
@@ -27,5 +27,5 @@ declare const Modal: import("svelte").Component<{
|
|
|
27
27
|
body?: import("svelte").Snippet;
|
|
28
28
|
footer?: import("svelte").Snippet;
|
|
29
29
|
class?: string;
|
|
30
|
-
}, {}, "
|
|
30
|
+
}, {}, "open">;
|
|
31
31
|
//# sourceMappingURL=Modal.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Modal/Modal.svelte.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Modal/Modal.svelte.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA6LA;WAZW,OAAO;mBACC,OAAO;gBACV,OAAO;WACZ,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI;iBACnB,OAAO;aACX,OAAO;eACL,MAAM,IAAI;aACZ,OAAO,QAAQ,EAAE,OAAO;WAC1B,OAAO,QAAQ,EAAE,OAAO;aACtB,OAAO,QAAQ,EAAE,OAAO;YACzB,MAAM;eAEkC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
/** Whether the modal is visible */
|
|
8
|
-
|
|
8
|
+
open?: boolean;
|
|
9
9
|
/** Modal title */
|
|
10
10
|
title?: string;
|
|
11
11
|
/** Modal description */
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
let {
|
|
26
|
-
|
|
26
|
+
open = $bindable(false),
|
|
27
27
|
title = '',
|
|
28
28
|
description = '',
|
|
29
29
|
warningText = '',
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
}: Props = $props();
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
|
-
<Modal bind:
|
|
37
|
+
<Modal bind:open {oncancel}>
|
|
38
38
|
{#snippet header()}
|
|
39
39
|
{#if customHeader}
|
|
40
40
|
{@render customHeader()}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
interface Props {
|
|
3
3
|
/** Whether the modal is visible */
|
|
4
|
-
|
|
4
|
+
open?: boolean;
|
|
5
5
|
/** Modal title */
|
|
6
6
|
title?: string;
|
|
7
7
|
/** Modal description */
|
|
@@ -17,7 +17,7 @@ interface Props {
|
|
|
17
17
|
/** Custom footer snippet */
|
|
18
18
|
customFooter?: Snippet;
|
|
19
19
|
}
|
|
20
|
-
declare const ModalTestWrapper: import("svelte").Component<Props, {}, "
|
|
20
|
+
declare const ModalTestWrapper: import("svelte").Component<Props, {}, "open">;
|
|
21
21
|
type ModalTestWrapper = ReturnType<typeof ModalTestWrapper>;
|
|
22
22
|
export default ModalTestWrapper;
|
|
23
23
|
//# sourceMappingURL=ModalTestWrapper.svelte.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* NavItem Component
|
|
4
|
+
* Bottom navigation item with vertical layout.
|
|
5
|
+
*
|
|
6
|
+
* Replaces: Button variant="nav"
|
|
7
|
+
*/
|
|
8
|
+
import { twMerge } from 'tailwind-merge';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Active/selected state */
|
|
13
|
+
active?: boolean;
|
|
14
|
+
/** Disabled state */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Link href (renders as <a> if provided) */
|
|
17
|
+
href?: string | null;
|
|
18
|
+
/** Content (icon + label) */
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
/** Additional classes */
|
|
21
|
+
class?: string;
|
|
22
|
+
/** Click handler */
|
|
23
|
+
onclick?: (e: MouseEvent) => void;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
active = false,
|
|
29
|
+
disabled = false,
|
|
30
|
+
href = null,
|
|
31
|
+
children,
|
|
32
|
+
class: className = '',
|
|
33
|
+
onclick,
|
|
34
|
+
...restProps
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
const baseClasses = 'flex flex-col items-center justify-center h-full py-2 bg-transparent border-transparent rounded-lg font-medium leading-none focus:outline-hidden transition-all duration-150 ease-out select-none';
|
|
38
|
+
const defaultClasses = 'text-gray-500 hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-500';
|
|
39
|
+
const activeClasses = 'text-blue-600 dark:text-blue-500';
|
|
40
|
+
const disabledClasses = 'text-gray-400 cursor-not-allowed dark:text-gray-500';
|
|
41
|
+
|
|
42
|
+
let variantClass = $derived(() => {
|
|
43
|
+
if (disabled) return disabledClasses;
|
|
44
|
+
if (active) return activeClasses;
|
|
45
|
+
return defaultClasses;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
let classes = $derived(twMerge(
|
|
49
|
+
baseClasses,
|
|
50
|
+
variantClass(),
|
|
51
|
+
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
|
52
|
+
className
|
|
53
|
+
));
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
{#if href}
|
|
57
|
+
<a
|
|
58
|
+
{href}
|
|
59
|
+
class={classes}
|
|
60
|
+
{onclick}
|
|
61
|
+
{...restProps}
|
|
62
|
+
>
|
|
63
|
+
{#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
|
|
64
|
+
</a>
|
|
65
|
+
{:else}
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
class={classes}
|
|
69
|
+
{disabled}
|
|
70
|
+
{onclick}
|
|
71
|
+
{...restProps}
|
|
72
|
+
>
|
|
73
|
+
{#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
|
|
74
|
+
</button>
|
|
75
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Active/selected state */
|
|
4
|
+
active?: boolean;
|
|
5
|
+
/** Disabled state */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Link href (renders as <a> if provided) */
|
|
8
|
+
href?: string | null;
|
|
9
|
+
/** Content (icon + label) */
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
/** Additional classes */
|
|
12
|
+
class?: string;
|
|
13
|
+
/** Click handler */
|
|
14
|
+
onclick?: (e: MouseEvent) => void;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
declare const NavItem: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type NavItem = ReturnType<typeof NavItem>;
|
|
19
|
+
export default NavItem;
|
|
20
|
+
//# sourceMappingURL=NavItem.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavItem.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/NavItem/NavItem.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,UAAU,KAAK;IACb,4BAA4B;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgDH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
|