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.
- checksums.yaml +7 -0
- data/README.md +195 -0
- data/app.css +20 -0
- data/css/shadcn-source.css +3 -0
- data/css/shadcn-tailwind.css +160 -0
- data/css/themes/mauve.css +62 -0
- data/css/themes/mist.css +62 -0
- data/css/themes/neutral.css +74 -0
- data/css/themes/olive.css +62 -0
- data/css/themes/stone.css +62 -0
- data/css/themes/taupe.css +62 -0
- data/css/themes/zinc.css +62 -0
- data/js/controllers/accordion_controller.js +135 -0
- data/js/controllers/checkbox_controller.js +52 -0
- data/js/controllers/collapsible_controller.js +85 -0
- data/js/controllers/combobox_controller.js +168 -0
- data/js/controllers/command_controller.js +171 -0
- data/js/controllers/context_menu_controller.js +132 -0
- data/js/controllers/dark_mode_controller.js +106 -0
- data/js/controllers/dialog_controller.js +205 -0
- data/js/controllers/drawer_controller.js +161 -0
- data/js/controllers/dropdown_menu_controller.js +189 -0
- data/js/controllers/hover_card_controller.js +85 -0
- data/js/controllers/index.js +89 -0
- data/js/controllers/menubar_controller.js +171 -0
- data/js/controllers/navigation_menu_controller.js +160 -0
- data/js/controllers/popover_controller.js +151 -0
- data/js/controllers/radio_group_controller.js +78 -0
- data/js/controllers/scroll_area_controller.js +117 -0
- data/js/controllers/select_controller.js +198 -0
- data/js/controllers/sheet_controller.js +130 -0
- data/js/controllers/slider_controller.js +142 -0
- data/js/controllers/switch_controller.js +40 -0
- data/js/controllers/tabs_controller.js +96 -0
- data/js/controllers/toast_controller.js +206 -0
- data/js/controllers/toggle_controller.js +30 -0
- data/js/controllers/toggle_group_controller.js +73 -0
- data/js/controllers/tooltip_controller.js +146 -0
- data/lib/generators/shadcn_phlex/component_generator.rb +79 -0
- data/lib/generators/shadcn_phlex/install_generator.rb +217 -0
- data/lib/shadcn/base.rb +27 -0
- data/lib/shadcn/engine.rb +24 -0
- data/lib/shadcn/kit.rb +1158 -0
- data/lib/shadcn/themes/accent_colors.rb +106 -0
- data/lib/shadcn/themes/base_colors.rb +313 -0
- data/lib/shadcn/ui/accordion.rb +135 -0
- data/lib/shadcn/ui/alert.rb +79 -0
- data/lib/shadcn/ui/alert_dialog.rb +220 -0
- data/lib/shadcn/ui/aspect_ratio.rb +35 -0
- data/lib/shadcn/ui/avatar.rb +134 -0
- data/lib/shadcn/ui/badge.rb +48 -0
- data/lib/shadcn/ui/breadcrumb.rb +180 -0
- data/lib/shadcn/ui/button.rb +63 -0
- data/lib/shadcn/ui/button_group.rb +58 -0
- data/lib/shadcn/ui/card.rb +133 -0
- data/lib/shadcn/ui/checkbox.rb +72 -0
- data/lib/shadcn/ui/collapsible.rb +76 -0
- data/lib/shadcn/ui/combobox.rb +229 -0
- data/lib/shadcn/ui/command.rb +256 -0
- data/lib/shadcn/ui/context_menu.rb +319 -0
- data/lib/shadcn/ui/dialog.rb +226 -0
- data/lib/shadcn/ui/direction.rb +23 -0
- data/lib/shadcn/ui/drawer.rb +217 -0
- data/lib/shadcn/ui/dropdown_menu.rb +384 -0
- data/lib/shadcn/ui/empty.rb +97 -0
- data/lib/shadcn/ui/field.rb +126 -0
- data/lib/shadcn/ui/hover_card.rb +75 -0
- data/lib/shadcn/ui/input.rb +36 -0
- data/lib/shadcn/ui/input_group.rb +32 -0
- data/lib/shadcn/ui/input_otp.rb +112 -0
- data/lib/shadcn/ui/item.rb +115 -0
- data/lib/shadcn/ui/kbd.rb +45 -0
- data/lib/shadcn/ui/label.rb +28 -0
- data/lib/shadcn/ui/menubar.rb +345 -0
- data/lib/shadcn/ui/native_select.rb +31 -0
- data/lib/shadcn/ui/navigation_menu.rb +238 -0
- data/lib/shadcn/ui/pagination.rb +224 -0
- data/lib/shadcn/ui/popover.rb +147 -0
- data/lib/shadcn/ui/progress.rb +40 -0
- data/lib/shadcn/ui/radio_group.rb +92 -0
- data/lib/shadcn/ui/resizable.rb +108 -0
- data/lib/shadcn/ui/scroll_area.rb +75 -0
- data/lib/shadcn/ui/select.rb +235 -0
- data/lib/shadcn/ui/separator.rb +36 -0
- data/lib/shadcn/ui/sheet.rb +231 -0
- data/lib/shadcn/ui/sidebar.rb +420 -0
- data/lib/shadcn/ui/skeleton.rb +23 -0
- data/lib/shadcn/ui/slider.rb +72 -0
- data/lib/shadcn/ui/sonner.rb +177 -0
- data/lib/shadcn/ui/spinner.rb +58 -0
- data/lib/shadcn/ui/switch.rb +75 -0
- data/lib/shadcn/ui/table.rb +154 -0
- data/lib/shadcn/ui/tabs.rb +154 -0
- data/lib/shadcn/ui/text_field.rb +146 -0
- data/lib/shadcn/ui/textarea.rb +32 -0
- data/lib/shadcn/ui/theme_toggle.rb +74 -0
- data/lib/shadcn/ui/toggle.rb +66 -0
- data/lib/shadcn/ui/toggle_group.rb +75 -0
- data/lib/shadcn/ui/tooltip.rb +78 -0
- data/lib/shadcn/ui/typography.rb +217 -0
- data/lib/shadcn/version.rb +5 -0
- data/lib/shadcn-phlex.rb +6 -0
- data/lib/shadcn.rb +80 -0
- data/package.json +14 -0
- data/skills/shadcn-phlex/SKILL.md +190 -0
- data/skills/shadcn-phlex/evals/evals.json +90 -0
- data/skills/shadcn-phlex/references/component-catalog.md +355 -0
- data/skills/shadcn-phlex/rules/composition.md +235 -0
- data/skills/shadcn-phlex/rules/forms.md +151 -0
- data/skills/shadcn-phlex/rules/helpers.md +54 -0
- data/skills/shadcn-phlex/rules/stimulus.md +61 -0
- data/skills/shadcn-phlex/rules/styling.md +177 -0
- 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
|