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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -2
  3. data/README.md +21 -8
  4. data/__mocks__/@floating-ui/dom.js +67 -0
  5. data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +23 -2
  6. data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +4 -31
  7. data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +32 -41
  8. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
  9. data/app/assets/javascripts/shadcn/controllers/popover_controller.js +29 -54
  10. data/app/assets/javascripts/shadcn/controllers/select_controller.js +26 -8
  11. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
  12. data/app/assets/javascripts/shadcn/index.js +7 -1
  13. data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
  14. data/app/assets/stylesheets/shadcn/base.css +32 -0
  15. data/app/components/shadcn/accordion_component.html.erb +8 -0
  16. data/app/components/shadcn/accordion_component.rb +6 -15
  17. data/app/components/shadcn/alert_component.html.erb +6 -0
  18. data/app/components/shadcn/alert_component.rb +0 -18
  19. data/app/components/shadcn/alert_dialog_component.html.erb +12 -0
  20. data/app/components/shadcn/alert_dialog_component.rb +7 -27
  21. data/app/components/shadcn/aspect_ratio_component.html.erb +7 -0
  22. data/app/components/shadcn/aspect_ratio_component.rb +4 -19
  23. data/app/components/shadcn/avatar_component.html.erb +20 -0
  24. data/app/components/shadcn/avatar_component.rb +8 -36
  25. data/app/components/shadcn/badge_component.html.erb +1 -0
  26. data/app/components/shadcn/badge_component.rb +0 -11
  27. data/app/components/shadcn/base_component.rb +15 -2
  28. data/app/components/shadcn/breadcrumb_component.html.erb +5 -0
  29. data/app/components/shadcn/breadcrumb_component.rb +6 -16
  30. data/app/components/shadcn/button_component.html.erb +18 -0
  31. data/app/components/shadcn/button_component.rb +1 -41
  32. data/app/components/shadcn/card_component.html.erb +8 -0
  33. data/app/components/shadcn/card_component.rb +2 -6
  34. data/app/components/shadcn/checkbox_component.html.erb +32 -0
  35. data/app/components/shadcn/checkbox_component.rb +4 -43
  36. data/app/components/shadcn/collapsible_component.html.erb +8 -0
  37. data/app/components/shadcn/collapsible_component.rb +6 -15
  38. data/app/components/shadcn/context_menu_component.html.erb +11 -0
  39. data/app/components/shadcn/context_menu_component.rb +6 -26
  40. data/app/components/shadcn/dialog_component.html.erb +14 -0
  41. data/app/components/shadcn/dialog_component.rb +8 -29
  42. data/app/components/shadcn/drawer_component.html.erb +12 -0
  43. data/app/components/shadcn/drawer_component.rb +7 -27
  44. data/app/components/shadcn/dropdown_menu_component.html.erb +14 -0
  45. data/app/components/shadcn/dropdown_menu_component.rb +9 -29
  46. data/app/components/shadcn/field_component.rb +7 -8
  47. data/app/components/shadcn/hover_card_component.html.erb +12 -0
  48. data/app/components/shadcn/hover_card_component.rb +7 -26
  49. data/app/components/shadcn/input_component.html.erb +18 -0
  50. data/app/components/shadcn/input_component.rb +2 -27
  51. data/app/components/shadcn/input_otp_component.rb +3 -3
  52. data/app/components/shadcn/kbd_component.html.erb +1 -0
  53. data/app/components/shadcn/kbd_component.rb +3 -10
  54. data/app/components/shadcn/label_component.html.erb +3 -0
  55. data/app/components/shadcn/label_component.rb +2 -18
  56. data/app/components/shadcn/menubar_component.html.erb +6 -0
  57. data/app/components/shadcn/menubar_component.rb +4 -15
  58. data/app/components/shadcn/native_select_component.html.erb +22 -0
  59. data/app/components/shadcn/native_select_component.rb +9 -39
  60. data/app/components/shadcn/navigation_menu_component.html.erb +6 -0
  61. data/app/components/shadcn/navigation_menu_component.rb +4 -15
  62. data/app/components/shadcn/pagination_component.html.erb +5 -0
  63. data/app/components/shadcn/pagination_component.rb +11 -15
  64. data/app/components/shadcn/popover_component.html.erb +15 -0
  65. data/app/components/shadcn/popover_component.rb +10 -30
  66. data/app/components/shadcn/progress_component.html.erb +13 -0
  67. data/app/components/shadcn/progress_component.rb +6 -26
  68. data/app/components/shadcn/radio_group_component.html.erb +8 -0
  69. data/app/components/shadcn/radio_group_component.rb +12 -26
  70. data/app/components/shadcn/scroll_area_component.html.erb +7 -0
  71. data/app/components/shadcn/scroll_area_component.rb +4 -16
  72. data/app/components/shadcn/select_component.html.erb +46 -0
  73. data/app/components/shadcn/select_component.rb +6 -80
  74. data/app/components/shadcn/separator_component.html.erb +5 -0
  75. data/app/components/shadcn/separator_component.rb +6 -14
  76. data/app/components/shadcn/sheet_component.html.erb +12 -0
  77. data/app/components/shadcn/sheet_component.rb +7 -27
  78. data/app/components/shadcn/sidebar_component.rb +2 -2
  79. data/app/components/shadcn/skeleton_component.html.erb +1 -0
  80. data/app/components/shadcn/skeleton_component.rb +4 -2
  81. data/app/components/shadcn/slider_component.html.erb +12 -0
  82. data/app/components/shadcn/slider_component.rb +2 -21
  83. data/app/components/shadcn/spinner_component.html.erb +18 -0
  84. data/app/components/shadcn/spinner_component.rb +2 -30
  85. data/app/components/shadcn/switch_component.html.erb +72 -0
  86. data/app/components/shadcn/switch_component.rb +4 -82
  87. data/app/components/shadcn/table_component.html.erb +9 -0
  88. data/app/components/shadcn/table_component.rb +2 -10
  89. data/app/components/shadcn/tabs_component.html.erb +8 -0
  90. data/app/components/shadcn/tabs_component.rb +4 -17
  91. data/app/components/shadcn/textarea_component.html.erb +13 -0
  92. data/app/components/shadcn/textarea_component.rb +6 -22
  93. data/app/components/shadcn/toast_component.html.erb +36 -0
  94. data/app/components/shadcn/toast_component.rb +6 -54
  95. data/app/components/shadcn/toggle_component.html.erb +12 -0
  96. data/app/components/shadcn/toggle_component.rb +6 -21
  97. data/app/components/shadcn/toggle_group_component.html.erb +14 -0
  98. data/app/components/shadcn/toggle_group_component.rb +6 -29
  99. data/app/components/shadcn/tooltip_component.html.erb +20 -0
  100. data/app/components/shadcn/tooltip_component.rb +13 -38
  101. data/lib/generators/shadcn/add/USAGE +24 -0
  102. data/lib/generators/shadcn/add/add_generator.rb +279 -0
  103. data/lib/generators/shadcn/install/USAGE +22 -0
  104. data/lib/generators/shadcn/install/install_generator.rb +8 -3
  105. data/lib/generators/shadcn/install/templates/initializer.rb.tt +7 -27
  106. data/lib/generators/shadcn/install/templates/shadcn.yml.tt +15 -31
  107. data/lib/shadcn/rails/version.rb +1 -1
  108. metadata +47 -45
  109. data/.dockerignore +0 -40
  110. data/CLAUDE.md +0 -612
  111. data/PROGRESS.md +0 -495
  112. data/Rakefile +0 -95
  113. data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +0 -13
  114. data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +0 -46
  115. data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +0 -111
  116. data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +0 -27
  117. data/__tests__/controllers/accordion_controller.test.js +0 -904
  118. data/__tests__/controllers/calendar_controller.test.js +0 -1370
  119. data/__tests__/controllers/carousel_controller.test.js +0 -912
  120. data/__tests__/controllers/checkbox_controller.test.js +0 -454
  121. data/__tests__/controllers/collapsible_controller.test.js +0 -407
  122. data/__tests__/controllers/combobox_controller.test.js +0 -971
  123. data/__tests__/controllers/context_menu_controller.test.js +0 -905
  124. data/__tests__/controllers/date_picker_controller.test.js +0 -636
  125. data/__tests__/controllers/dialog_controller.test.js +0 -878
  126. data/__tests__/controllers/drawer_controller.test.js +0 -995
  127. data/__tests__/controllers/menubar_controller.test.js +0 -737
  128. data/__tests__/controllers/navigation_menu_controller.test.js +0 -599
  129. data/__tests__/controllers/popover_controller.test.js +0 -982
  130. data/__tests__/controllers/radio_group_controller.test.js +0 -640
  131. data/__tests__/controllers/resizable_controller.test.js +0 -680
  132. data/__tests__/controllers/select_controller.test.js +0 -678
  133. data/__tests__/controllers/sheet_controller.test.js +0 -986
  134. data/__tests__/controllers/slider_controller.test.js +0 -1036
  135. data/__tests__/controllers/switch_controller.test.js +0 -424
  136. data/__tests__/controllers/tabs_controller.test.js +0 -907
  137. data/__tests__/controllers/toggle_group_controller.test.js +0 -839
  138. data/__tests__/controllers/tooltip_controller.test.js +0 -808
  139. data/__tests__/helpers/stimulus-test-helper.js +0 -203
  140. data/babel.config.cjs +0 -5
  141. data/bin/bump +0 -321
  142. data/bin/console +0 -11
  143. data/bin/release +0 -205
  144. data/bin/setup +0 -8
  145. data/bin/test +0 -75
  146. data/jest.config.js +0 -19
  147. data/jest.setup.js +0 -8
  148. data/lib/generators/shadcn/component/component_generator.rb +0 -188
  149. data/lib/generators/shadcn/theme/theme_generator.rb +0 -128
  150. data/package-lock.json +0 -7438
  151. data/package.json +0 -71
  152. 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/)