@idealyst/theme 1.1.6 → 1.1.7
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/package.json +1 -1
- package/src/builder.ts +317 -0
- package/src/darkTheme.ts +186 -943
- package/src/helpers.ts +206 -0
- package/src/index.ts +3 -1
- package/src/lightTheme.ts +286 -859
- package/src/theme/color.ts +36 -15
- package/src/theme/extensions.ts +92 -0
- package/src/theme/index.ts +41 -27
- package/src/theme/intent.ts +12 -7
- package/src/theme/shadow.ts +12 -16
- package/src/theme/size.ts +6 -202
- package/src/theme/structures.ts +237 -0
- package/src/unistyles.ts +5 -11
- package/src/variants/color.ts +3 -5
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { IntentValue, Theme, Shade, ColorValue } from './theme';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deep partial type for nested objects.
|
|
5
|
+
*/
|
|
6
|
+
type DeepPartial<T> = {
|
|
7
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create an IntentValue with all required properties.
|
|
12
|
+
* Automatically generates light and dark variants if not provided.
|
|
13
|
+
*
|
|
14
|
+
* @param primary - The primary color for this intent
|
|
15
|
+
* @param contrast - The contrast color (typically for text on primary background)
|
|
16
|
+
* @param options - Optional overrides for light, dark, and any extended properties
|
|
17
|
+
* @returns A complete IntentValue object
|
|
18
|
+
*
|
|
19
|
+
* @example Basic usage
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const brandIntent = createIntent('#8B5CF6', '#ffffff');
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example With extended properties
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const brandIntent = createIntent('#8B5CF6', '#ffffff', {
|
|
27
|
+
* shadowColor: 'rgba(139, 92, 246, 0.3)',
|
|
28
|
+
* hoverBackground: 'rgba(139, 92, 246, 0.1)',
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function createIntent(
|
|
33
|
+
primary: string,
|
|
34
|
+
contrast: string,
|
|
35
|
+
options?: Partial<Omit<IntentValue, 'primary' | 'contrast'>>
|
|
36
|
+
): IntentValue {
|
|
37
|
+
return {
|
|
38
|
+
primary,
|
|
39
|
+
contrast,
|
|
40
|
+
light: options?.light ?? lightenColor(primary, 0.3),
|
|
41
|
+
dark: options?.dark ?? darkenColor(primary, 0.3),
|
|
42
|
+
...options,
|
|
43
|
+
} as IntentValue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Deep merge two objects together.
|
|
48
|
+
* The source object's values take priority over target.
|
|
49
|
+
*
|
|
50
|
+
* @param target - Base object
|
|
51
|
+
* @param source - Object to merge in (takes priority)
|
|
52
|
+
* @returns Merged object
|
|
53
|
+
*/
|
|
54
|
+
function deepMerge<T extends Record<string, any>>(target: T, source: DeepPartial<T>): T {
|
|
55
|
+
const result: Record<string, any> = { ...target };
|
|
56
|
+
|
|
57
|
+
for (const key in source) {
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
59
|
+
const sourceValue = source[key];
|
|
60
|
+
const targetValue = result[key];
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
sourceValue !== null &&
|
|
64
|
+
typeof sourceValue === 'object' &&
|
|
65
|
+
!Array.isArray(sourceValue) &&
|
|
66
|
+
targetValue !== null &&
|
|
67
|
+
typeof targetValue === 'object' &&
|
|
68
|
+
!Array.isArray(targetValue)
|
|
69
|
+
) {
|
|
70
|
+
result[key] = deepMerge(targetValue, sourceValue as DeepPartial<typeof targetValue>);
|
|
71
|
+
} else if (sourceValue !== undefined) {
|
|
72
|
+
result[key] = sourceValue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result as T;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extend a base theme with custom values.
|
|
82
|
+
* Performs a deep merge where extensions override base values.
|
|
83
|
+
*
|
|
84
|
+
* @param base - The base theme to extend (e.g., lightTheme or darkTheme)
|
|
85
|
+
* @param extensions - Partial theme object with values to override
|
|
86
|
+
* @returns Complete theme with extensions applied
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* import { lightTheme, extendTheme, createIntent } from '@idealyst/theme';
|
|
91
|
+
*
|
|
92
|
+
* const myTheme = extendTheme(lightTheme, {
|
|
93
|
+
* intents: {
|
|
94
|
+
* brand: createIntent('#8B5CF6', '#ffffff'),
|
|
95
|
+
* accent: createIntent('#F59E0B', '#ffffff'),
|
|
96
|
+
* },
|
|
97
|
+
* colors: {
|
|
98
|
+
* surface: {
|
|
99
|
+
* elevated: '#ffffff',
|
|
100
|
+
* },
|
|
101
|
+
* },
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export function extendTheme(base: Theme, extensions: DeepPartial<Theme>): Theme {
|
|
106
|
+
return deepMerge(base, extensions);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Generate a color palette (50-900 shades) from a base color.
|
|
111
|
+
* Uses the 500 shade as the base and interpolates lighter/darker variants.
|
|
112
|
+
*
|
|
113
|
+
* @param baseColor - The base color (hex format, e.g., '#8B5CF6')
|
|
114
|
+
* @returns Record of shade values (50-900)
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const brandPalette = generatePalette('#8B5CF6');
|
|
119
|
+
* // brandPalette[50] = light variant
|
|
120
|
+
* // brandPalette[500] = base color
|
|
121
|
+
* // brandPalette[900] = dark variant
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function generatePalette(baseColor: string): Record<Shade, ColorValue> {
|
|
125
|
+
const palette = {} as Record<Shade, ColorValue>;
|
|
126
|
+
const baseRgb = hexToRgb(baseColor);
|
|
127
|
+
const white: [number, number, number] = [255, 255, 255];
|
|
128
|
+
const black: [number, number, number] = [0, 0, 0];
|
|
129
|
+
const shades: Shade[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
|
130
|
+
|
|
131
|
+
for (const shade of shades) {
|
|
132
|
+
if (shade === 500) {
|
|
133
|
+
palette[shade] = baseColor;
|
|
134
|
+
} else if (shade < 500) {
|
|
135
|
+
// Lighter shades - interpolate towards white
|
|
136
|
+
const factor = (500 - shade) / 450;
|
|
137
|
+
const interpolated = interpolateColor(baseRgb, white, factor);
|
|
138
|
+
palette[shade] = rgbToHex(...interpolated);
|
|
139
|
+
} else {
|
|
140
|
+
// Darker shades - interpolate towards black
|
|
141
|
+
const factor = (shade - 500) / 400;
|
|
142
|
+
const interpolated = interpolateColor(baseRgb, black, factor);
|
|
143
|
+
palette[shade] = rgbToHex(...interpolated);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return palette;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Internal Color Utilities
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Convert hex color to RGB tuple.
|
|
156
|
+
*/
|
|
157
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
158
|
+
const cleanHex = hex.replace('#', '');
|
|
159
|
+
const r = parseInt(cleanHex.slice(0, 2), 16);
|
|
160
|
+
const g = parseInt(cleanHex.slice(2, 4), 16);
|
|
161
|
+
const b = parseInt(cleanHex.slice(4, 6), 16);
|
|
162
|
+
return [r, g, b];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Convert RGB tuple to hex color.
|
|
167
|
+
*/
|
|
168
|
+
function rgbToHex(r: number, g: number, b: number): string {
|
|
169
|
+
const toHex = (n: number) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, '0');
|
|
170
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Interpolate between two RGB colors.
|
|
175
|
+
*/
|
|
176
|
+
function interpolateColor(
|
|
177
|
+
color1: [number, number, number],
|
|
178
|
+
color2: [number, number, number],
|
|
179
|
+
factor: number
|
|
180
|
+
): [number, number, number] {
|
|
181
|
+
return [
|
|
182
|
+
color1[0] + (color2[0] - color1[0]) * factor,
|
|
183
|
+
color1[1] + (color2[1] - color1[1]) * factor,
|
|
184
|
+
color1[2] + (color2[2] - color1[2]) * factor,
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Lighten a color by a given factor.
|
|
190
|
+
*/
|
|
191
|
+
function lightenColor(hex: string, factor: number): string {
|
|
192
|
+
const rgb = hexToRgb(hex);
|
|
193
|
+
const white: [number, number, number] = [255, 255, 255];
|
|
194
|
+
const interpolated = interpolateColor(rgb, white, factor);
|
|
195
|
+
return rgbToHex(...interpolated);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Darken a color by a given factor.
|
|
200
|
+
*/
|
|
201
|
+
function darkenColor(hex: string, factor: number): string {
|
|
202
|
+
const rgb = hexToRgb(hex);
|
|
203
|
+
const black: [number, number, number] = [0, 0, 0];
|
|
204
|
+
const interpolated = interpolateColor(rgb, black, factor);
|
|
205
|
+
return rgbToHex(...interpolated);
|
|
206
|
+
}
|