@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.
@@ -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
- export * from './unistyles';
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
- export * from './styles';
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';