omnira-ui 0.2.0 → 0.3.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.
Files changed (29) hide show
  1. package/cli/omnira-init.mjs +197 -16
  2. package/components/ui/ActivityGauge/ActivityGauge.module.css +109 -0
  3. package/components/ui/ActivityGauge/ActivityGauge.tsx +87 -0
  4. package/components/ui/ActivityGauge/index.ts +2 -0
  5. package/components/ui/Calendar/Calendar.module.css +492 -0
  6. package/components/ui/Calendar/Calendar.tsx +445 -0
  7. package/components/ui/Calendar/config.ts +130 -0
  8. package/components/ui/Calendar/index.ts +4 -0
  9. package/components/ui/CardHeader/CardHeader.module.css +79 -0
  10. package/components/ui/CardHeader/CardHeader.tsx +45 -0
  11. package/components/ui/CardHeader/index.ts +2 -0
  12. package/components/ui/EmptyState/EmptyState.module.css +65 -0
  13. package/components/ui/EmptyState/EmptyState.tsx +37 -0
  14. package/components/ui/EmptyState/index.ts +2 -0
  15. package/components/ui/Metric/Metric.module.css +140 -0
  16. package/components/ui/Metric/Metric.tsx +78 -0
  17. package/components/ui/Metric/index.ts +2 -0
  18. package/components/ui/PageHeader/PageHeader.module.css +128 -0
  19. package/components/ui/PageHeader/PageHeader.tsx +61 -0
  20. package/components/ui/PageHeader/index.ts +2 -0
  21. package/components/ui/Table/Table.module.css +444 -0
  22. package/components/ui/Table/Table.tsx +547 -0
  23. package/components/ui/Table/customers.json +74 -0
  24. package/components/ui/Table/index.ts +14 -0
  25. package/components/ui/Table/invoices.json +92 -0
  26. package/components/ui/Table/orders.json +108 -0
  27. package/components/ui/Table/team-members.json +130 -0
  28. package/components/ui/Table/uploaded-files.json +53 -0
  29. package/package.json +1 -1
@@ -1,20 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Omnira UI — Project Scaffolding CLI
4
+ * Omnira UI — CLI
5
5
  *
6
- * Usage: npx omnira-ui init
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
- * Scaffolds the full Omnira UI design system into your project:
9
- * - All base components components/ui/
10
- * - Utility helpers → lib/
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";
@@ -362,7 +358,192 @@ async function main() {
362
358
  blank();
363
359
  }
364
360
 
365
- main().catch((err) => {
366
- console.error(err);
367
- process.exit(1);
368
- });
361
+ // ── Add command — copy a single component ───────────────────────────
362
+
363
+ function getAvailableComponents() {
364
+ const componentsDir = path.join(PKG_ROOT, "components", "ui");
365
+ if (!fs.existsSync(componentsDir)) return [];
366
+ return fs.readdirSync(componentsDir, { withFileTypes: true })
367
+ .filter((d) => d.isDirectory())
368
+ .map((d) => d.name)
369
+ .sort();
370
+ }
371
+
372
+ function listComponents() {
373
+ const components = getAvailableComponents();
374
+
375
+ blank();
376
+ log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — Available Components${RESET}`);
377
+ log(` ${DIM}Copy individual components into your project${RESET}`);
378
+ blank();
379
+
380
+ if (components.length === 0) {
381
+ log(` ${RED}✗${RESET} No components found. This may happen when running locally.`);
382
+ blank();
383
+ return;
384
+ }
385
+
386
+ const cols = 3;
387
+ const rows = Math.ceil(components.length / cols);
388
+ const colWidth = 22;
389
+
390
+ for (let r = 0; r < rows; r++) {
391
+ let line = " ";
392
+ for (let c = 0; c < cols; c++) {
393
+ const idx = r + c * rows;
394
+ if (idx < components.length) {
395
+ line += components[idx].padEnd(colWidth);
396
+ }
397
+ }
398
+ log(line);
399
+ }
400
+
401
+ blank();
402
+ log(` ${DIM}Usage:${RESET} ${CYAN}npx omnira-ui add <Component>${RESET}`);
403
+ log(` ${DIM}Example:${RESET} ${CYAN}npx omnira-ui add Table${RESET}`);
404
+ log(` ${DIM}Multiple:${RESET} ${CYAN}npx omnira-ui add Table Button Badge${RESET}`);
405
+ blank();
406
+ }
407
+
408
+ function ensureLibDeps(cwd) {
409
+ const libFiles = ["cn.ts", "copy-to-clipboard.ts", "theme-context.tsx"];
410
+ const libDest = path.join(cwd, "lib");
411
+ let copied = 0;
412
+
413
+ for (const file of libFiles) {
414
+ const dest = path.join(libDest, file);
415
+ if (!fs.existsSync(dest)) {
416
+ const src = path.join(PKG_ROOT, "lib", file);
417
+ if (copyFile(src, dest)) {
418
+ log(` ${GREEN}✓${RESET} Copied ${BOLD}lib/${file}${RESET} ${DIM}(dependency)${RESET}`);
419
+ copied++;
420
+ }
421
+ }
422
+ }
423
+
424
+ // Ensure globals.css exists
425
+ const globalsDest = path.join(cwd, "app", "globals.css");
426
+ if (!fs.existsSync(globalsDest)) {
427
+ const globalsSrc = path.join(PKG_ROOT, "app", "globals.css");
428
+ if (copyFile(globalsSrc, globalsDest)) {
429
+ log(` ${GREEN}✓${RESET} Copied ${BOLD}app/globals.css${RESET} ${DIM}(design tokens)${RESET}`);
430
+ copied++;
431
+ }
432
+ }
433
+
434
+ return copied;
435
+ }
436
+
437
+ function addComponents(componentNames) {
438
+ const cwd = process.cwd();
439
+ const available = getAvailableComponents();
440
+ const availableLower = available.map((c) => c.toLowerCase());
441
+
442
+ blank();
443
+ log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — Add Components${RESET}`);
444
+ blank();
445
+
446
+ let totalFiles = 0;
447
+ const added = [];
448
+ const notFound = [];
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
+ }
457
+
458
+ const actualName = available[idx];
459
+ const src = path.join(PKG_ROOT, "components", "ui", actualName);
460
+ const dest = path.join(cwd, "components", "ui", actualName);
461
+
462
+ const count = copyDirRecursive(src, dest);
463
+ log(` ${GREEN}✓${RESET} Copied ${BOLD}${actualName}${RESET} ${DIM}(${count} files)${RESET} → ${DIM}components/ui/${actualName}/${RESET}`);
464
+ totalFiles += count;
465
+ added.push(actualName);
466
+ }
467
+
468
+ for (const name of notFound) {
469
+ log(` ${RED}✗${RESET} Component "${name}" not found.`);
470
+ }
471
+
472
+ // Ensure lib dependencies are present
473
+ if (added.length > 0) {
474
+ blank();
475
+ log(` ${DIM}Checking dependencies...${RESET}`);
476
+ ensureLibDeps(cwd);
477
+ }
478
+
479
+ blank();
480
+ log(` ${DIM}─────────────────────────────────────${RESET}`);
481
+ blank();
482
+
483
+ if (added.length > 0) {
484
+ log(` ${GREEN}✓${RESET} ${BOLD}${WHITE}Added ${added.length} component${added.length > 1 ? "s" : ""} (${totalFiles} files)${RESET}`);
485
+ blank();
486
+ log(` ${BOLD}${WHITE}Usage:${RESET}`);
487
+ blank();
488
+ for (const name of added) {
489
+ log(` ${MAGENTA}import${RESET} { ${name} } ${MAGENTA}from${RESET} ${WHITE}"@/components/ui/${name}"${RESET};`);
490
+ }
491
+ blank();
492
+
493
+ if (!fs.existsSync(path.join(cwd, "app", "globals.css"))) {
494
+ log(` ${YELLOW}!${RESET} Don't forget to import ${BOLD}globals.css${RESET} in your root layout.`);
495
+ blank();
496
+ }
497
+ }
498
+
499
+ if (notFound.length > 0) {
500
+ log(` ${YELLOW}!${RESET} Run ${CYAN}npx omnira-ui add${RESET} to see all available components.`);
501
+ blank();
502
+ }
503
+ }
504
+
505
+ // ── Help ─────────────────────────────────────────────────────────────
506
+
507
+ function showHelp() {
508
+ blank();
509
+ log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — CLI${RESET}`);
510
+ log(` ${DIM}The premium glassmorphism design system${RESET}`);
511
+ blank();
512
+ log(` ${BOLD}${WHITE}Commands:${RESET}`);
513
+ blank();
514
+ log(` ${CYAN}npx omnira-ui init${RESET} Scaffold the full design system`);
515
+ log(` ${CYAN}npx omnira-ui add <Component>${RESET} Add a single component`);
516
+ log(` ${CYAN}npx omnira-ui add${RESET} List all available components`);
517
+ log(` ${CYAN}npx omnira-ui help${RESET} Show this help message`);
518
+ blank();
519
+ log(` ${BOLD}${WHITE}Examples:${RESET}`);
520
+ blank();
521
+ log(` ${CYAN}npx omnira-ui add Table${RESET}`);
522
+ log(` ${CYAN}npx omnira-ui add Button Badge Input${RESET}`);
523
+ log(` ${CYAN}npx omnira-ui init${RESET}`);
524
+ blank();
525
+ }
526
+
527
+ // ── Command router ──────────────────────────────────────────────────
528
+
529
+ const args = process.argv.slice(2);
530
+ const command = args[0]?.toLowerCase();
531
+
532
+ if (!command || command === "init") {
533
+ main().catch((err) => {
534
+ console.error(err);
535
+ process.exit(1);
536
+ });
537
+ } else if (command === "add") {
538
+ const componentNames = args.slice(1);
539
+ if (componentNames.length === 0) {
540
+ listComponents();
541
+ } else {
542
+ addComponents(componentNames);
543
+ }
544
+ } else if (command === "help" || command === "--help" || command === "-h") {
545
+ showHelp();
546
+ } else {
547
+ log(`\n ${RED}✗${RESET} Unknown command: "${command}"\n`);
548
+ showHelp();
549
+ }
@@ -0,0 +1,109 @@
1
+ /* ============================================
2
+ Activity Gauge — Glassmorphism Styles
3
+ ============================================ */
4
+
5
+ .gaugeCard {
6
+ display: flex;
7
+ flex-direction: column;
8
+ align-items: center;
9
+ gap: 16px;
10
+ padding: 24px;
11
+ border-radius: var(--radius-lg);
12
+ background: var(--color-bg-card);
13
+ border: 1px solid var(--color-border-standard);
14
+ box-shadow: var(--shadow-card);
15
+ backdrop-filter: var(--blur-standard);
16
+ }
17
+
18
+ /* ── SVG Gauge ── */
19
+
20
+ .gaugeWrapper {
21
+ position: relative;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ }
26
+
27
+ .gaugeSvg {
28
+ transform: rotate(-90deg);
29
+ }
30
+
31
+ .gaugeTrack {
32
+ fill: none;
33
+ stroke: var(--color-bg-elevated);
34
+ stroke-linecap: round;
35
+ }
36
+
37
+ .gaugeFill {
38
+ fill: none;
39
+ stroke-linecap: round;
40
+ transition: stroke-dashoffset 0.6s ease;
41
+ }
42
+
43
+ .gaugeFillLime {
44
+ stroke: var(--color-lime);
45
+ }
46
+
47
+ .gaugeFillInfo {
48
+ stroke: var(--color-info);
49
+ }
50
+
51
+ .gaugeFillWarning {
52
+ stroke: var(--color-warning);
53
+ }
54
+
55
+ .gaugeFillError {
56
+ stroke: var(--color-error);
57
+ }
58
+
59
+ .gaugeFillSuccess {
60
+ stroke: var(--color-success);
61
+ }
62
+
63
+ /* ── Center label ── */
64
+
65
+ .gaugeCenter {
66
+ position: absolute;
67
+ display: flex;
68
+ flex-direction: column;
69
+ align-items: center;
70
+ justify-content: center;
71
+ }
72
+
73
+ .gaugeValue {
74
+ font-family: var(--font-display);
75
+ font-size: 28px;
76
+ font-weight: 700;
77
+ color: var(--color-text-primary);
78
+ line-height: 1;
79
+ }
80
+
81
+ .gaugeUnit {
82
+ font-size: 12px;
83
+ color: var(--color-text-tertiary);
84
+ margin-top: 2px;
85
+ }
86
+
87
+ /* ── Footer ── */
88
+
89
+ .gaugeLabel {
90
+ font-size: 14px;
91
+ font-weight: 500;
92
+ color: var(--color-text-secondary);
93
+ text-align: center;
94
+ }
95
+
96
+ .gaugeDescription {
97
+ font-size: 12px;
98
+ color: var(--color-text-tertiary);
99
+ text-align: center;
100
+ margin-top: -8px;
101
+ }
102
+
103
+ /* ── Grid layout for demos ── */
104
+
105
+ .gaugeGrid {
106
+ display: grid;
107
+ grid-template-columns: repeat(4, 1fr);
108
+ gap: 16px;
109
+ }
@@ -0,0 +1,87 @@
1
+ import { cn } from "@/lib/cn";
2
+ import styles from "./ActivityGauge.module.css";
3
+
4
+ /* ── Types ── */
5
+
6
+ export type GaugeColor = "lime" | "info" | "warning" | "error" | "success";
7
+
8
+ export interface ActivityGaugeProps {
9
+ value: number;
10
+ max?: number;
11
+ label?: string;
12
+ description?: string;
13
+ unit?: string;
14
+ color?: GaugeColor;
15
+ size?: number;
16
+ strokeWidth?: number;
17
+ className?: string;
18
+ }
19
+
20
+ /* ── Helpers ── */
21
+
22
+ function getColorClass(color: GaugeColor) {
23
+ switch (color) {
24
+ case "lime": return styles.gaugeFillLime;
25
+ case "info": return styles.gaugeFillInfo;
26
+ case "warning": return styles.gaugeFillWarning;
27
+ case "error": return styles.gaugeFillError;
28
+ case "success": return styles.gaugeFillSuccess;
29
+ default: return styles.gaugeFillLime;
30
+ }
31
+ }
32
+
33
+ /* ── Component ── */
34
+
35
+ export function ActivityGauge({
36
+ value,
37
+ max = 100,
38
+ label,
39
+ description,
40
+ unit = "%",
41
+ color = "lime",
42
+ size = 140,
43
+ strokeWidth = 10,
44
+ className,
45
+ }: ActivityGaugeProps) {
46
+ const radius = (size - strokeWidth) / 2;
47
+ const circumference = 2 * Math.PI * radius;
48
+ const percentage = Math.min(value / max, 1);
49
+ const offset = circumference * (1 - percentage);
50
+ const displayValue = unit === "%" ? Math.round(percentage * 100) : value;
51
+
52
+ return (
53
+ <div className={cn(styles.gaugeCard, className)}>
54
+ <div className={styles.gaugeWrapper} style={{ width: size, height: size }}>
55
+ <svg
56
+ className={styles.gaugeSvg}
57
+ width={size}
58
+ height={size}
59
+ viewBox={`0 0 ${size} ${size}`}
60
+ >
61
+ <circle
62
+ className={styles.gaugeTrack}
63
+ cx={size / 2}
64
+ cy={size / 2}
65
+ r={radius}
66
+ strokeWidth={strokeWidth}
67
+ />
68
+ <circle
69
+ className={cn(styles.gaugeFill, getColorClass(color))}
70
+ cx={size / 2}
71
+ cy={size / 2}
72
+ r={radius}
73
+ strokeWidth={strokeWidth}
74
+ strokeDasharray={circumference}
75
+ strokeDashoffset={offset}
76
+ />
77
+ </svg>
78
+ <div className={styles.gaugeCenter}>
79
+ <span className={styles.gaugeValue}>{displayValue}</span>
80
+ <span className={styles.gaugeUnit}>{unit}</span>
81
+ </div>
82
+ </div>
83
+ {label && <span className={styles.gaugeLabel}>{label}</span>}
84
+ {description && <span className={styles.gaugeDescription}>{description}</span>}
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,2 @@
1
+ export { ActivityGauge } from "./ActivityGauge";
2
+ export type { ActivityGaugeProps, GaugeColor } from "./ActivityGauge";