@getmicdrop/svelte-components 5.12.0 → 5.13.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 (78) hide show
  1. package/dist/index.spec.js +0 -1
  2. package/dist/patterns/navigation/Header.svelte +23 -27
  3. package/dist/patterns/navigation/Header.svelte.d.ts.map +1 -1
  4. package/dist/primitives/AvatarButton/AvatarButton.svelte +57 -0
  5. package/dist/primitives/AvatarButton/AvatarButton.svelte.d.ts +18 -0
  6. package/dist/primitives/AvatarButton/AvatarButton.svelte.d.ts.map +1 -0
  7. package/dist/primitives/BottomSheet/BottomSheet.spec.js +19 -19
  8. package/dist/primitives/BottomSheet/BottomSheet.svelte +5 -5
  9. package/dist/primitives/BottomSheet/BottomSheet.svelte.d.ts +2 -2
  10. package/dist/primitives/BottomSheet/BottomSheet.svelte.d.ts.map +1 -1
  11. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte +3 -3
  12. package/dist/primitives/BottomSheet/BottomSheetWrapper.test.svelte.d.ts +1 -1
  13. package/dist/primitives/Button/Button.spec.js +8 -8
  14. package/dist/primitives/Button/Button.svelte +9 -45
  15. package/dist/primitives/Button/Button.svelte.d.ts.map +1 -1
  16. package/dist/primitives/CardAction/CardAction.svelte +68 -0
  17. package/dist/primitives/CardAction/CardAction.svelte.d.ts +20 -0
  18. package/dist/primitives/CardAction/CardAction.svelte.d.ts.map +1 -0
  19. package/dist/primitives/Drawer/Drawer.spec.js +33 -33
  20. package/dist/primitives/Drawer/Drawer.svelte +5 -9
  21. package/dist/primitives/Drawer/Drawer.svelte.d.ts +2 -3
  22. package/dist/primitives/Drawer/Drawer.svelte.d.ts.map +1 -1
  23. package/dist/primitives/LandingButton/LandingButton.svelte +92 -0
  24. package/dist/primitives/LandingButton/LandingButton.svelte.d.ts +22 -0
  25. package/dist/primitives/LandingButton/LandingButton.svelte.d.ts.map +1 -0
  26. package/dist/primitives/MenuItem/MenuItem.svelte +85 -0
  27. package/dist/primitives/MenuItem/MenuItem.svelte.d.ts +24 -0
  28. package/dist/primitives/MenuItem/MenuItem.svelte.d.ts.map +1 -0
  29. package/dist/primitives/Modal/Modal.spec.js +7 -7
  30. package/dist/primitives/Modal/Modal.stories.svelte +3 -3
  31. package/dist/primitives/Modal/Modal.svelte +25 -18
  32. package/dist/primitives/Modal/Modal.svelte.d.ts +5 -5
  33. package/dist/primitives/Modal/Modal.svelte.d.ts.map +1 -1
  34. package/dist/primitives/Modal/ModalTestWrapper.svelte +3 -3
  35. package/dist/primitives/Modal/ModalTestWrapper.svelte.d.ts +2 -2
  36. package/dist/primitives/NavItem/NavItem.svelte +75 -0
  37. package/dist/primitives/NavItem/NavItem.svelte.d.ts +20 -0
  38. package/dist/primitives/NavItem/NavItem.svelte.d.ts.map +1 -0
  39. package/dist/primitives/SearchResultItem/SearchResultItem.svelte +109 -0
  40. package/dist/primitives/SearchResultItem/SearchResultItem.svelte.d.ts +26 -0
  41. package/dist/primitives/SearchResultItem/SearchResultItem.svelte.d.ts.map +1 -0
  42. package/dist/primitives/SidebarToggle/SidebarToggle.svelte +55 -0
  43. package/dist/primitives/SidebarToggle/SidebarToggle.svelte.d.ts +18 -0
  44. package/dist/primitives/SidebarToggle/SidebarToggle.svelte.d.ts.map +1 -0
  45. package/dist/primitives/index.d.ts +7 -0
  46. package/dist/primitives/index.js +21 -0
  47. package/dist/recipes/SuperLogin/SuperLogin.svelte +3 -3
  48. package/dist/recipes/SuperLogin/SuperLogin.svelte.d.ts.map +1 -1
  49. package/dist/recipes/inputs/index.d.ts +0 -1
  50. package/dist/recipes/inputs/index.js +0 -1
  51. package/dist/recipes/modals/AlertModal.spec.js +2 -2
  52. package/dist/recipes/modals/AlertModal.svelte +6 -6
  53. package/dist/recipes/modals/AlertModal.svelte.d.ts +3 -3
  54. package/dist/recipes/modals/ConfirmationModal.spec.js +2 -2
  55. package/dist/recipes/modals/ConfirmationModal.svelte +5 -5
  56. package/dist/recipes/modals/ConfirmationModal.svelte.d.ts +3 -3
  57. package/dist/recipes/modals/InputModal.spec.js +2 -2
  58. package/dist/recipes/modals/InputModal.svelte +4 -4
  59. package/dist/recipes/modals/InputModal.svelte.d.ts +3 -3
  60. package/dist/recipes/modals/ModalTestWrapper.spec.js +49 -49
  61. package/dist/recipes/modals/ModalTestWrapper.svelte +3 -3
  62. package/dist/recipes/modals/ModalTestWrapper.svelte.d.ts +2 -2
  63. package/dist/recipes/modals/StatusModal.spec.js +2 -2
  64. package/dist/recipes/modals/StatusModal.svelte +4 -4
  65. package/dist/recipes/modals/StatusModal.svelte.d.ts +3 -3
  66. package/dist/stories/ComponentConsolidation.stories.svelte +10 -10
  67. package/dist/stories/PrimitivesGallery.svelte +25 -21
  68. package/dist/stories/PrimitivesGallery.svelte.d.ts.map +1 -1
  69. package/dist/stories/RecipesGallery.spec.js +9 -18
  70. package/dist/stories/RecipesGallery.svelte +5 -22
  71. package/dist/stories/RecipesGallery.svelte.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/dist/recipes/inputs/SelectDropdown.spec.d.ts +0 -2
  74. package/dist/recipes/inputs/SelectDropdown.spec.d.ts.map +0 -1
  75. package/dist/recipes/inputs/SelectDropdown.spec.js +0 -518
  76. package/dist/recipes/inputs/SelectDropdown.svelte +0 -171
  77. package/dist/recipes/inputs/SelectDropdown.svelte.d.ts +0 -16
  78. package/dist/recipes/inputs/SelectDropdown.svelte.d.ts.map +0 -1
@@ -112,7 +112,6 @@ describe('Recipes Layer - Intermediate Index Files', () => {
112
112
  expect(exports).toContain('PasswordStrengthIndicator');
113
113
  expect(exports).toContain('PlaceAutocomplete');
114
114
  expect(exports).toContain('Search');
115
- expect(exports).toContain('SelectDropdown');
116
115
  });
117
116
  });
118
117
 
@@ -8,6 +8,8 @@
8
8
  import Icon from "../../primitives/Icons/Icon.svelte";
9
9
  import ChevronLeft from "../../primitives/Icons/ChevronLeft.svelte";
10
10
  import Button from "../../primitives/Button/Button.svelte";
11
+ import MenuItem from "../../primitives/MenuItem/MenuItem.svelte";
12
+ import AvatarButton from "../../primitives/AvatarButton/AvatarButton.svelte";
11
13
  import { fade, fly } from "svelte/transition";
12
14
  import { cubicOut } from "svelte/easing";
13
15
  import { portal } from "../../utils/portal.js";
@@ -89,13 +91,12 @@
89
91
  <div class="flex items-center gap-3">
90
92
  <DarkModeToggle />
91
93
  <div class="relative ml-1">
92
- <Button
93
- variant="avatar"
94
+ <AvatarButton
94
95
  size="md"
95
96
  onclick={() => showDesktopDropdown = !showDesktopDropdown}
96
97
  >
97
98
  <Avatar src={avatar} rounded size="md" />
98
- </Button>
99
+ </AvatarButton>
99
100
 
100
101
  {#if showDesktopDropdown}
101
102
  <!-- svelte-ignore a11y_click_events_have_key_events -->
@@ -107,22 +108,21 @@
107
108
  <span class={`block ${typography.xsMuted} mt-0.5`}>{email || ""}</span>
108
109
  </div>
109
110
  {#each dropdownLinks as { label, href }}
110
- <Button
111
- variant="menu-item"
111
+ <MenuItem
112
112
  size="md"
113
113
  onclick={() => handleDropdownItemClick(href)}
114
114
  >
115
115
  {label}
116
- </Button>
116
+ </MenuItem>
117
117
  {/each}
118
118
  <div class="h-px bg-gray-200 dark:bg-gray-700"></div>
119
- <Button
120
- variant="menu-item-danger"
119
+ <MenuItem
120
+ danger
121
121
  size="md"
122
122
  onclick={() => { showDesktopDropdown = false; signoutHandler(); }}
123
123
  >
124
124
  Sign out
125
- </Button>
125
+ </MenuItem>
126
126
  </div>
127
127
  {/if}
128
128
  </div>
@@ -150,23 +150,21 @@
150
150
  <DarkModeToggle />
151
151
 
152
152
  <div class="relative ml-1">
153
- <Button
154
- variant="avatar"
153
+ <AvatarButton
155
154
  size="md"
156
155
  class="block md:hidden"
157
156
  onclick={() => showMobileSheet = true}
158
157
  >
159
158
  <Avatar src={avatar} rounded size="md" />
160
- </Button>
159
+ </AvatarButton>
161
160
 
162
- <Button
163
- variant="avatar"
161
+ <AvatarButton
164
162
  size="md"
165
163
  class="hidden md:block"
166
164
  onclick={() => showDesktopDropdown = !showDesktopDropdown}
167
165
  >
168
166
  <Avatar src={avatar} rounded size="md" />
169
- </Button>
167
+ </AvatarButton>
170
168
 
171
169
  {#if showDesktopDropdown}
172
170
  <!-- svelte-ignore a11y_click_events_have_key_events -->
@@ -178,22 +176,21 @@
178
176
  <span class={`block ${typography.xsMuted} mt-0.5`}>{email || ""}</span>
179
177
  </div>
180
178
  {#each dropdownLinks as { label, href }}
181
- <Button
182
- variant="menu-item"
179
+ <MenuItem
183
180
  size="md"
184
181
  onclick={() => handleDropdownItemClick(href)}
185
182
  >
186
183
  {label}
187
- </Button>
184
+ </MenuItem>
188
185
  {/each}
189
186
  <div class="h-px bg-gray-200 dark:bg-gray-700"></div>
190
- <Button
191
- variant="menu-item-danger"
187
+ <MenuItem
188
+ danger
192
189
  size="md"
193
190
  onclick={() => { showDesktopDropdown = false; signoutHandler(); }}
194
191
  >
195
192
  Sign out
196
- </Button>
193
+ </MenuItem>
197
194
  </div>
198
195
  {/if}
199
196
  </div>
@@ -227,22 +224,21 @@
227
224
 
228
225
  <div class="py-2">
229
226
  {#each dropdownLinks as { label, href }}
230
- <Button
231
- variant="menu-item"
227
+ <MenuItem
232
228
  size="lg"
233
229
  onclick={() => handleMobileSheetItemClick(href)}
234
230
  >
235
231
  {label}
236
- </Button>
232
+ </MenuItem>
237
233
  {/each}
238
234
 
239
- <Button
240
- variant="menu-item-danger"
235
+ <MenuItem
236
+ danger
241
237
  size="lg"
242
238
  onclick={() => { showMobileSheet = false; signoutHandler(); }}
243
239
  >
244
240
  Sign Out
245
- </Button>
241
+ </MenuItem>
246
242
  </div>
247
243
 
248
244
  <div class="border-t-8 border-gray-100 dark:border-gray-700">
@@ -1 +1 @@
1
- {"version":3,"file":"Header.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/patterns/navigation/Header.svelte.ts"],"names":[],"mappings":"AAkBE,UAAU,OAAO;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,KAAK;IACb,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAiMH,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"Header.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/patterns/navigation/Header.svelte.ts"],"names":[],"mappings":"AAoBE,UAAU,OAAO;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,KAAK;IACb,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAmMH,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AvatarButton Component
4
+ * Clickable avatar/image trigger with opacity hover effect.
5
+ *
6
+ * Replaces: Button variant="avatar"
7
+ */
8
+ import { twMerge } from 'tailwind-merge';
9
+ import type { Snippet } from 'svelte';
10
+ import { buttonAvatarSizes } from '../../tokens/sizing.js';
11
+
12
+ interface Props {
13
+ /** Size variant */
14
+ size?: 'sm' | 'md' | 'lg';
15
+ /** Disabled state */
16
+ disabled?: boolean;
17
+ /** Content (typically an Avatar component or image) */
18
+ children?: Snippet;
19
+ /** Additional classes */
20
+ class?: string;
21
+ /** Click handler */
22
+ onclick?: (e: MouseEvent) => void;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ let {
27
+ size = 'md',
28
+ disabled = false,
29
+ children,
30
+ class: className = '',
31
+ onclick,
32
+ ...restProps
33
+ }: Props = $props();
34
+
35
+ const baseClasses = 'bg-transparent border-transparent rounded-lg focus:outline-hidden transition-all duration-150 ease-out select-none inline-flex items-center justify-center';
36
+ const hoverClasses = 'hover:opacity-80';
37
+ const disabledClasses = 'opacity-50 cursor-not-allowed';
38
+
39
+ let sizeClass = $derived(buttonAvatarSizes[size] || buttonAvatarSizes.md);
40
+
41
+ let classes = $derived(twMerge(
42
+ baseClasses,
43
+ sizeClass,
44
+ disabled ? disabledClasses : `${hoverClasses} cursor-pointer active:scale-[0.97] active:opacity-90`,
45
+ className
46
+ ));
47
+ </script>
48
+
49
+ <button
50
+ type="button"
51
+ class={classes}
52
+ {disabled}
53
+ {onclick}
54
+ {...restProps}
55
+ >
56
+ {#if typeof children === 'function'}{@render children()}{:else if children}{children}{/if}
57
+ </button>
@@ -0,0 +1,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ /** Size variant */
4
+ size?: 'sm' | 'md' | 'lg';
5
+ /** Disabled state */
6
+ disabled?: boolean;
7
+ /** Content (typically an Avatar component or image) */
8
+ children?: Snippet;
9
+ /** Additional classes */
10
+ class?: string;
11
+ /** Click handler */
12
+ onclick?: (e: MouseEvent) => void;
13
+ [key: string]: unknown;
14
+ }
15
+ declare const AvatarButton: import("svelte").Component<Props, {}, "">;
16
+ type AvatarButton = ReturnType<typeof AvatarButton>;
17
+ export default AvatarButton;
18
+ //# sourceMappingURL=AvatarButton.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AvatarButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/AvatarButton/AvatarButton.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,uDAAuD;IACvD,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;AAqCH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -14,44 +14,44 @@ describe('BottomSheet Component', () => {
14
14
  cleanup();
15
15
  });
16
16
 
17
- it('does not render when show is false', () => {
18
- const { container } = render(BottomSheet, { props: { show: false } });
17
+ it('does not render when open is false', () => {
18
+ const { container } = render(BottomSheet, { props: { open: false } });
19
19
  const backdrop = container.querySelector('.fixed.inset-0');
20
20
  expect(backdrop).toBeFalsy();
21
21
  });
22
22
 
23
- it('renders when show is true', () => {
24
- const { container } = render(BottomSheet, { props: { show: true } });
23
+ it('renders when open is true', () => {
24
+ const { container } = render(BottomSheet, { props: { open: true } });
25
25
  const backdrop = container.querySelector('.fixed.inset-0');
26
26
  expect(backdrop).toBeTruthy();
27
27
  });
28
28
 
29
29
  it('renders title when provided', () => {
30
30
  const { getByText } = render(BottomSheet, {
31
- props: { show: true, title: 'Test Title' },
31
+ props: { open: true, title: 'Test Title' },
32
32
  });
33
33
  expect(getByText('Test Title')).toBeTruthy();
34
34
  });
35
35
 
36
36
  it('does not render title when not provided', () => {
37
- const { container } = render(BottomSheet, { props: { show: true } });
37
+ const { container } = render(BottomSheet, { props: { open: true } });
38
38
  const title = container.querySelector('h3');
39
39
  expect(title).toBeFalsy();
40
40
  });
41
41
 
42
- it('locks body scroll when shown', async () => {
43
- render(BottomSheet, { props: { show: true } });
42
+ it('locks body scroll when open', async () => {
43
+ render(BottomSheet, { props: { open: true } });
44
44
  // Wait for reactive update
45
45
  await new Promise((resolve) => setTimeout(resolve, 10));
46
46
  expect(document.body.style.overflow).toBe('hidden');
47
47
  });
48
48
 
49
- it('unlocks body scroll when hidden', async () => {
50
- const { rerender } = render(BottomSheetWrapper, { props: { show: true } });
49
+ it('unlocks body scroll when closed', async () => {
50
+ const { rerender } = render(BottomSheetWrapper, { props: { open: true } });
51
51
  await new Promise((resolve) => setTimeout(resolve, 10));
52
52
  expect(document.body.style.overflow).toBe('hidden');
53
53
 
54
- await rerender({ show: false });
54
+ await rerender({ open: false });
55
55
  await new Promise((resolve) => setTimeout(resolve, 10));
56
56
 
57
57
  expect(document.body.style.overflow).toBe('');
@@ -64,7 +64,7 @@ describe('BottomSheet Component', () => {
64
64
  };
65
65
 
66
66
  const { container } = render(BottomSheet, {
67
- props: { show: true, onclose }
67
+ props: { open: true, onclose }
68
68
  });
69
69
  const backdrop = container.querySelector('.fixed.inset-0');
70
70
 
@@ -79,7 +79,7 @@ describe('BottomSheet Component', () => {
79
79
  };
80
80
 
81
81
  const { container } = render(BottomSheet, {
82
- props: { show: true, onclose }
82
+ props: { open: true, onclose }
83
83
  });
84
84
  const sheet = container.querySelector('.bg-white, .dark\\:bg-gray-800');
85
85
 
@@ -93,33 +93,33 @@ describe('BottomSheet Component', () => {
93
93
  closeCalled = true;
94
94
  };
95
95
 
96
- render(BottomSheet, { props: { show: true, onclose } });
96
+ render(BottomSheet, { props: { open: true, onclose } });
97
97
 
98
98
  await fireEvent.keyDown(window, { key: 'Escape' });
99
99
  expect(closeCalled).toBe(true);
100
100
  });
101
101
 
102
- it('does not dispatch close on Escape when not shown', async () => {
102
+ it('does not dispatch close on Escape when not open', async () => {
103
103
  let closeCalled = false;
104
104
  const onclose = () => {
105
105
  closeCalled = true;
106
106
  };
107
107
 
108
- render(BottomSheet, { props: { show: false, onclose } });
108
+ render(BottomSheet, { props: { open: false, onclose } });
109
109
 
110
110
  await fireEvent.keyDown(window, { key: 'Escape' });
111
111
  expect(closeCalled).toBe(false);
112
112
  });
113
113
 
114
114
  it('has handle bar element', () => {
115
- const { container } = render(BottomSheet, { props: { show: true } });
115
+ const { container } = render(BottomSheet, { props: { open: true } });
116
116
  // The handle is the gray bar with rounded corners
117
117
  const handle = container.querySelector('.w-10.h-1.bg-gray-300');
118
118
  expect(handle).toBeTruthy();
119
119
  });
120
120
 
121
121
  it('has content area for slot content', () => {
122
- const { container } = render(BottomSheet, { props: { show: true } });
122
+ const { container } = render(BottomSheet, { props: { open: true } });
123
123
  // The content area has overflow-y-auto class
124
124
  const content = container.querySelector('.overflow-y-auto');
125
125
  expect(content).toBeTruthy();
@@ -127,7 +127,7 @@ describe('BottomSheet Component', () => {
127
127
 
128
128
  it('cleans up body overflow on destroy', () => {
129
129
  document.body.style.overflow = 'hidden';
130
- const { unmount } = render(BottomSheet, { props: { show: true } });
130
+ const { unmount } = render(BottomSheet, { props: { open: true } });
131
131
 
132
132
  unmount();
133
133
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  interface Props {
9
9
  /** Whether the bottom sheet is visible */
10
- show?: boolean;
10
+ open?: boolean;
11
11
  /** Title displayed in the header */
12
12
  title?: string;
13
13
  /** Callback when the sheet should close */
@@ -19,7 +19,7 @@
19
19
  }
20
20
 
21
21
  let {
22
- show = false,
22
+ open = $bindable(false),
23
23
  title = '',
24
24
  onclose,
25
25
  children,
@@ -37,14 +37,14 @@
37
37
  }
38
38
 
39
39
  function handleKeydown(e: KeyboardEvent) {
40
- if (e.key === "Escape" && show) {
40
+ if (e.key === "Escape" && open) {
41
41
  close();
42
42
  }
43
43
  }
44
44
 
45
45
  $effect(() => {
46
46
  if (typeof document !== "undefined") {
47
- if (show) {
47
+ if (open) {
48
48
  document.body.style.overflow = "hidden";
49
49
  } else {
50
50
  document.body.style.overflow = "";
@@ -61,7 +61,7 @@
61
61
 
62
62
  <svelte:window onkeydown={handleKeydown} />
63
63
 
64
- {#if show}
64
+ {#if open}
65
65
  <!-- svelte-ignore a11y_click_events_have_key_events -->
66
66
  <!-- svelte-ignore a11y_no_static_element_interactions -->
67
67
  <div
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  interface Props {
3
3
  /** Whether the bottom sheet is visible */
4
- show?: boolean;
4
+ open?: boolean;
5
5
  /** Title displayed in the header */
6
6
  title?: string;
7
7
  /** Callback when the sheet should close */
@@ -11,7 +11,7 @@ interface Props {
11
11
  /** Actions slot (footer buttons) */
12
12
  actions?: Snippet;
13
13
  }
14
- declare const BottomSheet: import("svelte").Component<Props, {}, "">;
14
+ declare const BottomSheet: import("svelte").Component<Props, {}, "open">;
15
15
  type BottomSheet = ReturnType<typeof BottomSheet>;
16
16
  export default BottomSheet;
17
17
  //# sourceMappingURL=BottomSheet.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheet.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/BottomSheet/BottomSheet.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAIpC,UAAU,KAAK;IACb,0CAA0C;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAoFH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"BottomSheet.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/primitives/BottomSheet/BottomSheet.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAIpC,UAAU,KAAK;IACb,0CAA0C;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAoFH,QAAA,MAAM,WAAW,+CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -2,12 +2,12 @@
2
2
  import BottomSheet from './BottomSheet.svelte';
3
3
 
4
4
  interface Props {
5
- show?: boolean;
5
+ open?: boolean;
6
6
  }
7
7
 
8
- let { show = false }: Props = $props();
8
+ let { open = false }: Props = $props();
9
9
  </script>
10
10
 
11
- <BottomSheet {show}>
11
+ <BottomSheet {open}>
12
12
  <div>Test content</div>
13
13
  </BottomSheet>
@@ -1,5 +1,5 @@
1
1
  interface Props {
2
- show?: boolean;
2
+ open?: boolean;
3
3
  }
4
4
  declare const BottomSheetWrapper: import("svelte").Component<Props, {}, "">;
5
5
  type BottomSheetWrapper = ReturnType<typeof BottomSheetWrapper>;
@@ -175,10 +175,8 @@ describe('Button States', () => {
175
175
  expect(button.querySelector('svg')).toBeInTheDocument();
176
176
  });
177
177
 
178
- test('Active state for nav items', () => {
179
- const { button } = setupTest({ active: true, variant: 'nav' });
180
- expect(button).toHaveClass('text-blue-600');
181
- });
178
+ // Note: nav variant has been extracted to NavItem component
179
+ // This tests that unknown variants fall back to default styling
182
180
 
183
181
  test('Active state for toggle variant', () => {
184
182
  const { button } = setupTest({ active: true, variant: 'toggle' });
@@ -208,16 +206,18 @@ describe('Button as Link', () => {
208
206
  });
209
207
 
210
208
  describe('Button with Trailing Content', () => {
211
- test('Button with trailing content uses justify-between', () => {
209
+ test('Button with trailing content stays centered (extracted variants use justify-between)', () => {
212
210
  const trailing = () => '→';
213
211
  const { container } = render(Button, {
214
212
  props: {
215
- variant: 'menu-item',
216
- children: () => 'Menu Item',
213
+ variant: 'default',
214
+ children: () => 'Click Me',
217
215
  trailing,
218
216
  }
219
217
  });
220
218
  const button = container.querySelector('button');
221
- expect(button).toHaveClass('justify-between');
219
+ // Standard Button variants are always centered - left-aligned behavior
220
+ // has been extracted to dedicated components (MenuItem, NavItem, etc.)
221
+ expect(button).toHaveClass('justify-center');
222
222
  });
223
223
  });
@@ -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>