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,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module UI
|
|
5
|
+
# Port of shadcn/ui Slider
|
|
6
|
+
class Slider < Base
|
|
7
|
+
def initialize(value: 0, min: 0, max: 100, name: nil, **attrs)
|
|
8
|
+
@value = value
|
|
9
|
+
@min = min
|
|
10
|
+
@max = max
|
|
11
|
+
@name = name
|
|
12
|
+
@attrs = attrs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def view_template
|
|
16
|
+
span(**build_attrs) do
|
|
17
|
+
if @name
|
|
18
|
+
input(type: "hidden", name: @name, value: @value.to_s, data_shadcn__slider_target: "input")
|
|
19
|
+
end
|
|
20
|
+
pct = ((@value.to_f - @min) / (@max - @min) * 100).clamp(0, 100)
|
|
21
|
+
|
|
22
|
+
span(
|
|
23
|
+
data_slot: "slider-track",
|
|
24
|
+
data_shadcn__slider_target: "track",
|
|
25
|
+
data_action: "click->shadcn--slider#clickTrack",
|
|
26
|
+
data_orientation: "horizontal",
|
|
27
|
+
class: "relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
|
28
|
+
) do
|
|
29
|
+
span(
|
|
30
|
+
data_slot: "slider-range",
|
|
31
|
+
data_shadcn__slider_target: "range",
|
|
32
|
+
data_orientation: "horizontal",
|
|
33
|
+
class: "absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
|
34
|
+
style: "left: 0; width: #{pct}%"
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
span(
|
|
39
|
+
data_slot: "slider-thumb",
|
|
40
|
+
data_shadcn__slider_target: "thumb",
|
|
41
|
+
data_action: "pointerdown->shadcn--slider#startDrag keydown->shadcn--slider#keydown",
|
|
42
|
+
class: "absolute block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50",
|
|
43
|
+
style: "left: calc(#{pct}% - 8px)",
|
|
44
|
+
role: "slider",
|
|
45
|
+
tabindex: "0",
|
|
46
|
+
aria_valuemin: @min,
|
|
47
|
+
aria_valuemax: @max,
|
|
48
|
+
aria_valuenow: @value
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def build_attrs
|
|
56
|
+
classes = cn(
|
|
57
|
+
"relative flex w-full touch-none items-center select-none",
|
|
58
|
+
"data-[disabled]:opacity-50",
|
|
59
|
+
@attrs.delete(:class)
|
|
60
|
+
)
|
|
61
|
+
@attrs.merge(
|
|
62
|
+
data_slot: "slider",
|
|
63
|
+
data_controller: "shadcn--slider",
|
|
64
|
+
data_shadcn__slider_value_value: @value,
|
|
65
|
+
data_shadcn__slider_min_value: @min,
|
|
66
|
+
data_shadcn__slider_max_value: @max,
|
|
67
|
+
class: classes
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module UI
|
|
5
|
+
# Port of shadcn/ui Sonner (Toast container)
|
|
6
|
+
# The JS behavior must be provided by a Stimulus controller or similar
|
|
7
|
+
# This provides the styled markup structure
|
|
8
|
+
class Toaster < Base
|
|
9
|
+
def initialize(position: "bottom-right", **attrs)
|
|
10
|
+
@position = position
|
|
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
|
+
"fixed z-[100] flex max-h-screen flex-col gap-2 p-4",
|
|
23
|
+
position_classes,
|
|
24
|
+
@attrs.delete(:class)
|
|
25
|
+
)
|
|
26
|
+
@attrs.merge(
|
|
27
|
+
data_slot: "toaster",
|
|
28
|
+
data_controller: "shadcn--toast",
|
|
29
|
+
data_shadcn__toast_target: "container",
|
|
30
|
+
data_position: @position,
|
|
31
|
+
class: classes
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def position_classes
|
|
36
|
+
case @position
|
|
37
|
+
when "top-left" then "top-0 left-0"
|
|
38
|
+
when "top-center" then "top-0 left-1/2 -translate-x-1/2"
|
|
39
|
+
when "top-right" then "top-0 right-0"
|
|
40
|
+
when "bottom-left" then "bottom-0 left-0"
|
|
41
|
+
when "bottom-center" then "bottom-0 left-1/2 -translate-x-1/2"
|
|
42
|
+
when "bottom-right" then "bottom-0 right-0"
|
|
43
|
+
else "bottom-0 right-0"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Toast < Base
|
|
49
|
+
VARIANTS = ClassVariants.build(
|
|
50
|
+
base: [
|
|
51
|
+
"group pointer-events-auto relative flex w-full items-center justify-between gap-4 overflow-hidden",
|
|
52
|
+
"rounded-md border p-4 pr-6 shadow-lg transition-all",
|
|
53
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full",
|
|
54
|
+
"data-[state=open]:animate-in data-[state=open]:slide-in-from-top-full data-[state=open]:fade-in-0"
|
|
55
|
+
].join(" "),
|
|
56
|
+
variants: {
|
|
57
|
+
variant: {
|
|
58
|
+
default: "border bg-background text-foreground",
|
|
59
|
+
success: "border-green-500/50 bg-green-50 text-green-900 dark:bg-green-950 dark:text-green-100",
|
|
60
|
+
error: "border-destructive/50 bg-destructive text-white",
|
|
61
|
+
warning: "border-yellow-500/50 bg-yellow-50 text-yellow-900 dark:bg-yellow-950 dark:text-yellow-100",
|
|
62
|
+
info: "border-blue-500/50 bg-blue-50 text-blue-900 dark:bg-blue-950 dark:text-blue-100"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
defaults: { variant: :default }
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def initialize(variant: :default, **attrs)
|
|
69
|
+
@variant = variant
|
|
70
|
+
@attrs = attrs
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def view_template(&block)
|
|
74
|
+
div(**build_attrs, &block)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def build_attrs
|
|
80
|
+
classes = cn(VARIANTS.render(variant: @variant), @attrs.delete(:class))
|
|
81
|
+
@attrs.merge(data_slot: "toast", data_variant: @variant, role: "alert", class: classes)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class ToastTitle < Base
|
|
86
|
+
def initialize(**attrs)
|
|
87
|
+
@attrs = attrs
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def view_template(&block)
|
|
91
|
+
div(**build_attrs, &block)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def build_attrs
|
|
97
|
+
classes = cn("text-sm font-semibold", @attrs.delete(:class))
|
|
98
|
+
@attrs.merge(data_slot: "toast-title", class: classes)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class ToastDescription < Base
|
|
103
|
+
def initialize(**attrs)
|
|
104
|
+
@attrs = attrs
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def view_template(&block)
|
|
108
|
+
div(**build_attrs, &block)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def build_attrs
|
|
114
|
+
classes = cn("text-sm opacity-90", @attrs.delete(:class))
|
|
115
|
+
@attrs.merge(data_slot: "toast-description", class: classes)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class ToastAction < Base
|
|
120
|
+
def initialize(**attrs)
|
|
121
|
+
@attrs = attrs
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def view_template(&block)
|
|
125
|
+
button(**build_attrs, &block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def build_attrs
|
|
131
|
+
classes = cn(
|
|
132
|
+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3",
|
|
133
|
+
"text-sm font-medium transition-colors",
|
|
134
|
+
"hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring",
|
|
135
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
136
|
+
@attrs.delete(:class)
|
|
137
|
+
)
|
|
138
|
+
@attrs.merge(data_slot: "toast-action", type: "button", class: classes)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class ToastClose < Base
|
|
143
|
+
def initialize(**attrs)
|
|
144
|
+
@attrs = attrs
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def view_template
|
|
148
|
+
button(**build_attrs) do
|
|
149
|
+
svg(
|
|
150
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
151
|
+
width: "14", height: "14",
|
|
152
|
+
viewbox: "0 0 24 24",
|
|
153
|
+
fill: "none",
|
|
154
|
+
stroke: "currentColor",
|
|
155
|
+
stroke_width: "2",
|
|
156
|
+
class: "size-4"
|
|
157
|
+
) do |s|
|
|
158
|
+
s.path(d: "M18 6 6 18")
|
|
159
|
+
s.path(d: "m6 6 12 12")
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def build_attrs
|
|
167
|
+
classes = cn(
|
|
168
|
+
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity",
|
|
169
|
+
"group-hover:opacity-100",
|
|
170
|
+
"hover:text-foreground focus:opacity-100 focus:outline-none",
|
|
171
|
+
@attrs.delete(:class)
|
|
172
|
+
)
|
|
173
|
+
@attrs.merge(data_slot: "toast-close", type: "button", class: classes)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module UI
|
|
5
|
+
# Port of shadcn/ui Spinner
|
|
6
|
+
class Spinner < Base
|
|
7
|
+
def initialize(size: :default, **attrs)
|
|
8
|
+
@size = size
|
|
9
|
+
@attrs = attrs
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def view_template
|
|
13
|
+
span(**build_attrs) do
|
|
14
|
+
# Animated loader SVG (Lucide Loader2 equivalent)
|
|
15
|
+
svg(
|
|
16
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
17
|
+
width: "24", height: "24",
|
|
18
|
+
viewbox: "0 0 24 24",
|
|
19
|
+
fill: "none",
|
|
20
|
+
stroke: "currentColor",
|
|
21
|
+
stroke_width: "2",
|
|
22
|
+
stroke_linecap: "round",
|
|
23
|
+
stroke_linejoin: "round",
|
|
24
|
+
class: cn("animate-spin", size_class)
|
|
25
|
+
) do |s|
|
|
26
|
+
s.path(d: "M21 12a9 9 0 1 1-6.219-8.56")
|
|
27
|
+
end
|
|
28
|
+
span(class: "sr-only") { "Loading..." }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def build_attrs
|
|
35
|
+
classes = cn(
|
|
36
|
+
"inline-flex items-center justify-center text-muted-foreground",
|
|
37
|
+
@attrs.delete(:class)
|
|
38
|
+
)
|
|
39
|
+
@attrs.merge(
|
|
40
|
+
data_slot: "spinner",
|
|
41
|
+
role: "status",
|
|
42
|
+
aria_label: "Loading",
|
|
43
|
+
class: classes
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def size_class
|
|
48
|
+
case @size
|
|
49
|
+
when :xs then "size-3"
|
|
50
|
+
when :sm then "size-4"
|
|
51
|
+
when :default then "size-5"
|
|
52
|
+
when :lg then "size-8"
|
|
53
|
+
else "size-5"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module UI
|
|
5
|
+
# Port of shadcn/ui Switch
|
|
6
|
+
# Sizes: default, sm
|
|
7
|
+
class Switch < Base
|
|
8
|
+
def initialize(checked: false, size: :default, name: nil, **attrs)
|
|
9
|
+
@checked = checked
|
|
10
|
+
@size = size
|
|
11
|
+
@name = name
|
|
12
|
+
@attrs = attrs
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def view_template(&block)
|
|
16
|
+
label(
|
|
17
|
+
data_controller: "shadcn--switch",
|
|
18
|
+
data_shadcn__switch_checked_value: @checked.to_s,
|
|
19
|
+
class: "inline-flex items-center gap-2 cursor-pointer"
|
|
20
|
+
) do
|
|
21
|
+
if @name
|
|
22
|
+
input(type: "hidden", name: @name, value: @checked ? "1" : "0", data_shadcn__switch_target: "input")
|
|
23
|
+
end
|
|
24
|
+
button(**build_attrs) do
|
|
25
|
+
span(**thumb_attrs)
|
|
26
|
+
end
|
|
27
|
+
if block
|
|
28
|
+
span(class: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", &block)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def build_attrs
|
|
36
|
+
classes = cn(
|
|
37
|
+
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs",
|
|
38
|
+
"transition-all outline-none",
|
|
39
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
40
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
41
|
+
"data-[size=default]:h-[1.15rem] data-[size=default]:w-8",
|
|
42
|
+
"data-[size=sm]:h-3.5 data-[size=sm]:w-6",
|
|
43
|
+
"data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
|
44
|
+
"dark:data-[state=unchecked]:bg-input/80",
|
|
45
|
+
@attrs.delete(:class)
|
|
46
|
+
)
|
|
47
|
+
@attrs.merge(
|
|
48
|
+
data_slot: "switch",
|
|
49
|
+
data_shadcn__switch_target: "button",
|
|
50
|
+
data_action: "click->shadcn--switch#toggle",
|
|
51
|
+
data_state: @checked ? "checked" : "unchecked",
|
|
52
|
+
data_size: @size,
|
|
53
|
+
role: "switch",
|
|
54
|
+
type: "button",
|
|
55
|
+
aria_checked: @checked,
|
|
56
|
+
class: classes
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def thumb_attrs
|
|
61
|
+
{
|
|
62
|
+
data_slot: "switch-thumb",
|
|
63
|
+
data_shadcn__switch_target: "thumb",
|
|
64
|
+
data_state: @checked ? "checked" : "unchecked",
|
|
65
|
+
class: cn(
|
|
66
|
+
"pointer-events-none block rounded-full bg-background ring-0 transition-transform",
|
|
67
|
+
"group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3",
|
|
68
|
+
"data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
|
|
69
|
+
"dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground"
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module UI
|
|
5
|
+
# Port of shadcn/ui Table
|
|
6
|
+
class Table < Base
|
|
7
|
+
def initialize(**attrs)
|
|
8
|
+
@attrs = attrs
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def view_template(&block)
|
|
12
|
+
div(data_slot: "table-container", class: "relative w-full overflow-x-auto") do
|
|
13
|
+
table(**build_attrs, &block)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def build_attrs
|
|
20
|
+
classes = cn("w-full caption-bottom text-sm", @attrs.delete(:class))
|
|
21
|
+
@attrs.merge(data_slot: "table", class: classes)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class TableHeader < Base
|
|
26
|
+
def initialize(**attrs)
|
|
27
|
+
@attrs = attrs
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def view_template(&block)
|
|
31
|
+
thead(**build_attrs, &block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_attrs
|
|
37
|
+
classes = cn("[&_tr]:border-b", @attrs.delete(:class))
|
|
38
|
+
@attrs.merge(data_slot: "table-header", class: classes)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class TableBody < Base
|
|
43
|
+
def initialize(**attrs)
|
|
44
|
+
@attrs = attrs
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def view_template(&block)
|
|
48
|
+
tbody(**build_attrs, &block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def build_attrs
|
|
54
|
+
classes = cn("[&_tr:last-child]:border-0", @attrs.delete(:class))
|
|
55
|
+
@attrs.merge(data_slot: "table-body", class: classes)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class TableFooter < Base
|
|
60
|
+
def initialize(**attrs)
|
|
61
|
+
@attrs = attrs
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def view_template(&block)
|
|
65
|
+
tfoot(**build_attrs, &block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def build_attrs
|
|
71
|
+
classes = cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", @attrs.delete(:class))
|
|
72
|
+
@attrs.merge(data_slot: "table-footer", class: classes)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class TableRow < Base
|
|
77
|
+
def initialize(**attrs)
|
|
78
|
+
@attrs = attrs
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def view_template(&block)
|
|
82
|
+
tr(**build_attrs, &block)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def build_attrs
|
|
88
|
+
classes = cn(
|
|
89
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
90
|
+
@attrs.delete(:class)
|
|
91
|
+
)
|
|
92
|
+
@attrs.merge(data_slot: "table-row", class: classes)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class TableHead < Base
|
|
97
|
+
def initialize(**attrs)
|
|
98
|
+
@attrs = attrs
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def view_template(&block)
|
|
102
|
+
th(**build_attrs, &block)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def build_attrs
|
|
108
|
+
classes = cn(
|
|
109
|
+
"text-muted-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap",
|
|
110
|
+
"[&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
111
|
+
@attrs.delete(:class)
|
|
112
|
+
)
|
|
113
|
+
@attrs.merge(data_slot: "table-head", class: classes)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
class TableCell < Base
|
|
118
|
+
def initialize(**attrs)
|
|
119
|
+
@attrs = attrs
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def view_template(&block)
|
|
123
|
+
td(**build_attrs, &block)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def build_attrs
|
|
129
|
+
classes = cn(
|
|
130
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
131
|
+
@attrs.delete(:class)
|
|
132
|
+
)
|
|
133
|
+
@attrs.merge(data_slot: "table-cell", class: classes)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class TableCaption < Base
|
|
138
|
+
def initialize(**attrs)
|
|
139
|
+
@attrs = attrs
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def view_template(&block)
|
|
143
|
+
caption(**build_attrs, &block)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def build_attrs
|
|
149
|
+
classes = cn("text-muted-foreground mt-4 text-sm", @attrs.delete(:class))
|
|
150
|
+
@attrs.merge(data_slot: "table-caption", class: classes)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shadcn
|
|
4
|
+
module UI
|
|
5
|
+
# Port of shadcn/ui Tabs
|
|
6
|
+
# Wired to shadcn--tabs Stimulus controller
|
|
7
|
+
class Tabs < Base
|
|
8
|
+
def initialize(value: nil, orientation: :horizontal, **attrs)
|
|
9
|
+
@value = value
|
|
10
|
+
@orientation = orientation
|
|
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/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
|
23
|
+
@attrs.delete(:class)
|
|
24
|
+
)
|
|
25
|
+
result = @attrs.merge(
|
|
26
|
+
data_slot: "tabs",
|
|
27
|
+
data_orientation: @orientation,
|
|
28
|
+
data_controller: "shadcn--tabs",
|
|
29
|
+
data_shadcn__tabs_orientation_value: @orientation,
|
|
30
|
+
class: classes
|
|
31
|
+
)
|
|
32
|
+
result[:data_shadcn__tabs_value_value] = @value if @value
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class TabsList < Base
|
|
38
|
+
VARIANTS = ClassVariants.build(
|
|
39
|
+
base: [
|
|
40
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground",
|
|
41
|
+
"group-data-[orientation=horizontal]/tabs:h-9",
|
|
42
|
+
"group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
|
|
43
|
+
"data-[variant=line]:rounded-none"
|
|
44
|
+
].join(" "),
|
|
45
|
+
variants: {
|
|
46
|
+
variant: {
|
|
47
|
+
default: "bg-muted",
|
|
48
|
+
line: "gap-1 bg-transparent"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
defaults: { variant: :default }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def initialize(variant: :default, **attrs)
|
|
55
|
+
@variant = variant
|
|
56
|
+
@attrs = attrs
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def view_template(&block)
|
|
60
|
+
div(**build_attrs, &block)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def build_attrs
|
|
66
|
+
classes = cn(VARIANTS.render(variant: @variant), @attrs.delete(:class))
|
|
67
|
+
@attrs.merge(
|
|
68
|
+
data_slot: "tabs-list",
|
|
69
|
+
data_variant: @variant,
|
|
70
|
+
data_shadcn__tabs_target: "list",
|
|
71
|
+
role: "tablist",
|
|
72
|
+
class: classes
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class TabsTrigger < Base
|
|
78
|
+
def initialize(value:, **attrs)
|
|
79
|
+
@value = value
|
|
80
|
+
@attrs = attrs
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def view_template(&block)
|
|
84
|
+
button(**build_attrs, &block)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def build_attrs
|
|
90
|
+
classes = cn(
|
|
91
|
+
# Base
|
|
92
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5",
|
|
93
|
+
"rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap",
|
|
94
|
+
"text-foreground/60 transition-all",
|
|
95
|
+
"group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start",
|
|
96
|
+
"hover:text-foreground",
|
|
97
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring",
|
|
98
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
99
|
+
# Default variant active state
|
|
100
|
+
"group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm",
|
|
101
|
+
"group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none",
|
|
102
|
+
"dark:text-muted-foreground dark:hover:text-foreground",
|
|
103
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
104
|
+
# Line variant overrides
|
|
105
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
106
|
+
"dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
107
|
+
# Active state
|
|
108
|
+
"data-[state=active]:bg-background data-[state=active]:text-foreground",
|
|
109
|
+
"dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
|
|
110
|
+
# After pseudo-element (line indicator)
|
|
111
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity",
|
|
112
|
+
"group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5",
|
|
113
|
+
"group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5",
|
|
114
|
+
"group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
115
|
+
@attrs.delete(:class)
|
|
116
|
+
)
|
|
117
|
+
@attrs.merge(
|
|
118
|
+
data_slot: "tabs-trigger",
|
|
119
|
+
data_value: @value,
|
|
120
|
+
data_shadcn__tabs_target: "trigger",
|
|
121
|
+
data_action: "click->shadcn--tabs#select keydown->shadcn--tabs#keydown",
|
|
122
|
+
role: "tab",
|
|
123
|
+
type: "button",
|
|
124
|
+
class: classes
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
class TabsContent < Base
|
|
130
|
+
def initialize(value:, **attrs)
|
|
131
|
+
@value = value
|
|
132
|
+
@attrs = attrs
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def view_template(&block)
|
|
136
|
+
div(**build_attrs, &block)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
def build_attrs
|
|
142
|
+
classes = cn("flex-1 outline-none", @attrs.delete(:class))
|
|
143
|
+
@attrs.merge(
|
|
144
|
+
data_slot: "tabs-content",
|
|
145
|
+
data_value: @value,
|
|
146
|
+
data_shadcn__tabs_target: "content",
|
|
147
|
+
role: "tabpanel",
|
|
148
|
+
hidden: true,
|
|
149
|
+
class: classes
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|