create-forte-ui 1.0.0-alpha.5 → 1.0.0-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/README.md +1 -1
- package/dist/args.d.ts +1 -1
- package/dist/args.js +8 -2
- package/dist/index.js +13 -1
- package/dist/overlay.js +23 -1
- package/dist/prompts.js +13 -2
- package/dist/templates.d.ts +5 -1
- package/dist/templates.js +58 -8
- package/dist/theme.d.ts +6 -1
- package/dist/theme.js +10 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm create forte-ui@latest my-app
|
|
|
10
10
|
|
|
11
11
|
Four questions get you to a running app (name, framework, Tailwind, accent
|
|
12
12
|
color); everything else — secondary color, neutral tint, radius, density,
|
|
13
|
-
motion, fonts — hides behind one "customize further?" gate. Every prompt has a
|
|
13
|
+
motion, light/dark, fonts — hides behind one "customize further?" gate. Every prompt has a
|
|
14
14
|
flag twin, so the [Theme Studio](https://forte-ui.com/theme/) can hand you a
|
|
15
15
|
complete command line and `--yes` runs without a single question:
|
|
16
16
|
|
package/dist/args.d.ts
CHANGED
|
@@ -27,4 +27,4 @@ export type CliOptions = {
|
|
|
27
27
|
export declare class UsageError extends Error {
|
|
28
28
|
}
|
|
29
29
|
export declare function parseCliArgs(argv: string[]): CliOptions;
|
|
30
|
-
export declare const HELP = "create-forte-ui \u2014 scaffold a new app wired up with forte-ui\n\nUsage\n pnpm create forte-ui [name] [flags]\n npm create forte-ui@latest [name] -- [flags]\n\nEvery flag has a prompt twin; a passed flag suppresses its prompt. With no\nflags you get the questionnaire, with --yes you get a Next.js + Tailwind app\non the library's default theme.\n\nProject\n [name] project directory (prompted if omitted)\n -f, --framework next | vite\n --tailwind wire the Tailwind v4 bridge (default yes)\n --no-tailwind plain CSS setup\n --pm npm | pnpm | yarn | bun (default: whoever invoked us)\n --library version spec for @forte-ui/react \u2014 exact\n (\"1.0.0-alpha.4\"), a dist-tag (\"alpha\"), or a range.\n Default: latest.\n --no-install write files only; skip installing dependencies\n and the agent skill\n --no-skill skip installing the forte-ui agent skill\n (skills.sh \u2014 .agents/skills plus .claude/skills)\n -y, --yes accept the defaults for everything not passed\n\nTheme \u2014 every skipped value keeps the library default and writes NOTHING,\nso the app keeps following the library when defaults are tuned.\n --seed, --accent accent seed, hex (\"#6d43d4\")\n --secondary secondary seed, hex\n --tint neutral tint, 0 (pure grey) to 1 (default)\n --radius none | soft | pill\n --density compact | spacious\n --motion system (default) | reduce | full\n \"full\" overrides the OS reduced-motion preference\n for everyone \u2014 prefer leaving it unset.\n --font-sans
|
|
30
|
+
export declare const HELP = "create-forte-ui \u2014 scaffold a new app wired up with forte-ui\n\nUsage\n pnpm create forte-ui [name] [flags]\n npm create forte-ui@latest [name] -- [flags]\n\nEvery flag has a prompt twin; a passed flag suppresses its prompt. With no\nflags you get the questionnaire, with --yes you get a Next.js + Tailwind app\non the library's default theme.\n\nProject\n [name] project directory (prompted if omitted)\n -f, --framework next | vite\n --tailwind wire the Tailwind v4 bridge (default yes)\n --no-tailwind plain CSS setup\n --pm npm | pnpm | yarn | bun (default: whoever invoked us)\n --library version spec for @forte-ui/react \u2014 exact\n (\"1.0.0-alpha.4\"), a dist-tag (\"alpha\"), or a range.\n Default: latest.\n --no-install write files only; skip installing dependencies\n and the agent skill\n --no-skill skip installing the forte-ui agent skill\n (skills.sh \u2014 .agents/skills plus .claude/skills)\n -y, --yes accept the defaults for everything not passed\n\nTheme \u2014 every skipped value keeps the library default and writes NOTHING,\nso the app keeps following the library when defaults are tuned.\n --seed, --accent accent seed, hex (\"#6d43d4\")\n --secondary secondary seed, hex\n --tint neutral tint, 0 (pure grey) to 1 (default)\n --radius none | soft | pill\n --density compact | spacious\n --motion system (default) | reduce | full\n \"full\" overrides the OS reduced-motion preference\n for everyone \u2014 prefer leaving it unset.\n --scheme system (default) | light | dark\n \"light\" / \"dark\" pin one palette on <html> and\n leave the theme toggle and its replay script out.\n --font-sans a catalogue name (\"Inter\", \"DM Sans\", ...)\n --font-mono a catalogue name (\"JetBrains Mono\", ...)\n\nDesign the theme visually instead: https://forte-ui.com/theme\n";
|
package/dist/args.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { parseArgs } from "node:util";
|
|
12
12
|
import { SANS_FONTS, MONO_FONTS } from "./fonts.js";
|
|
13
13
|
import { hexToOklch } from "./color.js";
|
|
14
|
-
import { RADIUS, DENSITY, MOTION } from "./theme.js";
|
|
14
|
+
import { RADIUS, DENSITY, MOTION, SCHEME } from "./theme.js";
|
|
15
15
|
import { PACKAGE_MANAGERS } from "./scaffold.js";
|
|
16
16
|
export class UsageError extends Error {
|
|
17
17
|
}
|
|
@@ -51,6 +51,7 @@ export function parseCliArgs(argv) {
|
|
|
51
51
|
radius: { type: "string" },
|
|
52
52
|
density: { type: "string" },
|
|
53
53
|
motion: { type: "string" },
|
|
54
|
+
scheme: { type: "string" },
|
|
54
55
|
"font-sans": { type: "string" },
|
|
55
56
|
"font-mono": { type: "string" },
|
|
56
57
|
library: { type: "string" },
|
|
@@ -94,6 +95,8 @@ export function parseCliArgs(argv) {
|
|
|
94
95
|
answers.motion =
|
|
95
96
|
values.motion.toLowerCase() === "system" ? "default" : oneOf("motion", values.motion, MOTION);
|
|
96
97
|
}
|
|
98
|
+
if (values.scheme !== undefined)
|
|
99
|
+
answers.scheme = oneOf("scheme", values.scheme, SCHEME);
|
|
97
100
|
if (values["font-sans"] !== undefined) {
|
|
98
101
|
answers.fontSans = fontByName("font-sans", values["font-sans"], SANS_FONTS);
|
|
99
102
|
}
|
|
@@ -149,7 +152,10 @@ so the app keeps following the library when defaults are tuned.
|
|
|
149
152
|
--motion system (default) | reduce | full
|
|
150
153
|
"full" overrides the OS reduced-motion preference
|
|
151
154
|
for everyone — prefer leaving it unset.
|
|
152
|
-
--
|
|
155
|
+
--scheme system (default) | light | dark
|
|
156
|
+
"light" / "dark" pin one palette on <html> and
|
|
157
|
+
leave the theme toggle and its replay script out.
|
|
158
|
+
--font-sans a catalogue name ("Inter", "DM Sans", ...)
|
|
153
159
|
--font-mono a catalogue name ("JetBrains Mono", ...)
|
|
154
160
|
|
|
155
161
|
Design the theme visually instead: https://forte-ui.com/theme
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,19 @@ function warnAboutSeeds(plan) {
|
|
|
41
41
|
}
|
|
42
42
|
function scaffold(plan, pm) {
|
|
43
43
|
if (plan.framework === "vite") {
|
|
44
|
-
return runScaffolder(pm, "create-vite@latest", [
|
|
44
|
+
return runScaffolder(pm, "create-vite@latest", [
|
|
45
|
+
plan.name,
|
|
46
|
+
"--template",
|
|
47
|
+
"react-ts",
|
|
48
|
+
/* create-vite 9 goes interactive whenever stdin is a TTY, template
|
|
49
|
+
* or not: it asks which linter to use and then "Install and start
|
|
50
|
+
* now?". A yes to that starts the dev server INSIDE our child
|
|
51
|
+
* process, so control never comes back and the overlay is never
|
|
52
|
+
* applied — the user ends up with a stock Vite app and no forte-ui.
|
|
53
|
+
* Non-interactive takes its defaults (Oxlint, no install), and the
|
|
54
|
+
* one install happens at the end with the library included. */
|
|
55
|
+
"--no-interactive",
|
|
56
|
+
], process.cwd());
|
|
45
57
|
}
|
|
46
58
|
return runScaffolder(pm, "create-next-app@latest", [
|
|
47
59
|
plan.name,
|
package/dist/overlay.js
CHANGED
|
@@ -24,7 +24,7 @@ function applyVite({ name, dir, tailwind, answers }) {
|
|
|
24
24
|
written.push("src/index.css");
|
|
25
25
|
write(dir, "src/main.tsx", viteMainTsx(tailwind));
|
|
26
26
|
written.push("src/main.tsx");
|
|
27
|
-
write(dir, "src/App.tsx", viteAppTsx(name, tailwind));
|
|
27
|
+
write(dir, "src/App.tsx", viteAppTsx(name, tailwind, answers.scheme === "system"));
|
|
28
28
|
written.push("src/App.tsx");
|
|
29
29
|
/* The guide's warning covers App.css too: its rules are unlayered and the
|
|
30
30
|
* new App.tsx no longer imports it. Delete rather than empty, so nobody
|
|
@@ -47,6 +47,28 @@ function applyVite({ name, dir, tailwind, answers }) {
|
|
|
47
47
|
`the create-vite template may have changed; add the attribute(s) by hand.`);
|
|
48
48
|
}
|
|
49
49
|
html = anchored.replace(/<title>[^<]*<\/title>/, `<title>${name}</title>`);
|
|
50
|
+
/* Replay a stored light/dark choice before first paint — the guides'
|
|
51
|
+
* "Light and dark" step. A bundled component cannot run before the bundle,
|
|
52
|
+
* so on Vite the script lives in the document itself; the string must stay
|
|
53
|
+
* byte-identical to `themeInitScript` in @forte-ui/react, which is the
|
|
54
|
+
* other writer of the same localStorage key.
|
|
55
|
+
*
|
|
56
|
+
* Not when a scheme is pinned: `data-theme` is then static in the markup
|
|
57
|
+
* above, and the script would OVERRIDE it with any `forte-theme` record on
|
|
58
|
+
* the origin — which on localhost is shared by every project, so a toggle
|
|
59
|
+
* pressed in another app would flip this one's pinned palette. */
|
|
60
|
+
if (answers.scheme === "system") {
|
|
61
|
+
const themeReplay = " <!-- Replays a stored light/dark choice before first paint\n" +
|
|
62
|
+
" (themeInitScript from @forte-ui/react). -->\n" +
|
|
63
|
+
' <script>(function(){try{var t=localStorage.getItem("forte-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}})();</script>\n';
|
|
64
|
+
const replayed = html.replace(/[ \t]*<\/head>/, `${themeReplay} </head>`);
|
|
65
|
+
if (replayed === html) {
|
|
66
|
+
throw new Error("could not find '</head>' in index.html to add the theme replay script — " +
|
|
67
|
+
"the create-vite template may have changed; copy the snippet from the " +
|
|
68
|
+
"guide's 'Light and dark' step into <head> by hand.");
|
|
69
|
+
}
|
|
70
|
+
html = replayed;
|
|
71
|
+
}
|
|
50
72
|
/* Tailwind only: pin the cascade-layer order in the DOCUMENT, not the
|
|
51
73
|
* stylesheet. The bridge's own `@layer theme, base, forte, components,
|
|
52
74
|
* utilities;` statement is supposed to do this, but Vite's CSS pipeline
|
package/dist/prompts.js
CHANGED
|
@@ -95,12 +95,12 @@ export async function collectPlan(opts) {
|
|
|
95
95
|
if (!opts.yes && !flagged.has("seed")) {
|
|
96
96
|
answers.seed = await hexPrompt("Accent color — your brand's hex, the whole palette derives from it")();
|
|
97
97
|
}
|
|
98
|
-
const advancedKeys = ["secondary", "tint", "radius", "density", "motion", "fontSans", "fontMono"];
|
|
98
|
+
const advancedKeys = ["secondary", "tint", "radius", "density", "motion", "scheme", "fontSans", "fontMono"];
|
|
99
99
|
const remaining = advancedKeys.filter((k) => !flagged.has(k));
|
|
100
100
|
const customize = !opts.yes &&
|
|
101
101
|
remaining.length > 0 &&
|
|
102
102
|
accept(await p.confirm({
|
|
103
|
-
message: "Customize further? (secondary, neutrals, radius, density, motion, fonts)",
|
|
103
|
+
message: "Customize further? (secondary, neutrals, radius, density, motion, light/dark, fonts)",
|
|
104
104
|
initialValue: false,
|
|
105
105
|
}));
|
|
106
106
|
if (customize) {
|
|
@@ -155,6 +155,17 @@ export async function collectPlan(opts) {
|
|
|
155
155
|
],
|
|
156
156
|
}));
|
|
157
157
|
}
|
|
158
|
+
if (remaining.includes("scheme")) {
|
|
159
|
+
answers.scheme = accept(await p.select({
|
|
160
|
+
message: "Light and dark",
|
|
161
|
+
initialValue: "system",
|
|
162
|
+
options: [
|
|
163
|
+
{ value: "system", label: "Both", hint: "follows the OS, with a toggle" },
|
|
164
|
+
{ value: "light", label: "Light only" },
|
|
165
|
+
{ value: "dark", label: "Dark only" },
|
|
166
|
+
],
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
158
169
|
if (remaining.includes("fontSans")) {
|
|
159
170
|
answers.fontSans = await fontPrompt("Sans font", SANS_FONTS);
|
|
160
171
|
}
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { type ThemeAnswers } from "./theme.js";
|
|
2
2
|
export declare function viteIndexCss(a: ThemeAnswers, tailwind: boolean): string;
|
|
3
3
|
export declare function viteMainTsx(tailwind: boolean): string;
|
|
4
|
-
|
|
4
|
+
/** `toggle` is false when the answers pinned a colour scheme: `data-theme`
|
|
5
|
+
* then sits statically on `<html>` and a toggle would be a button whose one
|
|
6
|
+
* job is to fight it. The import goes with the element, so the starter
|
|
7
|
+
* still compiles clean under `noUnusedLocals`. */
|
|
8
|
+
export declare function viteAppTsx(name: string, tailwind: boolean, toggle: boolean): string;
|
|
5
9
|
export declare const VITE_CONFIG_TW = "import { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\nimport tailwindcss from \"@tailwindcss/vite\";\n\nexport default defineConfig({\n plugins: [react(), tailwindcss()],\n});\n";
|
|
6
10
|
export declare function nextGlobalsCss(a: ThemeAnswers, tailwind: boolean): string;
|
|
7
11
|
export declare function nextLayoutTsx(name: string, a: ThemeAnswers, tailwind: boolean): string;
|
package/dist/templates.js
CHANGED
|
@@ -18,6 +18,28 @@ const BODY_RULE = `body {
|
|
|
18
18
|
function joinBlocks(...blocks) {
|
|
19
19
|
return blocks.filter(Boolean).join("\n") + "\n";
|
|
20
20
|
}
|
|
21
|
+
/* The starter's one piece of chrome, and the working half of the guides'
|
|
22
|
+
* "Light and dark" step: the theme the questionnaire just seeded can be
|
|
23
|
+
* flipped without writing a line. Two spellings of the same corner, because
|
|
24
|
+
* the Tailwind paths have utilities and the plain ones do not.
|
|
25
|
+
*
|
|
26
|
+
* `fixed` rather than in flow, so the page still centres the component it is
|
|
27
|
+
* there to demonstrate — and the offsets are space tokens rather than `1rem`,
|
|
28
|
+
* because this file is the first thing a new app is copied out of and a
|
|
29
|
+
* hardcoded value here is the habit it teaches. */
|
|
30
|
+
const TOGGLE_TW = `<ThemeToggle className="fixed top-4 right-4" />`;
|
|
31
|
+
const TOGGLE_STYLE = `<ThemeToggle
|
|
32
|
+
style={{ position: "fixed", top: "var(--forte-space-4)", right: "var(--forte-space-4)" }}
|
|
33
|
+
/>`;
|
|
34
|
+
/** `TOGGLE_STYLE` re-indented to sit at `indent` spaces (its first line is
|
|
35
|
+
* placed by the caller, the continuation lines by this). */
|
|
36
|
+
function indented(block, indent) {
|
|
37
|
+
const pad = " ".repeat(indent);
|
|
38
|
+
return block
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map((line, i) => (i === 0 ? line : pad + line))
|
|
41
|
+
.join("\n");
|
|
42
|
+
}
|
|
21
43
|
/* -------------------------------------------------------------------------
|
|
22
44
|
* Vite
|
|
23
45
|
* ---------------------------------------------------------------------- */
|
|
@@ -56,14 +78,19 @@ createRoot(document.getElementById("root")!).render(
|
|
|
56
78
|
);
|
|
57
79
|
`;
|
|
58
80
|
}
|
|
59
|
-
|
|
81
|
+
/** `toggle` is false when the answers pinned a colour scheme: `data-theme`
|
|
82
|
+
* then sits statically on `<html>` and a toggle would be a button whose one
|
|
83
|
+
* job is to fight it. The import goes with the element, so the starter
|
|
84
|
+
* still compiles clean under `noUnusedLocals`. */
|
|
85
|
+
export function viteAppTsx(name, tailwind, toggle) {
|
|
60
86
|
if (tailwind) {
|
|
61
|
-
|
|
87
|
+
const toggleLine = toggle ? ` ${TOGGLE_TW}\n` : "";
|
|
88
|
+
return `import { Button, Card${toggle ? ", ThemeToggle" : ""} } from "@forte-ui/react";
|
|
62
89
|
|
|
63
90
|
export default function App() {
|
|
64
91
|
return (
|
|
65
92
|
<main className="grid min-h-dvh place-items-center bg-background text-foreground">
|
|
66
|
-
<Card.Root variant="elevated" className="items-start gap-5">
|
|
93
|
+
${toggleLine} <Card.Root variant="elevated" className="items-start gap-5">
|
|
67
94
|
<h1 className="text-5 font-semibold">${name}</h1>
|
|
68
95
|
<p className="text-2 text-foreground-muted">
|
|
69
96
|
Utilities and components, one theme.
|
|
@@ -75,12 +102,13 @@ export default function App() {
|
|
|
75
102
|
}
|
|
76
103
|
`;
|
|
77
104
|
}
|
|
78
|
-
|
|
105
|
+
const toggleLine = toggle ? ` ${indented(TOGGLE_STYLE, 6)}\n` : "";
|
|
106
|
+
return `import { Button${toggle ? ", ThemeToggle" : ""} } from "@forte-ui/react";
|
|
79
107
|
|
|
80
108
|
export default function App() {
|
|
81
109
|
return (
|
|
82
110
|
<main style={{ display: "grid", placeItems: "center", minHeight: "100dvh" }}>
|
|
83
|
-
<Button>It works</Button>
|
|
111
|
+
${toggleLine} <Button>It works</Button>
|
|
84
112
|
</main>
|
|
85
113
|
);
|
|
86
114
|
}
|
|
@@ -113,8 +141,28 @@ export function nextLayoutTsx(name, a, tailwind) {
|
|
|
113
141
|
* globals.css imports it after the bridge (see the guide's ordering note),
|
|
114
142
|
* and a second import here would pin the `forte` layer before `base`. */
|
|
115
143
|
const themeImport = tailwind ? "" : `import "@forte-ui/react/theme.css";\n`;
|
|
144
|
+
/* ThemeScript + suppressHydrationWarning are the guides' "Light and dark"
|
|
145
|
+
* step: the script replays a stored light/dark choice onto <html> before
|
|
146
|
+
* first paint, and the suppression covers the attribute it legitimately
|
|
147
|
+
* adds. Harmless with nothing stored — the page just follows the OS. */
|
|
148
|
+
/* The toggle that writes what the script replays. It goes in the LAYOUT,
|
|
149
|
+
* not the page, because it is chrome: it then survives every route the app
|
|
150
|
+
* grows, and the page stays the one file a reader edits first. Being a flat
|
|
151
|
+
* export it needs no `"use client"` here — the boundary the compound
|
|
152
|
+
* components force on `page.tsx` does not reach the layout. */
|
|
153
|
+
/* A pinned scheme drops all three. The attribute is static, so there is
|
|
154
|
+
* nothing for the script to replay and nothing for the suppression to
|
|
155
|
+
* cover — and the script is actively wrong here, not merely idle: it
|
|
156
|
+
* replays whatever `forte-theme` record is on the origin, and on
|
|
157
|
+
* localhost every project shares one origin, so a toggle pressed in some
|
|
158
|
+
* OTHER app would flip this one's pinned palette on load. */
|
|
159
|
+
const pinned = a.scheme !== "system";
|
|
160
|
+
const toggle = tailwind ? TOGGLE_TW : indented(TOGGLE_STYLE, 8);
|
|
161
|
+
const libImport = pinned ? "" : `import { ThemeScript, ThemeToggle } from "@forte-ui/react";\n`;
|
|
162
|
+
const head = pinned ? "" : ` <head>\n <ThemeScript />\n </head>\n`;
|
|
163
|
+
const toggleLine = pinned ? "" : ` ${toggle}\n`;
|
|
116
164
|
return `import type { Metadata } from "next";
|
|
117
|
-
${font.importLine ? font.importLine + "\n" : ""}${themeImport}import "./globals.css";
|
|
165
|
+
${libImport}${font.importLine ? font.importLine + "\n" : ""}${themeImport}import "./globals.css";
|
|
118
166
|
|
|
119
167
|
${font.consts ? font.consts + "\n\n" : ""}export const metadata: Metadata = {
|
|
120
168
|
title: "${name}",
|
|
@@ -127,8 +175,10 @@ export default function RootLayout({
|
|
|
127
175
|
children: React.ReactNode;
|
|
128
176
|
}>) {
|
|
129
177
|
return (
|
|
130
|
-
<html lang="en"${htmlAttrs(a)}${font.htmlClassAttr}>
|
|
131
|
-
<body>
|
|
178
|
+
<html lang="en"${htmlAttrs(a)}${font.htmlClassAttr}${pinned ? "" : " suppressHydrationWarning"}>
|
|
179
|
+
${head} <body>
|
|
180
|
+
${toggleLine} {children}
|
|
181
|
+
</body>
|
|
132
182
|
</html>
|
|
133
183
|
);
|
|
134
184
|
}
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export declare const RADIUS: readonly ["none", "default", "soft", "pill"];
|
|
2
2
|
export declare const DENSITY: readonly ["compact", "default", "spacious"];
|
|
3
3
|
export declare const MOTION: readonly ["full", "default", "reduce"];
|
|
4
|
+
export declare const SCHEME: readonly ["system", "light", "dark"];
|
|
4
5
|
export type Radius = (typeof RADIUS)[number];
|
|
5
6
|
export type Density = (typeof DENSITY)[number];
|
|
6
7
|
export type Motion = (typeof MOTION)[number];
|
|
8
|
+
export type Scheme = (typeof SCHEME)[number];
|
|
7
9
|
export type ThemeAnswers = {
|
|
8
10
|
/** Accent seed hex, or null to keep the library default (write nothing). */
|
|
9
11
|
seed: string | null;
|
|
@@ -13,6 +15,9 @@ export type ThemeAnswers = {
|
|
|
13
15
|
radius: Radius;
|
|
14
16
|
density: Density;
|
|
15
17
|
motion: Motion;
|
|
18
|
+
/** "system" follows the OS and scaffolds the toggle; "light" / "dark" pin
|
|
19
|
+
* one palette on `<html>` and leave the toggle and its replay script out. */
|
|
20
|
+
scheme: Scheme;
|
|
16
21
|
/** Catalogue names. "System" writes nothing. */
|
|
17
22
|
fontSans: string;
|
|
18
23
|
fontMono: string;
|
|
@@ -33,7 +38,7 @@ export declare function rootBlock(a: ThemeAnswers, fontMode: FontMode): string;
|
|
|
33
38
|
export declare function fontImports(a: ThemeAnswers): string[];
|
|
34
39
|
/** ` data-forte-radius="pill" ...` — leading space included, "" when all
|
|
35
40
|
* defaults. Default modes stay UNSET so the app keeps following the OS
|
|
36
|
-
* (motion) and the library's own defaults. */
|
|
41
|
+
* (motion, colour scheme) and the library's own defaults. */
|
|
37
42
|
export declare function htmlAttrs(a: ThemeAnswers): string;
|
|
38
43
|
export type NextFontSetup = {
|
|
39
44
|
/** `import { Inter, JetBrains_Mono } from "next/font/google";` or "". */
|
package/dist/theme.js
CHANGED
|
@@ -14,6 +14,13 @@ import { hexToOklch, bestOnColor } from "./color.js";
|
|
|
14
14
|
export const RADIUS = ["none", "default", "soft", "pill"];
|
|
15
15
|
export const DENSITY = ["compact", "default", "spacious"];
|
|
16
16
|
export const MOTION = ["full", "default", "reduce"];
|
|
17
|
+
/* "system" is spelled out rather than being the "default" the other presets
|
|
18
|
+
* use, because it is also the flag value and the studio's strip label: the
|
|
19
|
+
* one mode that is not a mode. Pinning one writes `data-theme` on `<html>`,
|
|
20
|
+
* and that ATTRIBUTE is the whole mechanism — the stylesheet resolves every
|
|
21
|
+
* `light-dark()` through the `color-scheme` it sets, so a pinned app ships
|
|
22
|
+
* both palettes and simply never shows the other one. */
|
|
23
|
+
export const SCHEME = ["system", "light", "dark"];
|
|
17
24
|
export const DEFAULT_ANSWERS = {
|
|
18
25
|
seed: null,
|
|
19
26
|
secondary: null,
|
|
@@ -21,6 +28,7 @@ export const DEFAULT_ANSWERS = {
|
|
|
21
28
|
radius: "default",
|
|
22
29
|
density: "default",
|
|
23
30
|
motion: "default",
|
|
31
|
+
scheme: "system",
|
|
24
32
|
fontSans: "System",
|
|
25
33
|
fontMono: "System",
|
|
26
34
|
};
|
|
@@ -80,12 +88,13 @@ export function fontImports(a) {
|
|
|
80
88
|
}
|
|
81
89
|
/** ` data-forte-radius="pill" ...` — leading space included, "" when all
|
|
82
90
|
* defaults. Default modes stay UNSET so the app keeps following the OS
|
|
83
|
-
* (motion) and the library's own defaults. */
|
|
91
|
+
* (motion, colour scheme) and the library's own defaults. */
|
|
84
92
|
export function htmlAttrs(a) {
|
|
85
93
|
const attrs = [
|
|
86
94
|
a.radius !== "default" && `data-forte-radius="${a.radius}"`,
|
|
87
95
|
a.density !== "default" && `data-forte-density="${a.density}"`,
|
|
88
96
|
a.motion !== "default" && `data-forte-motion="${a.motion}"`,
|
|
97
|
+
a.scheme !== "system" && `data-theme="${a.scheme}"`,
|
|
89
98
|
].filter(Boolean);
|
|
90
99
|
return attrs.length ? " " + attrs.join(" ") : "";
|
|
91
100
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-forte-ui",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.7",
|
|
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
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,13 +24,6 @@
|
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
26
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
27
|
"dependencies": {
|
|
35
28
|
"@clack/prompts": "^0.11.0",
|
|
36
29
|
"picocolors": "^1.1.1"
|
|
@@ -41,5 +34,12 @@
|
|
|
41
34
|
"engines": {
|
|
42
35
|
"node": ">=20"
|
|
43
36
|
},
|
|
44
|
-
"homepage": "https://forte-ui.com"
|
|
45
|
-
|
|
37
|
+
"homepage": "https://forte-ui.com",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
42
|
+
"smoke": "node scripts/smoke.mjs",
|
|
43
|
+
"clean": "rm -rf dist .turbo"
|
|
44
|
+
}
|
|
45
|
+
}
|