@puzzle-ui/theme 0.3.0 → 0.5.0

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.
Files changed (62) hide show
  1. package/README.md +124 -84
  2. package/dist/cli.cjs +96 -0
  3. package/dist/cli.cjs.map +1 -0
  4. package/dist/cli.d.cts +1 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +97 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/constants.cjs +21 -21
  9. package/dist/constants.cjs.map +1 -1
  10. package/dist/constants.d.cts +8 -8
  11. package/dist/constants.d.ts +8 -8
  12. package/dist/constants.js +21 -21
  13. package/dist/constants.js.map +1 -1
  14. package/dist/fonts/constants.cjs +3 -3
  15. package/dist/fonts/constants.cjs.map +1 -1
  16. package/dist/fonts/constants.js +3 -3
  17. package/dist/fonts/constants.js.map +1 -1
  18. package/dist/fonts/create-font-preload-filename.cjs +9 -0
  19. package/dist/fonts/create-font-preload-filename.cjs.map +1 -0
  20. package/dist/fonts/create-font-preload-filename.js +9 -0
  21. package/dist/fonts/create-font-preload-filename.js.map +1 -0
  22. package/dist/fonts/create-google-font-url.cjs +3 -2
  23. package/dist/fonts/create-google-font-url.cjs.map +1 -1
  24. package/dist/fonts/create-google-font-url.js +3 -2
  25. package/dist/fonts/create-google-font-url.js.map +1 -1
  26. package/dist/fonts/create-local-font-stylesheet-url.cjs +10 -0
  27. package/dist/fonts/create-local-font-stylesheet-url.cjs.map +1 -0
  28. package/dist/fonts/create-local-font-stylesheet-url.js +10 -0
  29. package/dist/fonts/create-local-font-stylesheet-url.js.map +1 -0
  30. package/dist/fonts/download-google-fonts.cjs +138 -0
  31. package/dist/fonts/download-google-fonts.cjs.map +1 -0
  32. package/dist/fonts/download-google-fonts.js +138 -0
  33. package/dist/fonts/download-google-fonts.js.map +1 -0
  34. package/dist/fonts/google-font.cjs +18 -8
  35. package/dist/fonts/google-font.cjs.map +1 -1
  36. package/dist/fonts/google-font.d.cts +11 -3
  37. package/dist/fonts/google-font.d.ts +11 -3
  38. package/dist/fonts/google-font.js +18 -8
  39. package/dist/fonts/google-font.js.map +1 -1
  40. package/dist/fonts/index.d.cts +2 -2
  41. package/dist/fonts/resolve-font-config.cjs +4 -1
  42. package/dist/fonts/resolve-font-config.cjs.map +1 -1
  43. package/dist/fonts/resolve-font-config.js +4 -1
  44. package/dist/fonts/resolve-font-config.js.map +1 -1
  45. package/dist/fonts/resolve-font-stylesheets.cjs +7 -1
  46. package/dist/fonts/resolve-font-stylesheets.cjs.map +1 -1
  47. package/dist/fonts/resolve-font-stylesheets.js +7 -1
  48. package/dist/fonts/resolve-font-stylesheets.js.map +1 -1
  49. package/dist/fonts/types.d.cts +12 -1
  50. package/dist/fonts/types.d.ts +12 -1
  51. package/dist/global.css +24 -24
  52. package/dist/index.d.cts +2 -2
  53. package/dist/index.d.ts +2 -2
  54. package/dist/theme-scopes.css +3 -3
  55. package/dist/theme.cjs +26 -4
  56. package/dist/theme.cjs.map +1 -1
  57. package/dist/theme.css +16 -16
  58. package/dist/theme.d.cts +27 -3
  59. package/dist/theme.d.ts +27 -3
  60. package/dist/theme.js +26 -4
  61. package/dist/theme.js.map +1 -1
  62. package/package.json +11 -4
package/README.md CHANGED
@@ -1,113 +1,153 @@
1
- # @puzzle-ui/theme
1
+ # Getting Started
2
2
 
3
- Puzzle's theme system: semantic design tokens, light/dark color schemes,
4
- typography, Google Fonts, and configurable link and image components.
3
+ Install Puzzle in your React app, build your first interface, and customize the theme.
5
4
 
6
- > Puzzle is under active development. APIs and framework compatibility may
7
- > change; use it for experimentation and evaluation rather than production.
5
+ > \[!WARNING\] **Puzzle is not ready for production use.**
6
+ >
7
+ > This project is under active development. APIs, component props,
8
+ > package exports, styling, and internal implementation may change
9
+ > without notice. Components may be incomplete, and testing,
10
+ > accessibility, documentation, and framework compatibility are not yet
11
+ > guaranteed. Use it for experimentation and evaluation only---not for
12
+ > production applications.
8
13
 
9
- ## Installation
14
+ Puzzle is a TypeScript and React UI component library built around reusable components and shared design tokens. Its components are composable and router-independent, with styling supplied by `"@puzzle-ui/theme`. The framework is setup to be compatible with `Next.js`, `Tanstack Start` and `react-router`.
10
15
 
11
- ```sh
12
- npm install @puzzle-ui/theme
13
- # or
14
- bun add @puzzle-ui/theme
15
- # or
16
- pnpm add @puzzle-ui/theme
17
- # or
18
- yarn add @puzzle-ui/theme
16
+ **Puzzle is under active development and is not ready for production use.** APIs, props, exports, and styling may change without notice. Accessibility and framework compatibility are still being tested. Use the project for experimentation and evaluation.
17
+
18
+ ## Prerequisites
19
+
20
+ - A package manager: npm, Bun, pnpm, or Yarn.
21
+ - An existing React application with a bundler that supports CSS imports, such as Vite.
22
+ - Familiarity with React and TypeScript. The UI and theme packages require React and React DOM 19 or later within the supported React 19 range.
23
+
24
+ ## Set up a new project
25
+
26
+ Simple run our @puzzle-ui/create project template to get started.
27
+
28
+ npm:
29
+
30
+ ```bash
31
+ npx @puzzle-ui/create
19
32
  ```
20
33
 
21
- Requires React and React DOM 19 (`^19.0.0`) and a CSS-aware application
22
- bundler. Styles are imported by the package; no separate stylesheet or
23
- Tailwind setup is required.
34
+ bun:
24
35
 
25
- ## Usage
36
+ ```bash
37
+ bunx @puzzle-ui/create
38
+ ```
26
39
 
27
- ```tsx
28
- import { Theme } from "@puzzle-ui/theme";
40
+ Puzzle can set up a new project for you with the following framework options:
29
41
 
30
- export const App = () => (
31
- <Theme
32
- theme="system"
33
- tokens={{ radius: { md: "0.5rem" } }}
34
- >
35
- <main>Your application</main>
36
- </Theme>
37
- );
42
+ - Basic React app
43
+ - Tanstack start
44
+ - Next.js
45
+
46
+ Furthermore you can choose to install tailwindcss or not.
47
+ It directly installs all dependencies and sets up the project for you.
48
+
49
+ ## Set up in an existing project:
50
+
51
+ Install the UI library and theme package in your application's directory. Choose your package manager below:
52
+
53
+ npm:
54
+
55
+ ```bash
56
+ npm install @puzzle-ui/ui --save
38
57
  ```
39
58
 
40
- Use one root theme for your application. By default it follows the system
41
- color scheme, emits `--puzzle-*` CSS custom properties, and applies global
42
- styles. Set `globalStyles={false}` to opt out of the global defaults.
43
-
44
- ## Configuration
45
-
46
- | Prop | Purpose |
47
- | --- | --- |
48
- | `theme` | `"light"`, `"dark"`, or `"system"`; defaults to `"system"`. |
49
- | `scope` | `"root"` for document-wide styling or `"local"` for a subtree. |
50
- | `tokens` | Deep-partial overrides of color, typography, spacing, radius, layout, motion, and other design tokens. |
51
- | `colorTheme` | Select a built-in palette, such as `"cobalt-sky"`. |
52
- | `fonts` | Configure Google Fonts for heading, body, and accent roles. |
53
- | `components` | Override link and image rendering. |
54
- | `nonce` | CSP nonce for injected style elements. |
55
-
56
- Local themes do not apply global styles. Fonts configured through `fonts`
57
- load external Google Fonts stylesheets; omit this prop to avoid those
58
- requests.
59
-
60
- ## Use tokens in your styles
61
-
62
- ```css
63
- .panel {
64
- color: var(--puzzle-color-text);
65
- background: var(--puzzle-color-surface);
66
- padding: var(--puzzle-space-4);
67
- border-radius: var(--puzzle-radius-md);
68
- }
59
+ bun:
60
+
61
+ ```bash
62
+ bun add @puzzle-ui/create
69
63
  ```
70
64
 
71
- ## Router integration
65
+ pnpm:
72
66
 
73
- For routers whose links use `to`, configure the destination prop:
67
+ ```bash
68
+ pnpm add @puzzle-ui/ui
69
+ ```
74
70
 
75
- ```tsx
76
- import { Link } from "@tanstack/react-router";
71
+ yarn:
77
72
 
78
- <Theme components={{ link: { component: Link, hrefProp: "to" } }}>
79
- <AppContent />
80
- </Theme>;
73
+ ```bash
74
+ yarn add @puzzle-ui/ui
81
75
  ```
82
76
 
83
- Components using `href` can be passed directly. Use `link: null` or
84
- `image: null` to restore native elements in a subtree.
77
+ These commands install published packages; they do not clone the repository or build its workspaces. Puzzle does not require Bun in your application. React and React DOM are peer dependencies and should already be installed in your app.
85
78
 
86
- ## Exports
79
+ ## Download fonts with the theme CLI
87
80
 
88
- The root exports `Theme`, `GoogleFont`, `createGoogleFontUrl`, `BREAKPOINTS`,
89
- and their configuration types. Context helpers are available through
90
- `@puzzle-ui/theme/components-context` and `@puzzle-ui/theme/scope-context`.
81
+ The `@puzzle-ui/theme` package includes the `puzzle-theme` executable.
82
+ It requires Node.js 20.19 or later.
91
83
 
92
- ESM and CommonJS builds with matching TypeScript declarations are included.
93
- CSS imports require a bundler rather than a bare Node.js runtime.
84
+ ```bash
85
+ npm install @puzzle-ui/theme
86
+ npm exec -- puzzle-theme --help
87
+ ```
94
88
 
95
- See the repository's [theme documentation](../../apps/site/content/docs/theme)
96
- for token references, fonts, colors, and component overrides.
89
+ Create `puzzle-fonts.json` in your application's working directory:
97
90
 
98
- ## Development
91
+ ```json
92
+ {
93
+ "body": {
94
+ "family": "Inter",
95
+ "weights": [400, 700]
96
+ }
97
+ }
98
+ ```
99
99
 
100
- ```sh
101
- # From the repository root
102
- npm run build --workspace=@puzzle-ui/theme
100
+ Download the fonts with either package manager:
103
101
 
104
- # From this package directory
105
- bun run vitest
102
+ ```bash
103
+ npm exec -- puzzle-theme fonts download
104
+ # Or, with the package installed using Bun:
105
+ bun run puzzle-theme fonts download
106
106
  ```
107
107
 
108
- `prepack` rebuilds the package before packing or publishing. See the root
109
- [publishing guide](../../README.md#publishing-packages).
108
+ The command writes WOFF2 files and `puzzle-fonts.css` to `public/fonts`.
109
+ Use `--config <path>` or `--out-dir <path>` to change these locations.
110
+ Unchanged downloads are skipped; `--force` downloads them again.
111
+
112
+ For variable fonts, use `"weightRange": [300, 700]` instead of `weights`
113
+ to request a continuous range. Only use ranges supported by the font family.
114
+ Discrete `weights` remain supported; when both are provided, the range is used.
115
+
116
+ The downloader keeps all language subsets by default. Pass
117
+ `--subsets latin,latin-ext` to retain only Latin and Latin Extended;
118
+ missing requested subsets produce an error. Other scripts will use fallback
119
+ fonts. Subset choices are included in the download cache key.
120
+
121
+ To load the generated stylesheet, set `fontSource="local"` on `Theme`
122
+ and pass the same configuration to its `fonts` prop. The default local
123
+ stylesheet URL is `/fonts/puzzle-fonts.css`.
124
+
125
+ When using local fonts, Puzzle automatically preloads the normal Latin
126
+ variant of the body font. Use `fontPreload="heading"` or
127
+ `fontPreload="accent"` to preload another configured role, or
128
+ `fontPreload={false}` to disable preloading.
110
129
 
111
- ## License
130
+ The generated stylesheet and preload link use the same font URL, avoiding
131
+ duplicate downloads. Preloading only applies to local fonts and only one
132
+ font is preloaded per Theme instance.
112
133
 
113
- MIT. See [LICENSE](../../LICENSE); the published license is at `dist/LICENSE`.
134
+ ## Your first interface
135
+
136
+ Import components from `"@puzzle-ui/ui` and wrap your application in `Theme` from `"@puzzle-ui/theme`:
137
+
138
+ ```tsx
139
+ import { Theme } from "@puzzle-ui/theme";
140
+ import { Button } from "@puzzle-ui/ui";
141
+
142
+ export const App = () => (
143
+ <Theme>
144
+ <main>
145
+ <h1>Hello, Puzzle</h1>
146
+ <p>Build your interface with reusable components and shared tokens.</p>
147
+ <Button onClick={() => window.alert("Welcome to Puzzle!")}>
148
+ Get started
149
+ </Button>
150
+ </main>
151
+ </Theme>
152
+ );
153
+ ```
package/dist/cli.cjs ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ const require_download_google_fonts = require("./fonts/download-google-fonts.cjs");
3
+ let node_fs_promises = require("node:fs/promises");
4
+ let node_path = require("node:path");
5
+ // src/cli.ts
6
+ const FONT_ROLES = [
7
+ "heading",
8
+ "body",
9
+ "accent"
10
+ ];
11
+ const printHelp = () => {
12
+ console.log(`
13
+ Puzzle Theme CLI
14
+
15
+ Usage:
16
+ puzzle-theme fonts download [options]
17
+
18
+ Commands:
19
+ fonts download Download configured Google Fonts.
20
+
21
+ Options:
22
+ --config <path> Configuration file (default: puzzle-fonts.json).
23
+ --out-dir <path> Output directory (default: public/fonts).
24
+ --force Force a new download.
25
+ --subsets <names> Comma-separated subsets (default: all).
26
+ --help Display this help.
27
+
28
+ Examples:
29
+ puzzle-theme fonts download
30
+ puzzle-theme fonts download --force
31
+ puzzle-theme fonts download --config ./config/fonts.json
32
+ puzzle-theme fonts download --out-dir public/assets/fonts
33
+ `);
34
+ };
35
+ const getOption = (args, name, fallback) => {
36
+ const inlineOption = args.find((arg) => arg.startsWith(`${name}=`));
37
+ if (inlineOption) {
38
+ const value = inlineOption.slice(name.length + 1);
39
+ if (!value) throw new Error(`Missing value for ${name}.`);
40
+ return value;
41
+ }
42
+ const index = args.indexOf(name);
43
+ if (index === -1) return fallback;
44
+ const value = args[index + 1];
45
+ if (!value || value.startsWith("--")) throw new Error(`Missing value for ${name}.`);
46
+ return value;
47
+ };
48
+ const isRecord = (value) => {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ };
51
+ const parseFontConfig = (value) => {
52
+ if (!isRecord(value)) throw new TypeError("The font configuration must be a JSON object.");
53
+ const fonts = {};
54
+ for (const [role, config] of Object.entries(value)) {
55
+ if (!FONT_ROLES.includes(role)) throw new TypeError(`Unknown font role "${role}". Expected heading, body, or accent.`);
56
+ if (!isRecord(config)) throw new TypeError(`Font configuration for "${role}" must be an object.`);
57
+ if (typeof config.family !== "string" || !config.family.trim()) throw new TypeError(`Font configuration for "${role}" requires a family.`);
58
+ fonts[role] = config;
59
+ }
60
+ if (Object.keys(fonts).length === 0) throw new TypeError("No fonts configured. Add at least one font role.");
61
+ return fonts;
62
+ };
63
+ const main = async () => {
64
+ const args = process.argv.slice(2);
65
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
66
+ printHelp();
67
+ return;
68
+ }
69
+ if (args[0] !== "fonts" || args[1] !== "download") {
70
+ printHelp();
71
+ throw new Error("Unknown command.");
72
+ }
73
+ const configPath = (0, node_path.resolve)(process.cwd(), getOption(args, "--config", "puzzle-fonts.json"));
74
+ const outputDir = (0, node_path.resolve)(process.cwd(), getOption(args, "--out-dir", "public/fonts"));
75
+ const rawConfig = JSON.parse(await (0, node_fs_promises.readFile)(configPath, "utf8"));
76
+ const fonts = parseFontConfig(rawConfig);
77
+ const result = await require_download_google_fonts.downloadGoogleFonts({
78
+ fonts,
79
+ outputDir,
80
+ force: args.includes("--force"),
81
+ subsets: getOption(args, "--subsets", "all") === "all" ? void 0 : getOption(args, "--subsets", "all").split(",").map((subset) => subset.trim())
82
+ });
83
+ if (result.skipped) {
84
+ console.log("Google Fonts are already up to date.");
85
+ console.log(`Location: ${result.cssPath}`);
86
+ return;
87
+ }
88
+ console.log(`Downloaded ${result.fontFiles} WOFF2 font files.`);
89
+ console.log(`Generated: ${result.cssPath}`);
90
+ };
91
+ main().catch((error) => {
92
+ console.error(error instanceof Error ? error.message : error);
93
+ process.exitCode = 1;
94
+ });
95
+
96
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.cjs","names":["resolve","readFile","downloadGoogleFonts"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nimport { downloadGoogleFonts } from \"./fonts/download-google-fonts\";\nimport type { FontConfig, FontRole, ThemeFonts } from \"./fonts/types\";\n\nconst FONT_ROLES: readonly FontRole[] = [\"heading\", \"body\", \"accent\"];\n\nconst printHelp = () => {\n console.log(`\nPuzzle Theme CLI\n\nUsage:\n puzzle-theme fonts download [options]\n\nCommands:\n fonts download Download configured Google Fonts.\n\nOptions:\n --config <path> Configuration file (default: puzzle-fonts.json).\n --out-dir <path> Output directory (default: public/fonts).\n --force Force a new download.\n --subsets <names> Comma-separated subsets (default: all).\n --help Display this help.\n\nExamples:\n puzzle-theme fonts download\n puzzle-theme fonts download --force\n puzzle-theme fonts download --config ./config/fonts.json\n puzzle-theme fonts download --out-dir public/assets/fonts\n`);\n};\n\nconst getOption = (args: string[], name: string, fallback: string): string => {\n const inlineOption = args.find((arg) => arg.startsWith(`${name}=`));\n\n if (inlineOption) {\n const value = inlineOption.slice(name.length + 1);\n\n if (!value) {\n throw new Error(`Missing value for ${name}.`);\n }\n\n return value;\n }\n\n const index = args.indexOf(name);\n\n if (index === -1) {\n return fallback;\n }\n\n const value = args[index + 1];\n\n if (!value || value.startsWith(\"--\")) {\n throw new Error(`Missing value for ${name}.`);\n }\n\n return value;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n};\n\nconst parseFontConfig = (value: unknown): ThemeFonts => {\n if (!isRecord(value)) {\n throw new TypeError(\"The font configuration must be a JSON object.\");\n }\n\n const fonts: Partial<Record<FontRole, FontConfig>> = {};\n\n for (const [role, config] of Object.entries(value)) {\n if (!FONT_ROLES.includes(role as FontRole)) {\n throw new TypeError(\n `Unknown font role \"${role}\". Expected heading, body, or accent.`,\n );\n }\n\n if (!isRecord(config)) {\n throw new TypeError(\n `Font configuration for \"${role}\" must be an object.`,\n );\n }\n\n if (typeof config.family !== \"string\" || !config.family.trim()) {\n throw new TypeError(\n `Font configuration for \"${role}\" requires a family.`,\n );\n }\n\n fonts[role as FontRole] = config as unknown as FontConfig;\n }\n\n if (Object.keys(fonts).length === 0) {\n throw new TypeError(\"No fonts configured. Add at least one font role.\");\n }\n\n return fonts;\n};\n\nconst main = async () => {\n const args = process.argv.slice(2);\n\n if (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n printHelp();\n return;\n }\n\n if (args[0] !== \"fonts\" || args[1] !== \"download\") {\n printHelp();\n throw new Error(\"Unknown command.\");\n }\n\n const configPath = resolve(\n process.cwd(),\n getOption(args, \"--config\", \"puzzle-fonts.json\"),\n );\n\n const outputDir = resolve(\n process.cwd(),\n getOption(args, \"--out-dir\", \"public/fonts\"),\n );\n\n const rawConfig = JSON.parse(await readFile(configPath, \"utf8\")) as unknown;\n\n const fonts = parseFontConfig(rawConfig);\n\n const result = await downloadGoogleFonts({\n fonts,\n outputDir,\n force: args.includes(\"--force\"),\n subsets: getOption(args, \"--subsets\", \"all\") === \"all\"\n ? undefined\n : getOption(args, \"--subsets\", \"all\").split(\",\").map((subset) => subset.trim()),\n });\n\n if (result.skipped) {\n console.log(\"Google Fonts are already up to date.\");\n console.log(`Location: ${result.cssPath}`);\n return;\n }\n\n console.log(`Downloaded ${result.fontFiles} WOFF2 font files.`);\n\n console.log(`Generated: ${result.cssPath}`);\n};\n\nvoid main().catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n\n process.exitCode = 1;\n});\n"],"mappings":"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"}
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.js ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import { downloadGoogleFonts } from "./fonts/download-google-fonts.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+ // src/cli.ts
6
+ const FONT_ROLES = [
7
+ "heading",
8
+ "body",
9
+ "accent"
10
+ ];
11
+ const printHelp = () => {
12
+ console.log(`
13
+ Puzzle Theme CLI
14
+
15
+ Usage:
16
+ puzzle-theme fonts download [options]
17
+
18
+ Commands:
19
+ fonts download Download configured Google Fonts.
20
+
21
+ Options:
22
+ --config <path> Configuration file (default: puzzle-fonts.json).
23
+ --out-dir <path> Output directory (default: public/fonts).
24
+ --force Force a new download.
25
+ --subsets <names> Comma-separated subsets (default: all).
26
+ --help Display this help.
27
+
28
+ Examples:
29
+ puzzle-theme fonts download
30
+ puzzle-theme fonts download --force
31
+ puzzle-theme fonts download --config ./config/fonts.json
32
+ puzzle-theme fonts download --out-dir public/assets/fonts
33
+ `);
34
+ };
35
+ const getOption = (args, name, fallback) => {
36
+ const inlineOption = args.find((arg) => arg.startsWith(`${name}=`));
37
+ if (inlineOption) {
38
+ const value = inlineOption.slice(name.length + 1);
39
+ if (!value) throw new Error(`Missing value for ${name}.`);
40
+ return value;
41
+ }
42
+ const index = args.indexOf(name);
43
+ if (index === -1) return fallback;
44
+ const value = args[index + 1];
45
+ if (!value || value.startsWith("--")) throw new Error(`Missing value for ${name}.`);
46
+ return value;
47
+ };
48
+ const isRecord = (value) => {
49
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50
+ };
51
+ const parseFontConfig = (value) => {
52
+ if (!isRecord(value)) throw new TypeError("The font configuration must be a JSON object.");
53
+ const fonts = {};
54
+ for (const [role, config] of Object.entries(value)) {
55
+ if (!FONT_ROLES.includes(role)) throw new TypeError(`Unknown font role "${role}". Expected heading, body, or accent.`);
56
+ if (!isRecord(config)) throw new TypeError(`Font configuration for "${role}" must be an object.`);
57
+ if (typeof config.family !== "string" || !config.family.trim()) throw new TypeError(`Font configuration for "${role}" requires a family.`);
58
+ fonts[role] = config;
59
+ }
60
+ if (Object.keys(fonts).length === 0) throw new TypeError("No fonts configured. Add at least one font role.");
61
+ return fonts;
62
+ };
63
+ const main = async () => {
64
+ const args = process.argv.slice(2);
65
+ if (args.includes("--help") || args.includes("-h") || args.length === 0) {
66
+ printHelp();
67
+ return;
68
+ }
69
+ if (args[0] !== "fonts" || args[1] !== "download") {
70
+ printHelp();
71
+ throw new Error("Unknown command.");
72
+ }
73
+ const configPath = resolve(process.cwd(), getOption(args, "--config", "puzzle-fonts.json"));
74
+ const outputDir = resolve(process.cwd(), getOption(args, "--out-dir", "public/fonts"));
75
+ const rawConfig = JSON.parse(await readFile(configPath, "utf8"));
76
+ const fonts = parseFontConfig(rawConfig);
77
+ const result = await downloadGoogleFonts({
78
+ fonts,
79
+ outputDir,
80
+ force: args.includes("--force"),
81
+ subsets: getOption(args, "--subsets", "all") === "all" ? void 0 : getOption(args, "--subsets", "all").split(",").map((subset) => subset.trim())
82
+ });
83
+ if (result.skipped) {
84
+ console.log("Google Fonts are already up to date.");
85
+ console.log(`Location: ${result.cssPath}`);
86
+ return;
87
+ }
88
+ console.log(`Downloaded ${result.fontFiles} WOFF2 font files.`);
89
+ console.log(`Generated: ${result.cssPath}`);
90
+ };
91
+ main().catch((error) => {
92
+ console.error(error instanceof Error ? error.message : error);
93
+ process.exitCode = 1;
94
+ });
95
+ export {};
96
+
97
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nimport { downloadGoogleFonts } from \"./fonts/download-google-fonts\";\nimport type { FontConfig, FontRole, ThemeFonts } from \"./fonts/types\";\n\nconst FONT_ROLES: readonly FontRole[] = [\"heading\", \"body\", \"accent\"];\n\nconst printHelp = () => {\n console.log(`\nPuzzle Theme CLI\n\nUsage:\n puzzle-theme fonts download [options]\n\nCommands:\n fonts download Download configured Google Fonts.\n\nOptions:\n --config <path> Configuration file (default: puzzle-fonts.json).\n --out-dir <path> Output directory (default: public/fonts).\n --force Force a new download.\n --subsets <names> Comma-separated subsets (default: all).\n --help Display this help.\n\nExamples:\n puzzle-theme fonts download\n puzzle-theme fonts download --force\n puzzle-theme fonts download --config ./config/fonts.json\n puzzle-theme fonts download --out-dir public/assets/fonts\n`);\n};\n\nconst getOption = (args: string[], name: string, fallback: string): string => {\n const inlineOption = args.find((arg) => arg.startsWith(`${name}=`));\n\n if (inlineOption) {\n const value = inlineOption.slice(name.length + 1);\n\n if (!value) {\n throw new Error(`Missing value for ${name}.`);\n }\n\n return value;\n }\n\n const index = args.indexOf(name);\n\n if (index === -1) {\n return fallback;\n }\n\n const value = args[index + 1];\n\n if (!value || value.startsWith(\"--\")) {\n throw new Error(`Missing value for ${name}.`);\n }\n\n return value;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n};\n\nconst parseFontConfig = (value: unknown): ThemeFonts => {\n if (!isRecord(value)) {\n throw new TypeError(\"The font configuration must be a JSON object.\");\n }\n\n const fonts: Partial<Record<FontRole, FontConfig>> = {};\n\n for (const [role, config] of Object.entries(value)) {\n if (!FONT_ROLES.includes(role as FontRole)) {\n throw new TypeError(\n `Unknown font role \"${role}\". Expected heading, body, or accent.`,\n );\n }\n\n if (!isRecord(config)) {\n throw new TypeError(\n `Font configuration for \"${role}\" must be an object.`,\n );\n }\n\n if (typeof config.family !== \"string\" || !config.family.trim()) {\n throw new TypeError(\n `Font configuration for \"${role}\" requires a family.`,\n );\n }\n\n fonts[role as FontRole] = config as unknown as FontConfig;\n }\n\n if (Object.keys(fonts).length === 0) {\n throw new TypeError(\"No fonts configured. Add at least one font role.\");\n }\n\n return fonts;\n};\n\nconst main = async () => {\n const args = process.argv.slice(2);\n\n if (args.includes(\"--help\") || args.includes(\"-h\") || args.length === 0) {\n printHelp();\n return;\n }\n\n if (args[0] !== \"fonts\" || args[1] !== \"download\") {\n printHelp();\n throw new Error(\"Unknown command.\");\n }\n\n const configPath = resolve(\n process.cwd(),\n getOption(args, \"--config\", \"puzzle-fonts.json\"),\n );\n\n const outputDir = resolve(\n process.cwd(),\n getOption(args, \"--out-dir\", \"public/fonts\"),\n );\n\n const rawConfig = JSON.parse(await readFile(configPath, \"utf8\")) as unknown;\n\n const fonts = parseFontConfig(rawConfig);\n\n const result = await downloadGoogleFonts({\n fonts,\n outputDir,\n force: args.includes(\"--force\"),\n subsets: getOption(args, \"--subsets\", \"all\") === \"all\"\n ? undefined\n : getOption(args, \"--subsets\", \"all\").split(\",\").map((subset) => subset.trim()),\n });\n\n if (result.skipped) {\n console.log(\"Google Fonts are already up to date.\");\n console.log(`Location: ${result.cssPath}`);\n return;\n }\n\n console.log(`Downloaded ${result.fontFiles} WOFF2 font files.`);\n\n console.log(`Generated: ${result.cssPath}`);\n};\n\nvoid main().catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n\n process.exitCode = 1;\n});\n"],"mappings":";;;;;AAQA,MAAM,aAAkC;CAAC;CAAW;CAAQ;AAAQ;AAEpE,MAAM,kBAAkB;CACtB,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;CAqBb;AACD;AAEA,MAAM,aAAa,MAAgB,MAAc,aAA6B;CAC5E,MAAM,eAAe,KAAK,MAAM,QAAQ,IAAI,WAAW,GAAG,KAAK,EAAE,CAAC;CAElE,IAAI,cAAc;EAChB,MAAM,QAAQ,aAAa,MAAM,KAAK,SAAS,CAAC;EAEhD,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,qBAAqB,KAAK,EAAE;EAG9C,OAAO;CACT;CAEA,MAAM,QAAQ,KAAK,QAAQ,IAAI;CAE/B,IAAI,UAAU,IACZ,OAAO;CAGT,MAAM,QAAQ,KAAK,QAAQ;CAE3B,IAAI,CAAC,SAAS,MAAM,WAAW,IAAI,GACjC,MAAM,IAAI,MAAM,qBAAqB,KAAK,EAAE;CAG9C,OAAO;AACT;AAEA,MAAM,YAAY,UAAqD;CACrE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,MAAM,mBAAmB,UAA+B;CACtD,IAAI,CAAC,SAAS,KAAK,GACjB,MAAM,IAAI,UAAU,+CAA+C;CAGrE,MAAM,QAA+C,CAAC;CAEtD,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,KAAK,GAAG;EAClD,IAAI,CAAC,WAAW,SAAS,IAAgB,GACvC,MAAM,IAAI,UACR,sBAAsB,KAAK,sCAC7B;EAGF,IAAI,CAAC,SAAS,MAAM,GAClB,MAAM,IAAI,UACR,2BAA2B,KAAK,qBAClC;EAGF,IAAI,OAAO,OAAO,WAAW,YAAY,CAAC,OAAO,OAAO,KAAK,GAC3D,MAAM,IAAI,UACR,2BAA2B,KAAK,qBAClC;EAGF,MAAM,QAAoB;CAC5B;CAEA,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAChC,MAAM,IAAI,UAAU,kDAAkD;CAGxE,OAAO;AACT;AAEA,MAAM,OAAO,YAAY;CACvB,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;CAEjC,IAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,GAAG;EACvE,UAAU;EACV;CACF;CAEA,IAAI,KAAK,OAAO,WAAW,KAAK,OAAO,YAAY;EACjD,UAAU;EACV,MAAM,IAAI,MAAM,kBAAkB;CACpC;CAEA,MAAM,aAAa,QACjB,QAAQ,IAAI,GACZ,UAAU,MAAM,YAAY,mBAAmB,CACjD;CAEA,MAAM,YAAY,QAChB,QAAQ,IAAI,GACZ,UAAU,MAAM,aAAa,cAAc,CAC7C;CAEA,MAAM,YAAY,KAAK,MAAM,MAAM,SAAS,YAAY,MAAM,CAAC;CAE/D,MAAM,QAAQ,gBAAgB,SAAS;CAEvC,MAAM,SAAS,MAAM,oBAAoB;EACvC;EACA;EACA,OAAO,KAAK,SAAS,SAAS;EAC9B,SAAS,UAAU,MAAM,aAAa,KAAK,MAAM,QAC7C,KAAA,IACA,UAAU,MAAM,aAAa,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,WAAW,OAAO,KAAK,CAAC;CAClF,CAAC;CAED,IAAI,OAAO,SAAS;EAClB,QAAQ,IAAI,sCAAsC;EAClD,QAAQ,IAAI,aAAa,OAAO,SAAS;EACzC;CACF;CAEA,QAAQ,IAAI,cAAc,OAAO,UAAU,mBAAmB;CAE9D,QAAQ,IAAI,cAAc,OAAO,SAAS;AAC5C;AAEK,KAAK,CAAC,CAAC,OAAO,UAAmB;CACpC,QAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,KAAK;CAE5D,QAAQ,WAAW;AACrB,CAAC"}
@@ -20,26 +20,26 @@ const DEFAULT_TOKENS = {
20
20
  background: "#FCFCFC",
21
21
  surface: "#FFFFFF",
22
22
  surfaceElevated: "#FFFFFF",
23
- surfaceSunken: "#F7F7F7",
23
+ surfaceSunken: "#F0F0F0",
24
24
  text: "#171A35",
25
25
  textMuted: "#5F6485",
26
26
  textSubtle: "#8B91B3",
27
27
  border: "#E0E5F5",
28
28
  borderStrong: "#C4CBE6",
29
- primary: "#0284C7",
29
+ primary: "#0277B5",
30
30
  primaryForeground: "#FFFFFF",
31
- secondary: "#075985",
31
+ secondary: "#475569",
32
32
  secondaryForeground: "#FFFFFF",
33
- accent: "#06B6D4",
33
+ accent: "#6D28D9",
34
34
  accentForeground: "#FFFFFF",
35
35
  ring: "#A5B4FC",
36
36
  success: "#15803D",
37
37
  successForeground: "#FFFFFF",
38
- warning: "#A16207",
38
+ warning: "#FABC0A",
39
39
  warningForeground: "#FFFFFF",
40
40
  danger: "#B91C1C",
41
41
  dangerForeground: "#FFFFFF",
42
- info: "#0369A1",
42
+ info: "#1D4ED8",
43
43
  infoForeground: "#FFFFFF"
44
44
  },
45
45
  dark: {
@@ -54,10 +54,10 @@ const DEFAULT_TOKENS = {
54
54
  borderStrong: "#4a555d",
55
55
  primary: "#38BDF8",
56
56
  primaryForeground: "#082F49",
57
- secondary: "#7DD3FC",
58
- secondaryForeground: "#082F49",
59
- accent: "#22D3EE",
60
- accentForeground: "#083344",
57
+ secondary: "#94A3B8",
58
+ secondaryForeground: "#0F172A",
59
+ accent: "#C4B5FD",
60
+ accentForeground: "#1E1B4B",
61
61
  ring: "#93c5fd",
62
62
  success: "#4ade80",
63
63
  successForeground: "#052e16",
@@ -65,8 +65,8 @@ const DEFAULT_TOKENS = {
65
65
  warningForeground: "#422006",
66
66
  danger: "#f87171",
67
67
  dangerForeground: "#450a0a",
68
- info: "#38bdf8",
69
- infoForeground: "#082f49"
68
+ info: "#818CF8",
69
+ infoForeground: "#1E1B4B"
70
70
  }
71
71
  },
72
72
  typography: {
@@ -176,7 +176,7 @@ const DEFAULT_TOKENS = {
176
176
  8: "0 25px 50px -12px rgb(0 0 0 / 0.20)",
177
177
  12: "0 35px 60px -15px rgb(0 0 0 / 0.22)",
178
178
  16: "0 45px 80px -20px rgb(0 0 0 / 0.25)",
179
- 24: "0 60px 100px -25px rgb(0 0 0 / 0.30)"
179
+ 24: "0 60px 100px -20px rgb(0 0 0 / 0.40), 0 24px 40px -12px rgb(0 0 0 / 0.25)"
180
180
  },
181
181
  motion: {
182
182
  fast: "120ms",
@@ -255,7 +255,7 @@ const COLOR_THEMES = {
255
255
  ring: "#C4B5FD",
256
256
  success: "#15803D",
257
257
  successForeground: "#FFFFFF",
258
- warning: "#A16207",
258
+ warning: "#FABC0A",
259
259
  warningForeground: "#FFFFFF",
260
260
  danger: "#BE123C",
261
261
  dangerForeground: "#FFFFFF",
@@ -291,7 +291,7 @@ const COLOR_THEMES = {
291
291
  ring: "#6EE7B7",
292
292
  success: "#15803D",
293
293
  successForeground: "#FFFFFF",
294
- warning: "#A16207",
294
+ warning: "#FABC0A",
295
295
  warningForeground: "#FFFFFF",
296
296
  danger: "#B91C1C",
297
297
  dangerForeground: "#FFFFFF",
@@ -327,7 +327,7 @@ const COLOR_THEMES = {
327
327
  ring: "#FDBA74",
328
328
  success: "#15803D",
329
329
  successForeground: "#FFFFFF",
330
- warning: "#A16207",
330
+ warning: "#FABC0A",
331
331
  warningForeground: "#FFFFFF",
332
332
  danger: "#B91C1C",
333
333
  dangerForeground: "#FFFFFF",
@@ -399,7 +399,7 @@ const COLOR_THEMES = {
399
399
  ring: "#5EEAD4",
400
400
  success: "#15803D",
401
401
  successForeground: "#FFFFFF",
402
- warning: "#A16207",
402
+ warning: "#FABC0A",
403
403
  warningForeground: "#FFFFFF",
404
404
  danger: "#B91C1C",
405
405
  dangerForeground: "#FFFFFF",
@@ -435,7 +435,7 @@ const COLOR_THEMES = {
435
435
  ring: "#D8B4FE",
436
436
  success: "#15803D",
437
437
  successForeground: "#FFFFFF",
438
- warning: "#A16207",
438
+ warning: "#FABC0A",
439
439
  warningForeground: "#FFFFFF",
440
440
  danger: "#B91C1C",
441
441
  dangerForeground: "#FFFFFF",
@@ -471,7 +471,7 @@ const COLOR_THEMES = {
471
471
  ring: "#67E8F9",
472
472
  success: "#15803D",
473
473
  successForeground: "#FFFFFF",
474
- warning: "#A16207",
474
+ warning: "#FABC0A",
475
475
  warningForeground: "#FFFFFF",
476
476
  danger: "#B91C1C",
477
477
  dangerForeground: "#FFFFFF",
@@ -507,7 +507,7 @@ const COLOR_THEMES = {
507
507
  ring: "#FDBA74",
508
508
  success: "#15803D",
509
509
  successForeground: "#FFFFFF",
510
- warning: "#A16207",
510
+ warning: "#FABC0A",
511
511
  warningForeground: "#FFFFFF",
512
512
  danger: "#B91C1C",
513
513
  dangerForeground: "#FFFFFF",
@@ -543,7 +543,7 @@ const COLOR_THEMES = {
543
543
  ring: "#A5B4FC",
544
544
  success: "#15803D",
545
545
  successForeground: "#FFFFFF",
546
- warning: "#A16207",
546
+ warning: "#FABC0A",
547
547
  warningForeground: "#FFFFFF",
548
548
  danger: "#B91C1C",
549
549
  dangerForeground: "#FFFFFF",