nanoui 0.1.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 +7 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +542 -0
- data/lib/generators/nanoui/component_generator.rb +132 -0
- data/lib/generators/nanoui/install_generator.rb +53 -0
- data/lib/generators/nanoui/templates/css/base/_globals.css +41 -0
- data/lib/generators/nanoui/templates/css/base/_reset.css +8 -0
- data/lib/generators/nanoui/templates/css/components/_accordion.css +74 -0
- data/lib/generators/nanoui/templates/css/components/_alert.css +78 -0
- data/lib/generators/nanoui/templates/css/components/_badge.css +48 -0
- data/lib/generators/nanoui/templates/css/components/_button.css +116 -0
- data/lib/generators/nanoui/templates/css/components/_card.css +48 -0
- data/lib/generators/nanoui/templates/css/components/_checkbox.css +86 -0
- data/lib/generators/nanoui/templates/css/components/_dialog.css +122 -0
- data/lib/generators/nanoui/templates/css/components/_dropdown.css +84 -0
- data/lib/generators/nanoui/templates/css/components/_input.css +82 -0
- data/lib/generators/nanoui/templates/css/components/_label.css +11 -0
- data/lib/generators/nanoui/templates/css/components/_progress.css +66 -0
- data/lib/generators/nanoui/templates/css/components/_radio.css +95 -0
- data/lib/generators/nanoui/templates/css/components/_select.css +45 -0
- data/lib/generators/nanoui/templates/css/components/_switch.css +42 -0
- data/lib/generators/nanoui/templates/css/components/_table.css +53 -0
- data/lib/generators/nanoui/templates/css/components/_tabs.css +51 -0
- data/lib/generators/nanoui/templates/css/components/_toast.css +128 -0
- data/lib/generators/nanoui/templates/css/components/_tooltip.css +87 -0
- data/lib/generators/nanoui/templates/css/fonts/inter-variable.ttf +0 -0
- data/lib/generators/nanoui/templates/css/nanoui.css +34 -0
- data/lib/generators/nanoui/templates/css/nanoui.install.css +16 -0
- data/lib/generators/nanoui/templates/css/tokens/_colors.css +61 -0
- data/lib/generators/nanoui/templates/css/tokens/_radius.css +10 -0
- data/lib/generators/nanoui/templates/css/tokens/_shadows.css +7 -0
- data/lib/generators/nanoui/templates/css/tokens/_spacing.css +17 -0
- data/lib/generators/nanoui/templates/css/tokens/_transitions.css +10 -0
- data/lib/generators/nanoui/templates/css/tokens/_typography.css +28 -0
- data/lib/generators/nanoui/templates/css/tokens/_z-index.css +10 -0
- data/lib/generators/nanoui/templates/js/controllers/accordion_controller.js +21 -0
- data/lib/generators/nanoui/templates/js/controllers/dialog_controller.js +40 -0
- data/lib/generators/nanoui/templates/js/controllers/dropdown_controller.js +101 -0
- data/lib/generators/nanoui/templates/js/controllers/switch_controller.js +10 -0
- data/lib/generators/nanoui/templates/js/controllers/tabs_controller.js +72 -0
- data/lib/generators/nanoui/templates/js/controllers/toast_controller.js +39 -0
- data/lib/generators/nanoui/templates/js/controllers/tooltip_controller.js +34 -0
- data/lib/generators/nanoui/templates/views/components/_accordion.html.erb +40 -0
- data/lib/generators/nanoui/templates/views/components/_alert.html.erb +33 -0
- data/lib/generators/nanoui/templates/views/components/_badge.html.erb +18 -0
- data/lib/generators/nanoui/templates/views/components/_button.html.erb +27 -0
- data/lib/generators/nanoui/templates/views/components/_card.html.erb +43 -0
- data/lib/generators/nanoui/templates/views/components/_checkbox.html.erb +36 -0
- data/lib/generators/nanoui/templates/views/components/_dialog.html.erb +65 -0
- data/lib/generators/nanoui/templates/views/components/_dropdown.html.erb +29 -0
- data/lib/generators/nanoui/templates/views/components/_input.html.erb +42 -0
- data/lib/generators/nanoui/templates/views/components/_label.html.erb +20 -0
- data/lib/generators/nanoui/templates/views/components/_progress.html.erb +29 -0
- data/lib/generators/nanoui/templates/views/components/_radio_group.html.erb +46 -0
- data/lib/generators/nanoui/templates/views/components/_select.html.erb +67 -0
- data/lib/generators/nanoui/templates/views/components/_switch.html.erb +32 -0
- data/lib/generators/nanoui/templates/views/components/_table.html.erb +41 -0
- data/lib/generators/nanoui/templates/views/components/_tabs.html.erb +46 -0
- data/lib/generators/nanoui/templates/views/components/_toast.html.erb +33 -0
- data/lib/generators/nanoui/templates/views/components/_toast_container.html.erb +17 -0
- data/lib/generators/nanoui/templates/views/components/_tooltip.html.erb +28 -0
- data/lib/nanoui/version.rb +3 -0
- data/lib/nanoui.rb +5 -0
- metadata +134 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c33ae0d2c94bdda9bddcf979a57177770c5c50f7ff1602d6744431402507f49d
|
|
4
|
+
data.tar.gz: d8d0c324e74ed81eb1fd84f985a81414ee3c343c034824457d84ee1856fe7fc4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b53749cc0036143533f008abf49da63c4a5de75d38b04958b196224461f0c962bcbfce352d18a584825bdf3bd3e119b54541597a5850b2bb3fc6b3aca8b29ec7
|
|
7
|
+
data.tar.gz: 73b2f16489e7669236551edf07f873ab2f3053ce96b40771efb54f49aa169cc62a1d84d9ad0b55e766a50c0e0c3b31705d65a4ea5bdd14279a2d846ea06a6859
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-03-09)
|
|
4
|
+
|
|
5
|
+
### Initial release
|
|
6
|
+
|
|
7
|
+
**Design tokens:** colors, typography, spacing, radius, shadows, transitions, z-index
|
|
8
|
+
|
|
9
|
+
**Base styles:** reset, globals
|
|
10
|
+
|
|
11
|
+
**Components (18):**
|
|
12
|
+
- button, input, label, card
|
|
13
|
+
- checkbox, radio, switch, select
|
|
14
|
+
- badge, alert
|
|
15
|
+
- dialog, dropdown, tooltip, toast
|
|
16
|
+
- table, tabs, accordion, progress
|
|
17
|
+
|
|
18
|
+
**Component groups:**
|
|
19
|
+
- `essentials` — button, input, label, card, badge, alert
|
|
20
|
+
- `forms` — button, input, label, checkbox, radio, switch, select, badge, alert
|
|
21
|
+
- `overlays` — dialog, dropdown, tooltip, toast
|
|
22
|
+
- `data` — table, tabs, accordion, progress
|
|
23
|
+
|
|
24
|
+
**Stimulus controllers (7):** dialog, dropdown, tooltip, toast, tabs, accordion, switch
|
|
25
|
+
|
|
26
|
+
**Generators:**
|
|
27
|
+
- `nanoui:install` — copies tokens, base styles, fonts, and entry point CSS
|
|
28
|
+
- `nanoui:component` — copies component CSS, Stimulus controllers, and ERB partials
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Denis Omerovic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
# NanoUI
|
|
2
|
+
|
|
3
|
+
Vanilla CSS + Stimulus component library for Rails. Zero runtime dependencies.
|
|
4
|
+
|
|
5
|
+
18 components. Semantic HTML. Accessible by default. No build step.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### Option A: Rubygem with Generators (recommended)
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Gemfile
|
|
13
|
+
gem "nanoui", group: :development
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bundle install
|
|
18
|
+
rails generate nanoui:install # Tokens, base, fonts
|
|
19
|
+
rails generate nanoui:component --all # All components
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Option B: Manual Installation
|
|
23
|
+
|
|
24
|
+
Copy files directly into your Rails 8 app:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. CSS (tokens, base, components, Inter Variable font)
|
|
28
|
+
cp -r lib/generators/nanoui/templates/css/ app/assets/stylesheets/nanoui/
|
|
29
|
+
|
|
30
|
+
# 2. Stimulus controllers
|
|
31
|
+
cp lib/generators/nanoui/templates/js/controllers/*_controller.js \
|
|
32
|
+
app/javascript/controllers/
|
|
33
|
+
|
|
34
|
+
# 3. ERB partials (optional — you can use CSS classes directly)
|
|
35
|
+
cp lib/generators/nanoui/templates/views/components/ app/views/components/
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Import CSS
|
|
39
|
+
|
|
40
|
+
Add to `app/assets/stylesheets/application.css`:
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
@import "nanoui/nanoui.css";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`rails generate nanoui:install` now creates a foundation-only `nanoui.css`, and each `nanoui:component` run rewrites the component imports so the file only references CSS that actually exists.
|
|
47
|
+
|
|
48
|
+
NanoUI ships with Inter Variable and expects it at `app/assets/stylesheets/nanoui/fonts/inter-variable.ttf`, which `rails generate nanoui:install` now copies automatically.
|
|
49
|
+
|
|
50
|
+
Or if using Propshaft, add the import to your layout:
|
|
51
|
+
|
|
52
|
+
```erb
|
|
53
|
+
<%= stylesheet_link_tag "nanoui/nanoui.css" %>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Troubleshooting Fonts
|
|
57
|
+
|
|
58
|
+
If NanoUI falls back to system fonts instead of Inter:
|
|
59
|
+
|
|
60
|
+
- Confirm the font file exists at `app/assets/stylesheets/nanoui/fonts/inter-variable.ttf`
|
|
61
|
+
- Confirm `app/assets/stylesheets/nanoui/base/_globals.css` still points to `url("../fonts/inter-variable.ttf")`
|
|
62
|
+
- If you installed NanoUI before this change, rerun `rails generate nanoui:install` or copy `lib/generators/nanoui/templates/css/fonts/inter-variable.ttf` into `app/assets/stylesheets/nanoui/fonts/`
|
|
63
|
+
- In the browser devtools Network tab, verify `inter-variable.ttf` loads successfully instead of returning `404`
|
|
64
|
+
|
|
65
|
+
### Register Stimulus Controllers
|
|
66
|
+
|
|
67
|
+
With Rails 8 + importmap + `eagerLoadControllersFrom`, controllers auto-register by file name. Rename the controllers with a `nanoui_` prefix:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
nanoui_dialog_controller.js → data-controller="nanoui-dialog"
|
|
71
|
+
nanoui_dropdown_controller.js → data-controller="nanoui-dropdown"
|
|
72
|
+
nanoui_tooltip_controller.js → data-controller="nanoui-tooltip"
|
|
73
|
+
nanoui_toast_controller.js → data-controller="nanoui-toast"
|
|
74
|
+
nanoui_tabs_controller.js → data-controller="nanoui-tabs"
|
|
75
|
+
nanoui_accordion_controller.js → data-controller="nanoui-accordion"
|
|
76
|
+
nanoui_switch_controller.js → data-controller="nanoui-switch"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or register manually:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
// app/javascript/controllers/index.js
|
|
83
|
+
import DialogController from "./nanoui_dialog_controller"
|
|
84
|
+
application.register("nanoui-dialog", DialogController)
|
|
85
|
+
// ... repeat for each controller
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Dark Mode
|
|
89
|
+
|
|
90
|
+
Add the `.dark` class to `<html>` to toggle dark mode. All color tokens swap automatically.
|
|
91
|
+
|
|
92
|
+
```erb
|
|
93
|
+
<!-- In your layout -->
|
|
94
|
+
<html class="<%= "dark" if user_prefers_dark? %>">
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or toggle via JavaScript:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
document.documentElement.classList.toggle("dark")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Component Reference
|
|
106
|
+
|
|
107
|
+
### Essentials
|
|
108
|
+
|
|
109
|
+
#### Button
|
|
110
|
+
|
|
111
|
+
6 variants, 3 sizes, icon-only, loading state.
|
|
112
|
+
|
|
113
|
+
```erb
|
|
114
|
+
<%= render "components/button", variant: "primary" do %>
|
|
115
|
+
Save Changes
|
|
116
|
+
<% end %>
|
|
117
|
+
|
|
118
|
+
<%= render "components/button", variant: "outline", size: "sm" do %>
|
|
119
|
+
Cancel
|
|
120
|
+
<% end %>
|
|
121
|
+
|
|
122
|
+
<%= render "components/button", variant: "destructive" do %>
|
|
123
|
+
Delete
|
|
124
|
+
<% end %>
|
|
125
|
+
|
|
126
|
+
<%# Link styled as button %>
|
|
127
|
+
<%= render "components/button", href: "/about", variant: "ghost" do %>
|
|
128
|
+
Learn More
|
|
129
|
+
<% end %>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Or use classes directly:
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<button class="button button--primary" type="button">Save</button>
|
|
136
|
+
<button class="button button--outline button--sm" type="button">Cancel</button>
|
|
137
|
+
<button class="button button--primary" disabled>Disabled</button>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Options:** `variant` (primary, secondary, destructive, outline, ghost, link), `size` (sm, lg, icon), `tag` (:button), `href`, `class`, `html`
|
|
141
|
+
|
|
142
|
+
#### Input
|
|
143
|
+
|
|
144
|
+
Text fields wrapped in `.field` with label and error support.
|
|
145
|
+
|
|
146
|
+
```erb
|
|
147
|
+
<%= render "components/input", label: "Email", type: "email", required: true,
|
|
148
|
+
html: { placeholder: "you@example.com" } %>
|
|
149
|
+
|
|
150
|
+
<%= render "components/input", label: "Name", error: "Name is required" %>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<div class="field">
|
|
155
|
+
<label for="email" class="label label--required">Email</label>
|
|
156
|
+
<input id="email" type="email" class="input" placeholder="you@example.com" required>
|
|
157
|
+
</div>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Options:** `label`, `type` ("text"), `required`, `error`, `id`, `class`, `html`
|
|
161
|
+
|
|
162
|
+
#### Label
|
|
163
|
+
|
|
164
|
+
```erb
|
|
165
|
+
<%= render "components/label", for: "name", required: true do %>Name<% end %>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<label for="name" class="label label--required">Name</label>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### Card
|
|
173
|
+
|
|
174
|
+
Container with header, content, and footer sections.
|
|
175
|
+
|
|
176
|
+
```erb
|
|
177
|
+
<%= render "components/card",
|
|
178
|
+
title: "Settings",
|
|
179
|
+
description: "Manage your account.",
|
|
180
|
+
footer: render("components/button", variant: "primary") { "Save" } do %>
|
|
181
|
+
<p>Card body content here.</p>
|
|
182
|
+
<% end %>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<article class="card">
|
|
187
|
+
<div class="card__header">
|
|
188
|
+
<h3 class="card__title">Settings</h3>
|
|
189
|
+
<p class="card__description">Manage your account.</p>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="card__content">...</div>
|
|
192
|
+
<div class="card__footer">...</div>
|
|
193
|
+
</article>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Variants:** default, `elevated`, `bordered`
|
|
197
|
+
|
|
198
|
+
#### Badge
|
|
199
|
+
|
|
200
|
+
Inline status indicators.
|
|
201
|
+
|
|
202
|
+
```erb
|
|
203
|
+
<%= render "components/badge", variant: "success" do %>Active<% end %>
|
|
204
|
+
<%= render "components/badge", variant: "warning" do %>Pending<% end %>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<span class="badge badge--success">Active</span>
|
|
209
|
+
<span class="badge badge--destructive">Failed</span>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Variants:** primary, secondary, destructive, outline, success, warning
|
|
213
|
+
|
|
214
|
+
#### Alert
|
|
215
|
+
|
|
216
|
+
Contextual feedback with icon, title, and description.
|
|
217
|
+
|
|
218
|
+
```erb
|
|
219
|
+
<%= render "components/alert", variant: "success", title: "Saved!" do %>
|
|
220
|
+
Your changes have been saved successfully.
|
|
221
|
+
<% end %>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```html
|
|
225
|
+
<div class="alert alert--success" role="alert">
|
|
226
|
+
<div class="alert__icon"><!-- SVG --></div>
|
|
227
|
+
<div class="alert__content">
|
|
228
|
+
<p class="alert__title">Saved!</p>
|
|
229
|
+
<p class="alert__description">Your changes have been saved.</p>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Variants:** default, destructive, success, warning
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### Forms
|
|
239
|
+
|
|
240
|
+
#### Checkbox
|
|
241
|
+
|
|
242
|
+
```erb
|
|
243
|
+
<%= render "components/checkbox", label: "Accept terms", name: "tos", error: true %>
|
|
244
|
+
<%= render "components/checkbox", label: "Remember me", checked: true %>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```html
|
|
248
|
+
<div class="checkbox">
|
|
249
|
+
<input type="checkbox" id="tos" class="checkbox__input" name="tos">
|
|
250
|
+
<label for="tos" class="checkbox__label">Accept terms</label>
|
|
251
|
+
</div>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Options:** `label`, `name`, `value`, `checked`, `disabled`, `error`, `id`
|
|
255
|
+
|
|
256
|
+
#### Radio Group
|
|
257
|
+
|
|
258
|
+
```erb
|
|
259
|
+
<%= render "components/radio_group",
|
|
260
|
+
legend: "Plan",
|
|
261
|
+
name: "plan",
|
|
262
|
+
options: [
|
|
263
|
+
{ label: "Free", value: "free", checked: true },
|
|
264
|
+
{ label: "Pro", value: "pro" },
|
|
265
|
+
{ label: "Enterprise", value: "enterprise" }
|
|
266
|
+
] %>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```html
|
|
270
|
+
<fieldset class="radio-group">
|
|
271
|
+
<legend class="radio-group__legend">Plan</legend>
|
|
272
|
+
<div class="radio">
|
|
273
|
+
<input type="radio" id="plan-0" name="plan" value="free" class="radio__input" checked>
|
|
274
|
+
<label for="plan-0" class="radio__label">Free</label>
|
|
275
|
+
</div>
|
|
276
|
+
<!-- ... -->
|
|
277
|
+
</fieldset>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### Switch
|
|
281
|
+
|
|
282
|
+
Toggle switch with Stimulus controller.
|
|
283
|
+
|
|
284
|
+
```erb
|
|
285
|
+
<%= render "components/switch", label: "Enable notifications", checked: true %>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```html
|
|
289
|
+
<button type="button" role="switch" aria-checked="true" class="switch"
|
|
290
|
+
data-controller="nanoui-switch" data-action="nanoui-switch#toggle">
|
|
291
|
+
<span class="switch__thumb"></span>
|
|
292
|
+
<span class="sr-only">Enable notifications</span>
|
|
293
|
+
</button>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### Select
|
|
297
|
+
|
|
298
|
+
Native `<select>` with custom styling.
|
|
299
|
+
|
|
300
|
+
```erb
|
|
301
|
+
<%= render "components/select",
|
|
302
|
+
label: "Country",
|
|
303
|
+
placeholder: "Select a country",
|
|
304
|
+
options: ["United States", "Canada", "United Kingdom"] %>
|
|
305
|
+
|
|
306
|
+
<%= render "components/select",
|
|
307
|
+
label: "Role",
|
|
308
|
+
required: true,
|
|
309
|
+
options: [
|
|
310
|
+
{ label: "Admin", value: "admin" },
|
|
311
|
+
{ label: "Editor", value: "editor" }
|
|
312
|
+
] %>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Options:** `label`, `placeholder`, `options`, `required`, `error`, `disabled`, `name`, `id`
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
### Overlays
|
|
320
|
+
|
|
321
|
+
#### Dialog
|
|
322
|
+
|
|
323
|
+
Native `<dialog>` with `showModal()` — free focus trap, Escape close, and `::backdrop`.
|
|
324
|
+
|
|
325
|
+
```erb
|
|
326
|
+
<%= render "components/dialog",
|
|
327
|
+
title: "Edit Profile",
|
|
328
|
+
description: "Update your info.",
|
|
329
|
+
trigger: render("components/button") { "Open" },
|
|
330
|
+
footer: safe_join([
|
|
331
|
+
render("components/button", variant: "outline", html: { data: { action: "nanoui-dialog#close" } }) { "Cancel" },
|
|
332
|
+
render("components/button", variant: "primary") { "Save" }
|
|
333
|
+
]) do %>
|
|
334
|
+
<p>Dialog body content.</p>
|
|
335
|
+
<% end %>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
```html
|
|
339
|
+
<div data-controller="nanoui-dialog">
|
|
340
|
+
<button data-action="nanoui-dialog#open">Open</button>
|
|
341
|
+
|
|
342
|
+
<dialog data-nanoui-dialog-target="modal" class="dialog"
|
|
343
|
+
aria-labelledby="dialog-title">
|
|
344
|
+
<div class="dialog__content">
|
|
345
|
+
<header class="dialog__header">
|
|
346
|
+
<h2 id="dialog-title" class="dialog__title">Title</h2>
|
|
347
|
+
</header>
|
|
348
|
+
<div class="dialog__body">...</div>
|
|
349
|
+
<footer class="dialog__footer">...</footer>
|
|
350
|
+
<button class="dialog__close" data-action="nanoui-dialog#close"
|
|
351
|
+
aria-label="Close dialog">...</button>
|
|
352
|
+
</div>
|
|
353
|
+
</dialog>
|
|
354
|
+
</div>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Sizes:** `sm` (24rem), default/`md` (32rem), `lg` (42rem), `full`
|
|
358
|
+
|
|
359
|
+
#### Dropdown
|
|
360
|
+
|
|
361
|
+
Click-activated menu with keyboard navigation.
|
|
362
|
+
|
|
363
|
+
```erb
|
|
364
|
+
<%= render "components/dropdown",
|
|
365
|
+
trigger: render("components/button", variant: "outline") { "Options" } do %>
|
|
366
|
+
<button class="dropdown__item">Profile</button>
|
|
367
|
+
<button class="dropdown__item">Settings</button>
|
|
368
|
+
<div class="dropdown__separator"></div>
|
|
369
|
+
<button class="dropdown__item">Log out</button>
|
|
370
|
+
<% end %>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Keyboard: Arrow Up/Down navigates items, Escape closes, click outside closes.
|
|
374
|
+
|
|
375
|
+
#### Tooltip
|
|
376
|
+
|
|
377
|
+
Hover/focus tooltip with configurable delay.
|
|
378
|
+
|
|
379
|
+
```erb
|
|
380
|
+
<%= render "components/tooltip", text: "Add to favorites", position: "top" do %>
|
|
381
|
+
<%= render "components/button", variant: "primary", size: "icon" do %>
|
|
382
|
+
<!-- heart icon SVG -->
|
|
383
|
+
<% end %>
|
|
384
|
+
<% end %>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Positions:** top (default), bottom, left, right. **Delay:** 200ms default.
|
|
388
|
+
|
|
389
|
+
#### Toast
|
|
390
|
+
|
|
391
|
+
Auto-dismissing notifications stacked bottom-right.
|
|
392
|
+
|
|
393
|
+
Place the container once in your layout:
|
|
394
|
+
|
|
395
|
+
```erb
|
|
396
|
+
<%# app/views/layouts/application.html.erb %>
|
|
397
|
+
<%= render "components/toast_container" %>
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Add toasts dynamically (via Turbo Stream or JS):
|
|
401
|
+
|
|
402
|
+
```erb
|
|
403
|
+
<%= render "components/toast", variant: "success",
|
|
404
|
+
title: "Saved!", description: "Changes applied." %>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**Variants:** default, success, destructive, warning. **Auto-dismiss:** 5000ms default.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### Data Display
|
|
412
|
+
|
|
413
|
+
#### Table
|
|
414
|
+
|
|
415
|
+
Semantic table with responsive scroll wrapper.
|
|
416
|
+
|
|
417
|
+
```erb
|
|
418
|
+
<%= render "components/table",
|
|
419
|
+
headers: ["Name", "Email", "Status"],
|
|
420
|
+
striped: true, hoverable: true do %>
|
|
421
|
+
<tr class="table__row">
|
|
422
|
+
<td class="table__cell">Jane Doe</td>
|
|
423
|
+
<td class="table__cell">jane@example.com</td>
|
|
424
|
+
<td class="table__cell">
|
|
425
|
+
<%= render "components/badge", variant: "success" do %>Active<% end %>
|
|
426
|
+
</td>
|
|
427
|
+
</tr>
|
|
428
|
+
<% end %>
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Options:** `headers`, `striped`, `hoverable`, `class`
|
|
432
|
+
|
|
433
|
+
#### Tabs
|
|
434
|
+
|
|
435
|
+
WAI-ARIA tabs pattern with arrow key navigation.
|
|
436
|
+
|
|
437
|
+
```erb
|
|
438
|
+
<%= render "components/tabs", label: "Settings", tabs: [
|
|
439
|
+
{ id: "general", label: "General", content: tag.p("General settings..."), active: true },
|
|
440
|
+
{ id: "security", label: "Security", content: tag.p("Security settings...") },
|
|
441
|
+
{ id: "billing", label: "Billing", content: tag.p("Billing info...") }
|
|
442
|
+
] %>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Options:** `tabs` (array), `label` (aria-label), `hash` (URL hash sync)
|
|
446
|
+
|
|
447
|
+
#### Accordion
|
|
448
|
+
|
|
449
|
+
Native `<details>`/`<summary>` with optional single-open mode.
|
|
450
|
+
|
|
451
|
+
```erb
|
|
452
|
+
<%= render "components/accordion", single: true, items: [
|
|
453
|
+
{ title: "Is it free?", content: "Yes, MIT licensed.", open: true },
|
|
454
|
+
{ title: "Build step?", content: "No, vanilla CSS." },
|
|
455
|
+
{ title: "Dark mode?", content: "Add .dark class to <html>." }
|
|
456
|
+
] %>
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Options:** `items` (array), `single` (single-open mode), `class`
|
|
460
|
+
|
|
461
|
+
#### Progress
|
|
462
|
+
|
|
463
|
+
Native `<progress>` element with custom styling.
|
|
464
|
+
|
|
465
|
+
```erb
|
|
466
|
+
<%= render "components/progress", value: 65, label: "65%", aria_label: "Upload progress" %>
|
|
467
|
+
<%= render "components/progress", value: 100, variant: "success", label: "Complete" %>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Variants:** default (primary), success, warning, destructive
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Design Tokens
|
|
475
|
+
|
|
476
|
+
Customize your theme by editing the CSS custom properties:
|
|
477
|
+
|
|
478
|
+
| Token File | What it controls |
|
|
479
|
+
|---|---|
|
|
480
|
+
| `tokens/_colors.css` | All colors (HSL), dark mode overrides |
|
|
481
|
+
| `tokens/_typography.css` | Font families, sizes, weights, line heights |
|
|
482
|
+
| `tokens/_spacing.css` | Spacing scale (0 to 16) |
|
|
483
|
+
| `tokens/_radius.css` | Border radii (sm to full) |
|
|
484
|
+
| `tokens/_shadows.css` | Box shadows (sm to xl) |
|
|
485
|
+
| `tokens/_transitions.css` | Durations and easings |
|
|
486
|
+
| `tokens/_z-index.css` | Z-index scale (dropdown to toast) |
|
|
487
|
+
|
|
488
|
+
### Changing your brand color
|
|
489
|
+
|
|
490
|
+
Edit one line in `tokens/_colors.css`:
|
|
491
|
+
|
|
492
|
+
```css
|
|
493
|
+
--color-primary: 220 70% 50%; /* Change this HSL value */
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
All components update automatically, including dark mode.
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Component Groups
|
|
501
|
+
|
|
502
|
+
| Group | Components |
|
|
503
|
+
|---|---|
|
|
504
|
+
| **Essentials** | Button, Input, Label, Card, Badge, Alert |
|
|
505
|
+
| **Forms** | Checkbox, Radio, Switch, Select |
|
|
506
|
+
| **Overlays** | Dialog, Dropdown, Tooltip, Toast |
|
|
507
|
+
| **Data** | Table, Tabs, Accordion, Progress |
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
rails generate nanoui:component --group essentials
|
|
511
|
+
rails generate nanoui:component --group forms
|
|
512
|
+
rails generate nanoui:component --group overlays
|
|
513
|
+
rails generate nanoui:component --group data
|
|
514
|
+
rails generate nanoui:component --all
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Philosophy
|
|
520
|
+
|
|
521
|
+
- **Semantic HTML first** — `<dialog>`, `<details>`, `<progress>`, `<fieldset>`, `<output>`
|
|
522
|
+
- **Accessibility is not optional** — ARIA attributes, keyboard navigation, focus management, screen reader support
|
|
523
|
+
- **No build step** — No Tailwind, no PostCSS, no webpack. Vanilla CSS with native nesting
|
|
524
|
+
- **You own the code** — Generator copies files into your app. Edit freely, no runtime dependency
|
|
525
|
+
- **BEM naming** — `.block`, `.block--modifier`, `.block__element`
|
|
526
|
+
- **CSS custom properties** — One file to theme everything. Dark mode with a single class swap
|
|
527
|
+
|
|
528
|
+
## Browser Support
|
|
529
|
+
|
|
530
|
+
Chrome 120+, Firefox 117+, Safari 17.2+ (native CSS nesting and `<dialog>` support).
|
|
531
|
+
|
|
532
|
+
## Icons
|
|
533
|
+
|
|
534
|
+
NanoUI works great with [Lucide Icons](https://lucide.dev) (MIT licensed). Use inline SVGs — copy what you need.
|
|
535
|
+
|
|
536
|
+
## Credits
|
|
537
|
+
|
|
538
|
+
Icons from [Lucide](https://lucide.dev).
|
|
539
|
+
|
|
540
|
+
## License
|
|
541
|
+
|
|
542
|
+
MIT
|