m9sh 0.1.0 → 0.2.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 +4 -4
- data/Dockerfile +2 -1
- data/GEM_README.md +284 -0
- data/LICENSE.txt +21 -0
- data/M9SH_CLI.md +453 -0
- data/PUBLISHING.md +331 -0
- data/README.md +120 -52
- data/app/components/m9sh/accordion_component.rb +3 -3
- data/app/components/m9sh/alert_component.rb +7 -9
- data/app/components/m9sh/base_component.rb +1 -0
- data/app/components/m9sh/button_component.rb +3 -2
- data/app/components/m9sh/color_customizer_component.rb +624 -0
- data/app/components/m9sh/dialog_close_component.rb +30 -0
- data/app/components/m9sh/dialog_component.rb +11 -99
- data/app/components/m9sh/dialog_content_component.rb +102 -0
- data/app/components/m9sh/dialog_description_component.rb +14 -0
- data/app/components/m9sh/dialog_footer_component.rb +14 -0
- data/app/components/m9sh/dialog_header_component.rb +27 -0
- data/app/components/m9sh/dialog_title_component.rb +14 -0
- data/app/components/m9sh/dialog_trigger_component.rb +23 -0
- data/app/components/m9sh/dropdown_menu_content_component.rb +1 -1
- data/app/components/m9sh/dropdown_menu_item_component.rb +1 -1
- data/app/components/m9sh/dropdown_menu_trigger_component.rb +1 -1
- data/app/components/m9sh/icon_component.rb +78 -0
- data/app/components/m9sh/main_component.rb +1 -1
- data/app/components/m9sh/menu_component.rb +85 -0
- data/app/components/m9sh/navbar_component.rb +186 -0
- data/app/components/m9sh/navigation_menu_component.rb +2 -2
- data/app/components/m9sh/popover_component.rb +12 -7
- data/app/components/m9sh/radio_group_component.rb +45 -13
- data/app/components/m9sh/sheet_component.rb +6 -6
- data/app/components/m9sh/sidebar_component.rb +6 -1
- data/app/components/m9sh/skeleton_component.rb +7 -1
- data/app/components/m9sh/tabs_component.rb +76 -48
- data/app/components/m9sh/textarea_component.rb +1 -1
- data/app/components/m9sh/theme_toggle_component.rb +1 -0
- data/app/javascript/controllers/m9sh/popover_controller.js +24 -18
- data/app/javascript/controllers/m9sh/sidebar_controller.js +29 -7
- data/lib/m9sh/config.rb +5 -5
- data/lib/m9sh/registry.rb +2 -2
- data/lib/m9sh/registry.yml +37 -0
- data/lib/m9sh/version.rb +1 -1
- data/lib/tasks/tailwindcss.rake +15 -0
- data/m9sh.gemspec +48 -0
- data/publish.sh +48 -0
- metadata +20 -3
- data/fix_namespaces.py +0 -32
@@ -4,11 +4,13 @@ module M9sh
|
|
4
4
|
class DialogComponent < BaseComponent
|
5
5
|
include Utilities
|
6
6
|
|
7
|
-
renders_one :
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
renders_one :
|
7
|
+
renders_one :dialog_trigger, lambda { |**attrs|
|
8
|
+
DialogTriggerComponent.new(**attrs)
|
9
|
+
}
|
10
|
+
|
11
|
+
renders_one :dialog_content, lambda { |**attrs, &block|
|
12
|
+
DialogContentComponent.new(**attrs, &block)
|
13
|
+
}
|
12
14
|
|
13
15
|
def initialize(open: false, **extra_attrs)
|
14
16
|
@open = open
|
@@ -17,107 +19,17 @@ module M9sh
|
|
17
19
|
|
18
20
|
def call
|
19
21
|
tag.div(
|
22
|
+
**component_attrs(""),
|
20
23
|
data: {
|
21
24
|
controller: "m9sh--dialog",
|
22
25
|
m9sh__dialog_open_value: @open.to_s
|
23
26
|
}
|
24
27
|
) do
|
25
28
|
safe_join([
|
26
|
-
|
27
|
-
|
28
|
-
])
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def render_trigger
|
35
|
-
return unless trigger?
|
36
|
-
|
37
|
-
tag.div(
|
38
|
-
trigger,
|
39
|
-
data: {
|
40
|
-
action: "click->m9sh--dialog#open",
|
41
|
-
m9sh__dialog_target: "trigger"
|
42
|
-
}
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
def render_dialog
|
47
|
-
tag.div(
|
48
|
-
class: overlay_classes,
|
49
|
-
data: {
|
50
|
-
m9sh__dialog_target: "overlay",
|
51
|
-
action: "click->m9sh--dialog#handleBackdropClick"
|
52
|
-
},
|
53
|
-
style: @open ? "" : "display: none;",
|
54
|
-
role: "dialog",
|
55
|
-
"aria-modal": "true"
|
56
|
-
) do
|
57
|
-
tag.div(
|
58
|
-
class: content_classes,
|
59
|
-
data: { m9sh__dialog_target: "content" }
|
60
|
-
) do
|
61
|
-
safe_join([
|
62
|
-
render_close_button,
|
63
|
-
render_header,
|
64
|
-
render_body,
|
65
|
-
render_footer
|
66
|
-
].compact)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def render_close_button
|
72
|
-
tag.button(
|
73
|
-
type: "button",
|
74
|
-
class: "absolute right-4 top-4 rounded-sm 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 data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
|
75
|
-
data: { action: "click->m9sh--dialog#close" }
|
76
|
-
) do
|
77
|
-
tag.svg(
|
78
|
-
class: "h-4 w-4",
|
79
|
-
xmlns: "http://www.w3.org/2000/svg",
|
80
|
-
viewBox: "0 0 24 24",
|
81
|
-
fill: "none",
|
82
|
-
stroke: "currentColor",
|
83
|
-
"stroke-width": "2",
|
84
|
-
"stroke-linecap": "round",
|
85
|
-
"stroke-linejoin": "round"
|
86
|
-
) do
|
87
|
-
tag.line(x1: "18", y1: "6", x2: "6", y2: "18") +
|
88
|
-
tag.line(x1: "6", y1: "6", x2: "18", y2: "18")
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def render_header
|
94
|
-
return unless header? || title? || description?
|
95
|
-
|
96
|
-
tag.div(class: "flex flex-col space-y-1.5 text-center sm:text-left") do
|
97
|
-
safe_join([
|
98
|
-
title? ? tag.h2(title, class: "text-lg font-semibold leading-none tracking-tight") : nil,
|
99
|
-
description? ? tag.p(description, class: "text-sm text-muted-foreground") : nil,
|
100
|
-
header
|
29
|
+
dialog_trigger,
|
30
|
+
dialog_content
|
101
31
|
].compact)
|
102
32
|
end
|
103
33
|
end
|
104
|
-
|
105
|
-
def render_body
|
106
|
-
tag.div(class: "py-4") { content }
|
107
|
-
end
|
108
|
-
|
109
|
-
def render_footer
|
110
|
-
return unless footer?
|
111
|
-
|
112
|
-
tag.div(class: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2") { footer }
|
113
|
-
end
|
114
|
-
|
115
|
-
def overlay_classes
|
116
|
-
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out"
|
117
|
-
end
|
118
|
-
|
119
|
-
def content_classes
|
120
|
-
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg"
|
121
|
-
end
|
122
34
|
end
|
123
|
-
end
|
35
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module M9sh
|
4
|
+
class DialogContentComponent < BaseComponent
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
renders_one :dialog_header, lambda { |**attrs, &block|
|
8
|
+
DialogHeaderComponent.new(**attrs, &block)
|
9
|
+
}
|
10
|
+
|
11
|
+
renders_one :dialog_footer, lambda { |**attrs, &block|
|
12
|
+
DialogFooterComponent.new(**attrs, &block)
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(show_close: true, **extra_attrs)
|
16
|
+
@show_close = show_close
|
17
|
+
super(**extra_attrs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
# Overlay (backdrop)
|
22
|
+
tag.div(
|
23
|
+
class: overlay_classes,
|
24
|
+
data: {
|
25
|
+
m9sh__dialog_target: "overlay",
|
26
|
+
action: "click->m9sh--dialog#handleBackdropClick"
|
27
|
+
},
|
28
|
+
style: "display: none;",
|
29
|
+
role: "dialog",
|
30
|
+
"aria-modal": "true"
|
31
|
+
) do
|
32
|
+
# Content container
|
33
|
+
tag.div(
|
34
|
+
class: content_classes,
|
35
|
+
data: { m9sh__dialog_target: "content" },
|
36
|
+
role: "document"
|
37
|
+
) do
|
38
|
+
safe_join([
|
39
|
+
render_close_button,
|
40
|
+
dialog_header,
|
41
|
+
render_body,
|
42
|
+
dialog_footer
|
43
|
+
].compact)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def render_close_button
|
51
|
+
return unless @show_close
|
52
|
+
|
53
|
+
tag.button(
|
54
|
+
type: "button",
|
55
|
+
class: "absolute right-4 top-4 rounded-sm 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 data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
|
56
|
+
data: { action: "click->m9sh--dialog#close" },
|
57
|
+
aria: { label: "Close" }
|
58
|
+
) do
|
59
|
+
safe_join([
|
60
|
+
tag.svg(
|
61
|
+
class: "h-4 w-4",
|
62
|
+
xmlns: "http://www.w3.org/2000/svg",
|
63
|
+
viewBox: "0 0 24 24",
|
64
|
+
fill: "none",
|
65
|
+
stroke: "currentColor",
|
66
|
+
"stroke-width": "2",
|
67
|
+
"stroke-linecap": "round",
|
68
|
+
"stroke-linejoin": "round"
|
69
|
+
) do
|
70
|
+
safe_join([
|
71
|
+
tag.line(x1: "18", y1: "6", x2: "6", y2: "18"),
|
72
|
+
tag.line(x1: "6", y1: "6", x2: "18", y2: "18")
|
73
|
+
])
|
74
|
+
end,
|
75
|
+
tag.span("Close", class: "sr-only")
|
76
|
+
])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def render_body
|
81
|
+
return unless content
|
82
|
+
|
83
|
+
tag.div(class: "grid gap-4 py-4") { content }
|
84
|
+
end
|
85
|
+
|
86
|
+
def overlay_classes
|
87
|
+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
88
|
+
end
|
89
|
+
|
90
|
+
def content_classes
|
91
|
+
cn(
|
92
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200",
|
93
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
94
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
95
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
96
|
+
"data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]",
|
97
|
+
"data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]",
|
98
|
+
"sm:rounded-lg"
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module M9sh
|
4
|
+
class DialogFooterComponent < BaseComponent
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
def call
|
8
|
+
tag.div(
|
9
|
+
content,
|
10
|
+
**component_attrs("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2")
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module M9sh
|
4
|
+
class DialogHeaderComponent < BaseComponent
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
renders_one :dialog_title, lambda { |**attrs|
|
8
|
+
DialogTitleComponent.new(**attrs)
|
9
|
+
}
|
10
|
+
|
11
|
+
renders_one :dialog_description, lambda { |**attrs|
|
12
|
+
DialogDescriptionComponent.new(**attrs)
|
13
|
+
}
|
14
|
+
|
15
|
+
def call
|
16
|
+
tag.div(
|
17
|
+
**component_attrs("flex flex-col space-y-1.5 text-center sm:text-left")
|
18
|
+
) do
|
19
|
+
safe_join([
|
20
|
+
dialog_title,
|
21
|
+
dialog_description,
|
22
|
+
content
|
23
|
+
].compact)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module M9sh
|
4
|
+
class DialogTriggerComponent < BaseComponent
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
def initialize(as_child: false, **extra_attrs)
|
8
|
+
@as_child = as_child
|
9
|
+
super(**extra_attrs)
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
# Always wrap content in a span with data attributes so clicks are captured
|
14
|
+
tag.span(
|
15
|
+
content,
|
16
|
+
data: {
|
17
|
+
action: "click->m9sh--dialog#open",
|
18
|
+
m9sh__dialog_target: "trigger"
|
19
|
+
}
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -13,7 +13,7 @@ module M9sh
|
|
13
13
|
transition_leave_start: "transform opacity-100 scale-100",
|
14
14
|
transition_leave_end: "transform opacity-0 scale-95"
|
15
15
|
},
|
16
|
-
class: "absolute z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg hidden",
|
16
|
+
class: "absolute z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg hidden",
|
17
17
|
style: "position: absolute;",
|
18
18
|
**(@extra_attrs || {})
|
19
19
|
) do
|
@@ -30,7 +30,7 @@ module M9sh
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def item_classes
|
33
|
-
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
33
|
+
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -9,7 +9,7 @@ module M9sh
|
|
9
9
|
m9sh__dropdown_menu_target: "trigger"
|
10
10
|
},
|
11
11
|
type: "button",
|
12
|
-
class: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
12
|
+
class: "inline-flex items-center justify-center rounded-md text-sm font-medium text-foreground ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
13
13
|
**(@extra_attrs || {})
|
14
14
|
) do
|
15
15
|
content
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module M9sh
|
4
|
+
class IconComponent < BaseComponent
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
def initialize(
|
8
|
+
name:,
|
9
|
+
size: nil,
|
10
|
+
class_name: nil,
|
11
|
+
**extra_attrs
|
12
|
+
)
|
13
|
+
@name = name
|
14
|
+
@size = size
|
15
|
+
@class_name = class_name
|
16
|
+
super(class_name: class_name, **extra_attrs)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
icon_path = Rails.root.join("app", "assets", "images", "icons", "#{@name}.svg")
|
21
|
+
|
22
|
+
unless File.exist?(icon_path)
|
23
|
+
Rails.logger.warn("Icon not found: #{@name}")
|
24
|
+
return tag.span("[Icon: #{@name}]", class: "text-muted-foreground")
|
25
|
+
end
|
26
|
+
|
27
|
+
svg_content = File.read(icon_path)
|
28
|
+
|
29
|
+
# Simple regex-based attribute modification
|
30
|
+
svg_content = modify_svg_attributes(svg_content)
|
31
|
+
|
32
|
+
svg_content.html_safe
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def modify_svg_attributes(svg)
|
38
|
+
# Extract existing SVG opening tag
|
39
|
+
svg_tag_match = svg.match(/<svg([^>]*)>/)
|
40
|
+
return svg unless svg_tag_match
|
41
|
+
|
42
|
+
existing_attrs = svg_tag_match[1]
|
43
|
+
|
44
|
+
# Build new attributes
|
45
|
+
new_attrs = []
|
46
|
+
|
47
|
+
# Add or update size
|
48
|
+
if @size
|
49
|
+
# Remove existing width/height
|
50
|
+
existing_attrs = existing_attrs.gsub(/\s*(width|height)="[^"]*"/, '')
|
51
|
+
new_attrs << "width=\"#{@size}\""
|
52
|
+
new_attrs << "height=\"#{@size}\""
|
53
|
+
end
|
54
|
+
|
55
|
+
# Add classes
|
56
|
+
if @class_name
|
57
|
+
if existing_attrs =~ /class="([^"]*)"/
|
58
|
+
existing_class = $1
|
59
|
+
merged_classes = "#{existing_class} #{@class_name}".strip
|
60
|
+
existing_attrs = existing_attrs.gsub(/class="[^"]*"/, "class=\"#{merged_classes}\"")
|
61
|
+
else
|
62
|
+
new_attrs << "class=\"#{@class_name}\""
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add extra attributes
|
67
|
+
@extra_attrs.each do |key, value|
|
68
|
+
next if key == :class
|
69
|
+
attr_name = key.to_s.gsub('_', '-')
|
70
|
+
new_attrs << "#{attr_name}=\"#{value}\""
|
71
|
+
end
|
72
|
+
|
73
|
+
# Reconstruct SVG tag
|
74
|
+
all_attrs = [existing_attrs, new_attrs.join(' ')].reject(&:blank?).join(' ')
|
75
|
+
svg.sub(/<svg[^>]*>/, "<svg#{all_attrs.present? ? ' ' + all_attrs : ''}>")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -6,7 +6,7 @@ module M9sh
|
|
6
6
|
|
7
7
|
def call
|
8
8
|
tag.div(
|
9
|
-
**component_attrs("flex-1 flex flex-col
|
9
|
+
**component_attrs("flex-1 flex flex-col p-6 md:px-12 md:py-20 md:rounded-lg md:shadow-xl bg-card overflow-hidden"),
|
10
10
|
data: { slot: "main" }
|
11
11
|
) do
|
12
12
|
content
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module M9sh
|
4
|
+
class MenuComponent < BaseComponent
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
renders_many :items, types: {
|
8
|
+
item: "ItemComponent",
|
9
|
+
separator: "SeparatorComponent"
|
10
|
+
}
|
11
|
+
|
12
|
+
def call
|
13
|
+
tag.div(**component_attrs("py-1 space-y-1")) do
|
14
|
+
safe_join(items)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ItemComponent < BaseComponent
|
19
|
+
include Utilities
|
20
|
+
|
21
|
+
def initialize(href: nil, icon: nil, disabled: false, **extra_attrs)
|
22
|
+
@href = href
|
23
|
+
@icon = icon
|
24
|
+
@disabled = disabled
|
25
|
+
super(**extra_attrs)
|
26
|
+
end
|
27
|
+
|
28
|
+
def call
|
29
|
+
if @href
|
30
|
+
render_link
|
31
|
+
else
|
32
|
+
render_button
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def render_link
|
39
|
+
tag.a(
|
40
|
+
href: @href,
|
41
|
+
**component_attrs(item_classes)
|
42
|
+
) do
|
43
|
+
render_content
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_button
|
48
|
+
tag.button(
|
49
|
+
type: "button",
|
50
|
+
disabled: @disabled,
|
51
|
+
**component_attrs(item_classes)
|
52
|
+
) do
|
53
|
+
render_content
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def render_content
|
58
|
+
if @icon
|
59
|
+
safe_join([
|
60
|
+
@icon,
|
61
|
+
tag.span(content, class: "flex-1")
|
62
|
+
])
|
63
|
+
else
|
64
|
+
content
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def item_classes
|
69
|
+
base = "group flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-sm font-medium transition-colors "
|
70
|
+
base += "text-foreground hover:bg-accent hover:text-accent-foreground "
|
71
|
+
base += "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 "
|
72
|
+
base += @disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
|
73
|
+
base
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class SeparatorComponent < BaseComponent
|
78
|
+
include Utilities
|
79
|
+
|
80
|
+
def call
|
81
|
+
tag.div(**component_attrs("my-1 h-px bg-border"))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|