omnira-ui 0.3.1 → 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.
- package/cli/omnira-init.mjs +213 -33
- 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/Alert/Alert.module.css +224 -0
- package/components/ui/Alert/Alert.tsx +138 -0
- package/components/ui/Alert/index.ts +2 -0
- package/components/ui/Breadcrumbs/Breadcrumbs.module.css +134 -0
- package/components/ui/Breadcrumbs/Breadcrumbs.tsx +139 -0
- package/components/ui/Breadcrumbs/index.ts +2 -0
- package/components/ui/Calendar/Calendar.module.css +235 -6
- package/components/ui/Calendar/Calendar.tsx +212 -11
- package/components/ui/Calendar/config.ts +54 -4
- package/components/ui/CodeSnippet/CodeSnippet.module.css +158 -0
- package/components/ui/CodeSnippet/CodeSnippet.tsx +106 -0
- package/components/ui/CodeSnippet/index.ts +2 -0
- package/components/ui/ContentDivider/ContentDivider.module.css +214 -0
- package/components/ui/ContentDivider/ContentDivider.tsx +197 -0
- package/components/ui/ContentDivider/index.ts +10 -0
- package/components/ui/DatePicker/DatePicker.module.css +319 -0
- package/components/ui/DatePicker/DatePicker.tsx +614 -0
- package/components/ui/DatePicker/index.ts +14 -0
- package/components/ui/FileUpload/FileUpload.module.css +322 -0
- package/components/ui/FileUpload/FileUpload.tsx +413 -0
- package/components/ui/FileUpload/index.ts +8 -0
- package/components/ui/LoadingIndicator/LoadingIndicator.module.css +381 -0
- package/components/ui/LoadingIndicator/LoadingIndicator.tsx +226 -0
- package/components/ui/LoadingIndicator/index.ts +8 -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/components/ui/Tabs/Tabs.module.css +257 -0
- package/components/ui/Tabs/Tabs.tsx +149 -0
- package/components/ui/Tabs/index.ts +2 -0
- package/package.json +3 -2
package/cli/omnira-init.mjs
CHANGED
|
@@ -25,6 +25,28 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
25
25
|
const __dirname = path.dirname(__filename);
|
|
26
26
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
27
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
|
+
|
|
28
50
|
// ── ANSI helpers ─────────────────────────────────────────────────────
|
|
29
51
|
|
|
30
52
|
const RESET = "\x1b[0m";
|
|
@@ -266,13 +288,21 @@ async function main() {
|
|
|
266
288
|
|
|
267
289
|
const cwd = process.cwd();
|
|
268
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
|
+
|
|
269
299
|
// ── 1. Copy all base components ──
|
|
270
300
|
const componentsSrc = path.join(PKG_ROOT, "components", "ui");
|
|
271
|
-
const componentsDest = path.join(
|
|
301
|
+
const componentsDest = path.join(base, "components", "ui");
|
|
272
302
|
|
|
273
303
|
if (fs.existsSync(componentsSrc)) {
|
|
274
304
|
const count = copyDirRecursive(componentsSrc, componentsDest);
|
|
275
|
-
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}`);
|
|
276
306
|
} else {
|
|
277
307
|
log(` ${RED}✗${RESET} Components source not found at ${componentsSrc}`);
|
|
278
308
|
log(` ${DIM}This may happen when running locally. Components are bundled in the npm package.${RESET}`);
|
|
@@ -280,7 +310,7 @@ async function main() {
|
|
|
280
310
|
|
|
281
311
|
// ── 2. Copy lib utilities ──
|
|
282
312
|
const libFiles = ["cn.ts", "copy-to-clipboard.ts", "theme-context.tsx"];
|
|
283
|
-
const libDest = path.join(
|
|
313
|
+
const libDest = path.join(base, "lib");
|
|
284
314
|
fs.mkdirSync(libDest, { recursive: true });
|
|
285
315
|
|
|
286
316
|
for (const file of libFiles) {
|
|
@@ -293,7 +323,7 @@ async function main() {
|
|
|
293
323
|
|
|
294
324
|
// ── 3. Copy globals.css (design system tokens) ──
|
|
295
325
|
const globalsSrc = path.join(PKG_ROOT, "app", "globals.css");
|
|
296
|
-
const appDir = path.join(
|
|
326
|
+
const appDir = path.join(base, "app");
|
|
297
327
|
fs.mkdirSync(appDir, { recursive: true });
|
|
298
328
|
|
|
299
329
|
if (copyFile(globalsSrc, path.join(appDir, "globals.css"))) {
|
|
@@ -311,7 +341,7 @@ async function main() {
|
|
|
311
341
|
log(` ${GREEN}✓${RESET} Created ${BOLD}omnira-overrides.css${RESET}`);
|
|
312
342
|
|
|
313
343
|
// ── 6. Generate providers.tsx ──
|
|
314
|
-
const providersPath = path.join(
|
|
344
|
+
const providersPath = path.join(base, "app", "providers.tsx");
|
|
315
345
|
if (!fs.existsSync(providersPath)) {
|
|
316
346
|
fs.writeFileSync(providersPath, generateProviders(themeMode), "utf-8");
|
|
317
347
|
log(` ${GREEN}✓${RESET} Created ${BOLD}app/providers.tsx${RESET} ${DIM}(ThemeProvider wrapper)${RESET}`);
|
|
@@ -358,6 +388,88 @@ async function main() {
|
|
|
358
388
|
blank();
|
|
359
389
|
}
|
|
360
390
|
|
|
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
|
+
|
|
361
473
|
// ── Add command — copy a single component ───────────────────────────
|
|
362
474
|
|
|
363
475
|
function getAvailableComponents() {
|
|
@@ -371,10 +483,11 @@ function getAvailableComponents() {
|
|
|
371
483
|
|
|
372
484
|
function listComponents() {
|
|
373
485
|
const components = getAvailableComponents();
|
|
486
|
+
const bundles = Object.keys(PAGE_BUNDLES).sort();
|
|
374
487
|
|
|
375
488
|
blank();
|
|
376
489
|
log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — Available Components${RESET}`);
|
|
377
|
-
log(` ${DIM}Copy individual components into your project${RESET}`);
|
|
490
|
+
log(` ${DIM}Copy individual components or full page bundles into your project${RESET}`);
|
|
378
491
|
blank();
|
|
379
492
|
|
|
380
493
|
if (components.length === 0) {
|
|
@@ -383,6 +496,9 @@ function listComponents() {
|
|
|
383
496
|
return;
|
|
384
497
|
}
|
|
385
498
|
|
|
499
|
+
log(` ${BOLD}${WHITE}Individual Components:${RESET}`);
|
|
500
|
+
blank();
|
|
501
|
+
|
|
386
502
|
const cols = 3;
|
|
387
503
|
const rows = Math.ceil(components.length / cols);
|
|
388
504
|
const colWidth = 22;
|
|
@@ -399,15 +515,38 @@ function listComponents() {
|
|
|
399
515
|
}
|
|
400
516
|
|
|
401
517
|
blank();
|
|
402
|
-
log(` ${
|
|
403
|
-
|
|
404
|
-
|
|
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}`);
|
|
405
543
|
blank();
|
|
406
544
|
}
|
|
407
545
|
|
|
408
546
|
function ensureLibDeps(cwd) {
|
|
547
|
+
const base = getProjectBase(cwd);
|
|
409
548
|
const libFiles = ["cn.ts", "copy-to-clipboard.ts", "theme-context.tsx"];
|
|
410
|
-
const libDest = path.join(
|
|
549
|
+
const libDest = path.join(base, "lib");
|
|
411
550
|
let copied = 0;
|
|
412
551
|
|
|
413
552
|
for (const file of libFiles) {
|
|
@@ -422,7 +561,7 @@ function ensureLibDeps(cwd) {
|
|
|
422
561
|
}
|
|
423
562
|
|
|
424
563
|
// Ensure globals.css exists
|
|
425
|
-
const globalsDest = path.join(
|
|
564
|
+
const globalsDest = path.join(base, "app", "globals.css");
|
|
426
565
|
if (!fs.existsSync(globalsDest)) {
|
|
427
566
|
const globalsSrc = path.join(PKG_ROOT, "app", "globals.css");
|
|
428
567
|
if (copyFile(globalsSrc, globalsDest)) {
|
|
@@ -434,39 +573,78 @@ function ensureLibDeps(cwd) {
|
|
|
434
573
|
return copied;
|
|
435
574
|
}
|
|
436
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
|
+
|
|
437
608
|
function addComponents(componentNames) {
|
|
438
609
|
const cwd = process.cwd();
|
|
610
|
+
const base = getProjectBase(cwd);
|
|
611
|
+
const srcPrefix = detectSrcDir(cwd);
|
|
439
612
|
const available = getAvailableComponents();
|
|
440
|
-
|
|
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()]);
|
|
441
619
|
|
|
442
620
|
blank();
|
|
443
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
|
+
}
|
|
444
628
|
blank();
|
|
445
629
|
|
|
446
630
|
let totalFiles = 0;
|
|
447
631
|
const added = [];
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
for (const name of componentNames) {
|
|
451
|
-
// Case-insensitive match
|
|
452
|
-
const idx = availableLower.indexOf(name.toLowerCase());
|
|
453
|
-
if (idx === -1) {
|
|
454
|
-
notFound.push(name);
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
632
|
+
const destPrefix = srcPrefix ? srcPrefix + "/" : "";
|
|
457
633
|
|
|
458
|
-
|
|
634
|
+
for (const actualName of resolved) {
|
|
459
635
|
const src = path.join(PKG_ROOT, "components", "ui", actualName);
|
|
460
|
-
const dest = path.join(
|
|
636
|
+
const dest = path.join(base, "components", "ui", actualName);
|
|
637
|
+
|
|
638
|
+
if (!fs.existsSync(src)) continue;
|
|
461
639
|
|
|
462
640
|
const count = copyDirRecursive(src, dest);
|
|
463
|
-
log(` ${GREEN}✓${RESET} Copied ${BOLD}${actualName}${RESET} ${DIM}(${count} files)${RESET} → ${DIM}components/ui/${actualName}/${RESET}`);
|
|
641
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}${actualName}${RESET} ${DIM}(${count} files)${RESET} → ${DIM}${destPrefix}components/ui/${actualName}/${RESET}`);
|
|
464
642
|
totalFiles += count;
|
|
465
643
|
added.push(actualName);
|
|
466
644
|
}
|
|
467
645
|
|
|
468
|
-
for (const name of
|
|
469
|
-
log(` ${RED}✗${RESET}
|
|
646
|
+
for (const name of unknown) {
|
|
647
|
+
log(` ${RED}✗${RESET} "${name}" is not a component or page bundle.`);
|
|
470
648
|
}
|
|
471
649
|
|
|
472
650
|
// Ensure lib dependencies are present
|
|
@@ -490,13 +668,14 @@ function addComponents(componentNames) {
|
|
|
490
668
|
}
|
|
491
669
|
blank();
|
|
492
670
|
|
|
493
|
-
|
|
671
|
+
const globalsPath = path.join(base, "app", "globals.css");
|
|
672
|
+
if (!fs.existsSync(globalsPath)) {
|
|
494
673
|
log(` ${YELLOW}!${RESET} Don't forget to import ${BOLD}globals.css${RESET} in your root layout.`);
|
|
495
674
|
blank();
|
|
496
675
|
}
|
|
497
676
|
}
|
|
498
677
|
|
|
499
|
-
if (
|
|
678
|
+
if (unknown.length > 0) {
|
|
500
679
|
log(` ${YELLOW}!${RESET} Run ${CYAN}npx omnira-ui add${RESET} to see all available components.`);
|
|
501
680
|
blank();
|
|
502
681
|
}
|
|
@@ -512,15 +691,16 @@ function showHelp() {
|
|
|
512
691
|
log(` ${BOLD}${WHITE}Commands:${RESET}`);
|
|
513
692
|
blank();
|
|
514
693
|
log(` ${CYAN}npx omnira-ui init${RESET} Scaffold the full design system`);
|
|
515
|
-
log(` ${CYAN}npx omnira-ui add <
|
|
516
|
-
log(` ${CYAN}npx omnira-ui add${RESET} List all
|
|
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`);
|
|
517
696
|
log(` ${CYAN}npx omnira-ui help${RESET} Show this help message`);
|
|
518
697
|
blank();
|
|
519
698
|
log(` ${BOLD}${WHITE}Examples:${RESET}`);
|
|
520
699
|
blank();
|
|
521
|
-
log(` ${CYAN}npx omnira-ui add Table${RESET}`);
|
|
522
|
-
log(` ${CYAN}npx omnira-ui add
|
|
523
|
-
log(` ${CYAN}npx omnira-ui
|
|
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}`);
|
|
524
704
|
blank();
|
|
525
705
|
}
|
|
526
706
|
|
|
@@ -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";
|