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
@@ -1,424 +0,0 @@
1
- import { Application } from "@hotwired/stimulus"
2
- import SwitchController from "../../app/assets/javascripts/shadcn/controllers/switch_controller.js"
3
- import { setupController, cleanupController, click, nextFrame, keydown } from '../helpers/stimulus-test-helper.js'
4
-
5
- describe("SwitchController", () => {
6
- let application
7
- let element
8
- let controller
9
-
10
- afterEach(() => {
11
- cleanupController(application)
12
- })
13
-
14
- describe("basic rendering and initialization", () => {
15
- const basicHTML = `
16
- <div data-controller="shadcn--switch"
17
- data-shadcn--switch-checked-value="false">
18
- <button data-shadcn--switch-target="button"
19
- type="button"
20
- role="switch"
21
- aria-checked="false"
22
- data-action="click->shadcn--switch#toggle keydown->shadcn--switch#handleKeydown">
23
- <span data-shadcn--switch-target="thumb"></span>
24
- </button>
25
- <input type="checkbox"
26
- data-shadcn--switch-target="input"
27
- name="notifications"
28
- hidden>
29
- </div>
30
- `
31
-
32
- beforeEach(async () => {
33
- const setup = await setupController(SwitchController, basicHTML, 'shadcn--switch')
34
- application = setup.application
35
- element = setup.element
36
- controller = setup.controller
37
- })
38
-
39
- test("initializes with unchecked state", () => {
40
- expect(controller.checkedValue).toBe(false)
41
- })
42
-
43
- test("sets data-state on element", () => {
44
- expect(element.dataset.state).toBe("unchecked")
45
- })
46
-
47
- test("sets data-state on button", () => {
48
- expect(controller.buttonTarget.dataset.state).toBe("unchecked")
49
- })
50
-
51
- test("sets aria-checked on button", () => {
52
- expect(controller.buttonTarget.getAttribute("aria-checked")).toBe("false")
53
- })
54
-
55
- test("sets data-state on thumb", () => {
56
- expect(controller.thumbTarget.dataset.state).toBe("unchecked")
57
- })
58
- })
59
-
60
- describe("toggle functionality", () => {
61
- const toggleHTML = `
62
- <div data-controller="shadcn--switch"
63
- data-shadcn--switch-checked-value="false">
64
- <button data-shadcn--switch-target="button"
65
- type="button"
66
- role="switch"
67
- data-action="click->shadcn--switch#toggle">
68
- <span data-shadcn--switch-target="thumb"></span>
69
- </button>
70
- <input type="checkbox"
71
- data-shadcn--switch-target="input"
72
- name="enabled"
73
- hidden>
74
- </div>
75
- `
76
-
77
- beforeEach(async () => {
78
- const setup = await setupController(SwitchController, toggleHTML, 'shadcn--switch')
79
- application = setup.application
80
- element = setup.element
81
- controller = setup.controller
82
- })
83
-
84
- test("toggles from unchecked to checked", async () => {
85
- controller.toggle()
86
- await nextFrame()
87
-
88
- expect(controller.checkedValue).toBe(true)
89
- })
90
-
91
- test("toggles from checked to unchecked", async () => {
92
- controller.checkedValue = true
93
- controller.toggle()
94
- await nextFrame()
95
-
96
- expect(controller.checkedValue).toBe(false)
97
- })
98
-
99
- test("updates data-state on toggle to checked", async () => {
100
- controller.toggle()
101
- await nextFrame()
102
-
103
- expect(element.dataset.state).toBe("checked")
104
- expect(controller.buttonTarget.dataset.state).toBe("checked")
105
- expect(controller.thumbTarget.dataset.state).toBe("checked")
106
- })
107
-
108
- test("updates aria-checked on toggle", async () => {
109
- controller.toggle()
110
- await nextFrame()
111
-
112
- expect(controller.buttonTarget.getAttribute("aria-checked")).toBe("true")
113
- })
114
-
115
- test("dispatches change event on toggle", async () => {
116
- let eventDetail = null
117
- element.addEventListener("shadcn--switch:change", (e) => {
118
- eventDetail = e.detail
119
- })
120
-
121
- controller.toggle()
122
- await nextFrame()
123
-
124
- expect(eventDetail).not.toBeNull()
125
- expect(eventDetail.checked).toBe(true)
126
- })
127
-
128
- test("dispatches change event with false when toggling off", async () => {
129
- controller.checkedValue = true
130
- let eventDetail = null
131
- element.addEventListener("shadcn--switch:change", (e) => {
132
- eventDetail = e.detail
133
- })
134
-
135
- controller.toggle()
136
- await nextFrame()
137
-
138
- expect(eventDetail.checked).toBe(false)
139
- })
140
- })
141
-
142
- describe("hidden input synchronization", () => {
143
- const inputSyncHTML = `
144
- <div data-controller="shadcn--switch"
145
- data-shadcn--switch-checked-value="false">
146
- <button data-shadcn--switch-target="button"
147
- data-action="click->shadcn--switch#toggle">
148
- <span data-shadcn--switch-target="thumb"></span>
149
- </button>
150
- <input type="checkbox"
151
- data-shadcn--switch-target="input"
152
- name="feature_flag"
153
- hidden>
154
- </div>
155
- `
156
-
157
- beforeEach(async () => {
158
- const setup = await setupController(SwitchController, inputSyncHTML, 'shadcn--switch')
159
- application = setup.application
160
- element = setup.element
161
- controller = setup.controller
162
- })
163
-
164
- test("syncs hidden input checked state", async () => {
165
- controller.toggle()
166
- await nextFrame()
167
-
168
- expect(controller.inputTarget.checked).toBe(true)
169
- })
170
-
171
- test("unchecks hidden input when toggled off", async () => {
172
- controller.checkedValue = true
173
- controller.syncInput()
174
- expect(controller.inputTarget.checked).toBe(true)
175
-
176
- controller.toggle()
177
- await nextFrame()
178
-
179
- expect(controller.inputTarget.checked).toBe(false)
180
- })
181
-
182
- test("dispatches native change event on input", async () => {
183
- let nativeChangeEvent = false
184
- controller.inputTarget.addEventListener("change", () => {
185
- nativeChangeEvent = true
186
- })
187
-
188
- controller.toggle()
189
- await nextFrame()
190
-
191
- expect(nativeChangeEvent).toBe(true)
192
- })
193
- })
194
-
195
- describe("keyboard navigation", () => {
196
- const keyboardHTML = `
197
- <div data-controller="shadcn--switch"
198
- data-shadcn--switch-checked-value="false">
199
- <button data-shadcn--switch-target="button"
200
- data-action="click->shadcn--switch#toggle keydown->shadcn--switch#handleKeydown">
201
- <span data-shadcn--switch-target="thumb"></span>
202
- </button>
203
- <input type="checkbox" data-shadcn--switch-target="input" hidden>
204
- </div>
205
- `
206
-
207
- beforeEach(async () => {
208
- const setup = await setupController(SwitchController, keyboardHTML, 'shadcn--switch')
209
- application = setup.application
210
- element = setup.element
211
- controller = setup.controller
212
- })
213
-
214
- test("toggles on Space key", async () => {
215
- controller.handleKeydown({ key: " ", preventDefault: jest.fn() })
216
- await nextFrame()
217
-
218
- expect(controller.checkedValue).toBe(true)
219
- })
220
-
221
- test("toggles on Enter key", async () => {
222
- controller.handleKeydown({ key: "Enter", preventDefault: jest.fn() })
223
- await nextFrame()
224
-
225
- expect(controller.checkedValue).toBe(true)
226
- })
227
-
228
- test("prevents default on Space", () => {
229
- const preventDefault = jest.fn()
230
- controller.handleKeydown({ key: " ", preventDefault })
231
-
232
- expect(preventDefault).toHaveBeenCalled()
233
- })
234
-
235
- test("prevents default on Enter", () => {
236
- const preventDefault = jest.fn()
237
- controller.handleKeydown({ key: "Enter", preventDefault })
238
-
239
- expect(preventDefault).toHaveBeenCalled()
240
- })
241
-
242
- test("ignores other keys", () => {
243
- const preventDefault = jest.fn()
244
- controller.handleKeydown({ key: "Tab", preventDefault })
245
-
246
- expect(preventDefault).not.toHaveBeenCalled()
247
- expect(controller.checkedValue).toBe(false)
248
- })
249
- })
250
-
251
- describe("disabled state", () => {
252
- const disabledHTML = `
253
- <div data-controller="shadcn--switch"
254
- data-shadcn--switch-checked-value="false">
255
- <button data-shadcn--switch-target="button"
256
- disabled
257
- data-action="click->shadcn--switch#toggle">
258
- <span data-shadcn--switch-target="thumb"></span>
259
- </button>
260
- <input type="checkbox" data-shadcn--switch-target="input" hidden>
261
- </div>
262
- `
263
-
264
- beforeEach(async () => {
265
- const setup = await setupController(SwitchController, disabledHTML, 'shadcn--switch')
266
- application = setup.application
267
- element = setup.element
268
- controller = setup.controller
269
- })
270
-
271
- test("does not toggle when disabled", async () => {
272
- controller.toggle()
273
- await nextFrame()
274
-
275
- expect(controller.checkedValue).toBe(false)
276
- })
277
- })
278
-
279
- describe("initial checked state", () => {
280
- const checkedHTML = `
281
- <div data-controller="shadcn--switch"
282
- data-shadcn--switch-checked-value="true">
283
- <button data-shadcn--switch-target="button"
284
- role="switch"
285
- aria-checked="true"
286
- data-action="click->shadcn--switch#toggle">
287
- <span data-shadcn--switch-target="thumb"></span>
288
- </button>
289
- <input type="checkbox" data-shadcn--switch-target="input" checked hidden>
290
- </div>
291
- `
292
-
293
- beforeEach(async () => {
294
- const setup = await setupController(SwitchController, checkedHTML, 'shadcn--switch')
295
- application = setup.application
296
- element = setup.element
297
- controller = setup.controller
298
- })
299
-
300
- test("initializes with checked state", () => {
301
- expect(controller.checkedValue).toBe(true)
302
- })
303
-
304
- test("sets checked data-state on init", () => {
305
- expect(element.dataset.state).toBe("checked")
306
- })
307
-
308
- test("sets aria-checked true on init", () => {
309
- expect(controller.buttonTarget.getAttribute("aria-checked")).toBe("true")
310
- })
311
- })
312
-
313
- describe("programmatic value change", () => {
314
- const programmaticHTML = `
315
- <div data-controller="shadcn--switch"
316
- data-shadcn--switch-checked-value="false">
317
- <button data-shadcn--switch-target="button">
318
- <span data-shadcn--switch-target="thumb"></span>
319
- </button>
320
- <input type="checkbox" data-shadcn--switch-target="input" hidden>
321
- </div>
322
- `
323
-
324
- beforeEach(async () => {
325
- const setup = await setupController(SwitchController, programmaticHTML, 'shadcn--switch')
326
- application = setup.application
327
- element = setup.element
328
- controller = setup.controller
329
- })
330
-
331
- test("updates UI when checkedValue changes", async () => {
332
- controller.checkedValue = true
333
- await nextFrame()
334
-
335
- expect(element.dataset.state).toBe("checked")
336
- expect(controller.buttonTarget.getAttribute("aria-checked")).toBe("true")
337
- })
338
-
339
- test("syncs input when checkedValue changes", async () => {
340
- controller.checkedValue = true
341
- await nextFrame()
342
-
343
- expect(controller.inputTarget.checked).toBe(true)
344
- })
345
- })
346
-
347
- describe("multiple toggles", () => {
348
- const multipleToggleHTML = `
349
- <div data-controller="shadcn--switch"
350
- data-shadcn--switch-checked-value="false">
351
- <button data-shadcn--switch-target="button"
352
- data-action="click->shadcn--switch#toggle">
353
- <span data-shadcn--switch-target="thumb"></span>
354
- </button>
355
- <input type="checkbox" data-shadcn--switch-target="input" hidden>
356
- </div>
357
- `
358
-
359
- beforeEach(async () => {
360
- const setup = await setupController(SwitchController, multipleToggleHTML, 'shadcn--switch')
361
- application = setup.application
362
- element = setup.element
363
- controller = setup.controller
364
- })
365
-
366
- test("handles rapid toggles correctly", async () => {
367
- controller.toggle() // true
368
- controller.toggle() // false
369
- controller.toggle() // true
370
- await nextFrame()
371
-
372
- expect(controller.checkedValue).toBe(true)
373
- })
374
-
375
- test("maintains state consistency through multiple toggles", async () => {
376
- let changeCount = 0
377
- element.addEventListener("shadcn--switch:change", () => {
378
- changeCount++
379
- })
380
-
381
- controller.toggle()
382
- controller.toggle()
383
- controller.toggle()
384
- controller.toggle()
385
- await nextFrame()
386
-
387
- expect(changeCount).toBe(4)
388
- expect(controller.checkedValue).toBe(false)
389
- })
390
- })
391
-
392
- describe("without optional targets", () => {
393
- const minimalHTML = `
394
- <div data-controller="shadcn--switch"
395
- data-shadcn--switch-checked-value="false">
396
- <button data-shadcn--switch-target="button"
397
- data-action="click->shadcn--switch#toggle">Toggle</button>
398
- </div>
399
- `
400
-
401
- beforeEach(async () => {
402
- const setup = await setupController(SwitchController, minimalHTML, 'shadcn--switch')
403
- application = setup.application
404
- element = setup.element
405
- controller = setup.controller
406
- })
407
-
408
- test("works without thumb target", async () => {
409
- expect(() => {
410
- controller.toggle()
411
- }).not.toThrow()
412
-
413
- expect(controller.checkedValue).toBe(true)
414
- })
415
-
416
- test("works without input target", async () => {
417
- expect(() => {
418
- controller.toggle()
419
- }).not.toThrow()
420
-
421
- expect(controller.checkedValue).toBe(true)
422
- })
423
- })
424
- })