@nghitrum/dsforge 0.1.5-alpha.8 → 0.2.0

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/index.js CHANGED
@@ -1143,31 +1143,6 @@ var rl = readline.createInterface({
1143
1143
  output: process.stdout
1144
1144
  });
1145
1145
 
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
1146
  // src/presets/index.ts
1172
1147
  var SPACING_PRESETS = {
1173
1148
  compact: {
@@ -1206,6 +1181,11 @@ var RADIUS_PRESETS = {
1206
1181
  comfortable: { none: 0, sm: 2, md: 4, lg: 8, xl: 16, full: 9999 },
1207
1182
  spacious: { none: 0, sm: 3, md: 6, lg: 12, xl: 20, full: 9999 }
1208
1183
  };
1184
+ var CONTROL_SIZE_PRESETS = {
1185
+ compact: { sm: 12, md: 14, lg: 18 },
1186
+ comfortable: { sm: 14, md: 16, lg: 20 },
1187
+ spacious: { sm: 16, md: 18, lg: 24 }
1188
+ };
1209
1189
  var PRESET_BASE_UNITS = {
1210
1190
  compact: 2,
1211
1191
  comfortable: 4,
@@ -1593,6 +1573,19 @@ function emitBaseCss(config, resolution) {
1593
1573
  if (typoEntries.length > 0) {
1594
1574
  sections.push(emitBlock(":root", typoEntries, "Typography"));
1595
1575
  }
1576
+ const currentPreset = config.philosophy?.density ?? "comfortable";
1577
+ const controlSizes = CONTROL_SIZE_PRESETS[currentPreset] ?? CONTROL_SIZE_PRESETS.comfortable;
1578
+ sections.push(
1579
+ emitBlock(
1580
+ ":root",
1581
+ [
1582
+ ["control-size-sm", `${controlSizes.sm}px`],
1583
+ ["control-size-md", `${controlSizes.md}px`],
1584
+ ["control-size-lg", `${controlSizes.lg}px`]
1585
+ ],
1586
+ "Control sizes"
1587
+ )
1588
+ );
1596
1589
  const radiusEntries = Object.entries(
1597
1590
  config.radius ?? {}
1598
1591
  ).filter(([, v]) => v !== void 0).map(([k, v]) => [`radius-${k}`, v === 9999 ? "9999px" : `${v}px`]);
@@ -2594,62 +2587,7 @@ function generateThemeProvider(config) {
2594
2587
  const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
2595
2588
  const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
2596
2589
  const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
2597
- const isPro = isProUnlocked();
2598
2590
  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>` : "";
2653
2591
  return `/**
2654
2592
  * ThemeProvider \u2014 ${config.meta.name}
2655
2593
  *
@@ -2661,27 +2599,39 @@ export function useDensity(): DensityContextValue {
2661
2599
  * import "@${config.meta.name}/tokens/light.css"; // or dark.css
2662
2600
  * import { ThemeProvider } from "@${config.meta.name}";
2663
2601
  *
2664
- * <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
2602
+ * <ThemeProvider theme="light" density="${defaultDensity}">
2665
2603
  * <App />
2666
2604
  * </ThemeProvider>
2667
2605
  */
2668
2606
 
2669
- import React from "react";${densityImport}
2607
+ import React from "react";
2608
+ import "../tokens/density.css";
2670
2609
 
2671
2610
  // \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
2672
2611
 
2673
2612
  export type ThemeName = ${themeType};
2674
- ${densityTypes}
2613
+
2614
+ export type DensityName = "compact" | "comfortable" | "spacious";
2615
+
2675
2616
  export interface ThemeContextValue {
2676
2617
  theme: ThemeName;
2677
2618
  setTheme: (theme: ThemeName) => void;
2678
2619
  }
2679
- ${densityContextTypes}
2620
+
2621
+ export interface DensityContextValue {
2622
+ density: DensityName;
2623
+ setDensity: (density: DensityName) => void;
2624
+ }
2625
+
2680
2626
  export interface ThemeProviderProps {
2681
2627
  /** Initial theme. Defaults to "${defaultTheme}". */
2682
2628
  theme?: ThemeName;
2683
2629
  /** Called when setTheme is invoked \u2014 use to persist theme preference. */
2684
- onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
2630
+ onThemeChange?: (theme: ThemeName) => void;
2631
+ /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
2632
+ density?: DensityName;
2633
+ /** Called when setDensity is invoked. */
2634
+ onDensityChange?: (density: DensityName) => void;
2685
2635
  children: React.ReactNode;
2686
2636
  }
2687
2637
 
@@ -2699,12 +2649,27 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
2699
2649
  export function useTheme(): ThemeContextValue {
2700
2650
  return React.useContext(ThemeContext);
2701
2651
  }
2702
- ${densityContext}
2652
+
2653
+ export const DensityContext = React.createContext<DensityContextValue>({
2654
+ density: "${defaultDensity}",
2655
+ setDensity: () => undefined,
2656
+ });
2657
+
2658
+ /**
2659
+ * Hook to read and change the current density.
2660
+ * Must be used inside a <ThemeProvider>.
2661
+ */
2662
+ export function useDensity(): DensityContextValue {
2663
+ return React.useContext(DensityContext);
2664
+ }
2665
+
2703
2666
  // \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
2704
2667
 
2705
2668
  export function ThemeProvider({
2706
2669
  theme: initialTheme = "${defaultTheme}",
2707
- onThemeChange,${densityDestructure}
2670
+ onThemeChange,
2671
+ density: initialDensity = "${defaultDensity}",
2672
+ onDensityChange,
2708
2673
  children,
2709
2674
  }: ThemeProviderProps) {
2710
2675
  const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
@@ -2720,13 +2685,29 @@ export function ThemeProvider({
2720
2685
  },
2721
2686
  [onThemeChange],
2722
2687
  );
2723
- ${densityState}
2724
- return (${densityProviderOpen}
2688
+
2689
+ const [density, setDensityState] = React.useState<DensityName>(initialDensity);
2690
+
2691
+ React.useEffect(() => {
2692
+ setDensityState(initialDensity);
2693
+ }, [initialDensity]);
2694
+
2695
+ const setDensity = React.useCallback(
2696
+ (next: DensityName) => {
2697
+ setDensityState(next);
2698
+ onDensityChange?.(next);
2699
+ },
2700
+ [onDensityChange],
2701
+ );
2702
+
2703
+ return (
2704
+ <DensityContext.Provider value={{ density, setDensity }}>
2725
2705
  <ThemeContext.Provider value={{ theme, setTheme }}>
2726
- <div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
2706
+ <div data-theme={theme} data-density={density} style={{ display: "contents" }}>
2727
2707
  {children}
2728
2708
  </div>
2729
- </ThemeContext.Provider>${densityProviderClose}
2709
+ </ThemeContext.Provider>
2710
+ </DensityContext.Provider>
2730
2711
  );
2731
2712
  }
2732
2713
  `;
@@ -2743,9 +2724,9 @@ function generateComponentIndex(config, componentNames) {
2743
2724
  ""
2744
2725
  ];
2745
2726
  for (const name of componentNames) {
2746
- lines.push(`export * from "./${name}";`);
2727
+ lines.push(`export * from "./components/${name}/${name}";`);
2747
2728
  }
2748
- lines.push(`export * from "./ThemeProvider";`);
2729
+ lines.push(`export * from "./components/ThemeProvider/ThemeProvider";`);
2749
2730
  lines.push("");
2750
2731
  return lines.join("\n");
2751
2732
  }
@@ -3148,12 +3129,14 @@ function generatePackageJson(config, componentNames) {
3148
3129
  ),
3149
3130
  "./tokens": "./tokens/tokens.js",
3150
3131
  "./tailwind": "./tokens/tailwind.js",
3151
- "./metadata": "./metadata/index.json",
3152
3132
  ...Object.fromEntries(
3153
- componentNames.map((c) => [`./metadata/${c}`, `./metadata/${c}.json`])
3133
+ componentNames.map((c) => {
3134
+ const pascal = c.charAt(0).toUpperCase() + c.slice(1);
3135
+ return [`./components/${pascal}`, `./components/${pascal}/${pascal}.json`];
3136
+ })
3154
3137
  )
3155
3138
  },
3156
- files: ["dist", "tokens", "metadata", "CHANGELOG.md"],
3139
+ files: ["dist", "tokens", "components", "CHANGELOG.md"],
3157
3140
  scripts: {
3158
3141
  build: "tsc",
3159
3142
  prepublishOnly: "npm run build"
@@ -3193,7 +3176,7 @@ function generateTsConfig() {
3193
3176
  moduleResolution: "NodeNext",
3194
3177
  lib: ["ES2020", "DOM"],
3195
3178
  outDir: "./dist",
3196
- rootDir: "./src",
3179
+ rootDir: ".",
3197
3180
  declaration: true,
3198
3181
  declarationMap: true,
3199
3182
  sourceMap: true,
@@ -3202,7 +3185,7 @@ function generateTsConfig() {
3202
3185
  skipLibCheck: true,
3203
3186
  jsx: "react-jsx"
3204
3187
  },
3205
- include: ["src/**/*"],
3188
+ include: ["index.ts", "components/**/*"],
3206
3189
  exclude: ["node_modules", "dist"]
3207
3190
  },
3208
3191
  null,
@@ -3251,7 +3234,10 @@ function App() {
3251
3234
  ## Components
3252
3235
 
3253
3236
  ${componentNames.map(
3254
- (c) => `- **${c.charAt(0).toUpperCase() + c.slice(1)}** \u2014 see \`metadata/${c}.json\` for contract`
3237
+ (c) => {
3238
+ const pascal = c.charAt(0).toUpperCase() + c.slice(1);
3239
+ return `- **${pascal}** \u2014 see \`components/${pascal}/${pascal}.json\` for props and usage`;
3240
+ }
3255
3241
  ).join("\n")}
3256
3242
 
3257
3243
  ## Themes
@@ -3326,20 +3312,10 @@ every semantic and component token that references it.
3326
3312
 
3327
3313
  ## AI tool integration
3328
3314
 
3329
- The \`metadata/\` directory contains machine-readable component contracts.
3315
+ Each component ships with a machine-readable JSON contract (e.g. \`components/Button/Button.json\`).
3330
3316
  AI coding assistants (Copilot, Cursor, Claude Code) can read these to
3331
3317
  generate UI that respects your governance rules automatically.
3332
3318
 
3333
- \`\`\`json
3334
- // ${pkgName}/metadata/button.json
3335
- {
3336
- "component": "Button",
3337
- "allowedVariants": ["primary", "secondary", "danger", "ghost"],
3338
- "requiredProps": ["aria-label"],
3339
- "accessibilityContract": { "keyboard": true, "focusRing": true }
3340
- }
3341
- \`\`\`
3342
-
3343
3319
  ---
3344
3320
 
3345
3321
  Generated by [dsforge](https://github.com/nghitrum/dsforge) v${config.meta.version}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nghitrum/dsforge",
3
- "version": "0.1.5-alpha.8",
3
+ "version": "0.2.0",
4
4
  "description": "AI-native design system generator — tokens → components → docs → npm",
5
5
  "keywords": [
6
6
  "design-system",