@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
|
@@ -103,54 +103,56 @@ describe('Button Sizes', () => {
|
|
|
103
103
|
test('Full width size', () => {
|
|
104
104
|
const { button } = setupTest({ size: 'full' });
|
|
105
105
|
expect(button).toHaveClass('w-full');
|
|
106
|
+
expect(button).toHaveClass('h-11');
|
|
106
107
|
expect(button).toHaveClass('px-5');
|
|
107
|
-
expect(button).toHaveClass('py-3');
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
test('Full width on mobile, auto on desktop', () => {
|
|
111
111
|
const { button } = setupTest({ size: 'full-md-auto' });
|
|
112
112
|
expect(button).toHaveClass('w-full');
|
|
113
|
+
expect(button).toHaveClass('h-11');
|
|
113
114
|
expect(button).toHaveClass('md:w-auto');
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
test('XL size', () => {
|
|
117
118
|
const { button } = setupTest({ size: 'xl' });
|
|
119
|
+
expect(button).toHaveClass('h-12');
|
|
118
120
|
expect(button).toHaveClass('px-6');
|
|
119
|
-
expect(button).toHaveClass('py-3.5');
|
|
120
121
|
expect(button).toHaveClass('text-sm');
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
test('Large size', () => {
|
|
124
125
|
const { button } = setupTest({ size: 'lg' });
|
|
126
|
+
expect(button).toHaveClass('h-11');
|
|
125
127
|
expect(button).toHaveClass('px-5');
|
|
126
|
-
expect(button).toHaveClass('py-3');
|
|
127
128
|
expect(button).toHaveClass('text-sm');
|
|
128
129
|
});
|
|
129
130
|
|
|
130
131
|
test('Medium size (default)', () => {
|
|
131
132
|
const { button } = setupTest({ size: 'md' });
|
|
133
|
+
expect(button).toHaveClass('h-10');
|
|
132
134
|
expect(button).toHaveClass('px-4');
|
|
133
|
-
expect(button).toHaveClass('py-2.5');
|
|
134
135
|
expect(button).toHaveClass('text-sm');
|
|
135
136
|
});
|
|
136
137
|
|
|
137
138
|
test('Small size', () => {
|
|
138
139
|
const { button } = setupTest({ size: 'sm' });
|
|
140
|
+
expect(button).toHaveClass('h-9');
|
|
139
141
|
expect(button).toHaveClass('px-3');
|
|
140
|
-
expect(button).toHaveClass('py-2');
|
|
141
142
|
expect(button).toHaveClass('text-sm');
|
|
142
143
|
});
|
|
143
144
|
|
|
144
145
|
test('Extra small size', () => {
|
|
145
146
|
const { button } = setupTest({ size: 'xs' });
|
|
147
|
+
expect(button).toHaveClass('h-8');
|
|
146
148
|
expect(button).toHaveClass('px-3');
|
|
147
|
-
expect(button).toHaveClass('py-1.5');
|
|
148
149
|
expect(button).toHaveClass('text-xs');
|
|
149
150
|
});
|
|
150
151
|
|
|
151
152
|
test('Half width size', () => {
|
|
152
153
|
const { button } = setupTest({ size: 'half' });
|
|
153
154
|
expect(button).toHaveClass('w-1/2');
|
|
155
|
+
expect(button).toHaveClass('h-10');
|
|
154
156
|
});
|
|
155
157
|
});
|
|
156
158
|
|
|
@@ -175,10 +177,8 @@ describe('Button States', () => {
|
|
|
175
177
|
expect(button.querySelector('svg')).toBeInTheDocument();
|
|
176
178
|
});
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
expect(button).toHaveClass('text-blue-600');
|
|
181
|
-
});
|
|
180
|
+
// Note: nav variant has been extracted to NavItem component
|
|
181
|
+
// This tests that unknown variants fall back to default styling
|
|
182
182
|
|
|
183
183
|
test('Active state for toggle variant', () => {
|
|
184
184
|
const { button } = setupTest({ active: true, variant: 'toggle' });
|
|
@@ -208,16 +208,18 @@ describe('Button as Link', () => {
|
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
describe('Button with Trailing Content', () => {
|
|
211
|
-
test('Button with trailing content
|
|
211
|
+
test('Button with trailing content stays centered (extracted variants use justify-between)', () => {
|
|
212
212
|
const trailing = () => '→';
|
|
213
213
|
const { container } = render(Button, {
|
|
214
214
|
props: {
|
|
215
|
-
variant: '
|
|
216
|
-
children: () => '
|
|
215
|
+
variant: 'default',
|
|
216
|
+
children: () => 'Click Me',
|
|
217
217
|
trailing,
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
220
|
const button = container.querySelector('button');
|
|
221
|
-
|
|
221
|
+
// Standard Button variants are always centered - left-aligned behavior
|
|
222
|
+
// has been extracted to dedicated components (MenuItem, NavItem, etc.)
|
|
223
|
+
expect(button).toHaveClass('justify-center');
|
|
222
224
|
});
|
|
223
225
|
});
|
|
@@ -35,9 +35,6 @@
|
|
|
35
35
|
import {
|
|
36
36
|
buttonSizes,
|
|
37
37
|
buttonIconSizes,
|
|
38
|
-
buttonAvatarSizes,
|
|
39
|
-
buttonMenuItemSizes,
|
|
40
|
-
buttonCardSizes,
|
|
41
38
|
} from '../../tokens/sizing.js';
|
|
42
39
|
import { triggerHaptic, getHapticForButtonVariant } from '../../utils/haptic.js';
|
|
43
40
|
|
|
@@ -106,6 +103,9 @@
|
|
|
106
103
|
// Size classes imported from centralized tokens
|
|
107
104
|
|
|
108
105
|
// Variant classes with all states in Tailwind (no focus rings - design decision)
|
|
106
|
+
// NOTE: menu-item, avatar, nav, card, search-result, landing, sidebar-toggle, calendar-day, chart-row
|
|
107
|
+
// have been extracted to dedicated components: MenuItem, AvatarButton, NavItem, CardAction,
|
|
108
|
+
// SearchResultItem, LandingButton, SidebarToggle. Use those components instead.
|
|
109
109
|
const variantClasses: Record<string, string> = {
|
|
110
110
|
default: "text-white bg-blue-700 border border-blue-700 hover:bg-blue-800 dark:bg-blue-600 dark:hover:bg-blue-700",
|
|
111
111
|
alternative: "text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-blue-700 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700",
|
|
@@ -119,35 +119,13 @@
|
|
|
119
119
|
icon: "text-gray-500 bg-transparent border-transparent hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700",
|
|
120
120
|
toggle: "text-gray-900 bg-gray-100 border border-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:hover:bg-gray-600",
|
|
121
121
|
success: "text-white bg-green-600 border border-green-600",
|
|
122
|
-
// Avatar/image trigger - no background, opacity hover
|
|
123
|
-
avatar: "bg-transparent border-transparent hover:opacity-80",
|
|
124
|
-
// Menu items - full width, left-aligned, for dropdowns/sheets and sidebar nav
|
|
125
|
-
"menu-item": "w-full text-left whitespace-nowrap text-gray-900 bg-transparent border-transparent hover:bg-gray-100 dark:text-white dark:hover:bg-gray-600",
|
|
126
|
-
// Danger menu item - red text version
|
|
127
|
-
"menu-item-danger": "w-full text-left whitespace-nowrap text-red-600 bg-transparent border-transparent hover:bg-red-50 dark:text-red-500 dark:hover:bg-gray-600",
|
|
128
|
-
// Selectable card - bordered card-like button for list selections
|
|
129
|
-
card: "w-full text-left text-gray-900 bg-white border border-gray-300 hover:bg-gray-50 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700",
|
|
130
|
-
// Search result item - blue hover for search dropdowns
|
|
131
|
-
"search-result": "w-full text-left text-gray-900 bg-transparent border-transparent hover:bg-blue-50 focus:bg-blue-50 dark:text-white dark:hover:bg-blue-900/20 dark:focus:bg-blue-900/20",
|
|
132
|
-
// Sidebar toggle - compact pill for sidebar expand/collapse
|
|
133
|
-
"sidebar-toggle": "w-6 h-7 text-gray-900 bg-blue-100 border border-blue-200 hover:bg-blue-200 shadow-lg dark:text-white dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700",
|
|
134
|
-
// Bottom navigation item - vertical layout, transparent, for mobile nav bars
|
|
135
|
-
nav: "flex-col h-full py-2 text-gray-500 bg-transparent border-transparent hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-500",
|
|
136
|
-
// Calendar day cell - base styling with hover, colors overridden via className
|
|
137
|
-
"calendar-day": "border-transparent text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700",
|
|
138
|
-
// Chart row - for leaderboard/chart list items with progress bars
|
|
139
|
-
"chart-row": "w-full text-left text-gray-900 bg-transparent border-transparent hover:bg-gray-50 dark:text-white dark:hover:bg-gray-800",
|
|
140
|
-
// Landing page hero buttons - prominent CTAs with shadow
|
|
141
|
-
landing: "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 no-underline hover:no-underline shadow hover:shadow-md",
|
|
142
|
-
"landing-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 no-underline hover:no-underline shadow hover:shadow-md",
|
|
143
122
|
};
|
|
144
123
|
|
|
145
|
-
// Active state classes for ghost
|
|
124
|
+
// Active state classes for ghost and toggle
|
|
125
|
+
// NOTE: menu-item and nav active states are now handled by MenuItem and NavItem components
|
|
146
126
|
const activeClasses: Record<string, string> = {
|
|
147
127
|
ghost: "text-blue-700 bg-blue-50 dark:text-white dark:bg-gray-900",
|
|
148
128
|
toggle: "text-white bg-blue-600 border-blue-600 hover:bg-blue-700 dark:text-white dark:bg-blue-600 dark:border-blue-600 dark:hover:bg-blue-700",
|
|
149
|
-
"menu-item": "bg-blue-50 dark:bg-gray-700",
|
|
150
|
-
nav: "text-blue-600 dark:text-blue-500",
|
|
151
129
|
};
|
|
152
130
|
|
|
153
131
|
// Disabled classes
|
|
@@ -157,14 +135,7 @@
|
|
|
157
135
|
|
|
158
136
|
let sizeClass = $derived((() => {
|
|
159
137
|
if (resolvedVariant === "icon") return buttonIconSizes[size as keyof typeof buttonIconSizes] || buttonIconSizes.sm;
|
|
160
|
-
if (resolvedVariant === "avatar") return buttonAvatarSizes[size as keyof typeof buttonAvatarSizes] || buttonAvatarSizes.md;
|
|
161
|
-
if (resolvedVariant === "menu-item" || resolvedVariant === "menu-item-danger" || resolvedVariant === "search-result") {
|
|
162
|
-
return buttonMenuItemSizes[size as keyof typeof buttonMenuItemSizes] || buttonMenuItemSizes.md;
|
|
163
|
-
}
|
|
164
|
-
if (resolvedVariant === "card") return buttonCardSizes[size as keyof typeof buttonCardSizes] || buttonCardSizes.md;
|
|
165
138
|
if (resolvedVariant === "link") return "text-sm";
|
|
166
|
-
if (resolvedVariant === "sidebar-toggle") return ""; // Fixed dimensions in variant
|
|
167
|
-
if (resolvedVariant === "nav") return ""; // Nav has sizing in variant class
|
|
168
139
|
return buttonSizes[size as keyof typeof buttonSizes] || buttonSizes.md;
|
|
169
140
|
})());
|
|
170
141
|
|
|
@@ -185,21 +156,14 @@ let sizeClass = $derived((() => {
|
|
|
185
156
|
return variantClasses[resolvedVariant] || variantClasses.default;
|
|
186
157
|
})());
|
|
187
158
|
|
|
188
|
-
//
|
|
189
|
-
let isLeftAligned = $derived(
|
|
190
|
-
resolvedVariant === "menu-item" ||
|
|
191
|
-
resolvedVariant === "menu-item-danger" ||
|
|
192
|
-
resolvedVariant === "card" ||
|
|
193
|
-
resolvedVariant === "search-result" ||
|
|
194
|
-
resolvedVariant === "chart-row"
|
|
195
|
-
);
|
|
159
|
+
// Button is always centered - left-aligned variants have been extracted to dedicated components
|
|
160
|
+
let isLeftAligned = $derived(false);
|
|
196
161
|
|
|
197
162
|
// Use justify-between when there's trailing content (e.g., chevrons in nav items)
|
|
198
163
|
let hasTrailing = $derived(typeof trailing === 'function' || trailing);
|
|
199
164
|
|
|
200
|
-
//
|
|
201
|
-
let
|
|
202
|
-
let roundedClass = $derived(isLandingVariant ? "rounded-xl" : "rounded-lg");
|
|
165
|
+
// Standard buttons use rounded-lg - landing variants have been extracted to LandingButton
|
|
166
|
+
let roundedClass = $derived("rounded-lg");
|
|
203
167
|
|
|
204
168
|
// Click handler with optional haptic feedback
|
|
205
169
|
function handleClick(e: MouseEvent) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Button/Button.svelte.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"Button.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Button/Button.svelte.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAQpC,UAAU,KAAK;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAoLH,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* CardAction Component
|
|
4
|
+
* Selectable card-like button for list selections.
|
|
5
|
+
*
|
|
6
|
+
* Replaces: Button variant="card"
|
|
7
|
+
*/
|
|
8
|
+
import { twMerge } from 'tailwind-merge';
|
|
9
|
+
import type { Snippet } from 'svelte';
|
|
10
|
+
import { buttonCardSizes } from '../../tokens/sizing.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Size variant */
|
|
14
|
+
size?: 'sm' | 'md' | 'lg';
|
|
15
|
+
/** Disabled state */
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** Selected/active state */
|
|
18
|
+
selected?: boolean;
|
|
19
|
+
/** Content */
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
/** Additional classes */
|
|
22
|
+
class?: string;
|
|
23
|
+
/** Click handler */
|
|
24
|
+
onclick?: (e: MouseEvent) => void;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let {
|
|
29
|
+
size = 'md',
|
|
30
|
+
disabled = false,
|
|
31
|
+
selected = false,
|
|
32
|
+
children,
|
|
33
|
+
class: className = '',
|
|
34
|
+
onclick,
|
|
35
|
+
...restProps
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
const baseClasses = 'w-full text-left rounded-lg font-medium leading-none focus:outline-hidden transition-all duration-150 ease-out select-none flex items-center justify-start';
|
|
39
|
+
const defaultClasses = 'text-gray-900 bg-white border border-gray-300 hover:bg-gray-50 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700';
|
|
40
|
+
const selectedClasses = 'text-gray-900 bg-blue-50 border border-blue-500 dark:bg-blue-900/20 dark:text-white dark:border-blue-500';
|
|
41
|
+
const disabledClasses = 'bg-gray-100 border-gray-200 text-gray-400 cursor-not-allowed dark:bg-gray-700 dark:border-gray-700 dark:text-gray-500';
|
|
42
|
+
|
|
43
|
+
let sizeClass = $derived(buttonCardSizes[size] || buttonCardSizes.md);
|
|
44
|
+
|
|
45
|
+
let variantClass = $derived(() => {
|
|
46
|
+
if (disabled) return disabledClasses;
|
|
47
|
+
if (selected) return selectedClasses;
|
|
48
|
+
return defaultClasses;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
let classes = $derived(twMerge(
|
|
52
|
+
baseClasses,
|
|
53
|
+
sizeClass,
|
|
54
|
+
variantClass(),
|
|
55
|
+
disabled ? 'cursor-not-allowed' : 'cursor-pointer active:scale-[0.99] active:opacity-90',
|
|
56
|
+
className
|
|
57
|
+
));
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
class={classes}
|
|
63
|
+
{disabled}
|
|
64
|
+
{onclick}
|
|
65
|
+
{...restProps}
|
|
66
|
+
>
|
|
67
|
+
{#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
|
|
68
|
+
</button>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Size variant */
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
/** Disabled state */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Selected/active state */
|
|
8
|
+
selected?: boolean;
|
|
9
|
+
/** Content */
|
|
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 CardAction: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type CardAction = ReturnType<typeof CardAction>;
|
|
19
|
+
export default CardAction;
|
|
20
|
+
//# sourceMappingURL=CardAction.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CardAction.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/CardAction/CardAction.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAIpC,UAAU,KAAK;IACb,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,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;AA8CH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -14,52 +14,52 @@ describe('Drawer Component', () => {
|
|
|
14
14
|
document.body.style.overflow = '';
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
test('does not render when
|
|
18
|
-
const { container } = render(Drawer, { props: {
|
|
17
|
+
test('does not render when open is false', () => {
|
|
18
|
+
const { container } = render(Drawer, { props: { open: false } });
|
|
19
19
|
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
test('renders when
|
|
23
|
-
const { container } = render(Drawer, { props: {
|
|
22
|
+
test('renders when open is true', () => {
|
|
23
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
24
24
|
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
test('renders when
|
|
28
|
-
const { container } = render(Drawer, { props: {
|
|
27
|
+
test('renders when open is true (explicit)', () => {
|
|
28
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
29
29
|
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
test('has role="dialog" and aria-modal="true"', () => {
|
|
33
|
-
const { container } = render(Drawer, { props: {
|
|
33
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
34
34
|
const dialog = container.querySelector('[role="dialog"]');
|
|
35
35
|
expect(dialog).toHaveAttribute('aria-modal', 'true');
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test('renders title when provided', () => {
|
|
39
|
-
render(Drawer, { props: {
|
|
39
|
+
render(Drawer, { props: { open: true, title: 'Test Drawer', id: 'test-drawer' } });
|
|
40
40
|
expect(screen.getByText('Test Drawer')).toBeInTheDocument();
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test('applies custom className', () => {
|
|
44
|
-
const { container } = render(Drawer, { props: {
|
|
44
|
+
const { container } = render(Drawer, { props: { open: true, class: 'custom-drawer' } });
|
|
45
45
|
const dialog = container.querySelector('[role="dialog"]');
|
|
46
46
|
expect(dialog).toHaveClass('custom-drawer');
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
test('renders backdrop by default when visible', () => {
|
|
50
|
-
const { container } = render(Drawer, { props: {
|
|
50
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
51
51
|
const backdrop = container.querySelector('[role="presentation"]');
|
|
52
52
|
expect(backdrop).toBeInTheDocument();
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test('does not render backdrop when backdrop is false', () => {
|
|
56
|
-
const { container } = render(Drawer, { props: {
|
|
56
|
+
const { container } = render(Drawer, { props: { open: true, backdrop: false } });
|
|
57
57
|
const backdrop = container.querySelector('[role="presentation"]');
|
|
58
58
|
expect(backdrop).not.toBeInTheDocument();
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
test('passes through id prop', () => {
|
|
62
|
-
const { container } = render(Drawer, { props: {
|
|
62
|
+
const { container } = render(Drawer, { props: { open: true, id: 'my-drawer' } });
|
|
63
63
|
const dialog = container.querySelector('#my-drawer');
|
|
64
64
|
expect(dialog).toBeInTheDocument();
|
|
65
65
|
});
|
|
@@ -67,25 +67,25 @@ describe('Drawer Component', () => {
|
|
|
67
67
|
|
|
68
68
|
describe('Drawer Placements', () => {
|
|
69
69
|
test('left placement has left-0 class', () => {
|
|
70
|
-
const { container } = render(Drawer, { props: {
|
|
70
|
+
const { container } = render(Drawer, { props: { open: true, placement: 'left' } });
|
|
71
71
|
const dialog = container.querySelector('[role="dialog"]');
|
|
72
72
|
expect(dialog).toHaveClass('left-0');
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('right placement has right-0 class', () => {
|
|
76
|
-
const { container } = render(Drawer, { props: {
|
|
76
|
+
const { container } = render(Drawer, { props: { open: true, placement: 'right' } });
|
|
77
77
|
const dialog = container.querySelector('[role="dialog"]');
|
|
78
78
|
expect(dialog).toHaveClass('right-0');
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
test('top placement has top-0 class', () => {
|
|
82
|
-
const { container } = render(Drawer, { props: {
|
|
82
|
+
const { container } = render(Drawer, { props: { open: true, placement: 'top' } });
|
|
83
83
|
const dialog = container.querySelector('[role="dialog"]');
|
|
84
84
|
expect(dialog).toHaveClass('top-0');
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test('bottom placement has bottom-0 class', () => {
|
|
88
|
-
const { container } = render(Drawer, { props: {
|
|
88
|
+
const { container } = render(Drawer, { props: { open: true, placement: 'bottom' } });
|
|
89
89
|
const dialog = container.querySelector('[role="dialog"]');
|
|
90
90
|
expect(dialog).toHaveClass('bottom-0');
|
|
91
91
|
});
|
|
@@ -93,25 +93,25 @@ describe('Drawer Placements', () => {
|
|
|
93
93
|
|
|
94
94
|
describe('Drawer Widths', () => {
|
|
95
95
|
test('sm width applies w-64', () => {
|
|
96
|
-
const { container } = render(Drawer, { props: {
|
|
96
|
+
const { container } = render(Drawer, { props: { open: true, width: 'sm' } });
|
|
97
97
|
const dialog = container.querySelector('[role="dialog"]');
|
|
98
98
|
expect(dialog).toHaveClass('w-64');
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
test('md width applies w-80 (default)', () => {
|
|
102
|
-
const { container } = render(Drawer, { props: {
|
|
102
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
103
103
|
const dialog = container.querySelector('[role="dialog"]');
|
|
104
104
|
expect(dialog).toHaveClass('w-80');
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
test('lg width applies w-96', () => {
|
|
108
|
-
const { container } = render(Drawer, { props: {
|
|
108
|
+
const { container } = render(Drawer, { props: { open: true, width: 'lg' } });
|
|
109
109
|
const dialog = container.querySelector('[role="dialog"]');
|
|
110
110
|
expect(dialog).toHaveClass('w-96');
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
test('full width applies w-full', () => {
|
|
114
|
-
const { container } = render(Drawer, { props: {
|
|
114
|
+
const { container } = render(Drawer, { props: { open: true, width: 'full' } });
|
|
115
115
|
const dialog = container.querySelector('[role="dialog"]');
|
|
116
116
|
expect(dialog).toHaveClass('w-full');
|
|
117
117
|
});
|
|
@@ -119,13 +119,13 @@ describe('Drawer Widths', () => {
|
|
|
119
119
|
|
|
120
120
|
describe('Drawer Accessibility', () => {
|
|
121
121
|
test('has tabindex="-1" for focus management', () => {
|
|
122
|
-
const { container } = render(Drawer, { props: {
|
|
122
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
123
123
|
const dialog = container.querySelector('[role="dialog"]');
|
|
124
124
|
expect(dialog).toHaveAttribute('tabindex', '-1');
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
test('has aria-labelledby when title and id are provided', () => {
|
|
128
|
-
const { container } = render(Drawer, { props: {
|
|
128
|
+
const { container } = render(Drawer, { props: { open: true, title: 'Test', id: 'test-drawer' } });
|
|
129
129
|
const dialog = container.querySelector('[role="dialog"]');
|
|
130
130
|
expect(dialog).toHaveAttribute('aria-labelledby', 'test-drawer-label');
|
|
131
131
|
});
|
|
@@ -133,32 +133,32 @@ describe('Drawer Accessibility', () => {
|
|
|
133
133
|
|
|
134
134
|
describe('Drawer Styling', () => {
|
|
135
135
|
test('has fixed positioning', () => {
|
|
136
|
-
const { container } = render(Drawer, { props: {
|
|
136
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
137
137
|
const dialog = container.querySelector('[role="dialog"]');
|
|
138
138
|
expect(dialog).toHaveClass('fixed');
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
test('has z-40 for stacking', () => {
|
|
142
|
-
const { container } = render(Drawer, { props: {
|
|
142
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
143
143
|
const dialog = container.querySelector('[role="dialog"]');
|
|
144
144
|
expect(dialog).toHaveClass('z-40');
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
test('has flex flex-col layout', () => {
|
|
148
|
-
const { container } = render(Drawer, { props: {
|
|
148
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
149
149
|
const dialog = container.querySelector('[role="dialog"]');
|
|
150
150
|
expect(dialog).toHaveClass('flex');
|
|
151
151
|
expect(dialog).toHaveClass('flex-col');
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
test('has bg-white background', () => {
|
|
155
|
-
const { container } = render(Drawer, { props: {
|
|
155
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
156
156
|
const dialog = container.querySelector('[role="dialog"]');
|
|
157
157
|
expect(dialog).toHaveClass('bg-white');
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
test('has dark mode background', () => {
|
|
161
|
-
const { container } = render(Drawer, { props: {
|
|
161
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
162
162
|
const dialog = container.querySelector('[role="dialog"]');
|
|
163
163
|
expect(dialog).toHaveClass('dark:bg-gray-800');
|
|
164
164
|
});
|
|
@@ -166,13 +166,13 @@ describe('Drawer Styling', () => {
|
|
|
166
166
|
|
|
167
167
|
describe('Drawer Edge Mode', () => {
|
|
168
168
|
test('shows edge trigger when edge=true, placement=bottom, and not visible', () => {
|
|
169
|
-
const { container } = render(Drawer, { props: { edge: true, placement: 'bottom',
|
|
169
|
+
const { container } = render(Drawer, { props: { edge: true, placement: 'bottom', open: false } });
|
|
170
170
|
const edgeTrigger = container.querySelector('.cursor-pointer');
|
|
171
171
|
expect(edgeTrigger).toBeInTheDocument();
|
|
172
172
|
});
|
|
173
173
|
|
|
174
174
|
test('hides edge trigger when drawer is visible', () => {
|
|
175
|
-
const { container } = render(Drawer, { props: { edge: true, placement: 'bottom',
|
|
175
|
+
const { container } = render(Drawer, { props: { edge: true, placement: 'bottom', open: true } });
|
|
176
176
|
// Edge trigger should not be visible when drawer is open
|
|
177
177
|
const edgeTriggers = container.querySelectorAll('.cursor-pointer.fixed');
|
|
178
178
|
// The drawer itself might have cursor classes, so check for the specific edge trigger pattern
|
|
@@ -184,20 +184,20 @@ describe('Drawer Callbacks', () => {
|
|
|
184
184
|
test('onclose prop is accepted', () => {
|
|
185
185
|
const onclose = vi.fn();
|
|
186
186
|
const { container } = render(Drawer, {
|
|
187
|
-
props: {
|
|
187
|
+
props: { open: true, closeOnBackdropClick: true, onclose }
|
|
188
188
|
});
|
|
189
189
|
// Verify drawer renders with onclose callback
|
|
190
190
|
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
|
|
191
191
|
});
|
|
192
192
|
|
|
193
193
|
test('closeOnBackdropClick prop defaults to true', () => {
|
|
194
|
-
const { container } = render(Drawer, { props: {
|
|
194
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
195
195
|
// Drawer renders with default closeOnBackdropClick behavior
|
|
196
196
|
expect(container.querySelector('[role="presentation"]')).toBeInTheDocument();
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
test('closeOnEscape prop defaults to true', () => {
|
|
200
|
-
const { container } = render(Drawer, { props: {
|
|
200
|
+
const { container } = render(Drawer, { props: { open: true } });
|
|
201
201
|
// Drawer accepts closeOnEscape prop
|
|
202
202
|
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
|
|
203
203
|
});
|
|
@@ -205,7 +205,7 @@ describe('Drawer Callbacks', () => {
|
|
|
205
205
|
test('onopen prop is accepted', () => {
|
|
206
206
|
const onopen = vi.fn();
|
|
207
207
|
const { container } = render(Drawer, {
|
|
208
|
-
props: {
|
|
208
|
+
props: { open: true, onopen }
|
|
209
209
|
});
|
|
210
210
|
expect(container.querySelector('[role="dialog"]')).toBeInTheDocument();
|
|
211
211
|
});
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
import type { Snippet } from "svelte";
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
|
-
|
|
9
|
-
hidden?: boolean;
|
|
8
|
+
open?: boolean;
|
|
10
9
|
title?: string;
|
|
11
10
|
placement?: string;
|
|
12
11
|
width?: string;
|
|
@@ -26,8 +25,7 @@
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
let {
|
|
29
|
-
|
|
30
|
-
hidden = $bindable(true),
|
|
28
|
+
open = $bindable(false),
|
|
31
29
|
title = "",
|
|
32
30
|
placement = "left",
|
|
33
31
|
width = "md",
|
|
@@ -46,7 +44,7 @@
|
|
|
46
44
|
...restProps
|
|
47
45
|
}: Props = $props();
|
|
48
46
|
|
|
49
|
-
let isVisible = $derived(
|
|
47
|
+
let isVisible = $derived(open);
|
|
50
48
|
let drawerElement: HTMLElement | null = $state(null);
|
|
51
49
|
let previouslyFocusedElement: Element | null = $state(null);
|
|
52
50
|
|
|
@@ -89,8 +87,7 @@
|
|
|
89
87
|
});
|
|
90
88
|
|
|
91
89
|
function close() {
|
|
92
|
-
|
|
93
|
-
show = false;
|
|
90
|
+
open = false;
|
|
94
91
|
onclose?.();
|
|
95
92
|
}
|
|
96
93
|
|
|
@@ -128,8 +125,7 @@
|
|
|
128
125
|
|
|
129
126
|
function handleEdgeClick() {
|
|
130
127
|
if (edge && placement === "bottom") {
|
|
131
|
-
|
|
132
|
-
show = true;
|
|
128
|
+
open = true;
|
|
133
129
|
onopen?.();
|
|
134
130
|
}
|
|
135
131
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
interface Props {
|
|
3
|
-
|
|
4
|
-
hidden?: boolean;
|
|
3
|
+
open?: boolean;
|
|
5
4
|
title?: string;
|
|
6
5
|
placement?: string;
|
|
7
6
|
width?: string;
|
|
@@ -19,7 +18,7 @@ interface Props {
|
|
|
19
18
|
class?: string;
|
|
20
19
|
[key: string]: unknown;
|
|
21
20
|
}
|
|
22
|
-
declare const Drawer: import("svelte").Component<Props, {}, "
|
|
21
|
+
declare const Drawer: import("svelte").Component<Props, {}, "open">;
|
|
23
22
|
type Drawer = ReturnType<typeof Drawer>;
|
|
24
23
|
export default Drawer;
|
|
25
24
|
//# sourceMappingURL=Drawer.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Drawer.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Drawer/Drawer.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,
|
|
1
|
+
{"version":3,"file":"Drawer.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/Drawer/Drawer.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGpC,UAAU,KAAK;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAmMH,QAAA,MAAM,MAAM,+CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
{:else}
|
|
343
343
|
<div class="relative flex items-center w-full">
|
|
344
344
|
{#if leftIcon}
|
|
345
|
-
<div class="absolute left-3
|
|
345
|
+
<div class="absolute left-3 inset-y-0 flex items-center text-gray-400 z-10 pointer-events-none">
|
|
346
346
|
{@render leftIcon()}
|
|
347
347
|
</div>
|
|
348
348
|
{/if}
|