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
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { Application } from "@hotwired/stimulus"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Helper to set up Stimulus controller tests
|
|
5
|
-
* Creates a DOM element with the controller connected
|
|
6
|
-
*/
|
|
7
|
-
export function setupController(Controller, html, controllerName = 'test') {
|
|
8
|
-
const application = Application.start()
|
|
9
|
-
application.register(controllerName, Controller)
|
|
10
|
-
|
|
11
|
-
document.body.innerHTML = html
|
|
12
|
-
|
|
13
|
-
// Wait for Stimulus to connect the controller
|
|
14
|
-
return new Promise((resolve) => {
|
|
15
|
-
requestAnimationFrame(() => {
|
|
16
|
-
const element = document.querySelector(`[data-controller="${controllerName}"]`)
|
|
17
|
-
const controller = application.getControllerForElementAndIdentifier(element, controllerName)
|
|
18
|
-
resolve({ application, element, controller })
|
|
19
|
-
})
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Clean up after tests
|
|
25
|
-
*/
|
|
26
|
-
export function cleanupController(application) {
|
|
27
|
-
if (application) {
|
|
28
|
-
application.stop()
|
|
29
|
-
}
|
|
30
|
-
document.body.innerHTML = ''
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Simulate a click event on an element
|
|
35
|
-
*/
|
|
36
|
-
export function click(element) {
|
|
37
|
-
element.dispatchEvent(new MouseEvent('click', {
|
|
38
|
-
bubbles: true,
|
|
39
|
-
cancelable: true,
|
|
40
|
-
view: window
|
|
41
|
-
}))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Wait for a specified number of milliseconds
|
|
46
|
-
*/
|
|
47
|
-
export function wait(ms) {
|
|
48
|
-
return new Promise(resolve => setTimeout(resolve, ms))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Wait for an animation frame
|
|
53
|
-
*/
|
|
54
|
-
export function nextFrame() {
|
|
55
|
-
return new Promise(resolve => requestAnimationFrame(resolve))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Simulate a keyboard event on an element
|
|
60
|
-
* @param {Element} element - Target element
|
|
61
|
-
* @param {string} key - Key name (e.g., 'ArrowDown', 'Enter', 'Escape')
|
|
62
|
-
* @param {Object} options - Additional options (shiftKey, ctrlKey, etc.)
|
|
63
|
-
*/
|
|
64
|
-
export function keydown(element, key, options = {}) {
|
|
65
|
-
element.dispatchEvent(new KeyboardEvent('keydown', {
|
|
66
|
-
key,
|
|
67
|
-
bubbles: true,
|
|
68
|
-
cancelable: true,
|
|
69
|
-
...options
|
|
70
|
-
}))
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Simulate a keyup event
|
|
75
|
-
*/
|
|
76
|
-
export function keyup(element, key, options = {}) {
|
|
77
|
-
element.dispatchEvent(new KeyboardEvent('keyup', {
|
|
78
|
-
key,
|
|
79
|
-
bubbles: true,
|
|
80
|
-
cancelable: true,
|
|
81
|
-
...options
|
|
82
|
-
}))
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Wait for a portal element to appear in the DOM
|
|
87
|
-
* @param {string} selector - CSS selector for the portal
|
|
88
|
-
* @param {boolean} shouldExist - Whether portal should exist (true) or not exist (false)
|
|
89
|
-
* @param {number} timeout - Maximum wait time in ms
|
|
90
|
-
*/
|
|
91
|
-
export async function waitForPortal(selector, shouldExist = true, timeout = 1000) {
|
|
92
|
-
const startTime = Date.now()
|
|
93
|
-
|
|
94
|
-
while (Date.now() - startTime < timeout) {
|
|
95
|
-
const element = document.querySelector(selector)
|
|
96
|
-
if (shouldExist && element) return element
|
|
97
|
-
if (!shouldExist && !element) return null
|
|
98
|
-
await wait(10)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
throw new Error(`Portal ${selector} ${shouldExist ? 'did not appear' : 'did not disappear'} within ${timeout}ms`)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Mock window.location for URL-related tests
|
|
106
|
-
* Uses jsdom's internal reconfigure or modifies location properties
|
|
107
|
-
* @param {string} url - The URL to mock
|
|
108
|
-
* @returns {Function} Cleanup function to restore original location
|
|
109
|
-
*/
|
|
110
|
-
export function mockLocation(url) {
|
|
111
|
-
// Store original href
|
|
112
|
-
const originalHref = window.location.href
|
|
113
|
-
|
|
114
|
-
// Use jsdom's reconfigure if available (available in newer jsdom)
|
|
115
|
-
if (typeof window._virtualConsole !== 'undefined' && window.location._setHref) {
|
|
116
|
-
window.location._setHref(url)
|
|
117
|
-
return () => {
|
|
118
|
-
if (window.location._setHref) {
|
|
119
|
-
window.location._setHref(originalHref)
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Fallback: Use history API to change URL without navigation
|
|
125
|
-
const urlObj = new URL(url, window.location.origin)
|
|
126
|
-
window.history.replaceState({}, '', urlObj.href)
|
|
127
|
-
|
|
128
|
-
return () => {
|
|
129
|
-
window.history.replaceState({}, '', originalHref)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Mock history.pushState and replaceState for URL sync tests
|
|
135
|
-
*/
|
|
136
|
-
export function mockHistory() {
|
|
137
|
-
const originalPushState = window.history.pushState
|
|
138
|
-
const originalReplaceState = window.history.replaceState
|
|
139
|
-
|
|
140
|
-
const calls = {
|
|
141
|
-
pushState: [],
|
|
142
|
-
replaceState: []
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
window.history.pushState = (state, title, url) => {
|
|
146
|
-
calls.pushState.push({ state, title, url })
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
window.history.replaceState = (state, title, url) => {
|
|
150
|
-
calls.replaceState.push({ state, title, url })
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
calls,
|
|
155
|
-
restore: () => {
|
|
156
|
-
window.history.pushState = originalPushState
|
|
157
|
-
window.history.replaceState = originalReplaceState
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get all focusable elements within a container
|
|
164
|
-
* @param {Element} container - Container element
|
|
165
|
-
*/
|
|
166
|
-
export function getFocusableElements(container) {
|
|
167
|
-
return container.querySelectorAll(
|
|
168
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Dispatch a custom event
|
|
174
|
-
* @param {Element} element - Target element
|
|
175
|
-
* @param {string} eventName - Event name
|
|
176
|
-
* @param {Object} detail - Event detail object
|
|
177
|
-
*/
|
|
178
|
-
export function dispatchEvent(element, eventName, detail = {}) {
|
|
179
|
-
element.dispatchEvent(new CustomEvent(eventName, {
|
|
180
|
-
bubbles: true,
|
|
181
|
-
cancelable: true,
|
|
182
|
-
detail
|
|
183
|
-
}))
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Wait for controller to emit a specific event
|
|
188
|
-
* @param {Element} element - Element to listen on
|
|
189
|
-
* @param {string} eventName - Event name (e.g., 'shadcn--accordion:expand')
|
|
190
|
-
* @param {number} timeout - Maximum wait time
|
|
191
|
-
*/
|
|
192
|
-
export function waitForEvent(element, eventName, timeout = 1000) {
|
|
193
|
-
return new Promise((resolve, reject) => {
|
|
194
|
-
const timer = setTimeout(() => {
|
|
195
|
-
reject(new Error(`Event ${eventName} not received within ${timeout}ms`))
|
|
196
|
-
}, timeout)
|
|
197
|
-
|
|
198
|
-
element.addEventListener(eventName, (event) => {
|
|
199
|
-
clearTimeout(timer)
|
|
200
|
-
resolve(event)
|
|
201
|
-
}, { once: true })
|
|
202
|
-
})
|
|
203
|
-
}
|
data/babel.config.cjs
DELETED
data/bin/console
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "bundler/setup"
|
|
5
|
-
require "shadcn/rails"
|
|
6
|
-
|
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
-
|
|
10
|
-
require "irb"
|
|
11
|
-
IRB.start(__FILE__)
|
data/bin/setup
DELETED
data/jest.config.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/** @type {import('jest').Config} */
|
|
2
|
-
export default {
|
|
3
|
-
testEnvironment: 'jsdom',
|
|
4
|
-
testMatch: ['**/__tests__/**/*.test.js'],
|
|
5
|
-
moduleFileExtensions: ['js', 'json'],
|
|
6
|
-
transform: {
|
|
7
|
-
'^.+\\.js$': 'babel-jest'
|
|
8
|
-
},
|
|
9
|
-
transformIgnorePatterns: [
|
|
10
|
-
'/node_modules/(?!@hotwired/stimulus)'
|
|
11
|
-
],
|
|
12
|
-
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
13
|
-
collectCoverageFrom: [
|
|
14
|
-
'app/assets/javascripts/shadcn/controllers/**/*.js',
|
|
15
|
-
'!**/node_modules/**'
|
|
16
|
-
],
|
|
17
|
-
coverageDirectory: 'coverage',
|
|
18
|
-
verbose: true
|
|
19
|
-
}
|
data/jest.setup.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/base"
|
|
5
|
-
|
|
6
|
-
module Shadcn
|
|
7
|
-
module Generators
|
|
8
|
-
# Generator for adding individual shadcn components
|
|
9
|
-
# Usage: rails generate shadcn:component button card dialog
|
|
10
|
-
class ComponentGenerator < Rails::Generators::Base
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
argument :components, type: :array, default: [], banner: "component [component ...]"
|
|
14
|
-
|
|
15
|
-
class_option :all, type: :boolean, default: false,
|
|
16
|
-
desc: "Install all available components"
|
|
17
|
-
class_option :list, type: :boolean, default: false,
|
|
18
|
-
desc: "List all available components"
|
|
19
|
-
|
|
20
|
-
desc "Adds shadcn-rails components to your application"
|
|
21
|
-
|
|
22
|
-
# List of all available components
|
|
23
|
-
AVAILABLE_COMPONENTS = %w[
|
|
24
|
-
accordion
|
|
25
|
-
alert
|
|
26
|
-
avatar
|
|
27
|
-
badge
|
|
28
|
-
button
|
|
29
|
-
card
|
|
30
|
-
checkbox
|
|
31
|
-
collapsible
|
|
32
|
-
dialog
|
|
33
|
-
dropdown_menu
|
|
34
|
-
input
|
|
35
|
-
label
|
|
36
|
-
popover
|
|
37
|
-
progress
|
|
38
|
-
scroll_area
|
|
39
|
-
select
|
|
40
|
-
separator
|
|
41
|
-
sheet
|
|
42
|
-
skeleton
|
|
43
|
-
switch
|
|
44
|
-
table
|
|
45
|
-
tabs
|
|
46
|
-
textarea
|
|
47
|
-
toast
|
|
48
|
-
tooltip
|
|
49
|
-
].freeze
|
|
50
|
-
|
|
51
|
-
def validate_components
|
|
52
|
-
if options[:list]
|
|
53
|
-
display_available_components
|
|
54
|
-
exit 0
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
if options[:all]
|
|
58
|
-
@components_to_install = AVAILABLE_COMPONENTS
|
|
59
|
-
else
|
|
60
|
-
validate_requested_components
|
|
61
|
-
@components_to_install = components
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def display_installation_plan
|
|
66
|
-
say ""
|
|
67
|
-
say "Installing #{@components_to_install.length} component(s):", :green
|
|
68
|
-
@components_to_install.each { |c| say " - #{c}" }
|
|
69
|
-
say ""
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def install_components
|
|
73
|
-
@components_to_install.each do |component|
|
|
74
|
-
install_component(component)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def display_usage_examples
|
|
79
|
-
say ""
|
|
80
|
-
say "=" * 60, :green
|
|
81
|
-
say " Components installed successfully!", :green
|
|
82
|
-
say "=" * 60, :green
|
|
83
|
-
say ""
|
|
84
|
-
say "Example usage:", :yellow
|
|
85
|
-
|
|
86
|
-
@components_to_install.first(3).each do |component|
|
|
87
|
-
display_component_example(component)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
say ""
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def display_available_components
|
|
96
|
-
say ""
|
|
97
|
-
say "Available shadcn-rails components:", :green
|
|
98
|
-
say ""
|
|
99
|
-
AVAILABLE_COMPONENTS.each { |c| say " - #{c}" }
|
|
100
|
-
say ""
|
|
101
|
-
say "Usage: rails generate shadcn:component button card dialog"
|
|
102
|
-
say " rails generate shadcn:component --all"
|
|
103
|
-
say ""
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def validate_requested_components
|
|
107
|
-
if components.empty?
|
|
108
|
-
say "Error: Please specify at least one component or use --all", :red
|
|
109
|
-
say ""
|
|
110
|
-
display_available_components
|
|
111
|
-
exit 1
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
invalid = components - AVAILABLE_COMPONENTS
|
|
115
|
-
if invalid.any?
|
|
116
|
-
say "Error: Unknown component(s): #{invalid.join(', ')}", :red
|
|
117
|
-
say ""
|
|
118
|
-
display_available_components
|
|
119
|
-
exit 1
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def install_component(name)
|
|
124
|
-
# Components are already available from the gem
|
|
125
|
-
# This generator just confirms installation and provides usage info
|
|
126
|
-
say " ✓ #{name.titleize}Component is available", :green
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def display_component_example(component)
|
|
130
|
-
case component
|
|
131
|
-
when "button"
|
|
132
|
-
say <<~EXAMPLE
|
|
133
|
-
|
|
134
|
-
Button:
|
|
135
|
-
<%= render Shadcn::ButtonComponent.new(variant: :primary) do %>
|
|
136
|
-
Click me
|
|
137
|
-
<% end %>
|
|
138
|
-
EXAMPLE
|
|
139
|
-
when "card"
|
|
140
|
-
say <<~EXAMPLE
|
|
141
|
-
|
|
142
|
-
Card:
|
|
143
|
-
<%= render Shadcn::CardComponent.new do |card| %>
|
|
144
|
-
<% card.with_header do %>
|
|
145
|
-
<% card.with_title { "Card Title" } %>
|
|
146
|
-
<% end %>
|
|
147
|
-
<% card.with_content { "Card content" } %>
|
|
148
|
-
<% end %>
|
|
149
|
-
EXAMPLE
|
|
150
|
-
when "dialog"
|
|
151
|
-
say <<~EXAMPLE
|
|
152
|
-
|
|
153
|
-
Dialog:
|
|
154
|
-
<%= render Shadcn::DialogComponent.new do |dialog| %>
|
|
155
|
-
<% dialog.with_trigger do %>
|
|
156
|
-
<%= render Shadcn::ButtonComponent.new { "Open" } %>
|
|
157
|
-
<% end %>
|
|
158
|
-
<% dialog.with_content do |content| %>
|
|
159
|
-
<% content.with_title { "Dialog Title" } %>
|
|
160
|
-
Dialog content here
|
|
161
|
-
<% end %>
|
|
162
|
-
<% end %>
|
|
163
|
-
EXAMPLE
|
|
164
|
-
when "input"
|
|
165
|
-
say <<~EXAMPLE
|
|
166
|
-
|
|
167
|
-
Input:
|
|
168
|
-
<%= render Shadcn::LabelComponent.new(for: "email") { "Email" } %>
|
|
169
|
-
<%= render Shadcn::InputComponent.new(
|
|
170
|
-
type: "email",
|
|
171
|
-
id: "email",
|
|
172
|
-
name: "email",
|
|
173
|
-
placeholder: "Enter your email"
|
|
174
|
-
) %>
|
|
175
|
-
EXAMPLE
|
|
176
|
-
else
|
|
177
|
-
say <<~EXAMPLE
|
|
178
|
-
|
|
179
|
-
#{component.titleize}:
|
|
180
|
-
<%= render Shadcn::#{component.camelize}Component.new do %>
|
|
181
|
-
Content here
|
|
182
|
-
<% end %>
|
|
183
|
-
EXAMPLE
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
end
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "rails/generators"
|
|
4
|
-
require "rails/generators/base"
|
|
5
|
-
|
|
6
|
-
module Shadcn
|
|
7
|
-
module Generators
|
|
8
|
-
# Generator for switching or customizing themes
|
|
9
|
-
# Usage: rails generate shadcn:theme slate
|
|
10
|
-
class ThemeGenerator < Rails::Generators::Base
|
|
11
|
-
source_root File.expand_path("templates", __dir__)
|
|
12
|
-
|
|
13
|
-
argument :theme_name, type: :string, default: "neutral",
|
|
14
|
-
banner: "theme_name"
|
|
15
|
-
|
|
16
|
-
class_option :list, type: :boolean, default: false,
|
|
17
|
-
desc: "List all available themes"
|
|
18
|
-
|
|
19
|
-
desc "Changes the shadcn-rails theme or creates a custom theme"
|
|
20
|
-
|
|
21
|
-
AVAILABLE_THEMES = %w[neutral slate stone zinc gray].freeze
|
|
22
|
-
|
|
23
|
-
def validate_theme
|
|
24
|
-
if options[:list]
|
|
25
|
-
display_available_themes
|
|
26
|
-
exit 0
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
unless AVAILABLE_THEMES.include?(theme_name)
|
|
30
|
-
say "Error: Unknown theme '#{theme_name}'", :red
|
|
31
|
-
display_available_themes
|
|
32
|
-
exit 1
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def update_initializer
|
|
37
|
-
initializer_path = "config/initializers/shadcn.rb"
|
|
38
|
-
|
|
39
|
-
if File.exist?(initializer_path)
|
|
40
|
-
gsub_file initializer_path,
|
|
41
|
-
/config\.base_color = ["']?\w+["']?/,
|
|
42
|
-
"config.base_color = \"#{theme_name}\""
|
|
43
|
-
say "Updated initializer to use '#{theme_name}' theme", :green
|
|
44
|
-
else
|
|
45
|
-
say "Initializer not found. Run 'rails generate shadcn:install' first.", :yellow
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def update_config_file
|
|
50
|
-
config_path = "config/shadcn.yml"
|
|
51
|
-
|
|
52
|
-
if File.exist?(config_path)
|
|
53
|
-
gsub_file config_path,
|
|
54
|
-
/base_color: \w+/,
|
|
55
|
-
"base_color: #{theme_name}"
|
|
56
|
-
say "Updated shadcn.yml to use '#{theme_name}' theme", :green
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def update_stylesheet
|
|
61
|
-
# Check for theme import in stylesheet
|
|
62
|
-
stylesheet_path = "app/assets/stylesheets/application.tailwind.css"
|
|
63
|
-
|
|
64
|
-
if File.exist?(stylesheet_path)
|
|
65
|
-
content = File.read(stylesheet_path)
|
|
66
|
-
|
|
67
|
-
# Remove existing theme import
|
|
68
|
-
AVAILABLE_THEMES.each do |theme|
|
|
69
|
-
next if theme == "neutral" # neutral uses base.css
|
|
70
|
-
gsub_file stylesheet_path, /@import ["']shadcn\/themes\/#{theme}["'];\n?/, ""
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Add new theme import if not neutral
|
|
74
|
-
unless theme_name == "neutral"
|
|
75
|
-
inject_into_file stylesheet_path, after: '@import "shadcn/base";' do
|
|
76
|
-
"\n@import \"shadcn/themes/#{theme_name}\";"
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
say "Updated stylesheet with '#{theme_name}' theme", :green
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def display_completion_message
|
|
85
|
-
say ""
|
|
86
|
-
say "Theme changed to '#{theme_name}'!", :green
|
|
87
|
-
say ""
|
|
88
|
-
say "The following colors are now active:", :yellow
|
|
89
|
-
display_theme_preview(theme_name)
|
|
90
|
-
say ""
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
private
|
|
94
|
-
|
|
95
|
-
def display_available_themes
|
|
96
|
-
say ""
|
|
97
|
-
say "Available themes:", :green
|
|
98
|
-
say ""
|
|
99
|
-
AVAILABLE_THEMES.each do |theme|
|
|
100
|
-
say " - #{theme}"
|
|
101
|
-
end
|
|
102
|
-
say ""
|
|
103
|
-
say "Usage: rails generate shadcn:theme slate"
|
|
104
|
-
say ""
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def display_theme_preview(theme)
|
|
108
|
-
case theme
|
|
109
|
-
when "neutral"
|
|
110
|
-
say " Primary: Pure black/white"
|
|
111
|
-
say " Style: Clean, minimal"
|
|
112
|
-
when "slate"
|
|
113
|
-
say " Primary: Cool blue-gray"
|
|
114
|
-
say " Style: Professional, corporate"
|
|
115
|
-
when "stone"
|
|
116
|
-
say " Primary: Warm gray-brown"
|
|
117
|
-
say " Style: Earthy, natural"
|
|
118
|
-
when "zinc"
|
|
119
|
-
say " Primary: Cool gray"
|
|
120
|
-
say " Style: Modern, sleek"
|
|
121
|
-
when "gray"
|
|
122
|
-
say " Primary: True gray"
|
|
123
|
-
say " Style: Neutral, balanced"
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|