@idealyst/theme 1.1.6 → 1.1.8
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 +29 -1
- package/src/babel/index.ts +9 -0
- package/src/babel/plugin.js +871 -0
- package/src/babel/plugin.ts +187 -0
- package/src/babel/runtime.ts +94 -0
- package/src/babel/theme-analyzer.js +357 -0
- package/src/builder.ts +317 -0
- package/src/componentStyles.ts +93 -0
- package/src/config/cli.ts +95 -0
- package/src/config/generator.ts +817 -0
- package/src/config/index.ts +10 -0
- package/src/config/types.ts +112 -0
- package/src/darkTheme.ts +186 -943
- package/src/extensions.ts +110 -0
- package/src/helpers.ts +206 -0
- package/src/index.ts +18 -4
- package/src/lightTheme.ts +286 -859
- package/src/styleBuilder.ts +108 -0
- package/src/theme/color.ts +36 -15
- package/src/theme/extensions.ts +99 -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 +8 -24
- package/src/variants/color.ts +3 -5
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extensions runtime for Idealyst theme.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the runtime helper used by the Babel plugin
|
|
5
|
+
* to merge extensions into component styles.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { __withExtension } from './babel/runtime';
|
|
9
|
+
|
|
10
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
11
|
+
import type { Theme } from './theme';
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// ThemeStyleWrapper - Type utility for style definitions
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
// Check if T is an object we can iterate over (not array, not function, not primitive)
|
|
18
|
+
type IsIterableObject<T> = T extends object
|
|
19
|
+
? T extends any[]
|
|
20
|
+
? false
|
|
21
|
+
: T extends (...args: any[]) => any
|
|
22
|
+
? false
|
|
23
|
+
: true
|
|
24
|
+
: false;
|
|
25
|
+
|
|
26
|
+
// Get value type from an object - works for both Record and named-key objects
|
|
27
|
+
type ObjectValue<T> = T extends { [key: string]: infer V }
|
|
28
|
+
? V
|
|
29
|
+
: T[keyof T];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Wraps a Theme type to add $iterator properties for any object types.
|
|
33
|
+
*
|
|
34
|
+
* For each property that is an object (dictionary-like), adds a sibling $propertyName
|
|
35
|
+
* that represents the value type. This enables Babel to expand iterations
|
|
36
|
+
* over theme values (like intents, typography sizes, etc.)
|
|
37
|
+
*
|
|
38
|
+
* Works with:
|
|
39
|
+
* - Record<string, V> types
|
|
40
|
+
* - Objects with named keys like { primary: V, success: V, ... }
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { ThemeStyleWrapper } from '@idealyst/theme/extensions';
|
|
45
|
+
* import type { Theme as BaseTheme } from '@idealyst/theme';
|
|
46
|
+
*
|
|
47
|
+
* type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
48
|
+
*
|
|
49
|
+
* // Now you can use $iterator properties:
|
|
50
|
+
* // theme.$intents.primary -> Babel expands to all intent keys
|
|
51
|
+
* // theme.sizes.$typography.fontSize -> Babel expands to all typography keys
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export type ThemeStyleWrapper<T> = T extends object
|
|
55
|
+
? {
|
|
56
|
+
[K in keyof T]: ThemeStyleWrapper<T[K]>
|
|
57
|
+
} & {
|
|
58
|
+
[K in keyof T as IsIterableObject<T[K]> extends true ? `$${K & string}` : never]:
|
|
59
|
+
ThemeStyleWrapper<ObjectValue<T[K]>>
|
|
60
|
+
}
|
|
61
|
+
: T;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Theme type with $iterator properties for use in style definitions.
|
|
65
|
+
* Use this instead of the base Theme type when using $iterator syntax.
|
|
66
|
+
*/
|
|
67
|
+
export type IteratorTheme = ThemeStyleWrapper<Theme>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Style callback type that accepts IteratorTheme.
|
|
71
|
+
* The variants structure is relaxed to accept $iterator patterns.
|
|
72
|
+
*/
|
|
73
|
+
export type IteratorStyleCallback<TStyles> = (theme: IteratorTheme) => TStyles;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a stylesheet with $iterator expansion support.
|
|
77
|
+
*
|
|
78
|
+
* This is a typed wrapper around StyleSheet.create that:
|
|
79
|
+
* 1. Accepts the IteratorTheme type (with $intents, $sizes, etc.)
|
|
80
|
+
* 2. Allows relaxed variant structures for $iterator patterns
|
|
81
|
+
*
|
|
82
|
+
* The Babel plugin will expand $iterator patterns before Unistyles processes the styles.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { createIteratorStyles } from '@idealyst/theme';
|
|
87
|
+
*
|
|
88
|
+
* export const styles = createIteratorStyles((theme) => ({
|
|
89
|
+
* box: {
|
|
90
|
+
* borderRadius: theme.radii.md,
|
|
91
|
+
* variants: {
|
|
92
|
+
* intent: {
|
|
93
|
+
* backgroundColor: theme.$intents.light,
|
|
94
|
+
* borderColor: theme.$intents.primary,
|
|
95
|
+
* },
|
|
96
|
+
* size: {
|
|
97
|
+
* padding: theme.sizes.$button.paddingVertical,
|
|
98
|
+
* },
|
|
99
|
+
* },
|
|
100
|
+
* },
|
|
101
|
+
* }));
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function createIteratorStyles<TStyles extends Record<string, any>>(
|
|
105
|
+
callback: IteratorStyleCallback<TStyles>
|
|
106
|
+
): ReturnType<typeof StyleSheet.create> {
|
|
107
|
+
// This function is transformed by Babel - it sees StyleSheet.create and processes it
|
|
108
|
+
// The callback is passed through with $iterator patterns expanded
|
|
109
|
+
return StyleSheet.create(callback as any);
|
|
110
|
+
}
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
export * from './lightTheme';
|
|
3
|
-
export * from './darkTheme';
|
|
1
|
+
// Core types first (no dependencies)
|
|
4
2
|
export * from './theme';
|
|
5
3
|
export * from './variants';
|
|
6
4
|
export * from './components';
|
|
7
|
-
|
|
5
|
+
|
|
6
|
+
// Builder (depends on theme types)
|
|
7
|
+
export * from './builder';
|
|
8
|
+
|
|
9
|
+
// Themes (depend on builder)
|
|
10
|
+
export * from './lightTheme';
|
|
11
|
+
export * from './darkTheme';
|
|
12
|
+
|
|
13
|
+
// Unistyles declaration (depends on theme)
|
|
14
|
+
export * from './unistyles';
|
|
15
|
+
|
|
16
|
+
// Helpers and styles
|
|
17
|
+
export * from './styles';
|
|
18
|
+
export * from './helpers';
|
|
19
|
+
export * from './styleBuilder';
|
|
20
|
+
export * from './extensions';
|
|
21
|
+
export * from './componentStyles';
|