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,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Sheet (slide-out panel)
6
+ # Side: top, right, bottom, left
7
+ class Sheet < 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: "sheet",
21
+ data_controller: "shadcn--sheet",
22
+ data_shadcn__sheet_open_value: false
23
+ )
24
+ end
25
+ end
26
+
27
+ class SheetTrigger < 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: "sheet-trigger",
41
+ data_shadcn__sheet_target: "trigger",
42
+ data_action: "click->shadcn--sheet#show",
43
+ role: "button",
44
+ style: "display: inline-block"
45
+ )
46
+ end
47
+ end
48
+
49
+ class SheetOverlay < Base
50
+ def initialize(**attrs)
51
+ @attrs = attrs
52
+ end
53
+
54
+ def view_template
55
+ div(**build_attrs)
56
+ end
57
+
58
+ private
59
+
60
+ def build_attrs
61
+ classes = cn(
62
+ "fixed inset-0 z-50 bg-black/50",
63
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
64
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0",
65
+ @attrs.delete(:class)
66
+ )
67
+ @attrs.merge(
68
+ data_slot: "sheet-overlay",
69
+ data_shadcn__sheet_target: "overlay",
70
+ data_action: "click->shadcn--sheet#clickOverlay",
71
+ hidden: true,
72
+ class: classes
73
+ )
74
+ end
75
+ end
76
+
77
+ class SheetContent < Base
78
+ SIDE_CLASSES = {
79
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
80
+ right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
81
+ bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
82
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm"
83
+ }.freeze
84
+
85
+ def initialize(side: :right, show_close_button: true, **attrs)
86
+ @side = side.to_sym
87
+ @show_close_button = show_close_button
88
+ @attrs = attrs
89
+ end
90
+
91
+ def view_template(&block)
92
+ render SheetOverlay.new
93
+
94
+ div(**build_attrs) do
95
+ yield if block_given?
96
+
97
+ if @show_close_button
98
+ button(
99
+ data_slot: "sheet-close",
100
+ data_action: "click->shadcn--sheet#hide",
101
+ type: "button",
102
+ class: "absolute right-4 top-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
103
+ ) do
104
+ svg(
105
+ xmlns: "http://www.w3.org/2000/svg",
106
+ width: "16", height: "16",
107
+ viewbox: "0 0 24 24",
108
+ fill: "none",
109
+ stroke: "currentColor",
110
+ stroke_width: "2",
111
+ stroke_linecap: "round",
112
+ stroke_linejoin: "round",
113
+ class: "size-4"
114
+ ) do |s|
115
+ s.path(d: "M18 6 6 18")
116
+ s.path(d: "m6 6 12 12")
117
+ end
118
+ span(class: "sr-only") { "Close" }
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def build_attrs
127
+ classes = cn(
128
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out",
129
+ "data-[state=closed]:animate-out data-[state=open]:animate-in data-[state=closed]:duration-300 data-[state=open]:duration-500",
130
+ SIDE_CLASSES[@side],
131
+ @attrs.delete(:class)
132
+ )
133
+ @attrs.merge(
134
+ data_slot: "sheet-content",
135
+ data_shadcn__sheet_target: "content",
136
+ data_side: @side,
137
+ hidden: true,
138
+ class: classes
139
+ )
140
+ end
141
+ end
142
+
143
+ class SheetHeader < Base
144
+ def initialize(**attrs)
145
+ @attrs = attrs
146
+ end
147
+
148
+ def view_template(&block)
149
+ div(**build_attrs, &block)
150
+ end
151
+
152
+ private
153
+
154
+ def build_attrs
155
+ classes = cn("flex flex-col gap-1.5 p-4", @attrs.delete(:class))
156
+ @attrs.merge(data_slot: "sheet-header", class: classes)
157
+ end
158
+ end
159
+
160
+ class SheetFooter < Base
161
+ def initialize(**attrs)
162
+ @attrs = attrs
163
+ end
164
+
165
+ def view_template(&block)
166
+ div(**build_attrs, &block)
167
+ end
168
+
169
+ private
170
+
171
+ def build_attrs
172
+ classes = cn("mt-auto flex flex-col gap-2 p-4", @attrs.delete(:class))
173
+ @attrs.merge(data_slot: "sheet-footer", class: classes)
174
+ end
175
+ end
176
+
177
+ class SheetTitle < Base
178
+ def initialize(**attrs)
179
+ @attrs = attrs
180
+ end
181
+
182
+ def view_template(&block)
183
+ h2(**build_attrs, &block)
184
+ end
185
+
186
+ private
187
+
188
+ def build_attrs
189
+ classes = cn("text-lg font-semibold text-foreground", @attrs.delete(:class))
190
+ @attrs.merge(data_slot: "sheet-title", class: classes)
191
+ end
192
+ end
193
+
194
+ class SheetDescription < Base
195
+ def initialize(**attrs)
196
+ @attrs = attrs
197
+ end
198
+
199
+ def view_template(&block)
200
+ p(**build_attrs, &block)
201
+ end
202
+
203
+ private
204
+
205
+ def build_attrs
206
+ classes = cn("text-sm text-muted-foreground", @attrs.delete(:class))
207
+ @attrs.merge(data_slot: "sheet-description", class: classes)
208
+ end
209
+ end
210
+
211
+ class SheetClose < Base
212
+ def initialize(**attrs)
213
+ @attrs = attrs
214
+ end
215
+
216
+ def view_template(&block)
217
+ button(**build_attrs, &block)
218
+ end
219
+
220
+ private
221
+
222
+ def build_attrs
223
+ @attrs.merge(
224
+ data_slot: "sheet-close",
225
+ data_action: "click->shadcn--sheet#hide",
226
+ type: "button"
227
+ )
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,420 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Sidebar
6
+ # Uses collapsible + sheet patterns for mobile
7
+ class Sidebar < Base
8
+ def initialize(side: :left, collapsible: :offcanvas, **attrs)
9
+ @side = side
10
+ @collapsible = collapsible
11
+ @attrs = attrs
12
+ end
13
+
14
+ def view_template(&block)
15
+ div(**build_attrs, &block)
16
+ end
17
+
18
+ private
19
+
20
+ def build_attrs
21
+ classes = cn(
22
+ "group/sidebar flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
23
+ @attrs.delete(:class)
24
+ )
25
+ @attrs.merge(
26
+ data_slot: "sidebar-wrapper",
27
+ data_side: @side,
28
+ data_collapsible: @collapsible,
29
+ class: classes
30
+ )
31
+ end
32
+ end
33
+
34
+ class SidebarPanel < Base
35
+ def initialize(side: :left, variant: :sidebar, collapsible: :offcanvas, **attrs)
36
+ @side = side
37
+ @variant = variant
38
+ @collapsible = collapsible
39
+ @attrs = attrs
40
+ end
41
+
42
+ def view_template(&block)
43
+ div(**build_attrs) do
44
+ div(data_slot: "sidebar-container", class: sidebar_container_classes, &block)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def build_attrs
51
+ classes = cn(
52
+ "group peer hidden text-sidebar-foreground md:block",
53
+ "data-[collapsible=offcanvas]:w-0 data-[collapsible=offcanvas]:opacity-0 data-[collapsible=offcanvas]:invisible",
54
+ @collapsible == :none ? "" : "transition-[width,opacity] duration-200 ease-linear",
55
+ @attrs.delete(:class)
56
+ )
57
+ @attrs.merge(
58
+ data_slot: "sidebar",
59
+ data_side: @side,
60
+ data_variant: @variant,
61
+ data_collapsible: @collapsible,
62
+ class: classes
63
+ )
64
+ end
65
+
66
+ def sidebar_container_classes
67
+ cn(
68
+ "flex h-full w-[--sidebar-width] flex-col bg-sidebar",
69
+ "group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow",
70
+ "group-data-[variant=inset]:bg-sidebar",
71
+ "group-data-[side=left]:border-r group-data-[side=right]:border-l"
72
+ )
73
+ end
74
+ end
75
+
76
+ class SidebarHeader < Base
77
+ def initialize(**attrs)
78
+ @attrs = attrs
79
+ end
80
+
81
+ def view_template(&block)
82
+ div(**build_attrs, &block)
83
+ end
84
+
85
+ private
86
+
87
+ def build_attrs
88
+ classes = cn("flex flex-col gap-2 p-2", @attrs.delete(:class))
89
+ @attrs.merge(data_slot: "sidebar-header", class: classes)
90
+ end
91
+ end
92
+
93
+ class SidebarContent < Base
94
+ def initialize(**attrs)
95
+ @attrs = attrs
96
+ end
97
+
98
+ def view_template(&block)
99
+ div(**build_attrs, &block)
100
+ end
101
+
102
+ private
103
+
104
+ def build_attrs
105
+ classes = cn(
106
+ "flex min-h-0 flex-1 flex-col gap-2 overflow-auto",
107
+ "group-data-[collapsible=icon]:overflow-hidden",
108
+ @attrs.delete(:class)
109
+ )
110
+ @attrs.merge(data_slot: "sidebar-content", class: classes)
111
+ end
112
+ end
113
+
114
+ class SidebarFooter < Base
115
+ def initialize(**attrs)
116
+ @attrs = attrs
117
+ end
118
+
119
+ def view_template(&block)
120
+ div(**build_attrs, &block)
121
+ end
122
+
123
+ private
124
+
125
+ def build_attrs
126
+ classes = cn("flex flex-col gap-2 p-2", @attrs.delete(:class))
127
+ @attrs.merge(data_slot: "sidebar-footer", class: classes)
128
+ end
129
+ end
130
+
131
+ class SidebarGroup < Base
132
+ def initialize(**attrs)
133
+ @attrs = attrs
134
+ end
135
+
136
+ def view_template(&block)
137
+ div(**build_attrs, &block)
138
+ end
139
+
140
+ private
141
+
142
+ def build_attrs
143
+ classes = cn("relative flex w-full min-w-0 flex-col p-2", @attrs.delete(:class))
144
+ @attrs.merge(data_slot: "sidebar-group", class: classes)
145
+ end
146
+ end
147
+
148
+ class SidebarGroupLabel < Base
149
+ def initialize(**attrs)
150
+ @attrs = attrs
151
+ end
152
+
153
+ def view_template(&block)
154
+ div(**build_attrs, &block)
155
+ end
156
+
157
+ private
158
+
159
+ def build_attrs
160
+ classes = cn(
161
+ "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70",
162
+ "outline-none ring-sidebar-ring transition-[margin,opa] duration-200 ease-linear",
163
+ "focus-visible:ring-2",
164
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
165
+ "[&>svg]:size-4 [&>svg]:shrink-0",
166
+ @attrs.delete(:class)
167
+ )
168
+ @attrs.merge(data_slot: "sidebar-group-label", class: classes)
169
+ end
170
+ end
171
+
172
+ class SidebarGroupContent < Base
173
+ def initialize(**attrs)
174
+ @attrs = attrs
175
+ end
176
+
177
+ def view_template(&block)
178
+ div(**build_attrs, &block)
179
+ end
180
+
181
+ private
182
+
183
+ def build_attrs
184
+ classes = cn("w-full text-sm", @attrs.delete(:class))
185
+ @attrs.merge(data_slot: "sidebar-group-content", class: classes)
186
+ end
187
+ end
188
+
189
+ class SidebarMenu < Base
190
+ def initialize(**attrs)
191
+ @attrs = attrs
192
+ end
193
+
194
+ def view_template(&block)
195
+ ul(**build_attrs, &block)
196
+ end
197
+
198
+ private
199
+
200
+ def build_attrs
201
+ classes = cn("flex w-full min-w-0 flex-col gap-1", @attrs.delete(:class))
202
+ @attrs.merge(data_slot: "sidebar-menu", class: classes)
203
+ end
204
+ end
205
+
206
+ class SidebarMenuItem < Base
207
+ def initialize(**attrs)
208
+ @attrs = attrs
209
+ end
210
+
211
+ def view_template(&block)
212
+ li(**build_attrs, &block)
213
+ end
214
+
215
+ private
216
+
217
+ def build_attrs
218
+ classes = cn("group/menu-item relative", @attrs.delete(:class))
219
+ @attrs.merge(data_slot: "sidebar-menu-item", class: classes)
220
+ end
221
+ end
222
+
223
+ class SidebarMenuButton < Base
224
+ VARIANTS = ClassVariants.build(
225
+ base: [
226
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm",
227
+ "outline-none ring-sidebar-ring transition-[width,height,padding]",
228
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
229
+ "focus-visible:ring-2",
230
+ "active:bg-sidebar-accent active:text-sidebar-accent-foreground",
231
+ "disabled:pointer-events-none disabled:opacity-50",
232
+ "group-has-[[data-slot=sidebar-menu-action]]/menu-item:pr-8",
233
+ "aria-disabled:pointer-events-none aria-disabled:opacity-50",
234
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:font-medium",
235
+ "data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground",
236
+ "group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2!",
237
+ "[&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0"
238
+ ].join(" "),
239
+ variants: {
240
+ size: {
241
+ default: "h-8 text-sm",
242
+ sm: "h-7 text-xs",
243
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!"
244
+ }
245
+ },
246
+ defaults: { size: :default }
247
+ )
248
+
249
+ def initialize(size: :default, active: false, **attrs)
250
+ @size = size
251
+ @active = active
252
+ @attrs = attrs
253
+ end
254
+
255
+ def view_template(&block)
256
+ button(**build_attrs, &block)
257
+ end
258
+
259
+ private
260
+
261
+ def build_attrs
262
+ classes = cn(VARIANTS.render(size: @size), @attrs.delete(:class))
263
+ @attrs.merge(
264
+ data_slot: "sidebar-menu-button",
265
+ data_size: @size,
266
+ data_active: @active,
267
+ type: "button",
268
+ class: classes
269
+ )
270
+ end
271
+ end
272
+
273
+ class SidebarMenuAction < Base
274
+ def initialize(show_on_hover: false, **attrs)
275
+ @show_on_hover = show_on_hover
276
+ @attrs = attrs
277
+ end
278
+
279
+ def view_template(&block)
280
+ button(**build_attrs, &block)
281
+ end
282
+
283
+ private
284
+
285
+ def build_attrs
286
+ classes = cn(
287
+ "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0",
288
+ "text-sidebar-foreground outline-none ring-sidebar-ring transition-transform",
289
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
290
+ "focus-visible:ring-2",
291
+ "peer-hover/menu-button:text-sidebar-accent-foreground",
292
+ "[&>svg]:size-4 [&>svg]:shrink-0",
293
+ @show_on_hover ? "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0" : "",
294
+ @attrs.delete(:class)
295
+ )
296
+ @attrs.merge(data_slot: "sidebar-menu-action", type: "button", class: classes)
297
+ end
298
+ end
299
+
300
+ class SidebarMenuSub < Base
301
+ def initialize(**attrs)
302
+ @attrs = attrs
303
+ end
304
+
305
+ def view_template(&block)
306
+ ul(**build_attrs, &block)
307
+ end
308
+
309
+ private
310
+
311
+ def build_attrs
312
+ classes = cn(
313
+ "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
314
+ "group-data-[collapsible=icon]:hidden",
315
+ @attrs.delete(:class)
316
+ )
317
+ @attrs.merge(data_slot: "sidebar-menu-sub", class: classes)
318
+ end
319
+ end
320
+
321
+ class SidebarMenuSubItem < Base
322
+ def initialize(**attrs)
323
+ @attrs = attrs
324
+ end
325
+
326
+ def view_template(&block)
327
+ li(**build_attrs, &block)
328
+ end
329
+
330
+ private
331
+
332
+ def build_attrs
333
+ @attrs.merge(data_slot: "sidebar-menu-sub-item")
334
+ end
335
+ end
336
+
337
+ class SidebarMenuSubButton < Base
338
+ def initialize(active: false, size: :md, **attrs)
339
+ @active = active
340
+ @size = size
341
+ @attrs = attrs
342
+ end
343
+
344
+ def view_template(&block)
345
+ a(**build_attrs, &block)
346
+ end
347
+
348
+ private
349
+
350
+ def build_attrs
351
+ classes = cn(
352
+ "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2",
353
+ "text-sidebar-foreground outline-none ring-sidebar-ring",
354
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
355
+ "focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground",
356
+ "disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
357
+ "[&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
358
+ @active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "",
359
+ @size == :sm ? "text-xs" : "text-sm",
360
+ @attrs.delete(:class)
361
+ )
362
+ @attrs.merge(data_slot: "sidebar-menu-sub-button", data_active: @active, data_size: @size, class: classes)
363
+ end
364
+ end
365
+
366
+ class SidebarSeparator < Base
367
+ def initialize(**attrs)
368
+ @attrs = attrs
369
+ end
370
+
371
+ def view_template
372
+ div(**build_attrs)
373
+ end
374
+
375
+ private
376
+
377
+ def build_attrs
378
+ classes = cn("mx-2 h-px bg-sidebar-border", @attrs.delete(:class))
379
+ @attrs.merge(data_slot: "sidebar-separator", class: classes)
380
+ end
381
+ end
382
+
383
+ class SidebarTrigger < Base
384
+ def initialize(**attrs)
385
+ @attrs = attrs
386
+ end
387
+
388
+ def view_template(&block)
389
+ button(**build_attrs) do
390
+ if block_given?
391
+ yield
392
+ else
393
+ # Default hamburger icon
394
+ svg(xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16",
395
+ viewbox: "0 0 24 24", fill: "none", stroke: "currentColor",
396
+ stroke_width: "2", class: "size-4") do |s|
397
+ s.line(x1: "3", x2: "21", y1: "6", y2: "6")
398
+ s.line(x1: "3", x2: "21", y1: "12", y2: "12")
399
+ s.line(x1: "3", x2: "21", y1: "18", y2: "18")
400
+ end
401
+ span(class: "sr-only") { "Toggle Sidebar" }
402
+ end
403
+ end
404
+ end
405
+
406
+ private
407
+
408
+ def build_attrs
409
+ classes = cn(
410
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium",
411
+ "ring-ring transition-colors hover:bg-accent hover:text-accent-foreground",
412
+ "focus-visible:outline-none focus-visible:ring-2",
413
+ "h-7 w-7",
414
+ @attrs.delete(:class)
415
+ )
416
+ @attrs.merge(data_slot: "sidebar-trigger", type: "button", class: classes)
417
+ end
418
+ end
419
+ end
420
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Skeleton
6
+ class Skeleton < 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("animate-pulse rounded-md bg-accent", @attrs.delete(:class))
19
+ @attrs.merge(data_slot: "skeleton", class: classes)
20
+ end
21
+ end
22
+ end
23
+ end