emily-css 1.0.8 → 1.0.14
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/CHANGELOG.md +154 -0
- package/README.md +64 -222
- package/bin/emilyui.js +35 -6
- package/package.json +18 -4
- package/src/generators.js +34 -1
- package/src/index.js +148 -47
- package/src/init.js +641 -188
- package/src/purge-cmd.js +2 -2
- package/src/showcase.js +90 -0
- package/src/watch.js +193 -0
package/src/index.js
CHANGED
|
@@ -142,8 +142,9 @@ function generateAllColours(colourConfig) {
|
|
|
142
142
|
// ============================================================================
|
|
143
143
|
|
|
144
144
|
function generateSpacing(baseUnit, scale) {
|
|
145
|
-
//
|
|
146
|
-
//
|
|
145
|
+
// Spacing values are defined explicitly in emily.config.json under spacing.scale.
|
|
146
|
+
// The baseUnit key in config is informational only — it documents the design intent
|
|
147
|
+
// (e.g. "this system is based on 8px") but does not drive generation.
|
|
147
148
|
return scale;
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -151,30 +152,40 @@ function generateSpacing(baseUnit, scale) {
|
|
|
151
152
|
// FONT PRESETS
|
|
152
153
|
// ============================================================================
|
|
153
154
|
|
|
155
|
+
// Font presets define the CSS font-family stack only.
|
|
156
|
+
// Loading the actual font files is the user's responsibility — link them in your HTML
|
|
157
|
+
// or use @fontsource packages in your build. emily-css does not generate @import rules
|
|
158
|
+
// for external CDNs so it stays self-contained and works offline.
|
|
159
|
+
// See docs: https://emilyui.com/docs/getting-started
|
|
154
160
|
const FONT_PRESETS = {
|
|
155
161
|
'system': {
|
|
156
162
|
stack: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
157
|
-
googleFont: false,
|
|
158
163
|
},
|
|
159
164
|
'inter': {
|
|
160
165
|
name: 'Inter',
|
|
161
166
|
stack: '"Inter", system-ui, sans-serif',
|
|
162
|
-
googleFont: true,
|
|
163
|
-
importUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap',
|
|
164
167
|
},
|
|
165
168
|
'lexend': {
|
|
166
169
|
name: 'Lexend',
|
|
167
170
|
stack: '"Lexend", system-ui, sans-serif',
|
|
168
|
-
googleFont: true,
|
|
169
|
-
importUrl: 'https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&display=swap',
|
|
170
171
|
},
|
|
171
172
|
'georgia': {
|
|
172
173
|
stack: 'Georgia, "Times New Roman", serif',
|
|
173
|
-
|
|
174
|
+
},
|
|
175
|
+
'dm-sans': {
|
|
176
|
+
name: 'DM Sans',
|
|
177
|
+
stack: '"DM Sans", system-ui, sans-serif',
|
|
178
|
+
},
|
|
179
|
+
'nunito': {
|
|
180
|
+
name: 'Nunito',
|
|
181
|
+
stack: '"Nunito", system-ui, sans-serif',
|
|
182
|
+
},
|
|
183
|
+
'atkinson': {
|
|
184
|
+
name: 'Atkinson Hyperlegible',
|
|
185
|
+
stack: '"Atkinson Hyperlegible", system-ui, sans-serif',
|
|
174
186
|
},
|
|
175
187
|
'mono': {
|
|
176
188
|
stack: '"Menlo", "Monaco", "Courier New", monospace',
|
|
177
|
-
googleFont: false,
|
|
178
189
|
},
|
|
179
190
|
};
|
|
180
191
|
|
|
@@ -197,14 +208,6 @@ function generateFontCSS(config) {
|
|
|
197
208
|
let fontFace = '';
|
|
198
209
|
let bodyFont = '';
|
|
199
210
|
|
|
200
|
-
// Import Google Fonts — dedupe if heading and body use the same font
|
|
201
|
-
const imports = new Set();
|
|
202
|
-
if (headingPreset.googleFont) imports.add(headingPreset.importUrl);
|
|
203
|
-
if (bodyPreset.googleFont) imports.add(bodyPreset.importUrl);
|
|
204
|
-
imports.forEach(url => {
|
|
205
|
-
fontFace += `@import url("${url}");\n`;
|
|
206
|
-
});
|
|
207
|
-
|
|
208
211
|
bodyFont += ` body {\n font-family: ${bodyPreset.stack};\n font-synthesis: style;\n }\n`;
|
|
209
212
|
bodyFont += ` h1, h2, h3, h4, h5, h6 {\n font-family: ${headingPreset.stack};\n }\n`;
|
|
210
213
|
|
|
@@ -284,7 +287,8 @@ const {
|
|
|
284
287
|
blendUtilities,
|
|
285
288
|
cursorUtilities,
|
|
286
289
|
accessibilityUtilities,
|
|
287
|
-
containerUtilities
|
|
290
|
+
containerUtilities,
|
|
291
|
+
codeUtilities
|
|
288
292
|
} = require('./generators');
|
|
289
293
|
|
|
290
294
|
// ============================================================================
|
|
@@ -471,8 +475,7 @@ function generateTypographyUtilities(config) {
|
|
|
471
475
|
css += `.text-right { text-align: right; }\n`;
|
|
472
476
|
css += `.text-justify { text-align: justify; }\n`;
|
|
473
477
|
|
|
474
|
-
// Text wrapping
|
|
475
|
-
css += `.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n`;
|
|
478
|
+
// Text wrapping (.truncate lives in overflowUtilities in generators.js — not duplicated here)
|
|
476
479
|
css += `.whitespace-nowrap { white-space: nowrap; }\n`;
|
|
477
480
|
css += `.whitespace-normal { white-space: normal; }\n`;
|
|
478
481
|
css += `.break-words { word-break: break-word; }\n`;
|
|
@@ -582,7 +585,7 @@ function generateBaseStyles(config) {
|
|
|
582
585
|
// Build a lookup map from font size name → CSS variable
|
|
583
586
|
const fontSizeMap = {};
|
|
584
587
|
(config.typography?.fontSizes || []).forEach(({ name }) => {
|
|
585
|
-
fontSizeMap[name] = `var(--
|
|
588
|
+
fontSizeMap[name] = `var(--text-${name})`;
|
|
586
589
|
});
|
|
587
590
|
|
|
588
591
|
// Line height hints per size — keeps headings tighter than body text
|
|
@@ -779,7 +782,7 @@ function addStateVariants(css) {
|
|
|
779
782
|
// BUILD FUNCTION
|
|
780
783
|
// ============================================================================
|
|
781
784
|
|
|
782
|
-
function
|
|
785
|
+
function buildFullFramework() {
|
|
783
786
|
const configPath = path.join(process.cwd(), 'emily.config.json');
|
|
784
787
|
if (!fs.existsSync(configPath)) {
|
|
785
788
|
console.error(`\n emily-css: No config found.\n Expected: ${configPath}\n Run "emily-css init" to create one.\n`);
|
|
@@ -787,24 +790,7 @@ function build(options = {}) {
|
|
|
787
790
|
}
|
|
788
791
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
789
792
|
|
|
790
|
-
|
|
791
|
-
const { purgeCSS } = require('./purge.js');
|
|
792
|
-
const cssPath = path.join(process.cwd(), 'dist/emily.css');
|
|
793
|
-
if (!fs.existsSync(cssPath)) {
|
|
794
|
-
console.error(' emily-css: Run "emily-css build" first.');
|
|
795
|
-
process.exit(1);
|
|
796
|
-
}
|
|
797
|
-
console.log(`Purging unused utilities from ${options.purge}...`);
|
|
798
|
-
const css = fs.readFileSync(cssPath, 'utf8');
|
|
799
|
-
const purged = purgeCSS(css, options.purge);
|
|
800
|
-
const minified = purged.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\s+/g, ' ').replace(/\s?\{/g, '{').replace(/\s?\}/g, '}').replace(/;\s/g, ';').trim();
|
|
801
|
-
fs.writeFileSync(path.join(process.cwd(), 'dist/emily.purged.css'), purged);
|
|
802
|
-
fs.writeFileSync(path.join(process.cwd(), 'dist/emily.purged.min.css'), minified);
|
|
803
|
-
console.log('✓ Purged CSS: dist/emily.purged.css');
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
console.log('Building EmilyUI...');
|
|
793
|
+
console.log('Building EmilyCSS full framework...');
|
|
808
794
|
|
|
809
795
|
// Generate colours
|
|
810
796
|
const colours = generateAllColours(config.colours);
|
|
@@ -844,6 +830,7 @@ function build(options = {}) {
|
|
|
844
830
|
utilityCss += cursorUtilities();
|
|
845
831
|
utilityCss += accessibilityUtilities();
|
|
846
832
|
utilityCss += containerUtilities();
|
|
833
|
+
utilityCss += codeUtilities();
|
|
847
834
|
|
|
848
835
|
// Add state, dark mode, and responsive variants to utilities
|
|
849
836
|
utilityCss = addStateVariants(utilityCss);
|
|
@@ -904,6 +891,38 @@ function build(options = {}) {
|
|
|
904
891
|
p, h1, h2, h3, h4, h5, h6 {
|
|
905
892
|
overflow-wrap: break-word;
|
|
906
893
|
}
|
|
894
|
+
|
|
895
|
+
/* Code blocks — VSCode Dark+ style by default */
|
|
896
|
+
pre {
|
|
897
|
+
background-color: #1e1e1e;
|
|
898
|
+
color: #d4d4d4;
|
|
899
|
+
padding: 1.25rem;
|
|
900
|
+
border-radius: 0 0 6px;
|
|
901
|
+
overflow-x: auto;
|
|
902
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
903
|
+
font-size: 0.875rem;
|
|
904
|
+
line-height: 1.7;
|
|
905
|
+
border: 1px solid #333;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
pre code {
|
|
909
|
+
background: none;
|
|
910
|
+
padding: 0;
|
|
911
|
+
border-radius: 0;
|
|
912
|
+
color: inherit;
|
|
913
|
+
font-size: inherit;
|
|
914
|
+
font-family: inherit;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/* Inline code */
|
|
918
|
+
code {
|
|
919
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace;
|
|
920
|
+
font-size: 0.875em;
|
|
921
|
+
background-color: #2d2d2d;
|
|
922
|
+
color: #d4d4d4;
|
|
923
|
+
padding: 0.125rem 0.375rem;
|
|
924
|
+
border-radius: 4px;
|
|
925
|
+
}
|
|
907
926
|
${bodyFont}`;
|
|
908
927
|
|
|
909
928
|
// @font-face must sit outside @layer for broadest browser compatibility
|
|
@@ -926,26 +945,107 @@ ${bodyFont}`;
|
|
|
926
945
|
fs.writeFileSync(outputPath, css);
|
|
927
946
|
console.log(`✓ Generated CSS: ${outputPath}`);
|
|
928
947
|
console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
|
|
948
|
+
console.log('Full framework build complete');
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function minify(css) {
|
|
952
|
+
return css
|
|
953
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
954
|
+
.replace(/\s+/g, ' ')
|
|
955
|
+
.replace(/\s?\{/g, '{')
|
|
956
|
+
.replace(/\s?\}/g, '}')
|
|
957
|
+
.replace(/;\s/g, ';')
|
|
958
|
+
.trim();
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function getConfig() {
|
|
962
|
+
const configPath = path.join(process.cwd(), 'emily.config.json');
|
|
963
|
+
|
|
964
|
+
if (!fs.existsSync(configPath)) {
|
|
965
|
+
console.error('\n emily-css: No config found. Run "emily-css init" first.\n');
|
|
966
|
+
process.exit(1);
|
|
967
|
+
}
|
|
929
968
|
|
|
930
|
-
|
|
931
|
-
|
|
969
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function getSourceDir(config) {
|
|
973
|
+
return config.purge && config.purge.sourceDir ? config.purge.sourceDir : '.';
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function buildProductionCss() {
|
|
977
|
+
const config = getConfig();
|
|
978
|
+
const sourceDir = getSourceDir(config);
|
|
979
|
+
const cssPath = path.join(process.cwd(), 'dist/emily.css');
|
|
932
980
|
const minPath = path.join(process.cwd(), 'dist/emily.min.css');
|
|
981
|
+
|
|
982
|
+
if (!fs.existsSync(cssPath)) {
|
|
983
|
+
buildFullFramework();
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const { purgeCSS } = require('./purge.js');
|
|
987
|
+
const css = fs.readFileSync(cssPath, 'utf8');
|
|
988
|
+
const purged = purgeCSS(css, sourceDir, config);
|
|
989
|
+
const minified = minify(purged);
|
|
990
|
+
|
|
933
991
|
fs.writeFileSync(minPath, minified);
|
|
934
|
-
|
|
935
|
-
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
css,
|
|
995
|
+
purged,
|
|
996
|
+
minified,
|
|
997
|
+
originalSize: Buffer.byteLength(css, 'utf8'),
|
|
998
|
+
outputSize: Buffer.byteLength(minified, 'utf8')
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function isFrameworkStale() {
|
|
1003
|
+
const configPath = path.join(process.cwd(), 'emily.config.json');
|
|
1004
|
+
const cssPath = path.join(process.cwd(), 'dist/emily.css');
|
|
1005
|
+
|
|
1006
|
+
if (!fs.existsSync(cssPath)) return true;
|
|
1007
|
+
if (!fs.existsSync(configPath)) return true;
|
|
1008
|
+
|
|
1009
|
+
return fs.statSync(configPath).mtimeMs > fs.statSync(cssPath).mtimeMs;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
function ensureFullFramework() {
|
|
1013
|
+
if (isFrameworkStale()) {
|
|
1014
|
+
buildFullFramework();
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function build(options = {}) {
|
|
1019
|
+
ensureFullFramework();
|
|
1020
|
+
|
|
1021
|
+
const result = buildProductionCss();
|
|
1022
|
+
const cssPath = path.join(process.cwd(), 'dist/emily.css');
|
|
1023
|
+
|
|
1024
|
+
console.log('✓ Generated production CSS: dist/emily.min.css');
|
|
1025
|
+
console.log('✓ File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
|
|
1026
|
+
|
|
1027
|
+
if (!options.keepFull && fs.existsSync(cssPath)) {
|
|
1028
|
+
try {
|
|
1029
|
+
fs.unlinkSync(cssPath);
|
|
1030
|
+
console.log('Removed dist/emily.css for production build.');
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
// Windows FUSE: can't delete, non-fatal
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
936
1035
|
|
|
937
1036
|
console.log('Build complete');
|
|
938
1037
|
}
|
|
939
1038
|
|
|
940
1039
|
if (require.main === module) {
|
|
941
1040
|
const args = process.argv.slice(2);
|
|
942
|
-
|
|
943
|
-
const purgeDir = purgeIndex !== -1 ? args[purgeIndex + 1] : null;
|
|
944
|
-
build(purgeDir ? { purge: purgeDir } : {});
|
|
1041
|
+
build({ keepFull: args.includes('--keep-full') });
|
|
945
1042
|
}
|
|
946
1043
|
|
|
947
1044
|
module.exports = {
|
|
948
1045
|
build,
|
|
1046
|
+
buildFullFramework,
|
|
1047
|
+
buildProductionCss,
|
|
1048
|
+
ensureFullFramework,
|
|
949
1049
|
hexToOklch,
|
|
950
1050
|
oklchToHex,
|
|
951
1051
|
generateColourScale,
|
|
@@ -958,4 +1058,5 @@ module.exports = {
|
|
|
958
1058
|
addStateVariants,
|
|
959
1059
|
addResponsiveVariants,
|
|
960
1060
|
generateFontCSS,
|
|
1061
|
+
codeUtilities,
|
|
961
1062
|
};
|