@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/LICENSE.md +21 -0
- package/README.md +0 -1
- package/dist/{chunk-JUMR3N5J.js → chunk-5YT3VNE6.js} +6 -26
- package/dist/chunk-A7VW6SII.js +436 -0
- package/dist/{chunk-QHE35QQQ.js → chunk-ZZRPNO6Z.js} +12 -17
- package/dist/cli/index.js +218 -310
- package/dist/componentDefinitions-5LFCNFQY.js +8 -0
- package/dist/{emitter-KNYIQTS5.js → emitter-IC77G4QF.js} +1 -1
- package/dist/generateAiFolder-3OOFWBH7.js +70 -0
- package/dist/generateComponentJson-XBEUWCW6.js +16 -0
- package/dist/generateComponentMetadata-2L5VNERD.js +13 -0
- package/dist/generateRegistry-3MEZDJAJ.js +19 -0
- package/dist/{html-LQHDCSG4.js → html-4DD6GOHE.js} +223 -130
- package/dist/index.js +86 -110
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3,16 +3,16 @@ import {
|
|
|
3
3
|
generateChangelog,
|
|
4
4
|
generatePackageJson,
|
|
5
5
|
generateTsConfig
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-ZZRPNO6Z.js";
|
|
7
7
|
import {
|
|
8
|
+
CONTROL_SIZE_PRESETS,
|
|
8
9
|
PRESETS,
|
|
9
10
|
PRESET_BASE_UNITS,
|
|
10
11
|
RADIUS_PRESETS,
|
|
11
12
|
SPACING_PRESETS,
|
|
12
13
|
applyPreset,
|
|
13
|
-
buildSemanticSpacing
|
|
14
|
-
|
|
15
|
-
} from "../chunk-JUMR3N5J.js";
|
|
14
|
+
buildSemanticSpacing
|
|
15
|
+
} from "../chunk-5YT3VNE6.js";
|
|
16
16
|
|
|
17
17
|
// src/cli/index.ts
|
|
18
18
|
import { program } from "commander";
|
|
@@ -737,15 +737,7 @@ async function runInit(cwd, options) {
|
|
|
737
737
|
}
|
|
738
738
|
const name = rawName.replace(/\s+/g, "-").toLowerCase();
|
|
739
739
|
let preset;
|
|
740
|
-
if (
|
|
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)) {
|
|
740
|
+
if (options.preset && VALID_PRESETS.includes(options.preset)) {
|
|
749
741
|
preset = options.preset;
|
|
750
742
|
} else {
|
|
751
743
|
const answer = await ask(
|
|
@@ -1773,6 +1765,19 @@ function emitBaseCss(config, resolution) {
|
|
|
1773
1765
|
if (typoEntries.length > 0) {
|
|
1774
1766
|
sections.push(emitBlock(":root", typoEntries, "Typography"));
|
|
1775
1767
|
}
|
|
1768
|
+
const currentPreset = config.philosophy?.density ?? "comfortable";
|
|
1769
|
+
const controlSizes = CONTROL_SIZE_PRESETS[currentPreset] ?? CONTROL_SIZE_PRESETS.comfortable;
|
|
1770
|
+
sections.push(
|
|
1771
|
+
emitBlock(
|
|
1772
|
+
":root",
|
|
1773
|
+
[
|
|
1774
|
+
["control-size-sm", `${controlSizes.sm}px`],
|
|
1775
|
+
["control-size-md", `${controlSizes.md}px`],
|
|
1776
|
+
["control-size-lg", `${controlSizes.lg}px`]
|
|
1777
|
+
],
|
|
1778
|
+
"Control sizes"
|
|
1779
|
+
)
|
|
1780
|
+
);
|
|
1776
1781
|
const radiusEntries = Object.entries(
|
|
1777
1782
|
config.radius ?? {}
|
|
1778
1783
|
).filter(([, v]) => v !== void 0).map(([k, v]) => [`radius-${k}`, v === 9999 ? "9999px" : `${v}px`]);
|
|
@@ -1854,6 +1859,10 @@ function emitDensityCss(config) {
|
|
|
1854
1859
|
for (const [key, value] of Object.entries(radius)) {
|
|
1855
1860
|
entries.push([`radius-${key}`, value === 9999 ? "9999px" : `${value}px`]);
|
|
1856
1861
|
}
|
|
1862
|
+
const controlSizes = CONTROL_SIZE_PRESETS[preset];
|
|
1863
|
+
entries.push(["control-size-sm", `${controlSizes.sm}px`]);
|
|
1864
|
+
entries.push(["control-size-md", `${controlSizes.md}px`]);
|
|
1865
|
+
entries.push(["control-size-lg", `${controlSizes.lg}px`]);
|
|
1857
1866
|
lines.push(emitBlock(`[data-density="${preset}"]`, entries, `Preset: ${preset}`));
|
|
1858
1867
|
lines.push("");
|
|
1859
1868
|
}
|
|
@@ -1889,192 +1898,6 @@ function generateCssFiles(config, resolution) {
|
|
|
1889
1898
|
return files;
|
|
1890
1899
|
}
|
|
1891
1900
|
|
|
1892
|
-
// src/generators/metadata/generator.ts
|
|
1893
|
-
var COMPONENT_DEFAULTS = {
|
|
1894
|
-
button: {
|
|
1895
|
-
description: "Triggers an action or navigation. The primary interactive element.",
|
|
1896
|
-
role: "action-trigger",
|
|
1897
|
-
hierarchyLevel: "primary",
|
|
1898
|
-
interactionModel: "synchronous",
|
|
1899
|
-
layoutImpact: "inline",
|
|
1900
|
-
destructive: false,
|
|
1901
|
-
sizes: ["sm", "md", "lg"]
|
|
1902
|
-
},
|
|
1903
|
-
input: {
|
|
1904
|
-
description: "Accepts user text input. Use with a label for accessibility.",
|
|
1905
|
-
role: "data-entry",
|
|
1906
|
-
hierarchyLevel: "primary",
|
|
1907
|
-
interactionModel: "synchronous",
|
|
1908
|
-
layoutImpact: "block",
|
|
1909
|
-
destructive: false,
|
|
1910
|
-
sizes: ["sm", "md", "lg"]
|
|
1911
|
-
},
|
|
1912
|
-
card: {
|
|
1913
|
-
description: "Groups related content with optional header, body, and footer slots.",
|
|
1914
|
-
role: "content-container",
|
|
1915
|
-
hierarchyLevel: "utility",
|
|
1916
|
-
interactionModel: "none",
|
|
1917
|
-
layoutImpact: "block",
|
|
1918
|
-
destructive: false
|
|
1919
|
-
},
|
|
1920
|
-
badge: {
|
|
1921
|
-
description: "Compact label for status, categories, or counts. Display-only \u2014 not interactive.",
|
|
1922
|
-
role: "status-indicator",
|
|
1923
|
-
hierarchyLevel: "utility",
|
|
1924
|
-
interactionModel: "none",
|
|
1925
|
-
layoutImpact: "inline",
|
|
1926
|
-
destructive: false,
|
|
1927
|
-
sizes: ["sm", "md", "lg"]
|
|
1928
|
-
},
|
|
1929
|
-
checkbox: {
|
|
1930
|
-
description: "Binary toggle for boolean values. Supports indeterminate state for partial selections.",
|
|
1931
|
-
role: "data-entry",
|
|
1932
|
-
hierarchyLevel: "primary",
|
|
1933
|
-
interactionModel: "synchronous",
|
|
1934
|
-
layoutImpact: "inline",
|
|
1935
|
-
destructive: false,
|
|
1936
|
-
sizes: ["sm", "md", "lg"]
|
|
1937
|
-
},
|
|
1938
|
-
radio: {
|
|
1939
|
-
description: "Single selection within a mutually exclusive group. Always use inside RadioGroup.",
|
|
1940
|
-
role: "data-entry",
|
|
1941
|
-
hierarchyLevel: "primary",
|
|
1942
|
-
interactionModel: "synchronous",
|
|
1943
|
-
layoutImpact: "inline",
|
|
1944
|
-
destructive: false,
|
|
1945
|
-
sizes: ["sm", "md", "lg"]
|
|
1946
|
-
},
|
|
1947
|
-
select: {
|
|
1948
|
-
description: "Dropdown picker for selecting from a list of options. Wraps native <select> for accessibility.",
|
|
1949
|
-
role: "data-entry",
|
|
1950
|
-
hierarchyLevel: "primary",
|
|
1951
|
-
interactionModel: "synchronous",
|
|
1952
|
-
layoutImpact: "block",
|
|
1953
|
-
destructive: false,
|
|
1954
|
-
sizes: ["sm", "md", "lg"]
|
|
1955
|
-
},
|
|
1956
|
-
toast: {
|
|
1957
|
-
description: "Feedback messages for user actions. Alert is inline; Toast is an overlay with auto-dismiss.",
|
|
1958
|
-
role: "feedback",
|
|
1959
|
-
hierarchyLevel: "utility",
|
|
1960
|
-
interactionModel: "asynchronous",
|
|
1961
|
-
layoutImpact: "overlay",
|
|
1962
|
-
destructive: false
|
|
1963
|
-
},
|
|
1964
|
-
spinner: {
|
|
1965
|
-
description: "Loading indicator for async operations. Use with an accessible label for screen readers.",
|
|
1966
|
-
role: "loading-indicator",
|
|
1967
|
-
hierarchyLevel: "utility",
|
|
1968
|
-
interactionModel: "asynchronous",
|
|
1969
|
-
layoutImpact: "inline",
|
|
1970
|
-
destructive: false,
|
|
1971
|
-
sizes: ["xs", "sm", "md", "lg", "xl"]
|
|
1972
|
-
}
|
|
1973
|
-
};
|
|
1974
|
-
function buildComponentMetadata(componentName, rule, config) {
|
|
1975
|
-
const defaults = COMPONENT_DEFAULTS[componentName.toLowerCase()] ?? {};
|
|
1976
|
-
const variants = rule.allowedVariants ?? ["default"];
|
|
1977
|
-
const requiredProps = rule.requiredProps ?? [];
|
|
1978
|
-
const tokens = {};
|
|
1979
|
-
for (const [tokenName] of Object.entries(rule.tokens ?? {})) {
|
|
1980
|
-
tokens[tokenName] = `--${tokenName}`;
|
|
1981
|
-
}
|
|
1982
|
-
const meta = {
|
|
1983
|
-
component: pascalCase(componentName),
|
|
1984
|
-
version: config.meta.version,
|
|
1985
|
-
description: defaults.description ?? `A ${componentName} component.`,
|
|
1986
|
-
role: defaults.role ?? "ui-element",
|
|
1987
|
-
hierarchyLevel: defaults.hierarchyLevel ?? "utility",
|
|
1988
|
-
interactionModel: defaults.interactionModel ?? "none",
|
|
1989
|
-
layoutImpact: defaults.layoutImpact ?? "inline",
|
|
1990
|
-
destructive: componentName.toLowerCase().includes("delete") || variants.includes("danger"),
|
|
1991
|
-
allowedVariants: variants,
|
|
1992
|
-
defaultVariant: variants[0] ?? "default",
|
|
1993
|
-
requiredProps,
|
|
1994
|
-
optionalProps: buildOptionalProps(componentName, defaults),
|
|
1995
|
-
tokens,
|
|
1996
|
-
accessibilityContract: {
|
|
1997
|
-
keyboard: rule.a11y?.keyboard ?? true,
|
|
1998
|
-
focusRing: rule.a11y?.focusRing ?? true,
|
|
1999
|
-
ariaLabel: rule.a11y?.ariaLabel ?? "optional",
|
|
2000
|
-
...rule.a11y?.role ? { role: rule.a11y.role } : {}
|
|
2001
|
-
},
|
|
2002
|
-
governanceRules: {
|
|
2003
|
-
...rule.maxWidth ? { maxWidth: rule.maxWidth } : {},
|
|
2004
|
-
...rule.allowedRadius ? { allowedRadius: rule.allowedRadius } : {},
|
|
2005
|
-
...rule.allowedShadows ? { allowedShadows: rule.allowedShadows } : {},
|
|
2006
|
-
...rule.colorPalette ? { colorPalette: rule.colorPalette } : {}
|
|
2007
|
-
}
|
|
2008
|
-
};
|
|
2009
|
-
if (defaults.sizes) {
|
|
2010
|
-
meta.sizes = defaults.sizes;
|
|
2011
|
-
}
|
|
2012
|
-
return meta;
|
|
2013
|
-
}
|
|
2014
|
-
function buildOptionalProps(componentName, _defaults) {
|
|
2015
|
-
const common = ["className", "style", "id", "data-testid"];
|
|
2016
|
-
const byComponent = {
|
|
2017
|
-
button: [
|
|
2018
|
-
"size",
|
|
2019
|
-
"loading",
|
|
2020
|
-
"disabled",
|
|
2021
|
-
"fullWidth",
|
|
2022
|
-
"iconLeft",
|
|
2023
|
-
"iconRight",
|
|
2024
|
-
"onClick"
|
|
2025
|
-
],
|
|
2026
|
-
input: [
|
|
2027
|
-
"size",
|
|
2028
|
-
"disabled",
|
|
2029
|
-
"label",
|
|
2030
|
-
"helperText",
|
|
2031
|
-
"errorMessage",
|
|
2032
|
-
"placeholder",
|
|
2033
|
-
"startAdornment",
|
|
2034
|
-
"endAdornment",
|
|
2035
|
-
"onChange"
|
|
2036
|
-
],
|
|
2037
|
-
card: ["maxWidth", "noPadding", "onClick"],
|
|
2038
|
-
badge: ["size", "dot"],
|
|
2039
|
-
checkbox: ["size", "disabled", "label", "helperText", "indeterminate", "checked", "onChange"],
|
|
2040
|
-
radio: ["size", "disabled", "label", "value", "onChange"],
|
|
2041
|
-
select: ["size", "disabled", "label", "helperText", "errorMessage", "placeholder", "options", "fullWidth", "onChange"],
|
|
2042
|
-
toast: ["variant", "title", "dismissible", "duration", "onDismiss"],
|
|
2043
|
-
spinner: ["size", "variant", "label"]
|
|
2044
|
-
};
|
|
2045
|
-
return [...byComponent[componentName.toLowerCase()] ?? [], ...common];
|
|
2046
|
-
}
|
|
2047
|
-
function pascalCase(str) {
|
|
2048
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2049
|
-
}
|
|
2050
|
-
function buildIndexMetadata(config, componentNames, tokenCount) {
|
|
2051
|
-
return {
|
|
2052
|
-
name: config.meta.name,
|
|
2053
|
-
version: config.meta.version,
|
|
2054
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2055
|
-
components: componentNames.map(pascalCase),
|
|
2056
|
-
tokenCount,
|
|
2057
|
-
themes: Object.keys(config.themes ?? {})
|
|
2058
|
-
};
|
|
2059
|
-
}
|
|
2060
|
-
function generateMetadata(config, rules, tokenCount) {
|
|
2061
|
-
const files = [];
|
|
2062
|
-
const componentNames = Object.keys(rules);
|
|
2063
|
-
for (const [componentName, rule] of Object.entries(rules)) {
|
|
2064
|
-
const metadata = buildComponentMetadata(componentName, rule, config);
|
|
2065
|
-
files.push({
|
|
2066
|
-
filename: `${componentName}.json`,
|
|
2067
|
-
content: JSON.stringify(metadata, null, 2)
|
|
2068
|
-
});
|
|
2069
|
-
}
|
|
2070
|
-
const index = buildIndexMetadata(config, componentNames, tokenCount);
|
|
2071
|
-
files.push({
|
|
2072
|
-
filename: "index.json",
|
|
2073
|
-
content: JSON.stringify(index, null, 2)
|
|
2074
|
-
});
|
|
2075
|
-
return files;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
1901
|
// src/adapters/react/components/button.ts
|
|
2079
1902
|
function generateButton(config, rule) {
|
|
2080
1903
|
const variants = rule?.allowedVariants ?? [
|
|
@@ -2864,9 +2687,17 @@ export interface CheckboxProps
|
|
|
2864
2687
|
// \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
|
|
2865
2688
|
|
|
2866
2689
|
const SIZE_BOX: Record<"sm" | "md" | "lg", string> = {
|
|
2867
|
-
sm: "14px",
|
|
2868
|
-
md: "16px",
|
|
2869
|
-
lg: "20px",
|
|
2690
|
+
sm: "var(--control-size-sm, 14px)",
|
|
2691
|
+
md: "var(--control-size-md, 16px)",
|
|
2692
|
+
lg: "var(--control-size-lg, 20px)",
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
// SVG check/dash icon fits inside the box with a 6px inset, via calc() so it
|
|
2696
|
+
// tracks the CSS var when density changes at runtime.
|
|
2697
|
+
const SVG_SIZE: Record<"sm" | "md" | "lg", string> = {
|
|
2698
|
+
sm: "calc(var(--control-size-sm, 14px) - 6px)",
|
|
2699
|
+
md: "calc(var(--control-size-md, 16px) - 6px)",
|
|
2700
|
+
lg: "calc(var(--control-size-lg, 20px) - 6px)",
|
|
2870
2701
|
};
|
|
2871
2702
|
|
|
2872
2703
|
const SIZE_FONT: Record<"sm" | "md" | "lg", string> = {
|
|
@@ -3010,8 +2841,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
3010
2841
|
<span style={boxStyle} aria-hidden="true">
|
|
3011
2842
|
{isActive && (
|
|
3012
2843
|
<svg
|
|
3013
|
-
|
|
3014
|
-
height={parseInt(boxSize) - 6}
|
|
2844
|
+
style={{ width: SVG_SIZE[size], height: SVG_SIZE[size] }}
|
|
3015
2845
|
viewBox="0 0 10 10"
|
|
3016
2846
|
fill="none"
|
|
3017
2847
|
stroke="#fff"
|
|
@@ -3113,9 +2943,9 @@ export interface RadioGroupProps {
|
|
|
3113
2943
|
// \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
|
|
3114
2944
|
|
|
3115
2945
|
const SIZE_BOX: Record<"sm" | "md" | "lg", string> = {
|
|
3116
|
-
sm: "14px",
|
|
3117
|
-
md: "16px",
|
|
3118
|
-
lg: "20px",
|
|
2946
|
+
sm: "var(--control-size-sm, 14px)",
|
|
2947
|
+
md: "var(--control-size-md, 16px)",
|
|
2948
|
+
lg: "var(--control-size-lg, 20px)",
|
|
3119
2949
|
};
|
|
3120
2950
|
|
|
3121
2951
|
const SIZE_FONT: Record<"sm" | "md" | "lg", string> = {
|
|
@@ -4086,62 +3916,7 @@ function generateThemeProvider(config) {
|
|
|
4086
3916
|
const themeNames = Object.keys(config.themes ?? { light: {}, dark: {} });
|
|
4087
3917
|
const defaultTheme = themeNames.includes("light") ? "light" : themeNames[0] ?? "light";
|
|
4088
3918
|
const themeType = themeNames.map((t) => `"${t}"`).join(" | ");
|
|
4089
|
-
const isPro = isProUnlocked();
|
|
4090
3919
|
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>` : "";
|
|
4145
3920
|
return `/**
|
|
4146
3921
|
* ThemeProvider \u2014 ${config.meta.name}
|
|
4147
3922
|
*
|
|
@@ -4153,27 +3928,39 @@ export function useDensity(): DensityContextValue {
|
|
|
4153
3928
|
* import "@${config.meta.name}/tokens/light.css"; // or dark.css
|
|
4154
3929
|
* import { ThemeProvider } from "@${config.meta.name}";
|
|
4155
3930
|
*
|
|
4156
|
-
* <ThemeProvider theme="light"
|
|
3931
|
+
* <ThemeProvider theme="light" density="${defaultDensity}">
|
|
4157
3932
|
* <App />
|
|
4158
3933
|
* </ThemeProvider>
|
|
4159
3934
|
*/
|
|
4160
3935
|
|
|
4161
|
-
import React from "react"
|
|
3936
|
+
import React from "react";
|
|
3937
|
+
import "../tokens/density.css";
|
|
4162
3938
|
|
|
4163
3939
|
// \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
|
|
4164
3940
|
|
|
4165
3941
|
export type ThemeName = ${themeType};
|
|
4166
|
-
|
|
3942
|
+
|
|
3943
|
+
export type DensityName = "compact" | "comfortable" | "spacious";
|
|
3944
|
+
|
|
4167
3945
|
export interface ThemeContextValue {
|
|
4168
3946
|
theme: ThemeName;
|
|
4169
3947
|
setTheme: (theme: ThemeName) => void;
|
|
4170
3948
|
}
|
|
4171
|
-
|
|
3949
|
+
|
|
3950
|
+
export interface DensityContextValue {
|
|
3951
|
+
density: DensityName;
|
|
3952
|
+
setDensity: (density: DensityName) => void;
|
|
3953
|
+
}
|
|
3954
|
+
|
|
4172
3955
|
export interface ThemeProviderProps {
|
|
4173
3956
|
/** Initial theme. Defaults to "${defaultTheme}". */
|
|
4174
3957
|
theme?: ThemeName;
|
|
4175
3958
|
/** Called when setTheme is invoked \u2014 use to persist theme preference. */
|
|
4176
|
-
onThemeChange?: (theme: ThemeName) => void
|
|
3959
|
+
onThemeChange?: (theme: ThemeName) => void;
|
|
3960
|
+
/** Component density. Requires density.css to be imported. Defaults to "${defaultDensity}". */
|
|
3961
|
+
density?: DensityName;
|
|
3962
|
+
/** Called when setDensity is invoked. */
|
|
3963
|
+
onDensityChange?: (density: DensityName) => void;
|
|
4177
3964
|
children: React.ReactNode;
|
|
4178
3965
|
}
|
|
4179
3966
|
|
|
@@ -4191,12 +3978,27 @@ export const ThemeContext = React.createContext<ThemeContextValue>({
|
|
|
4191
3978
|
export function useTheme(): ThemeContextValue {
|
|
4192
3979
|
return React.useContext(ThemeContext);
|
|
4193
3980
|
}
|
|
4194
|
-
|
|
3981
|
+
|
|
3982
|
+
export const DensityContext = React.createContext<DensityContextValue>({
|
|
3983
|
+
density: "${defaultDensity}",
|
|
3984
|
+
setDensity: () => undefined,
|
|
3985
|
+
});
|
|
3986
|
+
|
|
3987
|
+
/**
|
|
3988
|
+
* Hook to read and change the current density.
|
|
3989
|
+
* Must be used inside a <ThemeProvider>.
|
|
3990
|
+
*/
|
|
3991
|
+
export function useDensity(): DensityContextValue {
|
|
3992
|
+
return React.useContext(DensityContext);
|
|
3993
|
+
}
|
|
3994
|
+
|
|
4195
3995
|
// \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
|
|
4196
3996
|
|
|
4197
3997
|
export function ThemeProvider({
|
|
4198
3998
|
theme: initialTheme = "${defaultTheme}",
|
|
4199
|
-
onThemeChange
|
|
3999
|
+
onThemeChange,
|
|
4000
|
+
density: initialDensity = "${defaultDensity}",
|
|
4001
|
+
onDensityChange,
|
|
4200
4002
|
children,
|
|
4201
4003
|
}: ThemeProviderProps) {
|
|
4202
4004
|
const [theme, setThemeState] = React.useState<ThemeName>(initialTheme);
|
|
@@ -4212,13 +4014,29 @@ export function ThemeProvider({
|
|
|
4212
4014
|
},
|
|
4213
4015
|
[onThemeChange],
|
|
4214
4016
|
);
|
|
4215
|
-
|
|
4216
|
-
|
|
4017
|
+
|
|
4018
|
+
const [density, setDensityState] = React.useState<DensityName>(initialDensity);
|
|
4019
|
+
|
|
4020
|
+
React.useEffect(() => {
|
|
4021
|
+
setDensityState(initialDensity);
|
|
4022
|
+
}, [initialDensity]);
|
|
4023
|
+
|
|
4024
|
+
const setDensity = React.useCallback(
|
|
4025
|
+
(next: DensityName) => {
|
|
4026
|
+
setDensityState(next);
|
|
4027
|
+
onDensityChange?.(next);
|
|
4028
|
+
},
|
|
4029
|
+
[onDensityChange],
|
|
4030
|
+
);
|
|
4031
|
+
|
|
4032
|
+
return (
|
|
4033
|
+
<DensityContext.Provider value={{ density, setDensity }}>
|
|
4217
4034
|
<ThemeContext.Provider value={{ theme, setTheme }}>
|
|
4218
|
-
<div data-theme={theme}
|
|
4035
|
+
<div data-theme={theme} data-density={density} style={{ display: "contents" }}>
|
|
4219
4036
|
{children}
|
|
4220
4037
|
</div>
|
|
4221
|
-
</ThemeContext.Provider
|
|
4038
|
+
</ThemeContext.Provider>
|
|
4039
|
+
</DensityContext.Provider>
|
|
4222
4040
|
);
|
|
4223
4041
|
}
|
|
4224
4042
|
`;
|
|
@@ -4235,9 +4053,9 @@ function generateComponentIndex(config, componentNames) {
|
|
|
4235
4053
|
""
|
|
4236
4054
|
];
|
|
4237
4055
|
for (const name of componentNames) {
|
|
4238
|
-
lines.push(`export * from "
|
|
4056
|
+
lines.push(`export * from "./components/${name}/${name}";`);
|
|
4239
4057
|
}
|
|
4240
|
-
lines.push(`export * from "./ThemeProvider";`);
|
|
4058
|
+
lines.push(`export * from "./components/ThemeProvider/ThemeProvider";`);
|
|
4241
4059
|
lines.push("");
|
|
4242
4060
|
return lines.join("\n");
|
|
4243
4061
|
}
|
|
@@ -4875,13 +4693,11 @@ async function runGenerate(cwd, options) {
|
|
|
4875
4693
|
await writeFile(path3.join(tokensDir, filename), content);
|
|
4876
4694
|
logger.dim(` \u2192 tokens/${filename}`);
|
|
4877
4695
|
}
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
logger.dim(` \u2192 tokens/density.css`);
|
|
4884
|
-
}
|
|
4696
|
+
await writeFile(
|
|
4697
|
+
path3.join(tokensDir, "density.css"),
|
|
4698
|
+
emitDensityCss(config)
|
|
4699
|
+
);
|
|
4700
|
+
logger.dim(` \u2192 tokens/density.css`);
|
|
4885
4701
|
const tokenFiles = reactAdapter.generateTokenFiles(config, resolution);
|
|
4886
4702
|
for (const { filename, content } of tokenFiles) {
|
|
4887
4703
|
await writeFile(path3.join(tokensDir, filename), content);
|
|
@@ -4891,21 +4707,53 @@ async function runGenerate(cwd, options) {
|
|
|
4891
4707
|
}
|
|
4892
4708
|
if (!only || only === "components") {
|
|
4893
4709
|
logger.step("Generating React components...");
|
|
4894
|
-
const
|
|
4895
|
-
await ensureDir(
|
|
4710
|
+
const componentsDir = path3.join(outRoot, "components");
|
|
4711
|
+
await ensureDir(componentsDir);
|
|
4896
4712
|
const generatedNames = [];
|
|
4713
|
+
const generatedComponentJsons = [];
|
|
4714
|
+
const flatTokens = {};
|
|
4715
|
+
for (const [k, v] of Object.entries(resolution.tokens)) {
|
|
4716
|
+
flatTokens[`--${k.replace(/^(global|semantic|component)\./, "")}`] = v;
|
|
4717
|
+
}
|
|
4718
|
+
const lightOverrides = config.themes?.["light"] ?? {};
|
|
4719
|
+
const darkOverrides = config.themes?.["dark"] ?? {};
|
|
4720
|
+
const lightCssVars = {
|
|
4721
|
+
...flatTokens,
|
|
4722
|
+
...Object.fromEntries(Object.entries(lightOverrides).map(([k, v]) => [`--${k}`, String(v)]))
|
|
4723
|
+
};
|
|
4724
|
+
const darkCssVars = {
|
|
4725
|
+
...flatTokens,
|
|
4726
|
+
...Object.fromEntries(Object.entries(darkOverrides).map(([k, v]) => [`--${k}`, String(v)]))
|
|
4727
|
+
};
|
|
4728
|
+
const resolvedCssVars = { light: lightCssVars, dark: darkCssVars };
|
|
4729
|
+
const { generateComponentJson } = await import("../generateComponentJson-XBEUWCW6.js");
|
|
4730
|
+
const { generateComponentMetadata } = await import("../generateComponentMetadata-2L5VNERD.js");
|
|
4897
4731
|
for (const componentName of REACT_COMPONENTS) {
|
|
4732
|
+
const pascalName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
|
|
4898
4733
|
try {
|
|
4899
4734
|
const { filename, content } = reactAdapter.generateComponent(
|
|
4900
4735
|
componentName,
|
|
4901
4736
|
config,
|
|
4902
4737
|
rules[componentName]
|
|
4903
4738
|
);
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4739
|
+
const componentSubDir = path3.join(componentsDir, pascalName);
|
|
4740
|
+
await ensureDir(componentSubDir);
|
|
4741
|
+
await writeFile(path3.join(componentSubDir, filename), content);
|
|
4742
|
+
logger.dim(` \u2192 components/${pascalName}/${filename}`);
|
|
4743
|
+
generatedNames.push(pascalName);
|
|
4744
|
+
const componentJson = generateComponentJson(pascalName, resolvedCssVars);
|
|
4745
|
+
generatedComponentJsons.push(componentJson);
|
|
4746
|
+
await writeFile(
|
|
4747
|
+
path3.join(componentSubDir, `${pascalName}.json`),
|
|
4748
|
+
JSON.stringify(componentJson, null, 2)
|
|
4749
|
+
);
|
|
4750
|
+
logger.dim(` \u2192 components/${pascalName}/${pascalName}.json`);
|
|
4751
|
+
const metadata = generateComponentMetadata(pascalName);
|
|
4752
|
+
await writeFile(
|
|
4753
|
+
path3.join(componentSubDir, `${pascalName}.metadata.json`),
|
|
4754
|
+
JSON.stringify(metadata, null, 2)
|
|
4908
4755
|
);
|
|
4756
|
+
logger.dim(` \u2192 components/${pascalName}/${pascalName}.metadata.json`);
|
|
4909
4757
|
} catch (err) {
|
|
4910
4758
|
logger.warn(
|
|
4911
4759
|
`[dsforge] Could not generate ${componentName} \u2014 ${err.message}`
|
|
@@ -4913,23 +4761,31 @@ async function runGenerate(cwd, options) {
|
|
|
4913
4761
|
}
|
|
4914
4762
|
}
|
|
4915
4763
|
const { filename: tpFile, content: tpContent } = reactAdapter.generateThemeProvider(config);
|
|
4916
|
-
|
|
4917
|
-
|
|
4764
|
+
const tpDir = path3.join(componentsDir, "ThemeProvider");
|
|
4765
|
+
await ensureDir(tpDir);
|
|
4766
|
+
await writeFile(path3.join(tpDir, tpFile), tpContent);
|
|
4767
|
+
logger.dim(` \u2192 components/ThemeProvider/${tpFile}`);
|
|
4768
|
+
try {
|
|
4769
|
+
const tpJson = generateComponentJson("ThemeProvider", resolvedCssVars);
|
|
4770
|
+
generatedComponentJsons.push(tpJson);
|
|
4771
|
+
await writeFile(
|
|
4772
|
+
path3.join(tpDir, "ThemeProvider.json"),
|
|
4773
|
+
JSON.stringify(tpJson, null, 2)
|
|
4774
|
+
);
|
|
4775
|
+
logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.json`);
|
|
4776
|
+
const tpMeta = generateComponentMetadata("ThemeProvider");
|
|
4777
|
+
await writeFile(
|
|
4778
|
+
path3.join(tpDir, "ThemeProvider.metadata.json"),
|
|
4779
|
+
JSON.stringify(tpMeta, null, 2)
|
|
4780
|
+
);
|
|
4781
|
+
logger.dim(` \u2192 components/ThemeProvider/ThemeProvider.metadata.json`);
|
|
4782
|
+
} catch {
|
|
4783
|
+
}
|
|
4918
4784
|
const { filename: idxFile, content: idxContent } = reactAdapter.generateComponentIndex(config, generatedNames);
|
|
4919
|
-
await writeFile(path3.join(
|
|
4920
|
-
logger.dim(` \u2192
|
|
4785
|
+
await writeFile(path3.join(outRoot, idxFile), idxContent);
|
|
4786
|
+
logger.dim(` \u2192 ${idxFile}`);
|
|
4921
4787
|
logger.success(`${generatedNames.length} components generated`);
|
|
4922
|
-
|
|
4923
|
-
if (isProUnlocked() && (!only || only === "metadata")) {
|
|
4924
|
-
logger.step("Writing AI metadata...");
|
|
4925
|
-
const metaDir = path3.join(outRoot, "metadata");
|
|
4926
|
-
await ensureDir(metaDir);
|
|
4927
|
-
const metaFiles = generateMetadata(config, fullRules, tokenCount);
|
|
4928
|
-
for (const { filename, content } of metaFiles) {
|
|
4929
|
-
await writeFile(path3.join(metaDir, filename), content);
|
|
4930
|
-
logger.dim(` \u2192 metadata/${filename}`);
|
|
4931
|
-
}
|
|
4932
|
-
logger.success(`Metadata written (${metaFiles.length} files)`);
|
|
4788
|
+
globalThis["__dsforgGeneratedJsons"] = generatedComponentJsons;
|
|
4933
4789
|
}
|
|
4934
4790
|
if (!only) {
|
|
4935
4791
|
logger.step("Writing package files...");
|
|
@@ -4940,28 +4796,80 @@ async function runGenerate(cwd, options) {
|
|
|
4940
4796
|
const tsConfig = generateTsConfig();
|
|
4941
4797
|
await writeFile(path3.join(outRoot, "tsconfig.json"), tsConfig);
|
|
4942
4798
|
logger.dim(` \u2192 tsconfig.json`);
|
|
4943
|
-
const { generateReadme } = await import("../emitter-
|
|
4799
|
+
const { generateReadme } = await import("../emitter-IC77G4QF.js");
|
|
4944
4800
|
await writeFile(
|
|
4945
4801
|
path3.join(outRoot, "README.md"),
|
|
4946
4802
|
generateReadme(config, componentNames)
|
|
4947
4803
|
);
|
|
4948
4804
|
logger.dim(` \u2192 README.md`);
|
|
4949
4805
|
const changelogPath = path3.join(outRoot, "CHANGELOG.md");
|
|
4950
|
-
const
|
|
4951
|
-
const fsE2 =
|
|
4806
|
+
const fsExtra = await import("fs-extra");
|
|
4807
|
+
const fsE2 = fsExtra.default ?? fsExtra;
|
|
4952
4808
|
if (!await fsE2.pathExists(changelogPath)) {
|
|
4953
4809
|
await writeFile(changelogPath, generateChangelog(config));
|
|
4954
4810
|
logger.dim(` \u2192 CHANGELOG.md (seeded)`);
|
|
4955
4811
|
}
|
|
4956
4812
|
logger.success(`Package files written`);
|
|
4957
4813
|
}
|
|
4814
|
+
if (!only || only === "components") {
|
|
4815
|
+
logger.step("Writing registry + AI outputs...");
|
|
4816
|
+
const generatedJsons = globalThis["__dsforgGeneratedJsons"] ?? [];
|
|
4817
|
+
const { generateRegistry } = await import("../generateRegistry-3MEZDJAJ.js");
|
|
4818
|
+
const {
|
|
4819
|
+
generateSystemPrompt,
|
|
4820
|
+
generateComponentsJson,
|
|
4821
|
+
generateCursorContext,
|
|
4822
|
+
generateCopilotInstructions
|
|
4823
|
+
} = await import("../generateAiFolder-3OOFWBH7.js");
|
|
4824
|
+
const systemName = config.meta.name;
|
|
4825
|
+
const version = config.meta.version;
|
|
4826
|
+
const registry = generateRegistry(systemName, version, generatedJsons);
|
|
4827
|
+
await writeFile(path3.join(outRoot, "registry.json"), JSON.stringify(registry, null, 2));
|
|
4828
|
+
logger.dim(` \u2192 registry.json`);
|
|
4829
|
+
const { COMPONENT_METADATA_DEFINITIONS } = await import("../componentDefinitions-5LFCNFQY.js");
|
|
4830
|
+
const metadataList = generatedJsons.map((c) => COMPONENT_METADATA_DEFINITIONS[c.name]).filter((m) => Boolean(m));
|
|
4831
|
+
const flatTokensForAi = {};
|
|
4832
|
+
for (const [k, v] of Object.entries(resolution.tokens)) {
|
|
4833
|
+
const cssVar2 = `--${k.replace(/^(global|semantic|component)\./, "")}`;
|
|
4834
|
+
flatTokensForAi[cssVar2] = {
|
|
4835
|
+
light: config.themes?.["light"]?.[cssVar2.slice(2)] ?? v,
|
|
4836
|
+
dark: config.themes?.["dark"]?.[cssVar2.slice(2)] ?? v
|
|
4837
|
+
};
|
|
4838
|
+
}
|
|
4839
|
+
const aiDir = path3.join(outRoot, "ai");
|
|
4840
|
+
await ensureDir(aiDir);
|
|
4841
|
+
const cursorDir = path3.join(aiDir, ".cursor");
|
|
4842
|
+
await ensureDir(cursorDir);
|
|
4843
|
+
const componentNames = generatedJsons.map((c) => c.name);
|
|
4844
|
+
await writeFile(
|
|
4845
|
+
path3.join(aiDir, "system-prompt.md"),
|
|
4846
|
+
generateSystemPrompt(systemName, flatTokensForAi, componentNames)
|
|
4847
|
+
);
|
|
4848
|
+
logger.dim(` \u2192 ai/system-prompt.md`);
|
|
4849
|
+
await writeFile(
|
|
4850
|
+
path3.join(aiDir, "components.json"),
|
|
4851
|
+
generateComponentsJson(systemName, generatedJsons, metadataList)
|
|
4852
|
+
);
|
|
4853
|
+
logger.dim(` \u2192 ai/components.json`);
|
|
4854
|
+
await writeFile(
|
|
4855
|
+
path3.join(cursorDir, "context.md"),
|
|
4856
|
+
generateCursorContext(systemName)
|
|
4857
|
+
);
|
|
4858
|
+
logger.dim(` \u2192 ai/.cursor/context.md`);
|
|
4859
|
+
await writeFile(
|
|
4860
|
+
path3.join(outRoot, "copilot-instructions.md"),
|
|
4861
|
+
generateCopilotInstructions(systemName)
|
|
4862
|
+
);
|
|
4863
|
+
logger.dim(` \u2192 copilot-instructions.md`);
|
|
4864
|
+
logger.success(`Registry + AI outputs written`);
|
|
4865
|
+
}
|
|
4958
4866
|
logger.step("Generating showcase...");
|
|
4959
|
-
const { generateShowcase } = await import("../html-
|
|
4867
|
+
const { generateShowcase } = await import("../html-4DD6GOHE.js");
|
|
4960
4868
|
const showcaseHtml = generateShowcase(config, resolution);
|
|
4961
4869
|
await writeFile(path3.join(outRoot, "showcase.html"), showcaseHtml);
|
|
4962
4870
|
logger.dim(` \u2192 showcase.html`);
|
|
4963
|
-
const
|
|
4964
|
-
const fsE =
|
|
4871
|
+
const fsExtraShowcase = await import("fs-extra");
|
|
4872
|
+
const fsE = fsExtraShowcase.default ?? fsExtraShowcase;
|
|
4965
4873
|
const faviconSrc = path3.join(cwd, "assets", "favicon.svg");
|
|
4966
4874
|
const faviconDest = path3.join(outRoot, "assets", "favicon.svg");
|
|
4967
4875
|
if (await fsE.pathExists(faviconSrc)) {
|
|
@@ -5343,7 +5251,7 @@ async function runMenu() {
|
|
|
5343
5251
|
// package.json
|
|
5344
5252
|
var package_default = {
|
|
5345
5253
|
name: "@nghitrum/dsforge",
|
|
5346
|
-
version: "0.
|
|
5254
|
+
version: "0.2.0",
|
|
5347
5255
|
description: "AI-native design system generator \u2014 tokens \u2192 components \u2192 docs \u2192 npm",
|
|
5348
5256
|
keywords: [
|
|
5349
5257
|
"design-system",
|