@rokkit/ui 1.0.0-next.98 → 1.0.1
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 +159 -1
- package/package.json +52 -53
- package/src/MarkdownRenderer.svelte +106 -0
- package/src/components/AlertList.svelte +59 -0
- package/src/components/Avatar.svelte +68 -0
- package/src/components/Badge.svelte +57 -0
- package/src/components/BreadCrumbs.svelte +94 -0
- package/src/components/Button.svelte +91 -0
- package/src/components/ButtonGroup.svelte +9 -0
- package/src/components/Card.svelte +71 -0
- package/src/components/Carousel.svelte +169 -0
- package/src/components/Code.svelte +189 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/Divider.svelte +24 -0
- package/src/components/Dropdown.svelte +202 -0
- package/src/components/FloatingAction.svelte +367 -0
- package/src/components/FloatingNavigation.svelte +256 -0
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +25 -0
- package/src/components/LazyTree.svelte +171 -0
- package/src/components/List.svelte +224 -0
- package/src/components/Menu.svelte +270 -0
- package/src/components/Message.svelte +75 -0
- package/src/components/MultiSelect.svelte +367 -0
- package/src/components/PaletteManager.svelte +360 -0
- package/src/components/Pill.svelte +83 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +346 -0
- package/src/components/Rating.svelte +115 -0
- package/src/components/Reveal.svelte +46 -0
- package/src/components/SearchFilter.svelte +88 -0
- package/src/components/Select.svelte +424 -0
- package/src/components/Shine.svelte +87 -0
- package/src/components/Stack.svelte +38 -0
- package/src/components/StatusList.svelte +18 -0
- package/src/components/Stepper.svelte +174 -0
- package/src/components/Swatch.svelte +102 -0
- package/src/components/Switch.svelte +71 -0
- package/src/components/Table.svelte +245 -0
- package/src/components/Tabs.svelte +202 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +93 -0
- package/src/components/Toolbar.svelte +306 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tooltip.svelte +121 -0
- package/src/components/Tree.svelte +143 -0
- package/src/components/UploadFileStatus.svelte +102 -0
- package/src/components/UploadProgress.svelte +127 -0
- package/src/components/UploadTarget.svelte +126 -0
- package/src/components/index.ts +48 -0
- package/src/index.ts +59 -0
- package/src/markdown-plugin.ts +12 -0
- package/src/types/button.ts +86 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +123 -0
- package/src/types/floating-navigation.ts +80 -0
- package/src/types/index.ts +58 -0
- package/src/types/list.ts +200 -0
- package/src/types/menu.ts +95 -0
- package/src/types/palette.ts +160 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +176 -0
- package/src/types/switch.ts +68 -0
- package/src/types/table.ts +219 -0
- package/src/types/tabs.ts +103 -0
- package/src/types/timeline.ts +53 -0
- package/src/types/toggle.ts +68 -0
- package/src/types/toolbar.ts +167 -0
- package/src/types/tooltip.ts +21 -0
- package/src/types/tree.ts +250 -0
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +71 -0
- package/src/utils/palette.ts +618 -0
- package/src/utils/shiki.ts +114 -0
- package/src/utils/upload.js +145 -0
- package/LICENSE +0 -21
- package/src/index.js +0 -8
- package/src/wrappers.js +0 -2
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UploadTarget Component Types
|
|
3
|
+
*
|
|
4
|
+
* Drop zone with file validation. Accepts drag-and-drop or click-to-browse.
|
|
5
|
+
* Validates files against accept (MIME/extension) and maxSize constraints.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Snippet } from 'svelte'
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Item Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* File validation error returned via onerror callback
|
|
16
|
+
*/
|
|
17
|
+
export interface UploadError {
|
|
18
|
+
/** The file that failed validation */
|
|
19
|
+
file: File
|
|
20
|
+
/** Why the file was rejected */
|
|
21
|
+
reason: 'type' | 'size'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Snippet Types
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Snippet for custom drop zone content.
|
|
30
|
+
* Receives the current dragging state.
|
|
31
|
+
*/
|
|
32
|
+
export type UploadTargetContentSnippet = Snippet<[dragging: boolean]>
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Component Props
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Props for the UploadTarget component
|
|
40
|
+
*/
|
|
41
|
+
export interface UploadTargetProps {
|
|
42
|
+
/** Accepted file types (MIME types or extensions, comma-separated) */
|
|
43
|
+
accept?: string
|
|
44
|
+
|
|
45
|
+
/** Maximum file size in bytes */
|
|
46
|
+
maxSize?: number
|
|
47
|
+
|
|
48
|
+
/** Allow multiple file selection */
|
|
49
|
+
multiple?: boolean
|
|
50
|
+
|
|
51
|
+
/** Disable the drop zone */
|
|
52
|
+
disabled?: boolean
|
|
53
|
+
|
|
54
|
+
/** Label overrides merged with messages.uploadTarget */
|
|
55
|
+
labels?: Record<string, string>
|
|
56
|
+
|
|
57
|
+
/** Called with validated files after drop or browse */
|
|
58
|
+
onfiles?: (files: File[]) => void
|
|
59
|
+
|
|
60
|
+
/** Called for each file that fails validation */
|
|
61
|
+
onerror?: (err: UploadError) => void
|
|
62
|
+
|
|
63
|
+
/** Size variant — full (default) fills available area; small shows only border + icon */
|
|
64
|
+
size?: 'full' | 'small'
|
|
65
|
+
|
|
66
|
+
/** Additional CSS classes */
|
|
67
|
+
class?: string
|
|
68
|
+
|
|
69
|
+
/** Custom content snippet (replaces default drop zone UI) */
|
|
70
|
+
content?: UploadTargetContentSnippet
|
|
71
|
+
}
|
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette Utilities
|
|
3
|
+
*
|
|
4
|
+
* Functions for color manipulation, shade generation, and runtime palette application.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Types
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
export interface RGB {
|
|
12
|
+
r: number
|
|
13
|
+
g: number
|
|
14
|
+
b: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HSL {
|
|
18
|
+
h: number
|
|
19
|
+
s: number
|
|
20
|
+
l: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ShadeKey =
|
|
24
|
+
| '50'
|
|
25
|
+
| '100'
|
|
26
|
+
| '200'
|
|
27
|
+
| '300'
|
|
28
|
+
| '400'
|
|
29
|
+
| '500'
|
|
30
|
+
| '600'
|
|
31
|
+
| '700'
|
|
32
|
+
| '800'
|
|
33
|
+
| '900'
|
|
34
|
+
| '950'
|
|
35
|
+
|
|
36
|
+
export type Shades = Record<ShadeKey, string>
|
|
37
|
+
|
|
38
|
+
export type ColorRole =
|
|
39
|
+
| 'primary'
|
|
40
|
+
| 'secondary'
|
|
41
|
+
| 'accent'
|
|
42
|
+
| 'surface'
|
|
43
|
+
| 'success'
|
|
44
|
+
| 'warning'
|
|
45
|
+
| 'danger'
|
|
46
|
+
| 'info'
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mapping of color roles to Tailwind color names or hex values
|
|
50
|
+
*/
|
|
51
|
+
export type ColorMapping = Partial<Record<ColorRole, string>>
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Color Conversion Utilities
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Convert hex color to RGB
|
|
59
|
+
*/
|
|
60
|
+
export function hexToRgb(hex: string): RGB {
|
|
61
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
62
|
+
if (!result) {
|
|
63
|
+
throw new Error(`Invalid hex color: ${hex}`)
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
r: parseInt(result[1], 16),
|
|
67
|
+
g: parseInt(result[2], 16),
|
|
68
|
+
b: parseInt(result[3], 16)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert RGB to hex color
|
|
74
|
+
*/
|
|
75
|
+
export function rgbToHex(rgb: RGB): string {
|
|
76
|
+
const toHex = (n: number) => {
|
|
77
|
+
const hex = Math.round(Math.max(0, Math.min(255, n))).toString(16)
|
|
78
|
+
return hex.length === 1 ? `0${hex}` : hex
|
|
79
|
+
}
|
|
80
|
+
return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Convert RGB to HSL
|
|
85
|
+
*/
|
|
86
|
+
interface NormalizedRGB {
|
|
87
|
+
r: number
|
|
88
|
+
g: number
|
|
89
|
+
b: number
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function computeHue(norm: NormalizedRGB, max: number, d: number): number {
|
|
93
|
+
const { r, g, b } = norm
|
|
94
|
+
if (max === r) return ((g - b) / d + (g < b ? 6 : 0)) / 6
|
|
95
|
+
if (max === g) return ((b - r) / d + 2) / 6
|
|
96
|
+
return ((r - g) / d + 4) / 6
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function rgbToHsl(rgb: RGB): HSL {
|
|
100
|
+
const norm: NormalizedRGB = { r: rgb.r / 255, g: rgb.g / 255, b: rgb.b / 255 }
|
|
101
|
+
|
|
102
|
+
const max = Math.max(norm.r, norm.g, norm.b)
|
|
103
|
+
const min = Math.min(norm.r, norm.g, norm.b)
|
|
104
|
+
const l = (max + min) / 2
|
|
105
|
+
|
|
106
|
+
if (max === min) return { h: 0, s: 0, l: l * 100 }
|
|
107
|
+
|
|
108
|
+
const d = max - min
|
|
109
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
|
110
|
+
const h = computeHue(norm, max, d)
|
|
111
|
+
|
|
112
|
+
return { h: h * 360, s: s * 100, l: l * 100 }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeT(t: number): number {
|
|
116
|
+
if (t < 0) return t + 1
|
|
117
|
+
if (t > 1) return t - 1
|
|
118
|
+
return t
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function hue2rgb(p: number, q: number, t: number): number {
|
|
122
|
+
const tt = normalizeT(t)
|
|
123
|
+
if (tt < 1 / 6) return p + (q - p) * 6 * tt
|
|
124
|
+
if (tt < 1 / 2) return q
|
|
125
|
+
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6
|
|
126
|
+
return p
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convert HSL to RGB
|
|
131
|
+
*/
|
|
132
|
+
export function hslToRgb(hsl: HSL): RGB {
|
|
133
|
+
const h = hsl.h / 360
|
|
134
|
+
const s = hsl.s / 100
|
|
135
|
+
const l = hsl.l / 100
|
|
136
|
+
|
|
137
|
+
if (s === 0) {
|
|
138
|
+
const val = Math.round(l * 255)
|
|
139
|
+
return { r: val, g: val, b: val }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
|
143
|
+
const p = 2 * l - q
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
|
|
147
|
+
g: Math.round(hue2rgb(p, q, h) * 255),
|
|
148
|
+
b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Convert hex to HSL
|
|
154
|
+
*/
|
|
155
|
+
export function hexToHsl(hex: string): HSL {
|
|
156
|
+
return rgbToHsl(hexToRgb(hex))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Convert HSL to hex
|
|
161
|
+
*/
|
|
162
|
+
export function hslToHex(hsl: HSL): string {
|
|
163
|
+
return rgbToHex(hslToRgb(hsl))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// =============================================================================
|
|
167
|
+
// Shade Generation
|
|
168
|
+
// =============================================================================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate Tailwind-like shades from a base hex color
|
|
172
|
+
* The input color is used as the 500 shade
|
|
173
|
+
*/
|
|
174
|
+
export function generateShades(hex: string): Shades {
|
|
175
|
+
const hsl = hexToHsl(hex)
|
|
176
|
+
|
|
177
|
+
// Shade configuration: [saturation multiplier, target lightness]
|
|
178
|
+
// Lighter shades have higher lightness, lower saturation
|
|
179
|
+
// Darker shades have lower lightness, slightly higher saturation
|
|
180
|
+
const shadeConfig: Record<ShadeKey, [number, number]> = {
|
|
181
|
+
'50': [0.9, 97],
|
|
182
|
+
'100': [0.92, 94],
|
|
183
|
+
'200': [0.94, 86],
|
|
184
|
+
'300': [0.96, 75],
|
|
185
|
+
'400': [0.98, 60],
|
|
186
|
+
'500': [1.0, hsl.l], // Keep original lightness
|
|
187
|
+
'600': [1.02, Math.max(hsl.l * 0.82, 25)],
|
|
188
|
+
'700': [1.04, Math.max(hsl.l * 0.65, 20)],
|
|
189
|
+
'800': [1.06, Math.max(hsl.l * 0.5, 15)],
|
|
190
|
+
'900': [1.08, Math.max(hsl.l * 0.35, 12)],
|
|
191
|
+
'950': [1.12, Math.max(hsl.l * 0.22, 8)]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const shades: Partial<Shades> = {}
|
|
195
|
+
|
|
196
|
+
for (const [shade, [satMult, lightness]] of Object.entries(shadeConfig)) {
|
|
197
|
+
const newHsl: HSL = {
|
|
198
|
+
h: hsl.h,
|
|
199
|
+
s: Math.min(100, hsl.s * satMult),
|
|
200
|
+
l: lightness
|
|
201
|
+
}
|
|
202
|
+
shades[shade as ShadeKey] = hslToHex(newHsl)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return shades as Shades
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if a string is a valid hex color
|
|
210
|
+
*/
|
|
211
|
+
export function isHexColor(value: string): boolean {
|
|
212
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(value)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// =============================================================================
|
|
216
|
+
// Tailwind Colors (Built-in Presets)
|
|
217
|
+
// =============================================================================
|
|
218
|
+
|
|
219
|
+
export const tailwindColors: Record<string, Shades> = {
|
|
220
|
+
slate: {
|
|
221
|
+
'50': '#f8fafc',
|
|
222
|
+
'100': '#f1f5f9',
|
|
223
|
+
'200': '#e2e8f0',
|
|
224
|
+
'300': '#cbd5e1',
|
|
225
|
+
'400': '#94a3b8',
|
|
226
|
+
'500': '#64748b',
|
|
227
|
+
'600': '#475569',
|
|
228
|
+
'700': '#334155',
|
|
229
|
+
'800': '#1e293b',
|
|
230
|
+
'900': '#0f172a',
|
|
231
|
+
'950': '#020617'
|
|
232
|
+
},
|
|
233
|
+
gray: {
|
|
234
|
+
'50': '#f9fafb',
|
|
235
|
+
'100': '#f3f4f6',
|
|
236
|
+
'200': '#e5e7eb',
|
|
237
|
+
'300': '#d1d5db',
|
|
238
|
+
'400': '#9ca3af',
|
|
239
|
+
'500': '#6b7280',
|
|
240
|
+
'600': '#4b5563',
|
|
241
|
+
'700': '#374151',
|
|
242
|
+
'800': '#1f2937',
|
|
243
|
+
'900': '#111827',
|
|
244
|
+
'950': '#030712'
|
|
245
|
+
},
|
|
246
|
+
zinc: {
|
|
247
|
+
'50': '#fafafa',
|
|
248
|
+
'100': '#f4f4f5',
|
|
249
|
+
'200': '#e4e4e7',
|
|
250
|
+
'300': '#d4d4d8',
|
|
251
|
+
'400': '#a1a1aa',
|
|
252
|
+
'500': '#71717a',
|
|
253
|
+
'600': '#52525b',
|
|
254
|
+
'700': '#3f3f46',
|
|
255
|
+
'800': '#27272a',
|
|
256
|
+
'900': '#18181b',
|
|
257
|
+
'950': '#09090b'
|
|
258
|
+
},
|
|
259
|
+
neutral: {
|
|
260
|
+
'50': '#fafafa',
|
|
261
|
+
'100': '#f5f5f5',
|
|
262
|
+
'200': '#e5e5e5',
|
|
263
|
+
'300': '#d4d4d4',
|
|
264
|
+
'400': '#a3a3a3',
|
|
265
|
+
'500': '#737373',
|
|
266
|
+
'600': '#525252',
|
|
267
|
+
'700': '#404040',
|
|
268
|
+
'800': '#262626',
|
|
269
|
+
'900': '#171717',
|
|
270
|
+
'950': '#0a0a0a'
|
|
271
|
+
},
|
|
272
|
+
stone: {
|
|
273
|
+
'50': '#fafaf9',
|
|
274
|
+
'100': '#f5f5f4',
|
|
275
|
+
'200': '#e7e5e4',
|
|
276
|
+
'300': '#d6d3d1',
|
|
277
|
+
'400': '#a8a29e',
|
|
278
|
+
'500': '#78716c',
|
|
279
|
+
'600': '#57534e',
|
|
280
|
+
'700': '#44403c',
|
|
281
|
+
'800': '#292524',
|
|
282
|
+
'900': '#1c1917',
|
|
283
|
+
'950': '#0c0a09'
|
|
284
|
+
},
|
|
285
|
+
red: {
|
|
286
|
+
'50': '#fef2f2',
|
|
287
|
+
'100': '#fee2e2',
|
|
288
|
+
'200': '#fecaca',
|
|
289
|
+
'300': '#fca5a5',
|
|
290
|
+
'400': '#f87171',
|
|
291
|
+
'500': '#ef4444',
|
|
292
|
+
'600': '#dc2626',
|
|
293
|
+
'700': '#b91c1c',
|
|
294
|
+
'800': '#991b1b',
|
|
295
|
+
'900': '#7f1d1d',
|
|
296
|
+
'950': '#450a0a'
|
|
297
|
+
},
|
|
298
|
+
orange: {
|
|
299
|
+
'50': '#fff7ed',
|
|
300
|
+
'100': '#ffedd5',
|
|
301
|
+
'200': '#fed7aa',
|
|
302
|
+
'300': '#fdba74',
|
|
303
|
+
'400': '#fb923c',
|
|
304
|
+
'500': '#f97316',
|
|
305
|
+
'600': '#ea580c',
|
|
306
|
+
'700': '#c2410c',
|
|
307
|
+
'800': '#9a3412',
|
|
308
|
+
'900': '#7c2d12',
|
|
309
|
+
'950': '#431407'
|
|
310
|
+
},
|
|
311
|
+
amber: {
|
|
312
|
+
'50': '#fffbeb',
|
|
313
|
+
'100': '#fef3c7',
|
|
314
|
+
'200': '#fde68a',
|
|
315
|
+
'300': '#fcd34d',
|
|
316
|
+
'400': '#fbbf24',
|
|
317
|
+
'500': '#f59e0b',
|
|
318
|
+
'600': '#d97706',
|
|
319
|
+
'700': '#b45309',
|
|
320
|
+
'800': '#92400e',
|
|
321
|
+
'900': '#78350f',
|
|
322
|
+
'950': '#451a03'
|
|
323
|
+
},
|
|
324
|
+
yellow: {
|
|
325
|
+
'50': '#fefce8',
|
|
326
|
+
'100': '#fef9c3',
|
|
327
|
+
'200': '#fef08a',
|
|
328
|
+
'300': '#fde047',
|
|
329
|
+
'400': '#facc15',
|
|
330
|
+
'500': '#eab308',
|
|
331
|
+
'600': '#ca8a04',
|
|
332
|
+
'700': '#a16207',
|
|
333
|
+
'800': '#854d0e',
|
|
334
|
+
'900': '#713f12',
|
|
335
|
+
'950': '#422006'
|
|
336
|
+
},
|
|
337
|
+
lime: {
|
|
338
|
+
'50': '#f7fee7',
|
|
339
|
+
'100': '#ecfccb',
|
|
340
|
+
'200': '#d9f99d',
|
|
341
|
+
'300': '#bef264',
|
|
342
|
+
'400': '#a3e635',
|
|
343
|
+
'500': '#84cc16',
|
|
344
|
+
'600': '#65a30d',
|
|
345
|
+
'700': '#4d7c0f',
|
|
346
|
+
'800': '#3f6212',
|
|
347
|
+
'900': '#365314',
|
|
348
|
+
'950': '#1a2e05'
|
|
349
|
+
},
|
|
350
|
+
green: {
|
|
351
|
+
'50': '#f0fdf4',
|
|
352
|
+
'100': '#dcfce7',
|
|
353
|
+
'200': '#bbf7d0',
|
|
354
|
+
'300': '#86efac',
|
|
355
|
+
'400': '#4ade80',
|
|
356
|
+
'500': '#22c55e',
|
|
357
|
+
'600': '#16a34a',
|
|
358
|
+
'700': '#15803d',
|
|
359
|
+
'800': '#166534',
|
|
360
|
+
'900': '#14532d',
|
|
361
|
+
'950': '#052e16'
|
|
362
|
+
},
|
|
363
|
+
emerald: {
|
|
364
|
+
'50': '#ecfdf5',
|
|
365
|
+
'100': '#d1fae5',
|
|
366
|
+
'200': '#a7f3d0',
|
|
367
|
+
'300': '#6ee7b7',
|
|
368
|
+
'400': '#34d399',
|
|
369
|
+
'500': '#10b981',
|
|
370
|
+
'600': '#059669',
|
|
371
|
+
'700': '#047857',
|
|
372
|
+
'800': '#065f46',
|
|
373
|
+
'900': '#064e3b',
|
|
374
|
+
'950': '#022c22'
|
|
375
|
+
},
|
|
376
|
+
teal: {
|
|
377
|
+
'50': '#f0fdfa',
|
|
378
|
+
'100': '#ccfbf1',
|
|
379
|
+
'200': '#99f6e4',
|
|
380
|
+
'300': '#5eead4',
|
|
381
|
+
'400': '#2dd4bf',
|
|
382
|
+
'500': '#14b8a6',
|
|
383
|
+
'600': '#0d9488',
|
|
384
|
+
'700': '#0f766e',
|
|
385
|
+
'800': '#115e59',
|
|
386
|
+
'900': '#134e4a',
|
|
387
|
+
'950': '#042f2e'
|
|
388
|
+
},
|
|
389
|
+
cyan: {
|
|
390
|
+
'50': '#ecfeff',
|
|
391
|
+
'100': '#cffafe',
|
|
392
|
+
'200': '#a5f3fc',
|
|
393
|
+
'300': '#67e8f9',
|
|
394
|
+
'400': '#22d3ee',
|
|
395
|
+
'500': '#06b6d4',
|
|
396
|
+
'600': '#0891b2',
|
|
397
|
+
'700': '#0e7490',
|
|
398
|
+
'800': '#155e75',
|
|
399
|
+
'900': '#164e63',
|
|
400
|
+
'950': '#083344'
|
|
401
|
+
},
|
|
402
|
+
sky: {
|
|
403
|
+
'50': '#f0f9ff',
|
|
404
|
+
'100': '#e0f2fe',
|
|
405
|
+
'200': '#bae6fd',
|
|
406
|
+
'300': '#7dd3fc',
|
|
407
|
+
'400': '#38bdf8',
|
|
408
|
+
'500': '#0ea5e9',
|
|
409
|
+
'600': '#0284c7',
|
|
410
|
+
'700': '#0369a1',
|
|
411
|
+
'800': '#075985',
|
|
412
|
+
'900': '#0c4a6e',
|
|
413
|
+
'950': '#082f49'
|
|
414
|
+
},
|
|
415
|
+
blue: {
|
|
416
|
+
'50': '#eff6ff',
|
|
417
|
+
'100': '#dbeafe',
|
|
418
|
+
'200': '#bfdbfe',
|
|
419
|
+
'300': '#93c5fd',
|
|
420
|
+
'400': '#60a5fa',
|
|
421
|
+
'500': '#3b82f6',
|
|
422
|
+
'600': '#2563eb',
|
|
423
|
+
'700': '#1d4ed8',
|
|
424
|
+
'800': '#1e40af',
|
|
425
|
+
'900': '#1e3a8a',
|
|
426
|
+
'950': '#172554'
|
|
427
|
+
},
|
|
428
|
+
indigo: {
|
|
429
|
+
'50': '#eef2ff',
|
|
430
|
+
'100': '#e0e7ff',
|
|
431
|
+
'200': '#c7d2fe',
|
|
432
|
+
'300': '#a5b4fc',
|
|
433
|
+
'400': '#818cf8',
|
|
434
|
+
'500': '#6366f1',
|
|
435
|
+
'600': '#4f46e5',
|
|
436
|
+
'700': '#4338ca',
|
|
437
|
+
'800': '#3730a3',
|
|
438
|
+
'900': '#312e81',
|
|
439
|
+
'950': '#1e1b4b'
|
|
440
|
+
},
|
|
441
|
+
violet: {
|
|
442
|
+
'50': '#f5f3ff',
|
|
443
|
+
'100': '#ede9fe',
|
|
444
|
+
'200': '#ddd6fe',
|
|
445
|
+
'300': '#c4b5fd',
|
|
446
|
+
'400': '#a78bfa',
|
|
447
|
+
'500': '#8b5cf6',
|
|
448
|
+
'600': '#7c3aed',
|
|
449
|
+
'700': '#6d28d9',
|
|
450
|
+
'800': '#5b21b6',
|
|
451
|
+
'900': '#4c1d95',
|
|
452
|
+
'950': '#2e1065'
|
|
453
|
+
},
|
|
454
|
+
purple: {
|
|
455
|
+
'50': '#faf5ff',
|
|
456
|
+
'100': '#f3e8ff',
|
|
457
|
+
'200': '#e9d5ff',
|
|
458
|
+
'300': '#d8b4fe',
|
|
459
|
+
'400': '#c084fc',
|
|
460
|
+
'500': '#a855f7',
|
|
461
|
+
'600': '#9333ea',
|
|
462
|
+
'700': '#7e22ce',
|
|
463
|
+
'800': '#6b21a8',
|
|
464
|
+
'900': '#581c87',
|
|
465
|
+
'950': '#3b0764'
|
|
466
|
+
},
|
|
467
|
+
fuchsia: {
|
|
468
|
+
'50': '#fdf4ff',
|
|
469
|
+
'100': '#fae8ff',
|
|
470
|
+
'200': '#f5d0fe',
|
|
471
|
+
'300': '#f0abfc',
|
|
472
|
+
'400': '#e879f9',
|
|
473
|
+
'500': '#d946ef',
|
|
474
|
+
'600': '#c026d3',
|
|
475
|
+
'700': '#a21caf',
|
|
476
|
+
'800': '#86198f',
|
|
477
|
+
'900': '#701a75',
|
|
478
|
+
'950': '#4a044e'
|
|
479
|
+
},
|
|
480
|
+
pink: {
|
|
481
|
+
'50': '#fdf2f8',
|
|
482
|
+
'100': '#fce7f3',
|
|
483
|
+
'200': '#fbcfe8',
|
|
484
|
+
'300': '#f9a8d4',
|
|
485
|
+
'400': '#f472b6',
|
|
486
|
+
'500': '#ec4899',
|
|
487
|
+
'600': '#db2777',
|
|
488
|
+
'700': '#be185d',
|
|
489
|
+
'800': '#9d174d',
|
|
490
|
+
'900': '#831843',
|
|
491
|
+
'950': '#500724'
|
|
492
|
+
},
|
|
493
|
+
rose: {
|
|
494
|
+
'50': '#fff1f2',
|
|
495
|
+
'100': '#ffe4e6',
|
|
496
|
+
'200': '#fecdd3',
|
|
497
|
+
'300': '#fda4af',
|
|
498
|
+
'400': '#fb7185',
|
|
499
|
+
'500': '#f43f5e',
|
|
500
|
+
'600': '#e11d48',
|
|
501
|
+
'700': '#be123c',
|
|
502
|
+
'800': '#9f1239',
|
|
503
|
+
'900': '#881337',
|
|
504
|
+
'950': '#4c0519'
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Get shades for a color (either Tailwind preset or custom hex)
|
|
510
|
+
*/
|
|
511
|
+
export function getShades(color: string): Shades {
|
|
512
|
+
if (isHexColor(color)) {
|
|
513
|
+
return generateShades(color)
|
|
514
|
+
}
|
|
515
|
+
const preset = tailwindColors[color.toLowerCase()]
|
|
516
|
+
if (preset) {
|
|
517
|
+
return preset
|
|
518
|
+
}
|
|
519
|
+
throw new Error(`Unknown color: ${color}. Must be a hex color or Tailwind color name.`)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// =============================================================================
|
|
523
|
+
// Runtime Palette Application
|
|
524
|
+
// =============================================================================
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Apply a color palette to the document root
|
|
528
|
+
* Sets CSS variables for each color role and shade
|
|
529
|
+
*/
|
|
530
|
+
export function applyPalette(
|
|
531
|
+
mapping: Partial<Record<ColorRole, string>>,
|
|
532
|
+
element: HTMLElement = document.documentElement
|
|
533
|
+
): void {
|
|
534
|
+
for (const [role, color] of Object.entries(mapping)) {
|
|
535
|
+
if (!color) continue
|
|
536
|
+
|
|
537
|
+
const shades = getShades(color)
|
|
538
|
+
|
|
539
|
+
for (const [shade, hex] of Object.entries(shades)) {
|
|
540
|
+
const rgb = hexToRgb(hex)
|
|
541
|
+
// Store as RGB values (comma-separated) for rgba() compatibility
|
|
542
|
+
element.style.setProperty(`--color-${role}-${shade}`, `${rgb.r},${rgb.g},${rgb.b}`)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Also set the default (500) as the base color variable
|
|
546
|
+
const baseRgb = hexToRgb(shades['500'])
|
|
547
|
+
element.style.setProperty(`--color-${role}`, `${baseRgb.r},${baseRgb.g},${baseRgb.b}`)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const DEFAULT_PALETTE_ROLES: ColorRole[] = [
|
|
552
|
+
'primary',
|
|
553
|
+
'secondary',
|
|
554
|
+
'accent',
|
|
555
|
+
'surface',
|
|
556
|
+
'success',
|
|
557
|
+
'warning',
|
|
558
|
+
'danger',
|
|
559
|
+
'info'
|
|
560
|
+
]
|
|
561
|
+
|
|
562
|
+
const ALL_SHADE_KEYS: ShadeKey[] = [
|
|
563
|
+
'50',
|
|
564
|
+
'100',
|
|
565
|
+
'200',
|
|
566
|
+
'300',
|
|
567
|
+
'400',
|
|
568
|
+
'500',
|
|
569
|
+
'600',
|
|
570
|
+
'700',
|
|
571
|
+
'800',
|
|
572
|
+
'900',
|
|
573
|
+
'950'
|
|
574
|
+
]
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Remove custom palette CSS variables (reset to theme defaults)
|
|
578
|
+
*/
|
|
579
|
+
export function resetPalette(
|
|
580
|
+
roles: ColorRole[] = DEFAULT_PALETTE_ROLES,
|
|
581
|
+
element: HTMLElement = document.documentElement
|
|
582
|
+
): void {
|
|
583
|
+
for (const role of roles) {
|
|
584
|
+
element.style.removeProperty(`--color-${role}`)
|
|
585
|
+
for (const shade of ALL_SHADE_KEYS) {
|
|
586
|
+
element.style.removeProperty(`--color-${role}-${shade}`)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Save palette to localStorage
|
|
593
|
+
*/
|
|
594
|
+
export function savePalette(key: string, mapping: Partial<Record<ColorRole, string>>): void {
|
|
595
|
+
localStorage.setItem(key, JSON.stringify(mapping))
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Load palette from localStorage
|
|
600
|
+
*/
|
|
601
|
+
export function loadPalette(key: string): Partial<Record<ColorRole, string>> | null {
|
|
602
|
+
const stored = localStorage.getItem(key)
|
|
603
|
+
if (stored) {
|
|
604
|
+
try {
|
|
605
|
+
return JSON.parse(stored)
|
|
606
|
+
} catch {
|
|
607
|
+
return null
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return null
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Get list of available Tailwind color names
|
|
615
|
+
*/
|
|
616
|
+
export function getTailwindColorNames(): string[] {
|
|
617
|
+
return Object.keys(tailwindColors)
|
|
618
|
+
}
|