@makolabs/ripple 2.5.8 → 3.0.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 (184) hide show
  1. package/README.md +403 -497
  2. package/dist/adapters/storage/S3Adapter.d.ts +49 -1
  3. package/dist/adapters/storage/S3Adapter.js +38 -1
  4. package/dist/adapters/storage/types.d.ts +20 -0
  5. package/dist/ai/AIChatInterface.svelte +2 -1
  6. package/dist/ai/AIChatInterface.svelte.d.ts +2 -1
  7. package/dist/ai/CodeRenderer.svelte +7 -2
  8. package/dist/ai/CodeRenderer.svelte.d.ts +2 -1
  9. package/dist/ai/ComposeDropdown.svelte +1 -1
  10. package/dist/ai/MessageBox.svelte +3 -3
  11. package/dist/ai/MessageBox.svelte.d.ts +3 -2
  12. package/dist/ai/ThinkingDisplay.svelte +4 -3
  13. package/dist/ai/ThinkingDisplay.svelte.d.ts +2 -1
  14. package/dist/ai/ai-types.d.ts +55 -1
  15. package/dist/button/Button.svelte +5 -5
  16. package/dist/button/button-types.d.ts +49 -4
  17. package/dist/button/button.d.ts +9 -9
  18. package/dist/button/button.js +6 -6
  19. package/dist/charts/Chart.svelte +8 -16
  20. package/dist/charts/chart-types.d.ts +78 -1
  21. package/dist/drawer/Drawer.svelte +6 -26
  22. package/dist/drawer/drawer-types.d.ts +33 -12
  23. package/dist/drawer/drawer.d.ts +3 -3
  24. package/dist/drawer/drawer.js +1 -1
  25. package/dist/elements/accordion/Accordion.svelte +6 -17
  26. package/dist/elements/accordion/accordion-types.d.ts +53 -6
  27. package/dist/elements/alert/Alert.svelte +3 -0
  28. package/dist/elements/badge/Badge.svelte +1 -1
  29. package/dist/elements/badge/badge-types.d.ts +22 -0
  30. package/dist/elements/badge/badge.d.ts +3 -3
  31. package/dist/elements/badge/badge.js +1 -1
  32. package/dist/elements/combobox/ComboBox.svelte +247 -0
  33. package/dist/elements/combobox/ComboBox.svelte.d.ts +4 -0
  34. package/dist/elements/combobox/combobox-types.d.ts +41 -0
  35. package/dist/elements/combobox/combobox-types.js +1 -0
  36. package/dist/elements/context-menu/ContextMenu.svelte +137 -0
  37. package/dist/elements/context-menu/ContextMenu.svelte.d.ts +4 -0
  38. package/dist/elements/context-menu/context-menu-types.d.ts +40 -0
  39. package/dist/elements/context-menu/context-menu-types.js +1 -0
  40. package/dist/elements/dropdown/Dropdown.svelte +1 -1
  41. package/dist/elements/dropdown/Select.svelte +4 -1
  42. package/dist/elements/dropdown/dropdown-types.d.ts +114 -0
  43. package/dist/elements/dropdown/dropdown.d.ts +3 -3
  44. package/dist/elements/dropdown/dropdown.js +2 -2
  45. package/dist/elements/dropdown/select.d.ts +3 -3
  46. package/dist/elements/dropdown/select.js +2 -2
  47. package/dist/elements/empty-state/EmptyState.svelte +1 -1
  48. package/dist/elements/empty-state/empty-state-types.d.ts +32 -1
  49. package/dist/elements/empty-state/empty-state.d.ts +3 -3
  50. package/dist/elements/empty-state/empty-state.js +2 -2
  51. package/dist/elements/file-upload/FileUpload.svelte +5 -0
  52. package/dist/elements/file-upload/file-upload-types.d.ts +59 -0
  53. package/dist/elements/pagination/Pagination.svelte +53 -21
  54. package/dist/elements/pagination/Pagination.svelte.d.ts +33 -5
  55. package/dist/elements/popover/Popover.svelte +234 -0
  56. package/dist/elements/popover/Popover.svelte.d.ts +4 -0
  57. package/dist/elements/popover/index.d.ts +2 -0
  58. package/dist/elements/popover/index.js +1 -0
  59. package/dist/elements/popover/popover-types.d.ts +60 -0
  60. package/dist/elements/popover/popover-types.js +1 -0
  61. package/dist/elements/progress/Progress.svelte +32 -7
  62. package/dist/elements/progress/progress-types.d.ts +48 -1
  63. package/dist/elements/skeleton/Skeleton.svelte +56 -0
  64. package/dist/elements/skeleton/Skeleton.svelte.d.ts +4 -0
  65. package/dist/elements/skeleton/index.d.ts +2 -0
  66. package/dist/elements/skeleton/index.js +1 -0
  67. package/dist/elements/skeleton/skeleton-types.d.ts +50 -0
  68. package/dist/elements/skeleton/skeleton-types.js +1 -0
  69. package/dist/elements/spinner/Spinner.svelte +1 -1
  70. package/dist/elements/spinner/spinner-types.d.ts +20 -0
  71. package/dist/elements/spinner/spinner.d.ts +3 -3
  72. package/dist/elements/spinner/spinner.js +2 -2
  73. package/dist/elements/tooltip/Tooltip.svelte +108 -11
  74. package/dist/elements/tooltip/tooltip-types.d.ts +49 -1
  75. package/dist/file-browser/FileBrowser.svelte +21 -12
  76. package/dist/filters/CompactFilters.svelte +221 -33
  77. package/dist/filters/CompactFilters.svelte.d.ts +1 -1
  78. package/dist/filters/FilterBar.svelte +184 -0
  79. package/dist/filters/FilterBar.svelte.d.ts +4 -0
  80. package/dist/filters/FilterPopover.svelte +346 -0
  81. package/dist/filters/FilterPopover.svelte.d.ts +4 -0
  82. package/dist/filters/date-presets.d.ts +15 -0
  83. package/dist/filters/date-presets.js +107 -0
  84. package/dist/filters/filter-types.d.ts +69 -3
  85. package/dist/filters/index.d.ts +5 -0
  86. package/dist/filters/index.js +4 -0
  87. package/dist/filters/sync-filters-to-url.svelte.d.ts +37 -0
  88. package/dist/filters/sync-filters-to-url.svelte.js +114 -0
  89. package/dist/forms/DateRange.svelte +4 -2
  90. package/dist/forms/Input.svelte +2 -2
  91. package/dist/forms/MarketSelector.svelte +8 -3
  92. package/dist/forms/NumberInput.svelte +4 -4
  93. package/dist/forms/RadioGroup.svelte +123 -0
  94. package/dist/forms/RadioGroup.svelte.d.ts +4 -0
  95. package/dist/forms/SegmentedControl.svelte +11 -4
  96. package/dist/forms/Slider.svelte +72 -3
  97. package/dist/forms/Tags.svelte +14 -5
  98. package/dist/forms/Textarea.svelte +126 -0
  99. package/dist/forms/Textarea.svelte.d.ts +4 -0
  100. package/dist/forms/Toggle.svelte +8 -8
  101. package/dist/forms/calendar/Calendar.svelte +218 -0
  102. package/dist/forms/calendar/Calendar.svelte.d.ts +4 -0
  103. package/dist/forms/calendar/calendar-types.d.ts +46 -0
  104. package/dist/forms/calendar/calendar-types.js +1 -0
  105. package/dist/forms/calendar/index.d.ts +2 -0
  106. package/dist/forms/calendar/index.js +1 -0
  107. package/dist/forms/date-picker/DatePicker.svelte +144 -0
  108. package/dist/forms/date-picker/DatePicker.svelte.d.ts +4 -0
  109. package/dist/forms/date-picker/date-picker-types.d.ts +29 -0
  110. package/dist/forms/date-picker/date-picker-types.js +1 -0
  111. package/dist/forms/form-types.d.ts +425 -6
  112. package/dist/forms/market/market-selector-types.d.ts +52 -1
  113. package/dist/forms/segmented-control.d.ts +5 -2
  114. package/dist/forms/segmented-control.js +16 -5
  115. package/dist/forms/slider.d.ts +3 -3
  116. package/dist/forms/slider.js +2 -2
  117. package/dist/funcs/user-management.remote.d.ts +1 -1
  118. package/dist/funcs/user-management.remote.js +2 -2
  119. package/dist/header/Breadcrumbs.svelte +4 -20
  120. package/dist/header/PageHeader.svelte +6 -14
  121. package/dist/header/breadcrumbs.d.ts +3 -11
  122. package/dist/header/breadcrumbs.js +10 -5
  123. package/dist/header/header-types.d.ts +62 -11
  124. package/dist/index.d.ts +35 -9
  125. package/dist/index.js +24 -4
  126. package/dist/layout/activity-list/ActivityList.svelte +13 -7
  127. package/dist/layout/activity-list/activity-list-types.d.ts +46 -7
  128. package/dist/layout/card/Card.svelte +12 -15
  129. package/dist/layout/card/MetricCard.svelte +50 -32
  130. package/dist/layout/card/card-types.d.ts +114 -4
  131. package/dist/layout/navbar/navbar-types.d.ts +48 -0
  132. package/dist/layout/navbar/navbar.d.ts +3 -3
  133. package/dist/layout/navbar/navbar.js +2 -2
  134. package/dist/layout/sidebar/Sidebar.svelte +87 -11
  135. package/dist/layout/sidebar/sidebar-types.d.ts +60 -1
  136. package/dist/layout/stepper/Stepper.svelte +288 -0
  137. package/dist/layout/stepper/Stepper.svelte.d.ts +4 -0
  138. package/dist/layout/stepper/stepper-types.d.ts +80 -0
  139. package/dist/layout/stepper/stepper-types.js +1 -0
  140. package/dist/layout/table/Table.svelte +91 -85
  141. package/dist/layout/table/table-types.d.ts +148 -24
  142. package/dist/layout/table/table.d.ts +3 -3
  143. package/dist/layout/table/table.js +2 -2
  144. package/dist/layout/tabs/Tab.svelte +6 -2
  145. package/dist/layout/tabs/Tab.svelte.d.ts +4 -1
  146. package/dist/layout/tabs/TabGroup.svelte +9 -2
  147. package/dist/layout/tabs/tabs-types.d.ts +63 -0
  148. package/dist/layout/tabs/tabs.d.ts +3 -3
  149. package/dist/layout/tabs/tabs.js +12 -6
  150. package/dist/modal/ConfirmDialog.svelte +65 -0
  151. package/dist/modal/ConfirmDialog.svelte.d.ts +4 -0
  152. package/dist/modal/Modal.svelte +6 -26
  153. package/dist/modal/confirm-dialog-types.d.ts +39 -0
  154. package/dist/modal/confirm-dialog-types.js +1 -0
  155. package/dist/modal/modal-types.d.ts +51 -12
  156. package/dist/modal/modal.d.ts +3 -3
  157. package/dist/modal/modal.js +3 -3
  158. package/dist/pipeline/Pipeline.svelte +8 -3
  159. package/dist/pipeline/pipeline-types.d.ts +55 -3
  160. package/dist/pipeline/pipeline.d.ts +18 -3
  161. package/dist/pipeline/pipeline.js +7 -2
  162. package/dist/server/s3.d.ts +35 -3
  163. package/dist/sonner/Toaster.svelte +29 -0
  164. package/dist/sonner/Toaster.svelte.d.ts +4 -0
  165. package/dist/sonner/index.d.ts +21 -0
  166. package/dist/sonner/index.js +20 -0
  167. package/dist/user-management/UserManagement.svelte +22 -16
  168. package/dist/user-management/UserModal.svelte +10 -7
  169. package/dist/user-management/UserTable.svelte +16 -17
  170. package/dist/user-management/UserViewModal.svelte +11 -11
  171. package/dist/user-management/user-management-types.d.ts +118 -31
  172. package/dist/variants.d.ts +1 -1
  173. package/dist/variants.js +1 -1
  174. package/package.json +7 -4
  175. package/dist/config/ai.d.ts +0 -13
  176. package/dist/config/ai.js +0 -44
  177. package/dist/elements/empty-state/EmptyStateTestWrapper.svelte +0 -25
  178. package/dist/elements/empty-state/EmptyStateTestWrapper.svelte.d.ts +0 -8
  179. package/dist/elements/tooltip/TooltipTestWrapper.svelte +0 -14
  180. package/dist/elements/tooltip/TooltipTestWrapper.svelte.d.ts +0 -7
  181. package/dist/helper/deprecation.d.ts +0 -14
  182. package/dist/helper/deprecation.js +0 -24
  183. package/dist/modal/ModalFooterTestWrapper.svelte +0 -17
  184. package/dist/modal/ModalFooterTestWrapper.svelte.d.ts +0 -8
@@ -1,6 +1,34 @@
1
1
  import type { ClassValue } from 'tailwind-variants';
2
2
  /**
3
- * Pagination component props
3
+ * Props for `<Pagination>` page navigation controls. Used standalone
4
+ * for client-driven lists, or automatically inside `<Table>` when
5
+ * `pagination` is on.
6
+ *
7
+ * `currentPage` is a regular (non-bindable) prop — pass it in and
8
+ * update via `onpagechange`, or set `internalState={true}` and omit
9
+ * `currentPage` to let the component track pages itself. Most apps
10
+ * prefer external state so the URL or a store can drive it.
11
+ *
12
+ * @example
13
+ * ```svelte
14
+ * <Pagination
15
+ * currentPage={page}
16
+ * totalItems={42}
17
+ * pageSize={10}
18
+ * onpagechange={(p) => goto(`?page=${p}`)}
19
+ * />
20
+ * ```
21
+ *
22
+ * @example
23
+ * ```svelte
24
+ * <!-- Compact "Page 3 of 7" mode for tight footers -->
25
+ * <Pagination
26
+ * {totalItems}
27
+ * pageSize={20}
28
+ * template="compact"
29
+ * showFirstLast={false}
30
+ * />
31
+ * ```
4
32
  */
5
33
  export interface PaginationProps {
6
34
  /** Current page number (1-indexed). If not provided, component manages state internally. */
@@ -10,13 +38,13 @@ export interface PaginationProps {
10
38
  /** Number of items per page */
11
39
  pageSize?: number;
12
40
  /** Callback when page changes */
13
- onPageChange?: (page: number) => void;
41
+ onpagechange?: (page: number) => void;
14
42
  /** Callback when page size changes */
15
- onPageSizeChange?: (pageSize: number) => void;
43
+ onpagesizechange?: (pageSize: number) => void;
16
44
  /** Callback when first page is clicked */
17
- onFirstPage?: () => void;
45
+ onfirstpage?: () => void;
18
46
  /** Callback when last page is clicked */
19
- onLastPage?: () => void;
47
+ onlastpage?: () => void;
20
48
  /** Whether pagination controls are disabled */
21
49
  disabled?: boolean;
22
50
  /** Show page size selector */
@@ -0,0 +1,234 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../helper/cls.js';
3
+ import { buildTestId } from '../../helper/testid.js';
4
+ import type { PopoverProps, PopoverPlacement } from './popover-types.js';
5
+
6
+ let {
7
+ open = $bindable(false),
8
+ placement = 'bottom',
9
+ trigger = 'click',
10
+ arrow = false,
11
+ delay = 100,
12
+ hideDelay = 0,
13
+ closeOnEscape = true,
14
+ closeOnOutsideClick = true,
15
+ disabled = false,
16
+ class: className = '',
17
+ panelClass = '',
18
+ children,
19
+ content,
20
+ testId
21
+ }: PopoverProps = $props();
22
+
23
+ let wrapper = $state<HTMLSpanElement | undefined>();
24
+ let panelEl = $state<HTMLDivElement | undefined>();
25
+ let showTimer: ReturnType<typeof setTimeout> | undefined;
26
+ let hideTimer: ReturnType<typeof setTimeout> | undefined;
27
+
28
+ // Panel position in viewport coordinates — updated on open and on
29
+ // scroll/resize so the panel tracks the trigger. Kept reactive so the
30
+ // style attribute re-renders when these change.
31
+ let panelTop = $state(0);
32
+ let panelLeft = $state(0);
33
+ let panelTransform = $state('');
34
+
35
+ function show() {
36
+ if (disabled) return;
37
+ clearTimeout(showTimer);
38
+ clearTimeout(hideTimer);
39
+ if (delay > 0 && trigger === 'hover') {
40
+ showTimer = setTimeout(() => (open = true), delay);
41
+ } else {
42
+ open = true;
43
+ }
44
+ }
45
+
46
+ function hide() {
47
+ clearTimeout(showTimer);
48
+ clearTimeout(hideTimer);
49
+ if (hideDelay > 0 && trigger === 'hover') {
50
+ hideTimer = setTimeout(() => (open = false), hideDelay);
51
+ } else {
52
+ open = false;
53
+ }
54
+ }
55
+
56
+ function toggle() {
57
+ if (disabled) return;
58
+ if (open) hide();
59
+ else show();
60
+ }
61
+
62
+ function close() {
63
+ clearTimeout(showTimer);
64
+ clearTimeout(hideTimer);
65
+ open = false;
66
+ }
67
+
68
+ // Clean up any in-flight timers on unmount.
69
+ $effect(() => {
70
+ return () => {
71
+ clearTimeout(showTimer);
72
+ clearTimeout(hideTimer);
73
+ };
74
+ });
75
+
76
+ function handleWindowKey(e: KeyboardEvent) {
77
+ if (closeOnEscape && open && e.key === 'Escape') close();
78
+ }
79
+
80
+ function handleWindowClick(e: MouseEvent) {
81
+ if (!closeOnOutsideClick || !open) return;
82
+ const target = e.target as Node;
83
+ // The panel lives outside the trigger wrapper in the DOM, so check both.
84
+ if (wrapper?.contains(target) || panelEl?.contains(target)) return;
85
+ close();
86
+ }
87
+
88
+ /**
89
+ * Compute panel position from the trigger's bounding rect. Uses
90
+ * `position: fixed` + a very high z-index so the panel sits above
91
+ * sibling content (Storybook sidebars, sticky headers, etc.) instead
92
+ * of being clipped / layered under it.
93
+ *
94
+ * The computed position is then clamped to the viewport with an 8px
95
+ * gutter — this keeps panels on-screen near viewport edges on narrow
96
+ * devices. We measure the panel's own rect so the clamp accounts for
97
+ * the `translate(-50%, 0)` etc. transforms that re-anchor the panel.
98
+ */
99
+ function updatePosition() {
100
+ if (!wrapper || !panelEl) return;
101
+ const r = wrapper.getBoundingClientRect();
102
+ const GAP = 8;
103
+ const VIEWPORT_GUTTER = 8;
104
+ switch (placement as PopoverPlacement) {
105
+ case 'top':
106
+ panelTop = r.top - GAP;
107
+ panelLeft = r.left + r.width / 2;
108
+ panelTransform = 'translate(-50%, -100%)';
109
+ break;
110
+ case 'bottom':
111
+ panelTop = r.bottom + GAP;
112
+ panelLeft = r.left + r.width / 2;
113
+ panelTransform = 'translate(-50%, 0)';
114
+ break;
115
+ case 'left':
116
+ panelTop = r.top + r.height / 2;
117
+ panelLeft = r.left - GAP;
118
+ panelTransform = 'translate(-100%, -50%)';
119
+ break;
120
+ case 'right':
121
+ panelTop = r.top + r.height / 2;
122
+ panelLeft = r.right + GAP;
123
+ panelTransform = 'translate(0, -50%)';
124
+ break;
125
+ }
126
+
127
+ // Clamp to viewport. We re-measure the panel after applying the
128
+ // transform (next frame) because its true on-screen rect depends
129
+ // on `panelTransform`. Adjust `panelLeft`/`panelTop` so the rect
130
+ // ends up inside [VIEWPORT_GUTTER, viewport - VIEWPORT_GUTTER].
131
+ requestAnimationFrame(() => {
132
+ if (!panelEl) return;
133
+ const pr = panelEl.getBoundingClientRect();
134
+ const vw = window.innerWidth;
135
+ const vh = window.innerHeight;
136
+ let dx = 0;
137
+ let dy = 0;
138
+ if (pr.left < VIEWPORT_GUTTER) dx = VIEWPORT_GUTTER - pr.left;
139
+ else if (pr.right > vw - VIEWPORT_GUTTER) dx = vw - VIEWPORT_GUTTER - pr.right;
140
+ if (pr.top < VIEWPORT_GUTTER) dy = VIEWPORT_GUTTER - pr.top;
141
+ else if (pr.bottom > vh - VIEWPORT_GUTTER) dy = vh - VIEWPORT_GUTTER - pr.bottom;
142
+ if (dx !== 0) panelLeft += dx;
143
+ if (dy !== 0) panelTop += dy;
144
+ });
145
+ }
146
+
147
+ /**
148
+ * Re-measure on every open, and while open, on scroll/resize so the
149
+ * panel follows its trigger when the page scrolls.
150
+ */
151
+ $effect(() => {
152
+ if (!open) return;
153
+ // Initial placement (defer one frame so panelEl has mounted).
154
+ requestAnimationFrame(updatePosition);
155
+
156
+ const handler = () => updatePosition();
157
+ window.addEventListener('scroll', handler, true);
158
+ window.addEventListener('resize', handler);
159
+ return () => {
160
+ window.removeEventListener('scroll', handler, true);
161
+ window.removeEventListener('resize', handler);
162
+ };
163
+ });
164
+
165
+ const arrowClass = $derived(
166
+ {
167
+ top: 'top-full left-1/2 -translate-x-1/2 border-t-white border-l-transparent border-r-transparent border-b-transparent',
168
+ bottom:
169
+ 'bottom-full left-1/2 -translate-x-1/2 border-b-white border-l-transparent border-r-transparent border-t-transparent',
170
+ left: 'left-full top-1/2 -translate-y-1/2 border-l-white border-t-transparent border-b-transparent border-r-transparent',
171
+ right:
172
+ 'right-full top-1/2 -translate-y-1/2 border-r-white border-t-transparent border-b-transparent border-l-transparent'
173
+ }[placement]
174
+ );
175
+ </script>
176
+
177
+ <svelte:window onkeydown={handleWindowKey} onmousedown={handleWindowClick} />
178
+
179
+ <span
180
+ bind:this={wrapper}
181
+ class={cn('relative inline-flex', className)}
182
+ data-testid={buildTestId('popover', undefined, testId)}
183
+ >
184
+ {#if trigger === 'click'}
185
+ <!--
186
+ Click-mode: forward clicks to toggle. Do NOT add role/tabindex here
187
+ — consumers pass an interactive child (Button, <a>, etc.) that
188
+ handles its own focus/keyboard; adding them here would nest
189
+ interactive elements (invalid HTML + a11y issue). Keyboard support
190
+ comes via Enter/Space on the inner button dispatching a click,
191
+ which bubbles to this handler.
192
+ -->
193
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
194
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
195
+ <span class="inline-flex" aria-haspopup="dialog" aria-expanded={open} onclick={toggle}>
196
+ {@render children()}
197
+ </span>
198
+ {:else if trigger === 'hover'}
199
+ <span
200
+ class="inline-flex"
201
+ role="group"
202
+ onmouseenter={show}
203
+ onmouseleave={hide}
204
+ onfocusin={show}
205
+ onfocusout={hide}
206
+ >
207
+ {@render children()}
208
+ </span>
209
+ {:else}
210
+ <!-- manual — consumer drives `open` -->
211
+ <span class="inline-flex">{@render children()}</span>
212
+ {/if}
213
+ </span>
214
+
215
+ {#if open}
216
+ <div
217
+ bind:this={panelEl}
218
+ role="dialog"
219
+ tabindex="-1"
220
+ class={cn(
221
+ 'border-default-200 fixed z-[9999] min-w-[10rem] rounded-lg border bg-white p-2 shadow-lg',
222
+ panelClass
223
+ )}
224
+ style="top: {panelTop}px; left: {panelLeft}px; transform: {panelTransform};"
225
+ onmouseenter={trigger === 'hover' && hideDelay > 0 ? () => clearTimeout(hideTimer) : undefined}
226
+ onmouseleave={trigger === 'hover' ? hide : undefined}
227
+ data-testid={buildTestId('popover', 'panel', testId)}
228
+ >
229
+ {#if arrow}
230
+ <span class={cn('absolute size-0 border-[5px]', arrowClass)} aria-hidden="true"></span>
231
+ {/if}
232
+ {@render content({ close })}
233
+ </div>
234
+ {/if}
@@ -0,0 +1,4 @@
1
+ import type { PopoverProps } from './popover-types.js';
2
+ declare const Popover: import("svelte").Component<PopoverProps, {}, "open">;
3
+ type Popover = ReturnType<typeof Popover>;
4
+ export default Popover;
@@ -0,0 +1,2 @@
1
+ export { default as Popover } from './Popover.svelte';
2
+ export type { PopoverProps, PopoverPlacement, PopoverTrigger } from './popover-types.js';
@@ -0,0 +1 @@
1
+ export { default as Popover } from './Popover.svelte';
@@ -0,0 +1,60 @@
1
+ import type { ClassValue } from 'tailwind-variants';
2
+ import type { Snippet } from 'svelte';
3
+ /**
4
+ * Where the Popover panel appears relative to its trigger. Popover always
5
+ * centers along the cross-axis of the placement.
6
+ */
7
+ export type PopoverPlacement = 'top' | 'bottom' | 'left' | 'right';
8
+ /**
9
+ * How the Popover opens:
10
+ * - `'click'` — clicking the trigger toggles open/close (default, most common)
11
+ * - `'hover'` — opens on mouseenter/focusin. Use `hideDelay` to let users
12
+ * cross from trigger into panel without it closing.
13
+ * - `'manual'` — you drive `open` yourself via `bind:open`. Trigger
14
+ * children are just visual (no event handlers attached by Popover).
15
+ */
16
+ export type PopoverTrigger = 'click' | 'hover' | 'manual';
17
+ export type PopoverProps = {
18
+ /** Bindable open state. Defaults to false. */
19
+ open?: boolean;
20
+ /** Placement relative to the trigger. @default 'bottom' */
21
+ placement?: PopoverPlacement;
22
+ /** How the trigger opens the panel. @default 'click' */
23
+ trigger?: PopoverTrigger;
24
+ /** Show an arrow pointing at the trigger. @default false */
25
+ arrow?: boolean;
26
+ /**
27
+ * When `trigger='hover'`, delay before showing (ms). Ignored for
28
+ * click/manual. @default 100
29
+ */
30
+ delay?: number;
31
+ /**
32
+ * When `trigger='hover'`, delay before hiding after mouseleave (ms).
33
+ * Non-zero lets users move into the panel. Ignored for click/manual.
34
+ * @default 0
35
+ */
36
+ hideDelay?: number;
37
+ /** Close when the Escape key is pressed while panel is open. @default true */
38
+ closeOnEscape?: boolean;
39
+ /** Close when user clicks outside the panel and trigger. @default true */
40
+ closeOnOutsideClick?: boolean;
41
+ /** Disable the trigger and prevent opening. */
42
+ disabled?: boolean;
43
+ /** Wrapper class (around trigger). */
44
+ class?: ClassValue;
45
+ /** Panel class (the floating element). */
46
+ panelClass?: ClassValue;
47
+ /**
48
+ * The trigger element. Rendered inline — the Popover wraps it in an
49
+ * inline-flex container so it sits where you put the <Popover>.
50
+ */
51
+ children: Snippet;
52
+ /**
53
+ * The panel content. Receives `close()` so you can wire buttons inside
54
+ * the panel that dismiss it.
55
+ */
56
+ content: Snippet<[{
57
+ close: () => void;
58
+ }]>;
59
+ testId?: string;
60
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,12 +1,14 @@
1
1
  <script lang="ts">
2
2
  import { cn } from '../../helper/cls.js';
3
3
  import { Color, Size } from '../../variants.js';
4
+ import { Tween } from 'svelte/motion';
5
+ import { quintOut } from 'svelte/easing';
4
6
  import type { ProgressProps, VariantColors } from '../../index.js';
5
7
 
6
8
  let {
7
9
  value,
8
10
  max = 100,
9
- size = Size.BASE,
11
+ size = Size.MD,
10
12
  color = Color.PRIMARY,
11
13
  showLabel = true,
12
14
  labelPosition = 'right',
@@ -38,12 +40,12 @@
38
40
 
39
41
  function getSizeTextClass(size: string): string {
40
42
  if (size === Size.XS || size === Size.SM) return 'text-xs';
41
- if (size === Size.BASE) return 'text-sm';
43
+ if (size === Size.MD) return 'text-sm';
42
44
  return 'text-base'; // For Size.LG, Size.XL
43
45
  }
44
46
 
45
47
  // Derived values
46
- const percentage = $derived(segments ? 100 : calculatePercentage(value, max));
48
+ const percentage = $derived(segments ? 100 : calculatePercentage(value ?? 0, max));
47
49
  const segmentPercentages = $derived(
48
50
  segments?.map((segment) => ({
49
51
  ...segment,
@@ -51,6 +53,29 @@
51
53
  })) || []
52
54
  );
53
55
 
56
+ // Animate from 0 → target on mount (and on subsequent value changes)
57
+ // using a tween with quintOut easing for a smooth, non-jittery feel.
58
+ const tween = new Tween(0, { duration: 800, easing: quintOut });
59
+
60
+ // Derive tween instances from the current segment count so that
61
+ // dynamically-added/removed segments get their own tweens instead of
62
+ // being stuck at 0. The $derived runs whenever `segments` identity
63
+ // changes — consumers should pass a stable array unless they truly
64
+ // want a rebuild.
65
+ const segmentTweens = $derived(
66
+ (segments ?? []).map(() => new Tween(0, { duration: 800, easing: quintOut }))
67
+ );
68
+
69
+ $effect(() => {
70
+ tween.target = percentage;
71
+ });
72
+
73
+ $effect(() => {
74
+ segmentPercentages.forEach((s, i) => {
75
+ if (segmentTweens[i]) segmentTweens[i].target = s.percentage;
76
+ });
77
+ });
78
+
54
79
  // Class compositions
55
80
  const containerClass = $derived(
56
81
  cn(
@@ -68,7 +93,7 @@
68
93
  cn('w-full rounded-full bg-default-200', {
69
94
  'h-1.5': size === Size.XS,
70
95
  'h-2': size === Size.SM,
71
- 'h-2.5': size === Size.BASE,
96
+ 'h-2.5': size === Size.MD,
72
97
  'h-3': size === Size.LG,
73
98
  'h-4': size === Size.XL,
74
99
  'order-2': !segments && labelPosition === 'top',
@@ -78,7 +103,7 @@
78
103
  );
79
104
 
80
105
  const fillClass = $derived(
81
- cn('h-full rounded-full transition-all', segments ? '' : getColorClass(color), barClass)
106
+ cn('h-full rounded-full', segments ? '' : getColorClass(color), barClass)
82
107
  );
83
108
 
84
109
  const labelTextClass = $derived(
@@ -108,13 +133,13 @@
108
133
  {#if segment.percentage > 0}
109
134
  <div
110
135
  class={cn(getColorClass(segment.color), barClass)}
111
- style="width: {segment.percentage}%"
136
+ style="width: {segmentTweens[index]?.current ?? 0}%"
112
137
  title={segment.label || `${segment.value} (${segment.percentage}%)`}
113
138
  ></div>
114
139
  {/if}
115
140
  {/each}
116
141
  {:else}
117
- <div class={fillClass} style="width: {percentage}%"></div>
142
+ <div class={fillClass} style="width: {tween.current}%"></div>
118
143
  {/if}
119
144
  </div>
120
145
 
@@ -1,22 +1,69 @@
1
1
  import type { ClassValue } from 'tailwind-variants';
2
2
  import type { VariantColors, VariantSizes } from '../../index.js';
3
+ /** One segment of a multi-segment progress bar (`ProgressProps.segments`). */
3
4
  export type ProgressSegment = {
5
+ /** Segment size. Treated as a share of `max` (so segment values should sum to ≤ `max`). */
4
6
  value: number;
7
+ /** Segment fill color. */
5
8
  color: VariantColors;
9
+ /** Optional label rendered inside the legend when `showLabels` is set. */
6
10
  label?: string;
7
11
  };
12
+ /**
13
+ * Props for `<Progress>` — a horizontal progress bar. Supports two modes:
14
+ * 1. **Single value** — pass `value` (and optionally `max`). Renders one filled bar.
15
+ * 2. **Segmented** — pass `segments: ProgressSegment[]`. Renders a stacked bar with
16
+ * per-segment colors and an optional legend below.
17
+ *
18
+ * For KPI-style displays where the progress bar is part of a card,
19
+ * `<MetricCard>` accepts the same `percent`/`segments` props directly.
20
+ *
21
+ * @example
22
+ * ```svelte
23
+ * <!-- Simple -->
24
+ * <Progress value={68} showLabel />
25
+ * ```
26
+ *
27
+ * @example
28
+ * ```svelte
29
+ * <!-- Multi-segment with legend -->
30
+ * <Progress
31
+ * max={100}
32
+ * segments={[
33
+ * { value: 50, color: 'success', label: 'Healthy' },
34
+ * { value: 30, color: 'warning', label: 'Warning' },
35
+ * { value: 20, color: 'danger', label: 'Critical' }
36
+ * ]}
37
+ * showLabels
38
+ * showValues
39
+ * />
40
+ * ```
41
+ */
8
42
  export type ProgressProps = {
9
- value: number;
43
+ /** Current value (single-mode). Ignored when `segments` is provided. @default 0 */
44
+ value?: number;
45
+ /** Max scale value. @default 100 */
10
46
  max?: number;
11
47
  size?: VariantSizes;
48
+ /** Fill color for single-mode. @default 'primary' */
12
49
  color?: VariantColors;
50
+ /** Show "value / max" label next to the bar (single-mode). @default false */
13
51
  showLabel?: boolean;
52
+ /** Where to render the label. @default 'top' */
14
53
  labelPosition?: 'top' | 'bottom' | 'right';
54
+ /**
55
+ * Segmented bars — when provided, takes precedence over `value`.
56
+ * Each segment renders in its own color, and an optional legend
57
+ * appears below when `showLabels` or `showValues` is set.
58
+ */
15
59
  segments?: ProgressSegment[];
60
+ /** Show the legend under segmented bars. @default false */
16
61
  showLabels?: boolean;
62
+ /** Show numeric values alongside labels in the legend. @default false */
17
63
  showValues?: boolean;
18
64
  class?: ClassValue;
19
65
  labelClass?: ClassValue;
66
+ /** Classes on the inner bar element(s). */
20
67
  barClass?: ClassValue;
21
68
  testId?: string;
22
69
  };
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../helper/cls.js';
3
+ import { buildTestId } from '../../helper/testid.js';
4
+ import type { SkeletonProps } from './skeleton-types.js';
5
+
6
+ let {
7
+ class: className = '',
8
+ variant = 'pulse',
9
+ rounded = 'rounded-md',
10
+ ariaLabel = 'Loading',
11
+ style,
12
+ testId
13
+ }: SkeletonProps = $props();
14
+ </script>
15
+
16
+ <div
17
+ class={cn(
18
+ 'bg-default-200',
19
+ rounded,
20
+ variant === 'pulse' && 'animate-pulse',
21
+ variant === 'shimmer' && 'ripple-skeleton-shimmer overflow-hidden',
22
+ className
23
+ )}
24
+ {style}
25
+ role="status"
26
+ aria-label={ariaLabel}
27
+ aria-busy="true"
28
+ data-testid={buildTestId('skeleton', undefined, testId)}
29
+ ></div>
30
+
31
+ <style>
32
+ /* Shimmer variant: gradient sweep using a CSS animation. Kept here (not Tailwind)
33
+ because moving gradients aren't expressible with Tailwind utilities. */
34
+ .ripple-skeleton-shimmer {
35
+ position: relative;
36
+ isolation: isolate;
37
+ }
38
+ .ripple-skeleton-shimmer::after {
39
+ content: '';
40
+ position: absolute;
41
+ inset: 0;
42
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
43
+ transform: translateX(-100%);
44
+ animation: ripple-skeleton-shimmer 1.6s infinite;
45
+ }
46
+ @keyframes ripple-skeleton-shimmer {
47
+ 100% {
48
+ transform: translateX(100%);
49
+ }
50
+ }
51
+ @media (prefers-reduced-motion: reduce) {
52
+ .ripple-skeleton-shimmer::after {
53
+ animation: none;
54
+ }
55
+ }
56
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { SkeletonProps } from './skeleton-types.js';
2
+ declare const Skeleton: import("svelte").Component<SkeletonProps, {}, "">;
3
+ type Skeleton = ReturnType<typeof Skeleton>;
4
+ export default Skeleton;
@@ -0,0 +1,2 @@
1
+ export { default as Skeleton } from './Skeleton.svelte';
2
+ export type { SkeletonProps, SkeletonVariant } from './skeleton-types.js';
@@ -0,0 +1 @@
1
+ export { default as Skeleton } from './Skeleton.svelte';
@@ -0,0 +1,50 @@
1
+ import type { ClassValue } from 'tailwind-variants';
2
+ /**
3
+ * Animation style for `<Skeleton>`.
4
+ * - `'pulse'` — subtle opacity pulse (Tailwind's `animate-pulse`)
5
+ * - `'shimmer'` — gradient slides across the block
6
+ */
7
+ export type SkeletonVariant = 'pulse' | 'shimmer';
8
+ /**
9
+ * Props for `<Skeleton>` — a placeholder block that mimics the shape of
10
+ * content while it loads. Use to preserve layout and avoid layout shift
11
+ * when the real content arrives.
12
+ *
13
+ * For wholesale "is my component loading?" UX, most components expose
14
+ * a `loading` prop that renders skeletons automatically (`<Card loading>`,
15
+ * `<Table loading>`, etc.). Use `<Skeleton>` directly when composing
16
+ * custom placeholder layouts.
17
+ *
18
+ * @example
19
+ * ```svelte
20
+ * <!-- Three-line text placeholder -->
21
+ * <div class="space-y-2">
22
+ * <Skeleton class="h-4 w-48" />
23
+ * <Skeleton class="h-4 w-64" />
24
+ * <Skeleton class="h-4 w-40" />
25
+ * </div>
26
+ * ```
27
+ *
28
+ * @example
29
+ * ```svelte
30
+ * <!-- Avatar placeholder -->
31
+ * <Skeleton class="size-10" rounded="rounded-full" variant="shimmer" />
32
+ * ```
33
+ */
34
+ export interface SkeletonProps {
35
+ /** Additional Tailwind classes — typically `w-*` and `h-*` to size the block. */
36
+ class?: ClassValue;
37
+ /**
38
+ * Animation style.
39
+ * - `pulse` (default): subtle opacity pulse via Tailwind's animate-pulse
40
+ * - `shimmer`: gradient slides across the block
41
+ */
42
+ variant?: SkeletonVariant;
43
+ /** Override the rounded class. Pass `''` to suppress default rounding. @default 'rounded-md' */
44
+ rounded?: string;
45
+ /** Optional accessible label announced by screen readers. @default 'Loading' */
46
+ ariaLabel?: string;
47
+ /** Inline style — useful for dynamic widths/heights that Tailwind can't express. */
48
+ style?: string;
49
+ testId?: string;
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -6,7 +6,7 @@
6
6
  import type { SpinnerProps } from '../../index.js';
7
7
 
8
8
  let {
9
- size = Size.BASE,
9
+ size = Size.MD,
10
10
  color = Color.DEFAULT,
11
11
  label,
12
12
  class: className = '',