@makolabs/ripple 2.5.9 → 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 (183) 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.js +1 -1
  118. package/dist/header/Breadcrumbs.svelte +4 -20
  119. package/dist/header/PageHeader.svelte +6 -14
  120. package/dist/header/breadcrumbs.d.ts +3 -11
  121. package/dist/header/breadcrumbs.js +10 -5
  122. package/dist/header/header-types.d.ts +62 -11
  123. package/dist/index.d.ts +35 -9
  124. package/dist/index.js +24 -4
  125. package/dist/layout/activity-list/ActivityList.svelte +13 -7
  126. package/dist/layout/activity-list/activity-list-types.d.ts +46 -7
  127. package/dist/layout/card/Card.svelte +12 -15
  128. package/dist/layout/card/MetricCard.svelte +50 -32
  129. package/dist/layout/card/card-types.d.ts +114 -4
  130. package/dist/layout/navbar/navbar-types.d.ts +48 -0
  131. package/dist/layout/navbar/navbar.d.ts +3 -3
  132. package/dist/layout/navbar/navbar.js +2 -2
  133. package/dist/layout/sidebar/Sidebar.svelte +87 -11
  134. package/dist/layout/sidebar/sidebar-types.d.ts +60 -1
  135. package/dist/layout/stepper/Stepper.svelte +288 -0
  136. package/dist/layout/stepper/Stepper.svelte.d.ts +4 -0
  137. package/dist/layout/stepper/stepper-types.d.ts +80 -0
  138. package/dist/layout/stepper/stepper-types.js +1 -0
  139. package/dist/layout/table/Table.svelte +91 -85
  140. package/dist/layout/table/table-types.d.ts +148 -24
  141. package/dist/layout/table/table.d.ts +3 -3
  142. package/dist/layout/table/table.js +2 -2
  143. package/dist/layout/tabs/Tab.svelte +6 -2
  144. package/dist/layout/tabs/Tab.svelte.d.ts +4 -1
  145. package/dist/layout/tabs/TabGroup.svelte +9 -2
  146. package/dist/layout/tabs/tabs-types.d.ts +63 -0
  147. package/dist/layout/tabs/tabs.d.ts +3 -3
  148. package/dist/layout/tabs/tabs.js +12 -6
  149. package/dist/modal/ConfirmDialog.svelte +65 -0
  150. package/dist/modal/ConfirmDialog.svelte.d.ts +4 -0
  151. package/dist/modal/Modal.svelte +6 -26
  152. package/dist/modal/confirm-dialog-types.d.ts +39 -0
  153. package/dist/modal/confirm-dialog-types.js +1 -0
  154. package/dist/modal/modal-types.d.ts +51 -12
  155. package/dist/modal/modal.d.ts +3 -3
  156. package/dist/modal/modal.js +3 -3
  157. package/dist/pipeline/Pipeline.svelte +8 -3
  158. package/dist/pipeline/pipeline-types.d.ts +55 -3
  159. package/dist/pipeline/pipeline.d.ts +18 -3
  160. package/dist/pipeline/pipeline.js +7 -2
  161. package/dist/server/s3.d.ts +35 -3
  162. package/dist/sonner/Toaster.svelte +29 -0
  163. package/dist/sonner/Toaster.svelte.d.ts +4 -0
  164. package/dist/sonner/index.d.ts +21 -0
  165. package/dist/sonner/index.js +20 -0
  166. package/dist/user-management/UserManagement.svelte +22 -16
  167. package/dist/user-management/UserModal.svelte +10 -7
  168. package/dist/user-management/UserTable.svelte +16 -17
  169. package/dist/user-management/UserViewModal.svelte +11 -11
  170. package/dist/user-management/user-management-types.d.ts +118 -31
  171. package/dist/variants.d.ts +1 -1
  172. package/dist/variants.js +1 -1
  173. package/package.json +7 -4
  174. package/dist/config/ai.d.ts +0 -13
  175. package/dist/config/ai.js +0 -44
  176. package/dist/elements/empty-state/EmptyStateTestWrapper.svelte +0 -25
  177. package/dist/elements/empty-state/EmptyStateTestWrapper.svelte.d.ts +0 -8
  178. package/dist/elements/tooltip/TooltipTestWrapper.svelte +0 -14
  179. package/dist/elements/tooltip/TooltipTestWrapper.svelte.d.ts +0 -7
  180. package/dist/helper/deprecation.d.ts +0 -14
  181. package/dist/helper/deprecation.js +0 -24
  182. package/dist/modal/ModalFooterTestWrapper.svelte +0 -17
  183. package/dist/modal/ModalFooterTestWrapper.svelte.d.ts +0 -8
package/README.md CHANGED
@@ -1,610 +1,521 @@
1
1
  # Ripple UI
2
2
 
3
- A modern, standardized Svelte 5 component library designed for simplicity, consistency, and AI-friendly usage patterns.
4
-
5
- ## Key Features
6
-
7
- - **Standardized API** with consistent prop naming and patterns across components
8
- - **Enum-based properties** for predictable component customization
9
- - **Strong TypeScript support** with comprehensive type definitions
10
- - **Utility-first approach** built with TailwindCSS
11
- - **Accessible components** adhering to modern web standards
12
- - **Simplified component consumption** ideal for both human and AI developers
13
-
14
- ## Getting started
15
-
16
- Install the project
3
+ A Svelte 5 + TailwindCSS 4 component library for building data-dense product UIs (dashboards, admin tools, internal apps). Built on `tailwind-variants` with consistent naming, comprehensive TypeScript types, and per-prop JSDoc that lets LLMs and IDE tooltips answer "how do I use this?" without leaving the editor.
17
4
 
18
5
  ```shell
19
6
  npm i @makolabs/ripple
20
7
  ```
21
8
 
22
- ## Usage
9
+ ---
23
10
 
24
- Import ripple UI components
11
+ ## Table of contents
25
12
 
26
- ```svelte
27
- <script lang="ts">
28
- import { Button, Card, Modal } from '@makolabs/ripple';
29
- </script>
13
+ - [Quick start](#quick-start)
14
+ - [Theme tokens](#theme-tokens)
15
+ - [Component catalog](#component-catalog)
16
+ - [Conventions](#conventions)
17
+ - [Selected examples](#selected-examples)
18
+ - [Forms](#forms)
19
+ - [Tables + cells](#tables--cells)
20
+ - [Modals + dialogs](#modals--dialogs)
21
+ - [Filters](#filters)
22
+ - [Charts](#charts)
23
+ - [Server-side helpers](#server-side-helpers)
24
+ - [Testing](#testing)
25
+ - [Troubleshooting](#troubleshooting)
26
+
27
+ ---
30
28
 
31
- <div class="px-12 pt-12">
32
- <Card title="Hello World" color="warning">
33
- <p>This is a card component</p>
34
- </Card>
35
- </div>
29
+ ## Quick start
30
+
31
+ ### 1. Install
32
+
33
+ ```shell
34
+ npm i @makolabs/ripple
36
35
  ```
37
36
 
38
- Paste the following CSS import code in `app.css`
37
+ ### 2. Wire up your CSS
38
+
39
+ Tailwind 4 does not scan `node_modules` automatically. Add a `@source` directive plus the `@theme` color tokens to your `app.css`:
39
40
 
40
41
  ```css
42
+ @import 'tailwindcss';
41
43
  @source '../node_modules/@makolabs/ripple';
42
44
 
43
45
  @theme {
44
- /* Default (default) */
45
- --color-default-50: oklch(0.984 0.003 247.858);
46
- --color-default-100: oklch(0.96 0.006 247.858);
47
- --color-default-200: oklch(0.91 0.008 247.858);
48
- --color-default-300: oklch(0.85 0.01 247.858);
49
- --color-default-400: oklch(0.76 0.012 247.858);
50
- --color-default-500: oklch(0.65 0.015 247.858);
51
- --color-default-600: oklch(0.54 0.018 247.858);
52
- --color-default-700: oklch(0.45 0.015 247.858);
53
- --color-default-800: oklch(0.35 0.012 247.858);
54
- --color-default-900: oklch(0.25 0.01 247.858);
55
- --color-default-950: oklch(0.15 0.008 247.858);
56
-
57
- /* Primary (Blue) */
58
- --color-primary-50: oklch(0.97 0.025 250);
59
- --color-primary-100: oklch(0.94 0.035 250);
60
- --color-primary-200: oklch(0.89 0.055 250);
61
- --color-primary-300: oklch(0.82 0.075 250);
62
- --color-primary-400: oklch(0.74 0.095 250);
46
+ /* Ripple expects 7 color scales — see "Theme tokens" below for the full set */
63
47
  --color-primary-500: oklch(0.65 0.115 250);
64
- --color-primary-600: oklch(0.55 0.125 250);
65
- --color-primary-700: oklch(0.45 0.115 250);
66
- --color-primary-800: oklch(0.35 0.095 250);
67
- --color-primary-900: oklch(0.25 0.075 250);
68
- --color-primary-950: oklch(0.15 0.055 250);
69
-
70
- /* Secondary (Slate) */
71
- --color-secondary-50: oklch(0.97 0.02 255);
72
- --color-secondary-100: oklch(0.94 0.03 255);
73
- --color-secondary-200: oklch(0.89 0.04 255);
74
- --color-secondary-300: oklch(0.82 0.05 255);
75
- --color-secondary-400: oklch(0.74 0.06 255);
76
- --color-secondary-500: oklch(0.65 0.07 255);
77
- --color-secondary-600: oklch(0.55 0.065 255);
78
- --color-secondary-700: oklch(0.45 0.055 255);
79
- --color-secondary-800: oklch(0.35 0.045 255);
80
- --color-secondary-900: oklch(0.25 0.035 255);
81
- --color-secondary-950: oklch(0.15 0.025 255);
82
-
83
- /* Info (Sky) */
84
- --color-info-50: oklch(0.97 0.025 220);
85
- --color-info-100: oklch(0.94 0.04 220);
86
- --color-info-200: oklch(0.89 0.06 220);
87
- --color-info-300: oklch(0.82 0.085 220);
88
- --color-info-400: oklch(0.74 0.105 220);
89
- --color-info-500: oklch(0.65 0.125 220);
90
- --color-info-600: oklch(0.55 0.115 220);
91
- --color-info-700: oklch(0.45 0.105 220);
92
- --color-info-800: oklch(0.35 0.085 220);
93
- --color-info-900: oklch(0.25 0.065 220);
94
- --color-info-950: oklch(0.15 0.045 220);
95
-
96
- /* Success (Green) */
97
- --color-success-50: oklch(0.97 0.025 145);
98
- --color-success-100: oklch(0.94 0.04 145);
99
- --color-success-200: oklch(0.89 0.06 145);
100
- --color-success-300: oklch(0.82 0.08 145);
101
- --color-success-400: oklch(0.74 0.1 145);
102
48
  --color-success-500: oklch(0.65 0.12 145);
103
- --color-success-600: oklch(0.55 0.11 145);
104
- --color-success-700: oklch(0.45 0.1 145);
105
- --color-success-800: oklch(0.35 0.08 145);
106
- --color-success-900: oklch(0.25 0.06 145);
107
- --color-success-950: oklch(0.15 0.04 145);
108
-
109
- /* Warning (Yellow) */
110
- --color-warning-50: oklch(0.97 0.025 90);
111
- --color-warning-100: oklch(0.94 0.045 90);
112
- --color-warning-200: oklch(0.89 0.065 90);
113
- --color-warning-300: oklch(0.82 0.085 90);
114
- --color-warning-400: oklch(0.74 0.105 90);
115
- --color-warning-500: oklch(0.65 0.125 90);
116
- --color-warning-600: oklch(0.55 0.115 90);
117
- --color-warning-700: oklch(0.45 0.105 90);
118
- --color-warning-800: oklch(0.35 0.085 90);
119
- --color-warning-900: oklch(0.25 0.065 90);
120
- --color-warning-950: oklch(0.15 0.045 90);
121
-
122
- /* Danger (Red) */
123
- --color-danger-50: oklch(0.97 0.025 25);
124
- --color-danger-100: oklch(0.94 0.045 25);
125
- --color-danger-200: oklch(0.89 0.065 25);
126
- --color-danger-300: oklch(0.82 0.085 25);
127
- --color-danger-400: oklch(0.74 0.105 25);
128
49
  --color-danger-500: oklch(0.65 0.125 25);
129
- --color-danger-600: oklch(0.55 0.115 25);
130
- --color-danger-700: oklch(0.45 0.105 25);
131
- --color-danger-800: oklch(0.35 0.085 25);
132
- --color-danger-900: oklch(0.25 0.065 25);
133
- --color-danger-950: oklch(0.15 0.045 25);
50
+ /* …etc */
134
51
  }
135
52
  ```
136
53
 
137
- ## Design Philosophy
138
-
139
- Ripple UI was built with a focus on consistency and standardization. Every component follows the same patterns for customization:
140
-
141
- ### Standardized Enums
54
+ ### 3. Use a component
142
55
 
143
- Components use standardized enums for colors, sizes, and variants:
144
-
145
- ```typescript
146
- // Colors available for most components
147
- Color.DEFAULT; // 'default'
148
- Color.PRIMARY; // 'primary'
149
- Color.SECONDARY; // 'secondary'
150
- Color.INFO; // 'info'
151
- Color.SUCCESS; // 'success'
152
- Color.WARNING; // 'warning'
153
- Color.DANGER; // 'danger'
56
+ ```svelte
57
+ <script lang="ts">
58
+ import { Button, Card } from '@makolabs/ripple';
59
+ let count = $state(0);
60
+ </script>
154
61
 
155
- // Sizes available for most components
156
- Size.XS; // 'xs'
157
- Size.SM; // 'sm'
158
- Size.BASE; // 'base'
159
- Size.LG; // 'lg'
160
- Size.XL; // 'xl'
161
- Size.XXL; // '2xl'
62
+ <Card title="Counter" color="primary">
63
+ <p>Clicks: {count}</p>
64
+ <Button onclick={() => count++}>Increment</Button>
65
+ </Card>
162
66
  ```
163
67
 
164
- ### Consistent Props Pattern
165
-
166
- All components follow a consistent props pattern with predictable naming:
167
-
168
- - `color`: Component color theme (using the Color enum)
169
- - `size`: Component size (using the Size enum)
170
- - `class`: Custom CSS classes for the component
171
- - Event handlers with `on` prefix (e.g., `onclick`, `onchange`)
172
- - Element-specific class props named with component + 'class' (e.g., `titleclass`, `bodyclass`)
68
+ That's it no provider component, no global CSS reset, no theme context.
69
+
70
+ ---
71
+
72
+ ## Theme tokens
73
+
74
+ Ripple components use seven semantic color scales. Each scale needs steps `50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950`.
75
+
76
+ | Scale | Use for | Example component |
77
+ | ----------- | ------------------------------------------ | ----------------- |
78
+ | `default` | Neutral text, borders, surfaces | Disabled buttons |
79
+ | `primary` | Brand color, primary actions, focus rings | "Save" button |
80
+ | `secondary` | Alternate brand color (counter to primary) | Sidebar accent |
81
+ | `info` | Informational tints (often blue/cyan) | Alert (info) |
82
+ | `success` | Success states, positive metrics | Alert (success) |
83
+ | `warning` | Caution, pending states (yellow/amber) | Alert (warning) |
84
+ | `danger` | Destructive actions, errors (red) | Delete button |
85
+
86
+ A complete `@theme` block with all seven scales is in [`docs/THEME.md`](docs/THEME.md) (or copy-paste from this README's git history). Pick any palette — Ripple just consumes the token names.
87
+
88
+ ---
89
+
90
+ ## Component catalog
91
+
92
+ All components are exported from the package root: `import { Component } from '@makolabs/ripple'`. Per-prop JSDoc with `@example` blocks is on every type, so IDE tooltips and AI assistants give you the right signature immediately.
93
+
94
+ ### Forms
95
+
96
+ | Component | Use for |
97
+ | ------------------ | ------------------------------------------------- |
98
+ | `Input` | Single-line text/email/number/etc. |
99
+ | `Textarea` | Multi-line text with autoGrow + character counter |
100
+ | `Checkbox` | Boolean selection ("I accept the terms") |
101
+ | `Toggle` | Boolean switch (on/off settings) |
102
+ | `RadioGroup` | Pick one from a short list |
103
+ | `Select` | Single/multi-select dropdown with optional search |
104
+ | `ComboBox` | Searchable select with free-typing + filter |
105
+ | `SegmentedControl` | Compact pill-style picker (2-5 options) |
106
+ | `MarketSelector` | Specialised SegmentedControl with country flags |
107
+ | `Slider` | Numeric or range slider, with optional ticks |
108
+ | `NumberInput` | Number input with currency/unit dropdown |
109
+ | `Tags` | List of short string tags with autocomplete |
110
+ | `DatePicker` | Single date picker with calendar popover |
111
+ | `DateRange` | Two-date (from/to) picker |
112
+ | `Calendar` | Standalone month-view calendar (single + range) |
113
+ | `Form` | sveltekit-superforms wrapper |
114
+
115
+ ### Layout
116
+
117
+ | Component | Use for |
118
+ | --------------------------------- | ------------------------------------------------------------------- |
119
+ | `Card` | Generic bordered container |
120
+ | `MetricCard` | KPI-focused card (big number + optional progress) |
121
+ | `RankedCard` | Leaderboard-style ranked list |
122
+ | `Table` | Data table with sorting/selection/pagination |
123
+ | `Cells` | Pre-built cell renderers (`Cells.Currency`, `Cells.DateCell`, etc.) |
124
+ | `TabGroup` + `TabContent` + `Tab` | Tabbed panels |
125
+ | `Navbar` | Top navigation bar |
126
+ | `Sidebar` | Left rail navigation, collapsible |
127
+ | `ActivityList` | Feed of recent events / audit trail |
128
+ | `Pipeline` | Chevron stage bar (sales pipeline, funnel) |
129
+ | `Stepper` | Multi-step form wizard |
130
+ | `PageHeader` | Title + subtitle + breadcrumbs + actions |
131
+ | `Breadcrumbs` | Hierarchical link trail |
132
+
133
+ ### Elements
134
+
135
+ | Component | Use for |
136
+ | ------------- | ---------------------------------------- |
137
+ | `Button` | Buttons + anchor links (set `href`) |
138
+ | `Badge` | Pills for statuses, counts, tags |
139
+ | `Alert` | Inline page-level banner |
140
+ | `Accordion` | Collapsible section |
141
+ | `Spinner` | Indeterminate loading indicator |
142
+ | `Skeleton` | Layout-preserving content placeholder |
143
+ | `Progress` | Linear progress bar (single + segmented) |
144
+ | `EmptyState` | Centered "nothing here" placeholder |
145
+ | `Tooltip` | Hover/focus-triggered text label |
146
+ | `Popover` | Generic anchored floating panel |
147
+ | `Dropdown` | Action menu attached to a trigger |
148
+ | `ContextMenu` | Right-click / long-press menu |
149
+ | `Pagination` | Page navigation controls |
150
+
151
+ ### Overlays
152
+
153
+ | Component | Use for |
154
+ | --------------------- | ------------------------------------ |
155
+ | `Modal` | Centered overlay dialog |
156
+ | `Drawer` | Side-anchored sliding panel |
157
+ | `ConfirmDialog` | "Are you sure?" wrapper around Modal |
158
+ | `Toaster` + `toast()` | Transient notifications |
159
+
160
+ ### Filters
161
+
162
+ | Component | Use for |
163
+ | ------------------ | ------------------------------------------------------ |
164
+ | `CompactFilters` | Collapsible filter bar with chip-summary mode |
165
+ | `FilterPopover` | Per-group dropdown pills (status, priority, dates) |
166
+ | `FilterBar` | Chip-based add/remove filters with "+ Add filter" menu |
167
+ | `syncFiltersToUrl` | Two-way bind selections to URL query params |
168
+
169
+ ### Charts
170
+
171
+ | Component | Use for |
172
+ | --------- | --------------------------------------------------------------------------------- |
173
+ | `Chart` | ECharts wrapper — line, bar, stacked-bar, horizontal-bar, area, pie, donut, mixed |
174
+
175
+ ### File handling
176
+
177
+ | Component / helper | Use for |
178
+ | -------------------- | ------------------------------------------------------------ |
179
+ | `FileUpload` | Drag-and-drop with single + multi-file + rich-mode progress |
180
+ | `FilesPreview` | List of uploaded files with delete |
181
+ | `FileBrowser` | Browse/select files on remote storage (S3, GDrive) |
182
+ | `S3Adapter` | Storage adapter for S3-compatible backends (DO Spaces, etc.) |
183
+ | `GoogleDriveAdapter` | Storage adapter for Google Drive |
184
+ | `createS3Handlers` | Server-side route handlers (`@makolabs/ripple/server`) |
185
+
186
+ ### AI / Chat
187
+
188
+ | Component | Use for |
189
+ | ----------------- | ---------------------------------------------- |
190
+ | `AIChatInterface` | Full chat UI with streaming + thinking content |
191
+ | `MermaidRenderer` | Render Mermaid diagrams from chat output |
192
+ | `CodeRenderer` | Syntax-highlighted code blocks |
193
+
194
+ ### User management
195
+
196
+ | Component | Use for |
197
+ | ---------------- | -------------------------------------------------------- |
198
+ | `UserManagement` | All-in-one user dashboard (table + modals + permissions) |
199
+ | `UserTable` | Just the user table |
200
+ | `UserModal` | Create/edit user form |
201
+ | `UserViewModal` | Read-only user details |
202
+
203
+ ### Helpers + variant utilities
204
+
205
+ | Helper | Purpose |
206
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------- |
207
+ | `cn` | `twMerge(clsx(…))` — conditional class composition |
208
+ | `tv` | `tailwind-variants` factory pre-configured with `twMerge` |
209
+ | `buildTestId` | Compose `data-testid` strings consistently |
210
+ | `buttonVariants`, `badge`, `modal`, `drawer`, `breadcrumbs`, `card`, `metricCard`, `rankedCard`, `activityList`, `slider`, `segmentedTrack`, `dropdownMenu`, `selectTV`, `pipelineVariants`, `spinnerVariants`, `emptyStateVariants` | Raw `tv()` outputs for consumers who want the styling without the component |
211
+
212
+ ---
213
+
214
+ ## Conventions
215
+
216
+ Knowing five rules unlocks every component.
217
+
218
+ ### 1. `Color` and `Size` enums
219
+
220
+ ```ts
221
+ import { Color, Size } from '@makolabs/ripple';
222
+
223
+ Color.DEFAULT |
224
+ Color.PRIMARY |
225
+ Color.SECONDARY |
226
+ Color.INFO |
227
+ Color.SUCCESS |
228
+ Color.WARNING |
229
+ Color.DANGER;
230
+
231
+ Size.XS | Size.SM | Size.MD | Size.LG | Size.XL | Size.XXL;
232
+ ```
173
233
 
174
- ## Component Variants
234
+ You can also pass the string literal (`color="primary"`, `size="md"`) — both are accepted because the enum values are plain strings.
175
235
 
176
- Most components in Ripple UI support variants to customize their appearance. Here are some examples:
236
+ A few components narrow `Size` to the variants their visual design supports (e.g. `Tooltip` uses `'sm' | 'md' | 'lg'`). The narrowed types are exported from the same package.
177
237
 
178
- ### Button Variants
238
+ ### 2. Event handler naming
179
239
 
180
- Buttons come with different variants, colors, sizes, and shapes:
240
+ All component event-handler props are **lowercase `on`-prefixed** (Svelte 5 convention):
181
241
 
182
242
  ```svelte
183
- <script lang="ts">
184
- import { Button, Color, Size } from '@makolabs/ripple';
185
- </script>
243
+ <Button onclick={save}>Save</Button>
244
+ <Modal onclose={() => (open = false)}>…</Modal>
245
+ <TabGroup onchange={(value) => goto(value)} />
246
+ <Slider oninput={(v) => (rate = v)} />
247
+ ```
186
248
 
187
- <!-- Different button variants -->
188
- <Button variant="solid" color={Color.PRIMARY}>Solid Button</Button>
189
- <Button variant="outline" color={Color.SECONDARY}>Outline Button</Button>
190
- <Button variant="ghost" color={Color.DANGER}>Ghost Button</Button>
191
- <Button variant="link" color={Color.INFO}>Link Button</Button>
192
-
193
- <!-- Button with onclick handler -->
194
- <Button color={Color.SUCCESS} onclick={() => console.log('Button clicked')}>Click Me</Button>
195
-
196
- <!-- Button as link -->
197
- <Button href="https://example.com" target="_blank" color={Color.PRIMARY}>Visit Website</Button>
198
-
199
- <!-- Button sizes -->
200
- <Button size={Size.XS}>Extra Small</Button>
201
- <Button size={Size.SM}>Small</Button>
202
- <Button size={Size.BASE}>Base</Button>
203
- <Button size={Size.LG}>Large</Button>
204
- <Button size={Size.XL}>Extra Large</Button>
205
- <Button size={Size.XXL}>2X Large</Button>
206
-
207
- <!-- Button variants with different colors -->
208
- <Button variant="solid" color={Color.PRIMARY}>Primary Solid</Button>
209
- <Button variant="solid" color={Color.DANGER}>Danger Solid</Button>
210
- <Button variant="outline" color={Color.SUCCESS}>Success Outline</Button>
211
- <Button variant="ghost" color={Color.WARNING}>Warning Ghost</Button>
212
- <Button variant="link" color={Color.INFO}>Info Link</Button>
249
+ Inside components, the lowercase prop is often aliased to a camelCase local for readability:
250
+
251
+ ```ts
252
+ let { onclose: onClose = () => {}, ...rest } = $props();
213
253
  ```
214
254
 
215
- ### Modal Variants
255
+ ### 3. Class props
216
256
 
217
- Modals with different sizes and custom content:
257
+ The root container takes `class?: ClassValue`. Sub-element class props use camelCase with a `Class` suffix:
218
258
 
219
259
  ```svelte
220
- <script lang="ts">
221
- import { Modal, Button, Size } from '@makolabs/ripple';
222
- let isOpen = false;
223
- </script>
260
+ <Modal
261
+ class="max-w-2xl"
262
+ titleClass="text-lg font-bold"
263
+ bodyClass="p-6"
264
+ footerClass="bg-default-50"
265
+ />
266
+ ```
224
267
 
225
- <Button onclick={() => (isOpen = true)}>Open Modal</Button>
268
+ `ClassValue` accepts strings, arrays, and conditional objects (like `clsx`).
226
269
 
227
- <!-- Basic modal -->
228
- <Modal open={isOpen} title="Basic Modal" size={Size.BASE} onClose={() => (isOpen = false)}>
229
- <p>Modal content goes here</p>
230
- </Modal>
270
+ ### 4. Snippets, not slots
231
271
 
232
- <!-- Modal with different size -->
233
- <Modal open={isOpen} title="Large Modal" size={Size.XL} onClose={() => (isOpen = false)}>
234
- <p>This modal is larger and provides more content space</p>
235
- </Modal>
272
+ Ripple is Svelte 5 no `<slot>` tags anywhere. All content projection uses snippets:
236
273
 
237
- <!-- Modal with custom header and footer -->
238
- <Modal open={isOpen} onClose={() => (isOpen = false)} size={Size.BASE}>
239
- <svelte:fragment slot="header">
240
- <div class="flex items-center">
241
- <svg
242
- xmlns="http://www.w3.org/2000/svg"
243
- width="24"
244
- height="24"
245
- fill="currentColor"
246
- viewBox="0 0 16 16"
247
- >
248
- <path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
249
- </svg>
250
- <h3 class="text-lg font-medium">Custom Header</h3>
251
- </div>
252
- </svelte:fragment>
253
-
254
- <p>Modal with custom header and footer</p>
255
-
256
- <svelte:fragment slot="footer">
257
- <div class="flex justify-end space-x-2">
258
- <Button variant="outline" onclick={() => (isOpen = false)}>Cancel</Button>
259
- <Button color={Color.PRIMARY}>Save Changes</Button>
260
- </div>
261
- </svelte:fragment>
274
+ ```svelte
275
+ <Modal bind:open title="Edit profile">
276
+ <ProfileForm />
277
+
278
+ {#snippet footer()}
279
+ <ModalFooter align="end">
280
+ <Button variant="outline" onclick={() => (open = false)}>Cancel</Button>
281
+ <Button onclick={save}>Save</Button>
282
+ </ModalFooter>
283
+ {/snippet}
262
284
  </Modal>
263
-
264
- <!-- TODO: Remove position prop from Modal component in future versions -->
265
285
  ```
266
286
 
267
- ### Drawer Component
268
-
269
- Drawers can slide in from different edges of the screen:
287
+ Snippets can also receive arguments — useful for per-row renderers in `Table`, per-step content in `Stepper`, etc.
270
288
 
271
- ```svelte
272
- <script lang="ts">
273
- import { Drawer, Button } from '@makolabs/ripple';
274
- let isDrawerOpen = false;
275
- </script>
289
+ ### 5. `testId` everywhere
276
290
 
277
- <Button onclick={() => (isDrawerOpen = true)}>Open Drawer</Button>
291
+ Every public component accepts an optional `testId?: string` and emits `data-testid` attributes scoped to its parts (`data-testid="modal-dialog"`, `data-testid="my-prefix-modal-body"` when `testId="my-prefix"`). Use it for E2E tests.
278
292
 
279
- <Drawer open={isDrawerOpen} position="right" onClose={() => (isDrawerOpen = false)}>
280
- <div class="p-4">
281
- <h3 class="mb-4 text-lg font-medium">Drawer Title</h3>
282
- <p class="mb-4">This is a drawer that slides in from the side of the screen.</p>
283
- <Button onclick={() => (isDrawerOpen = false)}>Close Drawer</Button>
284
- </div>
285
- </Drawer>
286
- ```
293
+ ---
287
294
 
288
- ### PageHeader Component
295
+ ## Selected examples
289
296
 
290
- A component for consistent page headers:
297
+ ### Forms
291
298
 
292
299
  ```svelte
293
300
  <script lang="ts">
294
- import { PageHeader, Button, Color } from '@makolabs/ripple';
295
-
296
- const breadcrumbs = [
297
- { label: 'Dashboard', href: '#' },
298
- { label: 'Projects', href: '#' },
299
- { label: 'Current Project' }
300
- ];
301
+ import { Form, Input, Textarea, RadioGroup, Button } from '@makolabs/ripple';
302
+ import { superForm } from 'sveltekit-superforms';
303
+ export let data;
304
+ const form = superForm(data.form);
301
305
  </script>
302
306
 
303
- <PageHeader
304
- title="Project Dashboard"
305
- description="View and manage your project details"
306
- {breadcrumbs}
307
- >
308
- <svelte:fragment slot="actions">
309
- <Button color={Color.PRIMARY}>New Project</Button>
310
- </svelte:fragment>
311
- </PageHeader>
312
- ```
313
-
314
- ### Card Variants
307
+ <Form {form} method="POST">
308
+ <Input name="email" type="email" label="Email" required />
315
309
 
316
- Cards can be customized with different styles:
310
+ <RadioGroup
311
+ name="plan"
312
+ label="Plan"
313
+ options={[
314
+ { value: 'free', label: 'Free' },
315
+ { value: 'pro', label: 'Pro', description: '$10/mo' }
316
+ ]}
317
+ />
317
318
 
318
- ```svelte
319
- <script lang="ts">
320
- import { Card, StatsCard, Color } from '@makolabs/ripple';
321
- </script>
319
+ <Textarea name="notes" label="Notes" rows={3} maxLength={500} showCount />
322
320
 
323
- <Card title="Basic Card" color={Color.PRIMARY}>
324
- <p>Card content goes here</p>
325
- </Card>
326
-
327
- <StatsCard
328
- label="Monthly Sales"
329
- value="$865,000"
330
- previousValue="$750,000"
331
- previousValuePrefix="vs"
332
- trend={15.3}
333
- color={Color.SUCCESS}
334
- chartData={[20, 25, 30, 22, 35, 40, 38, 45, 50]}
335
- icon={
336
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
337
- <path d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
338
- </svg>
339
- }
340
- />
321
+ <Button type="submit">Save</Button>
322
+ </Form>
341
323
  ```
342
324
 
343
- ### Table Component
325
+ ### Tables + cells
344
326
 
345
- Tables for displaying structured data with pagination and sorting:
327
+ `Cells` is a namespace import of pre-built snippets — `Currency`, `Email`, `DateCell`, `Time`, `PhoneNumber`, `Percentage`, `Status`. Hand them to `column.cell` to skip the boilerplate:
346
328
 
347
329
  ```svelte
348
330
  <script lang="ts">
349
- import { Table, Color, Size } from '@makolabs/ripple';
331
+ import { Table, Cells } from '@makolabs/ripple';
332
+ import type { TableColumn } from '@makolabs/ripple';
350
333
 
351
- let data = [
352
- { id: 1, name: 'John Doe', email: 'john@example.com', status: 'Active' },
353
- { id: 2, name: 'Jane Smith', email: 'jane@example.com', status: 'Inactive' },
354
- { id: 3, name: 'Robert Johnson', email: 'robert@example.com', status: 'Active' }
355
- ];
334
+ type User = { id: string; name: string; email: string; createdAt: string; lastSpent: number };
356
335
 
357
- const columns = [
358
- { key: 'name', label: 'Name', sortable: true },
359
- { key: 'email', label: 'Email', sortable: true },
360
- { key: 'status', label: 'Status' }
336
+ const columns: TableColumn<User>[] = [
337
+ { key: 'name', header: 'Name', sortable: true },
338
+ { key: 'email', header: 'Email', cell: Cells.Email },
339
+ { key: 'createdAt', header: 'Joined', cell: Cells.DateCell, align: 'right' },
340
+ { key: 'lastSpent', header: 'Last spent', cell: Cells.Currency, align: 'right' }
361
341
  ];
362
-
363
- let selected = [];
364
- let sort = { column: 'name', direction: 'asc' };
365
342
  </script>
366
343
 
367
344
  <Table
368
- {data}
369
345
  {columns}
370
- color={Color.PRIMARY}
371
- size={Size.BASE}
372
- pageSize={10}
373
- selectable={true}
374
- bind:selected
375
- bind:sort
376
- striped={true}
346
+ data={users}
347
+ {loading}
348
+ pageSize={25}
349
+ title="Customers"
350
+ onrowclick={(user) => goto(`/users/${user.id}`)}
377
351
  />
378
352
  ```
379
353
 
380
- ### Tab Component
354
+ ### Modals + dialogs
381
355
 
382
- Tabs for organizing content into different views:
356
+ Three flavours: `Modal` (general), `ConfirmDialog` (yes/no), `Drawer` (side panel).
383
357
 
384
358
  ```svelte
385
359
  <script lang="ts">
386
- import { TabGroup, TabContent, Color, Size } from '@makolabs/ripple';
387
-
388
- const tabs = [
389
- { value: 'overview', label: 'Overview' },
390
- { value: 'details', label: 'Details' },
391
- { value: 'settings', label: 'Settings' }
392
- ];
393
-
394
- let activeTab = 'overview';
395
-
396
- function handleTabChange(value) {
397
- console.log(`Tab changed to ${value}`);
360
+ import { ConfirmDialog, Button, Color } from '@makolabs/ripple';
361
+ let open = $state(false);
362
+ async function deleteAccount() {
363
+ await api.deleteAccount();
364
+ // dialog auto-closes when onconfirm resolves
398
365
  }
399
366
  </script>
400
367
 
401
- <TabGroup
402
- {tabs}
403
- bind:selected={activeTab}
404
- onchange={handleTabChange}
405
- color={Color.PRIMARY}
406
- size={Size.BASE}
407
- >
408
- <TabContent value="overview" persisted>
409
- <p>Overview content here</p>
410
- </TabContent>
411
-
412
- <TabContent value="details" persisted>
413
- <p>Details content here</p>
414
- </TabContent>
415
-
416
- <TabContent value="settings" persisted>
417
- <p>Settings content here</p>
418
- </TabContent>
419
- </TabGroup>
420
- ```
421
-
422
- ### Badge Component
423
-
424
- Badges for displaying statuses and counts:
425
-
426
- ```svelte
427
- <script lang="ts">
428
- import { Badge, Color, Size } from '@makolabs/ripple';
429
- </script>
368
+ <Button color={Color.DANGER} variant="outline" onclick={() => (open = true)}>Delete account</Button>
430
369
 
431
- <Badge color={Color.PRIMARY} size={Size.BASE}>New</Badge>
432
- <Badge color={Color.SUCCESS}>Success</Badge>
433
- <Badge color={Color.WARNING}>Warning</Badge>
434
- <Badge color={Color.DANGER}>43</Badge>
370
+ <ConfirmDialog
371
+ bind:open
372
+ title="Delete your account?"
373
+ message="This permanently removes your account, files, and history. This cannot be undone."
374
+ confirmLabel="Delete forever"
375
+ confirmColor={Color.DANGER}
376
+ onconfirm={deleteAccount}
377
+ />
435
378
  ```
436
379
 
437
- ### Select Component
380
+ ### Filters
438
381
 
439
- Dropdown selector for choosing from a list of options:
382
+ `FilterPopover` for per-group dropdowns, `FilterBar` for add-as-needed chips, `CompactFilters` for collapsed-by-default detail panels. All three take the same `filterGroups` shape, including date-range groups and URL syncing:
440
383
 
441
384
  ```svelte
442
385
  <script lang="ts">
443
- import { Select, Size } from '@makolabs/ripple';
386
+ import { FilterPopover, syncFiltersToUrl } from '@makolabs/ripple';
387
+ import type { FilterGroup, FilterSelectionValue } from '@makolabs/ripple';
444
388
 
445
- const items = [
446
- { label: 'Option 1', value: 'option1' },
447
- { label: 'Option 2', value: 'option2' },
448
- { label: 'Option 3', value: 'option3', disabled: true },
449
- { label: 'Option 4', value: 'option4' }
450
- ];
451
-
452
- let selected = 'option1';
453
-
454
- function handleSelect(event) {
455
- console.log('Selected:', event.value);
456
- }
457
- </script>
458
-
459
- <Select {items} bind:value={selected} class="w-64" size={Size.BASE} onselect={handleSelect} />
460
- ```
461
-
462
- ### Dropdown Component
463
-
464
- Menu dropdown for actions and navigation:
465
-
466
- ```svelte
467
- <script lang="ts">
468
- import { Dropdown, Size } from '@makolabs/ripple';
469
- import FluentChevronDown16Filled from '$icons/FluentChevronDown16Filled.svelte';
389
+ let selections = $state<Record<string, FilterSelectionValue>>({
390
+ status: 'all',
391
+ created: null
392
+ });
470
393
 
471
- const sections = [
394
+ const filterGroups: FilterGroup[] = [
472
395
  {
473
- items: [
474
- {
475
- label: 'Edit',
476
- icon: FluentPen16Filled,
477
- onclick: () => console.log('Edit clicked')
478
- },
479
- {
480
- label: 'Duplicate',
481
- icon: FluentPenSparkle24Filled,
482
- onclick: () => console.log('Duplicate clicked')
483
- }
396
+ key: 'status',
397
+ label: 'Status',
398
+ tabs: [
399
+ { value: 'all', label: 'All', count: 1247 },
400
+ { value: 'active', label: 'Active', count: 823 },
401
+ { value: 'archived', label: 'Archived', count: 112 }
484
402
  ]
485
403
  },
486
- {
487
- items: [
488
- {
489
- label: 'Delete',
490
- icon: FluentDelete24Filled,
491
- onclick: () => console.log('Delete clicked')
492
- }
493
- ]
494
- }
404
+ { key: 'created', label: 'Created', dateRange: true } // built-in calendar + presets
495
405
  ];
406
+
407
+ syncFiltersToUrl(
408
+ () => selections,
409
+ (v) => (selections = v),
410
+ { dateRangeKeys: ['created'] }
411
+ );
496
412
  </script>
497
413
 
498
- <Dropdown {sections} label="Actions" size={Size.BASE} icon={FluentChevronDown16Filled} />
414
+ <FilterPopover {filterGroups} bind:selections />
499
415
  ```
500
416
 
501
- ## Component Composition
502
-
503
- Ripple UI components are designed to work together seamlessly:
417
+ ### Charts
504
418
 
505
419
  ```svelte
506
420
  <script lang="ts">
507
- import { Card, TabGroup, TabContent, Button, Color, Size } from '@makolabs/ripple';
421
+ import { Chart, ChartColor } from '@makolabs/ripple';
508
422
 
509
- const tabs = [
510
- { value: 'overview', label: 'Overview' },
511
- { value: 'details', label: 'Details' },
512
- { value: 'settings', label: 'Settings' }
423
+ const data = [
424
+ { month: '2026-01', netCash: 250000 },
425
+ { month: '2026-02', netCash: -120000 },
426
+ { month: '2026-03', netCash: 60000 }
513
427
  ];
514
-
515
- let activeTab = 'overview';
516
428
  </script>
517
429
 
518
- <Card title="Project Information" color={Color.PRIMARY}>
519
- <TabGroup {tabs} bind:selected={activeTab} color={Color.INFO} size={Size.BASE}>
520
- <TabContent value="overview">
521
- <p>Project overview content here...</p>
522
- <Button variant="solid" color={Color.SUCCESS} size={Size.SM}>Take Action</Button>
523
- </TabContent>
524
-
525
- <TabContent value="details">
526
- <p>Project details content here...</p>
527
- </TabContent>
528
-
529
- <TabContent value="settings">
530
- <p>Project settings content here...</p>
531
- </TabContent>
532
- </TabGroup>
533
- </Card>
430
+ <Chart
431
+ {data}
432
+ config={{
433
+ xAxis: { dataKey: 'month' },
434
+ yAxis: [
435
+ {
436
+ dataKey: 'netCash',
437
+ label: 'Net cash (CHF)',
438
+ format: (v) => `CHF ${(v / 1000).toFixed(0)}K`
439
+ }
440
+ ],
441
+ series: [
442
+ {
443
+ dataKey: 'netCash',
444
+ name: 'Monthly Net Value',
445
+ type: 'bar',
446
+ color: ChartColor.HEALTH
447
+ }
448
+ ]
449
+ }}
450
+ height="320px"
451
+ />
534
452
  ```
535
453
 
536
- ## Latest Updates
454
+ ---
537
455
 
538
- Ripple UI now exports all components from a central entry point, making it easier to import components:
456
+ ## Server-side helpers
539
457
 
540
- ```svelte
541
- <script lang="ts">
542
- import { Button, Modal, Card, Table, Select, Dropdown } from '@makolabs/ripple';
543
- </script>
458
+ Some components have a server-side companion. Import from `@makolabs/ripple/server`:
459
+
460
+ ```ts
461
+ // src/routes/api/s3/list/+server.ts
462
+ import { createS3Handlers } from '@makolabs/ripple/server';
463
+ import { env } from '$env/dynamic/private';
464
+
465
+ const s3 = createS3Handlers({
466
+ bucket: env.S3_BUCKET,
467
+ region: env.S3_REGION,
468
+ accessKeyId: env.S3_ACCESS_KEY_ID,
469
+ secretAccessKey: env.S3_SECRET_ACCESS_KEY,
470
+ endpoint: env.S3_ENDPOINT // optional, e.g. for DigitalOcean Spaces
471
+ });
472
+
473
+ export const GET = ({ request }) => s3.list(request);
544
474
  ```
545
475
 
546
- You can still import specific component types when needed:
476
+ Add the matching `download` route at `src/routes/api/s3/download/+server.ts` (`s3.download`). The client `S3Adapter` calls both endpoints — see the `FileBrowser` example below.
547
477
 
548
478
  ```svelte
549
479
  <script lang="ts">
550
- import { Button, type ButtonProps } from '@makolabs/ripple';
551
-
552
- // Create a custom button with specific props
553
- const myButton: ButtonProps = {
554
- variant: 'outline',
555
- color: 'primary',
556
- size: 'lg',
557
- rounded: 'xl'
558
- };
480
+ import { FileBrowser, S3Adapter } from '@makolabs/ripple';
481
+ const adapter = new S3Adapter({ basePath: 'imports/' });
559
482
  </script>
560
483
 
561
- <Button {...myButton}>Custom Button</Button>
484
+ <FileBrowser {adapter} />
562
485
  ```
563
486
 
564
- ## Testing
565
-
566
- Ripple UI uses [Vitest](https://vitest.dev/) for unit and component testing. The test suite includes tests for core library components using Svelte 5's native testing APIs.
567
-
568
- ### Running Tests Locally
569
-
570
- Run all tests once:
571
-
572
- ```bash
573
- npm run test:unit -- --run
574
- ```
487
+ ---
575
488
 
576
- Run tests in watch mode (for development):
489
+ ## Testing
577
490
 
578
- ```bash
579
- npm run test:unit
580
- ```
491
+ Ripple uses Vitest with two workspace projects:
581
492
 
582
- Run all tests (unit + e2e):
493
+ - **client** jsdom environment, runs `*.svelte.{test,spec}.{js,ts}`
494
+ - **server** — node environment, runs other `*.{test,spec}.{js,ts}`
583
495
 
584
496
  ```bash
585
- npm test
497
+ npm test # run all tests
498
+ npm run test:unit # watch mode
499
+ npx vitest run path/to/file.test.ts # single test
586
500
  ```
587
501
 
588
- ### Writing Tests
502
+ ### Component test pattern
589
503
 
590
- Component tests use Svelte 5's `mount` and `unmount` APIs for testing components. Here's an example test for a Button component:
504
+ Svelte 5 snippets can't be passed as props from JS, so each tested component has a TestWrapper that takes scalar props and renders snippets internally:
591
505
 
592
- ```typescript
593
- import { flushSync, mount, unmount } from 'svelte';
506
+ ```ts
507
+ // Button.svelte.test.ts
508
+ import { mount, unmount } from 'svelte';
594
509
  import { expect, test } from 'vitest';
595
510
  import ButtonTestWrapper from './ButtonTestWrapper.svelte';
596
511
 
597
- test('renders as a button with slot content and respects disabled prop', () => {
512
+ test('renders disabled button with text', () => {
598
513
  const component = mount(ButtonTestWrapper, {
599
514
  target: document.body,
600
- props: {
601
- disabled: true,
602
- text: 'Click me'
603
- }
515
+ props: { disabled: true, text: 'Click me' }
604
516
  });
605
517
 
606
518
  const btn = document.body.querySelector('button') as HTMLButtonElement;
607
- expect(btn).toBeTruthy();
608
519
  expect(btn.disabled).toBe(true);
609
520
  expect(btn.textContent).toContain('Click me');
610
521
 
@@ -612,44 +523,39 @@ test('renders as a button with slot content and respects disabled prop', () => {
612
523
  });
613
524
  ```
614
525
 
615
- For components with snippet props (like `children` or `footer`), create a test wrapper component that accepts simple props and renders them as slots.
616
-
617
- ### Test Configuration
526
+ Use `flushSync()` from `svelte` after programmatic interactions that trigger reactive updates.
618
527
 
619
- Tests are configured using Vitest workspaces in `vite.config.ts`:
528
+ ---
620
529
 
621
- - **Client tests**: Run in jsdom environment, include files matching `src/**/*.svelte.{test,spec}.{js,ts}`
622
- - **Server tests**: Run in node environment, include files matching `src/**/*.{test,spec}.{js,ts}` (excluding `.svelte` tests)
623
-
624
- The setup file `vitest-setup-client.ts` includes:
530
+ ## Troubleshooting
625
531
 
626
- - `@testing-library/jest-dom` matchers for enhanced assertions
627
- - `matchMedia` mock for jsdom compatibility with Svelte 5
532
+ ### Components render without color
628
533
 
629
- ### Continuous Integration
534
+ Tailwind 4 doesn't ship the color tokens Ripple uses. Add the `@theme` block to your `app.css` (see [Theme tokens](#theme-tokens)).
630
535
 
631
- For CI environments (GitHub Actions, etc.), use:
536
+ ### Component classes missing in production
632
537
 
633
- ```yaml
634
- - name: Install dependencies
635
- run: npm ci
538
+ Tailwind 4 doesn't scan `node_modules` automatically. Add to `app.css`:
636
539
 
637
- - name: Run tests
638
- run: npm run test:unit -- --run
540
+ ```css
541
+ @source '../node_modules/@makolabs/ripple';
639
542
  ```
640
543
 
641
- ## Troubleshooting
544
+ ### `import 'foo' with `…/$lib/foo.ts'` doesn't resolve
642
545
 
643
- ### Missing color tokens (components render without color)
546
+ This codebase uses `moduleResolution: 'nodenext'` local imports must use `.js` extensions even when importing `.ts` files:
644
547
 
645
- Ripple UI uses custom color tokens (e.g. `primary`, `secondary`, `danger`) that must be defined in your app's CSS. If components appear unstyled or use fallback colors, add the `@theme` block with all required color token definitions to your `app.css`. See the full token list in the [Usage](#usage) section above.
548
+ ```ts
549
+ import { cn } from '$lib/helper/cls.js'; // ✅
550
+ import { cn } from '$lib/helper/cls'; // ❌
551
+ ```
646
552
 
647
- ### TailwindCSS 4 `@source` configuration
553
+ ### Storybook
648
554
 
649
- TailwindCSS 4 does not automatically scan `node_modules` for class names. You must add the following directive to your `app.css` so that Tailwind generates the utility classes used by Ripple UI components:
555
+ The repo includes Storybook (`npm run storybook`, port 6006) with stories for every component and every interesting prop combination. Browse it for live examples beyond what's in this README.
650
556
 
651
- ```css
652
- @source '../node_modules/@makolabs/ripple';
653
- ```
557
+ ---
558
+
559
+ ## License
654
560
 
655
- Without this, component styles that rely on Tailwind utilities will be missing from your build output.
561
+ MIT.