shadcn-rails 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -2
- data/README.md +102 -1398
- data/__mocks__/@floating-ui/dom.js +67 -0
- data/app/assets/javascripts/shadcn/controllers/base_menu_controller.js +266 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +34 -8
- data/app/assets/javascripts/shadcn/controllers/command_controller.js +5 -1
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +64 -135
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +56 -186
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
- 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 +35 -60
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +37 -17
- data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +24 -14
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
- data/app/assets/javascripts/shadcn/index.js +9 -1
- data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
- data/app/assets/stylesheets/shadcn/base.css +32 -0
- data/app/assets/stylesheets/shadcn/components.css +12 -0
- data/app/components/shadcn/accordion_component.html.erb +8 -0
- data/app/components/shadcn/accordion_component.rb +6 -15
- data/app/components/shadcn/alert_component.html.erb +6 -0
- data/app/components/shadcn/alert_component.rb +0 -18
- data/app/components/shadcn/alert_dialog_component.html.erb +12 -0
- data/app/components/shadcn/alert_dialog_component.rb +7 -27
- data/app/components/shadcn/aspect_ratio_component.html.erb +7 -0
- data/app/components/shadcn/aspect_ratio_component.rb +4 -19
- data/app/components/shadcn/avatar_component.html.erb +20 -0
- data/app/components/shadcn/avatar_component.rb +8 -36
- data/app/components/shadcn/badge_component.html.erb +1 -0
- data/app/components/shadcn/badge_component.rb +0 -11
- data/app/components/shadcn/base_component.rb +15 -2
- data/app/components/shadcn/breadcrumb_component.html.erb +5 -0
- data/app/components/shadcn/breadcrumb_component.rb +6 -16
- data/app/components/shadcn/button_component.html.erb +18 -0
- data/app/components/shadcn/button_component.rb +1 -41
- data/app/components/shadcn/card_component.html.erb +8 -0
- data/app/components/shadcn/card_component.rb +2 -6
- data/app/components/shadcn/checkbox_component.html.erb +32 -0
- data/app/components/shadcn/checkbox_component.rb +4 -43
- data/app/components/shadcn/collapsible_component.html.erb +8 -0
- data/app/components/shadcn/collapsible_component.rb +6 -15
- 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_component.html.erb +11 -0
- data/app/components/shadcn/context_menu_component.rb +6 -26
- 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/dialog_component.html.erb +14 -0
- data/app/components/shadcn/dialog_component.rb +8 -29
- data/app/components/shadcn/drawer_component.html.erb +12 -0
- data/app/components/shadcn/drawer_component.rb +7 -27
- data/app/components/shadcn/dropdown_menu_checkbox_item_component.rb +76 -0
- data/app/components/shadcn/dropdown_menu_component.html.erb +14 -0
- data/app/components/shadcn/dropdown_menu_component.rb +9 -29
- 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/field_component.rb +7 -8
- data/app/components/shadcn/hover_card_component.html.erb +12 -0
- data/app/components/shadcn/hover_card_component.rb +7 -26
- data/app/components/shadcn/input_component.html.erb +18 -0
- data/app/components/shadcn/input_component.rb +2 -27
- data/app/components/shadcn/input_otp_component.rb +3 -3
- data/app/components/shadcn/kbd_component.html.erb +1 -0
- data/app/components/shadcn/kbd_component.rb +3 -10
- data/app/components/shadcn/label_component.html.erb +3 -0
- data/app/components/shadcn/label_component.rb +2 -18
- data/app/components/shadcn/menubar_component.html.erb +6 -0
- data/app/components/shadcn/menubar_component.rb +4 -15
- 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/native_select_component.html.erb +22 -0
- data/app/components/shadcn/native_select_component.rb +9 -39
- data/app/components/shadcn/navigation_menu_component.html.erb +6 -0
- data/app/components/shadcn/navigation_menu_component.rb +4 -15
- data/app/components/shadcn/pagination_component.html.erb +5 -0
- data/app/components/shadcn/pagination_component.rb +11 -15
- data/app/components/shadcn/popover_component.html.erb +15 -0
- data/app/components/shadcn/popover_component.rb +10 -30
- data/app/components/shadcn/progress_component.html.erb +13 -0
- data/app/components/shadcn/progress_component.rb +6 -26
- data/app/components/shadcn/radio_group_component.html.erb +8 -0
- data/app/components/shadcn/radio_group_component.rb +12 -26
- 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/scroll_area_component.html.erb +7 -0
- data/app/components/shadcn/scroll_area_component.rb +4 -16
- data/app/components/shadcn/select_component.html.erb +46 -0
- data/app/components/shadcn/select_component.rb +29 -86
- data/app/components/shadcn/separator_component.html.erb +5 -0
- data/app/components/shadcn/separator_component.rb +6 -14
- data/app/components/shadcn/sheet_component.html.erb +12 -0
- data/app/components/shadcn/sheet_component.rb +7 -27
- data/app/components/shadcn/sidebar_component.rb +2 -2
- data/app/components/shadcn/skeleton_component.html.erb +1 -0
- data/app/components/shadcn/skeleton_component.rb +4 -2
- data/app/components/shadcn/slider_component.html.erb +12 -0
- data/app/components/shadcn/slider_component.rb +2 -21
- data/app/components/shadcn/spinner_component.html.erb +18 -0
- data/app/components/shadcn/spinner_component.rb +2 -30
- data/app/components/shadcn/switch_component.html.erb +72 -0
- data/app/components/shadcn/switch_component.rb +4 -82
- data/app/components/shadcn/table_component.html.erb +9 -0
- data/app/components/shadcn/table_component.rb +2 -10
- data/app/components/shadcn/tabs_component.html.erb +8 -0
- data/app/components/shadcn/tabs_component.rb +4 -17
- data/app/components/shadcn/textarea_component.html.erb +13 -0
- data/app/components/shadcn/textarea_component.rb +6 -22
- data/app/components/shadcn/toast_component.html.erb +36 -0
- data/app/components/shadcn/toast_component.rb +6 -54
- data/app/components/shadcn/toggle_component.html.erb +12 -0
- data/app/components/shadcn/toggle_component.rb +6 -21
- data/app/components/shadcn/toggle_group_component.html.erb +14 -0
- data/app/components/shadcn/toggle_group_component.rb +6 -29
- data/app/components/shadcn/tooltip_component.html.erb +20 -0
- data/app/components/shadcn/tooltip_component.rb +13 -38
- data/lib/generators/shadcn/add/USAGE +24 -0
- data/lib/generators/shadcn/add/add_generator.rb +279 -0
- data/lib/generators/shadcn/install/USAGE +22 -0
- data/lib/generators/shadcn/install/install_generator.rb +8 -3
- data/lib/generators/shadcn/install/templates/initializer.rb.tt +7 -27
- data/lib/generators/shadcn/install/templates/shadcn.yml.tt +15 -31
- data/lib/shadcn/rails/version.rb +1 -1
- metadata +54 -42
- data/.dockerignore +0 -40
- data/CLAUDE.md +0 -463
- data/PROGRESS.md +0 -485
- data/Rakefile +0 -29
- data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +0 -13
- data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +0 -46
- data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +0 -111
- data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +0 -27
- data/__tests__/controllers/accordion_controller.test.js +0 -904
- data/__tests__/controllers/calendar_controller.test.js +0 -1370
- data/__tests__/controllers/carousel_controller.test.js +0 -912
- data/__tests__/controllers/checkbox_controller.test.js +0 -454
- data/__tests__/controllers/collapsible_controller.test.js +0 -407
- data/__tests__/controllers/combobox_controller.test.js +0 -966
- data/__tests__/controllers/context_menu_controller.test.js +0 -627
- data/__tests__/controllers/date_picker_controller.test.js +0 -636
- data/__tests__/controllers/dialog_controller.test.js +0 -878
- data/__tests__/controllers/drawer_controller.test.js +0 -995
- data/__tests__/controllers/menubar_controller.test.js +0 -736
- data/__tests__/controllers/navigation_menu_controller.test.js +0 -598
- data/__tests__/controllers/popover_controller.test.js +0 -1007
- data/__tests__/controllers/radio_group_controller.test.js +0 -640
- data/__tests__/controllers/resizable_controller.test.js +0 -680
- data/__tests__/controllers/select_controller.test.js +0 -674
- data/__tests__/controllers/sheet_controller.test.js +0 -986
- data/__tests__/controllers/slider_controller.test.js +0 -1036
- data/__tests__/controllers/switch_controller.test.js +0 -424
- data/__tests__/controllers/tabs_controller.test.js +0 -907
- data/__tests__/controllers/toggle_group_controller.test.js +0 -839
- data/__tests__/controllers/tooltip_controller.test.js +0 -808
- data/__tests__/helpers/stimulus-test-helper.js +0 -203
- data/babel.config.cjs +0 -5
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/jest.config.js +0 -19
- data/jest.setup.js +0 -8
- data/lib/generators/shadcn/component/component_generator.rb +0 -188
- data/lib/generators/shadcn/theme/theme_generator.rb +0 -128
- data/package-lock.json +0 -7415
- data/package.json +0 -68
- data/rollup.config.js +0 -29
data/README.md
CHANGED
|
@@ -2,1482 +2,186 @@
|
|
|
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 |
|
|
44
|
+
## Adding Components
|
|
936
45
|
|
|
937
|
-
|
|
46
|
+
Copy components into your app for customization:
|
|
938
47
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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)`) |
|
|
48
|
+
```bash
|
|
49
|
+
# List all available components
|
|
50
|
+
rails generate shadcn:add --list
|
|
944
51
|
|
|
945
|
-
|
|
52
|
+
# Add specific components
|
|
53
|
+
rails generate shadcn:add button dialog tabs
|
|
946
54
|
|
|
947
|
-
|
|
55
|
+
# Add all components
|
|
56
|
+
rails generate shadcn:add --all
|
|
948
57
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
<% collapsible.with_trigger do %>
|
|
952
|
-
<div class="flex items-center justify-between px-4 py-2 border rounded-md">
|
|
953
|
-
<span>Click to expand</span>
|
|
954
|
-
<svg><!-- chevron icon --></svg>
|
|
955
|
-
</div>
|
|
956
|
-
<% end %>
|
|
957
|
-
<% collapsible.with_content do %>
|
|
958
|
-
<div class="mt-2 p-4 border rounded-md">
|
|
959
|
-
Collapsible content here
|
|
960
|
-
</div>
|
|
961
|
-
<% end %>
|
|
962
|
-
<% end %>
|
|
58
|
+
# Add without Stimulus controllers
|
|
59
|
+
rails generate shadcn:add dialog --exclude-controllers
|
|
963
60
|
```
|
|
964
61
|
|
|
965
|
-
|
|
62
|
+
Components are copied to `app/components/shadcn/` and controllers to `app/javascript/controllers/shadcn/`. Local files take precedence over the gem's built-in components.
|
|
966
63
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
Visually or semantically separates content.
|
|
64
|
+
## Quick Start
|
|
970
65
|
|
|
971
66
|
```erb
|
|
972
|
-
<%#
|
|
973
|
-
<%= render Shadcn::
|
|
67
|
+
<%# Button %>
|
|
68
|
+
<%= render Shadcn::ButtonComponent.new(variant: :default) { "Click me" } %>
|
|
974
69
|
|
|
975
|
-
<%#
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
Augments native scroll functionality for custom, cross-browser styling.
|
|
70
|
+
<%# Card %>
|
|
71
|
+
<%= render Shadcn::CardComponent.new do |card| %>
|
|
72
|
+
<% card.with_header do |header| %>
|
|
73
|
+
<% header.with_title { "Welcome" } %>
|
|
74
|
+
<% end %>
|
|
75
|
+
<% card.with_content_slot do %>
|
|
76
|
+
<p>Your content here</p>
|
|
77
|
+
<% end %>
|
|
78
|
+
<% end %>
|
|
986
79
|
|
|
987
|
-
|
|
988
|
-
<%= render Shadcn::
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
80
|
+
<%# Dialog %>
|
|
81
|
+
<%= render Shadcn::DialogComponent.new do |dialog| %>
|
|
82
|
+
<% dialog.with_trigger do %>
|
|
83
|
+
<%= render Shadcn::ButtonComponent.new { "Open" } %>
|
|
84
|
+
<% end %>
|
|
85
|
+
<% dialog.with_body do |body| %>
|
|
86
|
+
<% body.with_header do |header| %>
|
|
87
|
+
<% header.with_title { "Edit Profile" } %>
|
|
992
88
|
<% end %>
|
|
993
|
-
|
|
89
|
+
<p>Dialog content here</p>
|
|
90
|
+
<% end %>
|
|
994
91
|
<% end %>
|
|
995
92
|
```
|
|
996
93
|
|
|
997
|
-
##
|
|
998
|
-
|
|
999
|
-
### Available Themes
|
|
94
|
+
## Components
|
|
1000
95
|
|
|
1001
|
-
|
|
96
|
+
| Category | Components |
|
|
97
|
+
|----------|------------|
|
|
98
|
+
| **Actions** | Button, Toggle, Toggle Group |
|
|
99
|
+
| **Forms** | Input, Textarea, Label, Checkbox, Switch, Radio Group, Select, Slider |
|
|
100
|
+
| **Data Display** | Badge, Avatar, Card, Table, Progress, Skeleton, Aspect Ratio |
|
|
101
|
+
| **Feedback** | Alert, Tooltip, Toast |
|
|
102
|
+
| **Overlays** | Dialog, Alert Dialog, Sheet, Drawer, Popover, Hover Card, Dropdown Menu, Context Menu |
|
|
103
|
+
| **Navigation** | Tabs, Accordion, Breadcrumb, Pagination, Collapsible, Navigation Menu, Menubar |
|
|
104
|
+
| **Layout** | Separator, Scroll Area, Resizable |
|
|
1002
105
|
|
|
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 |
|
|
106
|
+
## Theming
|
|
1010
107
|
|
|
1011
|
-
|
|
108
|
+
Configure colors in your initializer:
|
|
1012
109
|
|
|
1013
110
|
```ruby
|
|
1014
111
|
# config/initializers/shadcn.rb
|
|
1015
112
|
Shadcn::Rails.configure do |config|
|
|
1016
113
|
config.base_color = "slate" # neutral, slate, stone, gray, zinc
|
|
1017
|
-
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
Or use the generator:
|
|
1021
|
-
|
|
1022
|
-
```bash
|
|
1023
|
-
rails generate shadcn:theme slate
|
|
1024
|
-
```
|
|
1025
|
-
|
|
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
|
|
114
|
+
config.dark_mode = :class # :class, :media, :both
|
|
1131
115
|
end
|
|
1132
116
|
```
|
|
1133
117
|
|
|
1134
118
|
## Stimulus Controllers
|
|
1135
119
|
|
|
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
|
|
120
|
+
All interactive components have corresponding Stimulus controllers:
|
|
1231
121
|
|
|
1232
122
|
| Controller | Components |
|
|
1233
123
|
|------------|------------|
|
|
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
124
|
| `shadcn--dialog` | Dialog |
|
|
1241
|
-
| `shadcn--
|
|
1242
|
-
| `shadcn--
|
|
1243
|
-
| `shadcn--
|
|
1244
|
-
| `shadcn--input-otp` | InputOtp |
|
|
1245
|
-
| `shadcn--menubar` | Menubar |
|
|
1246
|
-
| `shadcn--navigation-menu` | NavigationMenu |
|
|
125
|
+
| `shadcn--sheet` | Sheet |
|
|
126
|
+
| `shadcn--tabs` | Tabs |
|
|
127
|
+
| `shadcn--accordion` | Accordion |
|
|
1247
128
|
| `shadcn--popover` | Popover |
|
|
1248
|
-
| `shadcn--
|
|
1249
|
-
| `shadcn--resizable` | Resizable |
|
|
1250
|
-
| `shadcn--scroll-area` | ScrollArea |
|
|
129
|
+
| `shadcn--dropdown-menu` | DropdownMenu |
|
|
1251
130
|
| `shadcn--select` | Select |
|
|
1252
|
-
| `shadcn--sheet` | Sheet |
|
|
1253
|
-
| `shadcn--slider` | Slider |
|
|
1254
131
|
| `shadcn--switch` | Switch |
|
|
1255
|
-
| `shadcn--
|
|
1256
|
-
| `shadcn--toast` | Toast |
|
|
1257
|
-
| `shadcn--toggle` | Toggle |
|
|
1258
|
-
| `shadcn--toggle-group` | ToggleGroup |
|
|
132
|
+
| `shadcn--slider` | Slider |
|
|
1259
133
|
| `shadcn--tooltip` | Tooltip |
|
|
134
|
+
| `shadcn--toast` | Toast |
|
|
1260
135
|
|
|
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
|
|
136
|
+
Register individual controllers for tree-shaking:
|
|
1316
137
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
toggle(): void
|
|
1321
|
-
}
|
|
138
|
+
```javascript
|
|
139
|
+
import DialogController from "shadcn-rails-stimulus/controllers/dialog_controller"
|
|
140
|
+
application.register("shadcn--dialog", DialogController)
|
|
1322
141
|
```
|
|
1323
142
|
|
|
1324
|
-
|
|
143
|
+
## TypeScript Support
|
|
1325
144
|
|
|
1326
|
-
|
|
145
|
+
Full TypeScript definitions included for all controllers:
|
|
1327
146
|
|
|
1328
147
|
```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
|
|
148
|
+
import { registerShadcnControllers } from "shadcn-rails-stimulus"
|
|
149
|
+
import DialogController from "shadcn-rails-stimulus/controllers/dialog_controller"
|
|
1346
150
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
151
|
+
// Full autocomplete and type checking
|
|
152
|
+
const dialog = new DialogController()
|
|
153
|
+
dialog.open() // Methods are typed
|
|
154
|
+
dialog.openValue // Values are typed (boolean)
|
|
1351
155
|
```
|
|
1352
156
|
|
|
1353
|
-
|
|
157
|
+
## Requirements
|
|
1354
158
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
159
|
+
- Ruby >= 3.1
|
|
160
|
+
- Rails >= 7.0
|
|
161
|
+
- Tailwind CSS >= 3.0
|
|
162
|
+
- Stimulus >= 3.0
|
|
163
|
+
- ViewComponent >= 3.0
|
|
1358
164
|
|
|
1359
165
|
## Development
|
|
1360
166
|
|
|
1361
|
-
After checking out the repo:
|
|
1362
|
-
|
|
1363
167
|
```bash
|
|
1364
168
|
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 }
|
|
169
|
+
cd test/dummy && rails server
|
|
1449
170
|
```
|
|
1450
171
|
|
|
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
|
-
```
|
|
172
|
+
Visit http://localhost:3000/docs for the component documentation.
|
|
1463
173
|
|
|
1464
174
|
## Contributing
|
|
1465
175
|
|
|
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
|
|
176
|
+
Bug reports and pull requests are welcome at https://github.com/iheanyi/shadcn-rails.
|
|
1473
177
|
|
|
1474
178
|
## License
|
|
1475
179
|
|
|
1476
|
-
|
|
180
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
1477
181
|
|
|
1478
182
|
## Credits
|
|
1479
183
|
|
|
1480
|
-
- [shadcn/ui](https://ui.shadcn.com) -
|
|
184
|
+
- [shadcn/ui](https://ui.shadcn.com) - Original React component library
|
|
1481
185
|
- [ViewComponent](https://viewcomponent.org) - Ruby component framework
|
|
1482
186
|
- [Stimulus](https://stimulus.hotwired.dev) - JavaScript framework
|
|
1483
|
-
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS
|
|
187
|
+
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS
|