synthesisui 0.4.6 → 0.4.8

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.
@@ -1,8 +1,8 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { syncClaudeMd } from "../claude-md.js";
4
- import { resolveRegistry } from "../config.js";
5
- import { customFontFamilies, googleFontsHref } from "../fonts.js";
4
+ import { readProjectConfig, resolveRegistry } from "../config.js";
5
+ import { customFontFamilies, googleFontsHref, nextFontSnippet, } from "../fonts.js";
6
6
  import { buildGuide } from "../guide.js";
7
7
  import { body as line, section, snippet } from "../output.js";
8
8
  import { fetchDesignSystem } from "../registry.js";
@@ -131,10 +131,30 @@ export async function add(slug, opts) {
131
131
  console.log("");
132
132
  console.log(snippet([`<body data-ds="${payload.slug}">{children}</body>`]));
133
133
  // 3. Load the type - the DS ships token NAMES, not the fonts themselves.
134
- // Without this, display/body silently fall back and the identity is lost.
134
+ // Next apps get the next/font recipe (self-hosted + preloaded + adjusted
135
+ // fallback = no FOUT "blink" on refresh); the Google Fonts <link> stays
136
+ // as the framework-agnostic path.
135
137
  const families = payload.document.foundations.typography.families;
136
138
  const fontsHref = googleFontsHref(families);
137
- if (fontsHref) {
139
+ const projectConfig = await readProjectConfig(projectRoot);
140
+ const nextFonts = projectConfig.target === "next"
141
+ ? nextFontSnippet(families, payload.slug)
142
+ : null;
143
+ if (nextFonts) {
144
+ console.log("");
145
+ console.log(line("3. Load the type via next/font (recommended: self-hosted, preloaded, no font flash on refresh). Three small blocks:"));
146
+ console.log("");
147
+ console.log(snippet(nextFonts.fontsFile));
148
+ console.log("");
149
+ console.log(snippet(nextFonts.layout));
150
+ console.log("");
151
+ console.log(snippet(nextFonts.css));
152
+ if (fontsHref) {
153
+ console.log("");
154
+ console.log(line(` Quick alternative (works anywhere, may flash on cold loads): <link rel="stylesheet" href="${fontsHref}" /> in the <head>.`));
155
+ }
156
+ }
157
+ else if (fontsHref) {
138
158
  console.log("");
139
159
  console.log(line("3. Load the type - this system ships font NAMES, not the fonts. Add to your app's <head> (e.g. app/layout.tsx) so the families resolve (else they fall back and the look is lost):"));
140
160
  console.log("");
@@ -19,22 +19,25 @@ export async function template(slug, name, opts) {
19
19
  console.log(`→ generating "${name}" from "${slug}" (${target}) …`);
20
20
  const generated = await fetchTemplate(base, slug, name, target, opts.version);
21
21
  // --out targets the page (1st file); sibling files (e.g. the CSS) land in the
22
- // same directory. Without --out, everything goes under <pagesDir>.
22
+ // same directory. Without --out, everything goes under templates/<name>/ -
23
+ // a loose landing.tsx at the app/ root read as a route without being one,
24
+ // and a second template turned the app dir into soup.
23
25
  const [pageFile, ...siblings] = generated.files;
24
- const pageRel = opts.out ?? join(config.pagesDir, pageFile.filename);
26
+ const defaultDir = join("templates", name);
27
+ const pageRel = opts.out ?? join(defaultDir, pageFile.filename);
25
28
  const pageDir = dirname(join(root, pageRel));
26
29
  await mkdir(pageDir, { recursive: true });
27
30
  await writeFile(join(root, pageRel), pageFile.code, "utf8");
28
31
  console.log(`✓ wrote ${pageRel} (${slug} v${generated.version})`);
29
32
  for (const f of siblings) {
30
- const rel = opts.out
31
- ? join(dirname(pageRel), f.filename)
32
- : join(config.pagesDir, f.filename);
33
+ const rel = join(dirname(pageRel), f.filename);
33
34
  await writeFile(join(root, rel), f.code, "utf8");
34
35
  console.log(`✓ wrote ${rel}`);
35
36
  }
36
37
  console.log("");
37
38
  console.log("Next steps:");
39
+ console.log(` • use it in a route, e.g. ${join(config.pagesDir, "page.tsx")}:`);
40
+ console.log(` import Page from "@/${defaultDir.replace(/\\/g, "/")}/${pageFile.filename.replace(/\.tsx$/, "")}";`);
38
41
  console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
39
42
  console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
40
43
  console.log(" • refine the file: wire real data, split into components, swap placeholders");
package/dist/fonts.js CHANGED
@@ -38,3 +38,52 @@ export function googleFontsHref(families) {
38
38
  .join("&");
39
39
  return `https://fonts.googleapis.com/css2?${query}&display=swap`;
40
40
  }
41
+ /**
42
+ * The RECOMMENDED wiring for Next apps: `next/font` self-hosts the families
43
+ * (preloaded, size-adjusted fallbacks - no FOUT "blink" on refresh), and a
44
+ * small CSS block re-points the DS's family tokens to next/font's variables.
45
+ * Returns null when the doc only uses generic families. The naive
46
+ * `<link href="fonts.googleapis.com...">` stays as the framework-agnostic
47
+ * fallback - it works everywhere but swaps visibly on cold loads.
48
+ */
49
+ export function nextFontSnippet(families, slug) {
50
+ const roles = ["display", "body", "mono"].filter((role) => {
51
+ const name = families[role]?.trim();
52
+ return name && !GENERIC_FAMILIES.has(name.toLowerCase());
53
+ });
54
+ if (roles.length === 0)
55
+ return null;
56
+ const importName = (name) => name.trim().replace(/ /g, "_");
57
+ const seen = new Map(); // family name -> const name
58
+ const importNames = [];
59
+ const consts = [];
60
+ for (const role of roles) {
61
+ const name = families[role].trim();
62
+ if (!seen.has(name)) {
63
+ seen.set(name, role);
64
+ importNames.push(importName(name));
65
+ consts.push(`export const ${seen.get(name)} = ${importName(name)}({`, ` subsets: ["latin"],`, ` variable: "--font-ds-${seen.get(name)}",`, `});`);
66
+ }
67
+ }
68
+ const fontsFile = [
69
+ `// app/fonts.ts`,
70
+ `import { ${importNames.join(", ")} } from "next/font/google";`,
71
+ ...consts,
72
+ ];
73
+ const roleVar = (role) => {
74
+ const name = families[role].trim();
75
+ return `--font-ds-${seen.get(name)}`;
76
+ };
77
+ const layout = [
78
+ `// app/layout.tsx`,
79
+ `import { ${[...new Set(roles.map((r) => seen.get(families[r].trim())))].join(", ")} } from "./fonts";`,
80
+ `<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get(families[r].trim())}.variable}`))].join(" ")}\`}>`,
81
+ ];
82
+ const css = [
83
+ `/* app/globals.css - AFTER the tokens.css import */`,
84
+ `[data-ds="${slug}"] {`,
85
+ ...roles.map((role) => ` --ds-typography-families-${role}: var(${roleVar(role)});`),
86
+ `}`,
87
+ ];
88
+ return { fontsFile, layout, css };
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {