shadcn-phlex 0.1.0

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +195 -0
  3. data/app.css +20 -0
  4. data/css/shadcn-source.css +3 -0
  5. data/css/shadcn-tailwind.css +160 -0
  6. data/css/themes/mauve.css +62 -0
  7. data/css/themes/mist.css +62 -0
  8. data/css/themes/neutral.css +74 -0
  9. data/css/themes/olive.css +62 -0
  10. data/css/themes/stone.css +62 -0
  11. data/css/themes/taupe.css +62 -0
  12. data/css/themes/zinc.css +62 -0
  13. data/js/controllers/accordion_controller.js +135 -0
  14. data/js/controllers/checkbox_controller.js +52 -0
  15. data/js/controllers/collapsible_controller.js +85 -0
  16. data/js/controllers/combobox_controller.js +168 -0
  17. data/js/controllers/command_controller.js +171 -0
  18. data/js/controllers/context_menu_controller.js +132 -0
  19. data/js/controllers/dark_mode_controller.js +106 -0
  20. data/js/controllers/dialog_controller.js +205 -0
  21. data/js/controllers/drawer_controller.js +161 -0
  22. data/js/controllers/dropdown_menu_controller.js +189 -0
  23. data/js/controllers/hover_card_controller.js +85 -0
  24. data/js/controllers/index.js +89 -0
  25. data/js/controllers/menubar_controller.js +171 -0
  26. data/js/controllers/navigation_menu_controller.js +160 -0
  27. data/js/controllers/popover_controller.js +151 -0
  28. data/js/controllers/radio_group_controller.js +78 -0
  29. data/js/controllers/scroll_area_controller.js +117 -0
  30. data/js/controllers/select_controller.js +198 -0
  31. data/js/controllers/sheet_controller.js +130 -0
  32. data/js/controllers/slider_controller.js +142 -0
  33. data/js/controllers/switch_controller.js +40 -0
  34. data/js/controllers/tabs_controller.js +96 -0
  35. data/js/controllers/toast_controller.js +206 -0
  36. data/js/controllers/toggle_controller.js +30 -0
  37. data/js/controllers/toggle_group_controller.js +73 -0
  38. data/js/controllers/tooltip_controller.js +146 -0
  39. data/lib/generators/shadcn_phlex/component_generator.rb +79 -0
  40. data/lib/generators/shadcn_phlex/install_generator.rb +217 -0
  41. data/lib/shadcn/base.rb +27 -0
  42. data/lib/shadcn/engine.rb +24 -0
  43. data/lib/shadcn/kit.rb +1158 -0
  44. data/lib/shadcn/themes/accent_colors.rb +106 -0
  45. data/lib/shadcn/themes/base_colors.rb +313 -0
  46. data/lib/shadcn/ui/accordion.rb +135 -0
  47. data/lib/shadcn/ui/alert.rb +79 -0
  48. data/lib/shadcn/ui/alert_dialog.rb +220 -0
  49. data/lib/shadcn/ui/aspect_ratio.rb +35 -0
  50. data/lib/shadcn/ui/avatar.rb +134 -0
  51. data/lib/shadcn/ui/badge.rb +48 -0
  52. data/lib/shadcn/ui/breadcrumb.rb +180 -0
  53. data/lib/shadcn/ui/button.rb +63 -0
  54. data/lib/shadcn/ui/button_group.rb +58 -0
  55. data/lib/shadcn/ui/card.rb +133 -0
  56. data/lib/shadcn/ui/checkbox.rb +72 -0
  57. data/lib/shadcn/ui/collapsible.rb +76 -0
  58. data/lib/shadcn/ui/combobox.rb +229 -0
  59. data/lib/shadcn/ui/command.rb +256 -0
  60. data/lib/shadcn/ui/context_menu.rb +319 -0
  61. data/lib/shadcn/ui/dialog.rb +226 -0
  62. data/lib/shadcn/ui/direction.rb +23 -0
  63. data/lib/shadcn/ui/drawer.rb +217 -0
  64. data/lib/shadcn/ui/dropdown_menu.rb +384 -0
  65. data/lib/shadcn/ui/empty.rb +97 -0
  66. data/lib/shadcn/ui/field.rb +126 -0
  67. data/lib/shadcn/ui/hover_card.rb +75 -0
  68. data/lib/shadcn/ui/input.rb +36 -0
  69. data/lib/shadcn/ui/input_group.rb +32 -0
  70. data/lib/shadcn/ui/input_otp.rb +112 -0
  71. data/lib/shadcn/ui/item.rb +115 -0
  72. data/lib/shadcn/ui/kbd.rb +45 -0
  73. data/lib/shadcn/ui/label.rb +28 -0
  74. data/lib/shadcn/ui/menubar.rb +345 -0
  75. data/lib/shadcn/ui/native_select.rb +31 -0
  76. data/lib/shadcn/ui/navigation_menu.rb +238 -0
  77. data/lib/shadcn/ui/pagination.rb +224 -0
  78. data/lib/shadcn/ui/popover.rb +147 -0
  79. data/lib/shadcn/ui/progress.rb +40 -0
  80. data/lib/shadcn/ui/radio_group.rb +92 -0
  81. data/lib/shadcn/ui/resizable.rb +108 -0
  82. data/lib/shadcn/ui/scroll_area.rb +75 -0
  83. data/lib/shadcn/ui/select.rb +235 -0
  84. data/lib/shadcn/ui/separator.rb +36 -0
  85. data/lib/shadcn/ui/sheet.rb +231 -0
  86. data/lib/shadcn/ui/sidebar.rb +420 -0
  87. data/lib/shadcn/ui/skeleton.rb +23 -0
  88. data/lib/shadcn/ui/slider.rb +72 -0
  89. data/lib/shadcn/ui/sonner.rb +177 -0
  90. data/lib/shadcn/ui/spinner.rb +58 -0
  91. data/lib/shadcn/ui/switch.rb +75 -0
  92. data/lib/shadcn/ui/table.rb +154 -0
  93. data/lib/shadcn/ui/tabs.rb +154 -0
  94. data/lib/shadcn/ui/text_field.rb +146 -0
  95. data/lib/shadcn/ui/textarea.rb +32 -0
  96. data/lib/shadcn/ui/theme_toggle.rb +74 -0
  97. data/lib/shadcn/ui/toggle.rb +66 -0
  98. data/lib/shadcn/ui/toggle_group.rb +75 -0
  99. data/lib/shadcn/ui/tooltip.rb +78 -0
  100. data/lib/shadcn/ui/typography.rb +217 -0
  101. data/lib/shadcn/version.rb +5 -0
  102. data/lib/shadcn-phlex.rb +6 -0
  103. data/lib/shadcn.rb +80 -0
  104. data/package.json +14 -0
  105. data/skills/shadcn-phlex/SKILL.md +190 -0
  106. data/skills/shadcn-phlex/evals/evals.json +90 -0
  107. data/skills/shadcn-phlex/references/component-catalog.md +355 -0
  108. data/skills/shadcn-phlex/rules/composition.md +235 -0
  109. data/skills/shadcn-phlex/rules/forms.md +151 -0
  110. data/skills/shadcn-phlex/rules/helpers.md +54 -0
  111. data/skills/shadcn-phlex/rules/stimulus.md +61 -0
  112. data/skills/shadcn-phlex/rules/styling.md +177 -0
  113. metadata +209 -0
@@ -0,0 +1,384 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui DropdownMenu
6
+ # Requires JS for open/close behavior
7
+ class DropdownMenu < Base
8
+ def initialize(**attrs)
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template(&block)
13
+ div(**build_attrs, &block)
14
+ end
15
+
16
+ private
17
+
18
+ def build_attrs
19
+ @attrs.merge(
20
+ data_slot: "dropdown-menu",
21
+ data_controller: "shadcn--dropdown-menu",
22
+ data_action: "click@window->shadcn--dropdown-menu#hide keydown.esc@window->shadcn--dropdown-menu#hideOnEscape keydown->shadcn--dropdown-menu#navigate"
23
+ )
24
+ end
25
+ end
26
+
27
+ class DropdownMenuTrigger < Base
28
+ def initialize(**attrs)
29
+ @attrs = attrs
30
+ end
31
+
32
+ def view_template(&block)
33
+ div(**build_attrs, &block)
34
+ end
35
+
36
+ private
37
+
38
+ def build_attrs
39
+ @attrs.merge(
40
+ data_slot: "dropdown-menu-trigger",
41
+ data_shadcn__dropdown_menu_target: "trigger",
42
+ data_action: "click->shadcn--dropdown-menu#toggle",
43
+ role: "button",
44
+ style: "display: inline-block"
45
+ )
46
+ end
47
+ end
48
+
49
+ class DropdownMenuContent < Base
50
+ def initialize(**attrs)
51
+ @attrs = attrs
52
+ end
53
+
54
+ def view_template(&block)
55
+ div(**build_attrs, &block)
56
+ end
57
+
58
+ private
59
+
60
+ def build_attrs
61
+ classes = cn(
62
+ "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem]",
63
+ "overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
64
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
65
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
66
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
67
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
68
+ @attrs.delete(:class)
69
+ )
70
+ @attrs.merge(
71
+ data_slot: "dropdown-menu-content",
72
+ data_shadcn__dropdown_menu_target: "content",
73
+ role: "menu",
74
+ hidden: true,
75
+ class: classes
76
+ )
77
+ end
78
+ end
79
+
80
+ class DropdownMenuGroup < Base
81
+ def initialize(**attrs)
82
+ @attrs = attrs
83
+ end
84
+
85
+ def view_template(&block)
86
+ div(**build_attrs, &block)
87
+ end
88
+
89
+ private
90
+
91
+ def build_attrs
92
+ @attrs.merge(data_slot: "dropdown-menu-group", role: "group")
93
+ end
94
+ end
95
+
96
+ class DropdownMenuItem < Base
97
+ def initialize(inset: false, variant: :default, **attrs)
98
+ @inset = inset
99
+ @variant = variant
100
+ @attrs = attrs
101
+ end
102
+
103
+ def view_template(&block)
104
+ div(**build_attrs, &block)
105
+ end
106
+
107
+ private
108
+
109
+ def build_attrs
110
+ classes = cn(
111
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none",
112
+ "focus:bg-accent focus:text-accent-foreground",
113
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
114
+ "data-[inset]:pl-8",
115
+ "data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive",
116
+ "dark:data-[variant=destructive]:focus:bg-destructive/20",
117
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
118
+ "[&_svg:not([class*='text-'])]:text-muted-foreground",
119
+ @attrs.delete(:class)
120
+ )
121
+ result = @attrs.merge(
122
+ data_slot: "dropdown-menu-item",
123
+ data_shadcn__dropdown_menu_target: "item",
124
+ data_action: "click->shadcn--dropdown-menu#selectItem",
125
+ data_variant: @variant,
126
+ role: "menuitem",
127
+ tabindex: "-1",
128
+ class: classes
129
+ )
130
+ result[:data_inset] = true if @inset
131
+ result
132
+ end
133
+ end
134
+
135
+ class DropdownMenuCheckboxItem < Base
136
+ def initialize(checked: false, **attrs)
137
+ @checked = checked
138
+ @attrs = attrs
139
+ end
140
+
141
+ def view_template(&block)
142
+ div(**build_attrs) do
143
+ span(class: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center") do
144
+ if @checked
145
+ svg(
146
+ xmlns: "http://www.w3.org/2000/svg",
147
+ width: "16", height: "16",
148
+ viewbox: "0 0 24 24",
149
+ fill: "none",
150
+ stroke: "currentColor",
151
+ stroke_width: "2",
152
+ stroke_linecap: "round",
153
+ stroke_linejoin: "round",
154
+ class: "size-4"
155
+ ) do |s|
156
+ s.path(d: "M20 6 9 17l-5-5")
157
+ end
158
+ end
159
+ end
160
+ yield if block_given?
161
+ end
162
+ end
163
+
164
+ private
165
+
166
+ def build_attrs
167
+ classes = cn(
168
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm",
169
+ "outline-hidden select-none",
170
+ "focus:bg-accent focus:text-accent-foreground",
171
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
172
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
173
+ @attrs.delete(:class)
174
+ )
175
+ @attrs.merge(
176
+ data_slot: "dropdown-menu-checkbox-item",
177
+ data_shadcn__dropdown_menu_target: "item",
178
+ data_action: "click->shadcn--dropdown-menu#selectItem",
179
+ role: "menuitemcheckbox",
180
+ aria_checked: @checked,
181
+ tabindex: "-1",
182
+ class: classes
183
+ )
184
+ end
185
+ end
186
+
187
+ class DropdownMenuRadioGroup < Base
188
+ def initialize(**attrs)
189
+ @attrs = attrs
190
+ end
191
+
192
+ def view_template(&block)
193
+ div(**build_attrs, &block)
194
+ end
195
+
196
+ private
197
+
198
+ def build_attrs
199
+ @attrs.merge(data_slot: "dropdown-menu-radio-group", role: "group")
200
+ end
201
+ end
202
+
203
+ class DropdownMenuRadioItem < Base
204
+ def initialize(checked: false, **attrs)
205
+ @checked = checked
206
+ @attrs = attrs
207
+ end
208
+
209
+ def view_template(&block)
210
+ div(**build_attrs) do
211
+ span(class: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center") do
212
+ if @checked
213
+ svg(
214
+ xmlns: "http://www.w3.org/2000/svg",
215
+ width: "8", height: "8",
216
+ viewbox: "0 0 24 24",
217
+ fill: "currentColor",
218
+ class: "size-2"
219
+ ) do |s|
220
+ s.circle(cx: "12", cy: "12", r: "10")
221
+ end
222
+ end
223
+ end
224
+ yield if block_given?
225
+ end
226
+ end
227
+
228
+ private
229
+
230
+ def build_attrs
231
+ classes = cn(
232
+ "relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm",
233
+ "outline-hidden select-none",
234
+ "focus:bg-accent focus:text-accent-foreground",
235
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
236
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
237
+ @attrs.delete(:class)
238
+ )
239
+ @attrs.merge(
240
+ data_slot: "dropdown-menu-radio-item",
241
+ data_shadcn__dropdown_menu_target: "item",
242
+ data_action: "click->shadcn--dropdown-menu#selectItem",
243
+ role: "menuitemradio",
244
+ aria_checked: @checked,
245
+ tabindex: "-1",
246
+ class: classes
247
+ )
248
+ end
249
+ end
250
+
251
+ class DropdownMenuLabel < Base
252
+ def initialize(inset: false, **attrs)
253
+ @inset = inset
254
+ @attrs = attrs
255
+ end
256
+
257
+ def view_template(&block)
258
+ div(**build_attrs, &block)
259
+ end
260
+
261
+ private
262
+
263
+ def build_attrs
264
+ classes = cn(
265
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
266
+ @attrs.delete(:class)
267
+ )
268
+ result = @attrs.merge(data_slot: "dropdown-menu-label", class: classes)
269
+ result[:data_inset] = true if @inset
270
+ result
271
+ end
272
+ end
273
+
274
+ class DropdownMenuSeparator < Base
275
+ def initialize(**attrs)
276
+ @attrs = attrs
277
+ end
278
+
279
+ def view_template
280
+ div(**build_attrs)
281
+ end
282
+
283
+ private
284
+
285
+ def build_attrs
286
+ classes = cn("-mx-1 my-1 h-px bg-border", @attrs.delete(:class))
287
+ @attrs.merge(data_slot: "dropdown-menu-separator", role: "separator", class: classes)
288
+ end
289
+ end
290
+
291
+ class DropdownMenuShortcut < Base
292
+ def initialize(**attrs)
293
+ @attrs = attrs
294
+ end
295
+
296
+ def view_template(&block)
297
+ span(**build_attrs, &block)
298
+ end
299
+
300
+ private
301
+
302
+ def build_attrs
303
+ classes = cn("ml-auto text-xs tracking-widest text-muted-foreground", @attrs.delete(:class))
304
+ @attrs.merge(data_slot: "dropdown-menu-shortcut", class: classes)
305
+ end
306
+ end
307
+
308
+ class DropdownMenuSubTrigger < Base
309
+ def initialize(inset: false, **attrs)
310
+ @inset = inset
311
+ @attrs = attrs
312
+ end
313
+
314
+ def view_template(&block)
315
+ div(**build_attrs) do
316
+ yield if block_given?
317
+ # ChevronRight icon
318
+ svg(
319
+ xmlns: "http://www.w3.org/2000/svg",
320
+ width: "16", height: "16",
321
+ viewbox: "0 0 24 24",
322
+ fill: "none",
323
+ stroke: "currentColor",
324
+ stroke_width: "2",
325
+ stroke_linecap: "round",
326
+ stroke_linejoin: "round",
327
+ class: "ml-auto size-4"
328
+ ) do |s|
329
+ s.path(d: "m9 18 6-6-6-6")
330
+ end
331
+ end
332
+ end
333
+
334
+ private
335
+
336
+ def build_attrs
337
+ classes = cn(
338
+ "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none",
339
+ "focus:bg-accent focus:text-accent-foreground",
340
+ "data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
341
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
342
+ "[&_svg:not([class*='text-'])]:text-muted-foreground",
343
+ @attrs.delete(:class)
344
+ )
345
+ result = @attrs.merge(
346
+ data_slot: "dropdown-menu-sub-trigger",
347
+ data_shadcn__dropdown_menu_target: "subTrigger",
348
+ class: classes
349
+ )
350
+ result[:data_inset] = true if @inset
351
+ result
352
+ end
353
+ end
354
+
355
+ class DropdownMenuSubContent < Base
356
+ def initialize(**attrs)
357
+ @attrs = attrs
358
+ end
359
+
360
+ def view_template(&block)
361
+ div(**build_attrs, &block)
362
+ end
363
+
364
+ private
365
+
366
+ def build_attrs
367
+ classes = cn(
368
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg",
369
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
370
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
371
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
372
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
373
+ @attrs.delete(:class)
374
+ )
375
+ @attrs.merge(
376
+ data_slot: "dropdown-menu-sub-content",
377
+ data_shadcn__dropdown_menu_target: "subContent",
378
+ hidden: true,
379
+ class: classes
380
+ )
381
+ end
382
+ end
383
+ end
384
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Empty state
6
+ class Empty < Base
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ div(**build_attrs, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def build_attrs
18
+ classes = cn(
19
+ "flex min-h-[200px] flex-col items-center justify-center gap-4 rounded-lg border border-dashed p-8 text-center",
20
+ @attrs.delete(:class)
21
+ )
22
+ @attrs.merge(data_slot: "empty", class: classes)
23
+ end
24
+ end
25
+
26
+ class EmptyMedia < Base
27
+ def initialize(**attrs)
28
+ @attrs = attrs
29
+ end
30
+
31
+ def view_template(&block)
32
+ div(**build_attrs, &block)
33
+ end
34
+
35
+ private
36
+
37
+ def build_attrs
38
+ classes = cn(
39
+ "flex items-center justify-center [&_svg]:size-12 [&_svg]:text-muted-foreground",
40
+ @attrs.delete(:class)
41
+ )
42
+ @attrs.merge(data_slot: "empty-media", class: classes)
43
+ end
44
+ end
45
+
46
+ class EmptyTitle < Base
47
+ def initialize(**attrs)
48
+ @attrs = attrs
49
+ end
50
+
51
+ def view_template(&block)
52
+ h3(**build_attrs, &block)
53
+ end
54
+
55
+ private
56
+
57
+ def build_attrs
58
+ classes = cn("text-lg font-semibold", @attrs.delete(:class))
59
+ @attrs.merge(data_slot: "empty-title", class: classes)
60
+ end
61
+ end
62
+
63
+ class EmptyDescription < Base
64
+ def initialize(**attrs)
65
+ @attrs = attrs
66
+ end
67
+
68
+ def view_template(&block)
69
+ p(**build_attrs, &block)
70
+ end
71
+
72
+ private
73
+
74
+ def build_attrs
75
+ classes = cn("text-sm text-muted-foreground max-w-sm", @attrs.delete(:class))
76
+ @attrs.merge(data_slot: "empty-description", class: classes)
77
+ end
78
+ end
79
+
80
+ class EmptyActions < Base
81
+ def initialize(**attrs)
82
+ @attrs = attrs
83
+ end
84
+
85
+ def view_template(&block)
86
+ div(**build_attrs, &block)
87
+ end
88
+
89
+ private
90
+
91
+ def build_attrs
92
+ classes = cn("flex items-center gap-2", @attrs.delete(:class))
93
+ @attrs.merge(data_slot: "empty-actions", class: classes)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Field
6
+ # Form field composition: label, control, description, error message
7
+ class Field < Base
8
+ def initialize(disabled: false, **attrs)
9
+ @disabled = disabled
10
+ @attrs = attrs
11
+ end
12
+
13
+ def view_template(&block)
14
+ div(**build_attrs, &block)
15
+ end
16
+
17
+ private
18
+
19
+ def build_attrs
20
+ classes = cn(
21
+ "group/field grid gap-2",
22
+ @attrs.delete(:class)
23
+ )
24
+ @attrs.merge(
25
+ data_slot: "field",
26
+ data_disabled: @disabled || nil,
27
+ class: classes
28
+ )
29
+ end
30
+ end
31
+
32
+ class FieldControl < Base
33
+ def initialize(**attrs)
34
+ @attrs = attrs
35
+ end
36
+
37
+ def view_template(&block)
38
+ div(**build_attrs, &block)
39
+ end
40
+
41
+ private
42
+
43
+ def build_attrs
44
+ @attrs.merge(data_slot: "field-control")
45
+ end
46
+ end
47
+
48
+ class FieldDescription < Base
49
+ def initialize(**attrs)
50
+ @attrs = attrs
51
+ end
52
+
53
+ def view_template(&block)
54
+ p(**build_attrs, &block)
55
+ end
56
+
57
+ private
58
+
59
+ def build_attrs
60
+ classes = cn("text-muted-foreground text-sm", @attrs.delete(:class))
61
+ @attrs.merge(data_slot: "field-description", class: classes)
62
+ end
63
+ end
64
+
65
+ class FieldError < Base
66
+ def initialize(**attrs)
67
+ @attrs = attrs
68
+ end
69
+
70
+ def view_template(&block)
71
+ p(**build_attrs, &block)
72
+ end
73
+
74
+ private
75
+
76
+ def build_attrs
77
+ classes = cn("text-destructive text-sm font-medium", @attrs.delete(:class))
78
+ @attrs.merge(
79
+ data_slot: "field-error",
80
+ role: "alert",
81
+ class: classes
82
+ )
83
+ end
84
+ end
85
+
86
+ class Fieldset < Base
87
+ def initialize(disabled: false, **attrs)
88
+ @disabled = disabled
89
+ @attrs = attrs
90
+ end
91
+
92
+ def view_template(&block)
93
+ fieldset(**build_attrs, &block)
94
+ end
95
+
96
+ private
97
+
98
+ def build_attrs
99
+ classes = cn(
100
+ "grid gap-6 rounded-lg border p-4 disabled:opacity-50",
101
+ @attrs.delete(:class)
102
+ )
103
+ result = @attrs.merge(data_slot: "fieldset", class: classes)
104
+ result[:disabled] = true if @disabled
105
+ result
106
+ end
107
+ end
108
+
109
+ class FieldsetLegend < Base
110
+ def initialize(**attrs)
111
+ @attrs = attrs
112
+ end
113
+
114
+ def view_template(&block)
115
+ legend(**build_attrs, &block)
116
+ end
117
+
118
+ private
119
+
120
+ def build_attrs
121
+ classes = cn("text-sm font-medium leading-none", @attrs.delete(:class))
122
+ @attrs.merge(data_slot: "fieldset-legend", class: classes)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui HoverCard
6
+ class HoverCard < Base
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ div(**build_attrs, &block)
13
+ end
14
+
15
+ private
16
+
17
+ def build_attrs
18
+ @attrs.merge(
19
+ data_slot: "hover-card",
20
+ data_controller: "shadcn--hover-card"
21
+ )
22
+ end
23
+ end
24
+
25
+ class HoverCardTrigger < Base
26
+ def initialize(**attrs)
27
+ @attrs = attrs
28
+ end
29
+
30
+ def view_template(&block)
31
+ a(**build_attrs, &block)
32
+ end
33
+
34
+ private
35
+
36
+ def build_attrs
37
+ @attrs.merge(
38
+ data_slot: "hover-card-trigger",
39
+ data_shadcn__hover_card_target: "trigger",
40
+ data_action: "mouseenter->shadcn--hover-card#triggerEnter mouseleave->shadcn--hover-card#triggerLeave"
41
+ )
42
+ end
43
+ end
44
+
45
+ class HoverCardContent < Base
46
+ def initialize(**attrs)
47
+ @attrs = attrs
48
+ end
49
+
50
+ def view_template(&block)
51
+ div(**build_attrs, &block)
52
+ end
53
+
54
+ private
55
+
56
+ def build_attrs
57
+ classes = cn(
58
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden",
59
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
60
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
61
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
62
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
63
+ @attrs.delete(:class)
64
+ )
65
+ @attrs.merge(
66
+ data_slot: "hover-card-content",
67
+ data_shadcn__hover_card_target: "content",
68
+ data_action: "mouseenter->shadcn--hover-card#contentEnter mouseleave->shadcn--hover-card#contentLeave",
69
+ hidden: true,
70
+ class: classes
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Input
6
+ class Input < Base
7
+ def initialize(type: "text", **attrs)
8
+ @type = type
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template
13
+ input(**build_attrs)
14
+ end
15
+
16
+ private
17
+
18
+ def build_attrs
19
+ classes = cn(
20
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs",
21
+ "transition-[color,box-shadow] outline-none",
22
+ "selection:bg-primary selection:text-primary-foreground",
23
+ "file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
24
+ "placeholder:text-muted-foreground",
25
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
26
+ "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
27
+ "md:text-sm",
28
+ "dark:bg-input/30",
29
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
30
+ @attrs.delete(:class)
31
+ )
32
+ @attrs.merge(data_slot: "input", type: @type, class: classes)
33
+ end
34
+ end
35
+ end
36
+ end