nanoui 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -0
- data/README.md +353 -15
- data/lib/generators/nanoui/component_generator.rb +49 -14
- data/lib/generators/nanoui/templates/css/components/accordion.css +58 -58
- data/lib/generators/nanoui/templates/css/components/alert.css +56 -56
- data/lib/generators/nanoui/templates/css/components/avatar.css +88 -0
- data/lib/generators/nanoui/templates/css/components/badge.css +30 -30
- data/lib/generators/nanoui/templates/css/components/breadcrumb.css +54 -0
- data/lib/generators/nanoui/templates/css/components/button.css +136 -115
- data/lib/generators/nanoui/templates/css/components/card.css +35 -35
- data/lib/generators/nanoui/templates/css/components/checkbox.css +74 -72
- data/lib/generators/nanoui/templates/css/components/checklist.css +133 -0
- data/lib/generators/nanoui/templates/css/components/code.css +80 -0
- data/lib/generators/nanoui/templates/css/components/copy.css +71 -0
- data/lib/generators/nanoui/templates/css/components/dialog.css +103 -103
- data/lib/generators/nanoui/templates/css/components/dropdown.css +71 -71
- data/lib/generators/nanoui/templates/css/components/empty.css +68 -0
- data/lib/generators/nanoui/templates/css/components/input.css +72 -83
- data/lib/generators/nanoui/templates/css/components/label.css +11 -5
- data/lib/generators/nanoui/templates/css/components/navbar.css +128 -0
- data/lib/generators/nanoui/templates/css/components/progress.css +53 -47
- data/lib/generators/nanoui/templates/css/components/radio.css +73 -71
- data/lib/generators/nanoui/templates/css/components/select.css +49 -22
- data/lib/generators/nanoui/templates/css/components/sidebar.css +145 -0
- data/lib/generators/nanoui/templates/css/components/skeleton.css +49 -0
- data/lib/generators/nanoui/templates/css/components/stat.css +91 -0
- data/lib/generators/nanoui/templates/css/components/switch.css +25 -25
- data/lib/generators/nanoui/templates/css/components/table.css +169 -29
- data/lib/generators/nanoui/templates/css/components/tabs.css +20 -20
- data/lib/generators/nanoui/templates/css/components/timeline.css +141 -0
- data/lib/generators/nanoui/templates/css/components/toast.css +119 -68
- data/lib/generators/nanoui/templates/css/components/tooltip.css +79 -72
- data/lib/generators/nanoui/templates/css/components/upload.css +161 -0
- data/lib/generators/nanoui/templates/css/layout/container.css +6 -6
- data/lib/generators/nanoui/templates/js/controllers/copy_controller.js +64 -0
- data/lib/generators/nanoui/templates/js/controllers/data_table_controller.js +75 -0
- data/lib/generators/nanoui/templates/js/controllers/navbar_controller.js +70 -0
- data/lib/generators/nanoui/templates/js/controllers/sidebar_controller.js +27 -0
- data/lib/generators/nanoui/templates/js/controllers/upload_controller.js +137 -0
- data/lib/nanoui/version.rb +1 -1
- metadata +47 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7853589240e9826e1a84eacb068ffd3ec668c5c459081cfe3a8d93fd4231c096
|
|
4
|
+
data.tar.gz: d62fa64e526a04c473f30355e0cde98c5441a7e136bff1813ff08a376af0728f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb21d047b98e632f831fdf5dc96a0001f7cb6222ca566dd60b72a3b23ac5968f15b6b07dc0a4d0b7a7c35410607c4f1defcc1a858ce468a50f165f085e5c0a96
|
|
7
|
+
data.tar.gz: f1710c2cc03164d20663095e0b0acb7fae811498f754c43c23236d02c99e400c6e85f5771168ae56a628d7526c3181049af08886e6957df09e5bc9c9e770f1a7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 (2026-04-17)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Stat** component — dashboard KPI card with label, value, delta, and optional footer (CSS only)
|
|
8
|
+
- **Empty state** component — centered "no data yet" / "get started" layout with icon, title, description, and actions (CSS only)
|
|
9
|
+
- **Timeline** component — vertical activity feed with color-coded markers for retry history, audit logs, and status changes (CSS only)
|
|
10
|
+
- **Checklist** component — onboarding / setup checklist with pending / current / done states and optional progress summary (CSS only)
|
|
11
|
+
- **Copy** component — one-click copy-to-clipboard button with transient success state and `execCommand` fallback (CSS + Stimulus)
|
|
12
|
+
- **Code** component — monospace block for snippets, inline fragments, and terminal-style output (CSS only)
|
|
13
|
+
- **Upload** component — drag-and-drop file input with preview, size/type validation, and accessible fallback (CSS + Stimulus)
|
|
14
|
+
- **Data-table Stimulus controller** — click-to-sort headers (`string` / `number` / `date`), dispatches `nanoui-data-table:sort` for server-side sorting; paired with new CSS for `.nano-table__header--sortable` and `.nano-table-pagination`
|
|
15
|
+
- **Toast action buttons** — `.nano-toast__action` is now a fully styled inline CTA that adopts each variant's accent color
|
|
16
|
+
- New component groups: `dashboard` (stat, empty, timeline, checklist) and `utilities` (copy, code)
|
|
17
|
+
- GitHub Actions workflows for CI (RSpec on Ruby 3.1 / 3.2 / 3.3 + Rubocop) and Release (automated gem publish on `v*.*.*` tags)
|
|
18
|
+
- Rubocop config with `rubocop-rspec` plugin
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Component count: 23 → 30
|
|
23
|
+
- Stimulus controllers: 9 → 12
|
|
24
|
+
- Component generator now maps each component to its controller files via `CONTROLLER_FILES`; installing `table` automatically installs the richer `data_table` controller
|
|
25
|
+
- Gemspec declares `rubocop` and `rubocop-rspec` as development dependencies
|
|
26
|
+
|
|
27
|
+
## 0.5.0 (2026-03-21)
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **Navbar** component — Responsive top navigation bar with mobile hamburger menu (CSS + Stimulus)
|
|
32
|
+
- **Sidebar** component — Collapsible sidebar navigation panel with groups and icons (CSS + Stimulus)
|
|
33
|
+
- **Breadcrumb** component — Navigation trail with separators (CSS only)
|
|
34
|
+
- **Avatar** component — Circular avatar with image/initials fallback and status indicator (CSS only)
|
|
35
|
+
- **Skeleton** component — Loading placeholder with shimmer animation (CSS only)
|
|
36
|
+
- New component groups: `navigation` (navbar, sidebar, breadcrumb) and `feedback` (avatar, skeleton)
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Refactored all CSS to use native CSS nesting for improved readability and maintainability
|
|
41
|
+
- Component count: 18 → 23
|
|
42
|
+
- Stimulus controllers: 7 → 9
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 0.4.0 (2026-03-15)
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **Container** layout component with sm, md, lg sizes
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
3
54
|
## 0.3.0 (2026-03-12)
|
|
4
55
|
|
|
5
56
|
### Breaking changes
|
data/README.md
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Vanilla CSS + Stimulus component library for Rails. Zero runtime dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
30 components. Semantic HTML. Accessible by default. No build step.
|
|
6
6
|
|
|
7
|
-
**[Documentation & Live Previews](https://chille1987.github.io/nanoui/)** — Browse all
|
|
7
|
+
**[Documentation & Live Previews](https://chille1987.github.io/nanoui/)** — Browse all 30 components with interactive examples.
|
|
8
8
|
|
|
9
|
-
## What's New in v0.
|
|
9
|
+
## What's New in v0.6.0
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
11
|
+
- **7 new components** — Stat, Empty state, Timeline, Checklist, Copy, Code, Upload
|
|
12
|
+
- **Sortable data tables** — `nanoui-data-table` Stimulus controller with client- or server-side sorting + pagination styling
|
|
13
|
+
- **Toast action buttons** — inline CTAs styled to match each toast variant
|
|
14
|
+
- **New component groups** — `dashboard` (stat, empty, timeline, checklist) and `utilities` (copy, code)
|
|
15
|
+
- **CI + release automation** — GitHub Actions for RSpec / Rubocop and tag-driven gem publishing
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
16
18
|
|
|
@@ -61,13 +63,18 @@ If NanoUI falls back to system fonts instead of Inter:
|
|
|
61
63
|
With Rails 8 + importmap + `eagerLoadControllersFrom`, controllers auto-register by file name. Rename the controllers with a `nanoui_` prefix:
|
|
62
64
|
|
|
63
65
|
```
|
|
64
|
-
nanoui_dialog_controller.js
|
|
65
|
-
nanoui_dropdown_controller.js
|
|
66
|
-
nanoui_tooltip_controller.js
|
|
67
|
-
nanoui_toast_controller.js
|
|
68
|
-
nanoui_tabs_controller.js
|
|
69
|
-
nanoui_accordion_controller.js
|
|
70
|
-
nanoui_switch_controller.js
|
|
66
|
+
nanoui_dialog_controller.js → data-controller="nanoui-dialog"
|
|
67
|
+
nanoui_dropdown_controller.js → data-controller="nanoui-dropdown"
|
|
68
|
+
nanoui_tooltip_controller.js → data-controller="nanoui-tooltip"
|
|
69
|
+
nanoui_toast_controller.js → data-controller="nanoui-toast"
|
|
70
|
+
nanoui_tabs_controller.js → data-controller="nanoui-tabs"
|
|
71
|
+
nanoui_accordion_controller.js → data-controller="nanoui-accordion"
|
|
72
|
+
nanoui_switch_controller.js → data-controller="nanoui-switch"
|
|
73
|
+
nanoui_navbar_controller.js → data-controller="nanoui-navbar"
|
|
74
|
+
nanoui_sidebar_controller.js → data-controller="nanoui-sidebar"
|
|
75
|
+
nanoui_copy_controller.js → data-controller="nanoui-copy"
|
|
76
|
+
nanoui_upload_controller.js → data-controller="nanoui-upload"
|
|
77
|
+
nanoui_data_table_controller.js → data-controller="nanoui-data-table"
|
|
71
78
|
```
|
|
72
79
|
|
|
73
80
|
Or register manually:
|
|
@@ -464,6 +471,328 @@ Native `<progress>` element with custom styling.
|
|
|
464
471
|
|
|
465
472
|
---
|
|
466
473
|
|
|
474
|
+
### Navigation
|
|
475
|
+
|
|
476
|
+
#### Navbar
|
|
477
|
+
|
|
478
|
+
Responsive top navigation bar with mobile hamburger menu.
|
|
479
|
+
|
|
480
|
+
```html
|
|
481
|
+
<nav class="nano-navbar nano-navbar--sticky" data-controller="nanoui-navbar">
|
|
482
|
+
<a href="/" class="nano-navbar__brand">MyApp</a>
|
|
483
|
+
<ul class="nano-navbar__links" data-nanoui-navbar-target="links">
|
|
484
|
+
<li><a href="/dashboard" class="nano-navbar__link nano-navbar__link--active">Dashboard</a></li>
|
|
485
|
+
<li><a href="/projects" class="nano-navbar__link">Projects</a></li>
|
|
486
|
+
<li><a href="/settings" class="nano-navbar__link">Settings</a></li>
|
|
487
|
+
</ul>
|
|
488
|
+
<div class="nano-navbar__actions">
|
|
489
|
+
<button class="nano-btn--outline nano-btn--sm">Log out</button>
|
|
490
|
+
</div>
|
|
491
|
+
<button class="nano-navbar__toggle" data-action="nanoui-navbar#toggle"
|
|
492
|
+
data-nanoui-navbar-target="toggle" aria-label="Toggle menu">
|
|
493
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
494
|
+
<path d="M3 12h18M3 6h18M3 18h18"/>
|
|
495
|
+
</svg>
|
|
496
|
+
</button>
|
|
497
|
+
</nav>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Modifiers:** `--sticky`
|
|
501
|
+
|
|
502
|
+
#### Sidebar
|
|
503
|
+
|
|
504
|
+
Collapsible sidebar navigation panel with groups and icons.
|
|
505
|
+
|
|
506
|
+
```html
|
|
507
|
+
<aside class="nano-sidebar" data-controller="nanoui-sidebar">
|
|
508
|
+
<div class="nano-sidebar__header">
|
|
509
|
+
<span>MyApp</span>
|
|
510
|
+
<button class="nano-sidebar__toggle" data-action="nanoui-sidebar#toggle"
|
|
511
|
+
data-nanoui-sidebar-target="toggle">⟨</button>
|
|
512
|
+
</div>
|
|
513
|
+
<nav class="nano-sidebar__nav">
|
|
514
|
+
<div class="nano-sidebar__group">
|
|
515
|
+
<span class="nano-sidebar__group-label">Main</span>
|
|
516
|
+
<a href="/dashboard" class="nano-sidebar__item nano-sidebar__item--active">
|
|
517
|
+
<svg><!-- icon --></svg> <span>Dashboard</span>
|
|
518
|
+
</a>
|
|
519
|
+
<a href="/projects" class="nano-sidebar__item">
|
|
520
|
+
<svg><!-- icon --></svg> <span>Projects</span>
|
|
521
|
+
</a>
|
|
522
|
+
</div>
|
|
523
|
+
</nav>
|
|
524
|
+
<div class="nano-sidebar__footer">
|
|
525
|
+
<a href="/settings" class="nano-sidebar__item">
|
|
526
|
+
<svg><!-- icon --></svg> <span>Settings</span>
|
|
527
|
+
</a>
|
|
528
|
+
</div>
|
|
529
|
+
</aside>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Breadcrumb
|
|
533
|
+
|
|
534
|
+
Navigation trail with separators.
|
|
535
|
+
|
|
536
|
+
```html
|
|
537
|
+
<nav class="nano-breadcrumb" aria-label="Breadcrumb">
|
|
538
|
+
<ol class="nano-breadcrumb__list">
|
|
539
|
+
<li class="nano-breadcrumb__item">
|
|
540
|
+
<a href="/" class="nano-breadcrumb__link">Home</a>
|
|
541
|
+
<span class="nano-breadcrumb__separator" aria-hidden="true"></span>
|
|
542
|
+
</li>
|
|
543
|
+
<li class="nano-breadcrumb__item">
|
|
544
|
+
<a href="/projects" class="nano-breadcrumb__link">Projects</a>
|
|
545
|
+
<span class="nano-breadcrumb__separator" aria-hidden="true"></span>
|
|
546
|
+
</li>
|
|
547
|
+
<li class="nano-breadcrumb__item">
|
|
548
|
+
<span class="nano-breadcrumb__link" aria-current="page">Settings</span>
|
|
549
|
+
</li>
|
|
550
|
+
</ol>
|
|
551
|
+
</nav>
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
### Feedback
|
|
557
|
+
|
|
558
|
+
#### Avatar
|
|
559
|
+
|
|
560
|
+
Circular avatar with image or initials fallback and optional status indicator.
|
|
561
|
+
|
|
562
|
+
```html
|
|
563
|
+
<div class="nano-avatar">
|
|
564
|
+
<img src="/avatar.jpg" alt="Jane Doe" class="nano-avatar__image">
|
|
565
|
+
<span class="nano-avatar__status nano-avatar__status--online"></span>
|
|
566
|
+
</div>
|
|
567
|
+
|
|
568
|
+
<div class="nano-avatar nano-avatar--lg">
|
|
569
|
+
<span class="nano-avatar__fallback">JD</span>
|
|
570
|
+
</div>
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Sizes:** sm (32px), default (40px), lg (48px), xl (64px)
|
|
574
|
+
**Status:** `--online`, `--offline`, `--busy`
|
|
575
|
+
|
|
576
|
+
#### Skeleton
|
|
577
|
+
|
|
578
|
+
Loading placeholder with shimmer animation.
|
|
579
|
+
|
|
580
|
+
```html
|
|
581
|
+
<div class="nano-skeleton" style="width: 200px;"></div>
|
|
582
|
+
<div class="nano-skeleton nano-skeleton--text" style="width: 80%;"></div>
|
|
583
|
+
<div class="nano-skeleton nano-skeleton--circle"></div>
|
|
584
|
+
<div class="nano-skeleton nano-skeleton--card"></div>
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Variants:** default (1rem line), `--text` (0.75rem), `--circle` (40px), `--card` (200px)
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
### Dashboard
|
|
592
|
+
|
|
593
|
+
#### Stat
|
|
594
|
+
|
|
595
|
+
Dashboard KPI card with label, value, optional delta, helper text, and footer. Group multiples with `.nano-stat-grid`.
|
|
596
|
+
|
|
597
|
+
```html
|
|
598
|
+
<div class="nano-stat">
|
|
599
|
+
<span class="nano-stat__label">Recovered revenue</span>
|
|
600
|
+
<span class="nano-stat__value">$4,238</span>
|
|
601
|
+
<span class="nano-stat__delta nano-stat__delta--up">12% vs last month</span>
|
|
602
|
+
</div>
|
|
603
|
+
|
|
604
|
+
<div class="nano-stat-grid">
|
|
605
|
+
<div class="nano-stat">…</div>
|
|
606
|
+
<div class="nano-stat nano-stat--compact">…</div>
|
|
607
|
+
</div>
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Variants:** default, `--elevated`, `--bordered`, `--compact`
|
|
611
|
+
|
|
612
|
+
#### Empty state
|
|
613
|
+
|
|
614
|
+
Centered layout for "no data yet" or "get started" screens.
|
|
615
|
+
|
|
616
|
+
```html
|
|
617
|
+
<div class="nano-empty">
|
|
618
|
+
<div class="nano-empty__icon"><!-- SVG --></div>
|
|
619
|
+
<h3 class="nano-empty__title">No failed payments yet</h3>
|
|
620
|
+
<p class="nano-empty__description">When Stripe reports a failed charge we'll queue retries here.</p>
|
|
621
|
+
<div class="nano-empty__actions">
|
|
622
|
+
<button class="nano-btn nano-btn--primary">Connect Stripe</button>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
**Variants:** default, `--bordered` (dashed outline), `--compact`
|
|
628
|
+
|
|
629
|
+
#### Timeline
|
|
630
|
+
|
|
631
|
+
Vertical activity feed with color-coded markers.
|
|
632
|
+
|
|
633
|
+
```html
|
|
634
|
+
<ol class="nano-timeline">
|
|
635
|
+
<li class="nano-timeline__item nano-timeline__item--destructive">
|
|
636
|
+
<span class="nano-timeline__marker"><!-- SVG --></span>
|
|
637
|
+
<span class="nano-timeline__line"></span>
|
|
638
|
+
<div class="nano-timeline__body">
|
|
639
|
+
<div class="nano-timeline__header">
|
|
640
|
+
<span class="nano-timeline__title">Payment failed</span>
|
|
641
|
+
<time class="nano-timeline__time">3 days ago</time>
|
|
642
|
+
</div>
|
|
643
|
+
<span class="nano-timeline__description">Card declined — insufficient funds.</span>
|
|
644
|
+
</div>
|
|
645
|
+
</li>
|
|
646
|
+
</ol>
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**Item states:** default, `--info`, `--success`, `--warning`, `--destructive`
|
|
650
|
+
|
|
651
|
+
#### Checklist
|
|
652
|
+
|
|
653
|
+
Onboarding / setup checklist with pending, current, and done states. Drive the summary bar with the `--nano-checklist-progress` CSS variable.
|
|
654
|
+
|
|
655
|
+
```html
|
|
656
|
+
<div class="nano-checklist">
|
|
657
|
+
<div class="nano-checklist__summary">
|
|
658
|
+
<span><span class="nano-checklist__count">2 of 4</span> complete</span>
|
|
659
|
+
<span class="nano-checklist__bar" style="--nano-checklist-progress: 50%;">
|
|
660
|
+
<span class="nano-checklist__bar-fill"></span>
|
|
661
|
+
</span>
|
|
662
|
+
</div>
|
|
663
|
+
<div class="nano-checklist__item nano-checklist__item--done">
|
|
664
|
+
<span class="nano-checklist__indicator"><!-- check SVG --></span>
|
|
665
|
+
<div class="nano-checklist__body">
|
|
666
|
+
<span class="nano-checklist__title">Connect Stripe</span>
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="nano-checklist__item nano-checklist__item--current">
|
|
670
|
+
<span class="nano-checklist__indicator">3</span>
|
|
671
|
+
<div class="nano-checklist__body">
|
|
672
|
+
<span class="nano-checklist__title">Pick an email template</span>
|
|
673
|
+
<a class="nano-checklist__action" href="#">Choose template →</a>
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
</div>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
### Utilities
|
|
682
|
+
|
|
683
|
+
#### Copy to clipboard
|
|
684
|
+
|
|
685
|
+
Button (or button + value) that copies to clipboard with a transient success state. Uses the Clipboard API with a `document.execCommand` fallback.
|
|
686
|
+
|
|
687
|
+
```html
|
|
688
|
+
<div class="nano-copy" data-controller="nanoui-copy">
|
|
689
|
+
<span class="nano-copy__value" data-nanoui-copy-target="source">
|
|
690
|
+
https://app.example.com/webhooks/stripe
|
|
691
|
+
</span>
|
|
692
|
+
<button class="nano-copy__button"
|
|
693
|
+
data-nanoui-copy-target="button"
|
|
694
|
+
data-action="nanoui-copy#copy"
|
|
695
|
+
type="button">
|
|
696
|
+
<span data-nanoui-copy-target="idle">Copy</span>
|
|
697
|
+
<span data-nanoui-copy-target="copied" hidden>Copied</span>
|
|
698
|
+
</button>
|
|
699
|
+
</div>
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Dispatches `nanoui-copy:copied` with `{ text }` in the event detail.
|
|
703
|
+
|
|
704
|
+
#### Code
|
|
705
|
+
|
|
706
|
+
Monospace block with optional header, inline variant, terminal (dark) variant, and wrap mode.
|
|
707
|
+
|
|
708
|
+
```html
|
|
709
|
+
<div class="nano-code">
|
|
710
|
+
<div class="nano-code__header">
|
|
711
|
+
<span class="nano-code__language">shell</span>
|
|
712
|
+
</div>
|
|
713
|
+
<pre class="nano-code__body"><code>stripe listen --forward-to localhost:3000/webhooks/stripe</code></pre>
|
|
714
|
+
</div>
|
|
715
|
+
|
|
716
|
+
<!-- Inline -->
|
|
717
|
+
<p>Set <code class="nano-code nano-code--inline">STRIPE_WEBHOOK_SECRET</code> before deploying.</p>
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**Variants:** default, `--inline`, `--terminal`, `--wrap`
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
### Data table enhancements (built on Table)
|
|
725
|
+
|
|
726
|
+
Add sortable columns and pagination to any existing table. Installing the `table` component also copies `nanoui_data_table_controller.js`.
|
|
727
|
+
|
|
728
|
+
```html
|
|
729
|
+
<div class="nano-table-wrapper" data-controller="nanoui-data-table">
|
|
730
|
+
<table class="nano-table nano-table--hoverable">
|
|
731
|
+
<thead class="nano-table__head">
|
|
732
|
+
<tr>
|
|
733
|
+
<th class="nano-table__header nano-table__header--sortable"
|
|
734
|
+
data-nanoui-data-table-target="header"
|
|
735
|
+
data-sort-key="amount"
|
|
736
|
+
data-sort-type="number"
|
|
737
|
+
data-action="click->nanoui-data-table#sort">
|
|
738
|
+
<button type="button" class="nano-table__sort">Amount</button>
|
|
739
|
+
</th>
|
|
740
|
+
</tr>
|
|
741
|
+
</thead>
|
|
742
|
+
<tbody class="nano-table__body" data-nanoui-data-table-target="body">
|
|
743
|
+
<tr class="nano-table__row" data-nanoui-data-table-target="row">
|
|
744
|
+
<td class="nano-table__cell" data-sort-value="49">$49.00</td>
|
|
745
|
+
</tr>
|
|
746
|
+
</tbody>
|
|
747
|
+
</table>
|
|
748
|
+
</div>
|
|
749
|
+
|
|
750
|
+
<nav class="nano-table-pagination" aria-label="Table pagination">
|
|
751
|
+
<span class="nano-table-pagination__info">Showing <strong>1–10</strong> of <strong>42</strong></span>
|
|
752
|
+
<span class="nano-table-pagination__controls">
|
|
753
|
+
<a class="nano-table-pagination__button" aria-current="page" href="?page=1">1</a>
|
|
754
|
+
<a class="nano-table-pagination__button" href="?page=2">2</a>
|
|
755
|
+
</span>
|
|
756
|
+
</nav>
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
Set `data-nanoui-data-table-server-value="true"` to skip client-side sorting and only dispatch the `nanoui-data-table:sort` event (e.g. for Turbo Frames).
|
|
760
|
+
|
|
761
|
+
Supported `data-sort-type` values: `string` (default), `number`, `date`.
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
### Forms (extended)
|
|
766
|
+
|
|
767
|
+
#### Upload
|
|
768
|
+
|
|
769
|
+
Drag-and-drop file input with preview, `maxSize` / `accept` validation, and accessible fallback to the native picker. Wraps a hidden `<input type="file">` so standard form submission works unchanged (including `form_with` + Active Storage).
|
|
770
|
+
|
|
771
|
+
```html
|
|
772
|
+
<div class="nano-upload"
|
|
773
|
+
data-controller="nanoui-upload"
|
|
774
|
+
data-nanoui-upload-accept-value="image/*"
|
|
775
|
+
data-nanoui-upload-max-size-value="2097152"
|
|
776
|
+
data-action="dragover->nanoui-upload#onDragover dragleave->nanoui-upload#onDragleave drop->nanoui-upload#onDrop">
|
|
777
|
+
<input type="file" name="logo" class="nano-upload__input"
|
|
778
|
+
data-nanoui-upload-target="input"
|
|
779
|
+
data-action="change->nanoui-upload#onChange">
|
|
780
|
+
<label class="nano-upload__dropzone"
|
|
781
|
+
data-nanoui-upload-target="dropzone"
|
|
782
|
+
data-action="click->nanoui-upload#openPicker"
|
|
783
|
+
tabindex="0">
|
|
784
|
+
<span class="nano-upload__icon"><!-- SVG --></span>
|
|
785
|
+
<span class="nano-upload__prompt"><strong>Click to upload</strong> or drag and drop</span>
|
|
786
|
+
<span class="nano-upload__hint">PNG or JPG up to 2 MB</span>
|
|
787
|
+
</label>
|
|
788
|
+
<!-- preview element is toggled automatically via data-state -->
|
|
789
|
+
</div>
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
Dispatches `nanoui-upload:selected` and `nanoui-upload:removed`.
|
|
793
|
+
|
|
794
|
+
---
|
|
795
|
+
|
|
467
796
|
## Design Tokens
|
|
468
797
|
|
|
469
798
|
Customize your theme by editing the CSS custom properties:
|
|
@@ -495,15 +824,24 @@ All components update automatically, including dark mode.
|
|
|
495
824
|
| Group | Components |
|
|
496
825
|
|---|---|
|
|
497
826
|
| **Essentials** | Button, Input, Label, Card, Badge, Alert |
|
|
498
|
-
| **Forms** | Checkbox, Radio, Switch, Select |
|
|
827
|
+
| **Forms** | Checkbox, Radio, Switch, Select, Upload |
|
|
499
828
|
| **Overlays** | Dialog, Dropdown, Tooltip, Toast |
|
|
500
|
-
| **Data** | Table, Tabs, Accordion, Progress |
|
|
829
|
+
| **Data** | Table (with sortable data-table controller), Tabs, Accordion, Progress |
|
|
830
|
+
| **Navigation** | Navbar, Sidebar, Breadcrumb |
|
|
831
|
+
| **Feedback** | Avatar, Skeleton |
|
|
832
|
+
| **Dashboard** | Stat, Empty state, Timeline, Checklist |
|
|
833
|
+
| **Utilities** | Copy to clipboard, Code block |
|
|
834
|
+
| **Layout** | Container |
|
|
501
835
|
|
|
502
836
|
```bash
|
|
503
837
|
rails generate nanoui:component --group essentials
|
|
504
838
|
rails generate nanoui:component --group forms
|
|
505
839
|
rails generate nanoui:component --group overlays
|
|
506
840
|
rails generate nanoui:component --group data
|
|
841
|
+
rails generate nanoui:component --group navigation
|
|
842
|
+
rails generate nanoui:component --group feedback
|
|
843
|
+
rails generate nanoui:component --group dashboard
|
|
844
|
+
rails generate nanoui:component --group utilities
|
|
507
845
|
rails generate nanoui:component --all
|
|
508
846
|
```
|
|
509
847
|
|
|
@@ -22,6 +22,18 @@ module Nanoui
|
|
|
22
22
|
tabs
|
|
23
23
|
accordion
|
|
24
24
|
progress
|
|
25
|
+
navbar
|
|
26
|
+
sidebar
|
|
27
|
+
breadcrumb
|
|
28
|
+
avatar
|
|
29
|
+
skeleton
|
|
30
|
+
stat
|
|
31
|
+
empty
|
|
32
|
+
timeline
|
|
33
|
+
checklist
|
|
34
|
+
copy
|
|
35
|
+
code
|
|
36
|
+
upload
|
|
25
37
|
container
|
|
26
38
|
].freeze
|
|
27
39
|
|
|
@@ -34,22 +46,44 @@ module Nanoui
|
|
|
34
46
|
|
|
35
47
|
GROUPS = {
|
|
36
48
|
"essentials" => %w[button input label card badge alert],
|
|
37
|
-
"forms"
|
|
38
|
-
"overlays"
|
|
39
|
-
"data"
|
|
40
|
-
"
|
|
49
|
+
"forms" => %w[button input label checkbox radio switch select badge alert upload],
|
|
50
|
+
"overlays" => %w[dialog dropdown tooltip toast],
|
|
51
|
+
"data" => %w[table tabs accordion progress],
|
|
52
|
+
"navigation" => %w[navbar sidebar breadcrumb],
|
|
53
|
+
"feedback" => %w[avatar skeleton],
|
|
54
|
+
"dashboard" => %w[stat empty timeline checklist],
|
|
55
|
+
"utilities" => %w[copy code],
|
|
56
|
+
"layout" => %w[container],
|
|
41
57
|
}.freeze
|
|
42
58
|
|
|
43
|
-
|
|
59
|
+
# Maps a component name to the Stimulus controller files it installs.
|
|
60
|
+
# Most components ship with a same-named controller, but `table` ships
|
|
61
|
+
# with the richer `data_table` controller for sort + pagination behavior.
|
|
62
|
+
CONTROLLER_FILES = {
|
|
63
|
+
"dialog" => %w[dialog],
|
|
64
|
+
"dropdown" => %w[dropdown],
|
|
65
|
+
"tooltip" => %w[tooltip],
|
|
66
|
+
"toast" => %w[toast],
|
|
67
|
+
"tabs" => %w[tabs],
|
|
68
|
+
"accordion" => %w[accordion],
|
|
69
|
+
"switch" => %w[switch],
|
|
70
|
+
"navbar" => %w[navbar],
|
|
71
|
+
"sidebar" => %w[sidebar],
|
|
72
|
+
"copy" => %w[copy],
|
|
73
|
+
"upload" => %w[upload],
|
|
74
|
+
"table" => %w[data_table],
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
77
|
+
STIMULUS_COMPONENTS = CONTROLLER_FILES.keys.freeze
|
|
44
78
|
|
|
45
79
|
def resolve_components
|
|
46
80
|
@resolved = if options[:all]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
GROUPS.values.flatten.uniq
|
|
82
|
+
elsif options[:group]
|
|
83
|
+
GROUPS.fetch(options[:group]) { abort "Unknown group: #{options[:group]}" }
|
|
84
|
+
else
|
|
85
|
+
components
|
|
86
|
+
end
|
|
53
87
|
|
|
54
88
|
abort "Specify components, --group, or --all" if @resolved.empty?
|
|
55
89
|
end
|
|
@@ -64,9 +98,10 @@ module Nanoui
|
|
|
64
98
|
|
|
65
99
|
def copy_stimulus_controllers
|
|
66
100
|
@resolved.each do |name|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
101
|
+
CONTROLLER_FILES.fetch(name, []).each do |controller|
|
|
102
|
+
copy_file "js/controllers/#{controller}_controller.js",
|
|
103
|
+
"app/javascript/controllers/nanoui_#{controller}_controller.js"
|
|
104
|
+
end
|
|
70
105
|
end
|
|
71
106
|
end
|
|
72
107
|
|
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
.nano-accordion {
|
|
2
2
|
border: 1px solid hsl(var(--color-border));
|
|
3
3
|
border-radius: var(--radius-lg);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.nano-accordion__item {
|
|
7
|
-
border-bottom: 1px solid hsl(var(--color-border));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.nano-accordion__item:first-child {
|
|
11
|
-
border-top-left-radius: var(--radius-lg);
|
|
12
|
-
border-top-right-radius: var(--radius-lg);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.nano-accordion__item:last-child {
|
|
16
|
-
border-bottom: none;
|
|
17
|
-
border-bottom-left-radius: var(--radius-lg);
|
|
18
|
-
border-bottom-right-radius: var(--radius-lg);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.nano-accordion__trigger {
|
|
22
|
-
list-style: none;
|
|
23
|
-
display: flex;
|
|
24
|
-
align-items: center;
|
|
25
|
-
justify-content: space-between;
|
|
26
|
-
width: 100%;
|
|
27
|
-
padding: var(--space-4);
|
|
28
|
-
font-size: var(--text-sm);
|
|
29
|
-
font-weight: var(--font-medium);
|
|
30
|
-
color: hsl(var(--color-foreground));
|
|
31
|
-
cursor: pointer;
|
|
32
|
-
transition: background-color var(--duration-fast) var(--ease-default);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.nano-accordion__trigger:hover {
|
|
36
|
-
background-color: hsl(var(--color-muted) / 0.5);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.nano-accordion__trigger:focus-visible {
|
|
40
|
-
outline: 2px solid hsl(var(--color-ring));
|
|
41
|
-
outline-offset: -2px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.nano-accordion__icon {
|
|
45
|
-
width: 1rem;
|
|
46
|
-
height: 1rem;
|
|
47
|
-
flex-shrink: 0;
|
|
48
|
-
color: hsl(var(--color-muted-foreground));
|
|
49
|
-
transition: transform var(--duration-normal) var(--ease-default);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.nano-accordion__item[open] .nano-accordion__icon {
|
|
53
|
-
transform: rotate(180deg);
|
|
54
|
-
}
|
|
55
4
|
|
|
56
|
-
.nano-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
5
|
+
.nano-accordion__item {
|
|
6
|
+
border-bottom: 1px solid hsl(var(--color-border));
|
|
7
|
+
|
|
8
|
+
&:first-child {
|
|
9
|
+
border-top-left-radius: var(--radius-lg);
|
|
10
|
+
border-top-right-radius: var(--radius-lg);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&:last-child {
|
|
14
|
+
border-bottom: none;
|
|
15
|
+
border-bottom-left-radius: var(--radius-lg);
|
|
16
|
+
border-bottom-right-radius: var(--radius-lg);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&[open] .nano-accordion__icon {
|
|
20
|
+
transform: rotate(180deg);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.nano-accordion__trigger {
|
|
25
|
+
list-style: none;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
width: 100%;
|
|
30
|
+
padding: var(--space-4);
|
|
31
|
+
font-size: var(--text-sm);
|
|
32
|
+
font-weight: var(--font-medium);
|
|
33
|
+
color: hsl(var(--color-foreground));
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition: background-color var(--duration-fast) var(--ease-default);
|
|
36
|
+
|
|
37
|
+
&:hover {
|
|
38
|
+
background-color: hsl(var(--color-muted) / 0.5);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&:focus-visible {
|
|
42
|
+
outline: 2px solid hsl(var(--color-ring));
|
|
43
|
+
outline-offset: -2px;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.nano-accordion__icon {
|
|
48
|
+
width: 1rem;
|
|
49
|
+
height: 1rem;
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
color: hsl(var(--color-muted-foreground));
|
|
52
|
+
transition: transform var(--duration-normal) var(--ease-default);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.nano-accordion__content {
|
|
56
|
+
padding: 0 var(--space-4) var(--space-4);
|
|
57
|
+
font-size: var(--text-sm);
|
|
58
|
+
line-height: var(--leading-normal);
|
|
59
|
+
color: hsl(var(--color-muted-foreground));
|
|
60
|
+
display: grid;
|
|
61
|
+
grid-template-rows: 1fr;
|
|
62
|
+
}
|
|
63
63
|
}
|