scss-variable-extractor 1.6.6 → 2.0.1
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/README.md +230 -14
- package/THEME-GUIDE.md +116 -4
- package/bin/cli.js +101 -27
- package/package.json +1 -1
- package/src/theme-utils.js +200 -0
- package/test/color-extraction.test.js +166 -0
package/README.md
CHANGED
|
@@ -43,54 +43,154 @@ cd scss-variable-extractor
|
|
|
43
43
|
npm install
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## ⚡ Minimal Example
|
|
47
|
+
|
|
48
|
+
**Got an Angular project? Generate a theme in one command:**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd my-angular-app
|
|
52
|
+
npx scss-extract generate-themes --analyze
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
That's it! The tool:
|
|
56
|
+
|
|
57
|
+
- ✅ Finds your Angular project from `angular.json`
|
|
58
|
+
- ✅ Scans your SCSS files
|
|
59
|
+
- ✅ Extracts your app's actual colors
|
|
60
|
+
- ✅ Generates complete dark/light theme structure
|
|
61
|
+
- ✅ Places files in the right location
|
|
62
|
+
|
|
63
|
+
No configuration needed! 🎉
|
|
64
|
+
|
|
46
65
|
## Quick Start
|
|
47
66
|
|
|
48
|
-
>
|
|
67
|
+
> **� Zero Config:** If you have an `angular.json` file, just run the commands without any arguments - the tool automatically detects your project structure, source directories, and output paths!
|
|
49
68
|
|
|
50
69
|
### 1. Analyze (Dry Run)
|
|
51
70
|
|
|
52
71
|
See what values would be extracted without modifying any files:
|
|
53
72
|
|
|
54
73
|
```bash
|
|
55
|
-
#
|
|
74
|
+
# Auto-detects everything from angular.json ✨
|
|
56
75
|
npx scss-extract analyze
|
|
57
76
|
|
|
58
77
|
# Or specify path manually
|
|
59
78
|
npx scss-extract analyze ./apps/subapp/src --threshold 2
|
|
60
79
|
```
|
|
61
80
|
|
|
62
|
-
### 2. Generate
|
|
81
|
+
### 2. Generate Theme Structure (Most Used!)
|
|
82
|
+
|
|
83
|
+
Generate a complete dark/light theme with colors extracted from your app:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Auto-detects project and extracts YOUR colors! ✨
|
|
87
|
+
npx scss-extract generate-themes --analyze
|
|
88
|
+
|
|
89
|
+
# Without color extraction (uses Material defaults)
|
|
90
|
+
npx scss-extract generate-themes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Generate Variables File
|
|
63
94
|
|
|
64
95
|
Create a `_variables.scss` file with extracted variables:
|
|
65
96
|
|
|
66
97
|
```bash
|
|
67
|
-
#
|
|
98
|
+
# Auto-detects project ✨
|
|
68
99
|
npx scss-extract generate
|
|
69
100
|
|
|
70
101
|
# Or specify paths manually
|
|
71
102
|
npx scss-extract generate ./apps/subapp/src --output ./libs/styles/_variables.scss
|
|
72
103
|
```
|
|
73
104
|
|
|
74
|
-
###
|
|
105
|
+
### 4. Full Refactoring
|
|
75
106
|
|
|
76
107
|
Generate variables file AND replace hardcoded values in all SCSS files:
|
|
77
108
|
|
|
78
109
|
```bash
|
|
110
|
+
# Auto-detects project ✨
|
|
111
|
+
npx scss-extract refactor
|
|
112
|
+
|
|
113
|
+
# Or specify paths manually
|
|
79
114
|
npx scss-extract refactor ./apps/subapp/src --output ./libs/styles/_variables.scss
|
|
80
115
|
```
|
|
81
116
|
|
|
82
|
-
###
|
|
117
|
+
### 5. Modernize Code (Remove Anti-Patterns)
|
|
83
118
|
|
|
84
119
|
Remove `::ng-deep` and `!important` following Angular Material v15+ best practices:
|
|
85
120
|
|
|
86
121
|
```bash
|
|
87
|
-
# Preview changes first
|
|
88
|
-
npx scss-extract modernize
|
|
122
|
+
# Preview changes first (auto-detects project) ✨
|
|
123
|
+
npx scss-extract modernize --dry-run
|
|
89
124
|
|
|
90
125
|
# Apply modernization
|
|
91
|
-
npx scss-extract modernize
|
|
126
|
+
npx scss-extract modernize
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 🔧 Angular.json Integration
|
|
130
|
+
|
|
131
|
+
The tool **automatically detects and uses your Angular project structure** from `angular.json`. This means you can run commands **without specifying any paths**!
|
|
132
|
+
|
|
133
|
+
### How It Works
|
|
134
|
+
|
|
135
|
+
When you run a command (like `generate-themes --analyze`), the tool:
|
|
136
|
+
|
|
137
|
+
1. **Finds angular.json** in your current directory
|
|
138
|
+
2. **Detects your project** (uses default project or specify with `--project`)
|
|
139
|
+
3. **Auto-discovers:**
|
|
140
|
+
- ✅ Source directory (`src/app`, etc.)
|
|
141
|
+
- ✅ Global styles location (`src/styles.scss`)
|
|
142
|
+
- ✅ Output path for generated files
|
|
143
|
+
- ✅ Style preprocessor (scss, sass, css)
|
|
144
|
+
- ✅ Project prefix and configuration
|
|
145
|
+
|
|
146
|
+
### Example
|
|
147
|
+
|
|
148
|
+
Instead of:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# ❌ Manual paths - tedious!
|
|
152
|
+
npx scss-extract generate-themes ./apps/my-app/src --output ./apps/my-app/src/styles
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Just run:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# ✅ Auto-detects everything!
|
|
159
|
+
npx scss-extract generate-themes --analyze
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Multi-Project Workspaces
|
|
163
|
+
|
|
164
|
+
If you have multiple projects in your Angular workspace:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Use specific project
|
|
168
|
+
npx scss-extract generate-themes --analyze --project my-app
|
|
169
|
+
|
|
170
|
+
# Use default project (auto-detected)
|
|
171
|
+
npx scss-extract generate-themes --analyze
|
|
92
172
|
```
|
|
93
173
|
|
|
174
|
+
### Disable Auto-Detection
|
|
175
|
+
|
|
176
|
+
If you want to use manual paths instead:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npx scss-extract generate-themes ./src --output ./src/styles --no-angular
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### All Commands Support angular.json
|
|
183
|
+
|
|
184
|
+
Every command works with angular.json integration:
|
|
185
|
+
|
|
186
|
+
- `analyze`
|
|
187
|
+
- `generate`
|
|
188
|
+
- `refactor`
|
|
189
|
+
- `modernize`
|
|
190
|
+
- `generate-themes` ⭐
|
|
191
|
+
- `detect-bootstrap`
|
|
192
|
+
- `migrate-bootstrap`
|
|
193
|
+
|
|
94
194
|
## CLI Commands
|
|
95
195
|
|
|
96
196
|
### `analyze` - Dry-run Analysis
|
|
@@ -528,7 +628,7 @@ npx scss-extract migrate-bootstrap --no-custom-utilities
|
|
|
528
628
|
|
|
529
629
|
### `generate-themes` - Theme Structure Generator
|
|
530
630
|
|
|
531
|
-
Generate a complete dark/light theme structure for Angular Material applications.
|
|
631
|
+
Generate a complete dark/light theme structure for Angular Material applications with **automatic color extraction** from your existing styles.
|
|
532
632
|
|
|
533
633
|
```bash
|
|
534
634
|
npx scss-extract generate-themes [src]
|
|
@@ -544,7 +644,7 @@ npx scss-extract generate-themes ./src --output ./src/styles --analyze
|
|
|
544
644
|
**Options:**
|
|
545
645
|
|
|
546
646
|
- `--output <dir>` - Output directory for theme files (default: `./src/styles`)
|
|
547
|
-
- `--analyze` - Analyze existing styles
|
|
647
|
+
- `--analyze` - Analyze existing styles and extract actual colors from your app
|
|
548
648
|
- `--format <format>` - Report format for analysis (table, json, markdown)
|
|
549
649
|
|
|
550
650
|
**What it generates:**
|
|
@@ -560,6 +660,10 @@ npx scss-extract generate-themes ./src --output ./src/styles --analyze
|
|
|
560
660
|
|
|
561
661
|
✅ **Features:**
|
|
562
662
|
|
|
663
|
+
- **🎨 Automatic Color Extraction** - Uses actual colors from your app (with `--analyze`)
|
|
664
|
+
- **Intelligent Color Selection** - Automatically picks primary, accent, and warn colors based on usage
|
|
665
|
+
- **Smart Red Detection** - Prefers red hues for warn colors
|
|
666
|
+
- **Dark Theme Variants** - Auto-generates lightened colors for dark mode
|
|
563
667
|
- Material Design color palettes
|
|
564
668
|
- Automatic dark/light mode switching
|
|
565
669
|
- CSS custom properties support
|
|
@@ -569,13 +673,80 @@ npx scss-extract generate-themes ./src --output ./src/styles --analyze
|
|
|
569
673
|
**Example Usage:**
|
|
570
674
|
|
|
571
675
|
```bash
|
|
572
|
-
#
|
|
573
|
-
npx scss-extract generate-themes --
|
|
676
|
+
# 🚀 SIMPLEST: Auto-detects everything from angular.json and extracts YOUR colors!
|
|
677
|
+
npx scss-extract generate-themes --analyze
|
|
678
|
+
|
|
679
|
+
# Without color extraction (uses Material Design defaults)
|
|
680
|
+
npx scss-extract generate-themes
|
|
574
681
|
|
|
575
|
-
#
|
|
682
|
+
# Manual paths (if no angular.json)
|
|
576
683
|
npx scss-extract generate-themes ./src --analyze --output ./src/styles
|
|
577
684
|
```
|
|
578
685
|
|
|
686
|
+
**Output Example:**
|
|
687
|
+
|
|
688
|
+
When you run with `--analyze`, you'll see:
|
|
689
|
+
|
|
690
|
+
```
|
|
691
|
+
🎨 Theme Structure Generator
|
|
692
|
+
|
|
693
|
+
📦 Angular Project: my-app
|
|
694
|
+
Prefix: app
|
|
695
|
+
Style: scss
|
|
696
|
+
Source: src/app
|
|
697
|
+
|
|
698
|
+
🔍 Analyzing styles in: src/app
|
|
699
|
+
|
|
700
|
+
Found 47 SCSS files
|
|
701
|
+
|
|
702
|
+
📊 Theme Readiness Analysis:
|
|
703
|
+
|
|
704
|
+
Total hardcoded colors: 156
|
|
705
|
+
Unique colors: 12
|
|
706
|
+
Components needing theme mixins: 8
|
|
707
|
+
|
|
708
|
+
🎨 Extracted Color Palettes:
|
|
709
|
+
|
|
710
|
+
Primary Color:
|
|
711
|
+
Light theme: #007bff
|
|
712
|
+
Dark theme: #66b0ff
|
|
713
|
+
|
|
714
|
+
Accent Color:
|
|
715
|
+
Light theme: #28a745
|
|
716
|
+
Dark theme: #74d77c
|
|
717
|
+
|
|
718
|
+
Warn Color:
|
|
719
|
+
Light theme: #dc3545
|
|
720
|
+
Dark theme: #dc3545
|
|
721
|
+
|
|
722
|
+
📁 Output directory: src/styles
|
|
723
|
+
|
|
724
|
+
✓ Using extracted colors from your app
|
|
725
|
+
|
|
726
|
+
✓ Created _theme-base.scss
|
|
727
|
+
✓ Created _theme-light.scss
|
|
728
|
+
✓ Created _theme-dark.scss
|
|
729
|
+
✓ Created themes.scss
|
|
730
|
+
✓ Created _css-variables.scss
|
|
731
|
+
✓ Created _component-theme-mixin.scss
|
|
732
|
+
|
|
733
|
+
✓ Theme structure generated!
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
**How Color Extraction Works:**
|
|
737
|
+
|
|
738
|
+
When you use `--analyze`, the tool:
|
|
739
|
+
|
|
740
|
+
1. **Scans all SCSS files** in your source directory
|
|
741
|
+
2. **Extracts all colors** (hex, rgba) and counts usage frequency
|
|
742
|
+
3. **Filters out utility colors** (black, white, grays)
|
|
743
|
+
4. **Intelligently categorizes**:
|
|
744
|
+
- Most used color → **Primary** palette
|
|
745
|
+
- Second most used → **Accent** palette
|
|
746
|
+
- Red hues (or third most used) → **Warn** palette
|
|
747
|
+
5. **Generates dark variants** by lightening colors for dark theme
|
|
748
|
+
6. **Creates theme files** with your actual brand colors
|
|
749
|
+
|
|
579
750
|
**Using Generated Themes:**
|
|
580
751
|
|
|
581
752
|
1. Import in your `styles.scss`:
|
|
@@ -1184,6 +1355,51 @@ MIT License - see [LICENSE](./LICENSE) file for details
|
|
|
1184
1355
|
|
|
1185
1356
|
## Changelog
|
|
1186
1357
|
|
|
1358
|
+
### 2.0.0 (2026-02-12) - Zero Config Release 🚀
|
|
1359
|
+
|
|
1360
|
+
**BREAKING CHANGES:**
|
|
1361
|
+
|
|
1362
|
+
- `generate-themes --output` default removed (now auto-detected from angular.json)
|
|
1363
|
+
|
|
1364
|
+
**Major Improvements:**
|
|
1365
|
+
|
|
1366
|
+
- **🎉 Zero Configuration:** All commands now fully leverage angular.json integration
|
|
1367
|
+
- **✨ Auto-Detection:** `generate-themes` automatically finds source directory and output path
|
|
1368
|
+
- **📦 Smart Project Detection:** Shows Angular project info (name, prefix, style preprocessor)
|
|
1369
|
+
- **🔧 Simplified Usage:** Just run `npx scss-extract generate-themes --analyze` - no arguments needed!
|
|
1370
|
+
- **📚 Comprehensive Documentation:** New angular.json integration guide
|
|
1371
|
+
- **⚡ Minimal Example:** Added quick-start section showing one-command theme generation
|
|
1372
|
+
- **🎯 Better UX:** Clearer console output with project context and file counts
|
|
1373
|
+
- **💡 Helpful Tips:** Shows suggestions when auto-detection features aren't used
|
|
1374
|
+
|
|
1375
|
+
**Enhanced Commands:**
|
|
1376
|
+
|
|
1377
|
+
- `generate-themes` now accepts `--angular-json`, `--project`, `--no-angular` options
|
|
1378
|
+
- Auto-detects output directory from global styles configuration
|
|
1379
|
+
- Shows Angular project metadata in output
|
|
1380
|
+
- Provides context-aware help messages
|
|
1381
|
+
|
|
1382
|
+
**Documentation:**
|
|
1383
|
+
|
|
1384
|
+
- Added "Minimal Example" section showing simplest usage
|
|
1385
|
+
- Added "Angular.json Integration" section explaining auto-detection
|
|
1386
|
+
- Updated all examples to show angular.json usage first
|
|
1387
|
+
- Improved THEME-GUIDE.md with zero-config examples
|
|
1388
|
+
- Added output examples showing what users will see
|
|
1389
|
+
|
|
1390
|
+
### 1.9.0 (2026-02-12)
|
|
1391
|
+
|
|
1392
|
+
- **Added:** 🎨 Automatic color extraction from existing app styles
|
|
1393
|
+
- **Added:** `extractColorPalettes()` function to intelligently select primary, accent, and warn colors
|
|
1394
|
+
- **Added:** Smart red hue detection for warn color selection
|
|
1395
|
+
- **Enhanced:** `generate-themes --analyze` now uses actual app colors instead of Material defaults
|
|
1396
|
+
- **Enhanced:** Gray shade filtering to exclude utility colors
|
|
1397
|
+
- **Enhanced:** Automatic dark theme variant generation (lightens colors for dark mode)
|
|
1398
|
+
- **Added:** Color normalization (hex3 → hex6, rgba → hex)
|
|
1399
|
+
- **Added:** Usage-based color ranking (most-used = primary, etc.)
|
|
1400
|
+
- **Documented:** Comprehensive color extraction examples in THEME-GUIDE.md
|
|
1401
|
+
- **Tested:** 12 new tests for color extraction (144 total tests passing)
|
|
1402
|
+
|
|
1187
1403
|
### 1.8.0 (2026-02-12)
|
|
1188
1404
|
|
|
1189
1405
|
- **Added:** `analyze-dependencies` command for multi-app dependency analysis
|
package/THEME-GUIDE.md
CHANGED
|
@@ -1,13 +1,125 @@
|
|
|
1
|
-
# Theme Generation and Global Styles
|
|
1
|
+
# Theme Generation and Global Styles Guide
|
|
2
2
|
|
|
3
|
-
This guide demonstrates the
|
|
3
|
+
This guide demonstrates the theme generation features including **automatic color extraction** from your existing styles.
|
|
4
|
+
|
|
5
|
+
## 🎨 Automatic Color Extraction (New!)
|
|
6
|
+
|
|
7
|
+
The theme generator can now **automatically extract colors from your app** and use them to create theme palettes, instead of using generic Material Design colors.
|
|
8
|
+
|
|
9
|
+
### How It Works
|
|
10
|
+
|
|
11
|
+
When you use the `--analyze` flag, the tool:
|
|
12
|
+
|
|
13
|
+
1. **Scans all SCSS files** in your source directory
|
|
14
|
+
2. **Extracts all colors** (hex, rgba, named colors)
|
|
15
|
+
3. **Counts usage frequency** to identify your brand colors
|
|
16
|
+
4. **Filters out utility colors** (black, white, grays)
|
|
17
|
+
5. **Intelligently categorizes colors**:
|
|
18
|
+
- Most-used color → **Primary** palette
|
|
19
|
+
- Second most-used → **Accent** palette
|
|
20
|
+
- Red hues → **Warn** palette (or third most-used)
|
|
21
|
+
6. **Generates dark theme variants** by lightening colors
|
|
22
|
+
|
|
23
|
+
### Example
|
|
24
|
+
|
|
25
|
+
Suppose your app has these colors:
|
|
26
|
+
|
|
27
|
+
```scss
|
|
28
|
+
// component-a.component.scss
|
|
29
|
+
.header {
|
|
30
|
+
color: #007bff; // Used 25 times across app
|
|
31
|
+
background: #28a745; // Used 15 times
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// component-b.component.scss
|
|
35
|
+
.button {
|
|
36
|
+
background: #007bff; // Same blue, used frequently
|
|
37
|
+
border: 1px solid #dc3545; // Red, used 8 times
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Without `--analyze` (default):**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx scss-extract generate-themes --output ./src/styles
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Uses Material Design defaults:
|
|
48
|
+
|
|
49
|
+
- Primary: `#1976d2` (Material Blue)
|
|
50
|
+
- Accent: `#ff4081` (Material Pink)
|
|
51
|
+
- Warn: `#f44336` (Material Red)
|
|
52
|
+
|
|
53
|
+
**With `--analyze` (extracts your colors):**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx scss-extract generate-themes ./src --analyze --output ./src/styles
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Output:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
🎨 Extracted Color Palettes:
|
|
63
|
+
|
|
64
|
+
Primary Color:
|
|
65
|
+
Light theme: #007bff (used 25 times - your brand blue!)
|
|
66
|
+
Dark theme: #66b0ff (auto-lightened)
|
|
67
|
+
|
|
68
|
+
Accent Color:
|
|
69
|
+
Light theme: #28a745 (used 15 times - your brand green!)
|
|
70
|
+
Dark theme: #74d77c (auto-lightened)
|
|
71
|
+
|
|
72
|
+
Warn Color:
|
|
73
|
+
Light theme: #dc3545 (detected red hue - your error color!)
|
|
74
|
+
Dark theme: #dc3545 (red stays consistent)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Smart Red Detection
|
|
78
|
+
|
|
79
|
+
The tool prefers **red hues** for the warn palette:
|
|
80
|
+
|
|
81
|
+
```scss
|
|
82
|
+
// Even if yellow is used more...
|
|
83
|
+
$warning: #ffc107; // Used 12 times
|
|
84
|
+
$error: #dc3545; // Used 8 times
|
|
85
|
+
|
|
86
|
+
// ...red will be selected for warn palette
|
|
87
|
+
// because warn colors should indicate errors/danger
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Color Filtering
|
|
91
|
+
|
|
92
|
+
The tool automatically filters out:
|
|
93
|
+
|
|
94
|
+
- **Black/white**: `#000`, `#fff`, `transparent`
|
|
95
|
+
- **Gray shades**: `#333`, `#666`, `#ccc`, etc.
|
|
96
|
+
- **Low usage colors**: Colors used less than 2 times (configurable)
|
|
97
|
+
|
|
98
|
+
This ensures only your **brand colors** are selected for theme palettes.
|
|
4
99
|
|
|
5
100
|
## 1. Generate Theme Structure
|
|
6
101
|
|
|
7
|
-
|
|
102
|
+
Generate a complete theme structure for your Angular Material app:
|
|
103
|
+
|
|
104
|
+
### With angular.json (Recommended - Zero Config!)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 🚀 Auto-detects everything and extracts YOUR app's colors!
|
|
108
|
+
npx scss-extract generate-themes --analyze
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
That's it! The tool automatically:
|
|
112
|
+
|
|
113
|
+
- ✅ Finds your Angular project from angular.json
|
|
114
|
+
- ✅ Detects source directory
|
|
115
|
+
- ✅ Places theme files in your styles folder
|
|
116
|
+
- ✅ Extracts actual colors from your app
|
|
117
|
+
- ✅ Generates dark/light theme variants
|
|
118
|
+
|
|
119
|
+
### Without angular.json (Manual Paths)
|
|
8
120
|
|
|
9
121
|
```bash
|
|
10
|
-
#
|
|
122
|
+
# Specify paths manually
|
|
11
123
|
npx scss-extract generate-themes ./src --analyze --output ./src/styles
|
|
12
124
|
```
|
|
13
125
|
|
package/bin/cli.js
CHANGED
|
@@ -21,7 +21,11 @@ const {
|
|
|
21
21
|
generateBootstrapReport,
|
|
22
22
|
} = require('../src/bootstrap-migrator');
|
|
23
23
|
const { analyzeStyleOrganization, generateOrganizationReport } = require('../src/style-organizer');
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
generateThemeStructure,
|
|
26
|
+
analyzeThemeReadiness,
|
|
27
|
+
extractColorPalettes,
|
|
28
|
+
} = require('../src/theme-utils');
|
|
25
29
|
const { analyzeMultiAppDependencies } = require('../src/multi-app-analyzer');
|
|
26
30
|
|
|
27
31
|
const program = new Command();
|
|
@@ -690,48 +694,118 @@ program
|
|
|
690
694
|
program
|
|
691
695
|
.command('generate-themes')
|
|
692
696
|
.description('Generate theme structure for dark/light mode support with Angular Material')
|
|
693
|
-
.argument('[src]', 'Source directory to scan for analysis (optional)')
|
|
694
|
-
.option('--output <dir>', 'Output directory for theme files
|
|
695
|
-
.option('--analyze', 'Analyze existing styles
|
|
697
|
+
.argument('[src]', 'Source directory to scan for analysis (optional if using angular.json)')
|
|
698
|
+
.option('--output <dir>', 'Output directory for theme files (auto-detected from angular.json)')
|
|
699
|
+
.option('--analyze', 'Analyze existing styles and extract colors from your app')
|
|
696
700
|
.option('--format <format>', 'Report format (table, json, markdown)', 'table')
|
|
701
|
+
.option('--angular-json <path>', 'Path to angular.json file')
|
|
702
|
+
.option('--project <name>', 'Angular project name (uses default if not specified)')
|
|
703
|
+
.option('--no-angular', 'Disable angular.json integration')
|
|
697
704
|
.action(async (src, options) => {
|
|
698
705
|
try {
|
|
699
706
|
console.log(chalk.cyan.bold('\n🎨 Theme Structure Generator\n'));
|
|
700
707
|
|
|
701
708
|
const fs = require('fs');
|
|
702
|
-
|
|
709
|
+
|
|
710
|
+
// Load config with angular.json integration
|
|
711
|
+
const config = loadConfig(null, {
|
|
712
|
+
useAngularJson: options.angular !== false,
|
|
713
|
+
angularJsonPath: options.angularJson,
|
|
714
|
+
projectName: options.project,
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// Override with command-line options
|
|
718
|
+
if (src) {
|
|
719
|
+
config.src = src;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Determine output directory
|
|
723
|
+
let outputDir;
|
|
724
|
+
if (options.output) {
|
|
725
|
+
outputDir = path.resolve(options.output);
|
|
726
|
+
} else if (config.angular && config.output) {
|
|
727
|
+
// Use the styles directory from angular.json
|
|
728
|
+
outputDir = path.resolve(path.dirname(config.output));
|
|
729
|
+
} else {
|
|
730
|
+
outputDir = path.resolve('./src/styles');
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Show Angular project info if available
|
|
734
|
+
if (config.angular) {
|
|
735
|
+
console.log(chalk.cyan(`📦 Angular Project: ${config.angular.project}`));
|
|
736
|
+
console.log(chalk.gray(` Prefix: ${config.angular.prefix}`));
|
|
737
|
+
console.log(chalk.gray(` Style: ${config.angular.stylePreprocessor}`));
|
|
738
|
+
console.log(chalk.gray(` Source: ${config.src}\n`));
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
let extractedPalettes = null;
|
|
703
742
|
|
|
704
743
|
// Analyze existing styles if requested
|
|
705
|
-
if (options.analyze
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const files = await scanScssFiles(src, config.ignore);
|
|
709
|
-
const analysis = analyzeThemeReadiness(files);
|
|
710
|
-
|
|
711
|
-
console.log(chalk.blue.bold('Theme Readiness Analysis:\n'));
|
|
712
|
-
console.log(chalk.gray(`Total hardcoded colors: ${analysis.hardcodedColors.length}`));
|
|
713
|
-
console.log(chalk.gray(`Unique colors: ${analysis.colorUsage.size}`));
|
|
714
|
-
console.log(
|
|
715
|
-
chalk.gray(`Components needing theme mixins: ${analysis.themeableComponents.length}\n`)
|
|
716
|
-
);
|
|
744
|
+
if (options.analyze) {
|
|
745
|
+
const scanDir = config.src || './src';
|
|
746
|
+
console.log(chalk.gray(`🔍 Analyzing styles in: ${scanDir}\n`));
|
|
717
747
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
});
|
|
724
|
-
|
|
748
|
+
const files = await scanScssFiles(scanDir, config.ignore);
|
|
749
|
+
|
|
750
|
+
if (files.length === 0) {
|
|
751
|
+
console.log(chalk.yellow('⚠ No SCSS files found. Using default Material colors.\n'));
|
|
752
|
+
} else {
|
|
753
|
+
console.log(chalk.gray(` Found ${files.length} SCSS files\n`));
|
|
754
|
+
|
|
755
|
+
const analysis = analyzeThemeReadiness(files);
|
|
756
|
+
|
|
757
|
+
console.log(chalk.blue.bold('📊 Theme Readiness Analysis:\n'));
|
|
758
|
+
console.log(chalk.gray(` Total hardcoded colors: ${analysis.hardcodedColors.length}`));
|
|
759
|
+
console.log(chalk.gray(` Unique colors: ${analysis.colorUsage.size}`));
|
|
760
|
+
console.log(
|
|
761
|
+
chalk.gray(
|
|
762
|
+
` Components needing theme mixins: ${analysis.themeableComponents.length}\n`
|
|
763
|
+
)
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
// Extract color palettes from usage
|
|
767
|
+
extractedPalettes = extractColorPalettes(analysis.colorUsage);
|
|
768
|
+
|
|
769
|
+
console.log(chalk.green.bold('🎨 Extracted Color Palettes:\n'));
|
|
770
|
+
console.log(chalk.cyan('Primary Color:'));
|
|
771
|
+
console.log(chalk.gray(` Light theme: ${extractedPalettes.primary.light}`));
|
|
772
|
+
console.log(chalk.gray(` Dark theme: ${extractedPalettes.primary.dark}`));
|
|
773
|
+
console.log(chalk.cyan('Accent Color:'));
|
|
774
|
+
console.log(chalk.gray(` Light theme: ${extractedPalettes.accent.light}`));
|
|
775
|
+
console.log(chalk.gray(` Dark theme: ${extractedPalettes.accent.dark}`));
|
|
776
|
+
console.log(chalk.cyan('Warn Color:'));
|
|
777
|
+
console.log(chalk.gray(` Light theme: ${extractedPalettes.warn.light}`));
|
|
778
|
+
console.log(chalk.gray(` Dark theme: ${extractedPalettes.warn.dark}\n`));
|
|
779
|
+
|
|
780
|
+
if (analysis.recommendations.length > 0) {
|
|
781
|
+
console.log(chalk.yellow.bold('📋 Recommendations:\n'));
|
|
782
|
+
analysis.recommendations.forEach(rec => {
|
|
783
|
+
const icon = rec.priority === 'high' ? '🔴' : '🟡';
|
|
784
|
+
console.log(chalk.gray(` ${icon} ${rec.message}`));
|
|
785
|
+
});
|
|
786
|
+
console.log();
|
|
787
|
+
}
|
|
725
788
|
}
|
|
726
789
|
}
|
|
727
790
|
|
|
728
791
|
// Generate theme files
|
|
729
|
-
console.log(chalk.gray(
|
|
792
|
+
console.log(chalk.gray(`📁 Output directory: ${outputDir}\n`));
|
|
730
793
|
|
|
731
|
-
const
|
|
794
|
+
const themeOptions = {
|
|
732
795
|
outputDir,
|
|
733
796
|
includeComponents: true,
|
|
734
|
-
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// Use extracted palettes if available
|
|
800
|
+
if (extractedPalettes) {
|
|
801
|
+
themeOptions.themePalettes = extractedPalettes;
|
|
802
|
+
console.log(chalk.green('✓ Using extracted colors from your app\n'));
|
|
803
|
+
} else {
|
|
804
|
+
console.log(chalk.yellow('ℹ Using default Material Design colors\n'));
|
|
805
|
+
console.log(chalk.gray(' 💡 Tip: Use --analyze to extract colors from your app\n'));
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const themeStructure = generateThemeStructure(themeOptions);
|
|
735
809
|
|
|
736
810
|
// Create output directory if it doesn't exist
|
|
737
811
|
if (!fs.existsSync(outputDir)) {
|
package/package.json
CHANGED
package/src/theme-utils.js
CHANGED
|
@@ -1,6 +1,205 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Extracts color palettes from analyzed color usage in the app
|
|
6
|
+
* Intelligently categorizes colors as primary, accent, and warn based on usage patterns
|
|
7
|
+
* @param {Map} colorUsage - Map of color → usage count from analyzeThemeReadiness
|
|
8
|
+
* @param {Object} options - Configuration options
|
|
9
|
+
* @returns {Object} - Theme palettes with light and dark variants
|
|
10
|
+
*/
|
|
11
|
+
function extractColorPalettes(colorUsage, options = {}) {
|
|
12
|
+
const { minUsage = 2 } = options;
|
|
13
|
+
|
|
14
|
+
if (!colorUsage || colorUsage.size === 0) {
|
|
15
|
+
console.warn('No color usage data provided. Using default Material palettes.');
|
|
16
|
+
return {
|
|
17
|
+
primary: { light: '#1976d2', dark: '#90caf9' },
|
|
18
|
+
accent: { light: '#ff4081', dark: '#ff4081' },
|
|
19
|
+
warn: { light: '#f44336', dark: '#f44336' },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Filter out common utility colors (black, white, transparent, gray shades)
|
|
24
|
+
const utilityColors = [
|
|
25
|
+
'#000',
|
|
26
|
+
'#000000',
|
|
27
|
+
'#fff',
|
|
28
|
+
'#ffffff',
|
|
29
|
+
'transparent',
|
|
30
|
+
'rgba(0,0,0,0)',
|
|
31
|
+
'rgba(0, 0, 0, 0)',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Filter and sort colors by usage
|
|
35
|
+
const colorsByUsage = Array.from(colorUsage.entries())
|
|
36
|
+
.filter(([color, count]) => {
|
|
37
|
+
const normalized = color.trim().toLowerCase();
|
|
38
|
+
// Skip utility colors
|
|
39
|
+
if (utilityColors.some(util => normalized.includes(util.toLowerCase()))) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Skip gray shades (common in borders, backgrounds)
|
|
43
|
+
if (normalized.match(/#[0-9a-f]{3,6}/i)) {
|
|
44
|
+
const hex = normalized.match(/#([0-9a-f]{3,6})/i)[1];
|
|
45
|
+
const expanded =
|
|
46
|
+
hex.length === 3
|
|
47
|
+
? hex
|
|
48
|
+
.split('')
|
|
49
|
+
.map(c => c + c)
|
|
50
|
+
.join('')
|
|
51
|
+
: hex;
|
|
52
|
+
const r = parseInt(expanded.substr(0, 2), 16);
|
|
53
|
+
const g = parseInt(expanded.substr(2, 2), 16);
|
|
54
|
+
const b = parseInt(expanded.substr(4, 2), 16);
|
|
55
|
+
// If all RGB values are similar (within 15), it's a gray
|
|
56
|
+
if (Math.abs(r - g) < 15 && Math.abs(g - b) < 15 && Math.abs(r - b) < 15) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return count >= minUsage;
|
|
61
|
+
})
|
|
62
|
+
.sort((a, b) => b[1] - a[1]);
|
|
63
|
+
|
|
64
|
+
if (colorsByUsage.length === 0) {
|
|
65
|
+
console.warn('No suitable brand colors found. Using default Material palettes.');
|
|
66
|
+
return {
|
|
67
|
+
primary: { light: '#1976d2', dark: '#90caf9' },
|
|
68
|
+
accent: { light: '#ff4081', dark: '#ff4081' },
|
|
69
|
+
warn: { light: '#f44336', dark: '#f44336' },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Extract top 3 colors (or fewer if not available)
|
|
74
|
+
const primaryColor = colorsByUsage[0] ? colorsByUsage[0][0] : '#1976d2';
|
|
75
|
+
const accentColor = colorsByUsage[1] ? colorsByUsage[1][0] : '#ff4081';
|
|
76
|
+
const warnColor = colorsByUsage[2] ? colorsByUsage[2][0] : '#f44336';
|
|
77
|
+
|
|
78
|
+
// Try to identify warn color based on hue (prefer reds)
|
|
79
|
+
let finalWarnColor = warnColor;
|
|
80
|
+
for (const [color, count] of colorsByUsage) {
|
|
81
|
+
if (isRedHue(color)) {
|
|
82
|
+
finalWarnColor = color;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Generate dark variants (lighten colors for dark theme)
|
|
88
|
+
const palettes = {
|
|
89
|
+
primary: {
|
|
90
|
+
light: normalizeColor(primaryColor),
|
|
91
|
+
dark: lightenColor(primaryColor, 40),
|
|
92
|
+
},
|
|
93
|
+
accent: {
|
|
94
|
+
light: normalizeColor(accentColor),
|
|
95
|
+
dark: lightenColor(accentColor, 30),
|
|
96
|
+
},
|
|
97
|
+
warn: {
|
|
98
|
+
light: normalizeColor(finalWarnColor),
|
|
99
|
+
dark: normalizeColor(finalWarnColor), // Warn colors usually stay the same
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return palettes;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Checks if a color is in the red hue range (for warn color detection)
|
|
108
|
+
* @param {string} color - Color value (hex or rgba)
|
|
109
|
+
* @returns {boolean} - True if color is reddish
|
|
110
|
+
*/
|
|
111
|
+
function isRedHue(color) {
|
|
112
|
+
const hex = color.match(/#([0-9a-f]{3,6})/i);
|
|
113
|
+
if (!hex) return false;
|
|
114
|
+
|
|
115
|
+
const hexValue = hex[1];
|
|
116
|
+
const expanded =
|
|
117
|
+
hexValue.length === 3
|
|
118
|
+
? hexValue
|
|
119
|
+
.split('')
|
|
120
|
+
.map(c => c + c)
|
|
121
|
+
.join('')
|
|
122
|
+
: hexValue;
|
|
123
|
+
const r = parseInt(expanded.substring(0, 2), 16);
|
|
124
|
+
const g = parseInt(expanded.substring(2, 4), 16);
|
|
125
|
+
const b = parseInt(expanded.substring(4, 6), 16);
|
|
126
|
+
|
|
127
|
+
// Red hue criteria:
|
|
128
|
+
// 1. Red must be dominant (greater than both green and blue)
|
|
129
|
+
// 2. Red should be bright enough (r > 150)
|
|
130
|
+
// 3. Green should be significantly less than red (to exclude yellows/oranges)
|
|
131
|
+
// 4. Blue should be significantly less than red
|
|
132
|
+
const result =
|
|
133
|
+
r > g &&
|
|
134
|
+
r > b &&
|
|
135
|
+
r > 150 &&
|
|
136
|
+
g < r * 0.6 && // Green should be less than 60% of red (excludes yellow/orange)
|
|
137
|
+
r - g > 50; // Red should be significantly higher than green
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Normalizes color format to hex6 format
|
|
144
|
+
* @param {string} color - Color value
|
|
145
|
+
* @returns {string} - Normalized hex color
|
|
146
|
+
*/
|
|
147
|
+
function normalizeColor(color) {
|
|
148
|
+
// Already normalized hex6
|
|
149
|
+
if (color.match(/^#[0-9a-f]{6}$/i)) {
|
|
150
|
+
return color.toLowerCase();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Hex3 to hex6
|
|
154
|
+
const hex3 = color.match(/^#([0-9a-f]{3})$/i);
|
|
155
|
+
if (hex3) {
|
|
156
|
+
return (
|
|
157
|
+
'#' +
|
|
158
|
+
hex3[1]
|
|
159
|
+
.split('')
|
|
160
|
+
.map(c => c + c)
|
|
161
|
+
.join('')
|
|
162
|
+
.toLowerCase()
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// RGBA to hex (ignoring alpha)
|
|
167
|
+
const rgba = color.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
|
|
168
|
+
if (rgba) {
|
|
169
|
+
const r = parseInt(rgba[1]).toString(16).padStart(2, '0');
|
|
170
|
+
const g = parseInt(rgba[2]).toString(16).padStart(2, '0');
|
|
171
|
+
const b = parseInt(rgba[3]).toString(16).padStart(2, '0');
|
|
172
|
+
return `#${r}${g}${b}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Fallback
|
|
176
|
+
return color;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Lightens a color by a percentage for dark theme
|
|
181
|
+
* @param {string} color - Hex color
|
|
182
|
+
* @param {number} percent - Percentage to lighten (0-100)
|
|
183
|
+
* @returns {string} - Lightened hex color
|
|
184
|
+
*/
|
|
185
|
+
function lightenColor(color, percent) {
|
|
186
|
+
const normalized = normalizeColor(color);
|
|
187
|
+
const hex = normalized.replace('#', '');
|
|
188
|
+
|
|
189
|
+
const r = parseInt(hex.substr(0, 2), 16);
|
|
190
|
+
const g = parseInt(hex.substr(2, 2), 16);
|
|
191
|
+
const b = parseInt(hex.substr(4, 2), 16);
|
|
192
|
+
|
|
193
|
+
// Lighten by moving toward 255
|
|
194
|
+
const lighten = val => Math.min(255, Math.round(val + ((255 - val) * percent) / 100));
|
|
195
|
+
|
|
196
|
+
const newR = lighten(r).toString(16).padStart(2, '0');
|
|
197
|
+
const newG = lighten(g).toString(16).padStart(2, '0');
|
|
198
|
+
const newB = lighten(b).toString(16).padStart(2, '0');
|
|
199
|
+
|
|
200
|
+
return `#${newR}${newG}${newB}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
4
203
|
/**
|
|
5
204
|
* Generates SCSS theme structure for Angular Material applications
|
|
6
205
|
* Supports dark and light themes with proper variable organization
|
|
@@ -427,6 +626,7 @@ function getLineNumber(content, index) {
|
|
|
427
626
|
module.exports = {
|
|
428
627
|
generateThemeStructure,
|
|
429
628
|
analyzeThemeReadiness,
|
|
629
|
+
extractColorPalettes,
|
|
430
630
|
getThemeRecommendations,
|
|
431
631
|
getRecommendedStructure,
|
|
432
632
|
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const { extractColorPalettes } = require('../src/theme-utils');
|
|
2
|
+
|
|
3
|
+
describe('Color Palette Extraction', () => {
|
|
4
|
+
describe('extractColorPalettes', () => {
|
|
5
|
+
test('should return default palettes when no color usage provided', () => {
|
|
6
|
+
const palettes = extractColorPalettes(null);
|
|
7
|
+
|
|
8
|
+
expect(palettes).toHaveProperty('primary');
|
|
9
|
+
expect(palettes).toHaveProperty('accent');
|
|
10
|
+
expect(palettes).toHaveProperty('warn');
|
|
11
|
+
expect(palettes.primary).toHaveProperty('light');
|
|
12
|
+
expect(palettes.primary).toHaveProperty('dark');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should extract primary, accent, and warn colors from usage data', () => {
|
|
16
|
+
const colorUsage = new Map([
|
|
17
|
+
['#007bff', 25], // Most used (primary)
|
|
18
|
+
['#28a745', 15], // Second most (accent)
|
|
19
|
+
['#dc3545', 10], // Third most (warn - red)
|
|
20
|
+
['#ffc107', 5],
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
24
|
+
|
|
25
|
+
expect(palettes.primary.light).toBe('#007bff');
|
|
26
|
+
expect(palettes.accent.light).toBe('#28a745');
|
|
27
|
+
expect(palettes.warn.light).toBe('#dc3545');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should filter out utility colors (black, white, transparent)', () => {
|
|
31
|
+
const colorUsage = new Map([
|
|
32
|
+
['#000000', 100], // Should be filtered
|
|
33
|
+
['#ffffff', 90], // Should be filtered
|
|
34
|
+
['transparent', 80], // Should be filtered
|
|
35
|
+
['#007bff', 25],
|
|
36
|
+
['#28a745', 15],
|
|
37
|
+
['#dc3545', 10],
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
41
|
+
|
|
42
|
+
expect(palettes.primary.light).toBe('#007bff');
|
|
43
|
+
expect(palettes.accent.light).toBe('#28a745');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should filter out gray shades', () => {
|
|
47
|
+
const colorUsage = new Map([
|
|
48
|
+
['#808080', 50], // Gray - should be filtered
|
|
49
|
+
['#cccccc', 40], // Light gray - should be filtered
|
|
50
|
+
['#333333', 30], // Dark gray - should be filtered
|
|
51
|
+
['#007bff', 25],
|
|
52
|
+
['#28a745', 15],
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
56
|
+
|
|
57
|
+
expect(palettes.primary.light).toBe('#007bff');
|
|
58
|
+
expect(palettes.accent.light).toBe('#28a745');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should prefer red colors for warn palette', () => {
|
|
62
|
+
const colorUsage = new Map([
|
|
63
|
+
['#007bff', 25], // Blue
|
|
64
|
+
['#28a745', 15], // Green
|
|
65
|
+
['#ffc107', 12], // Yellow
|
|
66
|
+
['#dc3545', 8], // Red (less usage but should be warn)
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
70
|
+
|
|
71
|
+
expect(palettes.warn.light).toBe('#dc3545');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('should generate dark variants for primary and accent', () => {
|
|
75
|
+
const colorUsage = new Map([
|
|
76
|
+
['#1976d2', 25],
|
|
77
|
+
['#d32f2f', 15],
|
|
78
|
+
['#f57c00', 10],
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
82
|
+
|
|
83
|
+
// Dark variants should be lighter than light variants
|
|
84
|
+
expect(palettes.primary.dark).not.toBe(palettes.primary.light);
|
|
85
|
+
expect(palettes.accent.dark).not.toBe(palettes.accent.light);
|
|
86
|
+
expect(palettes.primary.dark).toMatch(/#[0-9a-f]{6}/);
|
|
87
|
+
expect(palettes.accent.dark).toMatch(/#[0-9a-f]{6}/);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should normalize hex3 colors to hex6', () => {
|
|
91
|
+
const colorUsage = new Map([
|
|
92
|
+
['#07f', 25], // Hex3
|
|
93
|
+
['#0f0', 15], // Hex3
|
|
94
|
+
['#f00', 10], // Hex3
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
98
|
+
|
|
99
|
+
expect(palettes.primary.light).toMatch(/#[0-9a-f]{6}/);
|
|
100
|
+
expect(palettes.primary.light).toBe('#0077ff');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should handle rgba colors', () => {
|
|
104
|
+
const colorUsage = new Map([
|
|
105
|
+
['rgba(25, 118, 210, 1)', 25],
|
|
106
|
+
['rgba(211, 47, 47, 0.8)', 15],
|
|
107
|
+
['rgba(245, 124, 0, 1)', 10],
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
111
|
+
|
|
112
|
+
// Should convert to hex
|
|
113
|
+
expect(palettes.primary.light).toMatch(/#[0-9a-f]{6}/);
|
|
114
|
+
expect(palettes.accent.light).toMatch(/#[0-9a-f]{6}/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should respect minUsage threshold', () => {
|
|
118
|
+
const colorUsage = new Map([
|
|
119
|
+
['#007bff', 25],
|
|
120
|
+
['#28a745', 2], // Below threshold
|
|
121
|
+
['#dc3545', 1], // Below threshold
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
const palettes = extractColorPalettes(colorUsage, { minUsage: 3 });
|
|
125
|
+
|
|
126
|
+
// Should only use colors with usage >= 3
|
|
127
|
+
expect(palettes.primary.light).toBe('#007bff');
|
|
128
|
+
// Others should fall back to defaults
|
|
129
|
+
expect(palettes.accent.light).toMatch(/#[0-9a-f]{6}/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should handle empty color usage', () => {
|
|
133
|
+
const colorUsage = new Map();
|
|
134
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
135
|
+
|
|
136
|
+
// Should return defaults
|
|
137
|
+
expect(palettes.primary.light).toBe('#1976d2');
|
|
138
|
+
expect(palettes.accent.light).toBe('#ff4081');
|
|
139
|
+
expect(palettes.warn.light).toBe('#f44336');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('should handle single color', () => {
|
|
143
|
+
const colorUsage = new Map([['#007bff', 25]]);
|
|
144
|
+
|
|
145
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
146
|
+
|
|
147
|
+
expect(palettes.primary.light).toBe('#007bff');
|
|
148
|
+
// Accent and warn should fall back to defaults
|
|
149
|
+
expect(palettes.accent.light).toMatch(/#[0-9a-f]{6}/);
|
|
150
|
+
expect(palettes.warn.light).toMatch(/#[0-9a-f]{6}/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('should keep warn color same for light and dark themes', () => {
|
|
154
|
+
const colorUsage = new Map([
|
|
155
|
+
['#007bff', 25],
|
|
156
|
+
['#28a745', 15],
|
|
157
|
+
['#dc3545', 10],
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
const palettes = extractColorPalettes(colorUsage);
|
|
161
|
+
|
|
162
|
+
// Warn colors typically stay the same in both themes
|
|
163
|
+
expect(palettes.warn.light).toBe(palettes.warn.dark);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|