unipo-ui 0.0.0-stage → 0.1.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 (99) hide show
  1. package/cli/index.mjs +352 -0
  2. package/dist/chunk-2FW3QITF.js +3457 -0
  3. package/dist/chunk-2FW3QITF.js.map +1 -0
  4. package/dist/chunk-SVRTAYZW.cjs +3628 -0
  5. package/dist/chunk-SVRTAYZW.cjs.map +1 -0
  6. package/dist/content-f6eK74xx.d.cts +1040 -0
  7. package/dist/content-f6eK74xx.d.ts +1040 -0
  8. package/dist/css.d.ts +2 -0
  9. package/dist/index.cjs +801 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.cts +726 -0
  12. package/dist/index.d.ts +726 -0
  13. package/dist/index.js +174 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/registry.json +1904 -0
  16. package/dist/scss/_base.scss +148 -0
  17. package/dist/scss/_config.scss +3 -0
  18. package/dist/scss/_mixins.scss +54 -0
  19. package/dist/scss/_tokens.scss +276 -0
  20. package/dist/scss/components/_actions.scss +403 -0
  21. package/dist/scss/components/_akademik.scss +669 -0
  22. package/dist/scss/components/_aplikasi.scss +872 -0
  23. package/dist/scss/components/_cbt.scss +528 -0
  24. package/dist/scss/components/_charts.scss +964 -0
  25. package/dist/scss/components/_code.scss +245 -0
  26. package/dist/scss/components/_content.scss +656 -0
  27. package/dist/scss/components/_data-table.scss +291 -0
  28. package/dist/scss/components/_display.scss +1157 -0
  29. package/dist/scss/components/_faq.scss +210 -0
  30. package/dist/scss/components/_filter.scss +306 -0
  31. package/dist/scss/components/_forms.scss +842 -0
  32. package/dist/scss/components/_identity.scss +65 -0
  33. package/dist/scss/components/_kurikulum.scss +1118 -0
  34. package/dist/scss/components/_layout.scss +453 -0
  35. package/dist/scss/components/_lms.scss +315 -0
  36. package/dist/scss/components/_navbar.scss +620 -0
  37. package/dist/scss/components/_overlays.scss +449 -0
  38. package/dist/scss/components/_schedule.scss +560 -0
  39. package/dist/scss/components/_siakad.scss +14 -0
  40. package/dist/scss/components/_status.scss +279 -0
  41. package/dist/scss/components/_wizard.scss +683 -0
  42. package/dist/scss/index.scss +27 -0
  43. package/dist/siakad.cjs +7506 -0
  44. package/dist/siakad.cjs.map +1 -0
  45. package/dist/siakad.d.cts +2777 -0
  46. package/dist/siakad.d.ts +2777 -0
  47. package/dist/siakad.js +7206 -0
  48. package/dist/siakad.js.map +1 -0
  49. package/dist/tema.cjs +268 -0
  50. package/dist/tema.cjs.map +1 -0
  51. package/dist/tema.d.cts +80 -0
  52. package/dist/tema.d.ts +80 -0
  53. package/dist/tema.js +256 -0
  54. package/dist/tema.js.map +1 -0
  55. package/dist/ui-unipo.css +12711 -0
  56. package/dist/ui-unipo.min.css +1 -0
  57. package/package.json +123 -4
  58. package/src/components/actions.tsx +152 -0
  59. package/src/components/calendar.tsx +316 -0
  60. package/src/components/charts.tsx +924 -0
  61. package/src/components/code.tsx +203 -0
  62. package/src/components/content.tsx +425 -0
  63. package/src/components/data-table.tsx +625 -0
  64. package/src/components/display.tsx +462 -0
  65. package/src/components/faq.tsx +166 -0
  66. package/src/components/filter.tsx +380 -0
  67. package/src/components/forms.tsx +369 -0
  68. package/src/components/identity.tsx +149 -0
  69. package/src/components/layout.tsx +349 -0
  70. package/src/components/navbar.tsx +448 -0
  71. package/src/components/overlays.tsx +295 -0
  72. package/src/components/schedule.tsx +404 -0
  73. package/src/components/select.tsx +275 -0
  74. package/src/components/status.tsx +241 -0
  75. package/src/components/wizard.tsx +228 -0
  76. package/src/index.ts +22 -0
  77. package/src/lib/cn.ts +24 -0
  78. package/src/lib/format.ts +95 -0
  79. package/src/siakad/akademik.tsx +880 -0
  80. package/src/siakad/aplikasi.tsx +1718 -0
  81. package/src/siakad/cari.tsx +398 -0
  82. package/src/siakad/cbt.tsx +612 -0
  83. package/src/siakad/dasbor.tsx +1469 -0
  84. package/src/siakad/filter-presets.tsx +223 -0
  85. package/src/siakad/index.ts +20 -0
  86. package/src/siakad/kalender.tsx +236 -0
  87. package/src/siakad/kelengkapan.tsx +454 -0
  88. package/src/siakad/kurikulum.tsx +1399 -0
  89. package/src/siakad/lms.tsx +534 -0
  90. package/src/siakad/log.tsx +694 -0
  91. package/src/siakad/navigasi.tsx +127 -0
  92. package/src/siakad/negara.tsx +44 -0
  93. package/src/siakad/pmb.tsx +637 -0
  94. package/src/siakad/portal.tsx +217 -0
  95. package/src/siakad/provider.tsx +158 -0
  96. package/src/siakad/status-layanan.tsx +87 -0
  97. package/src/siakad/tabel.tsx +701 -0
  98. package/src/tema/index.ts +320 -0
  99. package/README.md +0 -3
package/cli/index.mjs ADDED
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+ // CLI unipo-ui: pasang pustaka, atur tema, dan salin sumber komponen ke proyek.
3
+ // npx unipo-ui init pasang paket + berkas tema + impor CSS
4
+ // npx unipo-ui add FilterBar salin sumber komponen (beserta dependensinya) untuk diubah sendiri
5
+ // npx unipo-ui list [kata] daftar komponen
6
+ // npx unipo-ui tema --primary "#0f766e"
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
8
+ import { dirname, join, relative, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { execSync } from 'node:child_process';
11
+ import { createInterface } from 'node:readline/promises';
12
+
13
+ const here = dirname(fileURLToPath(import.meta.url));
14
+ const pkgRoot = resolve(here, '..');
15
+ const pkg = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'));
16
+ const cwd = process.cwd();
17
+
18
+ // ---------------------------------------------------------------- argumen
19
+ const argv = process.argv.slice(2);
20
+ const cmd = argv[0] && !argv[0].startsWith('-') ? argv[0] : 'help';
21
+ const flags = {};
22
+ const rest = [];
23
+ for (let i = cmd === 'help' ? 0 : 1; i < argv.length; i++) {
24
+ const a = argv[i];
25
+ if (a.startsWith('--')) {
26
+ const [k, v] = a.slice(2).split('=');
27
+ if (v !== undefined) flags[k] = v;
28
+ else if (argv[i + 1] && !argv[i + 1].startsWith('-')) flags[k] = argv[++i];
29
+ else flags[k] = true;
30
+ } else if (a === '-y') flags.yes = true;
31
+ else rest.push(a);
32
+ }
33
+
34
+ const c = process.stdout.isTTY && !process.env.NO_COLOR ? { b: (s) => `\x1b[1m${s}\x1b[0m`, g: (s) => `\x1b[32m${s}\x1b[0m`, y: (s) => `\x1b[33m${s}\x1b[0m`, r: (s) => `\x1b[31m${s}\x1b[0m`, d: (s) => `\x1b[2m${s}\x1b[0m`, c: (s) => `\x1b[36m${s}\x1b[0m` } : Object.fromEntries(['b', 'g', 'y', 'r', 'd', 'c'].map((k) => [k, (s) => s]));
35
+ const ok = (s) => console.log(`${c.g('✔')} ${s}`);
36
+ const warn = (s) => console.log(`${c.y('!')} ${s}`);
37
+ const fail = (s) => {
38
+ console.error(`${c.r('✖')} ${s}`);
39
+ process.exit(1);
40
+ };
41
+
42
+ // ---------------------------------------------------------------- proyek
43
+ function readJson(p) {
44
+ try {
45
+ return JSON.parse(readFileSync(p, 'utf8'));
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+ const project = readJson(join(cwd, 'package.json'));
51
+ const deps = { ...(project?.dependencies ?? {}), ...(project?.devDependencies ?? {}) };
52
+
53
+ function packageManager() {
54
+ if (flags.pm) return flags.pm;
55
+ if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
56
+ if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
57
+ if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock'))) return 'bun';
58
+ return 'npm';
59
+ }
60
+ function framework() {
61
+ if (deps.next) return 'next';
62
+ if (deps.nuxt) return 'nuxt';
63
+ if (deps['react-native'] || deps.expo) return 'react-native';
64
+ if (deps.vue) return 'vue';
65
+ if (deps.react) return 'react';
66
+ if (existsSync(join(cwd, 'artisan'))) return 'laravel';
67
+ return 'html';
68
+ }
69
+ const FW_LABEL = { next: 'Next.js', nuxt: 'Nuxt', 'react-native': 'React Native', vue: 'Vue', react: 'React (Vite/CRA)', laravel: 'Laravel/Blade', html: 'HTML/PHP biasa' };
70
+
71
+ function install(list, dev = false) {
72
+ const nameOf = (p) => (p.startsWith('@') ? '@' + p.slice(1).split('@')[0] : p.split('@')[0]);
73
+ const todo = list.filter((p) => !deps[nameOf(p)]);
74
+ if (!todo.length) return;
75
+ const pm = packageManager();
76
+ const verb = pm === 'npm' ? 'install' : 'add';
77
+ const cmdline = `${pm} ${verb} ${dev ? (pm === 'npm' ? '--save-dev ' : '-D ') : ''}${todo.join(' ')}`;
78
+ if (flags['no-install']) {
79
+ warn(`Lewati pemasangan. Jalankan sendiri: ${c.c(cmdline)}`);
80
+ return;
81
+ }
82
+ console.log(c.d(`$ ${cmdline}`));
83
+ try {
84
+ execSync(cmdline, { stdio: 'inherit', cwd });
85
+ } catch {
86
+ fail(`Gagal memasang paket. Jalankan sendiri: ${cmdline}`);
87
+ }
88
+ }
89
+
90
+ async function ask(q, def) {
91
+ if (flags.yes || !process.stdin.isTTY) return def;
92
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
93
+ const a = (await rl.question(`${c.b('?')} ${q} ${def ? c.d(`(${def}) `) : ''}`)).trim();
94
+ rl.close();
95
+ return a || def;
96
+ }
97
+ const HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
98
+
99
+ // ---------------------------------------------------------------- konfigurasi unipo.json
100
+ const CONFIG = join(cwd, 'unipo.json');
101
+ function config() {
102
+ return readJson(CONFIG) ?? {};
103
+ }
104
+ function defaultDir(fw) {
105
+ if (fw === 'next') return existsSync(join(cwd, 'src')) ? 'src/components/unipo' : 'components/unipo';
106
+ return existsSync(join(cwd, 'src')) ? 'src/components/unipo' : 'components/unipo';
107
+ }
108
+ // Generator tema lengkap (dist/tema.js, sama dengan pembuat tema di api.unipo.ac.id/docs/tema).
109
+ const temaLib = await import('../dist/tema.js').catch(() => null);
110
+ /** Opsi tema dari flag CLI: --dari "<query atau URL tema.css>" lalu flag satuan (--netral, --font, --teks, ...). */
111
+ function opsiTemaDariFlag(dasar) {
112
+ const opsi = { ...dasar };
113
+ if (typeof flags.dari === 'string') Object.assign(opsi, Object.fromEntries(new URLSearchParams(flags.dari.replace(/^[^?]*\?/, '')).entries()));
114
+ for (const k of ['primary', 'secondary', 'primaryGelap', 'success', 'warning', 'danger', 'info', 'netral', 'latar', 'latarGelap', 'radius', 'font', 'fontJudul', 'fontMono', 'teks', 'bayangan', 'gerak', 'muatFont']) {
115
+ if (flags[k] !== undefined) opsi[k] = flags[k];
116
+ }
117
+ return opsi;
118
+ }
119
+ function themeCss(opsi) {
120
+ if (temaLib) return temaLib.buatTema(opsi);
121
+ const { primary, secondary, radius } = opsi;
122
+ return `/* Tema UNIPO untuk proyek ini. */
123
+ :root {
124
+ --u-primary: ${primary};
125
+ --u-secondary: ${secondary};
126
+ --u-radius: ${radius}px;
127
+ }
128
+ `;
129
+ }
130
+ function entryFile() {
131
+ const cands = ['app/layout.tsx', 'src/app/layout.tsx', 'app/layout.jsx', 'src/app/layout.jsx', 'pages/_app.tsx', 'src/pages/_app.tsx', 'src/main.tsx', 'src/main.jsx', 'src/main.ts', 'src/main.js', 'app.vue', 'src/App.vue'];
132
+ return cands.find((f) => existsSync(join(cwd, f)));
133
+ }
134
+
135
+ // ---------------------------------------------------------------- perintah: init
136
+ async function init() {
137
+ if (!project) fail('package.json tidak ditemukan. Jalankan di folder akar proyek (atau `npm init -y` dulu).');
138
+ const fw = framework();
139
+ console.log(`\n${c.b('UNIPO UI')} ${c.d('v' + pkg.version)} · proyek ${c.b(project.name ?? relative(process.env.HOME ?? '', cwd))} · ${FW_LABEL[fw]} · ${packageManager()}\n`);
140
+
141
+ const cfg = config();
142
+ const primary = flags.primary ?? (await ask('Warna primer', cfg.primary ?? '#244ce6'));
143
+ const secondary = flags.secondary ?? (await ask('Warna sekunder', cfg.secondary ?? '#f2b42a'));
144
+ const radius = Number(flags.radius ?? (await ask('Radius sudut (px)', String(cfg.radius ?? 10))));
145
+ if (!HEX.test(primary) || !HEX.test(secondary)) fail('Warna harus berformat hex, mis. #244ce6.');
146
+ const sdk = flags.sdk ?? (fw === 'react' || fw === 'next' ? (await ask('Pasang juga SDK SIAKAD (unipo-siakad-sdk)? y/n', 'y')).toLowerCase().startsWith('y') : false);
147
+
148
+ if (fw === 'react-native') {
149
+ install(['unipo-ui-tokens']);
150
+ ok('Terpasang unipo-ui-tokens. Di React Native pakai token: import { colors } from "unipo-ui-tokens"; colors("light").primary');
151
+ writeFileSync(CONFIG, JSON.stringify({ framework: fw, primary, secondary, radius }, null, 2) + '\n');
152
+ return;
153
+ }
154
+
155
+ const font = !flags['no-font'] && fw !== 'laravel' && fw !== 'html';
156
+ install(['unipo-ui', ...(sdk ? ['unipo-siakad-sdk'] : []), ...(font ? ['@fontsource-variable/plus-jakarta-sans'] : [])]);
157
+
158
+ const styleDir = fw === 'next' ? (existsSync(join(cwd, 'src/app')) ? 'src/app' : existsSync(join(cwd, 'app')) ? 'app' : 'styles') : existsSync(join(cwd, 'src')) ? 'src' : fw === 'laravel' ? 'resources/css' : '.';
159
+ const themePath = join(styleDir, 'unipo-tema.css');
160
+ mkdirSync(join(cwd, styleDir), { recursive: true });
161
+ writeFileSync(join(cwd, themePath), themeCss({ primary, secondary, radius }));
162
+ ok(`Tema ditulis ke ${c.b(themePath)}`);
163
+
164
+ const entry = entryFile();
165
+ const isReactish = fw === 'next' || fw === 'react' || fw === 'vue' || fw === 'nuxt';
166
+ if (entry && isReactish && !flags['no-edit']) {
167
+ const p = join(cwd, entry);
168
+ let src = readFileSync(p, 'utf8');
169
+ if (!src.includes('unipo-ui/css')) {
170
+ const relTheme = './' + relative(dirname(p), join(cwd, themePath)).split('\\').join('/');
171
+ const lines = entry.endsWith('.vue') ? null : `${font ? "import '@fontsource-variable/plus-jakarta-sans';\n" : ''}import 'unipo-ui/css';\nimport '${relTheme.startsWith('./..') ? relTheme.slice(2) : relTheme}';\n`;
172
+ if (lines) {
173
+ const m = src.match(/^(['"]use client['"];?\s*\n)/);
174
+ src = m ? m[1] + lines + src.slice(m[1].length) : lines + src;
175
+ writeFileSync(p, src);
176
+ ok(`CSS diimpor di ${c.b(entry)}`);
177
+ } else warn(`Tambahkan di ${entry} (blok <script>): import 'unipo-ui/css'; import '${relTheme}';`);
178
+ }
179
+ } else if (fw === 'laravel' || fw === 'html') {
180
+ warn('Salin CSS jadi: node_modules/unipo-ui/dist/ui-unipo.min.css ke folder publik, lalu muat setelahnya unipo-tema.css.');
181
+ } else {
182
+ warn(`Impor sekali di berkas awal aplikasi: import 'unipo-ui/css'; import './${themePath}';`);
183
+ }
184
+
185
+ if (sdk && (fw === 'next' || fw === 'react')) {
186
+ const dir = join(cwd, defaultDir(fw), '..');
187
+ const prov = join(dir, 'unipo-provider.tsx');
188
+ if (!existsSync(prov)) {
189
+ mkdirSync(dir, { recursive: true });
190
+ writeFileSync(
191
+ prov,
192
+ `'use client';
193
+ import { Siakad } from 'unipo-siakad-sdk';
194
+ import { UnipoProvider } from 'unipo-ui/siakad';
195
+ import { useMemo } from 'react';
196
+
197
+ /**
198
+ * Bungkus aplikasi dengan komponen ini. Browser memanggil BFF/proksi milik aplikasi sendiri,
199
+ * bukan API SIAKAD langsung: client_secret hanya disimpan di server.
200
+ */
201
+ export function Providers({ children, accessToken }: { children: React.ReactNode; accessToken?: string }) {
202
+ const siakad = useMemo(() => new Siakad({ baseUrl: '/bff/api', accessToken }), [accessToken]);
203
+ return <UnipoProvider siakad={siakad}>{children}</UnipoProvider>;
204
+ }
205
+ `,
206
+ );
207
+ ok(`Provider SDK ditulis ke ${c.b(relative(cwd, prov))}`);
208
+ }
209
+ }
210
+
211
+ writeFileSync(CONFIG, JSON.stringify({ $schema: 'https://api.unipo.ac.id/docs/panduan/ui', framework: fw, dir: cfg.dir ?? defaultDir(fw), theme: themePath, primary, secondary, radius, sdk }, null, 2) + '\n');
212
+ ok(`Konfigurasi disimpan di ${c.b('unipo.json')}`);
213
+ console.log(`\nSelesai. Contoh pakai:\n ${c.c("import { Button, FilterBar } from 'unipo-ui';")}${sdk ? `\n ${c.c("import { CariMahasiswa } from 'unipo-ui/siakad';")}` : ''}\nDaftar komponen: ${c.c('npx unipo-ui list')} · Salin sumber untuk diubah: ${c.c('npx unipo-ui add <Komponen>')}\n`);
214
+ }
215
+
216
+ // ---------------------------------------------------------------- registry
217
+ function registry() {
218
+ const r = readJson(join(pkgRoot, 'dist/registry.json'));
219
+ if (!r) fail('registry.json tidak ada di paket. Pasang ulang unipo-ui.');
220
+ return r;
221
+ }
222
+ const GROUP = {
223
+ 'components/actions.tsx': 'Aksi',
224
+ 'components/forms.tsx': 'Formulir',
225
+ 'components/select.tsx': 'Pilihan',
226
+ 'components/overlays.tsx': 'Overlay',
227
+ 'components/display.tsx': 'Tampilan data',
228
+ 'components/calendar.tsx': 'Tanggal & berkas',
229
+ 'components/code.tsx': 'Blok kode',
230
+ 'components/layout.tsx': 'Tata letak',
231
+ 'components/identity.tsx': 'Identitas',
232
+ 'components/filter.tsx': 'Saringan',
233
+ 'lib/cn.ts': 'Utilitas',
234
+ 'lib/format.ts': 'Utilitas',
235
+ };
236
+ const groupOf = (file) => GROUP[file] ?? (file.startsWith('siakad/') ? 'Terhubung SDK (unipo-ui/siakad)' : file.startsWith('widgets/') ? 'Widget fitur' : 'Lainnya');
237
+
238
+ function list() {
239
+ const r = registry();
240
+ const q = (rest[0] ?? '').toLowerCase();
241
+ const byGroup = {};
242
+ for (const [file, info] of Object.entries(r.files)) {
243
+ const comps = info.exports.filter((e) => /^[A-Z]/.test(e) && !/Props$|Option$|Value(s)?$|Field$|^[A-Z][A-Z_]+$/.test(e));
244
+ const hooks = info.exports.filter((e) => /^use[A-Z]/.test(e));
245
+ const all = [...comps, ...hooks].filter((e) => !q || e.toLowerCase().includes(q) || file.includes(q));
246
+ if (!all.length) continue;
247
+ (byGroup[groupOf(file)] ??= []).push([file, all]);
248
+ }
249
+ for (const [g, items] of Object.entries(byGroup)) {
250
+ console.log(`\n${c.b(g)}`);
251
+ for (const [file, all] of items) console.log(` ${c.d(file.replace(/\.tsx?$/, '').padEnd(24))} ${all.join(', ')}`);
252
+ }
253
+ console.log(`\nImpor: ${c.c("import { Nama } from 'unipo-ui'")} · komponen SDK: ${c.c("from 'unipo-ui/siakad'")}\n`);
254
+ }
255
+
256
+ // ---------------------------------------------------------------- perintah: add
257
+ function add() {
258
+ if (!rest.length) fail('Sebutkan komponen, mis. `npx unipo-ui add FilterBar CariMahasiswa`.');
259
+ if (!project) fail('package.json tidak ditemukan. Jalankan di folder akar proyek.');
260
+ const r = registry();
261
+ const lower = Object.fromEntries(Object.keys(r.exports).map((k) => [k.toLowerCase(), k]));
262
+ const want = new Set();
263
+ for (const name of rest) {
264
+ const exact = r.exports[name] ?? r.exports[lower[name.toLowerCase()]];
265
+ const byFile = Object.keys(r.files).find((f) => f.replace(/\.tsx?$/, '') === name || f.replace(/\.tsx?$/, '').endsWith('/' + name));
266
+ const file = exact ?? byFile;
267
+ if (!file) fail(`Komponen "${name}" tidak dikenal. Lihat \`npx unipo-ui list ${name}\`.`);
268
+ want.add(file);
269
+ }
270
+ // tutup dependensi lokal
271
+ const queue = [...want];
272
+ const all = new Set();
273
+ while (queue.length) {
274
+ const f = queue.shift();
275
+ if (all.has(f)) continue;
276
+ all.add(f);
277
+ queue.push(...(r.files[f]?.local ?? []));
278
+ }
279
+ const dir = flags.dir ?? config().dir ?? defaultDir(framework());
280
+ let copied = 0;
281
+ for (const f of all) {
282
+ const from = join(pkgRoot, 'src', f);
283
+ const to = join(cwd, dir, f);
284
+ const requested = want.has(f);
285
+ if (existsSync(to) && !flags.overwrite) {
286
+ console.log(` ${c.d('lewati')} ${relative(cwd, to)} ${c.d('(sudah ada; --overwrite untuk menimpa)')}`);
287
+ continue;
288
+ }
289
+ mkdirSync(dirname(to), { recursive: true });
290
+ copyFileSync(from, to);
291
+ copied++;
292
+ console.log(` ${requested ? c.g('+') : c.d('+')} ${relative(cwd, to)}${requested ? '' : c.d(' (dependensi)')}`);
293
+ }
294
+ const npm = [...new Set([...all].flatMap((f) => r.files[f]?.npm ?? []))].filter((p) => p !== 'react' && p !== 'react-dom');
295
+ const missing = npm.filter((p) => !deps[p]).map((p) => (r.versions[p] ? `${p}@${r.versions[p]}` : p));
296
+ if (missing.length) install(missing);
297
+ if (!deps['unipo-ui']) install(['unipo-ui']);
298
+ ok(`${copied} berkas disalin ke ${c.b(dir)}. CSS tetap dari ${c.c("'unipo-ui/css'")}; impor komponen dari folder itu untuk mengubah markup atau perilakunya.`);
299
+ }
300
+
301
+ // ---------------------------------------------------------------- perintah: tema
302
+ async function tema() {
303
+ const cfg = config();
304
+ const primary = flags.primary ?? cfg.primary ?? '#244ce6';
305
+ const secondary = flags.secondary ?? cfg.secondary ?? '#f2b42a';
306
+ const radius = Number(flags.radius ?? cfg.radius ?? 10);
307
+ if (!HEX.test(primary) || !HEX.test(secondary)) fail('Warna harus berformat hex, mis. --primary "#0f766e".');
308
+ const out = flags.out ?? cfg.theme ?? 'unipo-tema.css';
309
+ const opsi = opsiTemaDariFlag({ ...(cfg.tema ?? {}), primary, secondary, radius });
310
+ if (flags.scss) {
311
+ console.log(`@use 'unipo-ui/scss' with ($u-primary: ${primary}, $u-secondary: ${secondary}, $u-radius-base: ${radius});`);
312
+ return;
313
+ }
314
+ if (flags.stdout) {
315
+ process.stdout.write(themeCss(opsi));
316
+ return;
317
+ }
318
+ mkdirSync(dirname(join(cwd, out)), { recursive: true });
319
+ writeFileSync(join(cwd, out), themeCss(opsi));
320
+ const tema = temaLib ? Object.fromEntries(new URLSearchParams(temaLib.paramTema(opsi)).entries()) : undefined;
321
+ if (existsSync(CONFIG)) writeFileSync(CONFIG, JSON.stringify({ ...cfg, primary, secondary, radius, tema, theme: out }, null, 2) + '\n');
322
+ ok(`Tema ditulis ke ${c.b(out)} (primer ${primary}, sekunder ${secondary}, radius ${radius}px)`);
323
+ }
324
+
325
+ function help() {
326
+ console.log(`
327
+ ${c.b('unipo-ui')} ${c.d('v' + pkg.version)} Komponen UI UNIPO
328
+
329
+ ${c.b('Perintah')}
330
+ init Pasang unipo-ui (+ SDK), buat berkas tema, impor CSS otomatis
331
+ --primary "#244ce6" --secondary "#f2b42a" --radius 10 --sdk -y
332
+ add <Komponen...> Salin sumber komponen dan dependensinya ke proyek (bisa diubah bebas)
333
+ --dir src/components/unipo --overwrite
334
+ list [kata] Daftar komponen per kelompok
335
+ tema Tulis ulang berkas tema --primary --secondary --radius --netral --font --teks
336
+ --bayangan --gerak --dari "<tautan tema.css dari api.unipo.ac.id/docs/tema>" --out --stdout
337
+
338
+ ${c.b('Opsi umum')}
339
+ --pm npm|pnpm|yarn|bun paksa pengelola paket --no-install --no-edit --no-font
340
+
341
+ ${c.b('Contoh')}
342
+ npx unipo-ui init -y --primary "#0f766e"
343
+ npx unipo-ui add FilterBar CariMahasiswa
344
+ npx unipo-ui list cari
345
+
346
+ Panduan: https://api.unipo.ac.id/docs/panduan/ui
347
+ `);
348
+ }
349
+
350
+ const run = { init, add, list, tema, theme: tema, help, '--help': help, '-h': help, '--version': () => console.log(pkg.version), '-v': () => console.log(pkg.version) }[cmd];
351
+ if (!run) fail(`Perintah "${cmd}" tidak dikenal. Lihat \`npx unipo-ui help\`.`);
352
+ await run();