@spavn/ui 0.0.1 → 0.0.2

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.
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Shared types and utilities for the Spavn UI design system
3
+ */
4
+
5
+ /** Size scale used across components */
6
+ export type SpavnSize = 'xs' | 'sm' | 'default' | 'lg' | 'xl'
7
+
8
+ /** Extended color palette */
9
+ export type SpavnColor =
10
+ | 'default'
11
+ | 'primary'
12
+ | 'secondary'
13
+ | 'destructive'
14
+ | 'success'
15
+ | 'info'
16
+ | 'warning'
17
+ | 'amber'
18
+ | 'blue'
19
+ | 'green'
20
+ | 'purple'
21
+ | 'pink'
22
+ | 'orange'
23
+
24
+ /** Border radius scale */
25
+ export type SpavnRadius = 'none' | 'sm' | 'md' | 'lg' | 'full'
26
+
27
+ /** Radius class map */
28
+ export const radiusMap: Record<SpavnRadius, string> = {
29
+ none: 'rounded-none',
30
+ sm: 'rounded-sm',
31
+ md: 'rounded-md',
32
+ lg: 'rounded-lg',
33
+ full: 'rounded-full',
34
+ }
35
+
36
+ /**
37
+ * Returns Tailwind classes for a given color and style combination.
38
+ * Used by components to apply color theming outside of CVA.
39
+ * Returns empty string for 'default' color (component uses its own defaults).
40
+ */
41
+ export function colorClasses(
42
+ color: SpavnColor,
43
+ style: 'solid' | 'soft' | 'surface' | 'outline'
44
+ ): string {
45
+ if (color === 'default') return ''
46
+
47
+ const colorMap: Record<Exclude<SpavnColor, 'default'>, Record<typeof style, string>> = {
48
+ primary: {
49
+ solid: 'bg-primary text-primary-foreground',
50
+ soft: 'bg-primary-soft text-primary border-primary/20',
51
+ surface: 'bg-primary-soft text-primary border border-primary/30',
52
+ outline: 'text-primary border border-primary/50',
53
+ },
54
+ secondary: {
55
+ solid: 'bg-secondary text-secondary-foreground',
56
+ soft: 'bg-secondary-soft text-secondary-foreground border-secondary/20',
57
+ surface: 'bg-secondary-soft text-secondary-foreground border border-secondary/30',
58
+ outline: 'text-secondary-foreground border border-secondary/50',
59
+ },
60
+ destructive: {
61
+ solid: 'bg-destructive text-destructive-foreground',
62
+ soft: 'bg-destructive-soft text-destructive border-destructive/20',
63
+ surface: 'bg-destructive-soft text-destructive border border-destructive/30',
64
+ outline: 'text-destructive border border-destructive/50',
65
+ },
66
+ success: {
67
+ solid: 'bg-success text-success-foreground',
68
+ soft: 'bg-success-soft text-success border-success/20',
69
+ surface: 'bg-success-soft text-success border border-success/30',
70
+ outline: 'text-success border border-success/50',
71
+ },
72
+ info: {
73
+ solid: 'bg-info text-info-foreground',
74
+ soft: 'bg-info-soft text-info border-info/20',
75
+ surface: 'bg-info-soft text-info border border-info/30',
76
+ outline: 'text-info border border-info/50',
77
+ },
78
+ warning: {
79
+ solid: 'bg-warning text-warning-foreground',
80
+ soft: 'bg-warning-soft text-warning border-warning/20',
81
+ surface: 'bg-warning-soft text-warning border border-warning/30',
82
+ outline: 'text-warning border border-warning/50',
83
+ },
84
+ amber: {
85
+ solid: 'bg-amber text-amber-foreground',
86
+ soft: 'bg-amber-soft text-amber border-amber/20',
87
+ surface: 'bg-amber-soft text-amber border border-amber/30',
88
+ outline: 'text-amber border border-amber/50',
89
+ },
90
+ blue: {
91
+ solid: 'bg-blue text-blue-foreground',
92
+ soft: 'bg-blue-soft text-blue border-blue/20',
93
+ surface: 'bg-blue-soft text-blue border border-blue/30',
94
+ outline: 'text-blue border border-blue/50',
95
+ },
96
+ green: {
97
+ solid: 'bg-green text-green-foreground',
98
+ soft: 'bg-green-soft text-green border-green/20',
99
+ surface: 'bg-green-soft text-green border border-green/30',
100
+ outline: 'text-green border border-green/50',
101
+ },
102
+ purple: {
103
+ solid: 'bg-purple text-purple-foreground',
104
+ soft: 'bg-purple-soft text-purple border-purple/20',
105
+ surface: 'bg-purple-soft text-purple border border-purple/30',
106
+ outline: 'text-purple border border-purple/50',
107
+ },
108
+ pink: {
109
+ solid: 'bg-pink text-pink-foreground',
110
+ soft: 'bg-pink-soft text-pink border-pink/20',
111
+ surface: 'bg-pink-soft text-pink border border-pink/30',
112
+ outline: 'text-pink border border-pink/50',
113
+ },
114
+ orange: {
115
+ solid: 'bg-orange text-orange-foreground',
116
+ soft: 'bg-orange-soft text-orange border-orange/20',
117
+ surface: 'bg-orange-soft text-orange border border-orange/30',
118
+ outline: 'text-orange border border-orange/50',
119
+ },
120
+ }
121
+
122
+ return colorMap[color][style]
123
+ }
124
+
125
+ type ColorKey = Exclude<SpavnColor, 'default'>
126
+
127
+ /**
128
+ * Static color class maps for Tailwind scanner detection.
129
+ * NEVER use template literals for Tailwind classes — the scanner cannot detect them.
130
+ */
131
+
132
+ /** Background color classes */
133
+ export const colorBg: Record<ColorKey, string> = {
134
+ primary: 'bg-primary',
135
+ secondary: 'bg-secondary',
136
+ destructive: 'bg-destructive',
137
+ success: 'bg-success',
138
+ info: 'bg-info',
139
+ warning: 'bg-warning',
140
+ amber: 'bg-amber',
141
+ blue: 'bg-blue',
142
+ green: 'bg-green',
143
+ purple: 'bg-purple',
144
+ pink: 'bg-pink',
145
+ orange: 'bg-orange',
146
+ }
147
+
148
+ /** Text color classes */
149
+ export const colorText: Record<ColorKey, string> = {
150
+ primary: 'text-primary',
151
+ secondary: 'text-secondary-foreground',
152
+ destructive: 'text-destructive',
153
+ success: 'text-success',
154
+ info: 'text-info',
155
+ warning: 'text-warning',
156
+ amber: 'text-amber',
157
+ blue: 'text-blue',
158
+ green: 'text-green',
159
+ purple: 'text-purple',
160
+ pink: 'text-pink',
161
+ orange: 'text-orange',
162
+ }
163
+
164
+ /** Border color classes */
165
+ export const colorBorder: Record<ColorKey, string> = {
166
+ primary: 'border-primary',
167
+ secondary: 'border-secondary',
168
+ destructive: 'border-destructive',
169
+ success: 'border-success',
170
+ info: 'border-info',
171
+ warning: 'border-warning',
172
+ amber: 'border-amber',
173
+ blue: 'border-blue',
174
+ green: 'border-green',
175
+ purple: 'border-purple',
176
+ pink: 'border-pink',
177
+ orange: 'border-orange',
178
+ }
179
+
180
+ /** Foreground text classes (for use on colored backgrounds) */
181
+ export const colorTextForeground: Record<ColorKey, string> = {
182
+ primary: 'text-primary-foreground',
183
+ secondary: 'text-secondary-foreground',
184
+ destructive: 'text-destructive-foreground',
185
+ success: 'text-success-foreground',
186
+ info: 'text-info-foreground',
187
+ warning: 'text-warning-foreground',
188
+ amber: 'text-amber-foreground',
189
+ blue: 'text-blue-foreground',
190
+ green: 'text-green-foreground',
191
+ purple: 'text-purple-foreground',
192
+ pink: 'text-pink-foreground',
193
+ orange: 'text-orange-foreground',
194
+ }
195
+
196
+ /** Soft background classes */
197
+ export const colorBgSoft: Record<ColorKey, string> = {
198
+ primary: 'bg-primary-soft',
199
+ secondary: 'bg-secondary-soft',
200
+ destructive: 'bg-destructive-soft',
201
+ success: 'bg-success-soft',
202
+ info: 'bg-info-soft',
203
+ warning: 'bg-warning-soft',
204
+ amber: 'bg-amber-soft',
205
+ blue: 'bg-blue-soft',
206
+ green: 'bg-green-soft',
207
+ purple: 'bg-purple-soft',
208
+ pink: 'bg-pink-soft',
209
+ orange: 'bg-orange-soft',
210
+ }
211
+
212
+ /** Hover classes for solid button variants */
213
+ export const colorHoverSolid: Record<ColorKey, string> = {
214
+ primary: 'hover:bg-primary/90 active:bg-primary/95',
215
+ secondary: 'hover:bg-secondary/80 active:bg-secondary/70',
216
+ destructive: 'hover:bg-destructive/90 active:bg-destructive/95',
217
+ success: 'hover:bg-success/90 active:bg-success/95',
218
+ info: 'hover:bg-info/90 active:bg-info/95',
219
+ warning: 'hover:bg-warning/90 active:bg-warning/95',
220
+ amber: 'hover:bg-amber/90 active:bg-amber/95',
221
+ blue: 'hover:bg-blue/90 active:bg-blue/95',
222
+ green: 'hover:bg-green/90 active:bg-green/95',
223
+ purple: 'hover:bg-purple/90 active:bg-purple/95',
224
+ pink: 'hover:bg-pink/90 active:bg-pink/95',
225
+ orange: 'hover:bg-orange/90 active:bg-orange/95',
226
+ }
227
+
228
+ /** Hover classes for outline button variant */
229
+ export const colorHoverOutline: Record<ColorKey, string> = {
230
+ primary: 'hover:bg-primary/10 hover:text-primary hover:border-primary/50 active:bg-primary/20',
231
+ secondary: 'hover:bg-secondary/10 hover:text-secondary-foreground hover:border-secondary/50 active:bg-secondary/20',
232
+ destructive: 'hover:bg-destructive/10 hover:text-destructive hover:border-destructive/50 active:bg-destructive/20',
233
+ success: 'hover:bg-success/10 hover:text-success hover:border-success/50 active:bg-success/20',
234
+ info: 'hover:bg-info/10 hover:text-info hover:border-info/50 active:bg-info/20',
235
+ warning: 'hover:bg-warning/10 hover:text-warning hover:border-warning/50 active:bg-warning/20',
236
+ amber: 'hover:bg-amber/10 hover:text-amber hover:border-amber/50 active:bg-amber/20',
237
+ blue: 'hover:bg-blue/10 hover:text-blue hover:border-blue/50 active:bg-blue/20',
238
+ green: 'hover:bg-green/10 hover:text-green hover:border-green/50 active:bg-green/20',
239
+ purple: 'hover:bg-purple/10 hover:text-purple hover:border-purple/50 active:bg-purple/20',
240
+ pink: 'hover:bg-pink/10 hover:text-pink hover:border-pink/50 active:bg-pink/20',
241
+ orange: 'hover:bg-orange/10 hover:text-orange hover:border-orange/50 active:bg-orange/20',
242
+ }
243
+
244
+ /** Hover classes for ghost/plain button variants */
245
+ export const colorHoverGhost: Record<ColorKey, string> = {
246
+ primary: 'hover:bg-primary-soft hover:text-primary active:bg-primary-soft/80',
247
+ secondary: 'hover:bg-secondary-soft hover:text-secondary-foreground active:bg-secondary-soft/80',
248
+ destructive: 'hover:bg-destructive-soft hover:text-destructive active:bg-destructive-soft/80',
249
+ success: 'hover:bg-success-soft hover:text-success active:bg-success-soft/80',
250
+ info: 'hover:bg-info-soft hover:text-info active:bg-info-soft/80',
251
+ warning: 'hover:bg-warning-soft hover:text-warning active:bg-warning-soft/80',
252
+ amber: 'hover:bg-amber-soft hover:text-amber active:bg-amber-soft/80',
253
+ blue: 'hover:bg-blue-soft hover:text-blue active:bg-blue-soft/80',
254
+ green: 'hover:bg-green-soft hover:text-green active:bg-green-soft/80',
255
+ purple: 'hover:bg-purple-soft hover:text-purple active:bg-purple-soft/80',
256
+ pink: 'hover:bg-pink-soft hover:text-pink active:bg-pink-soft/80',
257
+ orange: 'hover:bg-orange-soft hover:text-orange active:bg-orange-soft/80',
258
+ }
259
+
260
+ /** Hover classes for link variant */
261
+ export const colorHoverLink: Record<ColorKey, string> = {
262
+ primary: 'hover:text-primary/80',
263
+ secondary: 'hover:text-secondary-foreground/80',
264
+ destructive: 'hover:text-destructive/80',
265
+ success: 'hover:text-success/80',
266
+ info: 'hover:text-info/80',
267
+ warning: 'hover:text-warning/80',
268
+ amber: 'hover:text-amber/80',
269
+ blue: 'hover:text-blue/80',
270
+ green: 'hover:text-green/80',
271
+ purple: 'hover:text-purple/80',
272
+ pink: 'hover:text-pink/80',
273
+ orange: 'hover:text-orange/80',
274
+ }
275
+
276
+ /** Checked state for Switch */
277
+ export const colorCheckedBg: Record<ColorKey, string> = {
278
+ primary: 'data-[state=checked]:bg-primary',
279
+ secondary: 'data-[state=checked]:bg-secondary',
280
+ destructive: 'data-[state=checked]:bg-destructive',
281
+ success: 'data-[state=checked]:bg-success',
282
+ info: 'data-[state=checked]:bg-info',
283
+ warning: 'data-[state=checked]:bg-warning',
284
+ amber: 'data-[state=checked]:bg-amber',
285
+ blue: 'data-[state=checked]:bg-blue',
286
+ green: 'data-[state=checked]:bg-green',
287
+ purple: 'data-[state=checked]:bg-purple',
288
+ pink: 'data-[state=checked]:bg-pink',
289
+ orange: 'data-[state=checked]:bg-orange',
290
+ }
291
+
292
+ /** Checked state for Checkbox (border + bg + text) */
293
+ export const colorCheckedFull: Record<ColorKey, string> = {
294
+ primary: 'border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
295
+ secondary: 'border-secondary data-[state=checked]:bg-secondary data-[state=checked]:text-secondary-foreground',
296
+ destructive: 'border-destructive data-[state=checked]:bg-destructive data-[state=checked]:text-destructive-foreground',
297
+ success: 'border-success data-[state=checked]:bg-success data-[state=checked]:text-success-foreground',
298
+ info: 'border-info data-[state=checked]:bg-info data-[state=checked]:text-info-foreground',
299
+ warning: 'border-warning data-[state=checked]:bg-warning data-[state=checked]:text-warning-foreground',
300
+ amber: 'border-amber data-[state=checked]:bg-amber data-[state=checked]:text-amber-foreground',
301
+ blue: 'border-blue data-[state=checked]:bg-blue data-[state=checked]:text-blue-foreground',
302
+ green: 'border-green data-[state=checked]:bg-green data-[state=checked]:text-green-foreground',
303
+ purple: 'border-purple data-[state=checked]:bg-purple data-[state=checked]:text-purple-foreground',
304
+ pink: 'border-pink data-[state=checked]:bg-pink data-[state=checked]:text-pink-foreground',
305
+ orange: 'border-orange data-[state=checked]:bg-orange data-[state=checked]:text-orange-foreground',
306
+ }
307
+
308
+ /** Radio border + text */
309
+ export const colorBorderText: Record<ColorKey, string> = {
310
+ primary: 'border-primary text-primary',
311
+ secondary: 'border-secondary text-secondary',
312
+ destructive: 'border-destructive text-destructive',
313
+ success: 'border-success text-success',
314
+ info: 'border-info text-info',
315
+ warning: 'border-warning text-warning',
316
+ amber: 'border-amber text-amber',
317
+ blue: 'border-blue text-blue',
318
+ green: 'border-green text-green',
319
+ purple: 'border-purple text-purple',
320
+ pink: 'border-pink text-pink',
321
+ orange: 'border-orange text-orange',
322
+ }
323
+
324
+ /** Toggle on state */
325
+ export const colorOnState: Record<ColorKey, string> = {
326
+ primary: 'data-[state=on]:bg-primary data-[state=on]:text-primary-foreground',
327
+ secondary: 'data-[state=on]:bg-secondary data-[state=on]:text-secondary-foreground',
328
+ destructive: 'data-[state=on]:bg-destructive data-[state=on]:text-destructive-foreground',
329
+ success: 'data-[state=on]:bg-success data-[state=on]:text-success-foreground',
330
+ info: 'data-[state=on]:bg-info data-[state=on]:text-info-foreground',
331
+ warning: 'data-[state=on]:bg-warning data-[state=on]:text-warning-foreground',
332
+ amber: 'data-[state=on]:bg-amber data-[state=on]:text-amber-foreground',
333
+ blue: 'data-[state=on]:bg-blue data-[state=on]:text-blue-foreground',
334
+ green: 'data-[state=on]:bg-green data-[state=on]:text-green-foreground',
335
+ purple: 'data-[state=on]:bg-purple data-[state=on]:text-purple-foreground',
336
+ pink: 'data-[state=on]:bg-pink data-[state=on]:text-pink-foreground',
337
+ orange: 'data-[state=on]:bg-orange data-[state=on]:text-orange-foreground',
338
+ }
339
+
340
+ /** Tabs active state for underline variant */
341
+ export const colorActiveUnderline: Record<ColorKey, string> = {
342
+ primary: 'data-[state=active]:border-primary',
343
+ secondary: 'data-[state=active]:border-secondary',
344
+ destructive: 'data-[state=active]:border-destructive',
345
+ success: 'data-[state=active]:border-success',
346
+ info: 'data-[state=active]:border-info',
347
+ warning: 'data-[state=active]:border-warning',
348
+ amber: 'data-[state=active]:border-amber',
349
+ blue: 'data-[state=active]:border-blue',
350
+ green: 'data-[state=active]:border-green',
351
+ purple: 'data-[state=active]:border-purple',
352
+ pink: 'data-[state=active]:border-pink',
353
+ orange: 'data-[state=active]:border-orange',
354
+ }
355
+
356
+ /** Tabs active state for pills/enclosed variant */
357
+ export const colorActivePill: Record<ColorKey, string> = {
358
+ primary: 'data-[state=active]:bg-primary data-[state=active]:text-primary-foreground',
359
+ secondary: 'data-[state=active]:bg-secondary data-[state=active]:text-secondary-foreground',
360
+ destructive: 'data-[state=active]:bg-destructive data-[state=active]:text-destructive-foreground',
361
+ success: 'data-[state=active]:bg-success data-[state=active]:text-success-foreground',
362
+ info: 'data-[state=active]:bg-info data-[state=active]:text-info-foreground',
363
+ warning: 'data-[state=active]:bg-warning data-[state=active]:text-warning-foreground',
364
+ amber: 'data-[state=active]:bg-amber data-[state=active]:text-amber-foreground',
365
+ blue: 'data-[state=active]:bg-blue data-[state=active]:text-blue-foreground',
366
+ green: 'data-[state=active]:bg-green data-[state=active]:text-green-foreground',
367
+ purple: 'data-[state=active]:bg-purple data-[state=active]:text-purple-foreground',
368
+ pink: 'data-[state=active]:bg-pink data-[state=active]:text-pink-foreground',
369
+ orange: 'data-[state=active]:bg-orange data-[state=active]:text-orange-foreground',
370
+ }
package/src/lib/utils.ts CHANGED
@@ -1,10 +1,31 @@
1
1
  import { type ClassValue, clsx } from 'clsx'
2
- import { twMerge } from 'tailwind-merge'
2
+ import { extendTailwindMerge } from 'tailwind-merge'
3
+
4
+ const twMerge = extendTailwindMerge({
5
+ extend: {
6
+ theme: {
7
+ colors: [
8
+ // Extended palette colors
9
+ 'info', 'info-foreground', 'info-soft',
10
+ 'warning', 'warning-foreground', 'warning-soft',
11
+ 'amber', 'amber-foreground', 'amber-soft',
12
+ 'blue', 'blue-foreground', 'blue-soft',
13
+ 'green', 'green-foreground', 'green-soft',
14
+ 'purple', 'purple-foreground', 'purple-soft',
15
+ 'pink', 'pink-foreground', 'pink-soft',
16
+ 'orange', 'orange-foreground', 'orange-soft',
17
+ // Soft variants for semantic colors
18
+ 'primary-soft', 'secondary-soft', 'destructive-soft', 'success-soft',
19
+ ],
20
+ },
21
+ },
22
+ })
3
23
 
4
24
  /**
5
25
  * Utility function to merge Tailwind CSS classes
6
26
  * Combines clsx for conditional classes with tailwind-merge for conflict resolution
7
- *
27
+ * Extended to recognize Spavn UI custom color tokens from @theme
28
+ *
8
29
  * @example
9
30
  * cn('px-2 py-1', 'bg-red-500', isActive && 'bg-blue-500')
10
31
  * // → 'px-2 py-1 bg-blue-500' (when isActive is true)
package/src/theme.css CHANGED
@@ -34,6 +34,45 @@
34
34
  --color-card-elevated-1: hsl(var(--card-elevated-1));
35
35
  --color-card-elevated-2: hsl(var(--card-elevated-2));
36
36
 
37
+ /* Extended Color Palette */
38
+ --color-info: hsl(var(--info));
39
+ --color-info-foreground: hsl(var(--info-foreground));
40
+ --color-info-soft: hsl(var(--info-soft));
41
+
42
+ --color-warning: hsl(var(--warning));
43
+ --color-warning-foreground: hsl(var(--warning-foreground));
44
+ --color-warning-soft: hsl(var(--warning-soft));
45
+
46
+ --color-amber: hsl(var(--amber));
47
+ --color-amber-foreground: hsl(var(--amber-foreground));
48
+ --color-amber-soft: hsl(var(--amber-soft));
49
+
50
+ --color-blue: hsl(var(--blue));
51
+ --color-blue-foreground: hsl(var(--blue-foreground));
52
+ --color-blue-soft: hsl(var(--blue-soft));
53
+
54
+ --color-green: hsl(var(--green));
55
+ --color-green-foreground: hsl(var(--green-foreground));
56
+ --color-green-soft: hsl(var(--green-soft));
57
+
58
+ --color-purple: hsl(var(--purple));
59
+ --color-purple-foreground: hsl(var(--purple-foreground));
60
+ --color-purple-soft: hsl(var(--purple-soft));
61
+
62
+ --color-pink: hsl(var(--pink));
63
+ --color-pink-foreground: hsl(var(--pink-foreground));
64
+ --color-pink-soft: hsl(var(--pink-soft));
65
+
66
+ --color-orange: hsl(var(--orange));
67
+ --color-orange-foreground: hsl(var(--orange-foreground));
68
+ --color-orange-soft: hsl(var(--orange-soft));
69
+
70
+ /* Soft variants for existing semantic colors */
71
+ --color-primary-soft: hsl(var(--primary-soft));
72
+ --color-secondary-soft: hsl(var(--secondary-soft));
73
+ --color-destructive-soft: hsl(var(--destructive-soft));
74
+ --color-success-soft: hsl(var(--success-soft));
75
+
37
76
  /* Depth/Elevation Shadow System (Material Design 3 inspired) */
38
77
  --shadow-depth-1: 0 1px 2px 0 rgb(0 0 0 / 0.05);
39
78
  --shadow-depth-2: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);