shadcn-rails 0.1.0 → 0.2.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 +4 -1
- data/CLAUDE.md +151 -2
- data/PROGRESS.md +30 -20
- data/README.md +89 -1398
- data/Rakefile +66 -0
- data/__tests__/controllers/combobox_controller.test.js +56 -51
- data/__tests__/controllers/context_menu_controller.test.js +280 -2
- data/__tests__/controllers/menubar_controller.test.js +5 -4
- data/__tests__/controllers/navigation_menu_controller.test.js +5 -4
- data/__tests__/controllers/popover_controller.test.js +35 -60
- data/__tests__/controllers/select_controller.test.js +5 -1
- data/app/assets/javascripts/shadcn/controllers/base_menu_controller.js +266 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +13 -8
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +5 -1
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +61 -105
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +49 -170
- data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +10 -7
- data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +10 -6
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +7 -7
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +12 -10
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +24 -14
- data/app/assets/javascripts/shadcn/index.js +2 -0
- data/app/assets/stylesheets/shadcn/components.css +12 -0
- data/app/components/shadcn/command_list_component.rb +29 -14
- data/app/components/shadcn/context_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/context_menu_content_component.rb +37 -14
- data/app/components/shadcn/context_menu_item_component.rb +3 -2
- data/app/components/shadcn/context_menu_radio_group_component.rb +42 -0
- data/app/components/shadcn/context_menu_radio_item_component.rb +76 -0
- data/app/components/shadcn/dropdown_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/dropdown_menu_content_component.rb +45 -16
- data/app/components/shadcn/dropdown_menu_radio_group_component.rb +42 -0
- data/app/components/shadcn/dropdown_menu_radio_item_component.rb +76 -0
- data/app/components/shadcn/menubar_content_component.rb +45 -20
- data/app/components/shadcn/menubar_sub_content_component.rb +21 -8
- data/app/components/shadcn/radio_group_item_component.rb +32 -6
- data/app/components/shadcn/resizable_panel_group_component.rb +27 -16
- data/app/components/shadcn/select_component.rb +23 -6
- data/bin/bump +321 -0
- data/bin/release +205 -0
- data/bin/test +75 -0
- data/jest.config.js +1 -1
- data/lib/shadcn/rails/version.rb +1 -1
- data/package-lock.json +27 -4
- data/package.json +4 -1
- metadata +11 -1
data/README.md
CHANGED
|
@@ -2,1018 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
Beautiful, accessible UI components for Rails built with ViewComponents, Stimulus, and Tailwind CSS. A Ruby port of [shadcn/ui](https://ui.shadcn.com).
|
|
4
4
|
|
|
5
|
-
[](https://github.com/iheanyi/shadcn-rails/actions/workflows/ci.yml)
|
|
6
|
+
[](https://rubygems.org/gems/shadcn-rails)
|
|
7
|
+
[](https://www.npmjs.com/package/shadcn-rails-stimulus)
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **
|
|
12
|
-
- **Accessible** - Built with
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **Stimulus** - Interactive components powered by Stimulus controllers
|
|
17
|
-
- **Rails-first** - Designed specifically for Ruby on Rails applications
|
|
18
|
-
- **47 Components** - Comprehensive library covering all common UI patterns
|
|
11
|
+
- **47 Components** - Buttons, forms, dialogs, menus, and more
|
|
12
|
+
- **Accessible** - Built with WAI-ARIA patterns
|
|
13
|
+
- **Dark Mode** - Built-in light/dark theme support
|
|
14
|
+
- **Customizable** - CSS variables for easy theming
|
|
15
|
+
- **Rails-first** - ViewComponents + Stimulus + Tailwind CSS
|
|
19
16
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
- [Installation](#installation)
|
|
23
|
-
- [Quick Start](#quick-start)
|
|
24
|
-
- [Components](#components)
|
|
25
|
-
- [Buttons & Actions](#buttons--actions)
|
|
26
|
-
- [Form Inputs](#form-inputs)
|
|
27
|
-
- [Data Display](#data-display)
|
|
28
|
-
- [Feedback](#feedback)
|
|
29
|
-
- [Overlays](#overlays)
|
|
30
|
-
- [Navigation](#navigation)
|
|
31
|
-
- [Layout](#layout)
|
|
32
|
-
- [Theming](#theming)
|
|
33
|
-
- [Dark Mode](#dark-mode)
|
|
34
|
-
- [Configuration](#configuration)
|
|
35
|
-
- [Stimulus Controllers](#stimulus-controllers)
|
|
36
|
-
- [Testing](#testing)
|
|
37
|
-
- [Development](#development)
|
|
38
|
-
- [Security Considerations](#security-considerations)
|
|
39
|
-
- [Contributing](#contributing)
|
|
40
|
-
|
|
41
|
-
## Installation
|
|
42
|
-
|
|
43
|
-
Add this line to your application's Gemfile:
|
|
44
|
-
|
|
45
|
-
```ruby
|
|
46
|
-
gem "shadcn-rails"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Then execute:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
bundle install
|
|
53
|
-
rails generate shadcn:install
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
This will:
|
|
57
|
-
1. Create a configuration initializer at `config/initializers/shadcn.rb`
|
|
58
|
-
2. Add the required CSS imports to your application
|
|
59
|
-
3. Configure your Stimulus controllers
|
|
60
|
-
|
|
61
|
-
### Stylesheets
|
|
62
|
-
|
|
63
|
-
shadcn-rails includes two CSS files:
|
|
64
|
-
|
|
65
|
-
| File | Purpose |
|
|
66
|
-
|------|---------|
|
|
67
|
-
| `shadcn/base.css` | CSS variables for theming (colors, border radius), animations, and focus styles |
|
|
68
|
-
| `shadcn/components.css` | Component-specific styles for interactive elements (`data-state` attributes, custom inputs) |
|
|
69
|
-
|
|
70
|
-
**For Tailwind CSS** (application.tailwind.css):
|
|
71
|
-
|
|
72
|
-
```css
|
|
73
|
-
@import "shadcn/base";
|
|
74
|
-
@import "shadcn/components";
|
|
75
|
-
|
|
76
|
-
@tailwind base;
|
|
77
|
-
@tailwind components;
|
|
78
|
-
@tailwind utilities;
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**For Sprockets** (application.css):
|
|
82
|
-
|
|
83
|
-
```css
|
|
84
|
-
/*
|
|
85
|
-
*= require shadcn/base
|
|
86
|
-
*= require shadcn/components
|
|
87
|
-
*= require_self
|
|
88
|
-
*/
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The `components.css` file includes essential styles for:
|
|
92
|
-
- **Switch** - `data-state` based checked/unchecked styling
|
|
93
|
-
- **Slider** - Custom range input with fill indicator
|
|
94
|
-
- **Checkbox/Radio** - Native inputs with custom styling
|
|
95
|
-
- **Accordion/Collapsible** - Content animations
|
|
96
|
-
- **Dialog/Sheet/Popover** - Open/close animations and overlays
|
|
97
|
-
- **Tabs** - Active/inactive state styling
|
|
98
|
-
|
|
99
|
-
### Requirements
|
|
100
|
-
|
|
101
|
-
- Ruby >= 3.1
|
|
102
|
-
- Rails >= 7.0
|
|
103
|
-
- Tailwind CSS >= 3.0
|
|
104
|
-
- Stimulus >= 3.0
|
|
105
|
-
- ViewComponent >= 3.0
|
|
106
|
-
|
|
107
|
-
### Tailwind CSS Configuration
|
|
108
|
-
|
|
109
|
-
Ensure your `tailwind.config.js` includes the shadcn-rails color configuration:
|
|
110
|
-
|
|
111
|
-
```javascript
|
|
112
|
-
module.exports = {
|
|
113
|
-
darkMode: 'class',
|
|
114
|
-
theme: {
|
|
115
|
-
extend: {
|
|
116
|
-
colors: {
|
|
117
|
-
border: "hsl(var(--border))",
|
|
118
|
-
input: "hsl(var(--input))",
|
|
119
|
-
ring: "hsl(var(--ring))",
|
|
120
|
-
background: "hsl(var(--background))",
|
|
121
|
-
foreground: "hsl(var(--foreground))",
|
|
122
|
-
primary: {
|
|
123
|
-
DEFAULT: "hsl(var(--primary))",
|
|
124
|
-
foreground: "hsl(var(--primary-foreground))",
|
|
125
|
-
},
|
|
126
|
-
secondary: {
|
|
127
|
-
DEFAULT: "hsl(var(--secondary))",
|
|
128
|
-
foreground: "hsl(var(--secondary-foreground))",
|
|
129
|
-
},
|
|
130
|
-
destructive: {
|
|
131
|
-
DEFAULT: "hsl(var(--destructive))",
|
|
132
|
-
foreground: "hsl(var(--destructive-foreground))",
|
|
133
|
-
},
|
|
134
|
-
muted: {
|
|
135
|
-
DEFAULT: "hsl(var(--muted))",
|
|
136
|
-
foreground: "hsl(var(--muted-foreground))",
|
|
137
|
-
},
|
|
138
|
-
accent: {
|
|
139
|
-
DEFAULT: "hsl(var(--accent))",
|
|
140
|
-
foreground: "hsl(var(--accent-foreground))",
|
|
141
|
-
},
|
|
142
|
-
popover: {
|
|
143
|
-
DEFAULT: "hsl(var(--popover))",
|
|
144
|
-
foreground: "hsl(var(--popover-foreground))",
|
|
145
|
-
},
|
|
146
|
-
card: {
|
|
147
|
-
DEFAULT: "hsl(var(--card))",
|
|
148
|
-
foreground: "hsl(var(--card-foreground))",
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
borderRadius: {
|
|
152
|
-
lg: "var(--radius)",
|
|
153
|
-
md: "calc(var(--radius) - 2px)",
|
|
154
|
-
sm: "calc(var(--radius) - 4px)",
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Quick Start
|
|
162
|
-
|
|
163
|
-
```erb
|
|
164
|
-
<%# Simple button %>
|
|
165
|
-
<%= render Shadcn::ButtonComponent.new { "Click me" } %>
|
|
166
|
-
|
|
167
|
-
<%# Button with variant %>
|
|
168
|
-
<%= render Shadcn::ButtonComponent.new(variant: :destructive) { "Delete" } %>
|
|
169
|
-
|
|
170
|
-
<%# Card with slots %>
|
|
171
|
-
<%= render Shadcn::CardComponent.new do |card| %>
|
|
172
|
-
<% card.with_header do |header| %>
|
|
173
|
-
<% header.with_title { "Welcome" } %>
|
|
174
|
-
<% header.with_description { "Get started with shadcn-rails" } %>
|
|
175
|
-
<% end %>
|
|
176
|
-
<% card.with_content_slot do %>
|
|
177
|
-
<p>Your content here</p>
|
|
178
|
-
<% end %>
|
|
179
|
-
<% end %>
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Components
|
|
183
|
-
|
|
184
|
-
### Buttons & Actions
|
|
185
|
-
|
|
186
|
-
#### Button
|
|
187
|
-
|
|
188
|
-
Displays a button or a component that looks like a button.
|
|
189
|
-
|
|
190
|
-
```erb
|
|
191
|
-
<%# Variants %>
|
|
192
|
-
<%= render Shadcn::ButtonComponent.new(variant: :default) { "Default" } %>
|
|
193
|
-
<%= render Shadcn::ButtonComponent.new(variant: :secondary) { "Secondary" } %>
|
|
194
|
-
<%= render Shadcn::ButtonComponent.new(variant: :destructive) { "Destructive" } %>
|
|
195
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Outline" } %>
|
|
196
|
-
<%= render Shadcn::ButtonComponent.new(variant: :ghost) { "Ghost" } %>
|
|
197
|
-
<%= render Shadcn::ButtonComponent.new(variant: :link) { "Link" } %>
|
|
198
|
-
|
|
199
|
-
<%# Sizes %>
|
|
200
|
-
<%= render Shadcn::ButtonComponent.new(size: :sm) { "Small" } %>
|
|
201
|
-
<%= render Shadcn::ButtonComponent.new(size: :default) { "Default" } %>
|
|
202
|
-
<%= render Shadcn::ButtonComponent.new(size: :lg) { "Large" } %>
|
|
203
|
-
<%= render Shadcn::ButtonComponent.new(size: :icon) { "+" } %>
|
|
204
|
-
|
|
205
|
-
<%# States %>
|
|
206
|
-
<%= render Shadcn::ButtonComponent.new(disabled: true) { "Disabled" } %>
|
|
207
|
-
|
|
208
|
-
<%# As link %>
|
|
209
|
-
<%= render Shadcn::ButtonComponent.new(href: "/path", variant: :outline) { "Link Button" } %>
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Props:**
|
|
213
|
-
| Prop | Type | Default | Description |
|
|
214
|
-
|------|------|---------|-------------|
|
|
215
|
-
| `variant` | Symbol | `:default` | `:default`, `:secondary`, `:destructive`, `:outline`, `:ghost`, `:link` |
|
|
216
|
-
| `size` | Symbol | `:default` | `:default`, `:sm`, `:lg`, `:icon` |
|
|
217
|
-
| `disabled` | Boolean | `false` | Disables the button |
|
|
218
|
-
| `href` | String | `nil` | Renders as a link when provided |
|
|
219
|
-
| `type` | String | `"button"` | Button type attribute |
|
|
220
|
-
|
|
221
|
-
#### Toggle
|
|
222
|
-
|
|
223
|
-
A two-state button that can be either on or off.
|
|
224
|
-
|
|
225
|
-
```erb
|
|
226
|
-
<%= render Shadcn::ToggleComponent.new do %>
|
|
227
|
-
<svg><!-- icon --></svg>
|
|
228
|
-
<% end %>
|
|
229
|
-
|
|
230
|
-
<%= render Shadcn::ToggleComponent.new(variant: :outline, pressed: true) do %>
|
|
231
|
-
Bold
|
|
232
|
-
<% end %>
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
#### Toggle Group
|
|
236
|
-
|
|
237
|
-
A set of two-state buttons that can be toggled on or off.
|
|
238
|
-
|
|
239
|
-
```erb
|
|
240
|
-
<%# Single selection %>
|
|
241
|
-
<%= render Shadcn::ToggleGroupComponent.new(type: :single) do |group| %>
|
|
242
|
-
<% group.with_item(value: "bold") { "B" } %>
|
|
243
|
-
<% group.with_item(value: "italic") { "I" } %>
|
|
244
|
-
<% group.with_item(value: "underline") { "U" } %>
|
|
245
|
-
<% end %>
|
|
246
|
-
|
|
247
|
-
<%# Multiple selection %>
|
|
248
|
-
<%= render Shadcn::ToggleGroupComponent.new(type: :multiple, variant: :outline) do |group| %>
|
|
249
|
-
<% group.with_item(value: "left") { "Left" } %>
|
|
250
|
-
<% group.with_item(value: "center") { "Center" } %>
|
|
251
|
-
<% group.with_item(value: "right") { "Right" } %>
|
|
252
|
-
<% end %>
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### Form Inputs
|
|
256
|
-
|
|
257
|
-
#### Input
|
|
258
|
-
|
|
259
|
-
Displays a form input field.
|
|
260
|
-
|
|
261
|
-
```erb
|
|
262
|
-
<%= render Shadcn::InputComponent.new(
|
|
263
|
-
type: :email,
|
|
264
|
-
placeholder: "you@example.com",
|
|
265
|
-
id: "email",
|
|
266
|
-
name: "user[email]"
|
|
267
|
-
) %>
|
|
268
|
-
|
|
269
|
-
<%# Disabled %>
|
|
270
|
-
<%= render Shadcn::InputComponent.new(
|
|
271
|
-
type: :text,
|
|
272
|
-
placeholder: "Disabled",
|
|
273
|
-
disabled: true
|
|
274
|
-
) %>
|
|
275
|
-
|
|
276
|
-
<%# With validation error %>
|
|
277
|
-
<%= render Shadcn::InputComponent.new(
|
|
278
|
-
type: :text,
|
|
279
|
-
class_name: "border-destructive"
|
|
280
|
-
) %>
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
**Props:**
|
|
284
|
-
| Prop | Type | Default | Description |
|
|
285
|
-
|------|------|---------|-------------|
|
|
286
|
-
| `type` | Symbol/String | `:text` | Input type (`:text`, `:email`, `:password`, `:number`, `:search`, `:file`, etc.) |
|
|
287
|
-
| `placeholder` | String | `nil` | Placeholder text |
|
|
288
|
-
| `disabled` | Boolean | `false` | Disables the input |
|
|
289
|
-
| `required` | Boolean | `false` | Makes input required |
|
|
290
|
-
|
|
291
|
-
#### Textarea
|
|
292
|
-
|
|
293
|
-
Displays a multi-line text input.
|
|
294
|
-
|
|
295
|
-
```erb
|
|
296
|
-
<%= render Shadcn::TextareaComponent.new(
|
|
297
|
-
placeholder: "Type your message here...",
|
|
298
|
-
rows: 4,
|
|
299
|
-
name: "message"
|
|
300
|
-
) %>
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
#### Label
|
|
304
|
-
|
|
305
|
-
Renders an accessible label associated with controls.
|
|
306
|
-
|
|
307
|
-
```erb
|
|
308
|
-
<%= render Shadcn::LabelComponent.new(for: "email") { "Email Address" } %>
|
|
309
|
-
<%= render Shadcn::InputComponent.new(id: "email", type: :email) %>
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
#### Checkbox
|
|
313
|
-
|
|
314
|
-
A control that allows toggling between checked and not checked.
|
|
315
|
-
|
|
316
|
-
```erb
|
|
317
|
-
<%= render Shadcn::CheckboxComponent.new(
|
|
318
|
-
id: "terms",
|
|
319
|
-
label: "Accept terms and conditions"
|
|
320
|
-
) %>
|
|
321
|
-
|
|
322
|
-
<%# Checked by default %>
|
|
323
|
-
<%= render Shadcn::CheckboxComponent.new(
|
|
324
|
-
id: "newsletter",
|
|
325
|
-
label: "Subscribe to newsletter",
|
|
326
|
-
checked: true
|
|
327
|
-
) %>
|
|
328
|
-
|
|
329
|
-
<%# Disabled %>
|
|
330
|
-
<%= render Shadcn::CheckboxComponent.new(
|
|
331
|
-
id: "disabled",
|
|
332
|
-
label: "Disabled option",
|
|
333
|
-
disabled: true
|
|
334
|
-
) %>
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
#### Switch
|
|
338
|
-
|
|
339
|
-
A control that allows toggling between a checked and not checked state.
|
|
340
|
-
|
|
341
|
-
```erb
|
|
342
|
-
<%= render Shadcn::SwitchComponent.new(
|
|
343
|
-
id: "airplane",
|
|
344
|
-
label: "Airplane Mode"
|
|
345
|
-
) %>
|
|
346
|
-
|
|
347
|
-
<%= render Shadcn::SwitchComponent.new(
|
|
348
|
-
id: "notifications",
|
|
349
|
-
label: "Enable notifications",
|
|
350
|
-
checked: true
|
|
351
|
-
) %>
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
#### Radio Group
|
|
355
|
-
|
|
356
|
-
A set of checkable buttons where only one can be checked at a time.
|
|
357
|
-
|
|
358
|
-
```erb
|
|
359
|
-
<%= render Shadcn::RadioGroupComponent.new(name: "plan", default_value: "comfortable") do |group| %>
|
|
360
|
-
<% group.with_item(value: "default", label: "Default") %>
|
|
361
|
-
<% group.with_item(value: "comfortable", label: "Comfortable") %>
|
|
362
|
-
<% group.with_item(value: "compact", label: "Compact") %>
|
|
363
|
-
<% end %>
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
#### Select
|
|
367
|
-
|
|
368
|
-
Displays a list of options for the user to pick from.
|
|
369
|
-
|
|
370
|
-
```erb
|
|
371
|
-
<%= render Shadcn::SelectComponent.new(placeholder: "Select a fruit") do |select| %>
|
|
372
|
-
<% select.with_group(label: "Fruits") do |group| %>
|
|
373
|
-
<% group.with_item(value: "apple") { "Apple" } %>
|
|
374
|
-
<% group.with_item(value: "banana") { "Banana" } %>
|
|
375
|
-
<% group.with_item(value: "orange") { "Orange" } %>
|
|
376
|
-
<% end %>
|
|
377
|
-
<% end %>
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
#### Slider
|
|
381
|
-
|
|
382
|
-
An input where the user selects a value from within a given range.
|
|
383
|
-
|
|
384
|
-
```erb
|
|
385
|
-
<%= render Shadcn::SliderComponent.new(value: 50, max: 100) %>
|
|
386
|
-
<%= render Shadcn::SliderComponent.new(value: 25, min: 0, max: 100, step: 5) %>
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### Data Display
|
|
390
|
-
|
|
391
|
-
#### Badge
|
|
392
|
-
|
|
393
|
-
Displays a badge or label.
|
|
394
|
-
|
|
395
|
-
```erb
|
|
396
|
-
<%= render Shadcn::BadgeComponent.new(variant: :default) { "Default" } %>
|
|
397
|
-
<%= render Shadcn::BadgeComponent.new(variant: :secondary) { "Secondary" } %>
|
|
398
|
-
<%= render Shadcn::BadgeComponent.new(variant: :destructive) { "Error" } %>
|
|
399
|
-
<%= render Shadcn::BadgeComponent.new(variant: :outline) { "Outline" } %>
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
#### Avatar
|
|
403
|
-
|
|
404
|
-
An image element with a fallback for representing the user.
|
|
405
|
-
|
|
406
|
-
```erb
|
|
407
|
-
<%# With image %>
|
|
408
|
-
<%= render Shadcn::AvatarComponent.new(
|
|
409
|
-
src: "https://example.com/avatar.jpg",
|
|
410
|
-
alt: "John Doe",
|
|
411
|
-
fallback: "JD"
|
|
412
|
-
) %>
|
|
413
|
-
|
|
414
|
-
<%# Without image (shows fallback) %>
|
|
415
|
-
<%= render Shadcn::AvatarComponent.new(
|
|
416
|
-
alt: "Jane Smith",
|
|
417
|
-
fallback: "JS"
|
|
418
|
-
) %>
|
|
419
|
-
|
|
420
|
-
<%# Sizes %>
|
|
421
|
-
<%= render Shadcn::AvatarComponent.new(size: :sm, fallback: "SM") %>
|
|
422
|
-
<%= render Shadcn::AvatarComponent.new(size: :default, fallback: "MD") %>
|
|
423
|
-
<%= render Shadcn::AvatarComponent.new(size: :lg, fallback: "LG") %>
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
#### Card
|
|
427
|
-
|
|
428
|
-
Displays a card with header, content, and footer.
|
|
429
|
-
|
|
430
|
-
```erb
|
|
431
|
-
<%= render Shadcn::CardComponent.new do |card| %>
|
|
432
|
-
<% card.with_header do |header| %>
|
|
433
|
-
<% header.with_title { "Card Title" } %>
|
|
434
|
-
<% header.with_description { "Card description goes here" } %>
|
|
435
|
-
<% end %>
|
|
436
|
-
<% card.with_content_slot do %>
|
|
437
|
-
<p>This is the main content of the card.</p>
|
|
438
|
-
<% end %>
|
|
439
|
-
<% card.with_footer do %>
|
|
440
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Cancel" } %>
|
|
441
|
-
<%= render Shadcn::ButtonComponent.new { "Save" } %>
|
|
442
|
-
<% end %>
|
|
443
|
-
<% end %>
|
|
444
|
-
|
|
445
|
-
<%# Simple card (content only) %>
|
|
446
|
-
<%= render Shadcn::CardComponent.new do |card| %>
|
|
447
|
-
<% card.with_content_slot(standalone: true) do %>
|
|
448
|
-
<p>A simple card with just content.</p>
|
|
449
|
-
<% end %>
|
|
450
|
-
<% end %>
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
#### Table
|
|
454
|
-
|
|
455
|
-
A responsive table component.
|
|
456
|
-
|
|
457
|
-
```erb
|
|
458
|
-
<%= render Shadcn::TableComponent.new do |table| %>
|
|
459
|
-
<% table.with_header do |header| %>
|
|
460
|
-
<% header.with_row do |row| %>
|
|
461
|
-
<% row.with_head { "Name" } %>
|
|
462
|
-
<% row.with_head { "Status" } %>
|
|
463
|
-
<% row.with_head(class_name: "text-right") { "Amount" } %>
|
|
464
|
-
<% end %>
|
|
465
|
-
<% end %>
|
|
466
|
-
<% table.with_body do |body| %>
|
|
467
|
-
<% body.with_row do |row| %>
|
|
468
|
-
<% row.with_cell { "John Doe" } %>
|
|
469
|
-
<% row.with_cell { "Active" } %>
|
|
470
|
-
<% row.with_cell(class_name: "text-right") { "$250.00" } %>
|
|
471
|
-
<% end %>
|
|
472
|
-
<% end %>
|
|
473
|
-
<% end %>
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
#### Progress
|
|
477
|
-
|
|
478
|
-
Displays an indicator showing the completion progress of a task.
|
|
479
|
-
|
|
480
|
-
```erb
|
|
481
|
-
<%= render Shadcn::ProgressComponent.new(value: 33) %>
|
|
482
|
-
<%= render Shadcn::ProgressComponent.new(value: 66) %>
|
|
483
|
-
<%= render Shadcn::ProgressComponent.new(value: 100) %>
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
#### Skeleton
|
|
487
|
-
|
|
488
|
-
Use to show a placeholder while content is loading.
|
|
489
|
-
|
|
490
|
-
```erb
|
|
491
|
-
<div class="flex items-center space-x-4">
|
|
492
|
-
<%= render Shadcn::SkeletonComponent.new(class_name: "h-12 w-12 rounded-full") %>
|
|
493
|
-
<div class="space-y-2">
|
|
494
|
-
<%= render Shadcn::SkeletonComponent.new(class_name: "h-4 w-[250px]") %>
|
|
495
|
-
<%= render Shadcn::SkeletonComponent.new(class_name: "h-4 w-[200px]") %>
|
|
496
|
-
</div>
|
|
497
|
-
</div>
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
#### Aspect Ratio
|
|
501
|
-
|
|
502
|
-
Displays content within a desired ratio.
|
|
503
|
-
|
|
504
|
-
```erb
|
|
505
|
-
<%= render Shadcn::AspectRatioComponent.new(ratio: "16/9") do %>
|
|
506
|
-
<img src="image.jpg" class="object-cover w-full h-full" />
|
|
507
|
-
<% end %>
|
|
508
|
-
|
|
509
|
-
<%# Common ratios: "1/1", "4/3", "16/9", "21/9" %>
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
### Feedback
|
|
513
|
-
|
|
514
|
-
#### Alert
|
|
515
|
-
|
|
516
|
-
Displays a callout for user attention.
|
|
517
|
-
|
|
518
|
-
```erb
|
|
519
|
-
<%# Default alert %>
|
|
520
|
-
<%= render Shadcn::AlertComponent.new do |alert| %>
|
|
521
|
-
<% alert.with_title { "Heads up!" } %>
|
|
522
|
-
<% alert.with_description { "You can add components using the CLI." } %>
|
|
523
|
-
<% end %>
|
|
524
|
-
|
|
525
|
-
<%# Destructive alert %>
|
|
526
|
-
<%= render Shadcn::AlertComponent.new(variant: :destructive) do |alert| %>
|
|
527
|
-
<% alert.with_title { "Error" } %>
|
|
528
|
-
<% alert.with_description { "Your session has expired." } %>
|
|
529
|
-
<% end %>
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
#### Tooltip
|
|
533
|
-
|
|
534
|
-
A popup that displays information when hovering.
|
|
535
|
-
|
|
536
|
-
```erb
|
|
537
|
-
<%= render Shadcn::TooltipComponent.new(content: "Add to library", side: :top) do |tooltip| %>
|
|
538
|
-
<% tooltip.with_trigger do %>
|
|
539
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline, size: :icon) { "+" } %>
|
|
540
|
-
<% end %>
|
|
541
|
-
<% end %>
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
**Props:**
|
|
545
|
-
| Prop | Type | Default | Description |
|
|
546
|
-
|------|------|---------|-------------|
|
|
547
|
-
| `content` | String | required | Tooltip text |
|
|
548
|
-
| `side` | Symbol | `:top` | `:top`, `:bottom`, `:left`, `:right` |
|
|
549
|
-
| `delay` | Integer | `200` | Delay in milliseconds before showing |
|
|
550
|
-
|
|
551
|
-
### Overlays
|
|
552
|
-
|
|
553
|
-
#### Dialog
|
|
554
|
-
|
|
555
|
-
A modal dialog window.
|
|
556
|
-
|
|
557
|
-
```erb
|
|
558
|
-
<%= render Shadcn::DialogComponent.new do |dialog| %>
|
|
559
|
-
<% dialog.with_trigger do %>
|
|
560
|
-
<%= render Shadcn::ButtonComponent.new { "Open Dialog" } %>
|
|
561
|
-
<% end %>
|
|
562
|
-
<% dialog.with_body do |body| %>
|
|
563
|
-
<% body.with_header do |header| %>
|
|
564
|
-
<% header.with_title { "Edit Profile" } %>
|
|
565
|
-
<% header.with_description { "Make changes to your profile here." } %>
|
|
566
|
-
<% end %>
|
|
567
|
-
<div class="py-4">
|
|
568
|
-
<%# Form content %>
|
|
569
|
-
</div>
|
|
570
|
-
<% body.with_footer do %>
|
|
571
|
-
<%= render Shadcn::ButtonComponent.new { "Save changes" } %>
|
|
572
|
-
<% end %>
|
|
573
|
-
<% end %>
|
|
574
|
-
<% end %>
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
##### Dialog with ID (for Turbo Stream targeting)
|
|
578
|
-
|
|
579
|
-
```erb
|
|
580
|
-
<%= render Shadcn::DialogComponent.new(id: "edit-profile-dialog") do |dialog| %>
|
|
581
|
-
<%# ... %>
|
|
582
|
-
<% end %>
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
##### Closing Dialog Programmatically
|
|
586
|
-
|
|
587
|
-
Use Stimulus actions to close dialogs from buttons:
|
|
588
|
-
|
|
589
|
-
```erb
|
|
590
|
-
<%# Cancel button closes immediately %>
|
|
591
|
-
<%= render Shadcn::ButtonComponent.new(
|
|
592
|
-
variant: :outline,
|
|
593
|
-
type: "button",
|
|
594
|
-
data: { action: "click->shadcn--dialog#close" }
|
|
595
|
-
) { "Cancel" } %>
|
|
596
|
-
|
|
597
|
-
<%# Or close from any element %>
|
|
598
|
-
<button data-action="click->shadcn--dialog#close">Close</button>
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
##### Forms Inside Dialogs
|
|
602
|
-
|
|
603
|
-
For forms that should close the dialog only on successful submission:
|
|
604
|
-
|
|
605
|
-
```erb
|
|
606
|
-
<%= render Shadcn::DialogComponent.new do |dialog| %>
|
|
607
|
-
<% dialog.with_trigger do %>
|
|
608
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Edit Profile" } %>
|
|
609
|
-
<% end %>
|
|
610
|
-
<% dialog.with_body do |body| %>
|
|
611
|
-
<% body.with_header do |header| %>
|
|
612
|
-
<% header.with_title { "Edit Profile" } %>
|
|
613
|
-
<% header.with_description { "Make changes to your profile here." } %>
|
|
614
|
-
<% end %>
|
|
615
|
-
<%= form_with model: @user, data: { remote: "true" } do |f| %>
|
|
616
|
-
<div class="space-y-4">
|
|
617
|
-
<%= render Shadcn::LabelComponent.new(for: "name") { "Name" } %>
|
|
618
|
-
<%= render Shadcn::InputComponent.new(id: "name", name: "user[name]", value: @user.name) %>
|
|
619
|
-
</div>
|
|
620
|
-
<div class="flex justify-end gap-3 mt-4">
|
|
621
|
-
<%= render Shadcn::ButtonComponent.new(
|
|
622
|
-
variant: :outline,
|
|
623
|
-
type: "button",
|
|
624
|
-
data: { action: "click->shadcn--dialog#close" }
|
|
625
|
-
) { "Cancel" } %>
|
|
626
|
-
<%= render Shadcn::ButtonComponent.new(type: "submit") { "Save Changes" } %>
|
|
627
|
-
</div>
|
|
628
|
-
<% end %>
|
|
629
|
-
<% end %>
|
|
630
|
-
<% end %>
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
**Props:**
|
|
634
|
-
| Prop | Type | Default | Description |
|
|
635
|
-
|------|------|---------|-------------|
|
|
636
|
-
| `id` | String | `nil` | Unique identifier for Turbo Stream targeting |
|
|
637
|
-
| `open` | Boolean | `false` | Whether dialog starts open |
|
|
638
|
-
| `modal` | Boolean | `true` | Whether dialog traps focus and blocks interaction |
|
|
639
|
-
|
|
640
|
-
#### Alert Dialog
|
|
641
|
-
|
|
642
|
-
A modal dialog for destructive or important actions.
|
|
643
|
-
|
|
644
|
-
```erb
|
|
645
|
-
<%= render Shadcn::AlertDialogComponent.new do |dialog| %>
|
|
646
|
-
<% dialog.with_trigger do %>
|
|
647
|
-
<%= render Shadcn::ButtonComponent.new(variant: :destructive) { "Delete Account" } %>
|
|
648
|
-
<% end %>
|
|
649
|
-
<% dialog.with_body do |body| %>
|
|
650
|
-
<% body.with_header do |header| %>
|
|
651
|
-
<% header.with_title { "Are you absolutely sure?" } %>
|
|
652
|
-
<% header.with_description { "This action cannot be undone." } %>
|
|
653
|
-
<% end %>
|
|
654
|
-
<% body.with_footer do %>
|
|
655
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline, data: { action: "click->shadcn--alert-dialog#close" }) { "Cancel" } %>
|
|
656
|
-
<%= render Shadcn::ButtonComponent.new(variant: :destructive) { "Delete" } %>
|
|
657
|
-
<% end %>
|
|
658
|
-
<% end %>
|
|
659
|
-
<% end %>
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
#### Sheet
|
|
663
|
-
|
|
664
|
-
Extends the Dialog component to display content that complements the main content.
|
|
665
|
-
|
|
666
|
-
```erb
|
|
667
|
-
<%= render Shadcn::SheetComponent.new(side: :right) do |sheet| %>
|
|
668
|
-
<% sheet.with_trigger do %>
|
|
669
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open Sheet" } %>
|
|
670
|
-
<% end %>
|
|
671
|
-
<% sheet.with_body do |body| %>
|
|
672
|
-
<% body.with_header do |header| %>
|
|
673
|
-
<% header.with_title { "Settings" } %>
|
|
674
|
-
<% header.with_description { "Configure your preferences." } %>
|
|
675
|
-
<% end %>
|
|
676
|
-
<div class="py-4">
|
|
677
|
-
<%# Sheet content %>
|
|
678
|
-
</div>
|
|
679
|
-
<% end %>
|
|
680
|
-
<% end %>
|
|
681
|
-
```
|
|
682
|
-
|
|
683
|
-
**Props:**
|
|
684
|
-
| Prop | Type | Default | Description |
|
|
685
|
-
|------|------|---------|-------------|
|
|
686
|
-
| `side` | Symbol | `:right` | `:top`, `:right`, `:bottom`, `:left` |
|
|
687
|
-
|
|
688
|
-
#### Drawer
|
|
689
|
-
|
|
690
|
-
A drawer component for mobile interfaces.
|
|
691
|
-
|
|
692
|
-
```erb
|
|
693
|
-
<%= render Shadcn::DrawerComponent.new do |drawer| %>
|
|
694
|
-
<% drawer.with_trigger do %>
|
|
695
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open Drawer" } %>
|
|
696
|
-
<% end %>
|
|
697
|
-
<% drawer.with_body do |body| %>
|
|
698
|
-
<% body.with_header do |header| %>
|
|
699
|
-
<% header.with_title { "Drawer Title" } %>
|
|
700
|
-
<% header.with_description { "Drawer description" } %>
|
|
701
|
-
<% end %>
|
|
702
|
-
<div class="p-4">
|
|
703
|
-
<%# Drawer content %>
|
|
704
|
-
</div>
|
|
705
|
-
<% body.with_footer do %>
|
|
706
|
-
<%= render Shadcn::ButtonComponent.new(class_name: "w-full") { "Submit" } %>
|
|
707
|
-
<% end %>
|
|
708
|
-
<% end %>
|
|
709
|
-
<% end %>
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
#### Popover
|
|
713
|
-
|
|
714
|
-
Displays rich content in a portal, triggered by a button.
|
|
715
|
-
|
|
716
|
-
```erb
|
|
717
|
-
<%= render Shadcn::PopoverComponent.new do |popover| %>
|
|
718
|
-
<% popover.with_trigger do %>
|
|
719
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open Popover" } %>
|
|
720
|
-
<% end %>
|
|
721
|
-
<% popover.with_content do %>
|
|
722
|
-
<div class="grid gap-4">
|
|
723
|
-
<h4 class="font-medium">Dimensions</h4>
|
|
724
|
-
<p class="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
|
|
725
|
-
</div>
|
|
726
|
-
<% end %>
|
|
727
|
-
<% end %>
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
#### Hover Card
|
|
731
|
-
|
|
732
|
-
For sighted users to preview content available behind a link.
|
|
733
|
-
|
|
734
|
-
```erb
|
|
735
|
-
<%= render Shadcn::HoverCardComponent.new do |card| %>
|
|
736
|
-
<% card.with_trigger do %>
|
|
737
|
-
<a href="#" class="underline">@shadcn</a>
|
|
738
|
-
<% end %>
|
|
739
|
-
<% card.with_card_content do %>
|
|
740
|
-
<div class="flex space-x-4">
|
|
741
|
-
<%= render Shadcn::AvatarComponent.new(src: "avatar.jpg", fallback: "SC") %>
|
|
742
|
-
<div>
|
|
743
|
-
<h4 class="text-sm font-semibold">@shadcn</h4>
|
|
744
|
-
<p class="text-sm">Creator of shadcn/ui</p>
|
|
745
|
-
</div>
|
|
746
|
-
</div>
|
|
747
|
-
<% end %>
|
|
748
|
-
<% end %>
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
#### Dropdown Menu
|
|
752
|
-
|
|
753
|
-
Displays a menu of actions or functions triggered by a button.
|
|
754
|
-
|
|
755
|
-
```erb
|
|
756
|
-
<%= render Shadcn::DropdownMenuComponent.new do |menu| %>
|
|
757
|
-
<% menu.with_trigger do %>
|
|
758
|
-
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open Menu" } %>
|
|
759
|
-
<% end %>
|
|
760
|
-
<% menu.with_content do |content| %>
|
|
761
|
-
<% content.with_label { "My Account" } %>
|
|
762
|
-
<% content.with_separator %>
|
|
763
|
-
<% content.with_item { "Profile" } %>
|
|
764
|
-
<% content.with_item { "Settings" } %>
|
|
765
|
-
<% content.with_separator %>
|
|
766
|
-
<% content.with_item { "Log out" } %>
|
|
767
|
-
<% end %>
|
|
768
|
-
<% end %>
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
### Navigation
|
|
772
|
-
|
|
773
|
-
#### Tabs
|
|
774
|
-
|
|
775
|
-
A set of layered sections of content that are displayed one at a time.
|
|
776
|
-
|
|
777
|
-
```erb
|
|
778
|
-
<%= render Shadcn::TabsComponent.new(default_value: "account") do |tabs| %>
|
|
779
|
-
<% tabs.with_list do |list| %>
|
|
780
|
-
<% list.with_trigger(value: "account") { "Account" } %>
|
|
781
|
-
<% list.with_trigger(value: "password") { "Password" } %>
|
|
782
|
-
<% list.with_trigger(value: "settings", disabled: true) { "Settings" } %>
|
|
783
|
-
<% end %>
|
|
784
|
-
<% tabs.with_panel(value: "account") do %>
|
|
785
|
-
<p>Account settings here.</p>
|
|
786
|
-
<% end %>
|
|
787
|
-
<% tabs.with_panel(value: "password") do %>
|
|
788
|
-
<p>Password settings here.</p>
|
|
789
|
-
<% end %>
|
|
790
|
-
<% end %>
|
|
791
|
-
```
|
|
792
|
-
|
|
793
|
-
##### URL Synchronization
|
|
794
|
-
|
|
795
|
-
Sync the active tab state with the URL query parameter for shareable links and browser history support:
|
|
796
|
-
|
|
797
|
-
```erb
|
|
798
|
-
<%# Tab state syncs to URL: /settings?tab=billing %>
|
|
799
|
-
<%= render Shadcn::TabsComponent.new(default_value: "general", url_param: "tab") do |tabs| %>
|
|
800
|
-
<% tabs.with_list do |list| %>
|
|
801
|
-
<% list.with_trigger(value: "general") { "General" } %>
|
|
802
|
-
<% list.with_trigger(value: "billing") { "Billing" } %>
|
|
803
|
-
<% list.with_trigger(value: "security") { "Security" } %>
|
|
804
|
-
<% end %>
|
|
805
|
-
<% tabs.with_panel(value: "general") do %>
|
|
806
|
-
<p>General settings</p>
|
|
807
|
-
<% end %>
|
|
808
|
-
<% tabs.with_panel(value: "billing") do %>
|
|
809
|
-
<p>Billing settings</p>
|
|
810
|
-
<% end %>
|
|
811
|
-
<% tabs.with_panel(value: "security") do %>
|
|
812
|
-
<p>Security settings</p>
|
|
813
|
-
<% end %>
|
|
814
|
-
<% end %>
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
When `url_param` is set:
|
|
818
|
-
- The URL updates when tabs are clicked (e.g., `?tab=billing`)
|
|
819
|
-
- Direct navigation to URLs with the parameter selects the correct tab
|
|
820
|
-
- Browser back/forward navigation works as expected
|
|
821
|
-
|
|
822
|
-
**Props:**
|
|
823
|
-
| Prop | Type | Default | Description |
|
|
824
|
-
|------|------|---------|-------------|
|
|
825
|
-
| `default_value` | String | `nil` | Initially active tab value |
|
|
826
|
-
| `url_param` | String | `nil` | URL query parameter name for state sync |
|
|
827
|
-
|
|
828
|
-
#### Accordion
|
|
829
|
-
|
|
830
|
-
A vertically stacked set of interactive headings that reveal sections of content.
|
|
831
|
-
|
|
832
|
-
```erb
|
|
833
|
-
<%= render Shadcn::AccordionComponent.new(type: :single, collapsible: true) do |accordion| %>
|
|
834
|
-
<% accordion.with_item(value: "item-1") do |item| %>
|
|
835
|
-
<% item.with_trigger { "Is it accessible?" } %>
|
|
836
|
-
<% item.with_body { "Yes. It adheres to WAI-ARIA design patterns." } %>
|
|
837
|
-
<% end %>
|
|
838
|
-
<% accordion.with_item(value: "item-2") do |item| %>
|
|
839
|
-
<% item.with_trigger { "Is it styled?" } %>
|
|
840
|
-
<% item.with_body { "Yes. It comes with default styles." } %>
|
|
841
|
-
<% end %>
|
|
842
|
-
<% end %>
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
**Props:**
|
|
846
|
-
| Prop | Type | Default | Description |
|
|
847
|
-
|------|------|---------|-------------|
|
|
848
|
-
| `type` | Symbol | `:single` | `:single` (one open), `:multiple` (many open) |
|
|
849
|
-
| `collapsible` | Boolean | `false` | Allow closing all items |
|
|
850
|
-
| `default_value` | String | `nil` | Initially open item(s) |
|
|
851
|
-
|
|
852
|
-
#### Breadcrumb
|
|
853
|
-
|
|
854
|
-
Displays the path to the current resource using a hierarchy of links.
|
|
855
|
-
|
|
856
|
-
```erb
|
|
857
|
-
<%= render Shadcn::BreadcrumbComponent.new do |breadcrumb| %>
|
|
858
|
-
<% breadcrumb.with_item(href: "/") { "Home" } %>
|
|
859
|
-
<% breadcrumb.with_item(href: "/products") { "Products" } %>
|
|
860
|
-
<% breadcrumb.with_item(current: true) { "Widget" } %>
|
|
861
|
-
<% end %>
|
|
862
|
-
```
|
|
863
|
-
|
|
864
|
-
#### Pagination
|
|
865
|
-
|
|
866
|
-
Pagination with page navigation, next and previous links. Supports three usage patterns:
|
|
867
|
-
|
|
868
|
-
**1. Auto-generated from Kaminari collection:**
|
|
869
|
-
|
|
870
|
-
```erb
|
|
871
|
-
<%# Works with Kaminari paginated collections %>
|
|
872
|
-
<%= render Shadcn::PaginationComponent.new(collection: @posts) %>
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
**2. Auto-generated from will_paginate collection:**
|
|
876
|
-
|
|
877
|
-
```erb
|
|
878
|
-
<%# Works with will_paginate collections %>
|
|
879
|
-
<%= render Shadcn::PaginationComponent.new(collection: @users) %>
|
|
880
|
-
```
|
|
881
|
-
|
|
882
|
-
**3. Auto-generated from Pagy object:**
|
|
883
|
-
|
|
884
|
-
```erb
|
|
885
|
-
<%# Works with Pagy pagination objects %>
|
|
886
|
-
<%= render Shadcn::PaginationComponent.new(pagy: @pagy) %>
|
|
887
|
-
```
|
|
17
|
+
## Installation
|
|
888
18
|
|
|
889
|
-
|
|
19
|
+
### Ruby Gem
|
|
890
20
|
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
collection: @posts,
|
|
895
|
-
url_builder: ->(page) { posts_path(page: page, sort: params[:sort]) }
|
|
896
|
-
) %>
|
|
21
|
+
```bash
|
|
22
|
+
bundle add shadcn-rails
|
|
23
|
+
rails generate shadcn:install
|
|
897
24
|
```
|
|
898
25
|
|
|
899
|
-
|
|
26
|
+
### Stimulus Controllers (npm)
|
|
900
27
|
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
<% content.with_item(href: "?page=1") { "1" } %>
|
|
906
|
-
<% content.with_item(href: "?page=2", active: true) { "2" } %>
|
|
907
|
-
<% content.with_item(href: "?page=3") { "3" } %>
|
|
908
|
-
<% content.with_ellipse %>
|
|
909
|
-
<% content.with_item(href: "?page=10") { "10" } %>
|
|
910
|
-
<% content.with_next_page(href: "?page=3") %>
|
|
911
|
-
<% end %>
|
|
912
|
-
<% end %>
|
|
28
|
+
```bash
|
|
29
|
+
npm install shadcn-rails-stimulus
|
|
30
|
+
# or
|
|
31
|
+
yarn add shadcn-rails-stimulus
|
|
913
32
|
```
|
|
914
33
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
```erb
|
|
918
|
-
<%# Simple one-liner that auto-detects your pagination gem %>
|
|
919
|
-
<%= shadcn_paginate @posts %>
|
|
34
|
+
Then register the controllers:
|
|
920
35
|
|
|
921
|
-
|
|
922
|
-
|
|
36
|
+
```javascript
|
|
37
|
+
import { Application } from "@hotwired/stimulus"
|
|
38
|
+
import { registerShadcnControllers } from "shadcn-rails-stimulus"
|
|
923
39
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
url_builder: ->(page) { posts_path(page: page) },
|
|
927
|
-
window: 3 %>
|
|
40
|
+
const application = Application.start()
|
|
41
|
+
registerShadcnControllers(application)
|
|
928
42
|
```
|
|
929
43
|
|
|
930
|
-
|
|
931
|
-
|------|------|---------|-------------|
|
|
932
|
-
| `collection` | Object | `nil` | Kaminari or will_paginate collection |
|
|
933
|
-
| `pagy` | Object | `nil` | Pagy pagination object |
|
|
934
|
-
| `url_builder` | Proc | `"?page=N"` | Lambda to generate page URLs |
|
|
935
|
-
| `window` | Integer | `2` | Pages to show around current page |
|
|
936
|
-
|
|
937
|
-
**Supported Pagination Gems:**
|
|
938
|
-
|
|
939
|
-
| Gem | Usage |
|
|
940
|
-
|-----|-------|
|
|
941
|
-
| [Kaminari](https://github.com/kaminari/kaminari) | `collection: @posts.page(1).per(10)` |
|
|
942
|
-
| [will_paginate](https://github.com/mislav/will_paginate) | `collection: @posts.paginate(page: 1)` |
|
|
943
|
-
| [Pagy](https://github.com/ddnexus/pagy) | `pagy: @pagy` (from `pagy(@posts)`) |
|
|
944
|
-
|
|
945
|
-
#### Collapsible
|
|
946
|
-
|
|
947
|
-
An interactive component which expands/collapses a panel.
|
|
44
|
+
## Quick Start
|
|
948
45
|
|
|
949
46
|
```erb
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
47
|
+
<%# Button %>
|
|
48
|
+
<%= render Shadcn::ButtonComponent.new(variant: :default) { "Click me" } %>
|
|
49
|
+
|
|
50
|
+
<%# Card %>
|
|
51
|
+
<%= render Shadcn::CardComponent.new do |card| %>
|
|
52
|
+
<% card.with_header do |header| %>
|
|
53
|
+
<% header.with_title { "Welcome" } %>
|
|
956
54
|
<% end %>
|
|
957
|
-
<%
|
|
958
|
-
<
|
|
959
|
-
Collapsible content here
|
|
960
|
-
</div>
|
|
55
|
+
<% card.with_content_slot do %>
|
|
56
|
+
<p>Your content here</p>
|
|
961
57
|
<% end %>
|
|
962
58
|
<% end %>
|
|
963
|
-
```
|
|
964
|
-
|
|
965
|
-
### Layout
|
|
966
|
-
|
|
967
|
-
#### Separator
|
|
968
|
-
|
|
969
|
-
Visually or semantically separates content.
|
|
970
|
-
|
|
971
|
-
```erb
|
|
972
|
-
<%# Horizontal separator %>
|
|
973
|
-
<%= render Shadcn::SeparatorComponent.new %>
|
|
974
|
-
|
|
975
|
-
<%# Vertical separator %>
|
|
976
|
-
<div class="flex h-5 items-center space-x-4">
|
|
977
|
-
<span>Blog</span>
|
|
978
|
-
<%= render Shadcn::SeparatorComponent.new(orientation: :vertical) %>
|
|
979
|
-
<span>Docs</span>
|
|
980
|
-
</div>
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
#### Scroll Area
|
|
984
59
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
<%= render Shadcn::
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
60
|
+
<%# Dialog %>
|
|
61
|
+
<%= render Shadcn::DialogComponent.new do |dialog| %>
|
|
62
|
+
<% dialog.with_trigger do %>
|
|
63
|
+
<%= render Shadcn::ButtonComponent.new { "Open" } %>
|
|
64
|
+
<% end %>
|
|
65
|
+
<% dialog.with_body do |body| %>
|
|
66
|
+
<% body.with_header do |header| %>
|
|
67
|
+
<% header.with_title { "Edit Profile" } %>
|
|
992
68
|
<% end %>
|
|
993
|
-
|
|
69
|
+
<p>Dialog content here</p>
|
|
70
|
+
<% end %>
|
|
994
71
|
<% end %>
|
|
995
72
|
```
|
|
996
73
|
|
|
997
|
-
##
|
|
998
|
-
|
|
999
|
-
### Available Themes
|
|
74
|
+
## Components
|
|
1000
75
|
|
|
1001
|
-
|
|
76
|
+
| Category | Components |
|
|
77
|
+
|----------|------------|
|
|
78
|
+
| **Actions** | Button, Toggle, Toggle Group |
|
|
79
|
+
| **Forms** | Input, Textarea, Label, Checkbox, Switch, Radio Group, Select, Slider |
|
|
80
|
+
| **Data Display** | Badge, Avatar, Card, Table, Progress, Skeleton, Aspect Ratio |
|
|
81
|
+
| **Feedback** | Alert, Tooltip, Toast |
|
|
82
|
+
| **Overlays** | Dialog, Alert Dialog, Sheet, Drawer, Popover, Hover Card, Dropdown Menu, Context Menu |
|
|
83
|
+
| **Navigation** | Tabs, Accordion, Breadcrumb, Pagination, Collapsible, Navigation Menu, Menubar |
|
|
84
|
+
| **Layout** | Separator, Scroll Area, Resizable |
|
|
1002
85
|
|
|
1003
|
-
|
|
1004
|
-
|-------|-------------|
|
|
1005
|
-
| `neutral` | Clean grayscale palette (default) |
|
|
1006
|
-
| `slate` | Cool blue-gray tones |
|
|
1007
|
-
| `stone` | Warm brown-gray tones |
|
|
1008
|
-
| `gray` | Standard gray palette |
|
|
1009
|
-
| `zinc` | Cool gray with slight purple tint |
|
|
86
|
+
## Theming
|
|
1010
87
|
|
|
1011
|
-
|
|
88
|
+
Configure colors in your initializer:
|
|
1012
89
|
|
|
1013
90
|
```ruby
|
|
1014
91
|
# config/initializers/shadcn.rb
|
|
1015
92
|
Shadcn::Rails.configure do |config|
|
|
1016
93
|
config.base_color = "slate" # neutral, slate, stone, gray, zinc
|
|
94
|
+
config.dark_mode = :class # :class, :media
|
|
95
|
+
config.radius = "0.5rem"
|
|
1017
96
|
end
|
|
1018
97
|
```
|
|
1019
98
|
|
|
@@ -1023,461 +102,73 @@ Or use the generator:
|
|
|
1023
102
|
rails generate shadcn:theme slate
|
|
1024
103
|
```
|
|
1025
104
|
|
|
1026
|
-
### CSS Variables
|
|
1027
|
-
|
|
1028
|
-
shadcn-rails uses CSS variables for theming, matching the shadcn/ui approach:
|
|
1029
|
-
|
|
1030
|
-
```css
|
|
1031
|
-
:root {
|
|
1032
|
-
--background: 0 0% 100%;
|
|
1033
|
-
--foreground: 0 0% 3.9%;
|
|
1034
|
-
--card: 0 0% 100%;
|
|
1035
|
-
--card-foreground: 0 0% 3.9%;
|
|
1036
|
-
--popover: 0 0% 100%;
|
|
1037
|
-
--popover-foreground: 0 0% 3.9%;
|
|
1038
|
-
--primary: 0 0% 9%;
|
|
1039
|
-
--primary-foreground: 0 0% 98%;
|
|
1040
|
-
--secondary: 0 0% 96.1%;
|
|
1041
|
-
--secondary-foreground: 0 0% 9%;
|
|
1042
|
-
--muted: 0 0% 96.1%;
|
|
1043
|
-
--muted-foreground: 0 0% 45.1%;
|
|
1044
|
-
--accent: 0 0% 96.1%;
|
|
1045
|
-
--accent-foreground: 0 0% 9%;
|
|
1046
|
-
--destructive: 0 84.2% 60.2%;
|
|
1047
|
-
--destructive-foreground: 0 0% 98%;
|
|
1048
|
-
--border: 0 0% 89.8%;
|
|
1049
|
-
--input: 0 0% 89.8%;
|
|
1050
|
-
--ring: 0 0% 3.9%;
|
|
1051
|
-
--radius: 0.5rem;
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
.dark {
|
|
1055
|
-
--background: 0 0% 3.9%;
|
|
1056
|
-
--foreground: 0 0% 98%;
|
|
1057
|
-
/* ... dark mode overrides */
|
|
1058
|
-
}
|
|
1059
|
-
```
|
|
1060
|
-
|
|
1061
|
-
### Custom Themes
|
|
1062
|
-
|
|
1063
|
-
Register custom themes in your initializer:
|
|
1064
|
-
|
|
1065
|
-
```ruby
|
|
1066
|
-
Shadcn::Rails.configure do |config|
|
|
1067
|
-
config.register_theme(:brand, {
|
|
1068
|
-
primary: "220 90% 56%",
|
|
1069
|
-
primary_foreground: "0 0% 100%",
|
|
1070
|
-
# ... other variables
|
|
1071
|
-
})
|
|
1072
|
-
end
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
## Dark Mode
|
|
1076
|
-
|
|
1077
|
-
### Class Strategy (Recommended)
|
|
1078
|
-
|
|
1079
|
-
Add the `dark` class to your `<html>` element:
|
|
1080
|
-
|
|
1081
|
-
```html
|
|
1082
|
-
<html class="dark">
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
Toggle with JavaScript:
|
|
1086
|
-
|
|
1087
|
-
```javascript
|
|
1088
|
-
// Toggle dark mode
|
|
1089
|
-
document.documentElement.classList.toggle('dark')
|
|
1090
|
-
|
|
1091
|
-
// Check system preference
|
|
1092
|
-
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
1093
|
-
document.documentElement.classList.add('dark')
|
|
1094
|
-
}
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
|
-
### Media Strategy
|
|
1098
|
-
|
|
1099
|
-
Use the system preference automatically:
|
|
1100
|
-
|
|
1101
|
-
```ruby
|
|
1102
|
-
Shadcn::Rails.configure do |config|
|
|
1103
|
-
config.dark_mode = :media
|
|
1104
|
-
end
|
|
1105
|
-
```
|
|
1106
|
-
|
|
1107
|
-
## Configuration
|
|
1108
|
-
|
|
1109
|
-
Full configuration options:
|
|
1110
|
-
|
|
1111
|
-
```ruby
|
|
1112
|
-
# config/initializers/shadcn.rb
|
|
1113
|
-
Shadcn::Rails.configure do |config|
|
|
1114
|
-
# Base color theme: neutral, stone, zinc, gray, slate
|
|
1115
|
-
config.base_color = "neutral"
|
|
1116
|
-
|
|
1117
|
-
# Use CSS variables for theming
|
|
1118
|
-
config.css_variables = true
|
|
1119
|
-
|
|
1120
|
-
# Dark mode strategy: :class, :media, or :selector
|
|
1121
|
-
config.dark_mode = :class
|
|
1122
|
-
|
|
1123
|
-
# Default border radius
|
|
1124
|
-
config.radius = "0.5rem"
|
|
1125
|
-
|
|
1126
|
-
# Tailwind class prefix (if using one)
|
|
1127
|
-
config.tailwind_prefix = ""
|
|
1128
|
-
|
|
1129
|
-
# Icon library: :lucide (default), :heroicons
|
|
1130
|
-
config.icon_library = :lucide
|
|
1131
|
-
end
|
|
1132
|
-
```
|
|
1133
|
-
|
|
1134
105
|
## Stimulus Controllers
|
|
1135
106
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
### Importmap-rails (Rails Default)
|
|
1139
|
-
|
|
1140
|
-
Add to your `config/importmap.rb`:
|
|
1141
|
-
|
|
1142
|
-
```ruby
|
|
1143
|
-
pin "shadcn-rails", to: "shadcn/index.js"
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
Then in `app/javascript/controllers/index.js`:
|
|
1147
|
-
|
|
1148
|
-
```javascript
|
|
1149
|
-
import { Application } from "@hotwired/stimulus"
|
|
1150
|
-
import { registerShadcnControllers } from "shadcn-rails"
|
|
1151
|
-
|
|
1152
|
-
const application = Application.start()
|
|
1153
|
-
registerShadcnControllers(application)
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
### esbuild
|
|
1157
|
-
|
|
1158
|
-
Install the npm package:
|
|
1159
|
-
|
|
1160
|
-
```bash
|
|
1161
|
-
npm install shadcn-rails
|
|
1162
|
-
# or
|
|
1163
|
-
yarn add shadcn-rails
|
|
1164
|
-
```
|
|
1165
|
-
|
|
1166
|
-
Then in `app/javascript/controllers/index.js`:
|
|
1167
|
-
|
|
1168
|
-
```javascript
|
|
1169
|
-
import { Application } from "@hotwired/stimulus"
|
|
1170
|
-
import { registerShadcnControllers } from "shadcn-rails"
|
|
1171
|
-
|
|
1172
|
-
const application = Application.start()
|
|
1173
|
-
registerShadcnControllers(application)
|
|
1174
|
-
```
|
|
1175
|
-
|
|
1176
|
-
### Webpack
|
|
1177
|
-
|
|
1178
|
-
Install the npm package:
|
|
1179
|
-
|
|
1180
|
-
```bash
|
|
1181
|
-
npm install shadcn-rails
|
|
1182
|
-
# or
|
|
1183
|
-
yarn add shadcn-rails
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
In `app/javascript/controllers/index.js`:
|
|
1187
|
-
|
|
1188
|
-
```javascript
|
|
1189
|
-
import { Application } from "@hotwired/stimulus"
|
|
1190
|
-
import { registerShadcnControllers } from "shadcn-rails"
|
|
1191
|
-
|
|
1192
|
-
const application = Application.start()
|
|
1193
|
-
registerShadcnControllers(application)
|
|
1194
|
-
```
|
|
1195
|
-
|
|
1196
|
-
### Vite (vite-ruby)
|
|
1197
|
-
|
|
1198
|
-
Install the npm package:
|
|
1199
|
-
|
|
1200
|
-
```bash
|
|
1201
|
-
npm install shadcn-rails
|
|
1202
|
-
# or
|
|
1203
|
-
yarn add shadcn-rails
|
|
1204
|
-
```
|
|
1205
|
-
|
|
1206
|
-
In your entrypoint (e.g., `app/frontend/entrypoints/application.js`):
|
|
1207
|
-
|
|
1208
|
-
```javascript
|
|
1209
|
-
import { Application } from "@hotwired/stimulus"
|
|
1210
|
-
import { registerShadcnControllers } from "shadcn-rails"
|
|
1211
|
-
|
|
1212
|
-
const application = Application.start()
|
|
1213
|
-
registerShadcnControllers(application)
|
|
1214
|
-
```
|
|
1215
|
-
|
|
1216
|
-
### Registering Individual Controllers
|
|
1217
|
-
|
|
1218
|
-
If you prefer to only load specific controllers (tree-shaking):
|
|
1219
|
-
|
|
1220
|
-
```javascript
|
|
1221
|
-
import { Application } from "@hotwired/stimulus"
|
|
1222
|
-
import DialogController from "shadcn-rails/controllers/dialog_controller"
|
|
1223
|
-
import TabsController from "shadcn-rails/controllers/tabs_controller"
|
|
1224
|
-
|
|
1225
|
-
const application = Application.start()
|
|
1226
|
-
application.register("shadcn--dialog", DialogController)
|
|
1227
|
-
application.register("shadcn--tabs", TabsController)
|
|
1228
|
-
```
|
|
1229
|
-
|
|
1230
|
-
### Available Controllers
|
|
107
|
+
All interactive components have corresponding Stimulus controllers:
|
|
1231
108
|
|
|
1232
109
|
| Controller | Components |
|
|
1233
110
|
|------------|------------|
|
|
1234
|
-
| `shadcn--accordion` | Accordion |
|
|
1235
|
-
| `shadcn--alert-dialog` | AlertDialog |
|
|
1236
|
-
| `shadcn--avatar` | Avatar |
|
|
1237
|
-
| `shadcn--checkbox` | Checkbox |
|
|
1238
|
-
| `shadcn--collapsible` | Collapsible |
|
|
1239
|
-
| `shadcn--context-menu` | ContextMenu |
|
|
1240
111
|
| `shadcn--dialog` | Dialog |
|
|
1241
|
-
| `shadcn--
|
|
1242
|
-
| `shadcn--
|
|
1243
|
-
| `shadcn--
|
|
1244
|
-
| `shadcn--input-otp` | InputOtp |
|
|
1245
|
-
| `shadcn--menubar` | Menubar |
|
|
1246
|
-
| `shadcn--navigation-menu` | NavigationMenu |
|
|
112
|
+
| `shadcn--sheet` | Sheet |
|
|
113
|
+
| `shadcn--tabs` | Tabs |
|
|
114
|
+
| `shadcn--accordion` | Accordion |
|
|
1247
115
|
| `shadcn--popover` | Popover |
|
|
1248
|
-
| `shadcn--
|
|
1249
|
-
| `shadcn--resizable` | Resizable |
|
|
1250
|
-
| `shadcn--scroll-area` | ScrollArea |
|
|
116
|
+
| `shadcn--dropdown-menu` | DropdownMenu |
|
|
1251
117
|
| `shadcn--select` | Select |
|
|
1252
|
-
| `shadcn--sheet` | Sheet |
|
|
1253
|
-
| `shadcn--slider` | Slider |
|
|
1254
118
|
| `shadcn--switch` | Switch |
|
|
1255
|
-
| `shadcn--
|
|
1256
|
-
| `shadcn--toast` | Toast |
|
|
1257
|
-
| `shadcn--toggle` | Toggle |
|
|
1258
|
-
| `shadcn--toggle-group` | ToggleGroup |
|
|
119
|
+
| `shadcn--slider` | Slider |
|
|
1259
120
|
| `shadcn--tooltip` | Tooltip |
|
|
121
|
+
| `shadcn--toast` | Toast |
|
|
1260
122
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
shadcn-rails includes comprehensive TypeScript type definitions (`.d.ts` files) for all 20 Stimulus controllers. Types are provided without requiring TypeScript compilation - your JavaScript remains the source of truth.
|
|
1264
|
-
|
|
1265
|
-
**Using Types in TypeScript Projects:**
|
|
1266
|
-
|
|
1267
|
-
```typescript
|
|
1268
|
-
import { Application } from "@hotwired/stimulus"
|
|
1269
|
-
import { registerShadcnControllers } from "shadcn-rails"
|
|
1270
|
-
|
|
1271
|
-
// Full IDE autocomplete and type checking
|
|
1272
|
-
const application = Application.start()
|
|
1273
|
-
registerShadcnControllers(application)
|
|
1274
|
-
```
|
|
1275
|
-
|
|
1276
|
-
**Importing Individual Controllers with Types:**
|
|
1277
|
-
|
|
1278
|
-
```typescript
|
|
1279
|
-
import DialogController from "shadcn-rails/controllers/dialog_controller"
|
|
1280
|
-
import TabsController from "shadcn-rails/controllers/tabs_controller"
|
|
1281
|
-
|
|
1282
|
-
// Full type information available
|
|
1283
|
-
const dialog = new DialogController()
|
|
1284
|
-
dialog.open() // ✓ TypeScript knows this method exists
|
|
1285
|
-
dialog.openValue // ✓ Type: boolean
|
|
1286
|
-
```
|
|
1287
|
-
|
|
1288
|
-
**Available Type Definitions:**
|
|
1289
|
-
|
|
1290
|
-
Each controller includes typed definitions for:
|
|
1291
|
-
- Static `targets` and `values` declarations
|
|
1292
|
-
- Target accessors (`*Target`, `*Targets`, `has*Target`)
|
|
1293
|
-
- Value accessors (`*Value`, `has*Value`)
|
|
1294
|
-
- All public methods
|
|
1295
|
-
- Custom properties and getters
|
|
1296
|
-
|
|
1297
|
-
**Example Type Definition (DialogController):**
|
|
1298
|
-
|
|
1299
|
-
```typescript
|
|
1300
|
-
import { Controller } from "@hotwired/stimulus"
|
|
1301
|
-
|
|
1302
|
-
export default class DialogController extends Controller {
|
|
1303
|
-
static targets: ["trigger", "template", "overlay", "content"]
|
|
1304
|
-
static values: {
|
|
1305
|
-
open: { type: "Boolean"; default: false }
|
|
1306
|
-
modal: { type: "Boolean"; default: true }
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// Target accessors
|
|
1310
|
-
readonly triggerTarget: HTMLElement
|
|
1311
|
-
readonly hasTemplateTarget: boolean
|
|
1312
|
-
|
|
1313
|
-
// Value accessors
|
|
1314
|
-
openValue: boolean
|
|
1315
|
-
modalValue: boolean
|
|
123
|
+
Register individual controllers for tree-shaking:
|
|
1316
124
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
toggle(): void
|
|
1321
|
-
}
|
|
125
|
+
```javascript
|
|
126
|
+
import DialogController from "shadcn-rails-stimulus/controllers/dialog_controller"
|
|
127
|
+
application.register("shadcn--dialog", DialogController)
|
|
1322
128
|
```
|
|
1323
129
|
|
|
1324
|
-
|
|
130
|
+
## TypeScript Support
|
|
1325
131
|
|
|
1326
|
-
|
|
132
|
+
Full TypeScript definitions included for all controllers:
|
|
1327
133
|
|
|
1328
134
|
```typescript
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
export function registerShadcnControllers(application: Application): void
|
|
1332
|
-
export const controllers: Record<string, typeof import("@hotwired/stimulus").Controller>
|
|
1333
|
-
}
|
|
1334
|
-
```
|
|
1335
|
-
|
|
1336
|
-
## Helper Methods
|
|
1337
|
-
|
|
1338
|
-
shadcn-rails provides helper methods for your views:
|
|
1339
|
-
|
|
1340
|
-
```erb
|
|
1341
|
-
<%# Class name merging (like cn() in shadcn/ui) %>
|
|
1342
|
-
<div class="<%= Shadcn::Rails.cn("base-class", conditional && "conditional-class", @class_name) %>">
|
|
1343
|
-
```
|
|
1344
|
-
|
|
1345
|
-
## Testing
|
|
135
|
+
import { registerShadcnControllers } from "shadcn-rails-stimulus"
|
|
136
|
+
import DialogController from "shadcn-rails-stimulus/controllers/dialog_controller"
|
|
1346
137
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
138
|
+
// Full autocomplete and type checking
|
|
139
|
+
const dialog = new DialogController()
|
|
140
|
+
dialog.open() // Methods are typed
|
|
141
|
+
dialog.openValue // Values are typed (boolean)
|
|
1351
142
|
```
|
|
1352
143
|
|
|
1353
|
-
|
|
144
|
+
## Requirements
|
|
1354
145
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
146
|
+
- Ruby >= 3.1
|
|
147
|
+
- Rails >= 7.0
|
|
148
|
+
- Tailwind CSS >= 3.0
|
|
149
|
+
- Stimulus >= 3.0
|
|
150
|
+
- ViewComponent >= 3.0
|
|
1358
151
|
|
|
1359
152
|
## Development
|
|
1360
153
|
|
|
1361
|
-
After checking out the repo:
|
|
1362
|
-
|
|
1363
154
|
```bash
|
|
1364
155
|
bundle install
|
|
1365
|
-
cd test/dummy
|
|
1366
|
-
rails server
|
|
1367
|
-
```
|
|
1368
|
-
|
|
1369
|
-
Visit `http://localhost:3000` to see the demo app with:
|
|
1370
|
-
- `/docs` - Full documentation with examples
|
|
1371
|
-
- `/showcase` - Full component showcase
|
|
1372
|
-
- `/themes` - Theme preview and comparison
|
|
1373
|
-
- `/lookbook` - Component previews with Lookbook
|
|
1374
|
-
|
|
1375
|
-
### Deploying the Documentation Site
|
|
1376
|
-
|
|
1377
|
-
The documentation site in `test/dummy/` can be deployed as a standalone Rails application. Recommended platforms:
|
|
1378
|
-
|
|
1379
|
-
**Render (Free tier available)**
|
|
1380
|
-
```bash
|
|
1381
|
-
# In test/dummy/ directory
|
|
1382
|
-
render.yaml # Already configured for deployment
|
|
1383
|
-
```
|
|
1384
|
-
|
|
1385
|
-
**Railway**
|
|
1386
|
-
```bash
|
|
1387
|
-
cd test/dummy
|
|
1388
|
-
railway init
|
|
1389
|
-
railway up
|
|
1390
|
-
```
|
|
1391
|
-
|
|
1392
|
-
**Fly.io**
|
|
1393
|
-
```bash
|
|
1394
|
-
cd test/dummy
|
|
1395
|
-
fly launch
|
|
1396
|
-
fly deploy
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
**Heroku**
|
|
1400
|
-
```bash
|
|
1401
|
-
cd test/dummy
|
|
1402
|
-
heroku create your-shadcn-rails-docs
|
|
1403
|
-
git subtree push --prefix test/dummy heroku main
|
|
1404
|
-
```
|
|
1405
|
-
|
|
1406
|
-
## Security Considerations
|
|
1407
|
-
|
|
1408
|
-
shadcn-rails follows Rails security best practices. Here are important security guidelines:
|
|
1409
|
-
|
|
1410
|
-
### CSRF Protection
|
|
1411
|
-
|
|
1412
|
-
Always use Rails form helpers to automatically include CSRF tokens:
|
|
1413
|
-
|
|
1414
|
-
```erb
|
|
1415
|
-
<%= form_with url: "/submit" do |f| %>
|
|
1416
|
-
<%= render Shadcn::InputComponent.new(name: "email") %>
|
|
1417
|
-
<%= render Shadcn::ButtonComponent.new(type: "submit") { "Submit" } %>
|
|
1418
|
-
<% end %>
|
|
1419
|
-
```
|
|
1420
|
-
|
|
1421
|
-
### XSS Prevention
|
|
1422
|
-
|
|
1423
|
-
ViewComponent auto-escapes all content by default. Never call `html_safe` on user-provided content:
|
|
1424
|
-
|
|
1425
|
-
```erb
|
|
1426
|
-
<%# SAFE - auto-escaped %>
|
|
1427
|
-
<%= render Shadcn::BadgeComponent.new { user.name } %>
|
|
1428
|
-
|
|
1429
|
-
<%# DANGEROUS - never do this with user input! %>
|
|
1430
|
-
<%= render Shadcn::BadgeComponent.new { user.name.html_safe } %>
|
|
1431
|
-
```
|
|
1432
|
-
|
|
1433
|
-
### Input Validation
|
|
1434
|
-
|
|
1435
|
-
Form components (Input, Textarea, Select) pass through values without validation. Always implement:
|
|
1436
|
-
|
|
1437
|
-
- Server-side input validation
|
|
1438
|
-
- Strong parameters in controllers
|
|
1439
|
-
- Model validations
|
|
1440
|
-
|
|
1441
|
-
```ruby
|
|
1442
|
-
# In your controller
|
|
1443
|
-
def user_params
|
|
1444
|
-
params.require(:user).permit(:name, :email)
|
|
1445
|
-
end
|
|
1446
|
-
|
|
1447
|
-
# In your model
|
|
1448
|
-
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
156
|
+
cd test/dummy && rails server
|
|
1449
157
|
```
|
|
1450
158
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
If using CSP headers, ensure your policy allows inline styles for theming:
|
|
1454
|
-
|
|
1455
|
-
```ruby
|
|
1456
|
-
# config/initializers/content_security_policy.rb
|
|
1457
|
-
Rails.application.configure do
|
|
1458
|
-
config.content_security_policy do |policy|
|
|
1459
|
-
policy.style_src :self, :unsafe_inline # Required for CSS variables
|
|
1460
|
-
end
|
|
1461
|
-
end
|
|
1462
|
-
```
|
|
159
|
+
Visit http://localhost:3000/docs for the component documentation.
|
|
1463
160
|
|
|
1464
161
|
## Contributing
|
|
1465
162
|
|
|
1466
|
-
Bug reports and pull requests are welcome
|
|
1467
|
-
|
|
1468
|
-
1. Fork the repository
|
|
1469
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
1470
|
-
3. Commit your changes (`git commit -am 'Add amazing feature'`)
|
|
1471
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
1472
|
-
5. Open a Pull Request
|
|
163
|
+
Bug reports and pull requests are welcome at https://github.com/iheanyi/shadcn-rails.
|
|
1473
164
|
|
|
1474
165
|
## License
|
|
1475
166
|
|
|
1476
|
-
|
|
167
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
1477
168
|
|
|
1478
169
|
## Credits
|
|
1479
170
|
|
|
1480
|
-
- [shadcn/ui](https://ui.shadcn.com) -
|
|
171
|
+
- [shadcn/ui](https://ui.shadcn.com) - Original React component library
|
|
1481
172
|
- [ViewComponent](https://viewcomponent.org) - Ruby component framework
|
|
1482
173
|
- [Stimulus](https://stimulus.hotwired.dev) - JavaScript framework
|
|
1483
|
-
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS
|
|
174
|
+
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS
|