create-forte-ui 1.0.0-alpha.4

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/theme.js ADDED
@@ -0,0 +1,124 @@
1
+ /* The theme the questionnaire collects, and everything that serialises it.
2
+ *
3
+ * The record mirrors the Theme Studio's `ThemeConfig` — same keys, same value
4
+ * sets — so a studio-exported command line maps onto the CLI's flags with no
5
+ * translation. It differs in one deliberate way: the colour fields are
6
+ * nullable. The studio always shows a concrete colour because its controls
7
+ * need something to display; the CLI's contract is "only serialise
8
+ * deviations", so an unanswered prompt stays `null` and writes NOTHING. A
9
+ * scaffolded default would be a frozen default — an app that restates
10
+ * the shipped seed stops following it when the library tunes it.
11
+ */
12
+ import { SANS_FONTS, MONO_FONTS, findFont, SANS_FALLBACK, MONO_FALLBACK } from "./fonts.js";
13
+ import { hexToOklch, bestOnColor } from "./color.js";
14
+ export const RADIUS = ["none", "default", "soft", "pill"];
15
+ export const DENSITY = ["compact", "default", "spacious"];
16
+ export const MOTION = ["full", "default", "reduce"];
17
+ export const DEFAULT_ANSWERS = {
18
+ seed: null,
19
+ secondary: null,
20
+ tint: 1,
21
+ radius: "default",
22
+ density: "default",
23
+ motion: "default",
24
+ fontSans: "System",
25
+ fontMono: "System",
26
+ };
27
+ /** The declarations for the `:root` block, one string per line, WITHOUT the
28
+ * surrounding braces. Empty array = every answer was a default = no block. */
29
+ export function rootDeclarations(a, fontMode) {
30
+ const lines = [];
31
+ if (a.seed)
32
+ lines.push(`--forte-accent-seed: ${a.seed};`);
33
+ if (a.secondary)
34
+ lines.push(`--forte-secondary-seed: ${a.secondary};`);
35
+ if (a.tint !== 1)
36
+ lines.push(`--forte-neutral-tint: ${a.tint};`);
37
+ const sans = findFont(SANS_FONTS, a.fontSans);
38
+ const mono = findFont(MONO_FONTS, a.fontMono);
39
+ if (fontMode === "import") {
40
+ if (sans.stack)
41
+ lines.push(`--forte-font-sans: ${sans.stack};`);
42
+ if (mono.stack)
43
+ lines.push(`--forte-font-mono: ${mono.stack};`);
44
+ }
45
+ else {
46
+ if (sans.stack)
47
+ lines.push(`--forte-font-sans: var(--font-sans), ${SANS_FALLBACK};`);
48
+ if (mono.stack)
49
+ lines.push(`--forte-font-mono: var(--font-mono), ${MONO_FALLBACK};`);
50
+ }
51
+ /* Same emission as the studio's copied CSS: the on-colour is measured here
52
+ * rather than left to the CSS fallback's fitted lightness threshold, so it
53
+ * is exact in every browser — including those without contrast-color(). */
54
+ const on = a.seed ? bestOnColor(hexToOklch(a.seed)) : null;
55
+ const onSec = a.secondary ? bestOnColor(hexToOklch(a.secondary)) : null;
56
+ if (on || onSec) {
57
+ lines.push("");
58
+ lines.push("/* Measured rather than derived, so it is exact in every browser. */");
59
+ if (on)
60
+ lines.push(`--forte-color-on-primary: ${on.color};`);
61
+ if (onSec)
62
+ lines.push(`--forte-color-on-secondary: ${onSec.color};`);
63
+ }
64
+ return lines;
65
+ }
66
+ /** A complete `:root { ... }` block, or "" when nothing deviates. */
67
+ export function rootBlock(a, fontMode) {
68
+ const decls = rootDeclarations(a, fontMode);
69
+ if (decls.length === 0)
70
+ return "";
71
+ const body = decls.map((l) => (l === "" ? "" : ` ${l}`)).join("\n");
72
+ return `:root {\n${body}\n}\n`;
73
+ }
74
+ /** `@import url(...)` lines for the chosen Google fonts (Vite path). Font
75
+ * imports carry no layer statements, so they are safe ahead of the bridge. */
76
+ export function fontImports(a) {
77
+ return [findFont(SANS_FONTS, a.fontSans), findFont(MONO_FONTS, a.fontMono)]
78
+ .filter((f) => f.css)
79
+ .map((f) => `@import url("${f.css}");`);
80
+ }
81
+ /** ` data-forte-radius="pill" ...` — leading space included, "" when all
82
+ * defaults. Default modes stay UNSET so the app keeps following the OS
83
+ * (motion) and the library's own defaults. */
84
+ export function htmlAttrs(a) {
85
+ const attrs = [
86
+ a.radius !== "default" && `data-forte-radius="${a.radius}"`,
87
+ a.density !== "default" && `data-forte-density="${a.density}"`,
88
+ a.motion !== "default" && `data-forte-motion="${a.motion}"`,
89
+ ].filter(Boolean);
90
+ return attrs.length ? " " + attrs.join(" ") : "";
91
+ }
92
+ /** The axes string in the catalogue is the truth about what a family can
93
+ * serve: `400..700` is a variable font (no `weight` needed), `400;500;700`
94
+ * is static and next/font requires the explicit list. */
95
+ function weightsFromAxes(css) {
96
+ const m = /wght@([^&]+)/.exec(css);
97
+ if (!m || m[1].includes(".."))
98
+ return null;
99
+ return m[1].split(";");
100
+ }
101
+ export function nextFontSetup(a) {
102
+ const picks = [
103
+ { font: findFont(SANS_FONTS, a.fontSans), variable: "--font-sans", ident: "fontSans" },
104
+ { font: findFont(MONO_FONTS, a.fontMono), variable: "--font-mono", ident: "fontMono" },
105
+ ].filter((p) => p.font.css);
106
+ if (picks.length === 0)
107
+ return { importLine: "", consts: "", htmlClassAttr: "" };
108
+ const names = picks.map((p) => p.font.name.replaceAll(" ", "_"));
109
+ const consts = picks
110
+ .map((p, i) => {
111
+ const weights = weightsFromAxes(p.font.css);
112
+ const weightOpt = weights ? `, weight: [${weights.map((w) => `"${w}"`).join(", ")}]` : "";
113
+ return `const ${p.ident} = ${names[i]}({ subsets: ["latin"], variable: "${p.variable}"${weightOpt} });`;
114
+ })
115
+ .join("\n");
116
+ const classExpr = picks.length === 1
117
+ ? `{${picks[0].ident}.variable}`
118
+ : `{\`\${fontSans.variable} \${fontMono.variable}\`}`;
119
+ return {
120
+ importLine: `import { ${names.join(", ")} } from "next/font/google";`,
121
+ consts,
122
+ htmlClassAttr: ` className=${classExpr}`,
123
+ };
124
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "create-forte-ui",
3
+ "version": "1.0.0-alpha.4",
4
+ "description": "Scaffold a new app wired up with forte-ui — Vite or Next.js, with or without Tailwind, themed from your answers.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-forte-ui": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "exports": {
14
+ "./fonts": {
15
+ "types": "./dist/fonts.d.ts",
16
+ "import": "./dist/fonts.js"
17
+ },
18
+ "./color": {
19
+ "types": "./dist/color.d.ts",
20
+ "import": "./dist/color.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "dev": "tsc -p tsconfig.json --watch",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "smoke": "node scripts/smoke.mjs",
32
+ "clean": "rm -rf dist .turbo"
33
+ },
34
+ "dependencies": {
35
+ "@clack/prompts": "^0.11.0",
36
+ "picocolors": "^1.1.1"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^24.0.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=20"
43
+ },
44
+ "homepage": "https://forte-ui.com"
45
+ }