@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.
- package/README.md +403 -497
- package/dist/adapters/storage/S3Adapter.d.ts +49 -1
- package/dist/adapters/storage/S3Adapter.js +38 -1
- package/dist/adapters/storage/types.d.ts +20 -0
- package/dist/ai/AIChatInterface.svelte +2 -1
- package/dist/ai/AIChatInterface.svelte.d.ts +2 -1
- package/dist/ai/CodeRenderer.svelte +7 -2
- package/dist/ai/CodeRenderer.svelte.d.ts +2 -1
- package/dist/ai/ComposeDropdown.svelte +1 -1
- package/dist/ai/MessageBox.svelte +3 -3
- package/dist/ai/MessageBox.svelte.d.ts +3 -2
- package/dist/ai/ThinkingDisplay.svelte +4 -3
- package/dist/ai/ThinkingDisplay.svelte.d.ts +2 -1
- package/dist/ai/ai-types.d.ts +55 -1
- package/dist/button/Button.svelte +5 -5
- package/dist/button/button-types.d.ts +49 -4
- package/dist/button/button.d.ts +9 -9
- package/dist/button/button.js +6 -6
- package/dist/charts/Chart.svelte +8 -16
- package/dist/charts/chart-types.d.ts +78 -1
- package/dist/drawer/Drawer.svelte +6 -26
- package/dist/drawer/drawer-types.d.ts +33 -12
- package/dist/drawer/drawer.d.ts +3 -3
- package/dist/drawer/drawer.js +1 -1
- package/dist/elements/accordion/Accordion.svelte +6 -17
- package/dist/elements/accordion/accordion-types.d.ts +53 -6
- package/dist/elements/alert/Alert.svelte +3 -0
- package/dist/elements/badge/Badge.svelte +1 -1
- package/dist/elements/badge/badge-types.d.ts +22 -0
- package/dist/elements/badge/badge.d.ts +3 -3
- package/dist/elements/badge/badge.js +1 -1
- package/dist/elements/combobox/ComboBox.svelte +247 -0
- package/dist/elements/combobox/ComboBox.svelte.d.ts +4 -0
- package/dist/elements/combobox/combobox-types.d.ts +41 -0
- package/dist/elements/combobox/combobox-types.js +1 -0
- package/dist/elements/context-menu/ContextMenu.svelte +137 -0
- package/dist/elements/context-menu/ContextMenu.svelte.d.ts +4 -0
- package/dist/elements/context-menu/context-menu-types.d.ts +40 -0
- package/dist/elements/context-menu/context-menu-types.js +1 -0
- package/dist/elements/dropdown/Dropdown.svelte +1 -1
- package/dist/elements/dropdown/Select.svelte +4 -1
- package/dist/elements/dropdown/dropdown-types.d.ts +114 -0
- package/dist/elements/dropdown/dropdown.d.ts +3 -3
- package/dist/elements/dropdown/dropdown.js +2 -2
- package/dist/elements/dropdown/select.d.ts +3 -3
- package/dist/elements/dropdown/select.js +2 -2
- package/dist/elements/empty-state/EmptyState.svelte +1 -1
- package/dist/elements/empty-state/empty-state-types.d.ts +32 -1
- package/dist/elements/empty-state/empty-state.d.ts +3 -3
- package/dist/elements/empty-state/empty-state.js +2 -2
- package/dist/elements/file-upload/FileUpload.svelte +5 -0
- package/dist/elements/file-upload/file-upload-types.d.ts +59 -0
- package/dist/elements/pagination/Pagination.svelte +53 -21
- package/dist/elements/pagination/Pagination.svelte.d.ts +33 -5
- package/dist/elements/popover/Popover.svelte +234 -0
- package/dist/elements/popover/Popover.svelte.d.ts +4 -0
- package/dist/elements/popover/index.d.ts +2 -0
- package/dist/elements/popover/index.js +1 -0
- package/dist/elements/popover/popover-types.d.ts +60 -0
- package/dist/elements/popover/popover-types.js +1 -0
- package/dist/elements/progress/Progress.svelte +32 -7
- package/dist/elements/progress/progress-types.d.ts +48 -1
- package/dist/elements/skeleton/Skeleton.svelte +56 -0
- package/dist/elements/skeleton/Skeleton.svelte.d.ts +4 -0
- package/dist/elements/skeleton/index.d.ts +2 -0
- package/dist/elements/skeleton/index.js +1 -0
- package/dist/elements/skeleton/skeleton-types.d.ts +50 -0
- package/dist/elements/skeleton/skeleton-types.js +1 -0
- package/dist/elements/spinner/Spinner.svelte +1 -1
- package/dist/elements/spinner/spinner-types.d.ts +20 -0
- package/dist/elements/spinner/spinner.d.ts +3 -3
- package/dist/elements/spinner/spinner.js +2 -2
- package/dist/elements/tooltip/Tooltip.svelte +108 -11
- package/dist/elements/tooltip/tooltip-types.d.ts +49 -1
- package/dist/file-browser/FileBrowser.svelte +21 -12
- package/dist/filters/CompactFilters.svelte +221 -33
- package/dist/filters/CompactFilters.svelte.d.ts +1 -1
- package/dist/filters/FilterBar.svelte +184 -0
- package/dist/filters/FilterBar.svelte.d.ts +4 -0
- package/dist/filters/FilterPopover.svelte +346 -0
- package/dist/filters/FilterPopover.svelte.d.ts +4 -0
- package/dist/filters/date-presets.d.ts +15 -0
- package/dist/filters/date-presets.js +107 -0
- package/dist/filters/filter-types.d.ts +69 -3
- package/dist/filters/index.d.ts +5 -0
- package/dist/filters/index.js +4 -0
- package/dist/filters/sync-filters-to-url.svelte.d.ts +37 -0
- package/dist/filters/sync-filters-to-url.svelte.js +114 -0
- package/dist/forms/DateRange.svelte +4 -2
- package/dist/forms/Input.svelte +2 -2
- package/dist/forms/MarketSelector.svelte +8 -3
- package/dist/forms/NumberInput.svelte +4 -4
- package/dist/forms/RadioGroup.svelte +123 -0
- package/dist/forms/RadioGroup.svelte.d.ts +4 -0
- package/dist/forms/SegmentedControl.svelte +11 -4
- package/dist/forms/Slider.svelte +72 -3
- package/dist/forms/Tags.svelte +14 -5
- package/dist/forms/Textarea.svelte +126 -0
- package/dist/forms/Textarea.svelte.d.ts +4 -0
- package/dist/forms/Toggle.svelte +8 -8
- package/dist/forms/calendar/Calendar.svelte +218 -0
- package/dist/forms/calendar/Calendar.svelte.d.ts +4 -0
- package/dist/forms/calendar/calendar-types.d.ts +46 -0
- package/dist/forms/calendar/calendar-types.js +1 -0
- package/dist/forms/calendar/index.d.ts +2 -0
- package/dist/forms/calendar/index.js +1 -0
- package/dist/forms/date-picker/DatePicker.svelte +144 -0
- package/dist/forms/date-picker/DatePicker.svelte.d.ts +4 -0
- package/dist/forms/date-picker/date-picker-types.d.ts +29 -0
- package/dist/forms/date-picker/date-picker-types.js +1 -0
- package/dist/forms/form-types.d.ts +425 -6
- package/dist/forms/market/market-selector-types.d.ts +52 -1
- package/dist/forms/segmented-control.d.ts +5 -2
- package/dist/forms/segmented-control.js +16 -5
- package/dist/forms/slider.d.ts +3 -3
- package/dist/forms/slider.js +2 -2
- package/dist/funcs/user-management.remote.js +1 -1
- package/dist/header/Breadcrumbs.svelte +4 -20
- package/dist/header/PageHeader.svelte +6 -14
- package/dist/header/breadcrumbs.d.ts +3 -11
- package/dist/header/breadcrumbs.js +10 -5
- package/dist/header/header-types.d.ts +62 -11
- package/dist/index.d.ts +35 -9
- package/dist/index.js +24 -4
- package/dist/layout/activity-list/ActivityList.svelte +13 -7
- package/dist/layout/activity-list/activity-list-types.d.ts +46 -7
- package/dist/layout/card/Card.svelte +12 -15
- package/dist/layout/card/MetricCard.svelte +50 -32
- package/dist/layout/card/card-types.d.ts +114 -4
- package/dist/layout/navbar/navbar-types.d.ts +48 -0
- package/dist/layout/navbar/navbar.d.ts +3 -3
- package/dist/layout/navbar/navbar.js +2 -2
- package/dist/layout/sidebar/Sidebar.svelte +87 -11
- package/dist/layout/sidebar/sidebar-types.d.ts +60 -1
- package/dist/layout/stepper/Stepper.svelte +288 -0
- package/dist/layout/stepper/Stepper.svelte.d.ts +4 -0
- package/dist/layout/stepper/stepper-types.d.ts +80 -0
- package/dist/layout/stepper/stepper-types.js +1 -0
- package/dist/layout/table/Table.svelte +91 -85
- package/dist/layout/table/table-types.d.ts +148 -24
- package/dist/layout/table/table.d.ts +3 -3
- package/dist/layout/table/table.js +2 -2
- package/dist/layout/tabs/Tab.svelte +6 -2
- package/dist/layout/tabs/Tab.svelte.d.ts +4 -1
- package/dist/layout/tabs/TabGroup.svelte +9 -2
- package/dist/layout/tabs/tabs-types.d.ts +63 -0
- package/dist/layout/tabs/tabs.d.ts +3 -3
- package/dist/layout/tabs/tabs.js +12 -6
- package/dist/modal/ConfirmDialog.svelte +65 -0
- package/dist/modal/ConfirmDialog.svelte.d.ts +4 -0
- package/dist/modal/Modal.svelte +6 -26
- package/dist/modal/confirm-dialog-types.d.ts +39 -0
- package/dist/modal/confirm-dialog-types.js +1 -0
- package/dist/modal/modal-types.d.ts +51 -12
- package/dist/modal/modal.d.ts +3 -3
- package/dist/modal/modal.js +3 -3
- package/dist/pipeline/Pipeline.svelte +8 -3
- package/dist/pipeline/pipeline-types.d.ts +55 -3
- package/dist/pipeline/pipeline.d.ts +18 -3
- package/dist/pipeline/pipeline.js +7 -2
- package/dist/server/s3.d.ts +35 -3
- package/dist/sonner/Toaster.svelte +29 -0
- package/dist/sonner/Toaster.svelte.d.ts +4 -0
- package/dist/sonner/index.d.ts +21 -0
- package/dist/sonner/index.js +20 -0
- package/dist/user-management/UserManagement.svelte +22 -16
- package/dist/user-management/UserModal.svelte +10 -7
- package/dist/user-management/UserTable.svelte +16 -17
- package/dist/user-management/UserViewModal.svelte +11 -11
- package/dist/user-management/user-management-types.d.ts +118 -31
- package/dist/variants.d.ts +1 -1
- package/dist/variants.js +1 -1
- package/package.json +7 -4
- package/dist/config/ai.d.ts +0 -13
- package/dist/config/ai.js +0 -44
- package/dist/elements/empty-state/EmptyStateTestWrapper.svelte +0 -25
- package/dist/elements/empty-state/EmptyStateTestWrapper.svelte.d.ts +0 -8
- package/dist/elements/tooltip/TooltipTestWrapper.svelte +0 -14
- package/dist/elements/tooltip/TooltipTestWrapper.svelte.d.ts +0 -7
- package/dist/helper/deprecation.d.ts +0 -14
- package/dist/helper/deprecation.js +0 -24
- package/dist/modal/ModalFooterTestWrapper.svelte +0 -17
- 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
|
|
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
|
-
|
|
9
|
+
---
|
|
23
10
|
|
|
24
|
-
|
|
11
|
+
## Table of contents
|
|
25
12
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
### 1. Install
|
|
32
|
+
|
|
33
|
+
```shell
|
|
34
|
+
npm i @makolabs/ripple
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
|
|
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
|
-
/*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
238
|
+
### 2. Event handler naming
|
|
179
239
|
|
|
180
|
-
|
|
240
|
+
All component event-handler props are **lowercase `on`-prefixed** (Svelte 5 convention):
|
|
181
241
|
|
|
182
242
|
```svelte
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
###
|
|
255
|
+
### 3. Class props
|
|
216
256
|
|
|
217
|
-
|
|
257
|
+
The root container takes `class?: ClassValue`. Sub-element class props use camelCase with a `Class` suffix:
|
|
218
258
|
|
|
219
259
|
```svelte
|
|
220
|
-
<
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
268
|
+
`ClassValue` accepts strings, arrays, and conditional objects (like `clsx`).
|
|
226
269
|
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
<Modal open
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
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
|
-
|
|
272
|
-
<script lang="ts">
|
|
273
|
-
import { Drawer, Button } from '@makolabs/ripple';
|
|
274
|
-
let isDrawerOpen = false;
|
|
275
|
-
</script>
|
|
289
|
+
### 5. `testId` everywhere
|
|
276
290
|
|
|
277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
+
## Selected examples
|
|
289
296
|
|
|
290
|
-
|
|
297
|
+
### Forms
|
|
291
298
|
|
|
292
299
|
```svelte
|
|
293
300
|
<script lang="ts">
|
|
294
|
-
import {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
<
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
324
|
-
|
|
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
|
-
###
|
|
325
|
+
### Tables + cells
|
|
344
326
|
|
|
345
|
-
|
|
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,
|
|
331
|
+
import { Table, Cells } from '@makolabs/ripple';
|
|
332
|
+
import type { TableColumn } from '@makolabs/ripple';
|
|
350
333
|
|
|
351
|
-
|
|
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',
|
|
359
|
-
{ key: 'email',
|
|
360
|
-
{ key: '
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
pageSize={
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
###
|
|
354
|
+
### Modals + dialogs
|
|
381
355
|
|
|
382
|
-
|
|
356
|
+
Three flavours: `Modal` (general), `ConfirmDialog` (yes/no), `Drawer` (side panel).
|
|
383
357
|
|
|
384
358
|
```svelte
|
|
385
359
|
<script lang="ts">
|
|
386
|
-
import {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
###
|
|
380
|
+
### Filters
|
|
438
381
|
|
|
439
|
-
|
|
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 {
|
|
386
|
+
import { FilterPopover, syncFiltersToUrl } from '@makolabs/ripple';
|
|
387
|
+
import type { FilterGroup, FilterSelectionValue } from '@makolabs/ripple';
|
|
444
388
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
394
|
+
const filterGroups: FilterGroup[] = [
|
|
472
395
|
{
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
<
|
|
414
|
+
<FilterPopover {filterGroups} bind:selections />
|
|
499
415
|
```
|
|
500
416
|
|
|
501
|
-
|
|
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 {
|
|
421
|
+
import { Chart, ChartColor } from '@makolabs/ripple';
|
|
508
422
|
|
|
509
|
-
const
|
|
510
|
-
{
|
|
511
|
-
{
|
|
512
|
-
{
|
|
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
|
-
<
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
454
|
+
---
|
|
537
455
|
|
|
538
|
-
|
|
456
|
+
## Server-side helpers
|
|
539
457
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
<
|
|
484
|
+
<FileBrowser {adapter} />
|
|
562
485
|
```
|
|
563
486
|
|
|
564
|
-
|
|
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
|
-
|
|
489
|
+
## Testing
|
|
577
490
|
|
|
578
|
-
|
|
579
|
-
npm run test:unit
|
|
580
|
-
```
|
|
491
|
+
Ripple uses Vitest with two workspace projects:
|
|
581
492
|
|
|
582
|
-
|
|
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
|
-
###
|
|
502
|
+
### Component test pattern
|
|
589
503
|
|
|
590
|
-
|
|
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
|
-
```
|
|
593
|
-
|
|
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
|
|
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
|
-
|
|
616
|
-
|
|
617
|
-
### Test Configuration
|
|
526
|
+
Use `flushSync()` from `svelte` after programmatic interactions that trigger reactive updates.
|
|
618
527
|
|
|
619
|
-
|
|
528
|
+
---
|
|
620
529
|
|
|
621
|
-
|
|
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
|
-
|
|
627
|
-
- `matchMedia` mock for jsdom compatibility with Svelte 5
|
|
532
|
+
### Components render without color
|
|
628
533
|
|
|
629
|
-
|
|
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
|
-
|
|
536
|
+
### Component classes missing in production
|
|
632
537
|
|
|
633
|
-
|
|
634
|
-
- name: Install dependencies
|
|
635
|
-
run: npm ci
|
|
538
|
+
Tailwind 4 doesn't scan `node_modules` automatically. Add to `app.css`:
|
|
636
539
|
|
|
637
|
-
|
|
638
|
-
|
|
540
|
+
```css
|
|
541
|
+
@source '../node_modules/@makolabs/ripple';
|
|
639
542
|
```
|
|
640
543
|
|
|
641
|
-
|
|
544
|
+
### `import 'foo' with `…/$lib/foo.ts'` doesn't resolve
|
|
642
545
|
|
|
643
|
-
|
|
546
|
+
This codebase uses `moduleResolution: 'nodenext'` — local imports must use `.js` extensions even when importing `.ts` files:
|
|
644
547
|
|
|
645
|
-
|
|
548
|
+
```ts
|
|
549
|
+
import { cn } from '$lib/helper/cls.js'; // ✅
|
|
550
|
+
import { cn } from '$lib/helper/cls'; // ❌
|
|
551
|
+
```
|
|
646
552
|
|
|
647
|
-
###
|
|
553
|
+
### Storybook
|
|
648
554
|
|
|
649
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## License
|
|
654
560
|
|
|
655
|
-
|
|
561
|
+
MIT.
|