synthesisui 0.4.8 → 0.4.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/commands/add.js +54 -15
- package/dist/fonts.js +4 -4
- package/dist/guide.js +20 -4
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { syncClaudeMd } from "../claude-md.js";
|
|
4
4
|
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
@@ -14,6 +14,19 @@ async function readRootLock(path) {
|
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
const exists = (path) => access(path).then(() => true, () => false);
|
|
18
|
+
/**
|
|
19
|
+
* Where the app's routes live: the configured pagesDir at the project root
|
|
20
|
+
* (`app/`) or nested under `src/` (`src/app/` - create-next-app's other
|
|
21
|
+
* layout). Null when neither exists (instructions-only mode).
|
|
22
|
+
*/
|
|
23
|
+
async function detectAppDir(root, pagesDir) {
|
|
24
|
+
if (await exists(join(root, pagesDir)))
|
|
25
|
+
return pagesDir;
|
|
26
|
+
if (await exists(join(root, "src", pagesDir)))
|
|
27
|
+
return `src/${pagesDir}`;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
17
30
|
/**
|
|
18
31
|
* Materializes a published DS into `_synthesisui/ds/<slug>/v<version>/`, points
|
|
19
32
|
* stable root re-exports (tokens.css/theme.css) and a `.lock` at it, and updates
|
|
@@ -115,36 +128,62 @@ export async function add(slug, opts) {
|
|
|
115
128
|
return;
|
|
116
129
|
const hasTheme = cssArtifacts.includes("theme.css");
|
|
117
130
|
// ── DX: concrete paths + copy-pasteable snippets, with breathing room ──
|
|
131
|
+
// Where the app actually lives (app/ vs src/app/) drives every printed
|
|
132
|
+
// path: the @import depth, the layout example, and where fonts.ts lands.
|
|
133
|
+
const projectConfig = await readProjectConfig(projectRoot);
|
|
134
|
+
const appDir = (await detectAppDir(projectRoot, projectConfig.pagesDir)) ??
|
|
135
|
+
projectConfig.pagesDir;
|
|
136
|
+
const importPrefix = "../".repeat(appDir.split("/").length);
|
|
118
137
|
console.log(section("One-time setup (once per app)"));
|
|
119
|
-
console.log(line(
|
|
120
|
-
console.log(line(
|
|
138
|
+
console.log(line(`1. Import the system in your GLOBAL stylesheet, e.g. ${appDir}/globals.css`));
|
|
139
|
+
console.log(line(` (the path is relative to that file - hence the leading ${importPrefix}):`));
|
|
121
140
|
console.log("");
|
|
122
141
|
console.log(snippet(hasTheme
|
|
123
142
|
? [
|
|
124
143
|
`@import "tailwindcss";`,
|
|
125
|
-
`@import "
|
|
126
|
-
`@import "
|
|
144
|
+
`@import "${importPrefix}_synthesisui/ds/${payload.slug}/tokens.css";`,
|
|
145
|
+
`@import "${importPrefix}_synthesisui/ds/${payload.slug}/theme.css"; /* Tailwind utilities on your tokens */`,
|
|
127
146
|
]
|
|
128
|
-
: [
|
|
147
|
+
: [
|
|
148
|
+
`@import "${importPrefix}_synthesisui/ds/${payload.slug}/tokens.css";`,
|
|
149
|
+
]));
|
|
129
150
|
console.log("");
|
|
130
|
-
console.log(line(`2. Scope your app: add data-ds="${payload.slug}" to a ROOT element, e.g.
|
|
151
|
+
console.log(line(`2. Scope your app: add data-ds="${payload.slug}" to a ROOT element, e.g. ${appDir}/layout.tsx:`));
|
|
131
152
|
console.log("");
|
|
132
153
|
console.log(snippet([`<body data-ds="${payload.slug}">{children}</body>`]));
|
|
133
154
|
// 3. Load the type - the DS ships token NAMES, not the fonts themselves.
|
|
134
|
-
// Next apps get
|
|
135
|
-
//
|
|
136
|
-
// as the framework-agnostic path.
|
|
155
|
+
// Next apps get fonts.ts MATERIALIZED (deterministic does, not teaches):
|
|
156
|
+
// next/font = self-hosted + preloaded + adjusted fallback, no FOUT
|
|
157
|
+
// "blink". The Google Fonts <link> stays as the framework-agnostic path.
|
|
137
158
|
const families = payload.document.foundations.typography.families;
|
|
138
159
|
const fontsHref = googleFontsHref(families);
|
|
139
|
-
const projectConfig = await readProjectConfig(projectRoot);
|
|
140
160
|
const nextFonts = projectConfig.target === "next"
|
|
141
|
-
? nextFontSnippet(families, payload.slug)
|
|
161
|
+
? nextFontSnippet(families, payload.slug, appDir)
|
|
142
162
|
: null;
|
|
143
163
|
if (nextFonts) {
|
|
164
|
+
const fontsPath = join(projectRoot, ...appDir.split("/"), "fonts.ts");
|
|
165
|
+
let wroteFonts = false;
|
|
166
|
+
if (!(await exists(fontsPath)) &&
|
|
167
|
+
(await exists(join(projectRoot, ...appDir.split("/"))))) {
|
|
168
|
+
const header = [
|
|
169
|
+
`// Self-hosted type for the "${payload.slug}" design system (via next/font -`,
|
|
170
|
+
`// preloaded, no font flash). Generated by \`synthesisui add\`; edit freely.`,
|
|
171
|
+
];
|
|
172
|
+
await writeFile(fontsPath, `${[...header, ...nextFonts.fontsFile.slice(1)].join("\n")}\n`, "utf8");
|
|
173
|
+
wroteFonts = true;
|
|
174
|
+
}
|
|
144
175
|
console.log("");
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
176
|
+
if (wroteFonts) {
|
|
177
|
+
console.log(line(`3. ✓ wrote ${appDir}/fonts.ts - self-hosted type via next/font (preloaded, no font flash).`));
|
|
178
|
+
console.log(line(" Finish the wiring with two small edits:"));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.log(line(`3. Load the type via next/font (${appDir}/fonts.ts already exists - left untouched; it should export:)`));
|
|
182
|
+
console.log("");
|
|
183
|
+
console.log(snippet(nextFonts.fontsFile));
|
|
184
|
+
console.log("");
|
|
185
|
+
console.log(line(" Then finish the wiring:"));
|
|
186
|
+
}
|
|
148
187
|
console.log("");
|
|
149
188
|
console.log(snippet(nextFonts.layout));
|
|
150
189
|
console.log("");
|
package/dist/fonts.js
CHANGED
|
@@ -46,7 +46,7 @@ export function googleFontsHref(families) {
|
|
|
46
46
|
* `<link href="fonts.googleapis.com...">` stays as the framework-agnostic
|
|
47
47
|
* fallback - it works everywhere but swaps visibly on cold loads.
|
|
48
48
|
*/
|
|
49
|
-
export function nextFontSnippet(families, slug) {
|
|
49
|
+
export function nextFontSnippet(families, slug, appDir = "app") {
|
|
50
50
|
const roles = ["display", "body", "mono"].filter((role) => {
|
|
51
51
|
const name = families[role]?.trim();
|
|
52
52
|
return name && !GENERIC_FAMILIES.has(name.toLowerCase());
|
|
@@ -66,7 +66,7 @@ export function nextFontSnippet(families, slug) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
const fontsFile = [
|
|
69
|
-
`//
|
|
69
|
+
`// ${appDir}/fonts.ts`,
|
|
70
70
|
`import { ${importNames.join(", ")} } from "next/font/google";`,
|
|
71
71
|
...consts,
|
|
72
72
|
];
|
|
@@ -75,12 +75,12 @@ export function nextFontSnippet(families, slug) {
|
|
|
75
75
|
return `--font-ds-${seen.get(name)}`;
|
|
76
76
|
};
|
|
77
77
|
const layout = [
|
|
78
|
-
`//
|
|
78
|
+
`// ${appDir}/layout.tsx`,
|
|
79
79
|
`import { ${[...new Set(roles.map((r) => seen.get(families[r].trim())))].join(", ")} } from "./fonts";`,
|
|
80
80
|
`<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get(families[r].trim())}.variable}`))].join(" ")}\`}>`,
|
|
81
81
|
];
|
|
82
82
|
const css = [
|
|
83
|
-
`/*
|
|
83
|
+
`/* ${appDir}/globals.css - AFTER the tokens.css import */`,
|
|
84
84
|
`[data-ds="${slug}"] {`,
|
|
85
85
|
...roles.map((role) => ` --ds-typography-families-${role}: var(${roleVar(role)});`),
|
|
86
86
|
`}`,
|
package/dist/guide.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nextFontSnippet } from "./fonts.js";
|
|
1
2
|
const kebab = (v) => v.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
2
3
|
const list = (items) => items.length ? items.map((i) => `\`${i}\``).join(", ") : "_(none)_";
|
|
3
4
|
const dataAttrs = (variants) => Object.entries(variants).map(([axis, opts]) => `data-${kebab(axis)}="${Object.keys(opts).join("|")}"`);
|
|
@@ -67,6 +68,7 @@ export function buildGuide(payload) {
|
|
|
67
68
|
.map((n) => `family=${n.replace(/ /g, "+")}:wght@400;500;600;700`)
|
|
68
69
|
.join("&")}&display=swap`
|
|
69
70
|
: null;
|
|
71
|
+
const nextFonts = nextFontSnippet(foundations.typography.families, slug);
|
|
70
72
|
const fontsSection = fontsHref
|
|
71
73
|
? `
|
|
72
74
|
## Fonts
|
|
@@ -74,15 +76,29 @@ export function buildGuide(payload) {
|
|
|
74
76
|
This system's type relies on ${list(fontFamilies)} - **the DS ships token names, not the
|
|
75
77
|
fonts themselves.** If you don't load them they fall back to a generic family and the system loses
|
|
76
78
|
its typographic identity. Load them once (any one approach):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
${nextFonts
|
|
80
|
+
? `
|
|
81
|
+
- **Next.js (recommended)** - \`next/font\` self-hosts the families (preloaded, size-adjusted
|
|
82
|
+
fallbacks - no font flash on refresh). Three small blocks:
|
|
83
|
+
\`\`\`ts
|
|
84
|
+
${nextFonts.fontsFile.map((l) => ` ${l}`).join("\n")}
|
|
85
|
+
\`\`\`
|
|
86
|
+
\`\`\`tsx
|
|
87
|
+
${nextFonts.layout.map((l) => ` ${l}`).join("\n")}
|
|
88
|
+
\`\`\`
|
|
89
|
+
\`\`\`css
|
|
90
|
+
${nextFonts.css.map((l) => ` ${l}`).join("\n")}
|
|
91
|
+
\`\`\``
|
|
92
|
+
: ""}
|
|
93
|
+
- **Anywhere else** - Google Fonts \`<link>\` in the \`<head>\` (works everywhere, may flash on
|
|
94
|
+
cold loads):
|
|
79
95
|
\`\`\`html
|
|
80
96
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
81
97
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
82
98
|
<link rel="stylesheet" href="${fontsHref}" />
|
|
83
99
|
\`\`\`
|
|
84
|
-
- **
|
|
85
|
-
|
|
100
|
+
- **Fontsource** or self-hosted \`@font-face\` work too - just register the families above.
|
|
101
|
+
If a family isn't on Google Fonts, self-host it.
|
|
86
102
|
|
|
87
103
|
---
|
|
88
104
|
`
|