uix-ui 0.1.1 → 0.3.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.
- package/README.md +18 -0
- package/bin/uix-ui.mjs +100 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,4 +19,22 @@ import { Button } from "@/components/ui/button";
|
|
|
19
19
|
Dark mode: `<html data-theme="dark">`. Options: `--overwrite` (add), `--no-install` (print the install command
|
|
20
20
|
instead of running it), `--registry <url|folder>` (init).
|
|
21
21
|
|
|
22
|
+
UIX Pro (marketing blocks, nine visual styles, 210 more themes): set your key, then add them by name.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export UIX_LICENSE=<your key>
|
|
26
|
+
npx uix-ui@latest add hero pricing-card skins
|
|
27
|
+
npx uix-ui@latest theme acid-prompt
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Your own design system, made at [uixdesign.pagestro.com/generate](https://uixdesign.pagestro.com/generate) and saved to
|
|
31
|
+
your account: pull its theme, tokens (DTCG, in `src/styles/tokens`) and any components, then sync when it changes.
|
|
32
|
+
The API key is on your account page. `uix.json` remembers the system and the version you have.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export UIX_API_KEY=uixm_…
|
|
36
|
+
npx uix-ui@latest pull <id> button card # the id is in your Studio link: /s/<id>
|
|
37
|
+
npx uix-ui@latest sync # pulls again only if you saved a newer version
|
|
38
|
+
```
|
|
39
|
+
|
|
22
40
|
Docs, live components and all 42 themes: https://uixdesign.pagestro.com/ui · Source: https://github.com/mdrakibshekh/uix-ui · MIT
|
package/bin/uix-ui.mjs
CHANGED
|
@@ -5,17 +5,23 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
5
5
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const SITE = 'https://uixdesign.pagestro.com';
|
|
9
|
+
const DEFAULT_REGISTRY = `${SITE}/ui/r`;
|
|
9
10
|
const HELP = `uix-ui: UIX components and themes for React + Tailwind v4
|
|
10
11
|
|
|
11
12
|
npx uix-ui init [--preset <id>] [--registry <url|folder>] set up the theme, utils and components.json
|
|
12
13
|
npx uix-ui add <component...> copy components into your app
|
|
13
14
|
npx uix-ui theme <preset> switch to another UIX preset (uix = default)
|
|
14
15
|
npx uix-ui list components and themes in the registry
|
|
16
|
+
npx uix-ui pull <id> [component...] your saved design system (uixdesign.pagestro.com/s/<id>):
|
|
17
|
+
its theme, tokens and components; remembered in uix.json
|
|
18
|
+
npx uix-ui sync pull again if the system changed since the last pull
|
|
15
19
|
|
|
16
20
|
--overwrite replace files that already exist (add)
|
|
17
21
|
--no-install don't run the package manager, just print the command
|
|
18
|
-
|
|
22
|
+
pull, sync: set UIX_API_KEY (or --key) to the API key from your account page (uixdesign.pagestro.com/account).
|
|
23
|
+
UIX Pro: set UIX_LICENSE=<your key>, then add Pro items by name (hero, pricing-card, skins…) or switch to a
|
|
24
|
+
Pro theme (npx uix-ui theme acid-prompt). \`list\` shows them all.`;
|
|
19
25
|
|
|
20
26
|
const cwd = process.cwd();
|
|
21
27
|
const argv = process.argv.slice(2);
|
|
@@ -26,7 +32,7 @@ const flag = (n, bool = false) => {
|
|
|
26
32
|
argv.splice(i, takes ? 2 : 1);
|
|
27
33
|
return takes ? v : true;
|
|
28
34
|
};
|
|
29
|
-
const opts = { preset: flag('preset'), registry: flag('registry'), overwrite: flag('overwrite', true), noInstall: flag('no-install', true) };
|
|
35
|
+
const opts = { preset: flag('preset'), registry: flag('registry'), overwrite: flag('overwrite', true), noInstall: flag('no-install', true), key: flag('key'), site: flag('site') };
|
|
30
36
|
const [cmd, ...names] = argv;
|
|
31
37
|
|
|
32
38
|
const say = (s) => console.log(s);
|
|
@@ -54,28 +60,57 @@ function source(cfg, name) {
|
|
|
54
60
|
return { ns, item, url: url.replace('{name}', item), headers: Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, env(v)])) };
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
const PRICING = 'https://uixdesign.pagestro.com/pricing';
|
|
64
|
+
let pin = ''; // pull: `?v=<version>`, so the site answers with that version, not a copy cached a few seconds ago
|
|
65
|
+
const missing = (src) => Object.assign(new Error(`"${src.item}" is not in the registry (${src.url}).`), { missing: true });
|
|
66
|
+
|
|
57
67
|
async function load(src) {
|
|
58
68
|
if (!/^https?:/.test(src.url)) {
|
|
59
|
-
if (!existsSync(src.url))
|
|
69
|
+
if (!existsSync(src.url)) throw missing(src);
|
|
60
70
|
return JSON.parse(readFileSync(src.url, 'utf8'));
|
|
61
71
|
}
|
|
62
|
-
const res = await fetch(src.url, { headers: src.headers });
|
|
63
|
-
if (res.status === 401
|
|
72
|
+
const res = await fetch(src.url + pin, { headers: src.headers });
|
|
73
|
+
if (res.status === 401) die(process.env.UIX_LICENSE ? `UIX_LICENSE is set, but that key wasn't found. Check it in your UIX account (${PRICING}).` : `${src.item.startsWith('theme-') ? `The "${src.item.slice(6)}" theme` : `"${src.item}"`} is part of UIX Pro. Set UIX_LICENSE to your key (${PRICING}).`);
|
|
74
|
+
if (res.status === 403) die(`"${src.item}" needs an active UIX Pro licence; this key's plan has ended (${PRICING}).`);
|
|
75
|
+
if (res.status === 404) throw missing(src);
|
|
64
76
|
if (!res.ok) die(`"${src.item}" could not be loaded (${res.status} from ${src.url}).`);
|
|
65
77
|
return res.json();
|
|
66
78
|
}
|
|
67
79
|
|
|
80
|
+
/** The Pro index (names only, no key needed), or null when there is no Pro registry. */
|
|
81
|
+
let proIndex;
|
|
82
|
+
async function loadProIndex(cfg) {
|
|
83
|
+
if (proIndex !== undefined) return proIndex;
|
|
84
|
+
if (!cfg.registries?.['@uix-pro']) return (proIndex = null);
|
|
85
|
+
const ps = source(cfg, '@uix-pro/x');
|
|
86
|
+
return (proIndex = await load({ ...ps, item: 'registry.json', url: ps.url.replace(/\/x\.json$/, '/registry.json'), headers: {} }).catch(() => null));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A bare name that the free registry doesn't have is looked up in Pro (`hero` → `@uix-pro/hero`). */
|
|
90
|
+
async function loadAny(cfg, src) {
|
|
91
|
+
try { return { src, it: await load(src) }; } catch (e) {
|
|
92
|
+
if (!e.missing || src.ns !== '@uix') throw e;
|
|
93
|
+
const index = await loadProIndex(cfg);
|
|
94
|
+
if (!index?.items.some(i => i.name === src.item)) throw Object.assign(new Error(`"${src.item.replace(/^theme-/, '')}" is not a uix-ui component or theme. See the list: npx uix-ui list`), { missing: true });
|
|
95
|
+
const pro = source(cfg, `@uix-pro/${src.item}`);
|
|
96
|
+
return { src: pro, it: await load(pro) };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
68
100
|
/** Loads the items and everything they depend on, dependencies first. */
|
|
69
101
|
async function resolveAll(cfg, list) {
|
|
70
102
|
const seen = new Map();
|
|
71
103
|
const visit = async (name) => {
|
|
72
|
-
|
|
104
|
+
let src = source(cfg, name);
|
|
73
105
|
const key = `${src.ns}/${src.item}`;
|
|
74
106
|
if (seen.has(key)) return;
|
|
75
107
|
seen.set(key, null);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
const bare = !name.startsWith('@');
|
|
109
|
+
const got = bare ? await loadAny(cfg, src) : { src, it: await load(src) };
|
|
110
|
+
const it = got.it;
|
|
111
|
+
if (got.src !== src) { seen.delete(key); src = got.src; seen.set(`${src.ns}/${src.item}`, null); }
|
|
112
|
+
for (const d of it.registryDependencies ?? []) await visit(d.startsWith('@') ? d : `${src.ns}/${/^https?:/.test(d) ? d.split('/').pop().replace(/\.json$/, '') : d}`);
|
|
113
|
+
seen.delete(`${src.ns}/${src.item}`); seen.set(`${src.ns}/${src.item}`, it);
|
|
79
114
|
};
|
|
80
115
|
for (const n of list) await visit(n);
|
|
81
116
|
return [...seen.values()];
|
|
@@ -102,6 +137,20 @@ function place(cfg, items, overwrite) {
|
|
|
102
137
|
write(file, code);
|
|
103
138
|
say(` wrote ${shown}`);
|
|
104
139
|
}
|
|
140
|
+
for (const it of items) for (const f of it.files) if (/\.css$/.test(f.path) && !/(^|\/)uix\.css$/.test(f.path)) importCss(cfg, target(cfg, f));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Adds `@import "<file>";` to the app's main CSS, after the theme import (visual styles, backdrops). */
|
|
144
|
+
function importCss(cfg, file) {
|
|
145
|
+
const cssFile = join(cwd, cfg.tailwind?.css ?? 'src/index.css');
|
|
146
|
+
if (!existsSync(cssFile)) return;
|
|
147
|
+
let src = readFileSync(cssFile, 'utf8');
|
|
148
|
+
const imp = `@import "${rel(cssFile, file)}";`;
|
|
149
|
+
if (src.includes(imp)) return;
|
|
150
|
+
const last = [...src.matchAll(/@import [^;\n]+;?/g)].pop(); // after the theme and any earlier style sheets
|
|
151
|
+
src = last ? src.slice(0, last.index + last[0].length) + `\n${imp}` + src.slice(last.index + last[0].length) : `${imp}\n${src}`;
|
|
152
|
+
write(cssFile, src);
|
|
153
|
+
say(` updated ${relative(cwd, cssFile)}`);
|
|
105
154
|
}
|
|
106
155
|
|
|
107
156
|
function install(items) {
|
|
@@ -156,7 +205,8 @@ async function init() {
|
|
|
156
205
|
cfg.registries = {
|
|
157
206
|
...cfg.registries,
|
|
158
207
|
'@uix': `${registry}/{name}.json`,
|
|
159
|
-
|
|
208
|
+
// a saved system's registry (/r/<id>) already has the owner's Pro items
|
|
209
|
+
...(/\/r$/.test(registry) && { '@uix-pro': { url: `${registry.replace(/\/r$/, '/r/pro')}/{name}.json`, headers: { Authorization: 'Bearer ${UIX_LICENSE}' } } }),
|
|
160
210
|
};
|
|
161
211
|
write(join(cwd, 'components.json'), JSON.stringify(cfg, null, 2) + '\n');
|
|
162
212
|
say(' wrote components.json');
|
|
@@ -173,12 +223,42 @@ async function init() {
|
|
|
173
223
|
const items = await resolveAll(cfg, ['utils']);
|
|
174
224
|
place(cfg, items, false);
|
|
175
225
|
install(items);
|
|
176
|
-
|
|
226
|
+
return cfg;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Your saved system, through the account API (the key proves it's yours): { id, name, version, … }. */
|
|
230
|
+
async function system(site, id) {
|
|
231
|
+
const key = opts.key ?? process.env.UIX_API_KEY;
|
|
232
|
+
if (!key) die(`set UIX_API_KEY (or --key) to the API key from ${site}/account.`);
|
|
233
|
+
const res = await fetch(`${site}/api/me/systems/${id}`, { headers: { Authorization: `Bearer ${key}` } }).catch(e => die(`could not reach ${site} (${e.cause?.code ?? e.message}).`));
|
|
234
|
+
if (res.status === 401) die(`that API key wasn't accepted. Make a new one at ${site}/account.`);
|
|
235
|
+
if (res.status === 404) die(`there's no design system "${id}" in your account (${site}/account#systems).`);
|
|
236
|
+
if (!res.ok) die(`could not load "${id}" (${res.status} from ${site}).`);
|
|
237
|
+
return (await res.json()).system;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** pull: the system's theme, its DTCG tokens and any named components; sync: the same, only when the version moved. */
|
|
241
|
+
async function pull(id, names) {
|
|
242
|
+
const saved = existsSync(join(cwd, 'uix.json')) ? readJson('uix.json') : {};
|
|
243
|
+
id ??= saved.system;
|
|
244
|
+
if (!id) die(cmd === 'sync' ? 'nothing to sync yet. Run `npx uix-ui pull <id>` first (the id is in your Studio link, /s/<id>).' : 'name the design system, e.g. `npx uix-ui pull k3x9d0a2bq` (the id is in your Studio link, /s/<id>).');
|
|
245
|
+
const site = (opts.site ?? saved.site ?? SITE).replace(/\/$/, '');
|
|
246
|
+
const sys = await system(site, id);
|
|
247
|
+
if (cmd === 'sync' && sys.version === saved.version && id === saved.system) return say(`Up to date: "${sys.name}", version ${sys.version}.`);
|
|
248
|
+
pin = `?v=${sys.version}`;
|
|
249
|
+
opts.registry = `${site}/r/${id}`;
|
|
250
|
+
const cfg = await init();
|
|
251
|
+
const tokens = await load({ item: 'tokens', url: `${site}/s/${id}.json`, headers: {} });
|
|
252
|
+
for (const [name, t] of Object.entries(tokens)) { write(join(srcRoot, 'styles', name), JSON.stringify(t, null, 2) + '\n'); say(` wrote ${relative(cwd, join(srcRoot, 'styles', name))}`); }
|
|
253
|
+
if (names.length) { const items = await resolveAll(cfg, names); place(cfg, items, !!opts.overwrite); install(items); }
|
|
254
|
+
write(join(cwd, 'uix.json'), JSON.stringify({ system: id, name: sys.name, version: sys.version, site: site === SITE ? undefined : site }, null, 2) + '\n');
|
|
255
|
+
say(`\n"${sys.name}", version ${sys.version}${saved.version && saved.system === id ? ` (was ${saved.version})` : ''}. Later changes: npx uix-ui sync`);
|
|
177
256
|
}
|
|
178
257
|
|
|
179
258
|
async function main() {
|
|
180
259
|
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') return say(HELP);
|
|
181
|
-
if (cmd === 'init') return init();
|
|
260
|
+
if (cmd === 'init') return init().then(() => say(`\nDone. Next: npx uix-ui add button card input\nDark mode: <html data-theme="dark">. Other presets: npx uix-ui theme <preset>.`));
|
|
261
|
+
if (cmd === 'pull' || cmd === 'sync') return pull(cmd === 'pull' ? names[0] : undefined, names.slice(1));
|
|
182
262
|
const cfg = config();
|
|
183
263
|
if (cmd === 'add') {
|
|
184
264
|
if (!names.length) die('name at least one component, e.g. `npx uix-ui add button`.');
|
|
@@ -189,10 +269,15 @@ async function main() {
|
|
|
189
269
|
if (cmd === 'theme') return applyTheme(cfg, names[0] ?? opts.preset);
|
|
190
270
|
if (cmd === 'list') {
|
|
191
271
|
const src = source(cfg, 'x');
|
|
192
|
-
const index = await load({ ...src, item: 'registry.json', url: src.url.replace(/\/r\/x\.json$/, '/registry.json') });
|
|
272
|
+
const index = await load({ ...src, item: 'registry.json', url: src.url.replace(/\/r\/x\.json$/, '/registry.json').replace(/\/x\.json$/, '/registry.json') });
|
|
193
273
|
const comps = index.items.filter(i => i.type === 'registry:ui' && i.name !== 'icons').map(i => i.name);
|
|
194
274
|
const themes = index.items.filter(i => i.name.startsWith('theme')).map(i => i.name === 'theme' ? 'uix' : i.name.slice(6));
|
|
195
|
-
|
|
275
|
+
say(`Components (${comps.length}): ${comps.join(', ')}\n\nThemes (${themes.length}): ${themes.join(', ')}`);
|
|
276
|
+
const pro = await loadProIndex(cfg);
|
|
277
|
+
if (!pro) return;
|
|
278
|
+
const blocks = pro.items.filter(i => !i.name.startsWith('theme-')).map(i => i.name);
|
|
279
|
+
const proThemes = pro.items.filter(i => i.name.startsWith('theme-')).map(i => i.name.slice(6));
|
|
280
|
+
return say(`\nUIX Pro, with UIX_LICENSE (${PRICING}):\n Blocks and styles (${blocks.length}): ${blocks.join(', ')}\n Themes (${proThemes.length}): ${proThemes.join(', ')}`);
|
|
196
281
|
}
|
|
197
282
|
die(`unknown command "${cmd}".\n\n${HELP}`);
|
|
198
283
|
}
|