@idealyst/theme 1.2.30 → 1.2.32

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/theme",
3
- "version": "1.2.30",
3
+ "version": "1.2.32",
4
4
  "description": "Theming system for Idealyst Framework",
5
5
  "readme": "README.md",
6
6
  "main": "src/index.ts",
@@ -63,7 +63,7 @@
63
63
  "publish:npm": "npm publish"
64
64
  },
65
65
  "dependencies": {
66
- "@idealyst/tooling": "^1.2.3"
66
+ "@idealyst/tooling": "^1.2.30"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "react-native-unistyles": ">=3.0.0"
@@ -210,6 +210,14 @@ function expandIterators(t, callback, themeParam, keys, verbose, expandedVariant
210
210
  }
211
211
  }
212
212
 
213
+ // Look for compoundVariants: [ { type: 'filled', selected: true, styles: { ... } }, ... ]
214
+ if (t.isObjectProperty(node) && t.isIdentifier(node.key, { name: 'compoundVariants' })) {
215
+ if (t.isArrayExpression(node.value)) {
216
+ const expanded = expandCompoundVariantsArray(t, node.value, themeParam, keys, verbose, expandedVariants);
217
+ return t.objectProperty(node.key, expanded);
218
+ }
219
+ }
220
+
213
221
  if (t.isObjectExpression(node)) {
214
222
  return t.objectExpression(
215
223
  node.properties.map(prop => processNode(prop, depth + 1))
@@ -352,6 +360,112 @@ function expandVariantsObject(t, variantsObj, themeParam, keys, verbose, expande
352
360
  return t.objectExpression(newProperties);
353
361
  }
354
362
 
363
+ /**
364
+ * Expand $iterator patterns in compoundVariants arrays.
365
+ *
366
+ * Input:
367
+ * compoundVariants: [
368
+ * { type: 'filled', selected: true, styles: { backgroundColor: theme.$intents.contrast } }
369
+ * ]
370
+ *
371
+ * Output (for intents = ['primary', 'success', 'danger', ...]):
372
+ * compoundVariants: [
373
+ * { type: 'filled', selected: true, intent: 'primary', styles: { backgroundColor: theme.intents.primary.contrast } },
374
+ * { type: 'filled', selected: true, intent: 'success', styles: { backgroundColor: theme.intents.success.contrast } },
375
+ * { type: 'filled', selected: true, intent: 'danger', styles: { backgroundColor: theme.intents.danger.contrast } },
376
+ * ...
377
+ * ]
378
+ */
379
+ function expandCompoundVariantsArray(t, arrayNode, themeParam, keys, verbose, expandedVariants) {
380
+ const newElements = [];
381
+
382
+ verbose(` expandCompoundVariantsArray: processing ${arrayNode.elements?.length || 0} compound variants`);
383
+
384
+ for (const element of arrayNode.elements) {
385
+ if (!t.isObjectExpression(element)) {
386
+ newElements.push(element);
387
+ continue;
388
+ }
389
+
390
+ // Find the 'styles' property in this compound variant entry
391
+ let stylesProperty = null;
392
+ const otherProperties = [];
393
+
394
+ for (const prop of element.properties) {
395
+ if (t.isObjectProperty(prop)) {
396
+ const keyName = t.isIdentifier(prop.key) ? prop.key.name :
397
+ t.isStringLiteral(prop.key) ? prop.key.value : null;
398
+ if (keyName === 'styles') {
399
+ stylesProperty = prop;
400
+ } else {
401
+ otherProperties.push(prop);
402
+ }
403
+ } else {
404
+ otherProperties.push(prop);
405
+ }
406
+ }
407
+
408
+ if (!stylesProperty) {
409
+ // No styles property, keep as-is
410
+ newElements.push(element);
411
+ continue;
412
+ }
413
+
414
+ // Check if the styles object contains $iterator patterns
415
+ const iteratorInfo = findIteratorPattern(t, stylesProperty.value, themeParam);
416
+
417
+ if (!iteratorInfo) {
418
+ // No $iterator pattern, keep as-is
419
+ newElements.push(element);
420
+ continue;
421
+ }
422
+
423
+ verbose(` Found $iterator in compoundVariant styles: ${iteratorInfo.type}`);
424
+
425
+ // Get keys to expand
426
+ let keysToExpand = [];
427
+ let variantKeyName = 'intent'; // Default for intents
428
+
429
+ if (iteratorInfo.type === 'intents') {
430
+ keysToExpand = keys?.intents || [];
431
+ variantKeyName = 'intent';
432
+ } else if (iteratorInfo.type === 'typography') {
433
+ keysToExpand = keys?.typography || [];
434
+ variantKeyName = 'typography';
435
+ } else if (iteratorInfo.type === 'sizes' && iteratorInfo.componentName) {
436
+ keysToExpand = keys?.sizes?.[iteratorInfo.componentName] || [];
437
+ variantKeyName = 'size';
438
+ }
439
+
440
+ if (keysToExpand.length === 0) {
441
+ // No keys to expand, keep as-is
442
+ newElements.push(element);
443
+ continue;
444
+ }
445
+
446
+ verbose(` Expanding compoundVariant for ${keysToExpand.length} ${variantKeyName} keys`);
447
+
448
+ // Expand this compound variant for each key
449
+ for (const key of keysToExpand) {
450
+ // Replace $iterator refs in the styles
451
+ const expandedStyles = replaceIteratorRefs(t, stylesProperty.value, themeParam, iteratorInfo, key);
452
+
453
+ // Create new compound variant entry with the variant key added as a condition
454
+ const newProps = [
455
+ ...otherProperties.map(p => t.cloneDeep(p)),
456
+ t.objectProperty(t.identifier(variantKeyName), t.stringLiteral(key)),
457
+ t.objectProperty(t.identifier('styles'), expandedStyles),
458
+ ];
459
+
460
+ newElements.push(t.objectExpression(newProps));
461
+ }
462
+
463
+ expandedVariants.push({ variant: 'compoundVariants', iterator: iteratorInfo.type });
464
+ }
465
+
466
+ return t.arrayExpression(newElements);
467
+ }
468
+
355
469
  function findIteratorPattern(t, node, themeParam, debugLog = () => {}) {
356
470
  let result = null;
357
471
 
@@ -578,8 +692,6 @@ module.exports = function idealystStylesPlugin({ types: t }) {
578
692
  }
579
693
  }
580
694
 
581
- // Plugin initialization logged in debug mode only
582
-
583
695
  return {
584
696
  name: 'idealyst-styles',
585
697
 
@@ -16,6 +16,31 @@ const fs = require('fs');
16
16
  let themeKeys = null;
17
17
  let themeLoadAttempted = false;
18
18
 
19
+ // Global aliases configuration (set via plugin options)
20
+ let packageAliases = {};
21
+
22
+ /**
23
+ * Resolve an import source using configured aliases (static version for use outside AST traversal).
24
+ * Returns the resolved path or null if no alias matches.
25
+ */
26
+ function resolveWithAliasesStatic(source, fromDir) {
27
+ for (const [aliasPrefix, aliasPath] of Object.entries(packageAliases)) {
28
+ if (source === aliasPrefix || source.startsWith(aliasPrefix + '/')) {
29
+ // Replace the alias prefix with the actual path
30
+ const remainder = source.slice(aliasPrefix.length);
31
+ let resolved = aliasPath + remainder;
32
+
33
+ // If aliasPath is relative, resolve from fromDir
34
+ if (!nodePath.isAbsolute(resolved)) {
35
+ resolved = nodePath.resolve(fromDir, resolved);
36
+ }
37
+
38
+ return resolved;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+
19
44
  /**
20
45
  * Extract theme keys by statically analyzing the theme file's AST.
21
46
  */
@@ -99,6 +124,29 @@ function extractThemeKeysFromAST(themeFilePath, babelTypes, verboseMode) {
99
124
  return { calls, baseThemeVar: null };
100
125
  }
101
126
 
127
+ /**
128
+ * Resolve an import source using configured aliases.
129
+ * Returns the resolved path or null if no alias matches.
130
+ */
131
+ function resolveWithAliases(source, fromDir) {
132
+ for (const [aliasPrefix, aliasPath] of Object.entries(packageAliases)) {
133
+ if (source === aliasPrefix || source.startsWith(aliasPrefix + '/')) {
134
+ // Replace the alias prefix with the actual path
135
+ const remainder = source.slice(aliasPrefix.length);
136
+ let resolved = aliasPath + remainder;
137
+
138
+ // If aliasPath is relative, resolve from fromDir
139
+ if (!nodePath.isAbsolute(resolved)) {
140
+ resolved = nodePath.resolve(fromDir, resolved);
141
+ }
142
+
143
+ log('Resolved alias:', source, '->', resolved);
144
+ return resolved;
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+
102
150
  /**
103
151
  * Resolve and analyze a base theme from an import.
104
152
  */
@@ -122,42 +170,75 @@ function extractThemeKeysFromAST(themeFilePath, babelTypes, verboseMode) {
122
170
  } else {
123
171
  const packageDir = nodePath.dirname(themeFilePath);
124
172
 
125
- // Determine which theme file to look for based on variable name
126
- const themeFileName = varName.includes('dark') ? 'darkTheme.ts' : 'lightTheme.ts';
127
- let possiblePaths = [];
128
-
129
- if (importInfo.source === '@idealyst/theme') {
130
- possiblePaths = [
131
- // Symlinked packages at root level
132
- `/idealyst-packages/theme/src/${themeFileName}`,
133
- // Standard node_modules
134
- nodePath.resolve(packageDir, `node_modules/@idealyst/theme/src/${themeFileName}`),
135
- // Monorepo structure - walk up to find packages dir
136
- nodePath.resolve(packageDir, `../theme/src/${themeFileName}`),
137
- nodePath.resolve(packageDir, `../../theme/src/${themeFileName}`),
138
- nodePath.resolve(packageDir, `../../../theme/src/${themeFileName}`),
139
- nodePath.resolve(packageDir, `../../packages/theme/src/${themeFileName}`),
140
- nodePath.resolve(packageDir, `../../../packages/theme/src/${themeFileName}`),
141
- // This plugin's own package location
142
- nodePath.resolve(__dirname, `../${themeFileName}`),
173
+ // First, try to resolve using configured aliases
174
+ const aliasResolved = resolveWithAliases(importInfo.source, packageDir);
175
+ if (aliasResolved) {
176
+ // Determine which theme file to look for based on variable name
177
+ const themeFileName = varName.includes('dark') ? 'darkTheme.ts' : 'lightTheme.ts';
178
+
179
+ // Check if alias points to a directory or a specific file
180
+ let possiblePaths = [
181
+ aliasResolved,
182
+ nodePath.join(aliasResolved, 'src', themeFileName),
183
+ nodePath.join(aliasResolved, themeFileName),
143
184
  ];
144
185
 
145
- log('Looking for base theme in:', possiblePaths);
186
+ // Add .ts extension if needed
187
+ possiblePaths = possiblePaths.flatMap(p => {
188
+ if (p.endsWith('.ts') || p.endsWith('.tsx')) return [p];
189
+ return [p, p + '.ts', p + '.tsx'];
190
+ });
191
+
192
+ log('Looking for aliased theme in:', possiblePaths);
146
193
 
147
194
  for (const p of possiblePaths) {
148
195
  if (fs.existsSync(p)) {
149
196
  baseThemePath = p;
150
- log('Found base theme at:', p);
197
+ log('Found aliased theme at:', p);
151
198
  break;
152
199
  }
153
200
  }
154
201
  }
155
202
 
203
+ // If no alias match, use default resolution for @idealyst/theme
156
204
  if (!baseThemePath) {
157
- log('Could not resolve base theme path for:', importInfo.source);
158
- if (possiblePaths.length > 0) {
159
- log('Searched paths:', possiblePaths);
205
+ // Determine which theme file to look for based on variable name
206
+ const themeFileName = varName.includes('dark') ? 'darkTheme.ts' : 'lightTheme.ts';
207
+ let possiblePaths = [];
208
+
209
+ if (importInfo.source === '@idealyst/theme') {
210
+ possiblePaths = [
211
+ // Symlinked packages at root level
212
+ `/idealyst-packages/theme/src/${themeFileName}`,
213
+ // Standard node_modules
214
+ nodePath.resolve(packageDir, `node_modules/@idealyst/theme/src/${themeFileName}`),
215
+ // Monorepo structure - walk up to find packages dir
216
+ nodePath.resolve(packageDir, `../theme/src/${themeFileName}`),
217
+ nodePath.resolve(packageDir, `../../theme/src/${themeFileName}`),
218
+ nodePath.resolve(packageDir, `../../../theme/src/${themeFileName}`),
219
+ nodePath.resolve(packageDir, `../../packages/theme/src/${themeFileName}`),
220
+ nodePath.resolve(packageDir, `../../../packages/theme/src/${themeFileName}`),
221
+ nodePath.resolve(packageDir, `../../../../packages/theme/src/${themeFileName}`),
222
+ nodePath.resolve(packageDir, `../../../../../packages/theme/src/${themeFileName}`),
223
+ nodePath.resolve(packageDir, `../../../../../../packages/theme/src/${themeFileName}`),
224
+ // This plugin's own package location
225
+ nodePath.resolve(__dirname, `../${themeFileName}`),
226
+ ];
227
+
228
+ log('Looking for base theme in:', possiblePaths);
229
+
230
+ for (const p of possiblePaths) {
231
+ if (fs.existsSync(p)) {
232
+ baseThemePath = p;
233
+ log('Found base theme at:', p);
234
+ break;
235
+ }
236
+ }
160
237
  }
238
+ }
239
+
240
+ if (!baseThemePath) {
241
+ log('Could not resolve base theme path for:', importInfo.source);
161
242
  return;
162
243
  }
163
244
  }
@@ -366,11 +447,23 @@ function extractThemeKeysFromAST(themeFilePath, babelTypes, verboseMode) {
366
447
  *
367
448
  * REQUIRED Options:
368
449
  * - themePath: Path to the consumer's theme file (e.g., './src/theme/styles.ts')
450
+ *
451
+ * OPTIONAL Options:
452
+ * - aliases: Object mapping package prefixes to paths for resolution
453
+ * Example: { '@idealyst/theme': '/path/to/packages/theme' }
369
454
  */
370
455
  function loadThemeKeys(opts, rootDir, babelTypes, verboseMode) {
371
456
  if (themeLoadAttempted) return themeKeys;
372
457
  themeLoadAttempted = true;
373
458
 
459
+ // Set up package aliases for resolution
460
+ if (opts.aliases && typeof opts.aliases === 'object') {
461
+ packageAliases = opts.aliases;
462
+ if (verboseMode) {
463
+ console.log('[idealyst-plugin] Configured aliases:', packageAliases);
464
+ }
465
+ }
466
+
374
467
  const themePath = opts.themePath;
375
468
 
376
469
  if (!themePath) {
@@ -381,9 +474,16 @@ function loadThemeKeys(opts, rootDir, babelTypes, verboseMode) {
381
474
  );
382
475
  }
383
476
 
384
- const resolvedPath = themePath.startsWith('.')
385
- ? nodePath.resolve(rootDir, themePath)
386
- : require.resolve(themePath, { paths: [rootDir] });
477
+ // First try to resolve using aliases
478
+ let resolvedPath;
479
+ const aliasResolved = resolveWithAliasesStatic(themePath, rootDir);
480
+ if (aliasResolved && fs.existsSync(aliasResolved)) {
481
+ resolvedPath = aliasResolved;
482
+ } else {
483
+ resolvedPath = themePath.startsWith('.')
484
+ ? nodePath.resolve(rootDir, themePath)
485
+ : require.resolve(themePath, { paths: [rootDir] });
486
+ }
387
487
 
388
488
  if (verboseMode) {
389
489
  console.log('[idealyst-plugin] Analyzing theme file:', resolvedPath);
package/src/builder.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  Typography,
8
8
  TypographyValue,
9
9
  ButtonSizeValue,
10
+ IconButtonSizeValue,
10
11
  ChipSizeValue,
11
12
  BadgeSizeValue,
12
13
  IconSizeValue,
@@ -56,6 +57,7 @@ export type BuiltTheme<
56
57
  };
57
58
  sizes: {
58
59
  button: Record<TSize, ButtonSizeValue>;
60
+ iconButton: Record<TSize, IconButtonSizeValue>;
59
61
  chip: Record<TSize, ChipSizeValue>;
60
62
  badge: Record<TSize, BadgeSizeValue>;
61
63
  icon: Record<TSize, IconSizeValue>;
@@ -248,6 +250,7 @@ export class ThemeBuilder<
248
250
  */
249
251
  setSizes<S extends string>(sizes: {
250
252
  button: Record<S, ButtonSizeValue>;
253
+ iconButton: Record<S, IconButtonSizeValue>;
251
254
  chip: Record<S, ChipSizeValue>;
252
255
  badge: Record<S, BadgeSizeValue>;
253
256
  icon: Record<S, IconSizeValue>;
package/src/lightTheme.ts CHANGED
@@ -120,6 +120,13 @@ export const lightTheme = createTheme()
120
120
  lg: { paddingVertical: 10, paddingHorizontal: 20, minHeight: 48, fontSize: 18, lineHeight: 28, iconSize: 18 },
121
121
  xl: { paddingVertical: 12, paddingHorizontal: 24, minHeight: 56, fontSize: 20, lineHeight: 32, iconSize: 20 },
122
122
  },
123
+ iconButton: {
124
+ xs: { size: 24, iconSize: 12 },
125
+ sm: { size: 32, iconSize: 14 },
126
+ md: { size: 40, iconSize: 16 },
127
+ lg: { size: 48, iconSize: 18 },
128
+ xl: { size: 56, iconSize: 20 },
129
+ },
123
130
  chip: {
124
131
  xs: { paddingVertical: 1, paddingHorizontal: 6, minHeight: 16, borderRadius: 999, fontSize: 10, lineHeight: 12, iconSize: 10 },
125
132
  sm: { paddingVertical: 2, paddingHorizontal: 8, minHeight: 20, borderRadius: 999, fontSize: 11, lineHeight: 14, iconSize: 12 },
@@ -22,6 +22,7 @@ export interface DefaultTheme {
22
22
  chip: Record<string, ChipSizeValue>;
23
23
  badge: Record<string, BadgeSizeValue>;
24
24
  icon: Record<string, IconSizeValue>;
25
+ iconButton: Record<string, IconSizeValue>;
25
26
  input: Record<string, InputSizeValue>;
26
27
  radioButton: Record<string, RadioButtonSizeValue>;
27
28
  select: Record<string, SelectSizeValue>;
@@ -98,6 +98,11 @@ export type ButtonSizeValue = {
98
98
  iconSize: SizeValue;
99
99
  };
100
100
 
101
+ export type IconButtonSizeValue = {
102
+ size: SizeValue;
103
+ iconSize: SizeValue;
104
+ };
105
+
101
106
  export type ChipSizeValue = {
102
107
  paddingVertical: SizeValue;
103
108
  paddingHorizontal: SizeValue;