synthesisui 0.4.7 → 0.4.9

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("");
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/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
- - **Google Fonts** - drop in your \`<head>\` (or root layout):
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
- - **Next.js** (\`next/font/google\`), **Fontsource**, or self-hosted \`@font-face\` work too - just
85
- register the families above. If a family isn't on Google Fonts, self-host it.
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
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {