@nghitrum/dsforge 0.1.5-alpha.5 → 0.1.5-alpha.7
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/dist/chunk-JUMR3N5J.js +112 -0
- package/dist/{chunk-RI3XDGKU.js → chunk-QHE35QQQ.js} +1 -1
- package/dist/cli/index.js +134 -90
- package/dist/{emitter-ZNRPJ4D6.js → emitter-KNYIQTS5.js} +1 -1
- package/dist/{html-6SIG34W5.js → html-LQHDCSG4.js} +172 -45
- package/dist/index.d.ts +12 -0
- package/dist/index.js +119 -28
- package/package.json +1 -1
|
@@ -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
|
+
};
|
|
@@ -27,7 +27,7 @@ function generatePackageJson(config, componentNames) {
|
|
|
27
27
|
componentNames.map((c) => [`./metadata/${c}`, `./metadata/${c}.json`])
|
|
28
28
|
)
|
|
29
29
|
},
|
|
30
|
-
files: ["dist", "tokens", "metadata", "
|
|
30
|
+
files: ["dist", "tokens", "metadata", "CHANGELOG.md"],
|
|
31
31
|
scripts: {
|
|
32
32
|
build: "tsc",
|
|
33
33
|
prepublishOnly: "npm run build"
|
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,16 @@ import {
|
|
|
3
3
|
generateChangelog,
|
|
4
4
|
generatePackageJson,
|
|
5
5
|
generateTsConfig
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-QHE35QQQ.js";
|
|
7
|
+
import {
|
|
8
|
+
PRESETS,
|
|
9
|
+
PRESET_BASE_UNITS,
|
|
10
|
+
RADIUS_PRESETS,
|
|
11
|
+
SPACING_PRESETS,
|
|
12
|
+
applyPreset,
|
|
13
|
+
buildSemanticSpacing,
|
|
14
|
+
isProUnlocked
|
|
15
|
+
} from "../chunk-JUMR3N5J.js";
|
|
7
16
|
|
|
8
17
|
// src/cli/index.ts
|
|
9
18
|
import { program } from "commander";
|
|
@@ -399,43 +408,6 @@ async function confirm(question) {
|
|
|
399
408
|
}
|
|
400
409
|
|
|
401
410
|
// src/cli/commands/init.ts
|
|
402
|
-
var SPACING_PRESETS = {
|
|
403
|
-
compact: {
|
|
404
|
-
"1": 2,
|
|
405
|
-
"2": 4,
|
|
406
|
-
"3": 8,
|
|
407
|
-
"4": 12,
|
|
408
|
-
"5": 16,
|
|
409
|
-
"6": 24,
|
|
410
|
-
"7": 32,
|
|
411
|
-
"8": 48
|
|
412
|
-
},
|
|
413
|
-
comfortable: {
|
|
414
|
-
"1": 4,
|
|
415
|
-
"2": 8,
|
|
416
|
-
"3": 12,
|
|
417
|
-
"4": 16,
|
|
418
|
-
"5": 24,
|
|
419
|
-
"6": 32,
|
|
420
|
-
"7": 48,
|
|
421
|
-
"8": 64
|
|
422
|
-
},
|
|
423
|
-
spacious: {
|
|
424
|
-
"1": 6,
|
|
425
|
-
"2": 12,
|
|
426
|
-
"3": 18,
|
|
427
|
-
"4": 24,
|
|
428
|
-
"5": 36,
|
|
429
|
-
"6": 48,
|
|
430
|
-
"7": 72,
|
|
431
|
-
"8": 96
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
var RADIUS_PRESETS = {
|
|
435
|
-
compact: { none: 0, sm: 2, md: 3, lg: 6, xl: 10, full: 9999 },
|
|
436
|
-
comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
|
|
437
|
-
spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
|
|
438
|
-
};
|
|
439
411
|
function buildInitialConfig(name, preset = "comfortable") {
|
|
440
412
|
const spacing = SPACING_PRESETS[preset];
|
|
441
413
|
const radius = RADIUS_PRESETS[preset];
|
|
@@ -626,19 +598,9 @@ function buildInitialConfig(name, preset = "comfortable") {
|
|
|
626
598
|
}
|
|
627
599
|
},
|
|
628
600
|
spacing: {
|
|
629
|
-
baseUnit: preset
|
|
601
|
+
baseUnit: PRESET_BASE_UNITS[preset],
|
|
630
602
|
scale: spacing,
|
|
631
|
-
semantic:
|
|
632
|
-
"component-padding-xs": `${spacing[1]}`,
|
|
633
|
-
"component-padding-sm": `${spacing[2]}`,
|
|
634
|
-
"component-padding-md": `${spacing[4]}`,
|
|
635
|
-
"component-padding-lg": `${spacing[5]}`,
|
|
636
|
-
"layout-gap-xs": `${spacing[2]}`,
|
|
637
|
-
"layout-gap-sm": `${spacing[3]}`,
|
|
638
|
-
"layout-gap-md": `${spacing[5]}`,
|
|
639
|
-
"layout-gap-lg": `${spacing[6]}`,
|
|
640
|
-
"layout-section": `${spacing[7]}`
|
|
641
|
-
}
|
|
603
|
+
semantic: buildSemanticSpacing(spacing)
|
|
642
604
|
},
|
|
643
605
|
radius,
|
|
644
606
|
elevation: {
|
|
@@ -775,7 +737,15 @@ async function runInit(cwd, options) {
|
|
|
775
737
|
}
|
|
776
738
|
const name = rawName.replace(/\s+/g, "-").toLowerCase();
|
|
777
739
|
let preset;
|
|
778
|
-
if (
|
|
740
|
+
if (!isProUnlocked()) {
|
|
741
|
+
if (options.preset && options.preset !== "comfortable") {
|
|
742
|
+
logger.hint(
|
|
743
|
+
`Preset "${options.preset}" requires dsforge Pro`,
|
|
744
|
+
`Set DSFORGE_KEY to unlock compact and spacious. Using comfortable.`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
preset = "comfortable";
|
|
748
|
+
} else if (options.preset && VALID_PRESETS.includes(options.preset)) {
|
|
779
749
|
preset = options.preset;
|
|
780
750
|
} else {
|
|
781
751
|
const answer = await ask(
|
|
@@ -1861,6 +1831,34 @@ function emitThemeCss(themeName, themeOverrides, config) {
|
|
|
1861
1831
|
lines.push(emitBlock(`:root[data-theme="${themeName}"]`, entries));
|
|
1862
1832
|
return lines.join("\n") + "\n";
|
|
1863
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
|
+
}
|
|
1864
1862
|
function generateCssFiles(config, resolution) {
|
|
1865
1863
|
const files = [];
|
|
1866
1864
|
files.push({
|
|
@@ -4088,6 +4086,62 @@ function generateThemeProvider(config) {
|
|
|
4088
4086
|
const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
|
|
4089
4087
|
const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
|
|
4090
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>` : "";
|
|
4091
4145
|
return `/**
|
|
4092
4146
|
* ThemeProvider \u2014 ${config.meta.name}
|
|
4093
4147
|
*
|
|
@@ -4099,27 +4153,27 @@ function generateThemeProvider(config) {
|
|
|
4099
4153
|
* import "@${config.meta.name}/tokens/light.css"; // or dark.css
|
|
4100
4154
|
* import { ThemeProvider } from "@${config.meta.name}";
|
|
4101
4155
|
*
|
|
4102
|
-
* <ThemeProvider theme="light">
|
|
4156
|
+
* <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
|
|
4103
4157
|
* <App />
|
|
4104
4158
|
* </ThemeProvider>
|
|
4105
4159
|
*/
|
|
4106
4160
|
|
|
4107
|
-
import React from "react"
|
|
4161
|
+
import React from "react";${densityImport}
|
|
4108
4162
|
|
|
4109
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
|
|
4110
4164
|
|
|
4111
4165
|
export type ThemeName = ${themeType};
|
|
4112
|
-
|
|
4166
|
+
${densityTypes}
|
|
4113
4167
|
export interface ThemeContextValue {
|
|
4114
4168
|
theme: ThemeName;
|
|
4115
4169
|
setTheme: (theme: ThemeName) => void;
|
|
4116
4170
|
}
|
|
4117
|
-
|
|
4171
|
+
${densityContextTypes}
|
|
4118
4172
|
export interface ThemeProviderProps {
|
|
4119
4173
|
/** Initial theme. Defaults to "${defaultTheme}". */
|
|
4120
4174
|
theme?: ThemeName;
|
|
4121
4175
|
/** Called when setTheme is invoked \u2014 use to persist theme preference. */
|
|
4122
|
-
onThemeChange?: (theme: ThemeName) => void
|
|
4176
|
+
onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
|
|
4123
4177
|
children: React.ReactNode;
|
|
4124
4178
|
}
|
|
4125
4179
|
|
|
@@ -4137,12 +4191,12 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
|
|
|
4137
4191
|
export function useTheme(): ThemeContextValue {
|
|
4138
4192
|
return React.useContext(ThemeContext);
|
|
4139
4193
|
}
|
|
4140
|
-
|
|
4194
|
+
${densityContext}
|
|
4141
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
|
|
4142
4196
|
|
|
4143
4197
|
export function ThemeProvider({
|
|
4144
4198
|
theme: initialTheme = "${defaultTheme}",
|
|
4145
|
-
onThemeChange
|
|
4199
|
+
onThemeChange,${densityDestructure}
|
|
4146
4200
|
children,
|
|
4147
4201
|
}: ThemeProviderProps) {
|
|
4148
4202
|
const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
|
|
@@ -4158,13 +4212,13 @@ export function ThemeProvider({
|
|
|
4158
4212
|
},
|
|
4159
4213
|
[onThemeChange],
|
|
4160
4214
|
);
|
|
4161
|
-
|
|
4162
|
-
return (
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
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}
|
|
4168
4222
|
);
|
|
4169
4223
|
}
|
|
4170
4224
|
`;
|
|
@@ -4761,6 +4815,10 @@ async function runGenerate(cwd, options) {
|
|
|
4761
4815
|
options.debug ?? false
|
|
4762
4816
|
);
|
|
4763
4817
|
}
|
|
4818
|
+
const presetValue = config.meta.preset;
|
|
4819
|
+
if (presetValue === "compact" || presetValue === "comfortable" || presetValue === "spacious") {
|
|
4820
|
+
applyPreset(config, presetValue);
|
|
4821
|
+
}
|
|
4764
4822
|
const fullRules = Object.fromEntries(
|
|
4765
4823
|
REACT_COMPONENTS.map((name) => [name, rules[name] ?? {}])
|
|
4766
4824
|
);
|
|
@@ -4817,6 +4875,13 @@ async function runGenerate(cwd, options) {
|
|
|
4817
4875
|
await writeFile(path3.join(tokensDir, filename), content);
|
|
4818
4876
|
logger.dim(` \u2192 tokens/${filename}`);
|
|
4819
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
|
+
}
|
|
4820
4885
|
const tokenFiles = reactAdapter.generateTokenFiles(config, resolution);
|
|
4821
4886
|
for (const { filename, content } of tokenFiles) {
|
|
4822
4887
|
await writeFile(path3.join(tokensDir, filename), content);
|
|
@@ -4855,7 +4920,7 @@ async function runGenerate(cwd, options) {
|
|
|
4855
4920
|
logger.dim(` \u2192 src/${idxFile}`);
|
|
4856
4921
|
logger.success(`${generatedNames.length} components generated`);
|
|
4857
4922
|
}
|
|
4858
|
-
if (!only || only === "metadata") {
|
|
4923
|
+
if (isProUnlocked() && (!only || only === "metadata")) {
|
|
4859
4924
|
logger.step("Writing AI metadata...");
|
|
4860
4925
|
const metaDir = path3.join(outRoot, "metadata");
|
|
4861
4926
|
await ensureDir(metaDir);
|
|
@@ -4866,27 +4931,6 @@ async function runGenerate(cwd, options) {
|
|
|
4866
4931
|
}
|
|
4867
4932
|
logger.success(`Metadata written (${metaFiles.length} files)`);
|
|
4868
4933
|
}
|
|
4869
|
-
if (!only || only === "docs") {
|
|
4870
|
-
logger.step("Generating docs...");
|
|
4871
|
-
const docsDir = path3.join(outRoot, "docs");
|
|
4872
|
-
await ensureDir(docsDir);
|
|
4873
|
-
const metadataFiles = generateMetadata(config, fullRules, tokenCount);
|
|
4874
|
-
const metadataMap = {};
|
|
4875
|
-
for (const { filename, content } of metadataFiles) {
|
|
4876
|
-
const name = filename.replace(".json", "");
|
|
4877
|
-
if (name !== "index") {
|
|
4878
|
-
metadataMap[name] = JSON.parse(
|
|
4879
|
-
content
|
|
4880
|
-
);
|
|
4881
|
-
}
|
|
4882
|
-
}
|
|
4883
|
-
const docFiles = reactAdapter.generateDocs(config, fullRules, metadataMap);
|
|
4884
|
-
for (const { filename, content } of docFiles) {
|
|
4885
|
-
await writeFile(path3.join(docsDir, filename), content);
|
|
4886
|
-
logger.dim(` \u2192 docs/${filename}`);
|
|
4887
|
-
}
|
|
4888
|
-
logger.success(`Docs written (${docFiles.length} files)`);
|
|
4889
|
-
}
|
|
4890
4934
|
if (!only) {
|
|
4891
4935
|
logger.step("Writing package files...");
|
|
4892
4936
|
const componentNames = Object.keys(fullRules);
|
|
@@ -4896,7 +4940,7 @@ async function runGenerate(cwd, options) {
|
|
|
4896
4940
|
const tsConfig = generateTsConfig();
|
|
4897
4941
|
await writeFile(path3.join(outRoot, "tsconfig.json"), tsConfig);
|
|
4898
4942
|
logger.dim(` \u2192 tsconfig.json`);
|
|
4899
|
-
const { generateReadme } = await import("../emitter-
|
|
4943
|
+
const { generateReadme } = await import("../emitter-KNYIQTS5.js");
|
|
4900
4944
|
await writeFile(
|
|
4901
4945
|
path3.join(outRoot, "README.md"),
|
|
4902
4946
|
generateReadme(config, componentNames)
|
|
@@ -4912,7 +4956,7 @@ async function runGenerate(cwd, options) {
|
|
|
4912
4956
|
logger.success(`Package files written`);
|
|
4913
4957
|
}
|
|
4914
4958
|
logger.step("Generating showcase...");
|
|
4915
|
-
const { generateShowcase } = await import("../html-
|
|
4959
|
+
const { generateShowcase } = await import("../html-LQHDCSG4.js");
|
|
4916
4960
|
const showcaseHtml = generateShowcase(config, resolution);
|
|
4917
4961
|
await writeFile(path3.join(outRoot, "showcase.html"), showcaseHtml);
|
|
4918
4962
|
logger.dim(` \u2192 showcase.html`);
|
|
@@ -5299,7 +5343,7 @@ async function runMenu() {
|
|
|
5299
5343
|
// package.json
|
|
5300
5344
|
var package_default = {
|
|
5301
5345
|
name: "@nghitrum/dsforge",
|
|
5302
|
-
version: "0.1.5-alpha.
|
|
5346
|
+
version: "0.1.5-alpha.7",
|
|
5303
5347
|
description: "AI-native design system generator \u2014 tokens \u2192 components \u2192 docs \u2192 npm",
|
|
5304
5348
|
keywords: [
|
|
5305
5349
|
"design-system",
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PRESETS,
|
|
3
|
+
RADIUS_PRESETS,
|
|
4
|
+
SPACING_PRESETS,
|
|
5
|
+
buildSemanticSpacing,
|
|
6
|
+
isProUnlocked
|
|
7
|
+
} from "./chunk-JUMR3N5J.js";
|
|
8
|
+
|
|
1
9
|
// src/generators/showcase/types.ts
|
|
2
10
|
function esc(s) {
|
|
3
11
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -32,31 +40,6 @@ function componentTokens(config, tokens) {
|
|
|
32
40
|
};
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
// src/lib/license.ts
|
|
36
|
-
import { readFileSync } from "fs";
|
|
37
|
-
import { join } from "path";
|
|
38
|
-
function readKeyFromDotEnv() {
|
|
39
|
-
try {
|
|
40
|
-
const content = readFileSync(join(process.cwd(), ".env"), "utf8");
|
|
41
|
-
for (const raw of content.split("\n")) {
|
|
42
|
-
const line = raw.trim();
|
|
43
|
-
if (!line || line.startsWith("#")) continue;
|
|
44
|
-
const eq = line.indexOf("=");
|
|
45
|
-
if (eq === -1) continue;
|
|
46
|
-
const key = line.slice(0, eq).trim();
|
|
47
|
-
if (key !== "DSFORGE_KEY") continue;
|
|
48
|
-
const val = line.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
49
|
-
return val || void 0;
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
return void 0;
|
|
54
|
-
}
|
|
55
|
-
function isProUnlocked() {
|
|
56
|
-
const key = process.env["DSFORGE_KEY"] ?? readKeyFromDotEnv();
|
|
57
|
-
return typeof key === "string" && key.length > 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
43
|
// src/generators/showcase/foundations.ts
|
|
61
44
|
function buildColorSection(config, tokens) {
|
|
62
45
|
const groups = [];
|
|
@@ -117,26 +100,33 @@ function buildTypographySection(config) {
|
|
|
117
100
|
}
|
|
118
101
|
function buildSpacingSection(config) {
|
|
119
102
|
const scale = config.spacing?.scale ?? {};
|
|
120
|
-
const
|
|
121
|
-
|
|
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) => `
|
|
122
112
|
<div class="spacing-row">
|
|
123
113
|
<span class="spacing-key">${esc(key)}</span>
|
|
124
114
|
<div class="spacing-bar-wrap">
|
|
125
|
-
<div class="spacing-bar" style="width
|
|
115
|
+
<div class="spacing-bar" style="width:min(calc(var(--${esc(key)}) * 2), 320px)"></div>
|
|
126
116
|
</div>
|
|
127
|
-
<span class="spacing-val"
|
|
117
|
+
<span class="spacing-val" data-spacing-var="--${esc(key)}"></span>
|
|
128
118
|
</div>`;
|
|
129
119
|
return `
|
|
130
120
|
<div class="section-block">
|
|
131
|
-
<h3 class="group-title">
|
|
121
|
+
<h3 class="group-title">Scale</h3>
|
|
132
122
|
<div class="spacing-list">
|
|
133
|
-
${Object.
|
|
123
|
+
${Object.keys(scale).map((k) => scaleRow(k)).join("")}
|
|
134
124
|
</div>
|
|
135
125
|
</div>
|
|
136
126
|
<div class="section-block">
|
|
137
127
|
<h3 class="group-title">Semantic Spacing</h3>
|
|
138
128
|
<div class="spacing-list">
|
|
139
|
-
${Object.
|
|
129
|
+
${Object.keys(config.spacing?.semantic ?? {}).map((k) => semanticRow(k)).join("")}
|
|
140
130
|
</div>
|
|
141
131
|
</div>
|
|
142
132
|
`;
|
|
@@ -147,12 +137,12 @@ function buildRadiusSection(config) {
|
|
|
147
137
|
<div class="section-block">
|
|
148
138
|
<h3 class="group-title">Border Radius</h3>
|
|
149
139
|
<div class="radius-grid">
|
|
150
|
-
${Object.
|
|
151
|
-
(
|
|
140
|
+
${Object.keys(radius).map(
|
|
141
|
+
(key) => `
|
|
152
142
|
<div class="radius-item">
|
|
153
|
-
<div class="radius-box" style="border-radius
|
|
143
|
+
<div class="radius-box" style="border-radius:var(--radius-${esc(key)})"></div>
|
|
154
144
|
<span class="radius-key">${esc(key)}</span>
|
|
155
|
-
<span class="radius-val"
|
|
145
|
+
<span class="radius-val" data-spacing-var="--radius-${esc(key)}"></span>
|
|
156
146
|
</div>
|
|
157
147
|
`
|
|
158
148
|
).join("")}
|
|
@@ -231,7 +221,9 @@ var lockedPanel = (label) => `
|
|
|
231
221
|
function buildComponentPage(def, isPro) {
|
|
232
222
|
const tabId = (tab) => `${def.id}-tab-${tab}`;
|
|
233
223
|
const panelId = (tab) => `${def.id}-panel-${tab}`;
|
|
234
|
-
const overviewHtml =
|
|
224
|
+
const overviewHtml = `
|
|
225
|
+
<div class="comp-overview">${def.overviewHtml}</div>
|
|
226
|
+
<p class="component-description">${esc(def.description)}</p>`;
|
|
235
227
|
const propsTable = `
|
|
236
228
|
<table class="props-table">
|
|
237
229
|
<thead>
|
|
@@ -366,6 +358,9 @@ function buttonDef(config, tokens) {
|
|
|
366
358
|
id: "button",
|
|
367
359
|
label: "Button",
|
|
368
360
|
description: "Triggers an action or event. Use for form submissions, dialogs, and in-page actions.",
|
|
361
|
+
usageExample: `<Button variant="primary" size="md" onClick={() => {}}>
|
|
362
|
+
Save changes
|
|
363
|
+
</Button>`,
|
|
369
364
|
overviewHtml: `
|
|
370
365
|
<div class="comp-overview-section">
|
|
371
366
|
<div class="comp-overview-label">Variants</div>
|
|
@@ -552,6 +547,13 @@ function inputDef(config, tokens) {
|
|
|
552
547
|
id: "input",
|
|
553
548
|
label: "Input",
|
|
554
549
|
description: "Single-line text field. Covers all standard input types with label, helper text, and validation states.",
|
|
550
|
+
usageExample: `<Input
|
|
551
|
+
label="Email"
|
|
552
|
+
placeholder="you@example.com"
|
|
553
|
+
value={email}
|
|
554
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
555
|
+
error={emailError}
|
|
556
|
+
/>`,
|
|
555
557
|
overviewHtml: `
|
|
556
558
|
<div class="comp-overview-section">
|
|
557
559
|
<div class="comp-overview-label">States</div>
|
|
@@ -757,6 +759,10 @@ function cardDef(config, tokens) {
|
|
|
757
759
|
id: "card",
|
|
758
760
|
label: "Card",
|
|
759
761
|
description: "A surface that groups related content. Supports header, body, and optional footer slots.",
|
|
762
|
+
usageExample: `<Card padding="lg">
|
|
763
|
+
<h2>Card title</h2>
|
|
764
|
+
<p>Card content goes here.</p>
|
|
765
|
+
</Card>`,
|
|
760
766
|
overviewHtml: `
|
|
761
767
|
<div class="comp-overview-section">
|
|
762
768
|
<div class="comp-overview-label">Variants</div>
|
|
@@ -913,6 +919,8 @@ function badgeDef(config, tokens) {
|
|
|
913
919
|
id: "badge",
|
|
914
920
|
label: "Badge",
|
|
915
921
|
description: "Compact label for status, categories, or counts. Display-only \u2014 not interactive.",
|
|
922
|
+
usageExample: `<Badge variant="success">Active</Badge>
|
|
923
|
+
<Badge variant="warning">Pending</Badge>`,
|
|
916
924
|
overviewHtml: `
|
|
917
925
|
<div class="comp-overview-section">
|
|
918
926
|
<div class="comp-overview-label">Variants</div>
|
|
@@ -1057,6 +1065,11 @@ function checkboxDef(config, tokens) {
|
|
|
1057
1065
|
id: "checkbox",
|
|
1058
1066
|
label: "Checkbox",
|
|
1059
1067
|
description: "Binary toggle for boolean values. Supports indeterminate state for partial selections.",
|
|
1068
|
+
usageExample: `<Checkbox
|
|
1069
|
+
label="Accept terms"
|
|
1070
|
+
checked={accepted}
|
|
1071
|
+
onChange={(e) => setAccepted(e.target.checked)}
|
|
1072
|
+
/>`,
|
|
1060
1073
|
overviewHtml: `
|
|
1061
1074
|
<div class="comp-overview-section">
|
|
1062
1075
|
<div class="comp-overview-label">States</div>
|
|
@@ -1239,6 +1252,8 @@ function radioDef(config, tokens) {
|
|
|
1239
1252
|
id: "radio",
|
|
1240
1253
|
label: "Radio",
|
|
1241
1254
|
description: "Single selection within a mutually exclusive group. Always pair Radio with RadioGroup.",
|
|
1255
|
+
usageExample: `<Radio label="Option A" name="choice" value="a" checked={choice === 'a'} onChange={() => setChoice('a')} />
|
|
1256
|
+
<Radio label="Option B" name="choice" value="b" checked={choice === 'b'} onChange={() => setChoice('b')} />`,
|
|
1242
1257
|
overviewHtml: `
|
|
1243
1258
|
<div class="comp-overview-section">
|
|
1244
1259
|
<div class="comp-overview-label">RadioGroup (vertical)</div>
|
|
@@ -1425,6 +1440,12 @@ function selectDef(config, tokens) {
|
|
|
1425
1440
|
id: "select",
|
|
1426
1441
|
label: "Select",
|
|
1427
1442
|
description: "Dropdown picker for selecting from a list of options. Wraps native <select> for full accessibility.",
|
|
1443
|
+
usageExample: `<Select
|
|
1444
|
+
label="Country"
|
|
1445
|
+
options={[{ label: 'Norway', value: 'no' }, { label: 'Sweden', value: 'se' }]}
|
|
1446
|
+
value={country}
|
|
1447
|
+
onChange={(e) => setCountry(e.target.value)}
|
|
1448
|
+
/>`,
|
|
1428
1449
|
overviewHtml: `
|
|
1429
1450
|
<div class="comp-overview-section">
|
|
1430
1451
|
<div class="comp-overview-label">States</div>
|
|
@@ -1610,6 +1631,12 @@ function toastDef(config, tokens) {
|
|
|
1610
1631
|
id: "toast",
|
|
1611
1632
|
label: "Toast / Alert",
|
|
1612
1633
|
description: "Feedback for user actions. Alert is inline and static; Toast is an overlay with auto-dismiss and a useToast() hook.",
|
|
1634
|
+
usageExample: `<Toast
|
|
1635
|
+
message="Changes saved successfully"
|
|
1636
|
+
variant="success"
|
|
1637
|
+
duration={3000}
|
|
1638
|
+
onDismiss={() => setToast(null)}
|
|
1639
|
+
/>`,
|
|
1613
1640
|
overviewHtml: `
|
|
1614
1641
|
<div class="comp-overview-section">
|
|
1615
1642
|
<div class="comp-overview-label">Alert \u2014 inline variants</div>
|
|
@@ -1791,6 +1818,7 @@ function spinnerDef(config, tokens) {
|
|
|
1791
1818
|
id: "spinner",
|
|
1792
1819
|
label: "Spinner",
|
|
1793
1820
|
description: "Loading indicator for async operations. Includes a visually hidden status label for screen readers.",
|
|
1821
|
+
usageExample: `<Spinner size="lg" label="Saving your changes" />`,
|
|
1794
1822
|
overviewHtml: `
|
|
1795
1823
|
<div class="comp-overview-section">
|
|
1796
1824
|
<div class="comp-overview-label">Sizes</div>
|
|
@@ -1966,11 +1994,29 @@ var SHOWCASE_COMPONENTS = [
|
|
|
1966
1994
|
];
|
|
1967
1995
|
|
|
1968
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
|
+
}
|
|
1969
2014
|
function generateShowcase(config, resolution) {
|
|
1970
2015
|
const tokens = resolution.tokens;
|
|
1971
2016
|
const name = config.meta?.name ?? "Design System";
|
|
1972
2017
|
const version = config.meta?.version ?? "0.1.0";
|
|
1973
2018
|
const themes = Object.keys(config.themes ?? {});
|
|
2019
|
+
const defaultDensity = config.meta?.preset ?? "comfortable";
|
|
1974
2020
|
const foundationItems = [
|
|
1975
2021
|
{ id: "colors", label: "Colors" },
|
|
1976
2022
|
{ id: "typography", label: "Typography" },
|
|
@@ -2006,8 +2052,9 @@ function generateShowcase(config, resolution) {
|
|
|
2006
2052
|
const darkTheme = config.themes?.["dark"] ?? {};
|
|
2007
2053
|
const themeCssLight = Object.entries({ ...flatTokens, ...lightTheme }).map(([k, v]) => ` --${k}: ${v};`).join("\n");
|
|
2008
2054
|
const themeCssDark = Object.entries({ ...flatTokens, ...darkTheme }).map(([k, v]) => ` --${k}: ${v};`).join("\n");
|
|
2055
|
+
const densityCss = buildDensityCss();
|
|
2009
2056
|
return `<!DOCTYPE html>
|
|
2010
|
-
<html lang="en" data-theme="light">
|
|
2057
|
+
<html lang="en" data-theme="light" data-density="${defaultDensity}">
|
|
2011
2058
|
<head>
|
|
2012
2059
|
<meta charset="UTF-8" />
|
|
2013
2060
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
@@ -2022,6 +2069,9 @@ ${themeCssLight}
|
|
|
2022
2069
|
${themeCssDark}
|
|
2023
2070
|
}
|
|
2024
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
|
+
|
|
2025
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 */
|
|
2026
2076
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2027
2077
|
html { font-size: 16px; }
|
|
@@ -2087,6 +2137,33 @@ ${themeCssDark}
|
|
|
2087
2137
|
color: var(--color-text-primary, #0f172a); font-weight: 500;
|
|
2088
2138
|
box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
|
|
2089
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
|
+
}
|
|
2090
2167
|
.content { padding: 36px 40px 80px; max-width: 860px; }
|
|
2091
2168
|
.page { display: none; }
|
|
2092
2169
|
.page.active { display: block; }
|
|
@@ -2290,16 +2367,37 @@ ${themeCssDark}
|
|
|
2290
2367
|
.locked-hint code { background: var(--color-bg-overlay, #f1f5f9); padding: 1px 6px; border-radius: 4px; border: 1px solid var(--color-border-default, #e2e8f0); }
|
|
2291
2368
|
|
|
2292
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 */
|
|
2293
|
-
.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; }
|
|
2294
2371
|
.ds-btn:hover:not(:disabled) { filter: brightness(0.92); }
|
|
2295
|
-
.ds-field { display: flex; flex-direction: column; gap: 4px; }
|
|
2372
|
+
.ds-field { display: flex; flex-direction: column; gap: var(--component-padding-xs, 4px); }
|
|
2296
2373
|
.ds-label { font-size: 13px; font-weight: 500; }
|
|
2297
|
-
.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%; }
|
|
2298
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; }
|
|
2299
|
-
.ds-card { border: 1px solid; overflow: hidden; width: 220px; }
|
|
2300
|
-
.ds-card-header { padding: 12px 14px; font-size: 14px; font-weight: 600; }
|
|
2301
|
-
.ds-card-body { padding: 12px 14px; }
|
|
2302
|
-
.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; }
|
|
2380
|
+
|
|
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 */
|
|
2382
|
+
.component-docs { margin-top: 36px; }
|
|
2383
|
+
.component-description {
|
|
2384
|
+
font-size: 14px; color: var(--color-text-secondary, #64748b);
|
|
2385
|
+
line-height: 1.65; margin-bottom: 24px; max-width: 640px;
|
|
2386
|
+
}
|
|
2387
|
+
.component-docs h4 {
|
|
2388
|
+
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
|
2389
|
+
letter-spacing: 0.07em; color: var(--color-text-secondary, #64748b);
|
|
2390
|
+
margin-bottom: 12px; padding-bottom: 8px;
|
|
2391
|
+
border-bottom: 1px solid var(--color-border-default, #e2e8f0);
|
|
2392
|
+
}
|
|
2393
|
+
.usage-example {
|
|
2394
|
+
margin: 0; padding: 16px 18px;
|
|
2395
|
+
background: var(--color-bg-default, #fff);
|
|
2396
|
+
color: var(--color-text-primary, #0f172a);
|
|
2397
|
+
border: 1px solid var(--color-border-default, #e2e8f0); border-radius: 8px;
|
|
2398
|
+
font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
|
|
2399
|
+
font-size: 12.5px; line-height: 1.65; overflow-x: auto; white-space: pre;
|
|
2400
|
+
}
|
|
2303
2401
|
|
|
2304
2402
|
/* \u2500\u2500 Spinner animation \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 */
|
|
2305
2403
|
@keyframes dsforge-spin { to { transform: rotate(360deg); } }
|
|
@@ -2337,6 +2435,16 @@ ${themeCssDark}
|
|
|
2337
2435
|
${esc(name)} / <span id="topbar-current">Colors</span>
|
|
2338
2436
|
</div>
|
|
2339
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>`}
|
|
2340
2448
|
${themes.length >= 2 ? `
|
|
2341
2449
|
<div class="theme-toggle">
|
|
2342
2450
|
${themes.map(
|
|
@@ -2377,6 +2485,22 @@ ${themeCssDark}
|
|
|
2377
2485
|
btn.classList.add('active');
|
|
2378
2486
|
}
|
|
2379
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
|
+
|
|
2380
2504
|
function switchTab(compId, tabId, btn) {
|
|
2381
2505
|
const tabs = document.querySelectorAll('#' + compId + '-tabs .comp-tab');
|
|
2382
2506
|
const panels = document.querySelectorAll('#' + compId + '-tabs .comp-tab-panel');
|
|
@@ -2395,6 +2519,9 @@ ${themeCssDark}
|
|
|
2395
2519
|
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
|
|
2396
2520
|
});
|
|
2397
2521
|
}
|
|
2522
|
+
|
|
2523
|
+
// Populate spacing/radius value labels on load
|
|
2524
|
+
document.addEventListener('DOMContentLoaded', updateSpacingValues);
|
|
2398
2525
|
</script>
|
|
2399
2526
|
</body>
|
|
2400
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
|
@@ -1143,7 +1143,32 @@ var rl = readline.createInterface({
|
|
|
1143
1143
|
output: process.stdout
|
|
1144
1144
|
});
|
|
1145
1145
|
|
|
1146
|
-
// src/
|
|
1146
|
+
// src/lib/license.ts
|
|
1147
|
+
import { readFileSync } from "fs";
|
|
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
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/presets/index.ts
|
|
1147
1172
|
var SPACING_PRESETS = {
|
|
1148
1173
|
compact: {
|
|
1149
1174
|
"1": 2,
|
|
@@ -1181,6 +1206,26 @@ var RADIUS_PRESETS = {
|
|
|
1181
1206
|
comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
|
|
1182
1207
|
spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
|
|
1183
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
|
|
1184
1229
|
function buildInitialConfig(name, preset = "comfortable") {
|
|
1185
1230
|
const spacing = SPACING_PRESETS[preset];
|
|
1186
1231
|
const radius = RADIUS_PRESETS[preset];
|
|
@@ -1371,19 +1416,9 @@ function buildInitialConfig(name, preset = "comfortable") {
|
|
|
1371
1416
|
}
|
|
1372
1417
|
},
|
|
1373
1418
|
spacing: {
|
|
1374
|
-
baseUnit: preset
|
|
1419
|
+
baseUnit: PRESET_BASE_UNITS[preset],
|
|
1375
1420
|
scale: spacing,
|
|
1376
|
-
semantic:
|
|
1377
|
-
"component-padding-xs": `${spacing[1]}`,
|
|
1378
|
-
"component-padding-sm": `${spacing[2]}`,
|
|
1379
|
-
"component-padding-md": `${spacing[4]}`,
|
|
1380
|
-
"component-padding-lg": `${spacing[5]}`,
|
|
1381
|
-
"layout-gap-xs": `${spacing[2]}`,
|
|
1382
|
-
"layout-gap-sm": `${spacing[3]}`,
|
|
1383
|
-
"layout-gap-md": `${spacing[5]}`,
|
|
1384
|
-
"layout-gap-lg": `${spacing[6]}`,
|
|
1385
|
-
"layout-section": `${spacing[7]}`
|
|
1386
|
-
}
|
|
1421
|
+
semantic: buildSemanticSpacing(spacing)
|
|
1387
1422
|
},
|
|
1388
1423
|
radius,
|
|
1389
1424
|
elevation: {
|
|
@@ -2559,6 +2594,62 @@ function generateThemeProvider(config) {
|
|
|
2559
2594
|
const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
|
|
2560
2595
|
const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
|
|
2561
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>` : "";
|
|
2562
2653
|
return `/**
|
|
2563
2654
|
* ThemeProvider \u2014 ${config.meta.name}
|
|
2564
2655
|
*
|
|
@@ -2570,27 +2661,27 @@ function generateThemeProvider(config) {
|
|
|
2570
2661
|
* import "@${config.meta.name}/tokens/light.css"; // or dark.css
|
|
2571
2662
|
* import { ThemeProvider } from "@${config.meta.name}";
|
|
2572
2663
|
*
|
|
2573
|
-
* <ThemeProvider theme="light">
|
|
2664
|
+
* <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
|
|
2574
2665
|
* <App />
|
|
2575
2666
|
* </ThemeProvider>
|
|
2576
2667
|
*/
|
|
2577
2668
|
|
|
2578
|
-
import React from "react"
|
|
2669
|
+
import React from "react";${densityImport}
|
|
2579
2670
|
|
|
2580
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
|
|
2581
2672
|
|
|
2582
2673
|
export type ThemeName = ${themeType};
|
|
2583
|
-
|
|
2674
|
+
${densityTypes}
|
|
2584
2675
|
export interface ThemeContextValue {
|
|
2585
2676
|
theme: ThemeName;
|
|
2586
2677
|
setTheme: (theme: ThemeName) => void;
|
|
2587
2678
|
}
|
|
2588
|
-
|
|
2679
|
+
${densityContextTypes}
|
|
2589
2680
|
export interface ThemeProviderProps {
|
|
2590
2681
|
/** Initial theme. Defaults to "${defaultTheme}". */
|
|
2591
2682
|
theme?: ThemeName;
|
|
2592
2683
|
/** Called when setTheme is invoked \u2014 use to persist theme preference. */
|
|
2593
|
-
onThemeChange?: (theme: ThemeName) => void
|
|
2684
|
+
onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
|
|
2594
2685
|
children: React.ReactNode;
|
|
2595
2686
|
}
|
|
2596
2687
|
|
|
@@ -2608,12 +2699,12 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
|
|
|
2608
2699
|
export function useTheme(): ThemeContextValue {
|
|
2609
2700
|
return React.useContext(ThemeContext);
|
|
2610
2701
|
}
|
|
2611
|
-
|
|
2702
|
+
${densityContext}
|
|
2612
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
|
|
2613
2704
|
|
|
2614
2705
|
export function ThemeProvider({
|
|
2615
2706
|
theme: initialTheme = "${defaultTheme}",
|
|
2616
|
-
onThemeChange
|
|
2707
|
+
onThemeChange,${densityDestructure}
|
|
2617
2708
|
children,
|
|
2618
2709
|
}: ThemeProviderProps) {
|
|
2619
2710
|
const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
|
|
@@ -2629,13 +2720,13 @@ export function ThemeProvider({
|
|
|
2629
2720
|
},
|
|
2630
2721
|
[onThemeChange],
|
|
2631
2722
|
);
|
|
2632
|
-
|
|
2633
|
-
return (
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
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}
|
|
2639
2730
|
);
|
|
2640
2731
|
}
|
|
2641
2732
|
`;
|
|
@@ -3062,7 +3153,7 @@ function generatePackageJson(config, componentNames) {
|
|
|
3062
3153
|
componentNames.map((c) => [`./metadata/${c}`, `./metadata/${c}.json`])
|
|
3063
3154
|
)
|
|
3064
3155
|
},
|
|
3065
|
-
files: ["dist", "tokens", "metadata", "
|
|
3156
|
+
files: ["dist", "tokens", "metadata", "CHANGELOG.md"],
|
|
3066
3157
|
scripts: {
|
|
3067
3158
|
build: "tsc",
|
|
3068
3159
|
prepublishOnly: "npm run build"
|