@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.
Files changed (86) hide show
  1. package/dist/calendar/OrderSummary/OrderSummary.svelte +67 -7
  2. package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts +2 -0
  3. package/dist/calendar/OrderSummary/OrderSummary.svelte.d.ts.map +1 -1
  4. package/dist/index.spec.js +0 -1
  5. package/dist/patterns/navigation/Header.svelte +23 -27
  6. package/dist/patterns/navigation/Header.svelte.d.ts.map +1 -1
  7. package/dist/primitives/AvatarButton/AvatarButton.svelte +57 -0
  8. package/dist/primitives/AvatarButton/AvatarButton.svelte.d.ts +18 -0
  9. package/dist/primitives/AvatarButton/AvatarButton.svelte.d.ts.map +1 -0
  10. package/dist/primitives/BottomSheet/BottomSheet.spec.js +19 -19
  11. package/dist/primitives/BottomSheet/BottomSheet.svelte +5 -5
  12. package/dist/primitives/BottomSheet/BottomSheet.svelte.d.ts +2 -2
  13. package/dist/primitives/BottomSheet/BottomSheet.svelte.d.ts.map +1 -1
  14. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte +3 -3
  15. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts +1 -1
  16. package/dist/primitives/Button/Button.spec.js +16 -14
  17. package/dist/primitives/Button/Button.svelte +9 -45
  18. package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
  19. package/dist/primitives/CardAction/CardAction.svelte +68 -0
  20. package/dist/primitives/CardAction/CardAction.svelte.d.ts +20 -0
  21. package/dist/primitives/CardAction/CardAction.svelte.d.ts.map +1 -0
  22. package/dist/primitives/Drawer/Drawer.spec.js +33 -33
  23. package/dist/primitives/Drawer/Drawer.svelte +5 -9
  24. package/dist/primitives/Drawer/Drawer.svelte.d.ts +2 -3
  25. package/dist/primitives/Drawer/Drawer.svelte.d.ts.map +1 -1
  26. package/dist/primitives/Input/Input.svelte +1 -1
  27. package/dist/primitives/LandingButton/LandingButton.svelte +92 -0
  28. package/dist/primitives/LandingButton/LandingButton.svelte.d.ts +22 -0
  29. package/dist/primitives/LandingButton/LandingButton.svelte.d.ts.map +1 -0
  30. package/dist/primitives/MenuItem/MenuItem.svelte +85 -0
  31. package/dist/primitives/MenuItem/MenuItem.svelte.d.ts +24 -0
  32. package/dist/primitives/MenuItem/MenuItem.svelte.d.ts.map +1 -0
  33. package/dist/primitives/Modal/Modal.spec.js +7 -7
  34. package/dist/primitives/Modal/Modal.stories.svelte +3 -3
  35. package/dist/primitives/Modal/Modal.svelte +25 -18
  36. package/dist/primitives/Modal/Modal.svelte.d.ts +5 -5
  37. package/dist/primitives/Modal/Modal.svelte.d.ts.map +1 -1
  38. package/dist/primitives/Modal/ModalTestWrapper.svelte +3 -3
  39. package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts +2 -2
  40. package/dist/primitives/NavItem/NavItem.svelte +75 -0
  41. package/dist/primitives/NavItem/NavItem.svelte.d.ts +20 -0
  42. package/dist/primitives/NavItem/NavItem.svelte.d.ts.map +1 -0
  43. package/dist/primitives/SearchResultItem/SearchResultItem.svelte +109 -0
  44. package/dist/primitives/SearchResultItem/SearchResultItem.svelte.d.ts +26 -0
  45. package/dist/primitives/SearchResultItem/SearchResultItem.svelte.d.ts.map +1 -0
  46. package/dist/primitives/SidebarToggle/SidebarToggle.svelte +55 -0
  47. package/dist/primitives/SidebarToggle/SidebarToggle.svelte.d.ts +18 -0
  48. package/dist/primitives/SidebarToggle/SidebarToggle.svelte.d.ts.map +1 -0
  49. package/dist/primitives/index.d.ts +7 -0
  50. package/dist/primitives/index.js +21 -0
  51. package/dist/recipes/SuperLogin/SuperLogin.svelte +3 -3
  52. package/dist/recipes/SuperLogin/SuperLogin.svelte.d.ts.map +1 -1
  53. package/dist/recipes/inputs/index.d.ts +0 -1
  54. package/dist/recipes/inputs/index.js +0 -1
  55. package/dist/recipes/modals/AlertModal.spec.js +2 -2
  56. package/dist/recipes/modals/AlertModal.svelte +6 -6
  57. package/dist/recipes/modals/AlertModal.svelte.d.ts +3 -3
  58. package/dist/recipes/modals/ConfirmationModal.spec.js +2 -2
  59. package/dist/recipes/modals/ConfirmationModal.svelte +5 -5
  60. package/dist/recipes/modals/ConfirmationModal.svelte.d.ts +3 -3
  61. package/dist/recipes/modals/InputModal.spec.js +2 -2
  62. package/dist/recipes/modals/InputModal.svelte +4 -4
  63. package/dist/recipes/modals/InputModal.svelte.d.ts +3 -3
  64. package/dist/recipes/modals/ModalTestWrapper.spec.js +49 -49
  65. package/dist/recipes/modals/ModalTestWrapper.svelte +3 -3
  66. package/dist/recipes/modals/ModalTestWrapper.svelte.d.ts +2 -2
  67. package/dist/recipes/modals/StatusModal.spec.js +2 -2
  68. package/dist/recipes/modals/StatusModal.svelte +4 -4
  69. package/dist/recipes/modals/StatusModal.svelte.d.ts +3 -3
  70. package/dist/stories/ComponentConsolidation.stories.svelte +10 -10
  71. package/dist/stories/PrimitivesGallery.svelte +25 -21
  72. package/dist/stories/PrimitivesGallery.svelte.d.ts.map +1 -1
  73. package/dist/stories/RecipesGallery.spec.js +9 -18
  74. package/dist/stories/RecipesGallery.svelte +5 -22
  75. package/dist/stories/RecipesGallery.svelte.d.ts.map +1 -1
  76. package/dist/tokens/__tests__/sizing.test.js +5 -7
  77. package/dist/tokens/sizing.d.ts +20 -19
  78. package/dist/tokens/sizing.d.ts.map +1 -1
  79. package/dist/tokens/sizing.js +20 -19
  80. package/package.json +1 -1
  81. package/dist/recipes/inputs/SelectDropdown.spec.d.ts +0 -2
  82. package/dist/recipes/inputs/SelectDropdown.spec.d.ts.map +0 -1
  83. package/dist/recipes/inputs/SelectDropdown.spec.js +0 -518
  84. package/dist/recipes/inputs/SelectDropdown.svelte +0 -171
  85. package/dist/recipes/inputs/SelectDropdown.svelte.d.ts +0 -16
  86. 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
- test('Active state for nav items', () => {
179
- const { button } = setupTest({ active: true, variant: 'nav' });
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 uses justify-between', () => {
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: 'menu-item',
216
- children: () => 'Menu Item',
215
+ variant: 'default',
216
+ children: () => 'Click Me',
217
217
  trailing,
218
218
  }
219
219
  });
220
220
  const button = container.querySelector('button');
221
- expect(button).toHaveClass('justify-between');
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, toggle, menu-item, and nav
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
- // Menu items, cards, search results, and chart rows need left alignment, others are centered
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
- // Landing variants use rounded-xl for more prominent appearance
201
- let isLandingVariant = $derived(resolvedVariant === "landing" || resolvedVariant === "landing-secondary");
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;AAWpC,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;AAqNH,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,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 show is false and hidden is true', () => {
18
- const { container } = render(Drawer, { props: { show: false, hidden: true } });
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 show is true', () => {
23
- const { container } = render(Drawer, { props: { show: true } });
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 hidden is false', () => {
28
- const { container } = render(Drawer, { props: { hidden: false } });
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: { show: true } });
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: { show: true, title: 'Test Drawer', id: 'test-drawer' } });
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: { show: true, class: 'custom-drawer' } });
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: { show: true } });
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: { show: true, backdrop: false } });
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: { show: true, id: 'my-drawer' } });
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: { show: true, placement: 'left' } });
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: { show: true, placement: 'right' } });
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: { show: true, placement: 'top' } });
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: { show: true, placement: 'bottom' } });
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: { show: true, width: 'sm' } });
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: { show: true } });
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: { show: true, width: 'lg' } });
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: { show: true, width: 'full' } });
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: { show: true } });
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: { show: true, title: 'Test', id: 'test-drawer' } });
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: { show: true } });
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: { show: true } });
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: { show: true } });
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: { show: true } });
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: { show: true } });
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', show: false, hidden: true } });
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', show: true } });
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: { show: true, closeOnBackdropClick: true, onclose }
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: { show: true } });
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: { show: true } });
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: { show: true, onopen }
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
- show?: boolean;
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
- show = $bindable(false),
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(show || !hidden);
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
- hidden = true;
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
- hidden = false;
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
- show?: boolean;
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, {}, "show" | "hidden">;
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,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,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;AAsMH,QAAA,MAAM,MAAM,0DAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
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 top-1/2 transform -translate-y-1/2 text-gray-400 z-10 pointer-events-none">
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}