@evolve.ui/theme-system 1.0.1 → 1.2.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 (119) hide show
  1. package/{allThemes-CFS_9-tk.cjs → allThemes-3d8nhymo.cjs} +12 -2
  2. package/allThemes-3d8nhymo.cjs.map +1 -0
  3. package/{allThemes-OV-ayn4C.mjs → allThemes-Abn6KDm7.mjs} +7 -3
  4. package/allThemes-Abn6KDm7.mjs.map +1 -0
  5. package/allThemes.cjs +1 -1
  6. package/allThemes.d.cts +1 -0
  7. package/allThemes.d.cts.map +1 -1
  8. package/allThemes.d.mts +1 -0
  9. package/allThemes.d.mts.map +1 -1
  10. package/allThemes.mjs +1 -1
  11. package/brands-CE0wIkKX.d.cts +58 -0
  12. package/brands-CE0wIkKX.d.cts.map +1 -0
  13. package/brands-CE0wIkKX.d.mts +58 -0
  14. package/brands-CE0wIkKX.d.mts.map +1 -0
  15. package/brands.cjs +16 -13
  16. package/brands.cjs.map +1 -1
  17. package/brands.d.cts +2 -2
  18. package/brands.d.mts +2 -2
  19. package/brands.mjs +16 -13
  20. package/brands.mjs.map +1 -1
  21. package/css/EverydayHarbor.css +377 -0
  22. package/css/EverydayHarbor.effects.css +650 -0
  23. package/{default-b-tgfyxR.cjs → default-B6VWF0Js.cjs} +237 -36
  24. package/default-B6VWF0Js.cjs.map +1 -0
  25. package/{default-B2n4PnsJ.mjs → default-BogJDhKE.mjs} +238 -37
  26. package/default-BogJDhKE.mjs.map +1 -0
  27. package/effects.cjs +26 -0
  28. package/effects.cjs.map +1 -0
  29. package/effects.d.cts +22 -0
  30. package/effects.d.cts.map +1 -0
  31. package/effects.d.mts +22 -0
  32. package/effects.d.mts.map +1 -0
  33. package/effects.mjs +22 -0
  34. package/effects.mjs.map +1 -0
  35. package/evolve-ui.entries.json +51 -0
  36. package/index.cjs +12 -5
  37. package/index.cjs.map +1 -1
  38. package/index.d.cts +108 -4
  39. package/index.d.cts.map +1 -1
  40. package/index.d.mts +108 -4
  41. package/index.d.mts.map +1 -1
  42. package/index.mjs +9 -6
  43. package/index.mjs.map +1 -1
  44. package/package.json +5 -1
  45. package/{plugin-DPtvp4lp.cjs → plugin-B3PF4RMI.cjs} +299 -110
  46. package/plugin-B3PF4RMI.cjs.map +1 -0
  47. package/{plugin-D4eCyUJe.mjs → plugin-D6aLCeNB.mjs} +258 -111
  48. package/plugin-D6aLCeNB.mjs.map +1 -0
  49. package/src/effects/__fixtures__/fixtureLayer.ts +88 -0
  50. package/src/effects/brands/EverydayHarbor.ts +392 -0
  51. package/src/effects/classes.ts +19 -0
  52. package/src/effects/emit.ts +314 -0
  53. package/src/effects/hooks.ts +493 -0
  54. package/src/effects/index.ts +2 -0
  55. package/src/effects/layers.ts +18 -0
  56. package/src/effects/rules.ts +90 -0
  57. package/src/effects/rules.typecheck.ts +50 -0
  58. package/src/effects/tokens.ts +148 -0
  59. package/src/effects/validate.ts +683 -0
  60. package/src/effects/vocabulary.ts +32 -0
  61. package/src/index.ts +40 -0
  62. package/src/tailwind/animation.ts +30 -0
  63. package/src/tailwind/borderRadius.ts +32 -0
  64. package/src/tailwind/borderWidth.ts +14 -0
  65. package/src/tailwind/boxShadow.ts +19 -0
  66. package/src/tailwind/colors.ts +332 -0
  67. package/src/tailwind/cssVars.ts +301 -0
  68. package/src/tailwind/fontFamily.ts +14 -0
  69. package/src/tailwind/plugin.ts +299 -0
  70. package/src/tailwind/reducedMotion.ts +164 -0
  71. package/src/tailwind/screens.ts +22 -0
  72. package/src/tailwind/spacing.ts +20 -0
  73. package/src/tailwind/text.ts +172 -0
  74. package/src/tailwind/width.ts +14 -0
  75. package/src/themeValues.snapshot.ts +6735 -0
  76. package/src/themes/BasketballTraining.ts +166 -0
  77. package/src/themes/Default.ts +165 -0
  78. package/src/themes/EverydayHarbor.ts +147 -0
  79. package/src/themes/ProfessionalBrand.ts +166 -0
  80. package/src/themes/allThemes.ts +14 -0
  81. package/src/themes/brandSources.ts +37 -0
  82. package/src/themes/brands.ts +63 -0
  83. package/src/themes/common.ts +65 -0
  84. package/src/themes/contrastFloors.ts +26 -0
  85. package/src/themes/generateCSSVars.ts +123 -0
  86. package/src/themes/inkRamps.ts +43 -0
  87. package/src/themes/runtime/basketballTraining.ts +3 -0
  88. package/src/themes/runtime/buildTheme.ts +411 -0
  89. package/src/themes/runtime/default.ts +3 -0
  90. package/src/themes/runtime/everydayHarbor.ts +3 -0
  91. package/src/themes/runtime/professionalBrand.ts +3 -0
  92. package/src/themes/semanticColorTokenSteps.ts +23 -0
  93. package/src/themes/themeType.ts +169 -0
  94. package/src/themes/themeValues.ts +374 -0
  95. package/src/themes/wcag.ts +48 -0
  96. package/src/themes/writeToCSSFile.ts +11 -0
  97. package/src/utils/cn.ts +35 -0
  98. package/src/utils/color.ts +82 -0
  99. package/src/utils/wrapVar.ts +24 -0
  100. package/tailwind/plugin.cjs +5 -1
  101. package/tailwind/plugin.d.cts +458 -1
  102. package/tailwind/plugin.d.cts.map +1 -1
  103. package/tailwind/plugin.d.mts +458 -1
  104. package/tailwind/plugin.d.mts.map +1 -1
  105. package/tailwind/plugin.mjs +2 -2
  106. package/themes/runtime/default.cjs +1 -1
  107. package/themes/runtime/default.mjs +1 -1
  108. package/themes/themeType.d.cts +1 -1
  109. package/themes/themeType.d.mts +1 -1
  110. package/allThemes-CFS_9-tk.cjs.map +0 -1
  111. package/allThemes-OV-ayn4C.mjs.map +0 -1
  112. package/brands-n30rolHQ.d.cts +0 -33
  113. package/brands-n30rolHQ.d.cts.map +0 -1
  114. package/brands-n30rolHQ.d.mts +0 -33
  115. package/brands-n30rolHQ.d.mts.map +0 -1
  116. package/default-B2n4PnsJ.mjs.map +0 -1
  117. package/default-b-tgfyxR.cjs.map +0 -1
  118. package/plugin-D4eCyUJe.mjs.map +0 -1
  119. package/plugin-DPtvp4lp.cjs.map +0 -1
@@ -0,0 +1,374 @@
1
+ import type { ColorAliasesObject } from './generateCSSVars';
2
+
3
+ import { borderRadius } from '../tailwind/borderRadius';
4
+ import { fontFamily } from '../tailwind/fontFamily';
5
+ import { tailwindTheme, themePlugin } from '../tailwind/plugin';
6
+ import { screens } from '../tailwind/screens';
7
+ import { spacing } from '../tailwind/spacing';
8
+ import { textComponents } from '../tailwind/text';
9
+ import { generateNormalColorSteps, generateSurfaceColorSteps, type Steps } from '../utils/color';
10
+ import { brandSources } from './brandSources';
11
+ import {
12
+ FILLED_BUTTON_FAMILIES,
13
+ FILLED_BUTTON_STEPS,
14
+ TEXT_AA,
15
+ TEXT_AAA_BODY,
16
+ TEXT_CONTRAST_TOKENS,
17
+ UI_AA,
18
+ UI_CONTRAST_TOKENS,
19
+ } from './contrastFloors';
20
+
21
+ export const LOCKED_BRANDS = ['BasketballTraining', 'Default', 'ProfessionalBrand'] as const;
22
+
23
+ export const COLOR_GENERATOR_SEEDS = ['#112233', '#2563EB', '#D92525'] as const;
24
+
25
+ export type BrandSource = {
26
+ colorAliases: ColorAliasesObject;
27
+ nonColorVariables: Record<string, string>;
28
+ };
29
+
30
+ export type TokenValue = {
31
+ binding: string;
32
+ resolved: string;
33
+ };
34
+
35
+ export type BrandValues = {
36
+ palette: Record<string, string>;
37
+ tokens: Record<string, TokenValue>;
38
+ };
39
+
40
+ export type OrderedEntries<T> = readonly (readonly [string, T])[];
41
+
42
+ export type OrderedValue = string | number | boolean | null | readonly OrderedValue[];
43
+
44
+ export type ContrastValues = {
45
+ filledButtonFamilies: readonly string[];
46
+ filledButtonSteps: readonly string[];
47
+ textAa: number;
48
+ textAaaBody: number;
49
+ textTokens: readonly (readonly [string, number])[];
50
+ uiAa: number;
51
+ uiTokens: readonly string[];
52
+ };
53
+
54
+ export type ThemeValueInput = {
55
+ borderRadius?: Record<string, string>;
56
+ brands?: Record<string, BrandSource>;
57
+ contrast?: ContrastValues;
58
+ fontFamily?: Record<string, string>;
59
+ generateNormalColorSteps?: (color: string) => string[];
60
+ generateSurfaceColorSteps?: (color: string, isNeutral?: boolean) => string[];
61
+ screens?: Record<string, Record<string, string>>;
62
+ spacing?: Record<string, string>;
63
+ tailwind?: typeof tailwindTheme;
64
+ textComponents?: Record<string, unknown>;
65
+ };
66
+
67
+ const brandSource = (colorAliases: ColorAliasesObject, nonColorVariables: Record<string, string>): BrandSource => ({
68
+ colorAliases,
69
+ nonColorVariables,
70
+ });
71
+
72
+ export const themeBrandSources = (): Record<string, BrandSource> =>
73
+ Object.fromEntries(
74
+ Object.entries(brandSources).map(([brand, source]) => [
75
+ brand,
76
+ brandSource(source.colorAliases, source.nonColorVariables as Record<string, string>),
77
+ ])
78
+ );
79
+
80
+ const COLOR_STEPS = new Set<string>(['50', '100', '200', '300', '400', '500', '600', '700', '800', '900']);
81
+
82
+ const isColorAlias = (alias: string, aliases: ColorAliasesObject): alias is keyof ColorAliasesObject =>
83
+ Object.prototype.hasOwnProperty.call(aliases, alias);
84
+
85
+ const isStep = (step: string): step is Steps => COLOR_STEPS.has(step);
86
+
87
+ const resolveRefs = (value: string, aliases: BrandSource['colorAliases']): string =>
88
+ value.replace(/var\(--color-([a-z]+)-(\d+)\)/g, (full, alias: string, step: string) => {
89
+ if (!isColorAlias(alias, aliases) || !isStep(step)) {
90
+ return full;
91
+ }
92
+ return aliases[alias][step];
93
+ });
94
+
95
+ const paletteOf = (aliases: BrandSource['colorAliases']): Record<string, string> => {
96
+ const palette: Record<string, string> = {};
97
+ for (const [alias, steps] of Object.entries(aliases)) {
98
+ for (const [step, channels] of Object.entries(steps)) {
99
+ palette[`${alias}.${step}`] = channels;
100
+ }
101
+ }
102
+ return palette;
103
+ };
104
+
105
+ const tokensOf = (brand: BrandSource): Record<string, TokenValue> => {
106
+ const tokens: Record<string, TokenValue> = {};
107
+ for (const [name, binding] of Object.entries(brand.nonColorVariables)) {
108
+ tokens[name] = { binding, resolved: resolveRefs(binding, brand.colorAliases) };
109
+ }
110
+ return tokens;
111
+ };
112
+
113
+ const isTailwindTheme = (value: unknown): value is typeof tailwindTheme =>
114
+ value !== null &&
115
+ typeof value === 'object' &&
116
+ 'borderWidth' in value &&
117
+ 'colors' in value &&
118
+ 'fontFamily' in value &&
119
+ 'screens' in value;
120
+
121
+ const pluginTailwindTheme = (): typeof tailwindTheme => {
122
+ const theme = themePlugin.config?.theme;
123
+ if (!isTailwindTheme(theme)) {
124
+ throw new Error('theme plugin is not passing a theme object');
125
+ }
126
+ return theme;
127
+ };
128
+
129
+ const isSerializable = (value: unknown): value is string | number | boolean | null =>
130
+ typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null;
131
+
132
+ // Identity only. A lookalike null-parent object, and another realm's Object.prototype, are not these.
133
+ const hasAllowedPrototype = (value: object): boolean => {
134
+ const proto = Object.getPrototypeOf(value);
135
+ return proto === Object.prototype || proto === Array.prototype || proto === null;
136
+ };
137
+
138
+ const assertRecordableObject = (value: object): void => {
139
+ if (!hasAllowedPrototype(value)) {
140
+ throw new Error('theme value has an unexpected prototype');
141
+ }
142
+ if (Object.getOwnPropertySymbols(value).length > 0) {
143
+ throw new Error('theme value has a symbol key');
144
+ }
145
+ for (const key of Object.getOwnPropertyNames(value)) {
146
+ if (Array.isArray(value) && key === 'length') {
147
+ continue;
148
+ }
149
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
150
+ if (!descriptor) {
151
+ continue;
152
+ }
153
+ if (descriptor.get !== undefined || descriptor.set !== undefined) {
154
+ throw new Error('theme value has an accessor property');
155
+ }
156
+ if (!descriptor.enumerable) {
157
+ throw new Error('theme value has a non-enumerable property');
158
+ }
159
+ }
160
+ };
161
+
162
+ const orderValue = (value: unknown): OrderedValue => {
163
+ if (value !== null && typeof value === 'object') {
164
+ assertRecordableObject(value);
165
+ }
166
+ if (Array.isArray(value)) {
167
+ return ['array', value.map(orderValue)] as const;
168
+ }
169
+ if (value !== null && typeof value === 'object') {
170
+ return [
171
+ 'object',
172
+ Object.keys(value).map((key) => {
173
+ const child = Object.getOwnPropertyDescriptor(value, key)?.value;
174
+ return [key, orderValue(child)] as const;
175
+ }),
176
+ ] as const;
177
+ }
178
+ if (isSerializable(value)) {
179
+ return value;
180
+ }
181
+ throw new Error('theme value is not serializable');
182
+ };
183
+
184
+ const MATCH_UTILITY_PROBE = 'probe';
185
+
186
+ type UtilityCallback = (value: unknown, extra: { modifier: string | null }) => unknown;
187
+
188
+ const isUtilityCallback = (value: unknown): value is UtilityCallback => typeof value === 'function';
189
+
190
+ const valuesOf = (options: unknown): object | undefined => {
191
+ if (options === null || typeof options !== 'object') {
192
+ return undefined;
193
+ }
194
+ assertRecordableObject(options);
195
+ const values = Object.getOwnPropertyDescriptor(options, 'values')?.value;
196
+ if (values === undefined) {
197
+ return undefined;
198
+ }
199
+ if (values === null || typeof values !== 'object') {
200
+ throw new Error('theme value is not serializable');
201
+ }
202
+ assertRecordableObject(values);
203
+ return values;
204
+ };
205
+
206
+ const callUtility = (callback: UtilityCallback, value: unknown): unknown => callback(value, { modifier: null });
207
+
208
+ const recordCallbackOutputs = (callback: UtilityCallback, values: object | undefined): object => {
209
+ const recorded: Record<string, unknown> = {
210
+ probe: callUtility(callback, MATCH_UTILITY_PROBE),
211
+ };
212
+ if (values) {
213
+ const outputs: Record<string, unknown> = {};
214
+ for (const key of Object.keys(values)) {
215
+ outputs[key] = callUtility(callback, Object.getOwnPropertyDescriptor(values, key)?.value);
216
+ }
217
+ recorded.values = outputs;
218
+ }
219
+ return recorded;
220
+ };
221
+
222
+ const probeUtilities = (utilities: object, options: unknown): object => {
223
+ assertRecordableObject(utilities);
224
+ const values = valuesOf(options);
225
+ const probed: object = Object.create(Object.getPrototypeOf(utilities));
226
+ for (const key of Object.keys(utilities)) {
227
+ const child = Object.getOwnPropertyDescriptor(utilities, key)?.value;
228
+ const recorded = isUtilityCallback(child) ? recordCallbackOutputs(child, values) : child;
229
+ Object.defineProperty(probed, key, {
230
+ configurable: true,
231
+ enumerable: true,
232
+ value: recorded,
233
+ writable: true,
234
+ });
235
+ }
236
+ return probed;
237
+ };
238
+
239
+ const pluginConfig = (): object => {
240
+ const config = themePlugin.config;
241
+ if (!config) {
242
+ throw new Error('theme plugin is not passing a config');
243
+ }
244
+ return config;
245
+ };
246
+
247
+ const PLUGIN_API_METHODS = new Set([
248
+ 'addBase',
249
+ 'addComponents',
250
+ 'addDefaults',
251
+ 'addUtilities',
252
+ 'addVariant',
253
+ 'config',
254
+ 'corePlugins',
255
+ 'e',
256
+ 'matchComponents',
257
+ 'matchUtilities',
258
+ 'matchVariant',
259
+ 'postcss',
260
+ 'prefix',
261
+ 'theme',
262
+ 'variants',
263
+ ]);
264
+
265
+ const recordPluginCalls = (): readonly unknown[] => {
266
+ const calls: unknown[] = [];
267
+ const record =
268
+ (method: string) =>
269
+ (...args: readonly unknown[]) => {
270
+ if (method === 'matchUtilities' || method === 'matchComponents') {
271
+ const [utilities, options] = args;
272
+ const recorded =
273
+ utilities !== null && typeof utilities === 'object'
274
+ ? probeUtilities(utilities, options)
275
+ : utilities;
276
+ calls.push([method, recorded, ...args.slice(1)]);
277
+ return;
278
+ }
279
+ if (method === 'matchVariant') {
280
+ const [name, callback, options] = args;
281
+ const recorded = isUtilityCallback(callback)
282
+ ? recordCallbackOutputs(callback, valuesOf(options))
283
+ : callback;
284
+ calls.push([method, name, recorded, ...args.slice(2)]);
285
+ return;
286
+ }
287
+ calls.push([method, ...args]);
288
+ };
289
+ const api: Record<string, (...args: readonly unknown[]) => void> = new Proxy(
290
+ {},
291
+ {
292
+ get: (_target, property) => {
293
+ if (typeof property !== 'string' || !PLUGIN_API_METHODS.has(property)) {
294
+ throw new Error(`theme plugin API method is not recorded: ${String(property)}`);
295
+ }
296
+ return record(property);
297
+ },
298
+ }
299
+ );
300
+ themePlugin.handler(api as unknown as Parameters<typeof themePlugin.handler>[0]);
301
+ return calls;
302
+ };
303
+
304
+ const isObjectShape = (value: unknown): value is readonly ['object', OrderedEntries<OrderedValue>] =>
305
+ Array.isArray(value) &&
306
+ value.length === 2 &&
307
+ value[0] === 'object' &&
308
+ Array.isArray(value[1]) &&
309
+ value[1].every(
310
+ (entry): entry is readonly [string, OrderedValue] =>
311
+ Array.isArray(entry) && entry.length === 2 && typeof entry[0] === 'string'
312
+ );
313
+
314
+ const LOCKED_BRAND_NAMES: ReadonlySet<string> = new Set(LOCKED_BRANDS);
315
+
316
+ export const themeContrastValues = (): ContrastValues => ({
317
+ filledButtonFamilies: FILLED_BUTTON_FAMILIES,
318
+ filledButtonSteps: FILLED_BUTTON_STEPS,
319
+ textAa: TEXT_AA,
320
+ textAaaBody: TEXT_AAA_BODY,
321
+ textTokens: TEXT_CONTRAST_TOKENS,
322
+ uiAa: UI_AA,
323
+ uiTokens: UI_CONTRAST_TOKENS,
324
+ });
325
+
326
+ export const collectThemeValues = (input: ThemeValueInput = {}): OrderedValue => {
327
+ const brands = input.brands ?? themeBrandSources();
328
+ const normal = input.generateNormalColorSteps ?? generateNormalColorSteps;
329
+ const surface = input.generateSurfaceColorSteps ?? generateSurfaceColorSteps;
330
+ const recordedBrands: Record<string, BrandValues> = {};
331
+ for (const [name, brand] of Object.entries(brands)) {
332
+ recordedBrands[name] = { palette: paletteOf(brand.colorAliases), tokens: tokensOf(brand) };
333
+ }
334
+ const generatorFor = (run: (seed: string) => string[]) => {
335
+ const out: Record<string, string[]> = {};
336
+ for (const seed of COLOR_GENERATOR_SEEDS) {
337
+ out[seed] = run(seed);
338
+ }
339
+ return out;
340
+ };
341
+ const tailwind = input.tailwind ?? pluginTailwindTheme();
342
+ return orderValue({
343
+ brands: recordedBrands,
344
+ colorGenerator: {
345
+ normal: generatorFor(normal),
346
+ surface: generatorFor((seed) => surface(seed)),
347
+ surfaceNeutral: generatorFor((seed) => surface(seed, true)),
348
+ },
349
+ contrast: input.contrast ?? themeContrastValues(),
350
+ fontFamilies: input.fontFamily ?? (fontFamily as Record<string, string>),
351
+ radius: input.borderRadius ?? (borderRadius as Record<string, string>),
352
+ pluginCalls: recordPluginCalls(),
353
+ pluginConfig: pluginConfig(),
354
+ screens: input.screens ?? screens,
355
+ spacing: input.spacing ?? (spacing as Record<string, string>),
356
+ tailwind,
357
+ typeScale: input.textComponents ?? (textComponents as Record<string, unknown>),
358
+ });
359
+ };
360
+
361
+ export const lockThemeValues = (values: OrderedValue): OrderedValue => {
362
+ if (!isObjectShape(values)) {
363
+ throw new Error('theme values are not an object');
364
+ }
365
+ return [
366
+ 'object',
367
+ values[1].map(([key, child]) => {
368
+ if (key !== 'brands' || !isObjectShape(child)) {
369
+ return [key, child] as const;
370
+ }
371
+ return [key, ['object', child[1].filter(([name]) => LOCKED_BRAND_NAMES.has(name))] as const] as const;
372
+ }),
373
+ ] as const;
374
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * WCAG 2.1 relative luminance and contrast ratio, independent of the colour generator (no tinycolor).
3
+ * Shared by contrast.spec.ts and EverydayHarbor.spec.ts so every contrast gate uses ONE formula.
4
+ * Inputs are the generator's channel strings ("R G B", as `removeRGB` in utils/color.ts emits).
5
+ */
6
+
7
+ /** Parse a channel string such as "51 65 92". Throws on anything that is not three 0-255 numbers. */
8
+ export const parseChannels = (value: string): [number, number, number] => {
9
+ const parts = value
10
+ .trim()
11
+ .split(/[\s,]+/)
12
+ .map(Number);
13
+ if (parts.length !== 3 || parts.some((c) => !Number.isFinite(c) || c < 0 || c > 255)) {
14
+ throw new Error(`Not an "R G B" channel string: ${JSON.stringify(value)}`);
15
+ }
16
+ return parts as [number, number, number];
17
+ };
18
+
19
+ /** `#RRGGBB` to the generator's channel string. Throws on a malformed literal. */
20
+ export const hexToChannels = (hex: string): string => {
21
+ if (!/^#[0-9a-f]{6}$/i.test(hex)) {
22
+ throw new Error(`Not a #RRGGBB literal: ${JSON.stringify(hex)}`);
23
+ }
24
+ return [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(' ');
25
+ };
26
+
27
+ /** Generator channel string to `#RRGGBB` (uppercase). */
28
+ export const channelsToHex = (channels: string): string =>
29
+ `#${parseChannels(channels)
30
+ .map((c) => c.toString(16).padStart(2, '0'))
31
+ .join('')
32
+ .toUpperCase()}`;
33
+
34
+ /** WCAG 2.1 relative luminance. */
35
+ export const luminance = ([r, g, b]: [number, number, number]): number => {
36
+ const [lr, lg, lb] = [r, g, b].map((channel) => {
37
+ const c = channel / 255;
38
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
39
+ });
40
+ return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;
41
+ };
42
+
43
+ /** WCAG 2.1 contrast ratio between two channel strings. */
44
+ export const contrast = (a: string, b: string): number => {
45
+ const [la, lb] = [luminance(parseChannels(a)), luminance(parseChannels(b))];
46
+ const [lighter, darker] = la >= lb ? [la, lb] : [lb, la];
47
+ return (lighter + 0.05) / (darker + 0.05);
48
+ };
@@ -0,0 +1,11 @@
1
+ import { writeFile } from 'fs';
2
+
3
+ export const writeToCSSFile = (css: string, path: string): void => {
4
+ writeFile(path, css, (err) => {
5
+ if (err) {
6
+ // eslint-disable-next-line no-console
7
+ console.error(err);
8
+ process.exit(1);
9
+ }
10
+ });
11
+ };
@@ -0,0 +1,35 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ /**
5
+ * Utility function to merge Tailwind CSS classes with proper conflict resolution
6
+ *
7
+ * Combines clsx for conditional class names and tailwind-merge for
8
+ * deduplication and conflict resolution
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { cn } from '@evolve.ui/theme-system';
13
+ *
14
+ * // Basic usage
15
+ * cn('px-2 py-1', 'rounded-md');
16
+ * // => 'px-2 py-1 rounded-md'
17
+ *
18
+ * // With conditionals
19
+ * cn('button', isActive && 'button-active', isDisabled && 'opacity-50');
20
+ * // => 'button button-active' (if isActive is true)
21
+ *
22
+ * // Object syntax
23
+ * cn('button', {
24
+ * 'button-primary': variant === 'primary',
25
+ * 'button-secondary': variant === 'secondary',
26
+ * });
27
+ *
28
+ * // Conflict resolution (last wins)
29
+ * cn('p-4', 'p-2');
30
+ * // => 'p-2'
31
+ * ```
32
+ */
33
+ export const cn = (...inputs: ClassValue[]) => {
34
+ return twMerge(clsx(inputs));
35
+ };
@@ -0,0 +1,82 @@
1
+ import tinycolor from 'tinycolor2';
2
+
3
+ // https://github.com/bgrins/TinyColor
4
+
5
+ export const lighten = (percentAsInt: number, unprocessedColor: string): tinycolor.Instance => {
6
+ const color = tinycolor(unprocessedColor);
7
+ const amount = (percentAsInt * ((1 - color.toHsl().l) * 100)) / 100;
8
+ return color.lighten(amount);
9
+ };
10
+
11
+ export const darken = (percentAsInt: number, unprocessedColor: string): tinycolor.Instance => {
12
+ const color = tinycolor(unprocessedColor);
13
+ const amount = (percentAsInt * (color.toHsl().l * 100)) / 100;
14
+ return color.darken(amount);
15
+ };
16
+
17
+ // https://tailwindcss.com/docs/customizing-colors#using-custom-colors
18
+ const removeRGB = (color: string) => color.replace(/rgb\(|,|\)/g, '');
19
+
20
+ /**
21
+ * Generate a set of color steps from a base color. Base color becomes 500.
22
+ *
23
+ * @param color The color to generate the steps from
24
+ * @returns An array of 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 color values.
25
+ */
26
+ export const generateNormalColorSteps = (color: string) => {
27
+ return [
28
+ removeRGB(lighten(95, color).toRgbString()),
29
+ removeRGB(lighten(85, color).toRgbString()),
30
+ removeRGB(lighten(70, color).toRgbString()),
31
+ removeRGB(lighten(40, color).toRgbString()),
32
+ removeRGB(lighten(20, color).toRgbString()),
33
+ removeRGB(tinycolor(color).toRgbString()),
34
+ removeRGB(darken(20, color).toRgbString()),
35
+ removeRGB(darken(40, color).toRgbString()),
36
+ removeRGB(darken(50, color).toRgbString()),
37
+ removeRGB(darken(70, color).toRgbString()),
38
+ ];
39
+ };
40
+
41
+ /**
42
+ * Generate a set of color steps from a base color. Base color becomes 500.
43
+ *
44
+ * @param color The color to generate the steps from
45
+ * @param isNeutral Whether the color is a neutral color to make surface-50 true white
46
+ * @returns An array of 50, 100, 200, 300, 400, 500, 600, 700, 800, 900 color values.
47
+ */
48
+ export const generateSurfaceColorSteps = (color: string, isNeutral: boolean = false) => {
49
+ return [
50
+ removeRGB(lighten(isNeutral ? 100 : 99, color).toRgbString()),
51
+ removeRGB(lighten(95, color).toRgbString()),
52
+ removeRGB(lighten(80, color).toRgbString()),
53
+ removeRGB(lighten(50, color).toRgbString()),
54
+ removeRGB(lighten(30, color).toRgbString()),
55
+ removeRGB(tinycolor(color).toRgbString()),
56
+ removeRGB(darken(20, color).toRgbString()),
57
+ removeRGB(darken(40, color).toRgbString()),
58
+ removeRGB(darken(50, color).toRgbString()),
59
+ removeRGB(darken(70, color).toRgbString()),
60
+ ];
61
+ };
62
+
63
+ export type Steps = '50' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
64
+ export const steps: Steps[] = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
65
+ export type StepObject = Record<Steps, string>;
66
+
67
+ export const colorStepArrayToObject = (colorStepArray: string[]): StepObject => {
68
+ return {
69
+ '100': colorStepArray[1],
70
+ '200': colorStepArray[2],
71
+ '300': colorStepArray[3],
72
+ '400': colorStepArray[4],
73
+ '50': colorStepArray[0],
74
+ '500': colorStepArray[5],
75
+ '600': colorStepArray[6],
76
+ '700': colorStepArray[7],
77
+ '800': colorStepArray[8],
78
+ '900': colorStepArray[9],
79
+ };
80
+ };
81
+
82
+ export { tinycolor };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * CSS variable wrapping utilities for Tailwind CSS configuration
3
+ *
4
+ * These utilities handle the conversion of CSS custom property names
5
+ * into proper var() references, with optional opacity support for
6
+ * Tailwind's alpha modifier syntax (e.g., `bg-primary-500/50`).
7
+ *
8
+ * @see https://tailwindcss.com/docs/customizing-colors#using-css-variables
9
+ * Internal reference: Hammer UI patterns
10
+ * Ported from the upstream theme system.
11
+ */
12
+
13
+ /** Wraps a CSS variable name in var() */
14
+ export const wrapVar = (value: string): string => `var(${value})`;
15
+
16
+ /** Wraps a CSS variable name in rgb(var()) */
17
+ export const wrapVarRgb = (value: string): string => `rgb(var(${value}))`;
18
+
19
+ /** Wraps a variable reference with a fixed percentage opacity */
20
+ export const wrapOpacity = (wrappedVariable: string, percentOpacity: number): string =>
21
+ `rgb(${wrappedVariable} / ${percentOpacity}%)`;
22
+
23
+ /** Wraps a variable reference with Tailwind's alpha-value placeholder for dynamic opacity */
24
+ export const wrapVariableOpacity = (wrappedVariable: string): string => `rgb(${wrappedVariable} / <alpha-value>)`;
@@ -2,5 +2,9 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_plugin = require("../plugin-DPtvp4lp.cjs");
5
+ const require_plugin = require("../plugin-B3PF4RMI.cjs");
6
+ exports.autofillStyles = require_plugin.autofillStyles;
7
+ exports.pluginBaseStyles = require_plugin.pluginBaseStyles;
8
+ exports.pluginComponentStyles = require_plugin.pluginComponentStyles;
9
+ exports.tailwindTheme = require_plugin.tailwindTheme;
6
10
  exports.themePlugin = require_plugin.themePlugin;