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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui RadioGroup
6
+ class RadioGroup < Base
7
+ def initialize(name: nil, value: nil, **attrs)
8
+ @name = name
9
+ @value = value
10
+ @attrs = attrs
11
+ end
12
+
13
+ def view_template(&block)
14
+ div(**build_attrs) do
15
+ if @name
16
+ input(type: "hidden", name: @name, value: @value.to_s, data_shadcn__radio_group_target: "input")
17
+ end
18
+ yield if block_given?
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def build_attrs
25
+ classes = cn("grid gap-3", @attrs.delete(:class))
26
+ attrs = @attrs.merge(
27
+ data_slot: "radio-group",
28
+ data_controller: "shadcn--radio-group",
29
+ role: "radiogroup",
30
+ class: classes
31
+ )
32
+ attrs[:data_shadcn__radio_group_value_value] = @value.to_s if @value
33
+ attrs
34
+ end
35
+ end
36
+
37
+ class RadioGroupItem < Base
38
+ def initialize(value:, checked: false, **attrs)
39
+ @value = value
40
+ @checked = checked
41
+ @attrs = attrs
42
+ end
43
+
44
+ def view_template
45
+ button(**build_attrs) do
46
+ span(
47
+ data_shadcn__radio_group_target: "indicator",
48
+ data_value: @value,
49
+ hidden: !@checked,
50
+ class: "relative flex items-center justify-center"
51
+ ) do
52
+ svg(
53
+ xmlns: "http://www.w3.org/2000/svg",
54
+ width: "8", height: "8",
55
+ viewbox: "0 0 24 24",
56
+ fill: "currentColor",
57
+ class: "size-2.5 fill-primary"
58
+ ) do |s|
59
+ s.circle(cx: "12", cy: "12", r: "10")
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def build_attrs
68
+ classes = cn(
69
+ "aspect-square size-4 shrink-0 rounded-full border border-input shadow-xs",
70
+ "transition-shadow outline-none",
71
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
72
+ "disabled:cursor-not-allowed disabled:opacity-50",
73
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
74
+ "data-[state=checked]:border-primary",
75
+ "dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
76
+ @attrs.delete(:class)
77
+ )
78
+ @attrs.merge(
79
+ data_slot: "radio-group-item",
80
+ data_shadcn__radio_group_target: "item",
81
+ data_action: "click->shadcn--radio-group#select keydown->shadcn--radio-group#keydown",
82
+ data_state: @checked ? "checked" : "unchecked",
83
+ data_value: @value,
84
+ role: "radio",
85
+ type: "button",
86
+ aria_checked: @checked,
87
+ class: classes
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Resizable
6
+ # Panel groups with drag handles
7
+ class ResizablePanelGroup < Base
8
+ def initialize(direction: :horizontal, **attrs)
9
+ @direction = direction
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
+ "flex h-full w-full",
22
+ @direction == :vertical ? "flex-col" : "flex-row",
23
+ "data-[panel-group-direction=vertical]:flex-col",
24
+ @attrs.delete(:class)
25
+ )
26
+ @attrs.merge(
27
+ data_slot: "resizable-panel-group",
28
+ data_panel_group: true,
29
+ data_panel_group_direction: @direction,
30
+ class: classes
31
+ )
32
+ end
33
+ end
34
+
35
+ class ResizablePanel < Base
36
+ def initialize(default_size: nil, min_size: nil, max_size: nil, **attrs)
37
+ @default_size = default_size
38
+ @min_size = min_size
39
+ @max_size = max_size
40
+ @attrs = attrs
41
+ end
42
+
43
+ def view_template(&block)
44
+ div(**build_attrs, &block)
45
+ end
46
+
47
+ private
48
+
49
+ def build_attrs
50
+ style_parts = []
51
+ style_parts << "flex: #{@default_size} 1 0%" if @default_size
52
+ style_parts << "min-width: #{@min_size}%" if @min_size
53
+ style_parts << "max-width: #{@max_size}%" if @max_size
54
+
55
+ classes = cn("flex-1 overflow-auto", @attrs.delete(:class))
56
+ result = @attrs.merge(data_slot: "resizable-panel", data_panel: true, class: classes)
57
+ result[:style] = style_parts.join("; ") unless style_parts.empty?
58
+ result
59
+ end
60
+ end
61
+
62
+ class ResizableHandle < Base
63
+ def initialize(with_handle: false, **attrs)
64
+ @with_handle = with_handle
65
+ @attrs = attrs
66
+ end
67
+
68
+ def view_template(&block)
69
+ div(**build_attrs) do
70
+ if @with_handle
71
+ div(class: "z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border") do
72
+ svg(xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10",
73
+ viewbox: "0 0 24 24", fill: "none", stroke: "currentColor",
74
+ stroke_width: "2", class: "size-2.5") do |s|
75
+ s.path(d: "M9 5v14")
76
+ s.path(d: "M15 5v14")
77
+ end
78
+ end
79
+ end
80
+ yield if block_given?
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def build_attrs
87
+ classes = cn(
88
+ "relative flex w-px items-center justify-center bg-border",
89
+ "after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2",
90
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1",
91
+ "data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full",
92
+ "data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1",
93
+ "data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2",
94
+ "data-[panel-group-direction=vertical]:after:translate-x-0",
95
+ "[&[data-panel-group-direction=vertical]>div]:rotate-90",
96
+ @attrs.delete(:class)
97
+ )
98
+ @attrs.merge(
99
+ data_slot: "resizable-handle",
100
+ data_panel_resize_handle: true,
101
+ role: "separator",
102
+ tabindex: "0",
103
+ class: classes
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui ScrollArea
6
+ class ScrollArea < Base
7
+ def initialize(**attrs)
8
+ @attrs = attrs
9
+ end
10
+
11
+ def view_template(&block)
12
+ div(**build_attrs) do
13
+ div(
14
+ data_slot: "scroll-area-viewport",
15
+ data_shadcn__scroll_area_target: "viewport",
16
+ class: "size-full overflow-auto rounded-[inherit]",
17
+ &block
18
+ )
19
+ render ScrollBar.new
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def build_attrs
26
+ classes = cn("relative overflow-hidden", @attrs.delete(:class))
27
+ @attrs.merge(
28
+ data_slot: "scroll-area",
29
+ data_controller: "shadcn--scroll-area",
30
+ class: classes
31
+ )
32
+ end
33
+ end
34
+
35
+ class ScrollBar < Base
36
+ def initialize(orientation: :vertical, **attrs)
37
+ @orientation = orientation
38
+ @attrs = attrs
39
+ end
40
+
41
+ def view_template
42
+ div(**build_attrs) do
43
+ div(
44
+ data_slot: "scroll-area-thumb",
45
+ data_shadcn__scroll_area_target: "thumb",
46
+ data_action: "pointerdown->shadcn--scroll-area#startDrag",
47
+ class: "relative flex-1 rounded-full bg-border"
48
+ )
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def build_attrs
55
+ orientation_classes = if @orientation == :vertical
56
+ "h-full w-2.5 border-l border-l-transparent"
57
+ else
58
+ "h-2.5 flex-col border-t border-t-transparent"
59
+ end
60
+
61
+ classes = cn(
62
+ "flex touch-none p-px transition-colors select-none",
63
+ orientation_classes,
64
+ @attrs.delete(:class)
65
+ )
66
+ @attrs.merge(
67
+ data_slot: "scroll-area-scrollbar",
68
+ data_shadcn__scroll_area_target: "scrollbar",
69
+ data_orientation: @orientation,
70
+ class: classes
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Select
6
+ class Select < Base
7
+ def initialize(name: nil, **attrs)
8
+ @name = name
9
+ @attrs = attrs
10
+ end
11
+
12
+ def view_template(&block)
13
+ div(**build_attrs) do
14
+ if @name
15
+ input(type: "hidden", name: @name, value: "", data_shadcn__select_target: "input")
16
+ end
17
+ yield if block_given?
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def build_attrs
24
+ @attrs.merge(
25
+ data_slot: "select",
26
+ data_controller: "shadcn--select",
27
+ data_action: "click@window->shadcn--select#hide keydown.esc@window->shadcn--select#hideOnEscape keydown->shadcn--select#navigate"
28
+ )
29
+ end
30
+ end
31
+
32
+ class SelectTrigger < Base
33
+ def initialize(size: :default, **attrs)
34
+ @size = size
35
+ @attrs = attrs
36
+ end
37
+
38
+ def view_template(&block)
39
+ button(**build_attrs) do
40
+ yield if block_given?
41
+ # ChevronDown icon
42
+ svg(
43
+ xmlns: "http://www.w3.org/2000/svg",
44
+ width: "16", height: "16",
45
+ viewbox: "0 0 24 24",
46
+ fill: "none",
47
+ stroke: "currentColor",
48
+ stroke_width: "2",
49
+ stroke_linecap: "round",
50
+ stroke_linejoin: "round",
51
+ class: "size-4 opacity-50 shrink-0"
52
+ ) do |s|
53
+ s.path(d: "m6 9 6 6 6-6")
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def build_attrs
61
+ classes = cn(
62
+ "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs",
63
+ "transition-[color,box-shadow] outline-none",
64
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
65
+ "disabled:cursor-not-allowed disabled:opacity-50",
66
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20",
67
+ "data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8",
68
+ "*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2",
69
+ "dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40",
70
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
71
+ @attrs.delete(:class)
72
+ )
73
+ @attrs.merge(
74
+ data_slot: "select-trigger",
75
+ data_shadcn__select_target: "trigger",
76
+ data_action: "click->shadcn--select#toggle",
77
+ data_size: @size,
78
+ type: "button",
79
+ role: "combobox",
80
+ class: classes
81
+ )
82
+ end
83
+ end
84
+
85
+ class SelectValue < Base
86
+ def initialize(placeholder: nil, **attrs)
87
+ @placeholder = placeholder
88
+ @attrs = attrs
89
+ end
90
+
91
+ def view_template(&block)
92
+ span(**build_attrs) do
93
+ if block_given?
94
+ yield
95
+ elsif @placeholder
96
+ plain @placeholder
97
+ end
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def build_attrs
104
+ @attrs.merge(
105
+ data_slot: "select-value",
106
+ data_shadcn__select_target: "value"
107
+ )
108
+ end
109
+ end
110
+
111
+ class SelectContent < Base
112
+ def initialize(position: :popper, **attrs)
113
+ @position = position
114
+ @attrs = attrs
115
+ end
116
+
117
+ def view_template(&block)
118
+ div(**build_attrs, &block)
119
+ end
120
+
121
+ private
122
+
123
+ def build_attrs
124
+ popper_classes = @position == :popper ?
125
+ "max-h-[min(var(--radix-select-content-available-height),24rem)]" : ""
126
+
127
+ classes = cn(
128
+ "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-md origin-(--radix-select-content-transform-origin)",
129
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
130
+ "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
131
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
132
+ "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
133
+ popper_classes,
134
+ @attrs.delete(:class)
135
+ )
136
+ @attrs.merge(
137
+ data_slot: "select-content",
138
+ data_shadcn__select_target: "content",
139
+ data_position: @position,
140
+ role: "listbox",
141
+ hidden: true,
142
+ class: classes
143
+ )
144
+ end
145
+ end
146
+
147
+ class SelectItem < Base
148
+ def initialize(value:, **attrs)
149
+ @value = value
150
+ @attrs = attrs
151
+ end
152
+
153
+ def view_template(&block)
154
+ div(**build_attrs) do
155
+ yield if block_given?
156
+ span(class: "absolute right-2 flex size-3.5 items-center justify-center") do
157
+ # Check icon rendered when selected via data-state
158
+ end
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def build_attrs
165
+ classes = cn(
166
+ "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm",
167
+ "outline-hidden select-none",
168
+ "focus:bg-accent focus:text-accent-foreground",
169
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
170
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
171
+ @attrs.delete(:class)
172
+ )
173
+ @attrs.merge(
174
+ data_slot: "select-item",
175
+ data_shadcn__select_target: "item",
176
+ data_action: "click->shadcn--select#selectItem",
177
+ data_value: @value,
178
+ role: "option",
179
+ tabindex: "-1",
180
+ class: classes
181
+ )
182
+ end
183
+ end
184
+
185
+ class SelectGroup < Base
186
+ def initialize(**attrs)
187
+ @attrs = attrs
188
+ end
189
+
190
+ def view_template(&block)
191
+ div(**build_attrs, &block)
192
+ end
193
+
194
+ private
195
+
196
+ def build_attrs
197
+ @attrs.merge(data_slot: "select-group", role: "group")
198
+ end
199
+ end
200
+
201
+ class SelectLabel < Base
202
+ def initialize(**attrs)
203
+ @attrs = attrs
204
+ end
205
+
206
+ def view_template(&block)
207
+ div(**build_attrs, &block)
208
+ end
209
+
210
+ private
211
+
212
+ def build_attrs
213
+ classes = cn("px-2 py-1.5 text-xs text-muted-foreground font-medium", @attrs.delete(:class))
214
+ @attrs.merge(data_slot: "select-label", class: classes)
215
+ end
216
+ end
217
+
218
+ class SelectSeparator < Base
219
+ def initialize(**attrs)
220
+ @attrs = attrs
221
+ end
222
+
223
+ def view_template
224
+ div(**build_attrs)
225
+ end
226
+
227
+ private
228
+
229
+ def build_attrs
230
+ classes = cn("pointer-events-none -mx-1 my-1 h-px bg-muted", @attrs.delete(:class))
231
+ @attrs.merge(data_slot: "select-separator", class: classes)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ module UI
5
+ # Port of shadcn/ui Separator
6
+ class Separator < Base
7
+ def initialize(orientation: :horizontal, decorative: true, **attrs)
8
+ @orientation = orientation
9
+ @decorative = decorative
10
+ @attrs = attrs
11
+ end
12
+
13
+ def view_template
14
+ div(**build_attrs)
15
+ end
16
+
17
+ private
18
+
19
+ def build_attrs
20
+ classes = cn(
21
+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
22
+ @attrs.delete(:class)
23
+ )
24
+
25
+ result = @attrs.merge(
26
+ data_slot: "separator",
27
+ data_orientation: @orientation,
28
+ role: @decorative ? "none" : "separator",
29
+ class: classes
30
+ )
31
+ result[:aria_orientation] = @orientation.to_s unless @decorative
32
+ result
33
+ end
34
+ end
35
+ end
36
+ end