@nghitrum/dsforge 0.1.5-alpha.6 → 0.1.5-alpha.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/README.md +0 -5
- package/dist/chunk-JUMR3N5J.js +112 -0
- package/dist/cli/index.js +116 -92
- package/dist/{html-BJBKRTSX.js → html-LQHDCSG4.js} +109 -20
- package/dist/index.d.ts +12 -0
- package/dist/index.js +114 -27
- package/package.json +1 -1
- package/dist/chunk-OAMEFG6Q.js +0 -28
package/README.md
CHANGED
|
@@ -24,7 +24,6 @@ design-system.config.json → dsforge generate
|
|
|
24
24
|
dist-ds/
|
|
25
25
|
├── src/ 9 typed React components
|
|
26
26
|
├── tokens/ CSS custom properties, JS map, Tailwind extension
|
|
27
|
-
├── docs/ MDX documentation per component
|
|
28
27
|
├── metadata/ AI-readable JSON contracts per component
|
|
29
28
|
└── showcase.html visual docs — open directly in the browser, no server
|
|
30
29
|
```
|
|
@@ -97,10 +96,6 @@ Each component is typed, themed with your actual tokens, and ships with a prop t
|
|
|
97
96
|
`tokens.js` — JS token map for runtime use
|
|
98
97
|
`tailwind.js` — Tailwind theme extension, ready to drop into `tailwind.config.js`
|
|
99
98
|
|
|
100
|
-
### MDX docs
|
|
101
|
-
|
|
102
|
-
One `.mdx` file per component, generated from your config. Import them into any docs site.
|
|
103
|
-
|
|
104
99
|
### AI metadata contracts
|
|
105
100
|
|
|
106
101
|
Each component emits `dist-ds/metadata/<component>.json`:
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/lib/license.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
function readKeyFromDotEnv() {
|
|
5
|
+
try {
|
|
6
|
+
const content = readFileSync(join(process.cwd(), ".env"), "utf8");
|
|
7
|
+
for (const raw of content.split("\n")) {
|
|
8
|
+
const line = raw.trim();
|
|
9
|
+
if (!line || line.startsWith("#")) continue;
|
|
10
|
+
const eq = line.indexOf("=");
|
|
11
|
+
if (eq === -1) continue;
|
|
12
|
+
const key = line.slice(0, eq).trim();
|
|
13
|
+
if (key !== "DSFORGE_KEY") continue;
|
|
14
|
+
const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
15
|
+
return val || void 0;
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
function isProUnlocked() {
|
|
22
|
+
const key = process.env["DSFORGE_KEY"] ?? readKeyFromDotEnv();
|
|
23
|
+
return typeof key === "string" && key.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/presets/index.ts
|
|
27
|
+
var PRESETS = [
|
|
28
|
+
"compact",
|
|
29
|
+
"comfortable",
|
|
30
|
+
"spacious"
|
|
31
|
+
];
|
|
32
|
+
var SPACING_PRESETS = {
|
|
33
|
+
compact: {
|
|
34
|
+
"1": 2,
|
|
35
|
+
"2": 4,
|
|
36
|
+
"3": 8,
|
|
37
|
+
"4": 12,
|
|
38
|
+
"5": 16,
|
|
39
|
+
"6": 24,
|
|
40
|
+
"7": 32,
|
|
41
|
+
"8": 48
|
|
42
|
+
},
|
|
43
|
+
comfortable: {
|
|
44
|
+
"1": 4,
|
|
45
|
+
"2": 8,
|
|
46
|
+
"3": 12,
|
|
47
|
+
"4": 16,
|
|
48
|
+
"5": 24,
|
|
49
|
+
"6": 32,
|
|
50
|
+
"7": 48,
|
|
51
|
+
"8": 64
|
|
52
|
+
},
|
|
53
|
+
spacious: {
|
|
54
|
+
"1": 6,
|
|
55
|
+
"2": 12,
|
|
56
|
+
"3": 18,
|
|
57
|
+
"4": 24,
|
|
58
|
+
"5": 36,
|
|
59
|
+
"6": 48,
|
|
60
|
+
"7": 72,
|
|
61
|
+
"8": 96
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var RADIUS_PRESETS = {
|
|
65
|
+
compact: { none: 0, sm: 2, md: 3, lg: 6, xl: 10, full: 9999 },
|
|
66
|
+
comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
|
|
67
|
+
spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
|
|
68
|
+
};
|
|
69
|
+
var PRESET_BASE_UNITS = {
|
|
70
|
+
compact: 2,
|
|
71
|
+
comfortable: 4,
|
|
72
|
+
spacious: 6
|
|
73
|
+
};
|
|
74
|
+
function buildSemanticSpacing(scale) {
|
|
75
|
+
return {
|
|
76
|
+
"component-padding-xs": `${scale["1"]}`,
|
|
77
|
+
"component-padding-sm": `${scale["2"]}`,
|
|
78
|
+
"component-padding-md": `${scale["4"]}`,
|
|
79
|
+
"component-padding-lg": `${scale["5"]}`,
|
|
80
|
+
"layout-gap-xs": `${scale["2"]}`,
|
|
81
|
+
"layout-gap-sm": `${scale["3"]}`,
|
|
82
|
+
"layout-gap-md": `${scale["5"]}`,
|
|
83
|
+
"layout-gap-lg": `${scale["6"]}`,
|
|
84
|
+
"layout-section": `${scale["7"]}`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function applyPreset(config, preset) {
|
|
88
|
+
const scale = SPACING_PRESETS[preset];
|
|
89
|
+
const radius = RADIUS_PRESETS[preset];
|
|
90
|
+
const baseUnit = PRESET_BASE_UNITS[preset];
|
|
91
|
+
config.spacing = {
|
|
92
|
+
...config.spacing,
|
|
93
|
+
baseUnit,
|
|
94
|
+
scale,
|
|
95
|
+
semantic: buildSemanticSpacing(scale)
|
|
96
|
+
};
|
|
97
|
+
config.radius = { ...config.radius, ...radius };
|
|
98
|
+
config.philosophy = {
|
|
99
|
+
...config.philosophy,
|
|
100
|
+
density: preset
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
isProUnlocked,
|
|
106
|
+
PRESETS,
|
|
107
|
+
SPACING_PRESETS,
|
|
108
|
+
RADIUS_PRESETS,
|
|
109
|
+
PRESET_BASE_UNITS,
|
|
110
|
+
buildSemanticSpacing,
|
|
111
|
+
applyPreset
|
|
112
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -5,8 +5,14 @@ import {
|
|
|
5
5
|
generateTsConfig
|
|
6
6
|
} from "../chunk-QHE35QQQ.js";
|
|
7
7
|
import {
|
|
8
|
+
PRESETS,
|
|
9
|
+
PRESET_BASE_UNITS,
|
|
10
|
+
RADIUS_PRESETS,
|
|
11
|
+
SPACING_PRESETS,
|
|
12
|
+
applyPreset,
|
|
13
|
+
buildSemanticSpacing,
|
|
8
14
|
isProUnlocked
|
|
9
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-JUMR3N5J.js";
|
|
10
16
|
|
|
11
17
|
// src/cli/index.ts
|
|
12
18
|
import { program } from "commander";
|
|
@@ -402,69 +408,6 @@ async function confirm(question) {
|
|
|
402
408
|
}
|
|
403
409
|
|
|
404
410
|
// src/cli/commands/init.ts
|
|
405
|
-
var SPACING_PRESETS = {
|
|
406
|
-
compact: {
|
|
407
|
-
"1": 2,
|
|
408
|
-
"2": 4,
|
|
409
|
-
"3": 8,
|
|
410
|
-
"4": 12,
|
|
411
|
-
"5": 16,
|
|
412
|
-
"6": 24,
|
|
413
|
-
"7": 32,
|
|
414
|
-
"8": 48
|
|
415
|
-
},
|
|
416
|
-
comfortable: {
|
|
417
|
-
"1": 4,
|
|
418
|
-
"2": 8,
|
|
419
|
-
"3": 12,
|
|
420
|
-
"4": 16,
|
|
421
|
-
"5": 24,
|
|
422
|
-
"6": 32,
|
|
423
|
-
"7": 48,
|
|
424
|
-
"8": 64
|
|
425
|
-
},
|
|
426
|
-
spacious: {
|
|
427
|
-
"1": 6,
|
|
428
|
-
"2": 12,
|
|
429
|
-
"3": 18,
|
|
430
|
-
"4": 24,
|
|
431
|
-
"5": 36,
|
|
432
|
-
"6": 48,
|
|
433
|
-
"7": 72,
|
|
434
|
-
"8": 96
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
var RADIUS_PRESETS = {
|
|
438
|
-
compact: { none: 0, sm: 2, md: 3, lg: 6, xl: 10, full: 9999 },
|
|
439
|
-
comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
|
|
440
|
-
spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
|
|
441
|
-
};
|
|
442
|
-
function applyPreset(config, preset) {
|
|
443
|
-
const spacing = SPACING_PRESETS[preset];
|
|
444
|
-
const radius = RADIUS_PRESETS[preset];
|
|
445
|
-
const baseUnit = preset === "compact" ? 2 : preset === "spacious" ? 6 : 4;
|
|
446
|
-
config.spacing = {
|
|
447
|
-
...config.spacing,
|
|
448
|
-
baseUnit,
|
|
449
|
-
scale: spacing,
|
|
450
|
-
semantic: {
|
|
451
|
-
"component-padding-xs": `${spacing["1"]}`,
|
|
452
|
-
"component-padding-sm": `${spacing["2"]}`,
|
|
453
|
-
"component-padding-md": `${spacing["4"]}`,
|
|
454
|
-
"component-padding-lg": `${spacing["5"]}`,
|
|
455
|
-
"layout-gap-xs": `${spacing["2"]}`,
|
|
456
|
-
"layout-gap-sm": `${spacing["3"]}`,
|
|
457
|
-
"layout-gap-md": `${spacing["5"]}`,
|
|
458
|
-
"layout-gap-lg": `${spacing["6"]}`,
|
|
459
|
-
"layout-section": `${spacing["7"]}`
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
config.radius = { ...config.radius, ...radius };
|
|
463
|
-
config.philosophy = {
|
|
464
|
-
...config.philosophy,
|
|
465
|
-
density: preset
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
411
|
function buildInitialConfig(name, preset = "comfortable") {
|
|
469
412
|
const spacing = SPACING_PRESETS[preset];
|
|
470
413
|
const radius = RADIUS_PRESETS[preset];
|
|
@@ -655,19 +598,9 @@ function buildInitialConfig(name, preset = "comfortable") {
|
|
|
655
598
|
}
|
|
656
599
|
},
|
|
657
600
|
spacing: {
|
|
658
|
-
baseUnit: preset
|
|
601
|
+
baseUnit: PRESET_BASE_UNITS[preset],
|
|
659
602
|
scale: spacing,
|
|
660
|
-
semantic:
|
|
661
|
-
"component-padding-xs": `${spacing[1]}`,
|
|
662
|
-
"component-padding-sm": `${spacing[2]}`,
|
|
663
|
-
"component-padding-md": `${spacing[4]}`,
|
|
664
|
-
"component-padding-lg": `${spacing[5]}`,
|
|
665
|
-
"layout-gap-xs": `${spacing[2]}`,
|
|
666
|
-
"layout-gap-sm": `${spacing[3]}`,
|
|
667
|
-
"layout-gap-md": `${spacing[5]}`,
|
|
668
|
-
"layout-gap-lg": `${spacing[6]}`,
|
|
669
|
-
"layout-section": `${spacing[7]}`
|
|
670
|
-
}
|
|
603
|
+
semantic: buildSemanticSpacing(spacing)
|
|
671
604
|
},
|
|
672
605
|
radius,
|
|
673
606
|
elevation: {
|
|
@@ -1898,6 +1831,34 @@ function emitThemeCss(themeName, themeOverrides, config) {
|
|
|
1898
1831
|
lines.push(emitBlock(`:root[data-theme="${themeName}"]`, entries));
|
|
1899
1832
|
return lines.join("\n") + "\n";
|
|
1900
1833
|
}
|
|
1834
|
+
function emitDensityCss(config) {
|
|
1835
|
+
const lines = [
|
|
1836
|
+
`/* \u2500\u2500\u2500 ${config.meta.name} \u2014 density presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */`,
|
|
1837
|
+
`/* Generated by dsforge. Do not edit manually. */`,
|
|
1838
|
+
`/* Pro feature: import this file to enable runtime density switching. */`,
|
|
1839
|
+
`/* Usage: <html data-density="compact | comfortable | spacious"> */`,
|
|
1840
|
+
`/* or wrap with <DensityProvider density="compact"> */`,
|
|
1841
|
+
""
|
|
1842
|
+
];
|
|
1843
|
+
for (const preset of PRESETS) {
|
|
1844
|
+
const scale = SPACING_PRESETS[preset];
|
|
1845
|
+
const radius = RADIUS_PRESETS[preset];
|
|
1846
|
+
const semantic = buildSemanticSpacing(scale);
|
|
1847
|
+
const entries = [];
|
|
1848
|
+
for (const [key, value] of Object.entries(scale)) {
|
|
1849
|
+
entries.push([`spacing-${key}`, `${value}px`]);
|
|
1850
|
+
}
|
|
1851
|
+
for (const [key, value] of Object.entries(semantic)) {
|
|
1852
|
+
entries.push([key, `${value}px`]);
|
|
1853
|
+
}
|
|
1854
|
+
for (const [key, value] of Object.entries(radius)) {
|
|
1855
|
+
entries.push([`radius-${key}`, value === 9999 ? "9999px" : `${value}px`]);
|
|
1856
|
+
}
|
|
1857
|
+
lines.push(emitBlock(`[data-density="${preset}"]`, entries, `Preset: ${preset}`));
|
|
1858
|
+
lines.push("");
|
|
1859
|
+
}
|
|
1860
|
+
return lines.join("\n");
|
|
1861
|
+
}
|
|
1901
1862
|
function generateCssFiles(config, resolution) {
|
|
1902
1863
|
const files = [];
|
|
1903
1864
|
files.push({
|
|
@@ -4125,6 +4086,62 @@ function generateThemeProvider(config) {
|
|
|
4125
4086
|
const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
|
|
4126
4087
|
const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
|
|
4127
4088
|
const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
|
|
4089
|
+
const isPro = isProUnlocked();
|
|
4090
|
+
const defaultDensity = config.meta.preset ?? "comfortable";
|
|
4091
|
+
const densityImport = isPro ? `
|
|
4092
|
+
import "../tokens/density.css";` : "";
|
|
4093
|
+
const densityTypes = isPro ? `
|
|
4094
|
+
export type DensityName = "compact" | "comfortable" | "spacious";
|
|
4095
|
+
` : "";
|
|
4096
|
+
const densityContextTypes = isPro ? `
|
|
4097
|
+
export interface DensityContextValue {
|
|
4098
|
+
density: DensityName;
|
|
4099
|
+
setDensity: (density: DensityName) => void;
|
|
4100
|
+
}
|
|
4101
|
+
` : "";
|
|
4102
|
+
const densityContext = isPro ? `
|
|
4103
|
+
export const DensityContext = React.createContext<DensityContextValue>({
|
|
4104
|
+
density: "${defaultDensity}",
|
|
4105
|
+
setDensity: () => undefined,
|
|
4106
|
+
});
|
|
4107
|
+
|
|
4108
|
+
/**
|
|
4109
|
+
* Hook to read and change the current density.
|
|
4110
|
+
* Must be used inside a <ThemeProvider>.
|
|
4111
|
+
*/
|
|
4112
|
+
export function useDensity(): DensityContextValue {
|
|
4113
|
+
return React.useContext(DensityContext);
|
|
4114
|
+
}
|
|
4115
|
+
` : "";
|
|
4116
|
+
const densityProp = isPro ? `
|
|
4117
|
+
/** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
|
|
4118
|
+
density?: DensityName;` : "";
|
|
4119
|
+
const densityOnChangeProp = isPro ? `
|
|
4120
|
+
/** Called when setDensity is invoked. */
|
|
4121
|
+
onDensityChange?: (density: DensityName) => void;` : "";
|
|
4122
|
+
const densityState = isPro ? `
|
|
4123
|
+
const [density, setDensityState] = React.useState<DensityName>(initialDensity);
|
|
4124
|
+
|
|
4125
|
+
React.useEffect(() => {
|
|
4126
|
+
setDensityState(initialDensity);
|
|
4127
|
+
}, [initialDensity]);
|
|
4128
|
+
|
|
4129
|
+
const setDensity = React.useCallback(
|
|
4130
|
+
(next: DensityName) => {
|
|
4131
|
+
setDensityState(next);
|
|
4132
|
+
onDensityChange?.(next);
|
|
4133
|
+
},
|
|
4134
|
+
[onDensityChange],
|
|
4135
|
+
);
|
|
4136
|
+
` : "";
|
|
4137
|
+
const densityDestructure = isPro ? `,
|
|
4138
|
+
density: initialDensity = "${defaultDensity}",
|
|
4139
|
+
onDensityChange,` : "";
|
|
4140
|
+
const densityProviderOpen = isPro ? `
|
|
4141
|
+
<DensityContext.Provider value={{ density, setDensity }}>` : "";
|
|
4142
|
+
const densityDataAttr = isPro ? ` data-density={density}` : "";
|
|
4143
|
+
const densityProviderClose = isPro ? `
|
|
4144
|
+
</DensityContext.Provider>` : "";
|
|
4128
4145
|
return `/**
|
|
4129
4146
|
* ThemeProvider \u2014 ${config.meta.name}
|
|
4130
4147
|
*
|
|
@@ -4136,27 +4153,27 @@ function generateThemeProvider(config) {
|
|
|
4136
4153
|
* import "@${config.meta.name}/tokens/light.css"; // or dark.css
|
|
4137
4154
|
* import { ThemeProvider } from "@${config.meta.name}";
|
|
4138
4155
|
*
|
|
4139
|
-
* <ThemeProvider theme="light">
|
|
4156
|
+
* <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
|
|
4140
4157
|
* <App />
|
|
4141
4158
|
* </ThemeProvider>
|
|
4142
4159
|
*/
|
|
4143
4160
|
|
|
4144
|
-
import React from "react"
|
|
4161
|
+
import React from "react";${densityImport}
|
|
4145
4162
|
|
|
4146
4163
|
// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4147
4164
|
|
|
4148
4165
|
export type ThemeName = ${themeType};
|
|
4149
|
-
|
|
4166
|
+
${densityTypes}
|
|
4150
4167
|
export interface ThemeContextValue {
|
|
4151
4168
|
theme: ThemeName;
|
|
4152
4169
|
setTheme: (theme: ThemeName) => void;
|
|
4153
4170
|
}
|
|
4154
|
-
|
|
4171
|
+
${densityContextTypes}
|
|
4155
4172
|
export interface ThemeProviderProps {
|
|
4156
4173
|
/** Initial theme. Defaults to "${defaultTheme}". */
|
|
4157
4174
|
theme?: ThemeName;
|
|
4158
4175
|
/** Called when setTheme is invoked \u2014 use to persist theme preference. */
|
|
4159
|
-
onThemeChange?: (theme: ThemeName) => void
|
|
4176
|
+
onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
|
|
4160
4177
|
children: React.ReactNode;
|
|
4161
4178
|
}
|
|
4162
4179
|
|
|
@@ -4174,12 +4191,12 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
|
|
|
4174
4191
|
export function useTheme(): ThemeContextValue {
|
|
4175
4192
|
return React.useContext(ThemeContext);
|
|
4176
4193
|
}
|
|
4177
|
-
|
|
4194
|
+
${densityContext}
|
|
4178
4195
|
// \u2500\u2500\u2500 Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4179
4196
|
|
|
4180
4197
|
export function ThemeProvider({
|
|
4181
4198
|
theme: initialTheme = "${defaultTheme}",
|
|
4182
|
-
onThemeChange
|
|
4199
|
+
onThemeChange,${densityDestructure}
|
|
4183
4200
|
children,
|
|
4184
4201
|
}: ThemeProviderProps) {
|
|
4185
4202
|
const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
|
|
@@ -4195,13 +4212,13 @@ export function ThemeProvider({
|
|
|
4195
4212
|
},
|
|
4196
4213
|
[onThemeChange],
|
|
4197
4214
|
);
|
|
4198
|
-
|
|
4199
|
-
return (
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4215
|
+
${densityState}
|
|
4216
|
+
return (${densityProviderOpen}
|
|
4217
|
+
<ThemeContext.Provider value={{ theme, setTheme }}>
|
|
4218
|
+
<div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
|
|
4219
|
+
{children}
|
|
4220
|
+
</div>
|
|
4221
|
+
</ThemeContext.Provider>${densityProviderClose}
|
|
4205
4222
|
);
|
|
4206
4223
|
}
|
|
4207
4224
|
`;
|
|
@@ -4858,6 +4875,13 @@ async function runGenerate(cwd, options) {
|
|
|
4858
4875
|
await writeFile(path3.join(tokensDir, filename), content);
|
|
4859
4876
|
logger.dim(` \u2192 tokens/${filename}`);
|
|
4860
4877
|
}
|
|
4878
|
+
if (isProUnlocked()) {
|
|
4879
|
+
await writeFile(
|
|
4880
|
+
path3.join(tokensDir, "density.css"),
|
|
4881
|
+
emitDensityCss(config)
|
|
4882
|
+
);
|
|
4883
|
+
logger.dim(` \u2192 tokens/density.css`);
|
|
4884
|
+
}
|
|
4861
4885
|
const tokenFiles = reactAdapter.generateTokenFiles(config, resolution);
|
|
4862
4886
|
for (const { filename, content } of tokenFiles) {
|
|
4863
4887
|
await writeFile(path3.join(tokensDir, filename), content);
|
|
@@ -4932,7 +4956,7 @@ async function runGenerate(cwd, options) {
|
|
|
4932
4956
|
logger.success(`Package files written`);
|
|
4933
4957
|
}
|
|
4934
4958
|
logger.step("Generating showcase...");
|
|
4935
|
-
const { generateShowcase } = await import("../html-
|
|
4959
|
+
const { generateShowcase } = await import("../html-LQHDCSG4.js");
|
|
4936
4960
|
const showcaseHtml = generateShowcase(config, resolution);
|
|
4937
4961
|
await writeFile(path3.join(outRoot, "showcase.html"), showcaseHtml);
|
|
4938
4962
|
logger.dim(` \u2192 showcase.html`);
|
|
@@ -5319,7 +5343,7 @@ async function runMenu() {
|
|
|
5319
5343
|
// package.json
|
|
5320
5344
|
var package_default = {
|
|
5321
5345
|
name: "@nghitrum/dsforge",
|
|
5322
|
-
version: "0.1.5-alpha.
|
|
5346
|
+
version: "0.1.5-alpha.8",
|
|
5323
5347
|
description: "AI-native design system generator \u2014 tokens \u2192 components \u2192 docs \u2192 npm",
|
|
5324
5348
|
keywords: [
|
|
5325
5349
|
"design-system",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
PRESETS,
|
|
3
|
+
RADIUS_PRESETS,
|
|
4
|
+
SPACING_PRESETS,
|
|
5
|
+
buildSemanticSpacing,
|
|
2
6
|
isProUnlocked
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JUMR3N5J.js";
|
|
4
8
|
|
|
5
9
|
// src/generators/showcase/types.ts
|
|
6
10
|
function esc(s) {
|
|
@@ -96,26 +100,33 @@ function buildTypographySection(config) {
|
|
|
96
100
|
}
|
|
97
101
|
function buildSpacingSection(config) {
|
|
98
102
|
const scale = config.spacing?.scale ?? {};
|
|
99
|
-
const
|
|
100
|
-
|
|
103
|
+
const scaleRow = (key) => `
|
|
104
|
+
<div class="spacing-row">
|
|
105
|
+
<span class="spacing-key">spacing-${esc(key)}</span>
|
|
106
|
+
<div class="spacing-bar-wrap">
|
|
107
|
+
<div class="spacing-bar" style="width:min(calc(var(--spacing-${esc(key)}) * 2), 320px)"></div>
|
|
108
|
+
</div>
|
|
109
|
+
<span class="spacing-val" data-spacing-var="--spacing-${esc(key)}"></span>
|
|
110
|
+
</div>`;
|
|
111
|
+
const semanticRow = (key) => `
|
|
101
112
|
<div class="spacing-row">
|
|
102
113
|
<span class="spacing-key">${esc(key)}</span>
|
|
103
114
|
<div class="spacing-bar-wrap">
|
|
104
|
-
<div class="spacing-bar" style="width
|
|
115
|
+
<div class="spacing-bar" style="width:min(calc(var(--${esc(key)}) * 2), 320px)"></div>
|
|
105
116
|
</div>
|
|
106
|
-
<span class="spacing-val"
|
|
117
|
+
<span class="spacing-val" data-spacing-var="--${esc(key)}"></span>
|
|
107
118
|
</div>`;
|
|
108
119
|
return `
|
|
109
120
|
<div class="section-block">
|
|
110
|
-
<h3 class="group-title">
|
|
121
|
+
<h3 class="group-title">Scale</h3>
|
|
111
122
|
<div class="spacing-list">
|
|
112
|
-
${Object.
|
|
123
|
+
${Object.keys(scale).map((k) => scaleRow(k)).join("")}
|
|
113
124
|
</div>
|
|
114
125
|
</div>
|
|
115
126
|
<div class="section-block">
|
|
116
127
|
<h3 class="group-title">Semantic Spacing</h3>
|
|
117
128
|
<div class="spacing-list">
|
|
118
|
-
${Object.
|
|
129
|
+
${Object.keys(config.spacing?.semantic ?? {}).map((k) => semanticRow(k)).join("")}
|
|
119
130
|
</div>
|
|
120
131
|
</div>
|
|
121
132
|
`;
|
|
@@ -126,12 +137,12 @@ function buildRadiusSection(config) {
|
|
|
126
137
|
<div class="section-block">
|
|
127
138
|
<h3 class="group-title">Border Radius</h3>
|
|
128
139
|
<div class="radius-grid">
|
|
129
|
-
${Object.
|
|
130
|
-
(
|
|
140
|
+
${Object.keys(radius).map(
|
|
141
|
+
(key) => `
|
|
131
142
|
<div class="radius-item">
|
|
132
|
-
<div class="radius-box" style="border-radius
|
|
143
|
+
<div class="radius-box" style="border-radius:var(--radius-${esc(key)})"></div>
|
|
133
144
|
<span class="radius-key">${esc(key)}</span>
|
|
134
|
-
<span class="radius-val"
|
|
145
|
+
<span class="radius-val" data-spacing-var="--radius-${esc(key)}"></span>
|
|
135
146
|
</div>
|
|
136
147
|
`
|
|
137
148
|
).join("")}
|
|
@@ -1983,11 +1994,29 @@ var SHOWCASE_COMPONENTS = [
|
|
|
1983
1994
|
];
|
|
1984
1995
|
|
|
1985
1996
|
// src/generators/showcase/html.ts
|
|
1997
|
+
function buildDensityCss() {
|
|
1998
|
+
const blocks = [];
|
|
1999
|
+
for (const preset of PRESETS) {
|
|
2000
|
+
const scale = SPACING_PRESETS[preset];
|
|
2001
|
+
const radius = RADIUS_PRESETS[preset];
|
|
2002
|
+
const semantic = buildSemanticSpacing(scale);
|
|
2003
|
+
const vars = [];
|
|
2004
|
+
for (const [k, v] of Object.entries(scale)) vars.push(` --spacing-${k}: ${v}px;`);
|
|
2005
|
+
for (const [k, v] of Object.entries(semantic)) vars.push(` --${k}: ${v}px;`);
|
|
2006
|
+
for (const [k, v] of Object.entries(radius))
|
|
2007
|
+
vars.push(` --radius-${k}: ${v === 9999 ? "9999px" : `${v}px`};`);
|
|
2008
|
+
blocks.push(` [data-density="${preset}"] {
|
|
2009
|
+
${vars.join("\n")}
|
|
2010
|
+
}`);
|
|
2011
|
+
}
|
|
2012
|
+
return blocks.join("\n");
|
|
2013
|
+
}
|
|
1986
2014
|
function generateShowcase(config, resolution) {
|
|
1987
2015
|
const tokens = resolution.tokens;
|
|
1988
2016
|
const name = config.meta?.name ?? "Design System";
|
|
1989
2017
|
const version = config.meta?.version ?? "0.1.0";
|
|
1990
2018
|
const themes = Object.keys(config.themes ?? {});
|
|
2019
|
+
const defaultDensity = config.meta?.preset ?? "comfortable";
|
|
1991
2020
|
const foundationItems = [
|
|
1992
2021
|
{ id: "colors", label: "Colors" },
|
|
1993
2022
|
{ id: "typography", label: "Typography" },
|
|
@@ -2023,8 +2052,9 @@ function generateShowcase(config, resolution) {
|
|
|
2023
2052
|
const darkTheme = config.themes?.["dark"] ?? {};
|
|
2024
2053
|
const themeCssLight = Object.entries({ ...flatTokens, ...lightTheme }).map(([k, v]) => ` --${k}: ${v};`).join("\n");
|
|
2025
2054
|
const themeCssDark = Object.entries({ ...flatTokens, ...darkTheme }).map(([k, v]) => ` --${k}: ${v};`).join("\n");
|
|
2055
|
+
const densityCss = buildDensityCss();
|
|
2026
2056
|
return `<!DOCTYPE html>
|
|
2027
|
-
<html lang="en" data-theme="light">
|
|
2057
|
+
<html lang="en" data-theme="light" data-density="${defaultDensity}">
|
|
2028
2058
|
<head>
|
|
2029
2059
|
<meta charset="UTF-8" />
|
|
2030
2060
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
@@ -2039,6 +2069,9 @@ ${themeCssLight}
|
|
|
2039
2069
|
${themeCssDark}
|
|
2040
2070
|
}
|
|
2041
2071
|
|
|
2072
|
+
/* \u2500\u2500 Density presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2073
|
+
${densityCss}
|
|
2074
|
+
|
|
2042
2075
|
/* \u2500\u2500 Reset + base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2043
2076
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2044
2077
|
html { font-size: 16px; }
|
|
@@ -2104,6 +2137,33 @@ ${themeCssDark}
|
|
|
2104
2137
|
color: var(--color-text-primary, #0f172a); font-weight: 500;
|
|
2105
2138
|
box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
|
|
2106
2139
|
}
|
|
2140
|
+
.density-toggle {
|
|
2141
|
+
display: flex; gap: 3px; align-items: center;
|
|
2142
|
+
background: var(--color-bg-subtle, #f8fafc);
|
|
2143
|
+
border: 1px solid var(--color-border-default, #e2e8f0);
|
|
2144
|
+
border-radius: 7px; padding: 3px;
|
|
2145
|
+
}
|
|
2146
|
+
.density-toggle.locked { opacity: 0.5; cursor: not-allowed; }
|
|
2147
|
+
.density-btn {
|
|
2148
|
+
padding: 3px 10px; border-radius: 4px; border: none;
|
|
2149
|
+
background: transparent; font-size: 12px; cursor: pointer;
|
|
2150
|
+
color: var(--color-text-secondary, #64748b);
|
|
2151
|
+
transition: background 120ms, color 120ms;
|
|
2152
|
+
}
|
|
2153
|
+
.density-btn:disabled { cursor: not-allowed; }
|
|
2154
|
+
.density-btn.active {
|
|
2155
|
+
background: var(--color-bg-default, #fff);
|
|
2156
|
+
color: var(--color-text-primary, #0f172a); font-weight: 500;
|
|
2157
|
+
box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
|
|
2158
|
+
}
|
|
2159
|
+
.density-lock {
|
|
2160
|
+
font-size: 10px; font-weight: 600; letter-spacing: 0.04em;
|
|
2161
|
+
color: var(--color-text-secondary, #64748b);
|
|
2162
|
+
padding: 2px 6px; border-radius: 4px;
|
|
2163
|
+
background: var(--color-bg-overlay, #f1f5f9);
|
|
2164
|
+
border: 1px solid var(--color-border-default, #e2e8f0);
|
|
2165
|
+
white-space: nowrap;
|
|
2166
|
+
}
|
|
2107
2167
|
.content { padding: 36px 40px 80px; max-width: 860px; }
|
|
2108
2168
|
.page { display: none; }
|
|
2109
2169
|
.page.active { display: block; }
|
|
@@ -2307,16 +2367,16 @@ ${themeCssDark}
|
|
|
2307
2367
|
.locked-hint code { background: var(--color-bg-overlay, #f1f5f9); padding: 1px 6px; border-radius: 4px; border: 1px solid var(--color-border-default, #e2e8f0); }
|
|
2308
2368
|
|
|
2309
2369
|
/* \u2500\u2500 Component primitives \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2310
|
-
.ds-btn { border: none; cursor: pointer; font-size: 14px; font-weight: 500; padding: 8px 16px; transition: filter 120ms; }
|
|
2370
|
+
.ds-btn { border: none; cursor: pointer; font-size: 14px; font-weight: 500; padding: var(--component-padding-sm, 8px) var(--component-padding-md, 16px); border-radius: var(--radius-md, 4px); transition: filter 120ms; }
|
|
2311
2371
|
.ds-btn:hover:not(:disabled) { filter: brightness(0.92); }
|
|
2312
|
-
.ds-field { display: flex; flex-direction: column; gap: 4px; }
|
|
2372
|
+
.ds-field { display: flex; flex-direction: column; gap: var(--component-padding-xs, 4px); }
|
|
2313
2373
|
.ds-label { font-size: 13px; font-weight: 500; }
|
|
2314
|
-
.ds-input { border: 1.5px solid; padding: 8px 12px; font-size: 14px; outline: none; transition: border-color 150ms, box-shadow 150ms; width: 100%; }
|
|
2374
|
+
.ds-input { border: 1.5px solid; padding: var(--component-padding-sm, 8px) var(--component-padding-sm, 12px); font-size: 14px; outline: none; border-radius: var(--radius-sm, 2px); transition: border-color 150ms, box-shadow 150ms; width: 100%; }
|
|
2315
2375
|
.ds-input:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-action, #2563eb) 20%, transparent); border-color: var(--color-action, #2563eb) !important; }
|
|
2316
|
-
.ds-card { border: 1px solid; overflow: hidden; width: 220px; }
|
|
2317
|
-
.ds-card-header { padding: 12px 14px; font-size: 14px; font-weight: 600; }
|
|
2318
|
-
.ds-card-body { padding: 12px 14px; }
|
|
2319
|
-
.ds-card-footer { padding: 10px 14px; display: flex; justify-content: flex-end; }
|
|
2376
|
+
.ds-card { border: 1px solid; overflow: hidden; width: 220px; border-radius: var(--radius-lg, 8px); }
|
|
2377
|
+
.ds-card-header { padding: var(--component-padding-sm, 12px) var(--component-padding-sm, 14px); font-size: 14px; font-weight: 600; }
|
|
2378
|
+
.ds-card-body { padding: var(--component-padding-sm, 12px) var(--component-padding-sm, 14px); }
|
|
2379
|
+
.ds-card-footer { padding: var(--component-padding-xs, 10px) var(--component-padding-sm, 14px); display: flex; justify-content: flex-end; }
|
|
2320
2380
|
|
|
2321
2381
|
/* \u2500\u2500 Component docs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
2322
2382
|
.component-docs { margin-top: 36px; }
|
|
@@ -2375,6 +2435,16 @@ ${themeCssDark}
|
|
|
2375
2435
|
${esc(name)} / <span id="topbar-current">Colors</span>
|
|
2376
2436
|
</div>
|
|
2377
2437
|
<div class="topbar-actions">
|
|
2438
|
+
${isPro ? `<div class="density-toggle" id="density-toggle">
|
|
2439
|
+
${PRESETS.map((p) => `
|
|
2440
|
+
<button class="density-btn${p === defaultDensity ? " active" : ""}" onclick="setDensity('${p}', this)">${p.charAt(0).toUpperCase() + p.slice(1)}</button>
|
|
2441
|
+
`).join("")}
|
|
2442
|
+
</div>` : `<div class="density-toggle locked" title="Density switching requires dsforge Pro. Set DSFORGE_KEY to unlock.">
|
|
2443
|
+
${PRESETS.map((p) => `
|
|
2444
|
+
<button class="density-btn${p === defaultDensity ? " active" : ""}" disabled>${p.charAt(0).toUpperCase() + p.slice(1)}</button>
|
|
2445
|
+
`).join("")}
|
|
2446
|
+
<span class="density-lock">\u2298 Pro</span>
|
|
2447
|
+
</div>`}
|
|
2378
2448
|
${themes.length >= 2 ? `
|
|
2379
2449
|
<div class="theme-toggle">
|
|
2380
2450
|
${themes.map(
|
|
@@ -2415,6 +2485,22 @@ ${themeCssDark}
|
|
|
2415
2485
|
btn.classList.add('active');
|
|
2416
2486
|
}
|
|
2417
2487
|
|
|
2488
|
+
function setDensity(name, btn) {
|
|
2489
|
+
document.documentElement.setAttribute('data-density', name);
|
|
2490
|
+
document.querySelectorAll('.density-btn').forEach(b => b.classList.remove('active'));
|
|
2491
|
+
btn.classList.add('active');
|
|
2492
|
+
updateSpacingValues();
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
function updateSpacingValues() {
|
|
2496
|
+
const style = getComputedStyle(document.documentElement);
|
|
2497
|
+
document.querySelectorAll('[data-spacing-var]').forEach(el => {
|
|
2498
|
+
const prop = el.getAttribute('data-spacing-var');
|
|
2499
|
+
const val = style.getPropertyValue(prop).trim();
|
|
2500
|
+
if (val) el.textContent = val;
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2418
2504
|
function switchTab(compId, tabId, btn) {
|
|
2419
2505
|
const tabs = document.querySelectorAll('#' + compId + '-tabs .comp-tab');
|
|
2420
2506
|
const panels = document.querySelectorAll('#' + compId + '-tabs .comp-tab-panel');
|
|
@@ -2433,6 +2519,9 @@ ${themeCssDark}
|
|
|
2433
2519
|
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
|
|
2434
2520
|
});
|
|
2435
2521
|
}
|
|
2522
|
+
|
|
2523
|
+
// Populate spacing/radius value labels on load
|
|
2524
|
+
document.addEventListener('DOMContentLoaded', updateSpacingValues);
|
|
2436
2525
|
</script>
|
|
2437
2526
|
</body>
|
|
2438
2527
|
</html>`;
|
package/dist/index.d.ts
CHANGED
|
@@ -381,7 +381,19 @@ declare function hasRefs(value: string): boolean;
|
|
|
381
381
|
|
|
382
382
|
declare function validateConfig(config: DesignSystemConfig, rules: RulesConfig): ValidationResult;
|
|
383
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Density preset definitions.
|
|
386
|
+
*
|
|
387
|
+
* This is the single source of truth for compact / comfortable / spacious
|
|
388
|
+
* spacing and radius values. Imported by:
|
|
389
|
+
* - cli/commands/init.ts (initial config scaffolding)
|
|
390
|
+
* - cli/commands/generate.ts (re-applying preset on each generate run)
|
|
391
|
+
* - generators/tokens/css-vars.ts (emitting density.css for Pro)
|
|
392
|
+
* - generators/showcase/html.ts (embedding density CSS in showcase)
|
|
393
|
+
*/
|
|
394
|
+
|
|
384
395
|
type Preset = "compact" | "comfortable" | "spacious";
|
|
396
|
+
|
|
385
397
|
declare function buildInitialConfig(name: string, preset?: Preset): DesignSystemConfig;
|
|
386
398
|
declare function buildInitialRules(): RulesConfig;
|
|
387
399
|
|
package/dist/index.js
CHANGED
|
@@ -1146,8 +1146,29 @@ var rl = readline.createInterface({
|
|
|
1146
1146
|
// src/lib/license.ts
|
|
1147
1147
|
import { readFileSync } from "fs";
|
|
1148
1148
|
import { join } from "path";
|
|
1149
|
+
function readKeyFromDotEnv() {
|
|
1150
|
+
try {
|
|
1151
|
+
const content = readFileSync(join(process.cwd(), ".env"), "utf8");
|
|
1152
|
+
for (const raw of content.split("\n")) {
|
|
1153
|
+
const line = raw.trim();
|
|
1154
|
+
if (!line || line.startsWith("#")) continue;
|
|
1155
|
+
const eq = line.indexOf("=");
|
|
1156
|
+
if (eq === -1) continue;
|
|
1157
|
+
const key = line.slice(0, eq).trim();
|
|
1158
|
+
if (key !== "DSFORGE_KEY") continue;
|
|
1159
|
+
const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
1160
|
+
return val || void 0;
|
|
1161
|
+
}
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
return void 0;
|
|
1165
|
+
}
|
|
1166
|
+
function isProUnlocked() {
|
|
1167
|
+
const key = process.env["DSFORGE_KEY"] ?? readKeyFromDotEnv();
|
|
1168
|
+
return typeof key === "string" && key.length > 0;
|
|
1169
|
+
}
|
|
1149
1170
|
|
|
1150
|
-
// src/
|
|
1171
|
+
// src/presets/index.ts
|
|
1151
1172
|
var SPACING_PRESETS = {
|
|
1152
1173
|
compact: {
|
|
1153
1174
|
"1": 2,
|
|
@@ -1185,6 +1206,26 @@ var RADIUS_PRESETS = {
|
|
|
1185
1206
|
comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
|
|
1186
1207
|
spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
|
|
1187
1208
|
};
|
|
1209
|
+
var PRESET_BASE_UNITS = {
|
|
1210
|
+
compact: 2,
|
|
1211
|
+
comfortable: 4,
|
|
1212
|
+
spacious: 6
|
|
1213
|
+
};
|
|
1214
|
+
function buildSemanticSpacing(scale) {
|
|
1215
|
+
return {
|
|
1216
|
+
"component-padding-xs": `${scale["1"]}`,
|
|
1217
|
+
"component-padding-sm": `${scale["2"]}`,
|
|
1218
|
+
"component-padding-md": `${scale["4"]}`,
|
|
1219
|
+
"component-padding-lg": `${scale["5"]}`,
|
|
1220
|
+
"layout-gap-xs": `${scale["2"]}`,
|
|
1221
|
+
"layout-gap-sm": `${scale["3"]}`,
|
|
1222
|
+
"layout-gap-md": `${scale["5"]}`,
|
|
1223
|
+
"layout-gap-lg": `${scale["6"]}`,
|
|
1224
|
+
"layout-section": `${scale["7"]}`
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// src/cli/commands/init.ts
|
|
1188
1229
|
function buildInitialConfig(name, preset = "comfortable") {
|
|
1189
1230
|
const spacing = SPACING_PRESETS[preset];
|
|
1190
1231
|
const radius = RADIUS_PRESETS[preset];
|
|
@@ -1375,19 +1416,9 @@ function buildInitialConfig(name, preset = "comfortable") {
|
|
|
1375
1416
|
}
|
|
1376
1417
|
},
|
|
1377
1418
|
spacing: {
|
|
1378
|
-
baseUnit: preset
|
|
1419
|
+
baseUnit: PRESET_BASE_UNITS[preset],
|
|
1379
1420
|
scale: spacing,
|
|
1380
|
-
semantic:
|
|
1381
|
-
"component-padding-xs": `${spacing[1]}`,
|
|
1382
|
-
"component-padding-sm": `${spacing[2]}`,
|
|
1383
|
-
"component-padding-md": `${spacing[4]}`,
|
|
1384
|
-
"component-padding-lg": `${spacing[5]}`,
|
|
1385
|
-
"layout-gap-xs": `${spacing[2]}`,
|
|
1386
|
-
"layout-gap-sm": `${spacing[3]}`,
|
|
1387
|
-
"layout-gap-md": `${spacing[5]}`,
|
|
1388
|
-
"layout-gap-lg": `${spacing[6]}`,
|
|
1389
|
-
"layout-section": `${spacing[7]}`
|
|
1390
|
-
}
|
|
1421
|
+
semantic: buildSemanticSpacing(spacing)
|
|
1391
1422
|
},
|
|
1392
1423
|
radius,
|
|
1393
1424
|
elevation: {
|
|
@@ -2563,6 +2594,62 @@ function generateThemeProvider(config) {
|
|
|
2563
2594
|
const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
|
|
2564
2595
|
const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
|
|
2565
2596
|
const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
|
|
2597
|
+
const isPro = isProUnlocked();
|
|
2598
|
+
const defaultDensity = config.meta.preset ?? "comfortable";
|
|
2599
|
+
const densityImport = isPro ? `
|
|
2600
|
+
import "../tokens/density.css";` : "";
|
|
2601
|
+
const densityTypes = isPro ? `
|
|
2602
|
+
export type DensityName = "compact" | "comfortable" | "spacious";
|
|
2603
|
+
` : "";
|
|
2604
|
+
const densityContextTypes = isPro ? `
|
|
2605
|
+
export interface DensityContextValue {
|
|
2606
|
+
density: DensityName;
|
|
2607
|
+
setDensity: (density: DensityName) => void;
|
|
2608
|
+
}
|
|
2609
|
+
` : "";
|
|
2610
|
+
const densityContext = isPro ? `
|
|
2611
|
+
export const DensityContext = React.createContext<DensityContextValue>({
|
|
2612
|
+
density: "${defaultDensity}",
|
|
2613
|
+
setDensity: () => undefined,
|
|
2614
|
+
});
|
|
2615
|
+
|
|
2616
|
+
/**
|
|
2617
|
+
* Hook to read and change the current density.
|
|
2618
|
+
* Must be used inside a <ThemeProvider>.
|
|
2619
|
+
*/
|
|
2620
|
+
export function useDensity(): DensityContextValue {
|
|
2621
|
+
return React.useContext(DensityContext);
|
|
2622
|
+
}
|
|
2623
|
+
` : "";
|
|
2624
|
+
const densityProp = isPro ? `
|
|
2625
|
+
/** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
|
|
2626
|
+
density?: DensityName;` : "";
|
|
2627
|
+
const densityOnChangeProp = isPro ? `
|
|
2628
|
+
/** Called when setDensity is invoked. */
|
|
2629
|
+
onDensityChange?: (density: DensityName) => void;` : "";
|
|
2630
|
+
const densityState = isPro ? `
|
|
2631
|
+
const [density, setDensityState] = React.useState<DensityName>(initialDensity);
|
|
2632
|
+
|
|
2633
|
+
React.useEffect(() => {
|
|
2634
|
+
setDensityState(initialDensity);
|
|
2635
|
+
}, [initialDensity]);
|
|
2636
|
+
|
|
2637
|
+
const setDensity = React.useCallback(
|
|
2638
|
+
(next: DensityName) => {
|
|
2639
|
+
setDensityState(next);
|
|
2640
|
+
onDensityChange?.(next);
|
|
2641
|
+
},
|
|
2642
|
+
[onDensityChange],
|
|
2643
|
+
);
|
|
2644
|
+
` : "";
|
|
2645
|
+
const densityDestructure = isPro ? `,
|
|
2646
|
+
density: initialDensity = "${defaultDensity}",
|
|
2647
|
+
onDensityChange,` : "";
|
|
2648
|
+
const densityProviderOpen = isPro ? `
|
|
2649
|
+
<DensityContext.Provider value={{ density, setDensity }}>` : "";
|
|
2650
|
+
const densityDataAttr = isPro ? ` data-density={density}` : "";
|
|
2651
|
+
const densityProviderClose = isPro ? `
|
|
2652
|
+
</DensityContext.Provider>` : "";
|
|
2566
2653
|
return `/**
|
|
2567
2654
|
* ThemeProvider \u2014 ${config.meta.name}
|
|
2568
2655
|
*
|
|
@@ -2574,27 +2661,27 @@ function generateThemeProvider(config) {
|
|
|
2574
2661
|
* import "@${config.meta.name}/tokens/light.css"; // or dark.css
|
|
2575
2662
|
* import { ThemeProvider } from "@${config.meta.name}";
|
|
2576
2663
|
*
|
|
2577
|
-
* <ThemeProvider theme="light">
|
|
2664
|
+
* <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
|
|
2578
2665
|
* <App />
|
|
2579
2666
|
* </ThemeProvider>
|
|
2580
2667
|
*/
|
|
2581
2668
|
|
|
2582
|
-
import React from "react"
|
|
2669
|
+
import React from "react";${densityImport}
|
|
2583
2670
|
|
|
2584
2671
|
// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2585
2672
|
|
|
2586
2673
|
export type ThemeName = ${themeType};
|
|
2587
|
-
|
|
2674
|
+
${densityTypes}
|
|
2588
2675
|
export interface ThemeContextValue {
|
|
2589
2676
|
theme: ThemeName;
|
|
2590
2677
|
setTheme: (theme: ThemeName) => void;
|
|
2591
2678
|
}
|
|
2592
|
-
|
|
2679
|
+
${densityContextTypes}
|
|
2593
2680
|
export interface ThemeProviderProps {
|
|
2594
2681
|
/** Initial theme. Defaults to "${defaultTheme}". */
|
|
2595
2682
|
theme?: ThemeName;
|
|
2596
2683
|
/** Called when setTheme is invoked \u2014 use to persist theme preference. */
|
|
2597
|
-
onThemeChange?: (theme: ThemeName) => void
|
|
2684
|
+
onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
|
|
2598
2685
|
children: React.ReactNode;
|
|
2599
2686
|
}
|
|
2600
2687
|
|
|
@@ -2612,12 +2699,12 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
|
|
|
2612
2699
|
export function useTheme(): ThemeContextValue {
|
|
2613
2700
|
return React.useContext(ThemeContext);
|
|
2614
2701
|
}
|
|
2615
|
-
|
|
2702
|
+
${densityContext}
|
|
2616
2703
|
// \u2500\u2500\u2500 Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2617
2704
|
|
|
2618
2705
|
export function ThemeProvider({
|
|
2619
2706
|
theme: initialTheme = "${defaultTheme}",
|
|
2620
|
-
onThemeChange
|
|
2707
|
+
onThemeChange,${densityDestructure}
|
|
2621
2708
|
children,
|
|
2622
2709
|
}: ThemeProviderProps) {
|
|
2623
2710
|
const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
|
|
@@ -2633,13 +2720,13 @@ export function ThemeProvider({
|
|
|
2633
2720
|
},
|
|
2634
2721
|
[onThemeChange],
|
|
2635
2722
|
);
|
|
2636
|
-
|
|
2637
|
-
return (
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2723
|
+
${densityState}
|
|
2724
|
+
return (${densityProviderOpen}
|
|
2725
|
+
<ThemeContext.Provider value={{ theme, setTheme }}>
|
|
2726
|
+
<div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
|
|
2727
|
+
{children}
|
|
2728
|
+
</div>
|
|
2729
|
+
</ThemeContext.Provider>${densityProviderClose}
|
|
2643
2730
|
);
|
|
2644
2731
|
}
|
|
2645
2732
|
`;
|
package/package.json
CHANGED
package/dist/chunk-OAMEFG6Q.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// src/lib/license.ts
|
|
2
|
-
import { readFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
function readKeyFromDotEnv() {
|
|
5
|
-
try {
|
|
6
|
-
const content = readFileSync(join(process.cwd(), ".env"), "utf8");
|
|
7
|
-
for (const raw of content.split("\n")) {
|
|
8
|
-
const line = raw.trim();
|
|
9
|
-
if (!line || line.startsWith("#")) continue;
|
|
10
|
-
const eq = line.indexOf("=");
|
|
11
|
-
if (eq === -1) continue;
|
|
12
|
-
const key = line.slice(0, eq).trim();
|
|
13
|
-
if (key !== "DSFORGE_KEY") continue;
|
|
14
|
-
const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
15
|
-
return val || void 0;
|
|
16
|
-
}
|
|
17
|
-
} catch {
|
|
18
|
-
}
|
|
19
|
-
return void 0;
|
|
20
|
-
}
|
|
21
|
-
function isProUnlocked() {
|
|
22
|
-
const key = process.env["DSFORGE_KEY"] ?? readKeyFromDotEnv();
|
|
23
|
-
return typeof key === "string" && key.length > 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export {
|
|
27
|
-
isProUnlocked
|
|
28
|
-
};
|