@nghitrum/dsforge 0.1.5-alpha.1 → 0.1.5-alpha.10

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/cli/index.js CHANGED
@@ -3,7 +3,17 @@ import {
3
3
  generateChangelog,
4
4
  generatePackageJson,
5
5
  generateTsConfig
6
- } from "../chunk-RI3XDGKU.js";
6
+ } from "../chunk-ZZRPNO6Z.js";
7
+ import {
8
+ CONTROL_SIZE_PRESETS,
9
+ PRESETS,
10
+ PRESET_BASE_UNITS,
11
+ RADIUS_PRESETS,
12
+ SPACING_PRESETS,
13
+ applyPreset,
14
+ buildSemanticSpacing,
15
+ isProUnlocked
16
+ } from "../chunk-YUPXTQZ5.js";
7
17
 
8
18
  // src/cli/index.ts
9
19
  import { program } from "commander";
@@ -399,43 +409,6 @@ async function confirm(question) {
399
409
  }
400
410
 
401
411
  // 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
412
  function buildInitialConfig(name, preset = "comfortable") {
440
413
  const spacing = SPACING_PRESETS[preset];
441
414
  const radius = RADIUS_PRESETS[preset];
@@ -626,19 +599,9 @@ function buildInitialConfig(name, preset = "comfortable") {
626
599
  }
627
600
  },
628
601
  spacing: {
629
- baseUnit: preset === "compact" ? 2 : preset === "spacious" ? 6 : 4,
602
+ baseUnit: PRESET_BASE_UNITS[preset],
630
603
  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
- }
604
+ semantic: buildSemanticSpacing(spacing)
642
605
  },
643
606
  radius,
644
607
  elevation: {
@@ -775,7 +738,15 @@ async function runInit(cwd, options) {
775
738
  }
776
739
  const name = rawName.replace(/\s+/g, "-").toLowerCase();
777
740
  let preset;
778
- if (options.preset && VALID_PRESETS.includes(options.preset)) {
741
+ if (!isProUnlocked()) {
742
+ if (options.preset && options.preset !== "comfortable") {
743
+ logger.hint(
744
+ `Preset "${options.preset}" requires dsforge Pro`,
745
+ `Set DSFORGE_KEY to unlock compact and spacious. Using comfortable.`
746
+ );
747
+ }
748
+ preset = "comfortable";
749
+ } else if (options.preset && VALID_PRESETS.includes(options.preset)) {
779
750
  preset = options.preset;
780
751
  } else {
781
752
  const answer = await ask(
@@ -1803,6 +1774,19 @@ function emitBaseCss(config, resolution) {
1803
1774
  if (typoEntries.length > 0) {
1804
1775
  sections.push(emitBlock(":root", typoEntries, "Typography"));
1805
1776
  }
1777
+ const currentPreset = config.philosophy?.density ?? "comfortable";
1778
+ const controlSizes = CONTROL_SIZE_PRESETS[currentPreset] ?? CONTROL_SIZE_PRESETS.comfortable;
1779
+ sections.push(
1780
+ emitBlock(
1781
+ ":root",
1782
+ [
1783
+ ["control-size-sm", `${controlSizes.sm}px`],
1784
+ ["control-size-md", `${controlSizes.md}px`],
1785
+ ["control-size-lg", `${controlSizes.lg}px`]
1786
+ ],
1787
+ "Control sizes"
1788
+ )
1789
+ );
1806
1790
  const radiusEntries = Object.entries(
1807
1791
  config.radius ?? {}
1808
1792
  ).filter(([, v]) => v !== void 0).map(([k, v]) => [`radius-${k}`, v === 9999 ? "9999px" : `${v}px`]);
@@ -1861,6 +1845,38 @@ function emitThemeCss(themeName, themeOverrides, config) {
1861
1845
  lines.push(emitBlock(`:root[data-theme="${themeName}"]`, entries));
1862
1846
  return lines.join("\n") + "\n";
1863
1847
  }
1848
+ function emitDensityCss(config) {
1849
+ const lines = [
1850
+ `/* \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 */`,
1851
+ `/* Generated by dsforge. Do not edit manually. */`,
1852
+ `/* Pro feature: import this file to enable runtime density switching. */`,
1853
+ `/* Usage: <html data-density="compact | comfortable | spacious"> */`,
1854
+ `/* or wrap with <DensityProvider density="compact"> */`,
1855
+ ""
1856
+ ];
1857
+ for (const preset of PRESETS) {
1858
+ const scale = SPACING_PRESETS[preset];
1859
+ const radius = RADIUS_PRESETS[preset];
1860
+ const semantic = buildSemanticSpacing(scale);
1861
+ const entries = [];
1862
+ for (const [key, value] of Object.entries(scale)) {
1863
+ entries.push([`spacing-${key}`, `${value}px`]);
1864
+ }
1865
+ for (const [key, value] of Object.entries(semantic)) {
1866
+ entries.push([key, `${value}px`]);
1867
+ }
1868
+ for (const [key, value] of Object.entries(radius)) {
1869
+ entries.push([`radius-${key}`, value === 9999 ? "9999px" : `${value}px`]);
1870
+ }
1871
+ const controlSizes = CONTROL_SIZE_PRESETS[preset];
1872
+ entries.push(["control-size-sm", `${controlSizes.sm}px`]);
1873
+ entries.push(["control-size-md", `${controlSizes.md}px`]);
1874
+ entries.push(["control-size-lg", `${controlSizes.lg}px`]);
1875
+ lines.push(emitBlock(`[data-density="${preset}"]`, entries, `Preset: ${preset}`));
1876
+ lines.push("");
1877
+ }
1878
+ return lines.join("\n");
1879
+ }
1864
1880
  function generateCssFiles(config, resolution) {
1865
1881
  const files = [];
1866
1882
  files.push({
@@ -1891,192 +1907,6 @@ function generateCssFiles(config, resolution) {
1891
1907
  return files;
1892
1908
  }
1893
1909
 
1894
- // src/generators/metadata/generator.ts
1895
- var COMPONENT_DEFAULTS = {
1896
- button: {
1897
- description: "Triggers an action or navigation. The primary interactive element.",
1898
- role: "action-trigger",
1899
- hierarchyLevel: "primary",
1900
- interactionModel: "synchronous",
1901
- layoutImpact: "inline",
1902
- destructive: false,
1903
- sizes: ["sm", "md", "lg"]
1904
- },
1905
- input: {
1906
- description: "Accepts user text input. Use with a label for accessibility.",
1907
- role: "data-entry",
1908
- hierarchyLevel: "primary",
1909
- interactionModel: "synchronous",
1910
- layoutImpact: "block",
1911
- destructive: false,
1912
- sizes: ["sm", "md", "lg"]
1913
- },
1914
- card: {
1915
- description: "Groups related content with optional header, body, and footer slots.",
1916
- role: "content-container",
1917
- hierarchyLevel: "utility",
1918
- interactionModel: "none",
1919
- layoutImpact: "block",
1920
- destructive: false
1921
- },
1922
- badge: {
1923
- description: "Compact label for status, categories, or counts. Display-only \u2014 not interactive.",
1924
- role: "status-indicator",
1925
- hierarchyLevel: "utility",
1926
- interactionModel: "none",
1927
- layoutImpact: "inline",
1928
- destructive: false,
1929
- sizes: ["sm", "md", "lg"]
1930
- },
1931
- checkbox: {
1932
- description: "Binary toggle for boolean values. Supports indeterminate state for partial selections.",
1933
- role: "data-entry",
1934
- hierarchyLevel: "primary",
1935
- interactionModel: "synchronous",
1936
- layoutImpact: "inline",
1937
- destructive: false,
1938
- sizes: ["sm", "md", "lg"]
1939
- },
1940
- radio: {
1941
- description: "Single selection within a mutually exclusive group. Always use inside RadioGroup.",
1942
- role: "data-entry",
1943
- hierarchyLevel: "primary",
1944
- interactionModel: "synchronous",
1945
- layoutImpact: "inline",
1946
- destructive: false,
1947
- sizes: ["sm", "md", "lg"]
1948
- },
1949
- select: {
1950
- description: "Dropdown picker for selecting from a list of options. Wraps native <select> for accessibility.",
1951
- role: "data-entry",
1952
- hierarchyLevel: "primary",
1953
- interactionModel: "synchronous",
1954
- layoutImpact: "block",
1955
- destructive: false,
1956
- sizes: ["sm", "md", "lg"]
1957
- },
1958
- toast: {
1959
- description: "Feedback messages for user actions. Alert is inline; Toast is an overlay with auto-dismiss.",
1960
- role: "feedback",
1961
- hierarchyLevel: "utility",
1962
- interactionModel: "asynchronous",
1963
- layoutImpact: "overlay",
1964
- destructive: false
1965
- },
1966
- spinner: {
1967
- description: "Loading indicator for async operations. Use with an accessible label for screen readers.",
1968
- role: "loading-indicator",
1969
- hierarchyLevel: "utility",
1970
- interactionModel: "asynchronous",
1971
- layoutImpact: "inline",
1972
- destructive: false,
1973
- sizes: ["xs", "sm", "md", "lg", "xl"]
1974
- }
1975
- };
1976
- function buildComponentMetadata(componentName, rule, config) {
1977
- const defaults = COMPONENT_DEFAULTS[componentName.toLowerCase()] ?? {};
1978
- const variants = rule.allowedVariants ?? ["default"];
1979
- const requiredProps = rule.requiredProps ?? [];
1980
- const tokens = {};
1981
- for (const [tokenName] of Object.entries(rule.tokens ?? {})) {
1982
- tokens[tokenName] = `--${tokenName}`;
1983
- }
1984
- const meta = {
1985
- component: pascalCase(componentName),
1986
- version: config.meta.version,
1987
- description: defaults.description ?? `A ${componentName} component.`,
1988
- role: defaults.role ?? "ui-element",
1989
- hierarchyLevel: defaults.hierarchyLevel ?? "utility",
1990
- interactionModel: defaults.interactionModel ?? "none",
1991
- layoutImpact: defaults.layoutImpact ?? "inline",
1992
- destructive: componentName.toLowerCase().includes("delete") || variants.includes("danger"),
1993
- allowedVariants: variants,
1994
- defaultVariant: variants[0] ?? "default",
1995
- requiredProps,
1996
- optionalProps: buildOptionalProps(componentName, defaults),
1997
- tokens,
1998
- accessibilityContract: {
1999
- keyboard: rule.a11y?.keyboard ?? true,
2000
- focusRing: rule.a11y?.focusRing ?? true,
2001
- ariaLabel: rule.a11y?.ariaLabel ?? "optional",
2002
- ...rule.a11y?.role ? { role: rule.a11y.role } : {}
2003
- },
2004
- governanceRules: {
2005
- ...rule.maxWidth ? { maxWidth: rule.maxWidth } : {},
2006
- ...rule.allowedRadius ? { allowedRadius: rule.allowedRadius } : {},
2007
- ...rule.allowedShadows ? { allowedShadows: rule.allowedShadows } : {},
2008
- ...rule.colorPalette ? { colorPalette: rule.colorPalette } : {}
2009
- }
2010
- };
2011
- if (defaults.sizes) {
2012
- meta.sizes = defaults.sizes;
2013
- }
2014
- return meta;
2015
- }
2016
- function buildOptionalProps(componentName, _defaults) {
2017
- const common = ["className", "style", "id", "data-testid"];
2018
- const byComponent = {
2019
- button: [
2020
- "size",
2021
- "loading",
2022
- "disabled",
2023
- "fullWidth",
2024
- "iconLeft",
2025
- "iconRight",
2026
- "onClick"
2027
- ],
2028
- input: [
2029
- "size",
2030
- "disabled",
2031
- "label",
2032
- "helperText",
2033
- "errorMessage",
2034
- "placeholder",
2035
- "startAdornment",
2036
- "endAdornment",
2037
- "onChange"
2038
- ],
2039
- card: ["maxWidth", "noPadding", "onClick"],
2040
- badge: ["size", "dot"],
2041
- checkbox: ["size", "disabled", "label", "helperText", "indeterminate", "checked", "onChange"],
2042
- radio: ["size", "disabled", "label", "value", "onChange"],
2043
- select: ["size", "disabled", "label", "helperText", "errorMessage", "placeholder", "options", "fullWidth", "onChange"],
2044
- toast: ["variant", "title", "dismissible", "duration", "onDismiss"],
2045
- spinner: ["size", "variant", "label"]
2046
- };
2047
- return [...byComponent[componentName.toLowerCase()] ?? [], ...common];
2048
- }
2049
- function pascalCase(str) {
2050
- return str.charAt(0).toUpperCase() + str.slice(1);
2051
- }
2052
- function buildIndexMetadata(config, componentNames, tokenCount) {
2053
- return {
2054
- name: config.meta.name,
2055
- version: config.meta.version,
2056
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2057
- components: componentNames.map(pascalCase),
2058
- tokenCount,
2059
- themes: Object.keys(config.themes ?? {})
2060
- };
2061
- }
2062
- function generateMetadata(config, rules, tokenCount) {
2063
- const files = [];
2064
- const componentNames = Object.keys(rules);
2065
- for (const [componentName, rule] of Object.entries(rules)) {
2066
- const metadata = buildComponentMetadata(componentName, rule, config);
2067
- files.push({
2068
- filename: `${componentName}.json`,
2069
- content: JSON.stringify(metadata, null, 2)
2070
- });
2071
- }
2072
- const index = buildIndexMetadata(config, componentNames, tokenCount);
2073
- files.push({
2074
- filename: "index.json",
2075
- content: JSON.stringify(index, null, 2)
2076
- });
2077
- return files;
2078
- }
2079
-
2080
1910
  // src/adapters/react/components/button.ts
2081
1911
  function generateButton(config, rule) {
2082
1912
  const variants = rule?.allowedVariants ?? [
@@ -2866,9 +2696,17 @@ export interface CheckboxProps
2866
2696
  // \u2500\u2500\u2500 Size map \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
2867
2697
 
2868
2698
  const SIZE_BOX: Record<"sm" | "md" | "lg", string> = {
2869
- sm: "14px",
2870
- md: "16px",
2871
- lg: "20px",
2699
+ sm: "var(--control-size-sm, 14px)",
2700
+ md: "var(--control-size-md, 16px)",
2701
+ lg: "var(--control-size-lg, 20px)",
2702
+ };
2703
+
2704
+ // SVG check/dash icon fits inside the box with a 6px inset, via calc() so it
2705
+ // tracks the CSS var when density changes at runtime.
2706
+ const SVG_SIZE: Record<"sm" | "md" | "lg", string> = {
2707
+ sm: "calc(var(--control-size-sm, 14px) - 6px)",
2708
+ md: "calc(var(--control-size-md, 16px) - 6px)",
2709
+ lg: "calc(var(--control-size-lg, 20px) - 6px)",
2872
2710
  };
2873
2711
 
2874
2712
  const SIZE_FONT: Record<"sm" | "md" | "lg", string> = {
@@ -3012,8 +2850,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
3012
2850
  <span style={boxStyle} aria-hidden="true">
3013
2851
  {isActive && (
3014
2852
  <svg
3015
- width={parseInt(boxSize) - 6}
3016
- height={parseInt(boxSize) - 6}
2853
+ style={{ width: SVG_SIZE[size], height: SVG_SIZE[size] }}
3017
2854
  viewBox="0 0 10 10"
3018
2855
  fill="none"
3019
2856
  stroke="#fff"
@@ -3115,9 +2952,9 @@ export interface RadioGroupProps {
3115
2952
  // \u2500\u2500\u2500 Size map \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
3116
2953
 
3117
2954
  const SIZE_BOX: Record<"sm" | "md" | "lg", string> = {
3118
- sm: "14px",
3119
- md: "16px",
3120
- lg: "20px",
2955
+ sm: "var(--control-size-sm, 14px)",
2956
+ md: "var(--control-size-md, 16px)",
2957
+ lg: "var(--control-size-lg, 20px)",
3121
2958
  };
3122
2959
 
3123
2960
  const SIZE_FONT: Record<"sm" | "md" | "lg", string> = {
@@ -4088,6 +3925,62 @@ function generateThemeProvider(config) {
4088
3925
  const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
4089
3926
  const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
4090
3927
  const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
3928
+ const isPro = isProUnlocked();
3929
+ const defaultDensity = config.meta.preset ?? "comfortable";
3930
+ const densityImport = isPro ? `
3931
+ import "../tokens/density.css";` : "";
3932
+ const densityTypes = isPro ? `
3933
+ export type DensityName = "compact" | "comfortable" | "spacious";
3934
+ ` : "";
3935
+ const densityContextTypes = isPro ? `
3936
+ export interface DensityContextValue {
3937
+ density: DensityName;
3938
+ setDensity: (density: DensityName) => void;
3939
+ }
3940
+ ` : "";
3941
+ const densityContext = isPro ? `
3942
+ export const DensityContext = React.createContext<DensityContextValue>({
3943
+ density: "${defaultDensity}",
3944
+ setDensity: () => undefined,
3945
+ });
3946
+
3947
+ /**
3948
+ * Hook to read and change the current density.
3949
+ * Must be used inside a <ThemeProvider>.
3950
+ */
3951
+ export function useDensity(): DensityContextValue {
3952
+ return React.useContext(DensityContext);
3953
+ }
3954
+ ` : "";
3955
+ const densityProp = isPro ? `
3956
+ /** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
3957
+ density?: DensityName;` : "";
3958
+ const densityOnChangeProp = isPro ? `
3959
+ /** Called when setDensity is invoked. */
3960
+ onDensityChange?: (density: DensityName) => void;` : "";
3961
+ const densityState = isPro ? `
3962
+ const [density, setDensityState] = React.useState<DensityName>(initialDensity);
3963
+
3964
+ React.useEffect(() => {
3965
+ setDensityState(initialDensity);
3966
+ }, [initialDensity]);
3967
+
3968
+ const setDensity = React.useCallback(
3969
+ (next: DensityName) => {
3970
+ setDensityState(next);
3971
+ onDensityChange?.(next);
3972
+ },
3973
+ [onDensityChange],
3974
+ );
3975
+ ` : "";
3976
+ const densityDestructure = isPro ? `,
3977
+ density: initialDensity = "${defaultDensity}",
3978
+ onDensityChange,` : "";
3979
+ const densityProviderOpen = isPro ? `
3980
+ <DensityContext.Provider value={{ density, setDensity }}>` : "";
3981
+ const densityDataAttr = isPro ? ` data-density={density}` : "";
3982
+ const densityProviderClose = isPro ? `
3983
+ </DensityContext.Provider>` : "";
4091
3984
  return `/**
4092
3985
  * ThemeProvider \u2014 ${config.meta.name}
4093
3986
  *
@@ -4099,27 +3992,27 @@ function generateThemeProvider(config) {
4099
3992
  * import "@${config.meta.name}/tokens/light.css"; // or dark.css
4100
3993
  * import { ThemeProvider } from "@${config.meta.name}";
4101
3994
  *
4102
- * <ThemeProvider theme="light">
3995
+ * <ThemeProvider theme="light"${isPro ? ` density="${defaultDensity}"` : ""}>
4103
3996
  * <App />
4104
3997
  * </ThemeProvider>
4105
3998
  */
4106
3999
 
4107
- import React from "react";
4000
+ import React from "react";${densityImport}
4108
4001
 
4109
4002
  // \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
4003
 
4111
4004
  export type ThemeName = ${themeType};
4112
-
4005
+ ${densityTypes}
4113
4006
  export interface ThemeContextValue {
4114
4007
  theme: ThemeName;
4115
4008
  setTheme: (theme: ThemeName) => void;
4116
4009
  }
4117
-
4010
+ ${densityContextTypes}
4118
4011
  export interface ThemeProviderProps {
4119
4012
  /** Initial theme. Defaults to "${defaultTheme}". */
4120
4013
  theme?: ThemeName;
4121
4014
  /** Called when setTheme is invoked \u2014 use to persist theme preference. */
4122
- onThemeChange?: (theme: ThemeName) => void;
4015
+ onThemeChange?: (theme: ThemeName) => void;${densityProp}${densityOnChangeProp}
4123
4016
  children: React.ReactNode;
4124
4017
  }
4125
4018
 
@@ -4137,12 +4030,12 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
4137
4030
  export function useTheme(): ThemeContextValue {
4138
4031
  return React.useContext(ThemeContext);
4139
4032
  }
4140
-
4033
+ ${densityContext}
4141
4034
  // \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
4035
 
4143
4036
  export function ThemeProvider({
4144
4037
  theme: initialTheme = "${defaultTheme}",
4145
- onThemeChange,
4038
+ onThemeChange,${densityDestructure}
4146
4039
  children,
4147
4040
  }: ThemeProviderProps) {
4148
4041
  const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
@@ -4158,13 +4051,13 @@ export function ThemeProvider({
4158
4051
  },
4159
4052
  [onThemeChange],
4160
4053
  );
4161
-
4162
- return (
4163
- <ThemeContext.Provider value={{ theme, setTheme }}>
4164
- <div data-theme={theme} style={{ display: "contents" }}>
4165
- {children}
4166
- </div>
4167
- </ThemeContext.Provider>
4054
+ ${densityState}
4055
+ return (${densityProviderOpen}
4056
+ <ThemeContext.Provider value={{ theme, setTheme }}>
4057
+ <div data-theme={theme}${densityDataAttr} style={{ display: "contents" }}>
4058
+ {children}
4059
+ </div>
4060
+ </ThemeContext.Provider>${densityProviderClose}
4168
4061
  );
4169
4062
  }
4170
4063
  `;
@@ -4181,9 +4074,9 @@ function generateComponentIndex(config, componentNames) {
4181
4074
  ""
4182
4075
  ];
4183
4076
  for (const name of componentNames) {
4184
- lines.push(`export * from "./${name}";`);
4077
+ lines.push(`export * from "./components/${name}/${name}";`);
4185
4078
  }
4186
- lines.push(`export * from "./ThemeProvider";`);
4079
+ lines.push(`export * from "./components/ThemeProvider/ThemeProvider";`);
4187
4080
  lines.push("");
4188
4081
  return lines.join("\n");
4189
4082
  }
@@ -4761,6 +4654,13 @@ async function runGenerate(cwd, options) {
4761
4654
  options.debug ?? false
4762
4655
  );
4763
4656
  }
4657
+ const presetValue = config.meta.preset;
4658
+ if (presetValue === "compact" || presetValue === "comfortable" || presetValue === "spacious") {
4659
+ applyPreset(config, presetValue);
4660
+ }
4661
+ const fullRules = Object.fromEntries(
4662
+ REACT_COMPONENTS.map((name) => [name, rules[name] ?? {}])
4663
+ );
4764
4664
  logger.step("Running pre-flight validation...");
4765
4665
  const validation = validateConfig(config, rules);
4766
4666
  const errors = validation.issues.filter((i) => i.severity === "error");
@@ -4814,6 +4714,13 @@ async function runGenerate(cwd, options) {
4814
4714
  await writeFile(path3.join(tokensDir, filename), content);
4815
4715
  logger.dim(` \u2192 tokens/${filename}`);
4816
4716
  }
4717
+ if (isProUnlocked()) {
4718
+ await writeFile(
4719
+ path3.join(tokensDir, "density.css"),
4720
+ emitDensityCss(config)
4721
+ );
4722
+ logger.dim(` \u2192 tokens/density.css`);
4723
+ }
4817
4724
  const tokenFiles = reactAdapter.generateTokenFiles(config, resolution);
4818
4725
  for (const { filename, content } of tokenFiles) {
4819
4726
  await writeFile(path3.join(tokensDir, filename), content);
@@ -4823,21 +4730,55 @@ async function runGenerate(cwd, options) {
4823
4730
  }
4824
4731
  if (!only || only === "components") {
4825
4732
  logger.step("Generating React components...");
4826
- const srcDir = path3.join(outRoot, "src");
4827
- await ensureDir(srcDir);
4733
+ const componentsDir = path3.join(outRoot, "components");
4734
+ await ensureDir(componentsDir);
4828
4735
  const generatedNames = [];
4736
+ const generatedComponentJsons = [];
4737
+ const flatTokens = {};
4738
+ for (const [k, v] of Object.entries(resolution.tokens)) {
4739
+ flatTokens[`--${k.replace(/^(global|semantic|component)\./, "")}`] = v;
4740
+ }
4741
+ const lightOverrides = config.themes?.["light"] ?? {};
4742
+ const darkOverrides = config.themes?.["dark"] ?? {};
4743
+ const lightCssVars = {
4744
+ ...flatTokens,
4745
+ ...Object.fromEntries(Object.entries(lightOverrides).map(([k, v]) => [`--${k}`, String(v)]))
4746
+ };
4747
+ const darkCssVars = {
4748
+ ...flatTokens,
4749
+ ...Object.fromEntries(Object.entries(darkOverrides).map(([k, v]) => [`--${k}`, String(v)]))
4750
+ };
4751
+ const resolvedCssVars = { light: lightCssVars, dark: darkCssVars };
4752
+ const { generateComponentJson } = await import("../generateComponentJson-XBEUWCW6.js");
4753
+ const { generateComponentMetadata } = await import("../generateComponentMetadata-2L5VNERD.js");
4829
4754
  for (const componentName of REACT_COMPONENTS) {
4755
+ const pascalName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
4830
4756
  try {
4831
4757
  const { filename, content } = reactAdapter.generateComponent(
4832
4758
  componentName,
4833
4759
  config,
4834
4760
  rules[componentName]
4835
4761
  );
4836
- await writeFile(path3.join(srcDir, filename), content);
4837
- logger.dim(` \u2192 src/${filename}`);
4838
- generatedNames.push(
4839
- componentName.charAt(0).toUpperCase() + componentName.slice(1)
4762
+ const componentSubDir = path3.join(componentsDir, pascalName);
4763
+ await ensureDir(componentSubDir);
4764
+ await writeFile(path3.join(componentSubDir, filename), content);
4765
+ logger.dim(` \u2192 components/${pascalName}/${filename}`);
4766
+ generatedNames.push(pascalName);
4767
+ const componentJson = generateComponentJson(pascalName, resolvedCssVars);
4768
+ generatedComponentJsons.push(componentJson);
4769
+ await writeFile(
4770
+ path3.join(componentSubDir, `${pascalName}.json`),
4771
+ JSON.stringify(componentJson, null, 2)
4840
4772
  );
4773
+ logger.dim(` \u2192 components/${pascalName}/${pascalName}.json`);
4774
+ if (isProUnlocked()) {
4775
+ const metadata = generateComponentMetadata(pascalName);
4776
+ await writeFile(
4777
+ path3.join(componentSubDir, `${pascalName}.metadata.json`),
4778
+ JSON.stringify(metadata, null, 2)
4779
+ );
4780
+ logger.dim(` \u2192 components/${pascalName}/${pascalName}.metadata.json`);
4781
+ }
4841
4782
  } catch (err) {
4842
4783
  logger.warn(
4843
4784
  `[dsforge] Could not generate ${componentName} \u2014 ${err.message}`
@@ -4845,76 +4786,117 @@ async function runGenerate(cwd, options) {
4845
4786
  }
4846
4787
  }
4847
4788
  const { filename: tpFile, content: tpContent } = reactAdapter.generateThemeProvider(config);
4848
- await writeFile(path3.join(srcDir, tpFile), tpContent);
4849
- logger.dim(` \u2192 src/${tpFile}`);
4850
- const { filename: idxFile, content: idxContent } = reactAdapter.generateComponentIndex(config, generatedNames);
4851
- await writeFile(path3.join(srcDir, idxFile), idxContent);
4852
- logger.dim(` \u2192 src/${idxFile}`);
4853
- logger.success(`${generatedNames.length} components generated`);
4854
- }
4855
- if (!only || only === "metadata") {
4856
- logger.step("Writing AI metadata...");
4857
- const metaDir = path3.join(outRoot, "metadata");
4858
- await ensureDir(metaDir);
4859
- const metaFiles = generateMetadata(config, rules, tokenCount);
4860
- for (const { filename, content } of metaFiles) {
4861
- await writeFile(path3.join(metaDir, filename), content);
4862
- logger.dim(` \u2192 metadata/${filename}`);
4863
- }
4864
- logger.success(`Metadata written (${metaFiles.length} files)`);
4865
- }
4866
- if (!only || only === "docs") {
4867
- logger.step("Generating docs...");
4868
- const docsDir = path3.join(outRoot, "docs");
4869
- await ensureDir(docsDir);
4870
- const metadataFiles = generateMetadata(config, rules, tokenCount);
4871
- const metadataMap = {};
4872
- for (const { filename, content } of metadataFiles) {
4873
- const name = filename.replace(".json", "");
4874
- if (name !== "index") {
4875
- metadataMap[name] = JSON.parse(
4876
- content
4789
+ const tpDir = path3.join(componentsDir, "ThemeProvider");
4790
+ await ensureDir(tpDir);
4791
+ await writeFile(path3.join(tpDir, tpFile), tpContent);
4792
+ logger.dim(` \u2192 components/ThemeProvider/${tpFile}`);
4793
+ try {
4794
+ const tpJson = generateComponentJson("ThemeProvider", resolvedCssVars);
4795
+ generatedComponentJsons.push(tpJson);
4796
+ await writeFile(
4797
+ path3.join(tpDir, "ThemeProvider.json"),
4798
+ JSON.stringify(tpJson, null, 2)
4799
+ );
4800
+ logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.json`);
4801
+ if (isProUnlocked()) {
4802
+ const tpMeta = generateComponentMetadata("ThemeProvider");
4803
+ await writeFile(
4804
+ path3.join(tpDir, "ThemeProvider.metadata.json"),
4805
+ JSON.stringify(tpMeta, null, 2)
4877
4806
  );
4807
+ logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.metadata.json`);
4878
4808
  }
4809
+ } catch {
4879
4810
  }
4880
- const docFiles = reactAdapter.generateDocs(config, rules, metadataMap);
4881
- for (const { filename, content } of docFiles) {
4882
- await writeFile(path3.join(docsDir, filename), content);
4883
- logger.dim(` \u2192 docs/${filename}`);
4884
- }
4885
- logger.success(`Docs written (${docFiles.length} files)`);
4811
+ const { filename: idxFile, content: idxContent } = reactAdapter.generateComponentIndex(config, generatedNames);
4812
+ await writeFile(path3.join(outRoot, idxFile), idxContent);
4813
+ logger.dim(` \u2192 ${idxFile}`);
4814
+ logger.success(`${generatedNames.length} components generated`);
4815
+ globalThis["__dsforgGeneratedJsons"] = generatedComponentJsons;
4886
4816
  }
4887
4817
  if (!only) {
4888
4818
  logger.step("Writing package files...");
4889
- const componentNames = Object.keys(rules);
4819
+ const componentNames = Object.keys(fullRules);
4890
4820
  const { filename: pkgFile, content: pkgContent } = reactAdapter.generatePackageManifest(config, componentNames);
4891
4821
  await writeFile(path3.join(outRoot, pkgFile), pkgContent);
4892
4822
  logger.dim(` \u2192 ${pkgFile}`);
4893
4823
  const tsConfig = generateTsConfig();
4894
4824
  await writeFile(path3.join(outRoot, "tsconfig.json"), tsConfig);
4895
4825
  logger.dim(` \u2192 tsconfig.json`);
4896
- const { generateReadme } = await import("../emitter-ZNRPJ4D6.js");
4826
+ const { generateReadme } = await import("../emitter-IC77G4QF.js");
4897
4827
  await writeFile(
4898
4828
  path3.join(outRoot, "README.md"),
4899
4829
  generateReadme(config, componentNames)
4900
4830
  );
4901
4831
  logger.dim(` \u2192 README.md`);
4902
4832
  const changelogPath = path3.join(outRoot, "CHANGELOG.md");
4903
- const fsExtra2 = await import("fs-extra");
4904
- const fsE2 = fsExtra2.default ?? fsExtra2;
4833
+ const fsExtra = await import("fs-extra");
4834
+ const fsE2 = fsExtra.default ?? fsExtra;
4905
4835
  if (!await fsE2.pathExists(changelogPath)) {
4906
4836
  await writeFile(changelogPath, generateChangelog(config));
4907
4837
  logger.dim(` \u2192 CHANGELOG.md (seeded)`);
4908
4838
  }
4909
4839
  logger.success(`Package files written`);
4910
4840
  }
4841
+ if (isProUnlocked() && (!only || only === "components")) {
4842
+ logger.step("Writing Pro outputs...");
4843
+ const generatedJsons = globalThis["__dsforgGeneratedJsons"] ?? [];
4844
+ const { generateRegistry } = await import("../generateRegistry-3MEZDJAJ.js");
4845
+ const {
4846
+ generateSystemPrompt,
4847
+ generateComponentsJson,
4848
+ generateCursorContext,
4849
+ generateCopilotInstructions
4850
+ } = await import("../generateAiFolder-3OOFWBH7.js");
4851
+ const systemName = config.meta.name;
4852
+ const version = config.meta.version;
4853
+ const registry = generateRegistry(systemName, version, generatedJsons);
4854
+ await writeFile(path3.join(outRoot, "registry.json"), JSON.stringify(registry, null, 2));
4855
+ logger.dim(` \u2192 registry.json`);
4856
+ const { COMPONENT_METADATA_DEFINITIONS } = await import("../componentDefinitions-5LFCNFQY.js");
4857
+ const metadataList = generatedJsons.map((c) => COMPONENT_METADATA_DEFINITIONS[c.name]).filter((m) => Boolean(m));
4858
+ const flatTokensForAi = {};
4859
+ for (const [k, v] of Object.entries(resolution.tokens)) {
4860
+ const cssVar2 = `--${k.replace(/^(global|semantic|component)\./, "")}`;
4861
+ flatTokensForAi[cssVar2] = {
4862
+ light: config.themes?.["light"]?.[cssVar2.slice(2)] ?? v,
4863
+ dark: config.themes?.["dark"]?.[cssVar2.slice(2)] ?? v
4864
+ };
4865
+ }
4866
+ const aiDir = path3.join(outRoot, "ai");
4867
+ await ensureDir(aiDir);
4868
+ const cursorDir = path3.join(aiDir, ".cursor");
4869
+ await ensureDir(cursorDir);
4870
+ const componentNames = generatedJsons.map((c) => c.name);
4871
+ await writeFile(
4872
+ path3.join(aiDir, "system-prompt.md"),
4873
+ generateSystemPrompt(systemName, flatTokensForAi, componentNames)
4874
+ );
4875
+ logger.dim(` \u2192 ai/system-prompt.md`);
4876
+ await writeFile(
4877
+ path3.join(aiDir, "components.json"),
4878
+ generateComponentsJson(systemName, generatedJsons, metadataList)
4879
+ );
4880
+ logger.dim(` \u2192 ai/components.json`);
4881
+ await writeFile(
4882
+ path3.join(cursorDir, "context.md"),
4883
+ generateCursorContext(systemName)
4884
+ );
4885
+ logger.dim(` \u2192 ai/.cursor/context.md`);
4886
+ await writeFile(
4887
+ path3.join(outRoot, "copilot-instructions.md"),
4888
+ generateCopilotInstructions(systemName)
4889
+ );
4890
+ logger.dim(` \u2192 copilot-instructions.md`);
4891
+ logger.success(`Pro outputs written`);
4892
+ }
4911
4893
  logger.step("Generating showcase...");
4912
- const { generateShowcase } = await import("../html-6SIG34W5.js");
4894
+ const { generateShowcase } = await import("../html-XGJ22SXB.js");
4913
4895
  const showcaseHtml = generateShowcase(config, resolution);
4914
4896
  await writeFile(path3.join(outRoot, "showcase.html"), showcaseHtml);
4915
4897
  logger.dim(` \u2192 showcase.html`);
4916
- const fsExtra = await import("fs-extra");
4917
- const fsE = fsExtra.default ?? fsExtra;
4898
+ const fsExtraShowcase = await import("fs-extra");
4899
+ const fsE = fsExtraShowcase.default ?? fsExtraShowcase;
4918
4900
  const faviconSrc = path3.join(cwd, "assets", "favicon.svg");
4919
4901
  const faviconDest = path3.join(outRoot, "assets", "favicon.svg");
4920
4902
  if (await fsE.pathExists(faviconSrc)) {
@@ -5296,7 +5278,7 @@ async function runMenu() {
5296
5278
  // package.json
5297
5279
  var package_default = {
5298
5280
  name: "@nghitrum/dsforge",
5299
- version: "0.1.5-alpha.1",
5281
+ version: "0.1.5-alpha.10",
5300
5282
  description: "AI-native design system generator \u2014 tokens \u2192 components \u2192 docs \u2192 npm",
5301
5283
  keywords: [
5302
5284
  "design-system",