@miozu/jera 0.0.2 → 0.3.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 (53) hide show
  1. package/CLAUDE.md +443 -0
  2. package/README.md +211 -1
  3. package/llms.txt +64 -0
  4. package/package.json +44 -14
  5. package/src/actions/index.js +375 -0
  6. package/src/components/feedback/EmptyState.svelte +179 -0
  7. package/src/components/feedback/ProgressBar.svelte +116 -0
  8. package/src/components/feedback/Skeleton.svelte +107 -0
  9. package/src/components/feedback/Spinner.svelte +77 -0
  10. package/src/components/feedback/Toast.svelte +297 -0
  11. package/src/components/forms/Checkbox.svelte +147 -0
  12. package/src/components/forms/Dropzone.svelte +248 -0
  13. package/src/components/forms/FileUpload.svelte +266 -0
  14. package/src/components/forms/IconInput.svelte +184 -0
  15. package/src/components/forms/Input.svelte +121 -0
  16. package/src/components/forms/NumberInput.svelte +225 -0
  17. package/src/components/forms/PinInput.svelte +169 -0
  18. package/src/components/forms/Radio.svelte +143 -0
  19. package/src/components/forms/RadioGroup.svelte +62 -0
  20. package/src/components/forms/RangeSlider.svelte +212 -0
  21. package/src/components/forms/SearchInput.svelte +175 -0
  22. package/src/components/forms/Select.svelte +326 -0
  23. package/src/components/forms/Switch.svelte +159 -0
  24. package/src/components/forms/Textarea.svelte +122 -0
  25. package/src/components/navigation/Accordion.svelte +65 -0
  26. package/src/components/navigation/AccordionItem.svelte +146 -0
  27. package/src/components/navigation/Tabs.svelte +239 -0
  28. package/src/components/overlays/ConfirmDialog.svelte +272 -0
  29. package/src/components/overlays/Dropdown.svelte +153 -0
  30. package/src/components/overlays/DropdownDivider.svelte +23 -0
  31. package/src/components/overlays/DropdownItem.svelte +97 -0
  32. package/src/components/overlays/Modal.svelte +232 -0
  33. package/src/components/overlays/Popover.svelte +206 -0
  34. package/src/components/primitives/Avatar.svelte +132 -0
  35. package/src/components/primitives/Badge.svelte +118 -0
  36. package/src/components/primitives/Button.svelte +262 -0
  37. package/src/components/primitives/Card.svelte +104 -0
  38. package/src/components/primitives/Divider.svelte +105 -0
  39. package/src/components/primitives/LazyImage.svelte +104 -0
  40. package/src/components/primitives/Link.svelte +122 -0
  41. package/src/components/primitives/StatusBadge.svelte +122 -0
  42. package/src/index.js +128 -0
  43. package/src/tokens/colors.css +189 -0
  44. package/src/tokens/effects.css +128 -0
  45. package/src/tokens/index.css +81 -0
  46. package/src/tokens/spacing.css +49 -0
  47. package/src/tokens/typography.css +79 -0
  48. package/src/utils/cn.svelte.js +175 -0
  49. package/src/utils/index.js +17 -0
  50. package/src/utils/reactive.svelte.js +239 -0
  51. package/jera.js +0 -135
  52. package/www/components/jera/Input/Input.svelte +0 -63
  53. package/www/components/jera/Input/index.js +0 -1
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Miozu Color System
3
+ *
4
+ * A Base16-inspired palette designed using Newton's color wheel
5
+ * for visual harmony and reduced eye strain.
6
+ *
7
+ * Documentation: https://miozu.com/colors
8
+ * Source: https://github.com/miozu-com
9
+ * Author: Nicholas Glazer <glazer.nicholas@gmail.com>
10
+ *
11
+ * USAGE:
12
+ * @import '@miozu/jera/tokens/colors.css';
13
+ *
14
+ * Or with Tailwind 4:
15
+ * @import '@miozu/jera/tokens/colors.css' theme(static);
16
+ */
17
+
18
+ :root {
19
+ /* ========================================
20
+ * BASE COLORS (Grayscale Spectrum)
21
+ * From darkest (base0) to lightest (base7)
22
+ * ======================================== */
23
+
24
+ --base0: #232733; /* Darkest background */
25
+ --base1: #2C3040; /* Default dark background */
26
+ --base2: #3E4359; /* Selection, highlights */
27
+ --base3: #565E78; /* Comments, subtle text */
28
+ --base4: #737E99; /* Muted foreground */
29
+ --base5: #D0D2DB; /* Default foreground */
30
+ --base6: #F3F4F7; /* Light foreground */
31
+ --base7: #FAFBFD; /* Lightest/white */
32
+
33
+ /* ========================================
34
+ * ACCENT COLORS (Base16 Extended Palette)
35
+ * Official miozu palette from @miozu/js-theme
36
+ * ======================================== */
37
+
38
+ --base8: #EB3137; /* Red - errors, destructive */
39
+ --base9: #FF9837; /* Orange - numbers, attention */
40
+ --base10: #6DD672; /* Green - success, positive */
41
+ --base11: #E8D176; /* Yellow - warnings, highlights */
42
+ --base12: #83D2FC; /* Blue - info, links */
43
+ --base13: #C974E6; /* Magenta - primary brand, functions */
44
+ --base14: #FF9982; /* Peach - keywords, warm accent */
45
+ --base15: #40FFE2; /* Cyan - methods, cool accent */
46
+
47
+ /* Named color aliases */
48
+ --red: var(--base8);
49
+ --orange: var(--base9);
50
+ --green: var(--base10);
51
+ --yellow: var(--base11);
52
+ --blue: var(--base12);
53
+ --magenta: var(--base13);
54
+ --peach: var(--base14);
55
+ --cyan: var(--base15);
56
+
57
+ /* ========================================
58
+ * SEMANTIC MAPPINGS (Dark Theme Default)
59
+ * Use these in components for consistency
60
+ * ======================================== */
61
+
62
+ /* Backgrounds */
63
+ --color-bg: var(--base0);
64
+ --color-surface: var(--base1);
65
+ --color-surface-alt: var(--base2);
66
+ --color-hover: var(--base2);
67
+ --color-surface-hover: var(--base2);
68
+
69
+ /* Foregrounds */
70
+ --color-text: var(--base5);
71
+ --color-text-strong: var(--base7);
72
+ --color-text-muted: var(--base4);
73
+ --color-text-subtle: var(--base3);
74
+
75
+ /* Borders */
76
+ --color-border: var(--base3);
77
+ --color-border-muted: var(--base2);
78
+
79
+ /* Brand */
80
+ --color-primary: var(--magenta); /* base13 - #C974E6 */
81
+ --color-secondary: var(--blue); /* base12 - #83D2FC */
82
+ --color-accent: var(--cyan); /* base15 - #40FFE2 */
83
+
84
+ /* States */
85
+ --color-success: var(--green); /* base10 - #6DD672 */
86
+ --color-warning: var(--yellow); /* base11 - #E8D176 */
87
+ --color-error: var(--red); /* base8 - #EB3137 */
88
+ --color-info: var(--blue); /* base12 - #83D2FC */
89
+
90
+ /* ========================================
91
+ * BASE COLOR ALIASES
92
+ * For direct base color access in components
93
+ * ======================================== */
94
+
95
+ /* Grayscale (base0-7) */
96
+ --color-base0: var(--base0);
97
+ --color-base1: var(--base1);
98
+ --color-base2: var(--base2);
99
+ --color-base3: var(--base3);
100
+ --color-base4: var(--base4);
101
+ --color-base5: var(--base5);
102
+ --color-base6: var(--base6);
103
+ --color-base7: var(--base7);
104
+
105
+ /* Accents (base8-15) */
106
+ --color-base8: var(--base8);
107
+ --color-base9: var(--base9);
108
+ --color-base10: var(--base10);
109
+ --color-base11: var(--base11);
110
+ --color-base12: var(--base12);
111
+ --color-base13: var(--base13);
112
+ --color-base14: var(--base14);
113
+ --color-base15: var(--base15);
114
+ }
115
+
116
+ /* ========================================
117
+ * LIGHT THEME
118
+ * Inverted grayscale, same accents
119
+ *
120
+ * Supports multiple selectors for flexibility:
121
+ * - data-theme="light" (generic)
122
+ * - data-theme="miozu-light" (Selify/Miozu apps)
123
+ * - .light (class-based)
124
+ * ======================================== */
125
+
126
+ [data-theme="light"],
127
+ [data-theme="miozu-light"],
128
+ .light {
129
+ /* Inverted base colors */
130
+ --color-bg: var(--base7);
131
+ --color-surface: var(--base6);
132
+ --color-surface-alt: var(--base5);
133
+ --color-hover: var(--base5);
134
+ --color-surface-hover: var(--base5);
135
+
136
+ /* Inverted foregrounds */
137
+ --color-text: var(--base2);
138
+ --color-text-strong: var(--base0);
139
+ --color-text-muted: var(--base3);
140
+ --color-text-subtle: var(--base4);
141
+
142
+ /* Inverted borders */
143
+ --color-border: var(--base4);
144
+ --color-border-muted: var(--base5);
145
+ }
146
+
147
+ /* ========================================
148
+ * DARK THEME (Explicit)
149
+ * Same as :root default, for explicit dark mode
150
+ *
151
+ * Supports multiple selectors:
152
+ * - data-theme="dark" (generic)
153
+ * - data-theme="miozu-dark" (Selify/Miozu apps)
154
+ * - .dark (class-based)
155
+ * ======================================== */
156
+
157
+ [data-theme="dark"],
158
+ [data-theme="miozu-dark"],
159
+ .dark {
160
+ --color-bg: var(--base0);
161
+ --color-surface: var(--base1);
162
+ --color-surface-alt: var(--base2);
163
+ --color-hover: var(--base2);
164
+ --color-surface-hover: var(--base2);
165
+
166
+ --color-text: var(--base5);
167
+ --color-text-strong: var(--base7);
168
+ --color-text-muted: var(--base4);
169
+ --color-text-subtle: var(--base3);
170
+
171
+ --color-border: var(--base3);
172
+ --color-border-muted: var(--base2);
173
+ }
174
+
175
+ /* ========================================
176
+ * HIGH CONTRAST THEME
177
+ * For accessibility
178
+ * ======================================== */
179
+
180
+ [data-theme="high-contrast"] {
181
+ --color-bg: #000000;
182
+ --color-surface: #0a0a0a;
183
+ --color-surface-alt: #1a1a1a;
184
+ --color-text: var(--base7);
185
+ --color-text-strong: #FFFFFF;
186
+ --color-text-muted: var(--base5);
187
+ --color-border: var(--base5);
188
+ --color-border-muted: var(--base4);
189
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Miozu Effects System
3
+ *
4
+ * Shadows, borders, radius, and transitions.
5
+ * Consistent visual language for depth and motion.
6
+ */
7
+
8
+ :root {
9
+ /* ========================================
10
+ * BORDER WIDTHS
11
+ * ======================================== */
12
+
13
+ --border-width-thin: 1px;
14
+ --border-width-default: 1px;
15
+ --border-width-thick: 2px;
16
+
17
+ /* ========================================
18
+ * FOCUS RING
19
+ * ======================================== */
20
+
21
+ --focus-ring-width: 2px;
22
+ --focus-ring-offset: 2px;
23
+ --focus-ring-color: var(--color-primary);
24
+ --focus-ring: var(--focus-ring-width) solid var(--focus-ring-color);
25
+
26
+ /* ========================================
27
+ * BORDER RADIUS
28
+ * ======================================== */
29
+
30
+ --radius-none: 0;
31
+ --radius-sm: 0.125rem; /* 2px */
32
+ --radius-default: 0.25rem; /* 4px */
33
+ --radius-md: 0.375rem; /* 6px */
34
+ --radius-lg: 0.5rem; /* 8px */
35
+ --radius-xl: 0.75rem; /* 12px */
36
+ --radius-2xl: 1rem; /* 16px */
37
+ --radius-3xl: 1.5rem; /* 24px */
38
+ --radius-full: 9999px; /* Pill shape */
39
+
40
+ /* ========================================
41
+ * BOX SHADOWS (Dark theme optimized)
42
+ * ======================================== */
43
+
44
+ --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
45
+
46
+ --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1),
47
+ 0 1px 2px -1px rgb(0 0 0 / 0.1);
48
+
49
+ --shadow-default: 0 4px 6px -1px rgb(0 0 0 / 0.1),
50
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
51
+
52
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
53
+ 0 2px 4px -2px rgb(0 0 0 / 0.1);
54
+
55
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
56
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
57
+
58
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
59
+ 0 8px 10px -6px rgb(0 0 0 / 0.1);
60
+
61
+ --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
62
+
63
+ --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
64
+
65
+ --shadow-none: 0 0 #0000;
66
+
67
+ /* Colored shadows for states */
68
+ --shadow-primary: 0 4px 14px 0 rgb(201 116 230 / 0.25);
69
+ --shadow-error: 0 4px 14px 0 rgb(235 49 55 / 0.25);
70
+ --shadow-success: 0 4px 14px 0 rgb(109 214 114 / 0.25);
71
+
72
+ /* ========================================
73
+ * TRANSITIONS
74
+ * ======================================== */
75
+
76
+ /* Durations */
77
+ --duration-75: 75ms;
78
+ --duration-100: 100ms;
79
+ --duration-150: 150ms;
80
+ --duration-200: 200ms;
81
+ --duration-300: 300ms;
82
+ --duration-500: 500ms;
83
+ --duration-700: 700ms;
84
+ --duration-1000: 1000ms;
85
+
86
+ /* Semantic durations */
87
+ --duration-fast: var(--duration-150);
88
+ --duration-base: var(--duration-200);
89
+ --duration-slow: var(--duration-300);
90
+
91
+ /* Easings */
92
+ --ease-linear: linear;
93
+ --ease-in: cubic-bezier(0.4, 0, 1, 1);
94
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
95
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
96
+ --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
97
+ --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
98
+
99
+ /* Semantic easings */
100
+ --ease-default: var(--ease-out);
101
+
102
+ /* Common transitions */
103
+ --transition-colors: color var(--duration-fast) var(--ease-default),
104
+ background-color var(--duration-fast) var(--ease-default),
105
+ border-color var(--duration-fast) var(--ease-default);
106
+
107
+ --transition-opacity: opacity var(--duration-fast) var(--ease-default);
108
+
109
+ --transition-transform: transform var(--duration-base) var(--ease-default);
110
+
111
+ --transition-all: all var(--duration-base) var(--ease-default);
112
+
113
+ /* ========================================
114
+ * Z-INDEX SCALE
115
+ * ======================================== */
116
+
117
+ --z-behind: -1;
118
+ --z-default: 0;
119
+ --z-dropdown: 10;
120
+ --z-sticky: 20;
121
+ --z-fixed: 30;
122
+ --z-modal-backdrop: 40;
123
+ --z-modal: 50;
124
+ --z-popover: 60;
125
+ --z-tooltip: 70;
126
+ --z-toast: 80;
127
+ --z-max: 9999;
128
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Miozu Design Tokens
3
+ *
4
+ * A complete design token system for building consistent interfaces.
5
+ * Works standalone or with Tailwind CSS.
6
+ *
7
+ * @package @miozu/jera
8
+ * @author Nicholas Glazer <glazer.nicholas@gmail.com>
9
+ * @license MIT
10
+ *
11
+ * USAGE:
12
+ *
13
+ * 1. Import all tokens:
14
+ * @import '@miozu/jera/tokens';
15
+ *
16
+ * 2. Import individual token sets:
17
+ * @import '@miozu/jera/tokens/colors.css';
18
+ * @import '@miozu/jera/tokens/spacing.css';
19
+ *
20
+ * 3. With Tailwind 4:
21
+ * @import '@miozu/jera/tokens' theme(static);
22
+ *
23
+ * THEMING:
24
+ *
25
+ * Dark (default):
26
+ * <html data-theme="dark">
27
+ *
28
+ * Light:
29
+ * <html data-theme="light">
30
+ *
31
+ * System preference (JS):
32
+ * const theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
33
+ * document.documentElement.setAttribute('data-theme', theme);
34
+ */
35
+
36
+ @import './colors.css';
37
+ @import './spacing.css';
38
+ @import './typography.css';
39
+ @import './effects.css';
40
+
41
+ /* ========================================
42
+ * BASE STYLES (Optional)
43
+ * Reset and sensible defaults
44
+ * ======================================== */
45
+
46
+ *,
47
+ *::before,
48
+ *::after {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ html {
53
+ font-family: var(--font-sans);
54
+ font-size: 16px;
55
+ line-height: var(--leading-normal);
56
+ -webkit-font-smoothing: antialiased;
57
+ -moz-osx-font-smoothing: grayscale;
58
+ }
59
+
60
+ body {
61
+ margin: 0;
62
+ background-color: var(--color-bg);
63
+ color: var(--color-text);
64
+ }
65
+
66
+ /* Focus visible for accessibility */
67
+ :focus-visible {
68
+ outline: 2px solid var(--color-primary);
69
+ outline-offset: 2px;
70
+ }
71
+
72
+ /* Remove default focus for mouse users */
73
+ :focus:not(:focus-visible) {
74
+ outline: none;
75
+ }
76
+
77
+ /* Selection color */
78
+ ::selection {
79
+ background-color: var(--color-primary);
80
+ color: var(--base7);
81
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Miozu Spacing System
3
+ *
4
+ * Based on 4px base unit with geometric progression.
5
+ * Consistent spacing creates visual rhythm.
6
+ *
7
+ * USAGE:
8
+ * padding: var(--space-4); // 16px
9
+ * gap: var(--space-3); // 12px
10
+ */
11
+
12
+ :root {
13
+ /* Base unit: 4px */
14
+ --space-px: 1px;
15
+ --space-0: 0;
16
+ --space-0-5: 2px; /* 0.5 * 4 */
17
+ --space-1: 4px; /* 1 * 4 */
18
+ --space-1-5: 6px; /* 1.5 * 4 */
19
+ --space-2: 8px; /* 2 * 4 */
20
+ --space-2-5: 10px; /* 2.5 * 4 */
21
+ --space-3: 12px; /* 3 * 4 */
22
+ --space-3-5: 14px; /* 3.5 * 4 */
23
+ --space-4: 16px; /* 4 * 4 */
24
+ --space-5: 20px; /* 5 * 4 */
25
+ --space-6: 24px; /* 6 * 4 */
26
+ --space-7: 28px; /* 7 * 4 */
27
+ --space-8: 32px; /* 8 * 4 */
28
+ --space-9: 36px; /* 9 * 4 */
29
+ --space-10: 40px; /* 10 * 4 */
30
+ --space-11: 44px; /* 11 * 4 */
31
+ --space-12: 48px; /* 12 * 4 */
32
+ --space-14: 56px; /* 14 * 4 */
33
+ --space-16: 64px; /* 16 * 4 */
34
+ --space-20: 80px; /* 20 * 4 */
35
+ --space-24: 96px; /* 24 * 4 */
36
+ --space-28: 112px; /* 28 * 4 */
37
+ --space-32: 128px; /* 32 * 4 */
38
+ --space-36: 144px; /* 36 * 4 */
39
+ --space-40: 160px; /* 40 * 4 */
40
+ --space-44: 176px; /* 44 * 4 */
41
+ --space-48: 192px; /* 48 * 4 */
42
+ --space-52: 208px; /* 52 * 4 */
43
+ --space-56: 224px; /* 56 * 4 */
44
+ --space-60: 240px; /* 60 * 4 */
45
+ --space-64: 256px; /* 64 * 4 */
46
+ --space-72: 288px; /* 72 * 4 */
47
+ --space-80: 320px; /* 80 * 4 */
48
+ --space-96: 384px; /* 96 * 4 */
49
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Miozu Typography System
3
+ *
4
+ * Font scales using minor third ratio (1.2).
5
+ * Optimized for readability on screens.
6
+ *
7
+ * USAGE:
8
+ * font-size: var(--text-base);
9
+ * font-family: var(--font-sans);
10
+ */
11
+
12
+ :root {
13
+ /* ========================================
14
+ * FONT FAMILIES
15
+ * ======================================== */
16
+
17
+ --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
18
+ "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
19
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
20
+
21
+ --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
22
+
23
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
24
+ "Liberation Mono", "Courier New", monospace;
25
+
26
+ /* ========================================
27
+ * FONT SIZES (Minor Third Scale - 1.2)
28
+ * ======================================== */
29
+
30
+ --text-xs: 0.75rem; /* 12px */
31
+ --text-sm: 0.875rem; /* 14px */
32
+ --text-base: 1rem; /* 16px */
33
+ --text-lg: 1.125rem; /* 18px */
34
+ --text-xl: 1.25rem; /* 20px */
35
+ --text-2xl: 1.5rem; /* 24px */
36
+ --text-3xl: 1.875rem; /* 30px */
37
+ --text-4xl: 2.25rem; /* 36px */
38
+ --text-5xl: 3rem; /* 48px */
39
+ --text-6xl: 3.75rem; /* 60px */
40
+ --text-7xl: 4.5rem; /* 72px */
41
+ --text-8xl: 6rem; /* 96px */
42
+ --text-9xl: 8rem; /* 128px */
43
+
44
+ /* ========================================
45
+ * LINE HEIGHTS
46
+ * ======================================== */
47
+
48
+ --leading-none: 1;
49
+ --leading-tight: 1.25;
50
+ --leading-snug: 1.375;
51
+ --leading-normal: 1.5;
52
+ --leading-relaxed: 1.625;
53
+ --leading-loose: 2;
54
+
55
+ /* ========================================
56
+ * FONT WEIGHTS
57
+ * ======================================== */
58
+
59
+ --font-thin: 100;
60
+ --font-extralight: 200;
61
+ --font-light: 300;
62
+ --font-normal: 400;
63
+ --font-medium: 500;
64
+ --font-semibold: 600;
65
+ --font-bold: 700;
66
+ --font-extrabold: 800;
67
+ --font-black: 900;
68
+
69
+ /* ========================================
70
+ * LETTER SPACING
71
+ * ======================================== */
72
+
73
+ --tracking-tighter: -0.05em;
74
+ --tracking-tight: -0.025em;
75
+ --tracking-normal: 0em;
76
+ --tracking-wide: 0.025em;
77
+ --tracking-wider: 0.05em;
78
+ --tracking-widest: 0.1em;
79
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Reactive Class Composer (cn)
3
+ *
4
+ * Advanced Svelte 5 pattern for composing classes reactively.
5
+ * Uses $derived internally for fine-grained reactivity.
6
+ *
7
+ * @example
8
+ * // Basic usage
9
+ * const classes = cn('base', condition && 'conditional', ['array', 'of', 'classes']);
10
+ *
11
+ * // With variants
12
+ * const button = cv({
13
+ * base: 'inline-flex items-center justify-center',
14
+ * variants: {
15
+ * intent: {
16
+ * primary: 'bg-primary text-white',
17
+ * secondary: 'bg-surface text-text',
18
+ * },
19
+ * size: {
20
+ * sm: 'h-8 px-3 text-sm',
21
+ * md: 'h-10 px-4 text-base',
22
+ * lg: 'h-12 px-6 text-lg',
23
+ * }
24
+ * },
25
+ * defaults: { intent: 'primary', size: 'md' }
26
+ * });
27
+ *
28
+ * // Use in component
29
+ * <button class={button({ intent: 'secondary', size: 'lg' })}>Click</button>
30
+ */
31
+
32
+ /**
33
+ * Concatenate class names, filtering out falsy values
34
+ * @param {...(string|boolean|null|undefined|string[])} args
35
+ * @returns {string}
36
+ */
37
+ export function cn(...args) {
38
+ return args
39
+ .flat(Infinity)
40
+ .filter(Boolean)
41
+ .join(' ')
42
+ .replace(/\s+/g, ' ')
43
+ .trim();
44
+ }
45
+
46
+ /**
47
+ * Create a reactive class string using $derived
48
+ * Returns a getter that recomputes when dependencies change
49
+ *
50
+ * @param {() => (string|boolean|null|undefined|string[])[]} classFactory
51
+ * @returns {string} Reactive class string (use as class={rcn(() => [...])})
52
+ *
53
+ * @example
54
+ * let isActive = $state(false);
55
+ * let size = $state('md');
56
+ *
57
+ * // This recomputes automatically when isActive or size changes
58
+ * const buttonClass = $derived(cn(
59
+ * 'base-button',
60
+ * isActive && 'is-active',
61
+ * `size-${size}`
62
+ * ));
63
+ */
64
+ // Note: rcn is just cn - the reactivity comes from using it inside $derived
65
+ export const rcn = cn;
66
+
67
+ /**
68
+ * Class Variants (cv) - Type-safe variant composition
69
+ *
70
+ * Creates a function that generates class strings based on variant props.
71
+ * Inspired by CVA but optimized for Svelte 5 patterns.
72
+ *
73
+ * @template {Record<string, Record<string, string>>} V
74
+ * @template {Partial<{[K in keyof V]: keyof V[K]}>} D
75
+ *
76
+ * @param {{
77
+ * base?: string | string[],
78
+ * variants?: V,
79
+ * compounds?: Array<{condition: Partial<{[K in keyof V]: keyof V[K]}>, class: string}>,
80
+ * defaults?: D
81
+ * }} config
82
+ *
83
+ * @returns {(props?: Partial<{[K in keyof V]: keyof V[K]}> & {class?: string}) => string}
84
+ */
85
+ export function cv(config) {
86
+ const { base = '', variants = {}, compounds = [], defaults = {} } = config;
87
+
88
+ /**
89
+ * @param {Record<string, any>} [props]
90
+ * @returns {string}
91
+ */
92
+ return function (props = {}) {
93
+ // Start with base classes
94
+ /** @type {(string | string[])[]} */
95
+ const classes = [base];
96
+
97
+ // Apply variant classes
98
+ for (const [variantKey, variantOptions] of Object.entries(variants)) {
99
+ const selectedVariant = /** @type {string} */ (props[variantKey] ?? defaults[variantKey]);
100
+ const variantMap = /** @type {Record<string, string>} */ (variantOptions);
101
+ if (selectedVariant && variantMap[selectedVariant]) {
102
+ classes.push(variantMap[selectedVariant]);
103
+ }
104
+ }
105
+
106
+ // Apply compound variants (when multiple conditions match)
107
+ for (const compound of compounds) {
108
+ const { condition, class: compoundClass } = compound;
109
+ const conditionEntries = /** @type {[string, string][]} */ (Object.entries(condition));
110
+ const matches = conditionEntries.every(([key, value]) => {
111
+ const actualValue = /** @type {string} */ (props[key] ?? defaults[key]);
112
+ return actualValue === value;
113
+ });
114
+ if (matches) {
115
+ classes.push(compoundClass);
116
+ }
117
+ }
118
+
119
+ // Append any additional classes passed via props.class
120
+ if (props.class) {
121
+ classes.push(props.class);
122
+ }
123
+
124
+ return cn(...classes);
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Create a reactive variant class using $derived
130
+ *
131
+ * @example
132
+ * const buttonVariants = cv({...});
133
+ *
134
+ * let intent = $state('primary');
135
+ * let size = $state('md');
136
+ *
137
+ * // Reactive: recomputes when intent or size changes
138
+ * const buttonClass = $derived(buttonVariants({ intent, size }));
139
+ */
140
+
141
+ /**
142
+ * Slot class merger - combines component classes with user-provided classes
143
+ * Useful for compound components where parent passes classes to children
144
+ *
145
+ * @param {string} componentClass - The component's own classes
146
+ * @param {string} [userClass] - Classes provided by the user
147
+ * @returns {string}
148
+ */
149
+ export function mergeClasses(componentClass, userClass) {
150
+ return cn(componentClass, userClass);
151
+ }
152
+
153
+ /**
154
+ * Conditional class helper - returns class only if condition is truthy
155
+ *
156
+ * @param {boolean} condition
157
+ * @param {string} trueClass
158
+ * @param {string} [falseClass]
159
+ * @returns {string}
160
+ */
161
+ export function when(condition, trueClass, falseClass = '') {
162
+ return condition ? trueClass : falseClass;
163
+ }
164
+
165
+ /**
166
+ * Switch class helper - returns class based on value matching
167
+ *
168
+ * @param {string | number} value
169
+ * @param {Record<string, string>} cases
170
+ * @param {string} [fallback]
171
+ * @returns {string}
172
+ */
173
+ export function match(value, cases, fallback = '') {
174
+ return cases[String(value)] ?? fallback;
175
+ }