omnira-ui 0.3.0 → 0.4.1
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/cli/omnira-init.mjs +382 -21
- package/components/ui/ActivityGauge/ActivityGauge.module.css +43 -0
- package/components/ui/ActivityGauge/MultiActivityGauge.tsx +141 -0
- package/components/ui/ActivityGauge/index.ts +2 -0
- package/components/ui/AgentThinking/AgentThinking.module.css +428 -0
- package/components/ui/AgentThinking/AgentThinking.tsx +346 -0
- package/components/ui/AgentThinking/config.ts +324 -0
- package/components/ui/AgentThinking/index.ts +11 -0
- package/components/ui/AgentThinking/types.ts +38 -0
- package/components/ui/Metric/Metric.module.css +271 -19
- package/components/ui/Metric/Metric.tsx +189 -44
- package/components/ui/Modal/Modal.module.css +402 -0
- package/components/ui/Modal/Modal.tsx +123 -0
- package/components/ui/Modal/index.ts +2 -0
- package/components/ui/Notification/Notification.module.css +305 -0
- package/components/ui/Notification/Notification.tsx +282 -0
- package/components/ui/Notification/index.ts +13 -0
- package/components/ui/SlideOut/SlideOut.module.css +1420 -0
- package/components/ui/SlideOut/SlideOut.tsx +81 -0
- package/components/ui/SlideOut/index.ts +2 -0
- package/package.json +3 -2
package/cli/omnira-init.mjs
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Omnira UI —
|
|
4
|
+
* Omnira UI — CLI
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* npx omnira-ui init Full project scaffolding
|
|
8
|
+
* npx omnira-ui add <Component> Copy a single component into your project
|
|
9
|
+
* npx omnira-ui add List all available components
|
|
7
10
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* - Theme provider → lib/theme-context.tsx
|
|
12
|
-
* - Design tokens CSS → app/globals.css
|
|
13
|
-
* - Accent overrides → omnira-overrides.css (if non-default)
|
|
14
|
-
* - Config file → omnira.config.ts
|
|
15
|
-
*
|
|
16
|
-
* Advanced components (Sidebar, Feature Cards, etc.) can be copied
|
|
17
|
-
* from the documentation site: https://ui.omnira.space
|
|
11
|
+
* The `add` command copies only the requested component folder into
|
|
12
|
+
* components/ui/ and ensures required lib utilities (cn.ts, etc.)
|
|
13
|
+
* and globals.css are present. No full scaffolding needed.
|
|
18
14
|
*/
|
|
19
15
|
|
|
20
16
|
import * as readline from "node:readline";
|
|
@@ -29,6 +25,28 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
29
25
|
const __dirname = path.dirname(__filename);
|
|
30
26
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
31
27
|
|
|
28
|
+
// ── Detect src/ directory structure ─────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function detectSrcDir(cwd) {
|
|
31
|
+
const srcDir = path.join(cwd, "src");
|
|
32
|
+
if (fs.existsSync(srcDir)) {
|
|
33
|
+
// If src/ contains app/, components/, or lib/, it's a src-based project
|
|
34
|
+
if (
|
|
35
|
+
fs.existsSync(path.join(srcDir, "app")) ||
|
|
36
|
+
fs.existsSync(path.join(srcDir, "components")) ||
|
|
37
|
+
fs.existsSync(path.join(srcDir, "lib"))
|
|
38
|
+
) {
|
|
39
|
+
return "src";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getProjectBase(cwd) {
|
|
46
|
+
const srcPrefix = detectSrcDir(cwd);
|
|
47
|
+
return srcPrefix ? path.join(cwd, srcPrefix) : cwd;
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
// ── ANSI helpers ─────────────────────────────────────────────────────
|
|
33
51
|
|
|
34
52
|
const RESET = "\x1b[0m";
|
|
@@ -270,13 +288,21 @@ async function main() {
|
|
|
270
288
|
|
|
271
289
|
const cwd = process.cwd();
|
|
272
290
|
|
|
291
|
+
// ── Detect project structure ──
|
|
292
|
+
const base = getProjectBase(cwd);
|
|
293
|
+
const srcPrefix = detectSrcDir(cwd);
|
|
294
|
+
if (srcPrefix) {
|
|
295
|
+
log(` ${DIM}Detected ${BOLD}${srcPrefix}/${RESET}${DIM} directory structure${RESET}`);
|
|
296
|
+
blank();
|
|
297
|
+
}
|
|
298
|
+
|
|
273
299
|
// ── 1. Copy all base components ──
|
|
274
300
|
const componentsSrc = path.join(PKG_ROOT, "components", "ui");
|
|
275
|
-
const componentsDest = path.join(
|
|
301
|
+
const componentsDest = path.join(base, "components", "ui");
|
|
276
302
|
|
|
277
303
|
if (fs.existsSync(componentsSrc)) {
|
|
278
304
|
const count = copyDirRecursive(componentsSrc, componentsDest);
|
|
279
|
-
log(` ${GREEN}✓${RESET} Copied ${BOLD}${count} files${RESET} → ${DIM}components/ui/${RESET}`);
|
|
305
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}${count} files${RESET} → ${DIM}${srcPrefix ? srcPrefix + "/" : ""}components/ui/${RESET}`);
|
|
280
306
|
} else {
|
|
281
307
|
log(` ${RED}✗${RESET} Components source not found at ${componentsSrc}`);
|
|
282
308
|
log(` ${DIM}This may happen when running locally. Components are bundled in the npm package.${RESET}`);
|
|
@@ -284,7 +310,7 @@ async function main() {
|
|
|
284
310
|
|
|
285
311
|
// ── 2. Copy lib utilities ──
|
|
286
312
|
const libFiles = ["cn.ts", "copy-to-clipboard.ts", "theme-context.tsx"];
|
|
287
|
-
const libDest = path.join(
|
|
313
|
+
const libDest = path.join(base, "lib");
|
|
288
314
|
fs.mkdirSync(libDest, { recursive: true });
|
|
289
315
|
|
|
290
316
|
for (const file of libFiles) {
|
|
@@ -297,7 +323,7 @@ async function main() {
|
|
|
297
323
|
|
|
298
324
|
// ── 3. Copy globals.css (design system tokens) ──
|
|
299
325
|
const globalsSrc = path.join(PKG_ROOT, "app", "globals.css");
|
|
300
|
-
const appDir = path.join(
|
|
326
|
+
const appDir = path.join(base, "app");
|
|
301
327
|
fs.mkdirSync(appDir, { recursive: true });
|
|
302
328
|
|
|
303
329
|
if (copyFile(globalsSrc, path.join(appDir, "globals.css"))) {
|
|
@@ -315,7 +341,7 @@ async function main() {
|
|
|
315
341
|
log(` ${GREEN}✓${RESET} Created ${BOLD}omnira-overrides.css${RESET}`);
|
|
316
342
|
|
|
317
343
|
// ── 6. Generate providers.tsx ──
|
|
318
|
-
const providersPath = path.join(
|
|
344
|
+
const providersPath = path.join(base, "app", "providers.tsx");
|
|
319
345
|
if (!fs.existsSync(providersPath)) {
|
|
320
346
|
fs.writeFileSync(providersPath, generateProviders(themeMode), "utf-8");
|
|
321
347
|
log(` ${GREEN}✓${RESET} Created ${BOLD}app/providers.tsx${RESET} ${DIM}(ThemeProvider wrapper)${RESET}`);
|
|
@@ -362,7 +388,342 @@ async function main() {
|
|
|
362
388
|
blank();
|
|
363
389
|
}
|
|
364
390
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
// ── Page bundles — map page slugs to all required components ────────
|
|
392
|
+
|
|
393
|
+
const PAGE_BUNDLES = {
|
|
394
|
+
// Card Headers
|
|
395
|
+
"card-header": ["CardHeader", "Button", "Badge", "Avatar", "Dropdown"],
|
|
396
|
+
"card-headers": ["CardHeader", "Button", "Badge", "Avatar", "Dropdown"],
|
|
397
|
+
// Page Headers
|
|
398
|
+
"page-header": ["PageHeader", "Button", "Badge"],
|
|
399
|
+
"page-headers": ["PageHeader", "Button", "Badge"],
|
|
400
|
+
// Section Headers
|
|
401
|
+
"section-header": ["Button", "Badge"],
|
|
402
|
+
"section-headers": ["Button", "Badge"],
|
|
403
|
+
// Section Footers
|
|
404
|
+
"section-footer": ["Button"],
|
|
405
|
+
"section-footers": ["Button"],
|
|
406
|
+
// Navigation
|
|
407
|
+
"sidebar-navigation": ["SidebarNavigation", "Button", "Avatar", "Badge", "Dropdown", "Toggle", "Tooltip"],
|
|
408
|
+
"header-navigation": ["Button", "Avatar", "Badge", "Dropdown"],
|
|
409
|
+
// Modals
|
|
410
|
+
"modal": ["Modal", "Button", "Badge", "Input", "Toggle", "Checkbox"],
|
|
411
|
+
"modals": ["Modal", "Button", "Badge", "Input", "Toggle", "Checkbox"],
|
|
412
|
+
// Command Menus
|
|
413
|
+
"command-menu": ["Button", "Input", "Badge"],
|
|
414
|
+
"command-menus": ["Button", "Input", "Badge"],
|
|
415
|
+
// Charts
|
|
416
|
+
"line-bar-chart": ["Card"],
|
|
417
|
+
"line-bar-charts": ["Card"],
|
|
418
|
+
"activity-gauge": ["ActivityGauge"],
|
|
419
|
+
"activity-gauges": ["ActivityGauge"],
|
|
420
|
+
"pie-chart": ["Card"],
|
|
421
|
+
"pie-charts": ["Card"],
|
|
422
|
+
"radar-chart": ["Card"],
|
|
423
|
+
"radar-charts": ["Card"],
|
|
424
|
+
// Metrics
|
|
425
|
+
"metrics": ["Metric", "Button"],
|
|
426
|
+
// Slide Out
|
|
427
|
+
"slide-out": ["SlideOut", "Button", "Input", "Badge"],
|
|
428
|
+
// Inline CTA
|
|
429
|
+
"inline-cta": ["Button", "Badge", "Input"],
|
|
430
|
+
// Pagination
|
|
431
|
+
"pagination": ["Button", "ButtonUtility"],
|
|
432
|
+
// Carousel
|
|
433
|
+
"carousel": ["Button", "ButtonUtility"],
|
|
434
|
+
// Progress Steps
|
|
435
|
+
"progress-steps": ["ProgressBar", "Badge"],
|
|
436
|
+
// Activity Feed
|
|
437
|
+
"activity-feed": ["Avatar", "Badge", "Button"],
|
|
438
|
+
// Messaging
|
|
439
|
+
"messaging": ["Avatar", "Button", "Input"],
|
|
440
|
+
// Tabs
|
|
441
|
+
"tabs": ["Button", "Badge"],
|
|
442
|
+
// Table
|
|
443
|
+
"table": ["Table", "Avatar", "Badge", "Button", "ButtonUtility", "Dropdown", "ProgressBar"],
|
|
444
|
+
// Breadcrumbs
|
|
445
|
+
"breadcrumbs": ["Button"],
|
|
446
|
+
// Alerts
|
|
447
|
+
"alert": ["Button", "Badge"],
|
|
448
|
+
"alerts": ["Button", "Badge"],
|
|
449
|
+
// Notifications
|
|
450
|
+
"notification": ["Button", "Avatar", "Badge"],
|
|
451
|
+
"notifications": ["Button", "Avatar", "Badge"],
|
|
452
|
+
// Date Picker
|
|
453
|
+
"date-picker": ["Button", "Input"],
|
|
454
|
+
// Calendar
|
|
455
|
+
"calendar": ["Calendar", "Button"],
|
|
456
|
+
// File Upload
|
|
457
|
+
"file-upload": ["Button", "ProgressBar"],
|
|
458
|
+
// Content Divider
|
|
459
|
+
"content-divider": ["Button"],
|
|
460
|
+
// Loading Indicator
|
|
461
|
+
"loading-indicator": ["ProgressBar"],
|
|
462
|
+
// Empty States
|
|
463
|
+
"empty-state": ["EmptyState", "Button"],
|
|
464
|
+
"empty-states": ["EmptyState", "Button"],
|
|
465
|
+
// Code Snippet
|
|
466
|
+
"code-snippet": ["Button"],
|
|
467
|
+
// Card
|
|
468
|
+
"card": ["Card", "Button", "Badge"],
|
|
469
|
+
// Matrix
|
|
470
|
+
"matrix": ["Card"],
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// ── Add command — copy a single component ───────────────────────────
|
|
474
|
+
|
|
475
|
+
function getAvailableComponents() {
|
|
476
|
+
const componentsDir = path.join(PKG_ROOT, "components", "ui");
|
|
477
|
+
if (!fs.existsSync(componentsDir)) return [];
|
|
478
|
+
return fs.readdirSync(componentsDir, { withFileTypes: true })
|
|
479
|
+
.filter((d) => d.isDirectory())
|
|
480
|
+
.map((d) => d.name)
|
|
481
|
+
.sort();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function listComponents() {
|
|
485
|
+
const components = getAvailableComponents();
|
|
486
|
+
const bundles = Object.keys(PAGE_BUNDLES).sort();
|
|
487
|
+
|
|
488
|
+
blank();
|
|
489
|
+
log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — Available Components${RESET}`);
|
|
490
|
+
log(` ${DIM}Copy individual components or full page bundles into your project${RESET}`);
|
|
491
|
+
blank();
|
|
492
|
+
|
|
493
|
+
if (components.length === 0) {
|
|
494
|
+
log(` ${RED}✗${RESET} No components found. This may happen when running locally.`);
|
|
495
|
+
blank();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
log(` ${BOLD}${WHITE}Individual Components:${RESET}`);
|
|
500
|
+
blank();
|
|
501
|
+
|
|
502
|
+
const cols = 3;
|
|
503
|
+
const rows = Math.ceil(components.length / cols);
|
|
504
|
+
const colWidth = 22;
|
|
505
|
+
|
|
506
|
+
for (let r = 0; r < rows; r++) {
|
|
507
|
+
let line = " ";
|
|
508
|
+
for (let c = 0; c < cols; c++) {
|
|
509
|
+
const idx = r + c * rows;
|
|
510
|
+
if (idx < components.length) {
|
|
511
|
+
line += components[idx].padEnd(colWidth);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
log(line);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
blank();
|
|
518
|
+
log(` ${BOLD}${WHITE}Page Bundles:${RESET} ${DIM}(installs all components needed for a page)${RESET}`);
|
|
519
|
+
blank();
|
|
520
|
+
|
|
521
|
+
const bCols = 3;
|
|
522
|
+
const bRows = Math.ceil(bundles.length / bCols);
|
|
523
|
+
|
|
524
|
+
for (let r = 0; r < bRows; r++) {
|
|
525
|
+
let line = " ";
|
|
526
|
+
for (let c = 0; c < bCols; c++) {
|
|
527
|
+
const idx = r + c * bRows;
|
|
528
|
+
if (idx < bundles.length) {
|
|
529
|
+
const slug = bundles[idx];
|
|
530
|
+
const count = PAGE_BUNDLES[slug].length;
|
|
531
|
+
line += `${slug} ${DIM}(${count})${RESET}`.padEnd(colWidth + 10);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
log(line);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
blank();
|
|
538
|
+
log(` ${BOLD}${WHITE}Usage:${RESET}`);
|
|
539
|
+
blank();
|
|
540
|
+
log(` ${CYAN}npx omnira-ui add Table${RESET} ${DIM}Single component${RESET}`);
|
|
541
|
+
log(` ${CYAN}npx omnira-ui add card-headers${RESET} ${DIM}All components for Card Headers page${RESET}`);
|
|
542
|
+
log(` ${CYAN}npx omnira-ui add Button Badge${RESET} ${DIM}Multiple components${RESET}`);
|
|
543
|
+
blank();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function ensureLibDeps(cwd) {
|
|
547
|
+
const base = getProjectBase(cwd);
|
|
548
|
+
const libFiles = ["cn.ts", "copy-to-clipboard.ts", "theme-context.tsx"];
|
|
549
|
+
const libDest = path.join(base, "lib");
|
|
550
|
+
let copied = 0;
|
|
551
|
+
|
|
552
|
+
for (const file of libFiles) {
|
|
553
|
+
const dest = path.join(libDest, file);
|
|
554
|
+
if (!fs.existsSync(dest)) {
|
|
555
|
+
const src = path.join(PKG_ROOT, "lib", file);
|
|
556
|
+
if (copyFile(src, dest)) {
|
|
557
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}lib/${file}${RESET} ${DIM}(dependency)${RESET}`);
|
|
558
|
+
copied++;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Ensure globals.css exists
|
|
564
|
+
const globalsDest = path.join(base, "app", "globals.css");
|
|
565
|
+
if (!fs.existsSync(globalsDest)) {
|
|
566
|
+
const globalsSrc = path.join(PKG_ROOT, "app", "globals.css");
|
|
567
|
+
if (copyFile(globalsSrc, globalsDest)) {
|
|
568
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}app/globals.css${RESET} ${DIM}(design tokens)${RESET}`);
|
|
569
|
+
copied++;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return copied;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function resolveNames(inputNames) {
|
|
577
|
+
// Expand page bundle aliases into individual component names
|
|
578
|
+
const resolved = new Set();
|
|
579
|
+
const unknown = [];
|
|
580
|
+
|
|
581
|
+
const available = getAvailableComponents();
|
|
582
|
+
const availableLower = available.map((c) => c.toLowerCase());
|
|
583
|
+
|
|
584
|
+
for (const name of inputNames) {
|
|
585
|
+
const lower = name.toLowerCase();
|
|
586
|
+
|
|
587
|
+
// Check page bundles first
|
|
588
|
+
if (PAGE_BUNDLES[lower]) {
|
|
589
|
+
for (const comp of PAGE_BUNDLES[lower]) {
|
|
590
|
+
resolved.add(comp);
|
|
591
|
+
}
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Check individual component (case-insensitive)
|
|
596
|
+
const idx = availableLower.indexOf(lower);
|
|
597
|
+
if (idx !== -1) {
|
|
598
|
+
resolved.add(available[idx]);
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
unknown.push(name);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return { resolved: [...resolved], unknown };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function addComponents(componentNames) {
|
|
609
|
+
const cwd = process.cwd();
|
|
610
|
+
const base = getProjectBase(cwd);
|
|
611
|
+
const srcPrefix = detectSrcDir(cwd);
|
|
612
|
+
const available = getAvailableComponents();
|
|
613
|
+
|
|
614
|
+
// Resolve page bundles and individual names
|
|
615
|
+
const { resolved, unknown } = resolveNames(componentNames);
|
|
616
|
+
|
|
617
|
+
// Check if any input was a page bundle for nicer messaging
|
|
618
|
+
const isBundle = componentNames.some((n) => PAGE_BUNDLES[n.toLowerCase()]);
|
|
619
|
+
|
|
620
|
+
blank();
|
|
621
|
+
log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — Add Components${RESET}`);
|
|
622
|
+
if (isBundle) {
|
|
623
|
+
log(` ${DIM}Installing page bundle: ${componentNames.filter((n) => PAGE_BUNDLES[n.toLowerCase()]).join(", ")}${RESET}`);
|
|
624
|
+
}
|
|
625
|
+
if (srcPrefix) {
|
|
626
|
+
log(` ${DIM}Detected ${BOLD}${srcPrefix}/${RESET}${DIM} directory structure${RESET}`);
|
|
627
|
+
}
|
|
628
|
+
blank();
|
|
629
|
+
|
|
630
|
+
let totalFiles = 0;
|
|
631
|
+
const added = [];
|
|
632
|
+
const destPrefix = srcPrefix ? srcPrefix + "/" : "";
|
|
633
|
+
|
|
634
|
+
for (const actualName of resolved) {
|
|
635
|
+
const src = path.join(PKG_ROOT, "components", "ui", actualName);
|
|
636
|
+
const dest = path.join(base, "components", "ui", actualName);
|
|
637
|
+
|
|
638
|
+
if (!fs.existsSync(src)) continue;
|
|
639
|
+
|
|
640
|
+
const count = copyDirRecursive(src, dest);
|
|
641
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}${actualName}${RESET} ${DIM}(${count} files)${RESET} → ${DIM}${destPrefix}components/ui/${actualName}/${RESET}`);
|
|
642
|
+
totalFiles += count;
|
|
643
|
+
added.push(actualName);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
for (const name of unknown) {
|
|
647
|
+
log(` ${RED}✗${RESET} "${name}" is not a component or page bundle.`);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Ensure lib dependencies are present
|
|
651
|
+
if (added.length > 0) {
|
|
652
|
+
blank();
|
|
653
|
+
log(` ${DIM}Checking dependencies...${RESET}`);
|
|
654
|
+
ensureLibDeps(cwd);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
blank();
|
|
658
|
+
log(` ${DIM}─────────────────────────────────────${RESET}`);
|
|
659
|
+
blank();
|
|
660
|
+
|
|
661
|
+
if (added.length > 0) {
|
|
662
|
+
log(` ${GREEN}✓${RESET} ${BOLD}${WHITE}Added ${added.length} component${added.length > 1 ? "s" : ""} (${totalFiles} files)${RESET}`);
|
|
663
|
+
blank();
|
|
664
|
+
log(` ${BOLD}${WHITE}Usage:${RESET}`);
|
|
665
|
+
blank();
|
|
666
|
+
for (const name of added) {
|
|
667
|
+
log(` ${MAGENTA}import${RESET} { ${name} } ${MAGENTA}from${RESET} ${WHITE}"@/components/ui/${name}"${RESET};`);
|
|
668
|
+
}
|
|
669
|
+
blank();
|
|
670
|
+
|
|
671
|
+
const globalsPath = path.join(base, "app", "globals.css");
|
|
672
|
+
if (!fs.existsSync(globalsPath)) {
|
|
673
|
+
log(` ${YELLOW}!${RESET} Don't forget to import ${BOLD}globals.css${RESET} in your root layout.`);
|
|
674
|
+
blank();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (unknown.length > 0) {
|
|
679
|
+
log(` ${YELLOW}!${RESET} Run ${CYAN}npx omnira-ui add${RESET} to see all available components.`);
|
|
680
|
+
blank();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// ── Help ─────────────────────────────────────────────────────────────
|
|
685
|
+
|
|
686
|
+
function showHelp() {
|
|
687
|
+
blank();
|
|
688
|
+
log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — CLI${RESET}`);
|
|
689
|
+
log(` ${DIM}The premium glassmorphism design system${RESET}`);
|
|
690
|
+
blank();
|
|
691
|
+
log(` ${BOLD}${WHITE}Commands:${RESET}`);
|
|
692
|
+
blank();
|
|
693
|
+
log(` ${CYAN}npx omnira-ui init${RESET} Scaffold the full design system`);
|
|
694
|
+
log(` ${CYAN}npx omnira-ui add <name>${RESET} Add a component or page bundle`);
|
|
695
|
+
log(` ${CYAN}npx omnira-ui add${RESET} List all components & page bundles`);
|
|
696
|
+
log(` ${CYAN}npx omnira-ui help${RESET} Show this help message`);
|
|
697
|
+
blank();
|
|
698
|
+
log(` ${BOLD}${WHITE}Examples:${RESET}`);
|
|
699
|
+
blank();
|
|
700
|
+
log(` ${CYAN}npx omnira-ui add Table${RESET} ${DIM}Single component${RESET}`);
|
|
701
|
+
log(` ${CYAN}npx omnira-ui add card-headers${RESET} ${DIM}All components for Card Headers page${RESET}`);
|
|
702
|
+
log(` ${CYAN}npx omnira-ui add Button Badge${RESET} ${DIM}Multiple components${RESET}`);
|
|
703
|
+
log(` ${CYAN}npx omnira-ui init${RESET} ${DIM}Full project scaffolding${RESET}`);
|
|
704
|
+
blank();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// ── Command router ──────────────────────────────────────────────────
|
|
708
|
+
|
|
709
|
+
const args = process.argv.slice(2);
|
|
710
|
+
const command = args[0]?.toLowerCase();
|
|
711
|
+
|
|
712
|
+
if (!command || command === "init") {
|
|
713
|
+
main().catch((err) => {
|
|
714
|
+
console.error(err);
|
|
715
|
+
process.exit(1);
|
|
716
|
+
});
|
|
717
|
+
} else if (command === "add") {
|
|
718
|
+
const componentNames = args.slice(1);
|
|
719
|
+
if (componentNames.length === 0) {
|
|
720
|
+
listComponents();
|
|
721
|
+
} else {
|
|
722
|
+
addComponents(componentNames);
|
|
723
|
+
}
|
|
724
|
+
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
725
|
+
showHelp();
|
|
726
|
+
} else {
|
|
727
|
+
log(`\n ${RED}✗${RESET} Unknown command: "${command}"\n`);
|
|
728
|
+
showHelp();
|
|
729
|
+
}
|
|
@@ -100,6 +100,43 @@
|
|
|
100
100
|
margin-top: -8px;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/* ── Multi-ring center label ── */
|
|
104
|
+
|
|
105
|
+
.multiCenterLabel {
|
|
106
|
+
font-size: 12px;
|
|
107
|
+
color: var(--color-text-tertiary);
|
|
108
|
+
line-height: 1;
|
|
109
|
+
margin-bottom: 2px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ── Legend ── */
|
|
113
|
+
|
|
114
|
+
.legend {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
gap: 16px;
|
|
119
|
+
flex-wrap: wrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.legendItem {
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 6px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.legendDot {
|
|
129
|
+
width: 8px;
|
|
130
|
+
height: 8px;
|
|
131
|
+
border-radius: var(--radius-full);
|
|
132
|
+
flex-shrink: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.legendText {
|
|
136
|
+
font-size: 13px;
|
|
137
|
+
color: var(--color-text-secondary);
|
|
138
|
+
}
|
|
139
|
+
|
|
103
140
|
/* ── Grid layout for demos ── */
|
|
104
141
|
|
|
105
142
|
.gaugeGrid {
|
|
@@ -107,3 +144,9 @@
|
|
|
107
144
|
grid-template-columns: repeat(4, 1fr);
|
|
108
145
|
gap: 16px;
|
|
109
146
|
}
|
|
147
|
+
|
|
148
|
+
.multiGaugeGrid {
|
|
149
|
+
display: grid;
|
|
150
|
+
grid-template-columns: repeat(3, 1fr);
|
|
151
|
+
gap: 16px;
|
|
152
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { cn } from "@/lib/cn";
|
|
2
|
+
import styles from "./ActivityGauge.module.css";
|
|
3
|
+
|
|
4
|
+
/* ── Types ── */
|
|
5
|
+
|
|
6
|
+
export type MultiGaugeColor = "lime" | "info" | "warning" | "error" | "success";
|
|
7
|
+
|
|
8
|
+
export interface RingData {
|
|
9
|
+
value: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
label: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MultiActivityGaugeProps {
|
|
15
|
+
rings: RingData[];
|
|
16
|
+
centerLabel?: string;
|
|
17
|
+
centerValue?: string | number;
|
|
18
|
+
color?: MultiGaugeColor;
|
|
19
|
+
size?: number;
|
|
20
|
+
strokeWidth?: number;
|
|
21
|
+
gap?: number;
|
|
22
|
+
showLegend?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ── Color shades (dark → medium → light for each base color) ── */
|
|
27
|
+
|
|
28
|
+
const COLOR_SHADES: Record<MultiGaugeColor, string[]> = {
|
|
29
|
+
lime: [
|
|
30
|
+
"var(--color-lime)",
|
|
31
|
+
"rgba(163, 230, 53, 0.6)",
|
|
32
|
+
"rgba(163, 230, 53, 0.3)",
|
|
33
|
+
],
|
|
34
|
+
info: [
|
|
35
|
+
"var(--color-info)",
|
|
36
|
+
"rgba(96, 165, 250, 0.6)",
|
|
37
|
+
"rgba(96, 165, 250, 0.3)",
|
|
38
|
+
],
|
|
39
|
+
warning: [
|
|
40
|
+
"var(--color-warning)",
|
|
41
|
+
"rgba(251, 191, 36, 0.6)",
|
|
42
|
+
"rgba(251, 191, 36, 0.3)",
|
|
43
|
+
],
|
|
44
|
+
error: [
|
|
45
|
+
"var(--color-error)",
|
|
46
|
+
"rgba(248, 113, 113, 0.6)",
|
|
47
|
+
"rgba(248, 113, 113, 0.3)",
|
|
48
|
+
],
|
|
49
|
+
success: [
|
|
50
|
+
"var(--color-success)",
|
|
51
|
+
"rgba(74, 222, 128, 0.6)",
|
|
52
|
+
"rgba(74, 222, 128, 0.3)",
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/* ── Component ── */
|
|
57
|
+
|
|
58
|
+
export function MultiActivityGauge({
|
|
59
|
+
rings,
|
|
60
|
+
centerLabel,
|
|
61
|
+
centerValue,
|
|
62
|
+
color = "lime",
|
|
63
|
+
size = 180,
|
|
64
|
+
strokeWidth = 10,
|
|
65
|
+
gap = 4,
|
|
66
|
+
showLegend = true,
|
|
67
|
+
className,
|
|
68
|
+
}: MultiActivityGaugeProps) {
|
|
69
|
+
const shades = COLOR_SHADES[color];
|
|
70
|
+
const ringCount = Math.min(rings.length, 3);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className={cn(styles.gaugeCard, className)}>
|
|
74
|
+
<div className={styles.gaugeWrapper} style={{ width: size, height: size }}>
|
|
75
|
+
<svg
|
|
76
|
+
className={styles.gaugeSvg}
|
|
77
|
+
width={size}
|
|
78
|
+
height={size}
|
|
79
|
+
viewBox={`0 0 ${size} ${size}`}
|
|
80
|
+
>
|
|
81
|
+
{rings.slice(0, 3).map((ring, i) => {
|
|
82
|
+
const ringRadius =
|
|
83
|
+
(size - strokeWidth) / 2 - i * (strokeWidth + gap);
|
|
84
|
+
const circumference = 2 * Math.PI * ringRadius;
|
|
85
|
+
const percentage = Math.min(ring.value / (ring.max ?? 100), 1);
|
|
86
|
+
const offset = circumference * (1 - percentage);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<g key={i}>
|
|
90
|
+
{/* Track */}
|
|
91
|
+
<circle
|
|
92
|
+
className={styles.gaugeTrack}
|
|
93
|
+
cx={size / 2}
|
|
94
|
+
cy={size / 2}
|
|
95
|
+
r={ringRadius}
|
|
96
|
+
strokeWidth={strokeWidth}
|
|
97
|
+
/>
|
|
98
|
+
{/* Fill */}
|
|
99
|
+
<circle
|
|
100
|
+
className={styles.gaugeFill}
|
|
101
|
+
cx={size / 2}
|
|
102
|
+
cy={size / 2}
|
|
103
|
+
r={ringRadius}
|
|
104
|
+
strokeWidth={strokeWidth}
|
|
105
|
+
strokeDasharray={circumference}
|
|
106
|
+
strokeDashoffset={offset}
|
|
107
|
+
style={{ stroke: shades[i] }}
|
|
108
|
+
/>
|
|
109
|
+
</g>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</svg>
|
|
113
|
+
|
|
114
|
+
{/* Center text */}
|
|
115
|
+
<div className={styles.gaugeCenter}>
|
|
116
|
+
{centerLabel && (
|
|
117
|
+
<span className={styles.multiCenterLabel}>{centerLabel}</span>
|
|
118
|
+
)}
|
|
119
|
+
{centerValue !== undefined && (
|
|
120
|
+
<span className={styles.gaugeValue}>{centerValue}</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Legend */}
|
|
126
|
+
{showLegend && (
|
|
127
|
+
<div className={styles.legend}>
|
|
128
|
+
{rings.slice(0, 3).map((ring, i) => (
|
|
129
|
+
<div key={i} className={styles.legendItem}>
|
|
130
|
+
<span
|
|
131
|
+
className={styles.legendDot}
|
|
132
|
+
style={{ background: shades[i] }}
|
|
133
|
+
/>
|
|
134
|
+
<span className={styles.legendText}>{ring.label}</span>
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { ActivityGauge } from "./ActivityGauge";
|
|
2
2
|
export type { ActivityGaugeProps, GaugeColor } from "./ActivityGauge";
|
|
3
|
+
export { MultiActivityGauge } from "./MultiActivityGauge";
|
|
4
|
+
export type { MultiActivityGaugeProps, RingData, MultiGaugeColor } from "./MultiActivityGauge";
|