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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/README.md +353 -15
  4. data/lib/generators/nanoui/component_generator.rb +49 -14
  5. data/lib/generators/nanoui/templates/css/components/accordion.css +58 -58
  6. data/lib/generators/nanoui/templates/css/components/alert.css +56 -56
  7. data/lib/generators/nanoui/templates/css/components/avatar.css +88 -0
  8. data/lib/generators/nanoui/templates/css/components/badge.css +30 -30
  9. data/lib/generators/nanoui/templates/css/components/breadcrumb.css +54 -0
  10. data/lib/generators/nanoui/templates/css/components/button.css +136 -115
  11. data/lib/generators/nanoui/templates/css/components/card.css +35 -35
  12. data/lib/generators/nanoui/templates/css/components/checkbox.css +74 -72
  13. data/lib/generators/nanoui/templates/css/components/checklist.css +133 -0
  14. data/lib/generators/nanoui/templates/css/components/code.css +80 -0
  15. data/lib/generators/nanoui/templates/css/components/copy.css +71 -0
  16. data/lib/generators/nanoui/templates/css/components/dialog.css +103 -103
  17. data/lib/generators/nanoui/templates/css/components/dropdown.css +71 -71
  18. data/lib/generators/nanoui/templates/css/components/empty.css +68 -0
  19. data/lib/generators/nanoui/templates/css/components/input.css +72 -83
  20. data/lib/generators/nanoui/templates/css/components/label.css +11 -5
  21. data/lib/generators/nanoui/templates/css/components/navbar.css +128 -0
  22. data/lib/generators/nanoui/templates/css/components/progress.css +53 -47
  23. data/lib/generators/nanoui/templates/css/components/radio.css +73 -71
  24. data/lib/generators/nanoui/templates/css/components/select.css +49 -22
  25. data/lib/generators/nanoui/templates/css/components/sidebar.css +145 -0
  26. data/lib/generators/nanoui/templates/css/components/skeleton.css +49 -0
  27. data/lib/generators/nanoui/templates/css/components/stat.css +91 -0
  28. data/lib/generators/nanoui/templates/css/components/switch.css +25 -25
  29. data/lib/generators/nanoui/templates/css/components/table.css +169 -29
  30. data/lib/generators/nanoui/templates/css/components/tabs.css +20 -20
  31. data/lib/generators/nanoui/templates/css/components/timeline.css +141 -0
  32. data/lib/generators/nanoui/templates/css/components/toast.css +119 -68
  33. data/lib/generators/nanoui/templates/css/components/tooltip.css +79 -72
  34. data/lib/generators/nanoui/templates/css/components/upload.css +161 -0
  35. data/lib/generators/nanoui/templates/css/layout/container.css +6 -6
  36. data/lib/generators/nanoui/templates/js/controllers/copy_controller.js +64 -0
  37. data/lib/generators/nanoui/templates/js/controllers/data_table_controller.js +75 -0
  38. data/lib/generators/nanoui/templates/js/controllers/navbar_controller.js +70 -0
  39. data/lib/generators/nanoui/templates/js/controllers/sidebar_controller.js +27 -0
  40. data/lib/generators/nanoui/templates/js/controllers/upload_controller.js +137 -0
  41. data/lib/nanoui/version.rb +1 -1
  42. metadata +47 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f766927e1424d01ab34dd6f17a48b877f19de856399f5cb2584e86b6cc5accad
4
- data.tar.gz: a09a0124675316288e9ac788c0207f035b379ecf305f388f9da619a82002aeae
3
+ metadata.gz: 7853589240e9826e1a84eacb068ffd3ec668c5c459081cfe3a8d93fd4231c096
4
+ data.tar.gz: d62fa64e526a04c473f30355e0cde98c5441a7e136bff1813ff08a376af0728f
5
5
  SHA512:
6
- metadata.gz: ce86b42ceaf868b5fcc626d2f7cabcce77bfe273636e78cca191adbf783473c1b92fa0e44de1e0c1d44360aa9095048d78a0f959fb287a7aca4a55d576ae7d53
7
- data.tar.gz: 50b79fafd8493e2981d864cce626b121096eebd24a0206c670496bdace202bc6d85196bedd235ca2963b612a9418775c444b55fed8f7ae92f723057e46212e52
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
- 18 components. Semantic HTML. Accessible by default. No build step.
5
+ 30 components. Semantic HTML. Accessible by default. No build step.
6
6
 
7
- **[Documentation & Live Previews](https://chille1987.github.io/nanoui/)** — Browse all 18 components with interactive examples.
7
+ **[Documentation & Live Previews](https://chille1987.github.io/nanoui/)** — Browse all 30 components with interactive examples.
8
8
 
9
- ## What's New in v0.3.0
9
+ ## What's New in v0.6.0
10
10
 
11
- - **Native element styling** — Bare `<button>`, `<input>`, `<select>`, and `<label>` elements are styled out of the box. No `.nano-*` classes needed for default styling. Classes still work for variants.
12
- - **No @import needed** — Propshaft auto-loads all CSS files. Just run the generators and go.
13
- - **HTML-only components** — No ERB partials. Use plain HTML with CSS classes directly.
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 → data-controller="nanoui-dialog"
65
- nanoui_dropdown_controller.js → data-controller="nanoui-dropdown"
66
- nanoui_tooltip_controller.js → data-controller="nanoui-tooltip"
67
- nanoui_toast_controller.js → data-controller="nanoui-toast"
68
- nanoui_tabs_controller.js → data-controller="nanoui-tabs"
69
- nanoui_accordion_controller.js → data-controller="nanoui-accordion"
70
- nanoui_switch_controller.js → data-controller="nanoui-switch"
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" => %w[button input label checkbox radio switch select badge alert],
38
- "overlays" => %w[dialog dropdown tooltip toast],
39
- "data" => %w[table tabs accordion progress],
40
- "layout" => %w[container],
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
- STIMULUS_COMPONENTS = %w[dialog dropdown tooltip toast tabs accordion switch].freeze
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
- GROUPS.values.flatten.uniq
48
- elsif options[:group]
49
- GROUPS.fetch(options[:group]) { abort "Unknown group: #{options[:group]}" }
50
- else
51
- components
52
- end
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
- next unless STIMULUS_COMPONENTS.include?(name)
68
- copy_file "js/controllers/#{name}_controller.js",
69
- "app/javascript/controllers/nanoui_#{name}_controller.js"
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-accordion__content {
57
- padding: 0 var(--space-4) var(--space-4);
58
- font-size: var(--text-sm);
59
- line-height: var(--leading-normal);
60
- color: hsl(var(--color-muted-foreground));
61
- display: grid;
62
- grid-template-rows: 1fr;
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
  }