shadcn-rails 0.2.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 +66 -2
- data/README.md +21 -8
- data/__mocks__/@floating-ui/dom.js +67 -0
- data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +23 -2
- data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +4 -31
- data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +32 -41
- data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
- data/app/assets/javascripts/shadcn/controllers/popover_controller.js +29 -54
- data/app/assets/javascripts/shadcn/controllers/select_controller.js +26 -8
- data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
- data/app/assets/javascripts/shadcn/index.js +7 -1
- data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
- data/app/assets/stylesheets/shadcn/base.css +32 -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/context_menu_component.html.erb +11 -0
- data/app/components/shadcn/context_menu_component.rb +6 -26
- 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_component.html.erb +14 -0
- data/app/components/shadcn/dropdown_menu_component.rb +9 -29
- 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/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/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 +6 -80
- 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 +47 -45
- data/.dockerignore +0 -40
- data/CLAUDE.md +0 -612
- data/PROGRESS.md +0 -495
- data/Rakefile +0 -95
- 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 -971
- data/__tests__/controllers/context_menu_controller.test.js +0 -905
- 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 -737
- data/__tests__/controllers/navigation_menu_controller.test.js +0 -599
- data/__tests__/controllers/popover_controller.test.js +0 -982
- 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 -678
- 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/bump +0 -321
- data/bin/console +0 -11
- data/bin/release +0 -205
- data/bin/setup +0 -8
- data/bin/test +0 -75
- 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 -7438
- data/package.json +0 -71
- data/rollup.config.js +0 -29
data/CLAUDE.md
DELETED
|
@@ -1,612 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md - Development Guide for shadcn-rails
|
|
2
|
-
|
|
3
|
-
This document contains learnings, patterns, and guidelines for future development sessions on this project.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
shadcn-rails is a Ruby port of [shadcn/ui](https://ui.shadcn.com) for Rails applications. It uses:
|
|
8
|
-
- **ViewComponent** for Ruby-based component architecture
|
|
9
|
-
- **Stimulus** for JavaScript interactivity
|
|
10
|
-
- **Tailwind CSS** for styling
|
|
11
|
-
- **CSS Custom Properties** for theming
|
|
12
|
-
|
|
13
|
-
## Architecture Patterns
|
|
14
|
-
|
|
15
|
-
### Component Structure
|
|
16
|
-
|
|
17
|
-
All components inherit from `Shadcn::BaseComponent` which provides:
|
|
18
|
-
- `cn(*classes)` - Class name merging utility (like clsx + tailwind-merge)
|
|
19
|
-
- `stimulus_data(controller, values, actions)` - Stimulus data attribute builder
|
|
20
|
-
- `build_html_attributes(**attrs)` - HTML attribute builder with class merging
|
|
21
|
-
|
|
22
|
-
```ruby
|
|
23
|
-
# app/components/shadcn/base_component.rb
|
|
24
|
-
class Shadcn::BaseComponent < ViewComponent::Base
|
|
25
|
-
include Shadcn::Rails::Helpers::ClassNameHelper
|
|
26
|
-
|
|
27
|
-
def cn(*classes)
|
|
28
|
-
Shadcn::Rails.cn(*classes)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Component Location
|
|
34
|
-
|
|
35
|
-
Components are located in `app/components/shadcn/`:
|
|
36
|
-
- Each component is a single Ruby file (e.g., `button_component.rb`)
|
|
37
|
-
- Complex components use ViewComponent slots for composition
|
|
38
|
-
- ERB templates are inline using `erb_template` when simple, or separate `.html.erb` files when complex
|
|
39
|
-
|
|
40
|
-
### Stimulus Controllers
|
|
41
|
-
|
|
42
|
-
Controllers are in `app/assets/javascripts/shadcn/controllers/`:
|
|
43
|
-
- Named `{component}_controller.js`
|
|
44
|
-
- Use Stimulus naming convention: `shadcn--{component}`
|
|
45
|
-
- Handle interactivity: open/close states, keyboard navigation, focus management
|
|
46
|
-
|
|
47
|
-
```javascript
|
|
48
|
-
// Example controller pattern
|
|
49
|
-
import { Controller } from "@hotwired/stimulus"
|
|
50
|
-
|
|
51
|
-
export default class extends Controller {
|
|
52
|
-
static targets = ["trigger", "content"]
|
|
53
|
-
static values = { open: Boolean }
|
|
54
|
-
|
|
55
|
-
toggle() {
|
|
56
|
-
this.openValue = !this.openValue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
openValueChanged() {
|
|
60
|
-
// React to state changes
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### CSS Theming
|
|
66
|
-
|
|
67
|
-
CSS variables are defined in `app/assets/stylesheets/shadcn/base.css`:
|
|
68
|
-
- All colors use HSL format without `hsl()` wrapper: `--primary: 0 0% 9%;`
|
|
69
|
-
- Components use: `hsl(var(--primary))`
|
|
70
|
-
- Dark mode uses `.dark` class on `<html>` element
|
|
71
|
-
|
|
72
|
-
Theme presets are in `lib/shadcn/rails/configuration.rb` with full HSL values for:
|
|
73
|
-
- neutral, slate, stone, zinc, gray
|
|
74
|
-
|
|
75
|
-
## Key Files
|
|
76
|
-
|
|
77
|
-
### Core Library
|
|
78
|
-
- `lib/shadcn/rails.rb` - Main module, configuration, `cn()` helper
|
|
79
|
-
- `lib/shadcn/rails/engine.rb` - Rails engine setup
|
|
80
|
-
- `lib/shadcn/rails/configuration.rb` - Theme configuration with CSS variables
|
|
81
|
-
- `lib/shadcn/rails/class_merger.rb` - Tailwind class conflict resolution
|
|
82
|
-
|
|
83
|
-
### Components (app/components/shadcn/)
|
|
84
|
-
|
|
85
|
-
The library includes 47 components organized by category:
|
|
86
|
-
|
|
87
|
-
**Buttons & Actions**
|
|
88
|
-
| File | Description |
|
|
89
|
-
|------|-------------|
|
|
90
|
-
| `button_component.rb` | 6 variants (default, secondary, destructive, outline, ghost, link), 6 sizes |
|
|
91
|
-
| `toggle_component.rb` | Two-state button with pressed/unpressed states |
|
|
92
|
-
| `toggle_group_component.rb` | Group of toggle buttons (single/multiple selection) |
|
|
93
|
-
| `button_group_component.rb` | Group of related buttons |
|
|
94
|
-
|
|
95
|
-
**Form Inputs**
|
|
96
|
-
| File | Description |
|
|
97
|
-
|------|-------------|
|
|
98
|
-
| `input_component.rb` | Text input with various types |
|
|
99
|
-
| `textarea_component.rb` | Multi-line text input |
|
|
100
|
-
| `label_component.rb` | Form label element |
|
|
101
|
-
| `checkbox_component.rb` | Checkbox with indeterminate state support |
|
|
102
|
-
| `switch_component.rb` | Toggle switch control |
|
|
103
|
-
| `slider_component.rb` | Range slider input |
|
|
104
|
-
| `select_component.rb` | Custom styled select dropdown |
|
|
105
|
-
| `native_select_component.rb` | Native HTML select with styling |
|
|
106
|
-
| `radio_group_component.rb` | Radio button group |
|
|
107
|
-
| `field_component.rb` | Form field wrapper with label/error slots |
|
|
108
|
-
| `input_group_component.rb` | Input with prefix/suffix addons |
|
|
109
|
-
| `input_otp_component.rb` | One-time password input |
|
|
110
|
-
|
|
111
|
-
**Data Display**
|
|
112
|
-
| File | Description |
|
|
113
|
-
|------|-------------|
|
|
114
|
-
| `badge_component.rb` | Labels with variants (default, secondary, destructive, outline) |
|
|
115
|
-
| `avatar_component.rb` | User avatar with image/fallback |
|
|
116
|
-
| `card_component.rb` | Header, content, footer slots |
|
|
117
|
-
| `table_component.rb` | Styled table with header/body/footer |
|
|
118
|
-
| `progress_component.rb` | Progress bar indicator |
|
|
119
|
-
| `skeleton_component.rb` | Loading placeholder |
|
|
120
|
-
| `spinner_component.rb` | Loading spinner animation |
|
|
121
|
-
| `kbd_component.rb` | Keyboard key display |
|
|
122
|
-
| `typography_component.rb` | Text styling utilities |
|
|
123
|
-
| `aspect_ratio_component.rb` | Maintains aspect ratio for media content |
|
|
124
|
-
| `scroll_area_component.rb` | Custom scrollbar container |
|
|
125
|
-
|
|
126
|
-
**Feedback**
|
|
127
|
-
| File | Description |
|
|
128
|
-
|------|-------------|
|
|
129
|
-
| `alert_component.rb` | Callout/notification with variants (default, destructive) |
|
|
130
|
-
| `tooltip_component.rb` | Hover tooltip popup |
|
|
131
|
-
| `toast_component.rb` | Temporary notification messages |
|
|
132
|
-
|
|
133
|
-
**Overlays**
|
|
134
|
-
| File | Description |
|
|
135
|
-
|------|-------------|
|
|
136
|
-
| `dialog_component.rb` | Modal with portal, focus trap |
|
|
137
|
-
| `alert_dialog_component.rb` | Confirmation dialog with cancel/confirm actions |
|
|
138
|
-
| `sheet_component.rb` | Slide-out panel from any edge |
|
|
139
|
-
| `drawer_component.rb` | Bottom sheet drawer |
|
|
140
|
-
| `popover_component.rb` | Floating content panel |
|
|
141
|
-
| `hover_card_component.rb` | Card that appears on hover |
|
|
142
|
-
| `dropdown_menu_component.rb` | Dropdown menu with items, separators, keyboard shortcuts |
|
|
143
|
-
|
|
144
|
-
**Navigation**
|
|
145
|
-
| File | Description |
|
|
146
|
-
|------|-------------|
|
|
147
|
-
| `tabs_component.rb` | Tabbed interface with URL sync support |
|
|
148
|
-
| `accordion_component.rb` | Collapsible sections (single/multiple mode) |
|
|
149
|
-
| `breadcrumb_component.rb` | Navigation breadcrumb trail |
|
|
150
|
-
| `pagination_component.rb` | Page navigation with Kaminari/Pagy/WillPaginate support |
|
|
151
|
-
| `collapsible_component.rb` | Show/hide content section |
|
|
152
|
-
| `separator_component.rb` | Visual divider line |
|
|
153
|
-
| `context_menu_component.rb` | Right-click context menu |
|
|
154
|
-
| `menubar_component.rb` | Horizontal menu bar with dropdowns |
|
|
155
|
-
| `navigation_menu_component.rb` | Navigation with dropdown content |
|
|
156
|
-
| `resizable_component.rb` | Resizable panel groups |
|
|
157
|
-
|
|
158
|
-
### Generators (lib/generators/shadcn/)
|
|
159
|
-
- `install/` - Initial setup generator
|
|
160
|
-
- `component/` - Add individual components
|
|
161
|
-
- `theme/` - Switch color themes
|
|
162
|
-
|
|
163
|
-
### Tests
|
|
164
|
-
- `test/components/` - Component unit tests
|
|
165
|
-
- `test/components/previews/` - Lookbook previews
|
|
166
|
-
- `test/dummy/` - Rails dummy app for integration testing
|
|
167
|
-
|
|
168
|
-
## Development Commands
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
# Install dependencies
|
|
172
|
-
bundle install
|
|
173
|
-
|
|
174
|
-
# Run tests
|
|
175
|
-
bundle exec rake test
|
|
176
|
-
|
|
177
|
-
# Run component tests only
|
|
178
|
-
bundle exec rake test_components
|
|
179
|
-
|
|
180
|
-
# Build JavaScript (from project root)
|
|
181
|
-
npm run build
|
|
182
|
-
|
|
183
|
-
# Start dummy app for local development (RECOMMENDED)
|
|
184
|
-
cd test/dummy && bin/dev
|
|
185
|
-
|
|
186
|
-
# Start Lookbook previews
|
|
187
|
-
cd test/dummy && rails lookbook:preview
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Local Testing with bin/dev
|
|
191
|
-
|
|
192
|
-
**IMPORTANT**: Always use `bin/dev` instead of `rails server` when testing locally. This ensures:
|
|
193
|
-
1. JavaScript is automatically rebuilt when files change (via esbuild watch mode)
|
|
194
|
-
2. CSS is automatically rebuilt when Tailwind classes change
|
|
195
|
-
3. Changes to Stimulus controllers are immediately available
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
# Start the development server (from test/dummy)
|
|
199
|
-
cd test/dummy && bin/dev
|
|
200
|
-
|
|
201
|
-
# If port 3000 is in use, kill existing processes first:
|
|
202
|
-
lsof -ti:3000 | xargs kill -9 2>/dev/null
|
|
203
|
-
rm -f tmp/pids/server.pid
|
|
204
|
-
bin/dev
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
The development server runs:
|
|
208
|
-
- Rails server on port 3000
|
|
209
|
-
- esbuild in watch mode for JavaScript bundling
|
|
210
|
-
- Tailwind CSS in watch mode
|
|
211
|
-
|
|
212
|
-
**Note**: After modifying Stimulus controllers in `app/assets/javascripts/shadcn/controllers/`, you must also run `npm run build` from the project root to update the npm package distribution files.
|
|
213
|
-
|
|
214
|
-
## Regression Testing
|
|
215
|
-
|
|
216
|
-
When making changes to interactive components, always verify:
|
|
217
|
-
|
|
218
|
-
### Context Menu
|
|
219
|
-
1. **Open/Close**: Right-click to open, click outside or press Escape to close
|
|
220
|
-
2. **Double right-click**: Right-clicking twice in quick succession should reposition the menu
|
|
221
|
-
3. **Keyboard navigation**: Arrow keys, Home/End, Enter/Space for selection
|
|
222
|
-
4. **Scroll lock**: Background should not scroll when menu is open
|
|
223
|
-
5. **Animation**: Smooth fade-in/out animation (100ms)
|
|
224
|
-
6. **Positioning**: Menu stays within viewport bounds
|
|
225
|
-
|
|
226
|
-
### Radio Group
|
|
227
|
-
1. **Labels are clickable**: Clicking the label should select the radio
|
|
228
|
-
2. **Descriptions render**: Items with descriptions show them below the label
|
|
229
|
-
3. **Keyboard navigation**: Tab to focus, arrow keys to move between items
|
|
230
|
-
4. **Single selection**: Only one item can be selected at a time
|
|
231
|
-
|
|
232
|
-
### Dropdown Menu
|
|
233
|
-
1. **All item types**: Test items, checkboxes, radio groups, separators, labels
|
|
234
|
-
2. **Keyboard shortcuts**: Display correctly with proper styling
|
|
235
|
-
3. **Submenus**: Open on hover, close when moving away
|
|
236
|
-
|
|
237
|
-
### Dialog/Sheet/Drawer
|
|
238
|
-
1. **Focus trap**: Tab should cycle within the modal
|
|
239
|
-
2. **Escape closes**: Pressing Escape should close the modal
|
|
240
|
-
3. **Overlay click**: Clicking the overlay should close (unless modal)
|
|
241
|
-
4. **Body scroll lock**: Background should not scroll when open
|
|
242
|
-
|
|
243
|
-
### Testing with Playwright
|
|
244
|
-
|
|
245
|
-
Use the MCP Playwright tools for automated testing:
|
|
246
|
-
|
|
247
|
-
```javascript
|
|
248
|
-
// Navigate to docs page
|
|
249
|
-
mcp__playwright__browser_navigate({ url: "http://localhost:3000/docs/context-menu" })
|
|
250
|
-
|
|
251
|
-
// Take a snapshot to see the page state
|
|
252
|
-
mcp__playwright__browser_snapshot()
|
|
253
|
-
|
|
254
|
-
// Right-click to open context menu (use browser_run_code for context menu)
|
|
255
|
-
mcp__playwright__browser_run_code({
|
|
256
|
-
code: `await page.locator('[data-shadcn--context-menu-target="trigger"]').click({ button: 'right' })`
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// Press Escape to close
|
|
260
|
-
mcp__playwright__browser_press_key({ key: "Escape" })
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### Bug-Fixing Workflow
|
|
264
|
-
|
|
265
|
-
**IMPORTANT**: When fixing reported bugs, follow this workflow:
|
|
266
|
-
|
|
267
|
-
1. **Reproduce via Playwright** - Use MCP Playwright tools to confirm the bug exists
|
|
268
|
-
2. **Write a failing Jest test** - Create a test in `__tests__/controllers/` that captures the bug
|
|
269
|
-
3. **Fix the bug** - Update the controller/component code
|
|
270
|
-
4. **Verify Jest test passes** - Run `npm test` to confirm the fix
|
|
271
|
-
5. **Verify via Playwright** - Use MCP Playwright again to confirm the bug is fixed
|
|
272
|
-
|
|
273
|
-
This ensures:
|
|
274
|
-
- Bugs are properly understood before fixing
|
|
275
|
-
- Regression tests prevent the bug from returning
|
|
276
|
-
- Fixes are verified both in isolation (Jest) and integration (Playwright)
|
|
277
|
-
|
|
278
|
-
```bash
|
|
279
|
-
# Run Jest tests
|
|
280
|
-
npm test
|
|
281
|
-
|
|
282
|
-
# Run specific test file
|
|
283
|
-
npm test -- context_menu_controller.test.js
|
|
284
|
-
|
|
285
|
-
# Run tests with coverage
|
|
286
|
-
npm test -- --coverage
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
## Test-First Development
|
|
290
|
-
|
|
291
|
-
**IMPORTANT**: Always run tests for files you modify to ensure they still pass.
|
|
292
|
-
|
|
293
|
-
### Testing Guidelines
|
|
294
|
-
|
|
295
|
-
1. **Before making changes**: Run relevant tests to understand current behavior
|
|
296
|
-
2. **After making changes**: Run all affected tests to ensure nothing breaks
|
|
297
|
-
3. **When fixing bugs**: Write a failing test first, then fix the bug
|
|
298
|
-
4. **When adding features**: Write tests alongside the new code
|
|
299
|
-
|
|
300
|
-
### Running Tests
|
|
301
|
-
|
|
302
|
-
```bash
|
|
303
|
-
# Run all Ruby tests
|
|
304
|
-
bundle exec rake test
|
|
305
|
-
|
|
306
|
-
# Run specific component test
|
|
307
|
-
bundle exec ruby -Ilib:test test/components/context_menu_component_test.rb
|
|
308
|
-
|
|
309
|
-
# Run all Jest tests
|
|
310
|
-
npm test
|
|
311
|
-
|
|
312
|
-
# Run specific Jest test file
|
|
313
|
-
npm test -- context_menu_controller.test.js
|
|
314
|
-
|
|
315
|
-
# Run tests with coverage
|
|
316
|
-
npm test -- --coverage
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Test Coverage Expectations
|
|
320
|
-
|
|
321
|
-
- **Ruby Components**: Each component should have tests for:
|
|
322
|
-
- Default rendering
|
|
323
|
-
- All variants and sizes
|
|
324
|
-
- Slot rendering
|
|
325
|
-
- Accessibility attributes (ARIA)
|
|
326
|
-
- Custom class names and HTML options
|
|
327
|
-
|
|
328
|
-
- **JavaScript Controllers**: Each Stimulus controller should have tests for:
|
|
329
|
-
- Initialization and connect
|
|
330
|
-
- Value changes
|
|
331
|
-
- Target interactions
|
|
332
|
-
- Event handling (click, keyboard, etc.)
|
|
333
|
-
- Edge cases and error handling
|
|
334
|
-
- Cleanup on disconnect
|
|
335
|
-
|
|
336
|
-
## Patterns to Follow
|
|
337
|
-
|
|
338
|
-
### 1. Component Variants
|
|
339
|
-
|
|
340
|
-
Use symbols for variant options and define them as constants:
|
|
341
|
-
|
|
342
|
-
```ruby
|
|
343
|
-
VARIANTS = {
|
|
344
|
-
default: "bg-primary text-primary-foreground",
|
|
345
|
-
destructive: "bg-destructive text-destructive-foreground",
|
|
346
|
-
outline: "border border-input bg-background",
|
|
347
|
-
secondary: "bg-secondary text-secondary-foreground",
|
|
348
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
349
|
-
link: "text-primary underline-offset-4 hover:underline"
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
def initialize(variant: :default, **attrs)
|
|
353
|
-
@variant = variant.to_sym
|
|
354
|
-
end
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### 2. Compound Components with Slots
|
|
358
|
-
|
|
359
|
-
Use ViewComponent's `renders_one` and `renders_many` for composition:
|
|
360
|
-
|
|
361
|
-
```ruby
|
|
362
|
-
renders_one :header, "HeaderComponent"
|
|
363
|
-
renders_many :items, "ItemComponent"
|
|
364
|
-
|
|
365
|
-
class HeaderComponent < ViewComponent::Base
|
|
366
|
-
renders_one :title
|
|
367
|
-
renders_one :description
|
|
368
|
-
end
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### 3. Stimulus Integration
|
|
372
|
-
|
|
373
|
-
Components that need JavaScript should:
|
|
374
|
-
1. Define a Stimulus controller
|
|
375
|
-
2. Use `stimulus_data()` helper in the component
|
|
376
|
-
3. Follow the `shadcn--{name}` naming convention
|
|
377
|
-
|
|
378
|
-
```ruby
|
|
379
|
-
def stimulus_attributes
|
|
380
|
-
stimulus_data(
|
|
381
|
-
"shadcn--dialog",
|
|
382
|
-
{ open: @open },
|
|
383
|
-
{ "trigger->click": "toggle" }
|
|
384
|
-
)
|
|
385
|
-
end
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
### 4. Accessibility (ARIA)
|
|
389
|
-
|
|
390
|
-
All interactive components must include proper ARIA attributes:
|
|
391
|
-
- `role` attributes where needed
|
|
392
|
-
- `aria-expanded`, `aria-hidden`, `aria-controls`
|
|
393
|
-
- `aria-labelledby`, `aria-describedby` for relationships
|
|
394
|
-
- Keyboard navigation support
|
|
395
|
-
|
|
396
|
-
### 5. CSS Class Merging
|
|
397
|
-
|
|
398
|
-
Always use `cn()` for class composition to handle Tailwind conflicts:
|
|
399
|
-
|
|
400
|
-
```ruby
|
|
401
|
-
def classes
|
|
402
|
-
cn(
|
|
403
|
-
"base-classes here",
|
|
404
|
-
VARIANTS[@variant],
|
|
405
|
-
SIZES[@size],
|
|
406
|
-
@class_name
|
|
407
|
-
)
|
|
408
|
-
end
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 6. Test Coverage
|
|
412
|
-
|
|
413
|
-
For each component, create tests in `test/components/`:
|
|
414
|
-
- Test default rendering
|
|
415
|
-
- Test all variants
|
|
416
|
-
- Test all sizes (if applicable)
|
|
417
|
-
- Test slot rendering
|
|
418
|
-
- Test accessibility attributes
|
|
419
|
-
|
|
420
|
-
```ruby
|
|
421
|
-
class ButtonComponentTest < ViewComponent::TestCase
|
|
422
|
-
def test_renders_button
|
|
423
|
-
render_inline(Shadcn::ButtonComponent.new) { "Click" }
|
|
424
|
-
assert_selector "button", text: "Click"
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
def test_renders_variants
|
|
428
|
-
Shadcn::ButtonComponent::VARIANTS.keys.each do |variant|
|
|
429
|
-
render_inline(Shadcn::ButtonComponent.new(variant: variant)) { "Test" }
|
|
430
|
-
assert_selector "button"
|
|
431
|
-
end
|
|
432
|
-
end
|
|
433
|
-
end
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
## Adding New Components
|
|
437
|
-
|
|
438
|
-
1. Create component file in `app/components/shadcn/{name}_component.rb`
|
|
439
|
-
2. Inherit from `Shadcn::BaseComponent`
|
|
440
|
-
3. If interactive, create Stimulus controller in `app/assets/javascripts/shadcn/controllers/{name}_controller.js`
|
|
441
|
-
4. Add controller export to `app/assets/javascripts/shadcn/index.js`
|
|
442
|
-
5. Create test in `test/components/{name}_component_test.rb`
|
|
443
|
-
6. Create Lookbook preview in `test/components/previews/{name}_component_preview.rb`
|
|
444
|
-
7. Add example to dummy app views
|
|
445
|
-
|
|
446
|
-
## Common Issues & Solutions
|
|
447
|
-
|
|
448
|
-
### ViewComponent Preview Nested Render Issue
|
|
449
|
-
|
|
450
|
-
**Problem**: When using nested `render()` calls inside ViewComponent preview slots, the output is a hash like `{args: {}, block: #<Proc:...>, component: #<...>}` instead of rendered HTML.
|
|
451
|
-
|
|
452
|
-
**Solution**: Use raw HTML strings instead of nested render calls in previews:
|
|
453
|
-
|
|
454
|
-
```ruby
|
|
455
|
-
# BAD - nested render returns hash, not HTML
|
|
456
|
-
def default
|
|
457
|
-
render(Shadcn::DialogComponent.new) do |dialog|
|
|
458
|
-
dialog.with_trigger do
|
|
459
|
-
render(Shadcn::ButtonComponent.new(variant: :outline)) { "Open" } # Returns hash!
|
|
460
|
-
end
|
|
461
|
-
end
|
|
462
|
-
end
|
|
463
|
-
|
|
464
|
-
# GOOD - use raw HTML strings or helper methods
|
|
465
|
-
def default
|
|
466
|
-
render(Shadcn::DialogComponent.new) do |dialog|
|
|
467
|
-
dialog.with_trigger do
|
|
468
|
-
button_html(:outline, "Open") # Returns HTML string
|
|
469
|
-
end
|
|
470
|
-
end
|
|
471
|
-
end
|
|
472
|
-
|
|
473
|
-
private
|
|
474
|
-
|
|
475
|
-
def button_html(variant, text, extra_class = nil)
|
|
476
|
-
# Generate HTML string directly
|
|
477
|
-
%(<button class="#{classes}">#{text}</button>).html_safe
|
|
478
|
-
end
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
### Superclass Mismatch Error
|
|
482
|
-
|
|
483
|
-
**Problem**: Rails throws "superclass mismatch for class X" error for nested component classes (e.g., `DialogContentComponent`).
|
|
484
|
-
|
|
485
|
-
**Cause**: Rails Zeitwerk autoloading gets confused when nested classes are reloaded and the parent class definition has changed.
|
|
486
|
-
|
|
487
|
-
**Solution**: Restart the Rails server to clear cached class definitions. This commonly happens when modifying component files with nested classes.
|
|
488
|
-
|
|
489
|
-
### Tailwind Class Conflicts
|
|
490
|
-
|
|
491
|
-
The `ClassMerger` utility handles conflicts like `p-2 p-4` (p-4 wins). If you encounter unresolved conflicts, add the pattern to `CLASS_GROUPS` or `CONFLICT_PATTERNS` in `lib/shadcn/rails/class_merger.rb`.
|
|
492
|
-
|
|
493
|
-
**Pattern Ordering Issue**: When adding regex patterns to `CONFLICT_PATTERNS`, be careful of pattern matching order. The `find_conflict_group` method returns on the first match, so patterns must use negative lookaheads to exclude overlapping cases.
|
|
494
|
-
|
|
495
|
-
Example fix for border classes:
|
|
496
|
-
```ruby
|
|
497
|
-
# PROBLEM: border-0 was matching border_color before border_width
|
|
498
|
-
/^border-(?!solid|dashed|dotted|double|none)/ => :border_color,
|
|
499
|
-
|
|
500
|
-
# SOLUTION: Add [0-9] to the negative lookahead to exclude border widths
|
|
501
|
-
/^border-(?!solid|dashed|dotted|double|none|[0-9])/ => :border_color,
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
This ensures `border-0`, `border-2`, etc. are categorized as `:border_width` (not `:border_color`) so they properly conflict with and override `border`.
|
|
505
|
-
|
|
506
|
-
### Stimulus Controller Not Loading
|
|
507
|
-
|
|
508
|
-
Ensure:
|
|
509
|
-
1. Controller is exported in `index.js`
|
|
510
|
-
2. Controller name matches: `shadcn--{name}`
|
|
511
|
-
3. User has called `registerShadcnControllers(application)` or manually registered
|
|
512
|
-
|
|
513
|
-
### Preview Layout Requirements
|
|
514
|
-
|
|
515
|
-
The `component_preview.html.erb` layout must include:
|
|
516
|
-
1. **Tailwind CSS** via CDN with custom shadcn color tokens
|
|
517
|
-
2. **Stimulus application** initialized and connected
|
|
518
|
-
3. **Inline Stimulus controllers** for all interactive components (Dialog, Sheet, Tabs, Accordion, Checkbox, Switch, Popover, Tooltip)
|
|
519
|
-
4. **CSS animations** for transitions (dialog fade-in, sheet slide-in, etc.)
|
|
520
|
-
|
|
521
|
-
For previews, Stimulus controllers are inlined directly in the layout since the JavaScript bundling isn't available. This is different from production where controllers come from the npm package.
|
|
522
|
-
|
|
523
|
-
### Tabs Controller Method Names
|
|
524
|
-
|
|
525
|
-
The Tabs Stimulus controller uses `selectTab` for the click action, not `select`:
|
|
526
|
-
|
|
527
|
-
```javascript
|
|
528
|
-
// In tabs_controller.js
|
|
529
|
-
selectTab(event) {
|
|
530
|
-
const trigger = event.currentTarget
|
|
531
|
-
const value = trigger.dataset.value
|
|
532
|
-
this.valueValue = value
|
|
533
|
-
}
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
Ensure your triggers use: `data-action="click->shadcn--tabs#selectTab"`
|
|
537
|
-
|
|
538
|
-
### Dark Mode Not Working
|
|
539
|
-
|
|
540
|
-
Check:
|
|
541
|
-
1. CSS variables are loaded
|
|
542
|
-
2. `.dark` class is on `<html>` element (not `<body>`)
|
|
543
|
-
3. Tailwind dark mode is configured as `class` strategy
|
|
544
|
-
|
|
545
|
-
## Documentation Site
|
|
546
|
-
|
|
547
|
-
The `test/dummy` app serves as both a testing environment and documentation site. It includes:
|
|
548
|
-
- Full documentation for all 47 components at `/docs`
|
|
549
|
-
- Live interactive examples that demonstrate each component
|
|
550
|
-
- Code examples stored in `app/code_examples/{component}/` as `.txt` files
|
|
551
|
-
- `erb_example` helper to display code examples with syntax highlighting
|
|
552
|
-
- Showcase page at `/showcase`
|
|
553
|
-
|
|
554
|
-
### Documentation Structure
|
|
555
|
-
|
|
556
|
-
```
|
|
557
|
-
test/dummy/
|
|
558
|
-
├── app/
|
|
559
|
-
│ ├── views/docs/ # Component documentation pages
|
|
560
|
-
│ │ ├── button.html.erb
|
|
561
|
-
│ │ ├── dialog.html.erb
|
|
562
|
-
│ │ └── ...
|
|
563
|
-
│ ├── code_examples/ # Code examples displayed in docs
|
|
564
|
-
│ │ ├── button/
|
|
565
|
-
│ │ │ └── default.txt
|
|
566
|
-
│ │ └── pagination/
|
|
567
|
-
│ │ ├── kaminari_view.txt
|
|
568
|
-
│ │ └── pagy_view.txt
|
|
569
|
-
│ └── helpers/
|
|
570
|
-
│ └── docs_helper.rb # erb_example helper method
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
## Future Improvements
|
|
574
|
-
|
|
575
|
-
**Missing Components** (from shadcn/ui):
|
|
576
|
-
- [ ] Command palette
|
|
577
|
-
- [ ] Combobox
|
|
578
|
-
- [ ] Date Picker
|
|
579
|
-
- [ ] Calendar
|
|
580
|
-
- [ ] Sonner (toast alternative)
|
|
581
|
-
- [ ] Carousel
|
|
582
|
-
- [ ] Chart
|
|
583
|
-
- [ ] Data Table
|
|
584
|
-
- [ ] Sidebar
|
|
585
|
-
|
|
586
|
-
**Enhancements**:
|
|
587
|
-
- [ ] Form builder integration (Rails form helpers)
|
|
588
|
-
- [ ] Hotwire/Turbo Stream integration helpers
|
|
589
|
-
- [ ] RTL support
|
|
590
|
-
- [ ] Visual regression tests with Percy or similar
|
|
591
|
-
- [ ] Dropdown Menu submenus and checkbox/radio items
|
|
592
|
-
- [ ] Drawer gesture support (touch drag to dismiss)
|
|
593
|
-
|
|
594
|
-
**Completed**:
|
|
595
|
-
- [x] 47 core components implemented
|
|
596
|
-
- [x] Full documentation site with live examples
|
|
597
|
-
- [x] Stimulus controllers for all interactive components
|
|
598
|
-
- [x] Pagination with Kaminari/Pagy/WillPaginate adapters
|
|
599
|
-
- [x] CSS animations for dialogs, sheets, toasts, accordions
|
|
600
|
-
- [x] Context Menu component
|
|
601
|
-
- [x] Menubar component
|
|
602
|
-
- [x] Navigation Menu component
|
|
603
|
-
- [x] Resizable panels component
|
|
604
|
-
- [x] Input OTP with group separators
|
|
605
|
-
|
|
606
|
-
## Resources
|
|
607
|
-
|
|
608
|
-
- [shadcn/ui Documentation](https://ui.shadcn.com)
|
|
609
|
-
- [ViewComponent Guide](https://viewcomponent.org)
|
|
610
|
-
- [Stimulus Handbook](https://stimulus.hotwired.dev/handbook/introduction)
|
|
611
|
-
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
612
|
-
- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/)
|