@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/src/builder.ts ADDED
@@ -0,0 +1,317 @@
1
+ import {
2
+ IntentValue,
3
+ ShadowValue,
4
+ InteractionConfig,
5
+ ColorValue,
6
+ Shade,
7
+ Typography,
8
+ TypographyValue,
9
+ ButtonSizeValue,
10
+ ChipSizeValue,
11
+ BadgeSizeValue,
12
+ IconSizeValue,
13
+ InputSizeValue,
14
+ RadioButtonSizeValue,
15
+ SelectSizeValue,
16
+ SliderSizeValue,
17
+ SwitchSizeValue,
18
+ TextAreaSizeValue,
19
+ AvatarSizeValue,
20
+ ProgressSizeValue,
21
+ AccordionSizeValue,
22
+ ActivityIndicatorSizeValue,
23
+ BreadcrumbSizeValue,
24
+ ListSizeValue,
25
+ MenuSizeValue,
26
+ TextSizeValue,
27
+ TabBarSizeValue,
28
+ TableSizeValue,
29
+ TooltipSizeValue,
30
+ ViewSizeValue,
31
+ } from './theme/structures';
32
+
33
+ /**
34
+ * Built theme structure - self-contained, no external type dependencies.
35
+ */
36
+ export type BuiltTheme<
37
+ TIntents extends string,
38
+ TRadii extends string,
39
+ TShadows extends string,
40
+ TPallet extends string,
41
+ TSurface extends string,
42
+ TText extends string,
43
+ TBorder extends string,
44
+ TSize extends string,
45
+ > = {
46
+ intents: Record<TIntents, IntentValue>;
47
+ radii: Record<TRadii, number>;
48
+ shadows: Record<TShadows, ShadowValue>;
49
+ colors: {
50
+ pallet: Record<TPallet, Record<Shade, ColorValue>>;
51
+ surface: Record<TSurface, ColorValue>;
52
+ text: Record<TText, ColorValue>;
53
+ border: Record<TBorder, ColorValue>;
54
+ };
55
+ sizes: {
56
+ button: Record<TSize, ButtonSizeValue>;
57
+ chip: Record<TSize, ChipSizeValue>;
58
+ badge: Record<TSize, BadgeSizeValue>;
59
+ icon: Record<TSize, IconSizeValue>;
60
+ input: Record<TSize, InputSizeValue>;
61
+ radioButton: Record<TSize, RadioButtonSizeValue>;
62
+ select: Record<TSize, SelectSizeValue>;
63
+ slider: Record<TSize, SliderSizeValue>;
64
+ switch: Record<TSize, SwitchSizeValue>;
65
+ textarea: Record<TSize, TextAreaSizeValue>;
66
+ avatar: Record<TSize, AvatarSizeValue>;
67
+ progress: Record<TSize, ProgressSizeValue>;
68
+ accordion: Record<TSize, AccordionSizeValue>;
69
+ activityIndicator: Record<TSize, ActivityIndicatorSizeValue>;
70
+ breadcrumb: Record<TSize, BreadcrumbSizeValue>;
71
+ list: Record<TSize, ListSizeValue>;
72
+ menu: Record<TSize, MenuSizeValue>;
73
+ text: Record<TSize, TextSizeValue>;
74
+ tabBar: Record<TSize, TabBarSizeValue>;
75
+ table: Record<TSize, TableSizeValue>;
76
+ tooltip: Record<TSize, TooltipSizeValue>;
77
+ view: Record<TSize, ViewSizeValue>;
78
+ typography: Record<Typography, TypographyValue>;
79
+ };
80
+ interaction: InteractionConfig;
81
+ };
82
+
83
+ /**
84
+ * Internal config type for building.
85
+ */
86
+ type ThemeConfig<
87
+ TIntents extends string,
88
+ TRadii extends string,
89
+ TShadows extends string,
90
+ TPallet extends string,
91
+ TSurface extends string,
92
+ TText extends string,
93
+ TBorder extends string,
94
+ TSize extends string,
95
+ > = BuiltTheme<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize>;
96
+
97
+ /**
98
+ * Fluent builder for creating themes with full TypeScript inference.
99
+ *
100
+ * Build a theme, then register it to get automatic type inference for all components:
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const myTheme = createTheme()
105
+ * .addIntent('primary', { primary: '#3b82f6', contrast: '#fff', light: '#bfdbfe', dark: '#1e40af' })
106
+ * .addIntent('brand', { primary: '#6366f1', contrast: '#fff', light: '#818cf8', dark: '#4f46e5' })
107
+ * .addRadius('sm', 4)
108
+ * .addRadius('full', 9999)
109
+ * .build();
110
+ *
111
+ * // Register the theme type
112
+ * declare module '@idealyst/theme' {
113
+ * interface RegisteredTheme {
114
+ * theme: typeof myTheme;
115
+ * }
116
+ * }
117
+ *
118
+ * // Now Intent = 'primary' | 'brand', Radius = 'sm' | 'full', etc.
119
+ * ```
120
+ */
121
+ export class ThemeBuilder<
122
+ TIntents extends string = never,
123
+ TRadii extends string = never,
124
+ TShadows extends string = never,
125
+ TPallet extends string = never,
126
+ TSurface extends string = never,
127
+ TText extends string = never,
128
+ TBorder extends string = never,
129
+ TSize extends string = never,
130
+ > {
131
+ private config: ThemeConfig<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize>;
132
+
133
+ constructor() {
134
+ this.config = {
135
+ intents: {} as any,
136
+ radii: {} as any,
137
+ shadows: {} as any,
138
+ colors: {
139
+ pallet: {} as any,
140
+ surface: {} as any,
141
+ text: {} as any,
142
+ border: {} as any,
143
+ },
144
+ sizes: {} as any,
145
+ interaction: {} as any,
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Add a custom intent to the theme.
151
+ */
152
+ addIntent<K extends string>(
153
+ name: K,
154
+ value: IntentValue
155
+ ): ThemeBuilder<TIntents | K, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize> {
156
+ const newBuilder = new ThemeBuilder<TIntents | K, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize>();
157
+ newBuilder.config = {
158
+ ...this.config,
159
+ intents: {
160
+ ...this.config.intents,
161
+ [name]: value,
162
+ } as any,
163
+ };
164
+ return newBuilder;
165
+ }
166
+
167
+ /**
168
+ * Add a custom border radius value.
169
+ */
170
+ addRadius<K extends string>(
171
+ name: K,
172
+ value: number
173
+ ): ThemeBuilder<TIntents, TRadii | K, TShadows, TPallet, TSurface, TText, TBorder, TSize> {
174
+ const newBuilder = new ThemeBuilder<TIntents, TRadii | K, TShadows, TPallet, TSurface, TText, TBorder, TSize>();
175
+ newBuilder.config = {
176
+ ...this.config,
177
+ radii: {
178
+ ...this.config.radii,
179
+ [name]: value,
180
+ } as any,
181
+ };
182
+ return newBuilder;
183
+ }
184
+
185
+ /**
186
+ * Add a custom shadow value.
187
+ */
188
+ addShadow<K extends string>(
189
+ name: K,
190
+ value: ShadowValue
191
+ ): ThemeBuilder<TIntents, TRadii, TShadows | K, TPallet, TSurface, TText, TBorder, TSize> {
192
+ const newBuilder = new ThemeBuilder<TIntents, TRadii, TShadows | K, TPallet, TSurface, TText, TBorder, TSize>();
193
+ newBuilder.config = {
194
+ ...this.config,
195
+ shadows: {
196
+ ...this.config.shadows,
197
+ [name]: value,
198
+ } as any,
199
+ };
200
+ return newBuilder;
201
+ }
202
+
203
+ /**
204
+ * Set the interaction configuration.
205
+ */
206
+ setInteraction(
207
+ interaction: InteractionConfig
208
+ ): ThemeBuilder<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize> {
209
+ const newBuilder = new ThemeBuilder<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize>();
210
+ newBuilder.config = {
211
+ ...this.config,
212
+ interaction,
213
+ };
214
+ return newBuilder;
215
+ }
216
+
217
+ /**
218
+ * Set the colors configuration.
219
+ */
220
+ setColors<
221
+ P extends string,
222
+ S extends string,
223
+ T extends string,
224
+ B extends string,
225
+ >(colors: {
226
+ pallet: Record<P, Record<Shade, ColorValue>>;
227
+ surface: Record<S, ColorValue>;
228
+ text: Record<T, ColorValue>;
229
+ border: Record<B, ColorValue>;
230
+ }): ThemeBuilder<TIntents, TRadii, TShadows, P, S, T, B, TSize> {
231
+ const newBuilder = new ThemeBuilder<TIntents, TRadii, TShadows, P, S, T, B, TSize>();
232
+ newBuilder.config = {
233
+ ...this.config,
234
+ colors,
235
+ } as any;
236
+ return newBuilder;
237
+ }
238
+
239
+ /**
240
+ * Set the sizes configuration.
241
+ */
242
+ setSizes<S extends string>(sizes: {
243
+ button: Record<S, ButtonSizeValue>;
244
+ chip: Record<S, ChipSizeValue>;
245
+ badge: Record<S, BadgeSizeValue>;
246
+ icon: Record<S, IconSizeValue>;
247
+ input: Record<S, InputSizeValue>;
248
+ radioButton: Record<S, RadioButtonSizeValue>;
249
+ select: Record<S, SelectSizeValue>;
250
+ slider: Record<S, SliderSizeValue>;
251
+ switch: Record<S, SwitchSizeValue>;
252
+ textarea: Record<S, TextAreaSizeValue>;
253
+ avatar: Record<S, AvatarSizeValue>;
254
+ progress: Record<S, ProgressSizeValue>;
255
+ accordion: Record<S, AccordionSizeValue>;
256
+ activityIndicator: Record<S, ActivityIndicatorSizeValue>;
257
+ breadcrumb: Record<S, BreadcrumbSizeValue>;
258
+ list: Record<S, ListSizeValue>;
259
+ menu: Record<S, MenuSizeValue>;
260
+ text: Record<S, TextSizeValue>;
261
+ tabBar: Record<S, TabBarSizeValue>;
262
+ table: Record<S, TableSizeValue>;
263
+ tooltip: Record<S, TooltipSizeValue>;
264
+ view: Record<S, ViewSizeValue>;
265
+ typography: Record<Typography, TypographyValue>;
266
+ }): ThemeBuilder<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, S> {
267
+ const newBuilder = new ThemeBuilder<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, S>();
268
+ newBuilder.config = {
269
+ ...this.config,
270
+ sizes,
271
+ } as any;
272
+ return newBuilder;
273
+ }
274
+
275
+ /**
276
+ * Build the final theme object.
277
+ */
278
+ build(): BuiltTheme<TIntents, TRadii, TShadows, TPallet, TSurface, TText, TBorder, TSize> {
279
+ return this.config;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Create a new theme builder.
285
+ */
286
+ export function createTheme(): ThemeBuilder {
287
+ return new ThemeBuilder();
288
+ }
289
+
290
+ /**
291
+ * Create a builder from an existing theme to add more values.
292
+ */
293
+ export function fromTheme<T extends BuiltTheme<any, any, any, any, any, any, any, any>>(
294
+ base: T
295
+ ): ThemeBuilder<
296
+ keyof T['intents'] & string,
297
+ keyof T['radii'] & string,
298
+ keyof T['shadows'] & string,
299
+ keyof T['colors']['pallet'] & string,
300
+ keyof T['colors']['surface'] & string,
301
+ keyof T['colors']['text'] & string,
302
+ keyof T['colors']['border'] & string,
303
+ keyof T['sizes']['button'] & string
304
+ > {
305
+ const builder = new ThemeBuilder<
306
+ keyof T['intents'] & string,
307
+ keyof T['radii'] & string,
308
+ keyof T['shadows'] & string,
309
+ keyof T['colors']['pallet'] & string,
310
+ keyof T['colors']['surface'] & string,
311
+ keyof T['colors']['text'] & string,
312
+ keyof T['colors']['border'] & string,
313
+ keyof T['sizes']['button'] & string
314
+ >();
315
+ (builder as any).config = { ...base };
316
+ return builder;
317
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Component Style Types Registry
3
+ *
4
+ * This module provides type definitions for component styles used with
5
+ * defineStyle, extendStyle, and overrideStyle.
6
+ *
7
+ * Components register their style types via module augmentation:
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // In Text.styles.tsx
12
+ * declare module '@idealyst/theme' {
13
+ * interface ComponentStyleRegistry {
14
+ * Text: TextStyleDef;
15
+ * }
16
+ * }
17
+ * ```
18
+ */
19
+
20
+ import type { TextStyle, ViewStyle } from 'react-native';
21
+
22
+ /**
23
+ * Registry interface that components augment to register their style types.
24
+ * This enables type-safe extendStyle and overrideStyle calls.
25
+ */
26
+ export interface ComponentStyleRegistry {
27
+ // Components augment this interface to add their style types
28
+ // Example: Text: { text: (params: TextStyleParams) => TextStyleObject }
29
+ }
30
+
31
+ /**
32
+ * Get the style definition type for a component.
33
+ * Returns the registered type or a loose Record type for unregistered components.
34
+ */
35
+ export type ComponentStyleDef<K extends string> = K extends keyof ComponentStyleRegistry
36
+ ? ComponentStyleRegistry[K]
37
+ : Record<string, any>;
38
+
39
+ /**
40
+ * Deep partial type that works with functions.
41
+ * For style functions, preserves the function signature but makes the return type partial.
42
+ */
43
+ export type DeepPartialStyle<T> = T extends (...args: infer A) => infer R
44
+ ? (...args: A) => DeepPartialStyle<R>
45
+ : T extends object
46
+ ? { [K in keyof T]?: DeepPartialStyle<T[K]> }
47
+ : T;
48
+
49
+ /**
50
+ * Style definition for extendStyle - requires functions with same params as base.
51
+ * All style properties must be functions to access dynamic params.
52
+ */
53
+ export type ExtendStyleDef<K extends string> = DeepPartialStyle<ComponentStyleDef<K>>;
54
+
55
+ /**
56
+ * Style definition for overrideStyle - requires full implementation with functions.
57
+ */
58
+ export type OverrideStyleDef<K extends string> = ComponentStyleDef<K>;
59
+
60
+ /**
61
+ * Helper to extract the params type from a dynamic style function.
62
+ * Use this to type your extension functions.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * type TextParams = StyleParams<TextStyleDef['text']>;
67
+ * // TextParams = { color?: TextColorVariant }
68
+ * ```
69
+ */
70
+ export type StyleParams<T> = T extends (params: infer P) => any ? P : never;
71
+
72
+ // =============================================================================
73
+ // Common Style Types
74
+ // =============================================================================
75
+
76
+ /**
77
+ * Base style object with optional variants and compound variants.
78
+ */
79
+ export interface StyleWithVariants<TVariants extends Record<string, any> = Record<string, any>> {
80
+ variants?: {
81
+ [K in keyof TVariants]?: {
82
+ [V in TVariants[K] extends string | boolean ? TVariants[K] : string]?: ViewStyle | TextStyle;
83
+ };
84
+ };
85
+ compoundVariants?: Array<{
86
+ [K in keyof TVariants]?: TVariants[K];
87
+ } & { styles: ViewStyle | TextStyle }>;
88
+ }
89
+
90
+ /**
91
+ * Dynamic style function type.
92
+ */
93
+ export type DynamicStyleFn<TParams, TStyle> = (params: TParams) => TStyle;
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Idealyst Style Generator CLI
4
+ *
5
+ * Reads idealyst.config.ts and generates flat Unistyles-compatible style files.
6
+ *
7
+ * Usage:
8
+ * npx ts-node packages/theme/src/config/cli.ts [config-path] [output-dir]
9
+ *
10
+ * Defaults:
11
+ * config-path: ./idealyst.config.ts
12
+ * output-dir: ./generated
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import * as path from 'path';
17
+
18
+ // Dynamic import for ESM compatibility
19
+ async function main() {
20
+ const args = process.argv.slice(2);
21
+ const configPath = args[0] || './idealyst.config.ts';
22
+ const outputDir = args[1] || './generated';
23
+
24
+ console.log('🎨 Idealyst Style Generator');
25
+ console.log(` Config: ${configPath}`);
26
+ console.log(` Output: ${outputDir}`);
27
+ console.log('');
28
+
29
+ // Resolve paths
30
+ const resolvedConfigPath = path.resolve(process.cwd(), configPath);
31
+ const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
32
+
33
+ // Check config exists
34
+ if (!fs.existsSync(resolvedConfigPath)) {
35
+ console.error(`❌ Config file not found: ${resolvedConfigPath}`);
36
+ console.error('');
37
+ console.error('Create an idealyst.config.ts file with your theme configuration.');
38
+ console.error('See the documentation for examples.');
39
+ process.exit(1);
40
+ }
41
+
42
+ // Import config dynamically
43
+ // Note: This requires ts-node or a pre-compiled config
44
+ let config;
45
+ try {
46
+ // Try to import the config
47
+ const configModule = await import(resolvedConfigPath);
48
+ config = configModule.default || configModule;
49
+ } catch (err) {
50
+ console.error(`❌ Failed to load config: ${err}`);
51
+ console.error('');
52
+ console.error('Make sure your config file:');
53
+ console.error(' 1. Is a valid TypeScript/JavaScript file');
54
+ console.error(' 2. Uses export default or named exports');
55
+ console.error(' 3. Has all required theme properties');
56
+ process.exit(1);
57
+ }
58
+
59
+ // Validate config
60
+ if (!config.themes || !config.themes.light || !config.themes.dark) {
61
+ console.error('❌ Invalid config: themes.light and themes.dark are required');
62
+ process.exit(1);
63
+ }
64
+
65
+ // Import generator
66
+ const { generateStyles } = await import('./generator');
67
+
68
+ // Generate styles
69
+ console.log('⚙️ Generating styles...');
70
+ const files = generateStyles(config);
71
+
72
+ // Ensure output directory exists
73
+ if (!fs.existsSync(resolvedOutputDir)) {
74
+ fs.mkdirSync(resolvedOutputDir, { recursive: true });
75
+ }
76
+
77
+ // Write files
78
+ for (const [filename, content] of Object.entries(files)) {
79
+ const filePath = path.join(resolvedOutputDir, filename);
80
+ fs.writeFileSync(filePath, content, 'utf-8');
81
+ console.log(` ✓ ${filename}`);
82
+ }
83
+
84
+ console.log('');
85
+ console.log(`✅ Generated ${Object.keys(files).length} files in ${outputDir}`);
86
+ console.log('');
87
+ console.log('Next steps:');
88
+ console.log(' 1. Import the generated unistyles.generated.ts in your app entry');
89
+ console.log(' 2. Update components to import from generated style files');
90
+ }
91
+
92
+ main().catch((err) => {
93
+ console.error('Fatal error:', err);
94
+ process.exit(1);
95
+ });