m9sh 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/.do/app.yaml +25 -0
- data/.dockerignore +51 -0
- data/.idea/.gitignore +8 -0
- data/.idea/aws.xml +17 -0
- data/.idea/hotcdn.iml +189 -0
- data/.idea/jsLibraryMappings.xml +6 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.mise.toml +3 -0
- data/.node-version +1 -0
- data/Dockerfile +84 -0
- data/README.md +230 -0
- data/Rakefile +6 -0
- data/app/components/m9sh/accordion_component.rb +122 -0
- data/app/components/m9sh/alert_component.rb +72 -0
- data/app/components/m9sh/alert_dialog_component.rb +100 -0
- data/app/components/m9sh/avatar_component.rb +65 -0
- data/app/components/m9sh/badge_component.rb +37 -0
- data/app/components/m9sh/base_component.rb +21 -0
- data/app/components/m9sh/breadcrumb_component.rb +100 -0
- data/app/components/m9sh/button_component.rb +54 -0
- data/app/components/m9sh/card_component.rb +90 -0
- data/app/components/m9sh/checkbox_component.rb +36 -0
- data/app/components/m9sh/collapsible_component.rb +47 -0
- data/app/components/m9sh/dialog_component.rb +123 -0
- data/app/components/m9sh/dropdown_menu_component.rb +27 -0
- data/app/components/m9sh/dropdown_menu_content_component.rb +24 -0
- data/app/components/m9sh/dropdown_menu_item_component.rb +36 -0
- data/app/components/m9sh/dropdown_menu_separator_component.rb +9 -0
- data/app/components/m9sh/dropdown_menu_trigger_component.rb +19 -0
- data/app/components/m9sh/hover_card_component.rb +48 -0
- data/app/components/m9sh/input_component.rb +33 -0
- data/app/components/m9sh/label_component.rb +27 -0
- data/app/components/m9sh/main_component.rb +16 -0
- data/app/components/m9sh/navigation_menu_component.rb +95 -0
- data/app/components/m9sh/popover_component.rb +47 -0
- data/app/components/m9sh/progress_component.rb +46 -0
- data/app/components/m9sh/radio_group_component.rb +88 -0
- data/app/components/m9sh/select_component.rb +51 -0
- data/app/components/m9sh/separator_component.rb +40 -0
- data/app/components/m9sh/sheet_component.rb +123 -0
- data/app/components/m9sh/sidebar_component.rb +126 -0
- data/app/components/m9sh/sidebar_group_component.rb +51 -0
- data/app/components/m9sh/sidebar_inset_component.rb +16 -0
- data/app/components/m9sh/sidebar_menu_button_component.rb +56 -0
- data/app/components/m9sh/sidebar_menu_component.rb +16 -0
- data/app/components/m9sh/sidebar_menu_item_component.rb +16 -0
- data/app/components/m9sh/sidebar_provider_component.rb +29 -0
- data/app/components/m9sh/sidebar_trigger_component.rb +44 -0
- data/app/components/m9sh/skeleton_component.rb +32 -0
- data/app/components/m9sh/slider_component.rb +83 -0
- data/app/components/m9sh/spinner_component.rb +46 -0
- data/app/components/m9sh/switch_component.rb +47 -0
- data/app/components/m9sh/table_component.rb +111 -0
- data/app/components/m9sh/tabs_component.rb +92 -0
- data/app/components/m9sh/textarea_component.rb +44 -0
- data/app/components/m9sh/theme_toggle_component.rb +88 -0
- data/app/components/m9sh/toast_component.rb +86 -0
- data/app/components/m9sh/toaster_component.rb +20 -0
- data/app/components/m9sh/toggle_component.rb +64 -0
- data/app/components/m9sh/tooltip_component.rb +48 -0
- data/app/components/m9sh/typography_component.rb +56 -0
- data/app/components/m9sh/utilities.rb +26 -0
- data/app/javascript/controllers/m9sh/accordion_controller.js +110 -0
- data/app/javascript/controllers/m9sh/alert_dialog_controller.js +47 -0
- data/app/javascript/controllers/m9sh/collapsible_controller.js +57 -0
- data/app/javascript/controllers/m9sh/dialog_controller.js +119 -0
- data/app/javascript/controllers/m9sh/dropdown_menu_controller.js +103 -0
- data/app/javascript/controllers/m9sh/hover_card_controller.js +66 -0
- data/app/javascript/controllers/m9sh/navigation_menu_controller.js +219 -0
- data/app/javascript/controllers/m9sh/popover_controller.js +113 -0
- data/app/javascript/controllers/m9sh/radio_controller.js +59 -0
- data/app/javascript/controllers/m9sh/sheet_controller.js +46 -0
- data/app/javascript/controllers/m9sh/sidebar_controller.js +114 -0
- data/app/javascript/controllers/m9sh/sidebar_provider_controller.js +12 -0
- data/app/javascript/controllers/m9sh/slider_controller.js +90 -0
- data/app/javascript/controllers/m9sh/switch_controller.js +33 -0
- data/app/javascript/controllers/m9sh/tabs_controller.js +51 -0
- data/app/javascript/controllers/m9sh/theme_controller.js +50 -0
- data/app/javascript/controllers/m9sh/toast_controller.js +46 -0
- data/app/javascript/controllers/m9sh/toaster_controller.js +70 -0
- data/app/javascript/controllers/m9sh/toggle_controller.js +27 -0
- data/app/javascript/controllers/m9sh/tooltip_controller.js +86 -0
- data/components.json +21 -0
- data/config.ru +6 -0
- data/exe/m9sh +12 -0
- data/fix_namespaces.py +32 -0
- data/fly.toml +30 -0
- data/koyeb.yaml +26 -0
- data/lib/m9sh/cli.rb +234 -0
- data/lib/m9sh/config.rb +114 -0
- data/lib/m9sh/generator.rb +183 -0
- data/lib/m9sh/registry.rb +107 -0
- data/lib/m9sh/registry.yml +384 -0
- data/lib/m9sh/version.rb +5 -0
- data/lib/m9sh.rb +11 -0
- data/package-lock.json +99 -0
- data/package.json +28 -0
- data/pnpm-lock.yaml +75 -0
- data/tailwind.config.js +93 -0
- data/update_namespace.py +73 -0
- metadata +208 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class CollapsibleComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_one :trigger, lambda { |&block|
|
|
8
|
+
tag.div(
|
|
9
|
+
data: { action: "click->m9sh--collapsible#toggle" }
|
|
10
|
+
) { block.call }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
renders_one :collapsible_content, lambda { |&block|
|
|
14
|
+
tag.div(
|
|
15
|
+
class: "overflow-hidden transition-all duration-300 data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
|
|
16
|
+
data: {
|
|
17
|
+
m9sh__collapsible_target: "content"
|
|
18
|
+
}
|
|
19
|
+
) do
|
|
20
|
+
tag.div(
|
|
21
|
+
class: "pt-0",
|
|
22
|
+
data: { m9sh__collapsible_target: "inner" }
|
|
23
|
+
) { block.call }
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def initialize(open: false, **extra_attrs)
|
|
28
|
+
@open = open
|
|
29
|
+
super(**extra_attrs)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
tag.div(
|
|
34
|
+
**component_attrs(""),
|
|
35
|
+
data: {
|
|
36
|
+
controller: "m9sh--collapsible",
|
|
37
|
+
m9sh__collapsible_open_value: @open
|
|
38
|
+
}
|
|
39
|
+
) do
|
|
40
|
+
safe_join([
|
|
41
|
+
trigger,
|
|
42
|
+
collapsible_content
|
|
43
|
+
].compact)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class DialogComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_one :trigger
|
|
8
|
+
renders_one :header
|
|
9
|
+
renders_one :footer
|
|
10
|
+
renders_one :title
|
|
11
|
+
renders_one :description
|
|
12
|
+
|
|
13
|
+
def initialize(open: false, **extra_attrs)
|
|
14
|
+
@open = open
|
|
15
|
+
super(**extra_attrs)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
tag.div(
|
|
20
|
+
data: {
|
|
21
|
+
controller: "m9sh--dialog",
|
|
22
|
+
m9sh__dialog_open_value: @open.to_s
|
|
23
|
+
}
|
|
24
|
+
) do
|
|
25
|
+
safe_join([
|
|
26
|
+
render_trigger,
|
|
27
|
+
render_dialog
|
|
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
|
|
101
|
+
].compact)
|
|
102
|
+
end
|
|
103
|
+
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
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class DropdownMenuComponent < BaseComponent
|
|
5
|
+
attr_reader :align, :side
|
|
6
|
+
|
|
7
|
+
def initialize(align: "end", side: "bottom", **extra_attrs)
|
|
8
|
+
@align = align
|
|
9
|
+
@side = side
|
|
10
|
+
super(**extra_attrs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
tag.div(
|
|
15
|
+
data: {
|
|
16
|
+
controller: "m9sh--dropdown-menu",
|
|
17
|
+
m9sh__dropdown_menu_align_value: align,
|
|
18
|
+
m9sh__dropdown_menu_side_value: side
|
|
19
|
+
},
|
|
20
|
+
class: "relative inline-block text-left",
|
|
21
|
+
**(@extra_attrs || {})
|
|
22
|
+
) do
|
|
23
|
+
content
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class DropdownMenuContentComponent < BaseComponent
|
|
5
|
+
def call
|
|
6
|
+
tag.div(
|
|
7
|
+
data: {
|
|
8
|
+
m9sh__dropdown_menu_target: "content",
|
|
9
|
+
transition_enter: "transition ease-out duration-100",
|
|
10
|
+
transition_enter_start: "transform opacity-0 scale-95",
|
|
11
|
+
transition_enter_end: "transform opacity-100 scale-100",
|
|
12
|
+
transition_leave: "transition ease-in duration-75",
|
|
13
|
+
transition_leave_start: "transform opacity-100 scale-100",
|
|
14
|
+
transition_leave_end: "transform opacity-0 scale-95"
|
|
15
|
+
},
|
|
16
|
+
class: "absolute z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg hidden",
|
|
17
|
+
style: "position: absolute;",
|
|
18
|
+
**(@extra_attrs || {})
|
|
19
|
+
) do
|
|
20
|
+
content
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class DropdownMenuItemComponent < BaseComponent
|
|
5
|
+
attr_reader :href, :method
|
|
6
|
+
|
|
7
|
+
def initialize(href: nil, method: nil, **extra_attrs)
|
|
8
|
+
@href = href
|
|
9
|
+
@method = method
|
|
10
|
+
super(**extra_attrs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
if href
|
|
15
|
+
link_to href, method: method, class: item_classes, **(@extra_attrs || {}) do
|
|
16
|
+
content
|
|
17
|
+
end
|
|
18
|
+
else
|
|
19
|
+
tag.button(
|
|
20
|
+
type: "button",
|
|
21
|
+
class: item_classes,
|
|
22
|
+
data: { action: "click->m9sh--dropdown-menu#hide" },
|
|
23
|
+
**(@extra_attrs || {})
|
|
24
|
+
) do
|
|
25
|
+
content
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
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"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class DropdownMenuTriggerComponent < BaseComponent
|
|
5
|
+
def call
|
|
6
|
+
tag.button(
|
|
7
|
+
data: {
|
|
8
|
+
action: "click->m9sh--dropdown-menu#toggle click@window->m9sh--dropdown-menu#hide",
|
|
9
|
+
m9sh__dropdown_menu_target: "trigger"
|
|
10
|
+
},
|
|
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",
|
|
13
|
+
**(@extra_attrs || {})
|
|
14
|
+
) do
|
|
15
|
+
content
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class HoverCardComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_one :trigger, lambda { |&block|
|
|
8
|
+
tag.div(
|
|
9
|
+
data: {
|
|
10
|
+
action: "mouseenter->m9sh--hover-card#show mouseleave->m9sh--hover-card#hide",
|
|
11
|
+
m9sh__hover_card_target: "trigger"
|
|
12
|
+
}
|
|
13
|
+
) { block.call }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
renders_one :hover_content, lambda { |&block|
|
|
17
|
+
tag.div(
|
|
18
|
+
class: "absolute z-50 w-64 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none hidden 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",
|
|
19
|
+
data: {
|
|
20
|
+
m9sh__hover_card_target: "content",
|
|
21
|
+
action: "mouseenter->m9sh--hover-card#cancelHide mouseleave->m9sh--hover-card#hide"
|
|
22
|
+
}
|
|
23
|
+
) { block.call }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def initialize(open_delay: 200, close_delay: 300, **extra_attrs)
|
|
27
|
+
@open_delay = open_delay
|
|
28
|
+
@close_delay = close_delay
|
|
29
|
+
super(**extra_attrs)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
tag.div(
|
|
34
|
+
**component_attrs("relative inline-block"),
|
|
35
|
+
data: {
|
|
36
|
+
controller: "m9sh--hover-card",
|
|
37
|
+
m9sh__hover_card_open_delay_value: @open_delay,
|
|
38
|
+
m9sh__hover_card_close_delay_value: @close_delay
|
|
39
|
+
}
|
|
40
|
+
) do
|
|
41
|
+
safe_join([
|
|
42
|
+
trigger,
|
|
43
|
+
hover_content
|
|
44
|
+
].compact)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class InputComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(type: "text", name: nil, value: nil, placeholder: nil, disabled: false, **extra_attrs)
|
|
8
|
+
@type = type
|
|
9
|
+
@name = name
|
|
10
|
+
@value = value
|
|
11
|
+
@placeholder = placeholder
|
|
12
|
+
@disabled = disabled
|
|
13
|
+
super(**extra_attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
tag.input(
|
|
18
|
+
**component_attrs(base_classes),
|
|
19
|
+
type: @type,
|
|
20
|
+
name: @name,
|
|
21
|
+
value: @value,
|
|
22
|
+
placeholder: @placeholder,
|
|
23
|
+
disabled: @disabled
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def base_classes
|
|
30
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class LabelComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(for_id: nil, **extra_attrs)
|
|
8
|
+
@for_id = for_id
|
|
9
|
+
super(**extra_attrs)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
tag.label(
|
|
14
|
+
content,
|
|
15
|
+
**component_attrs(base_classes),
|
|
16
|
+
for: @for_id,
|
|
17
|
+
data: { slot: "label" }
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def base_classes
|
|
24
|
+
"flex items-center gap-2 text-sm leading-none font-medium text-foreground select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class MainComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def call
|
|
8
|
+
tag.div(
|
|
9
|
+
**component_attrs("flex-1 flex flex-col m-4 rounded-lg shadow-xl bg-card overflow-hidden border border-border"),
|
|
10
|
+
data: { slot: "main" }
|
|
11
|
+
) do
|
|
12
|
+
content
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class NavigationMenuComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_many :items, "ItemComponent"
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
tag.nav(
|
|
11
|
+
**component_attrs("relative z-10 flex max-w-max flex-1 items-center justify-center"),
|
|
12
|
+
data: { controller: "m9sh--navigation-menu" }
|
|
13
|
+
) do
|
|
14
|
+
tag.div(
|
|
15
|
+
class: "group flex flex-1 list-none items-center justify-center space-x-1",
|
|
16
|
+
role: "group"
|
|
17
|
+
) do
|
|
18
|
+
safe_join(items)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class ItemComponent < BaseComponent
|
|
24
|
+
include Utilities
|
|
25
|
+
|
|
26
|
+
renders_one :trigger, lambda { |text = nil, &block|
|
|
27
|
+
content = block ? block.call : text
|
|
28
|
+
tag.button(
|
|
29
|
+
type: "button",
|
|
30
|
+
class: "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-accent/50",
|
|
31
|
+
data: {
|
|
32
|
+
action: "mouseenter->m9sh--navigation-menu#onTriggerEnter mouseleave->m9sh--navigation-menu#onTriggerLeave click->m9sh--navigation-menu#toggle",
|
|
33
|
+
m9sh__navigation_menu_target: "trigger",
|
|
34
|
+
state: "closed"
|
|
35
|
+
}
|
|
36
|
+
) do
|
|
37
|
+
safe_join([
|
|
38
|
+
content,
|
|
39
|
+
tag.svg(
|
|
40
|
+
class: "relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180",
|
|
41
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
42
|
+
width: "24",
|
|
43
|
+
height: "24",
|
|
44
|
+
viewBox: "0 0 24 24",
|
|
45
|
+
fill: "none",
|
|
46
|
+
stroke: "currentColor",
|
|
47
|
+
stroke_width: "2",
|
|
48
|
+
stroke_linecap: "round",
|
|
49
|
+
stroke_linejoin: "round",
|
|
50
|
+
aria: { hidden: "true" }
|
|
51
|
+
) do
|
|
52
|
+
tag.path(d: "m6 9 6 6 6-6")
|
|
53
|
+
end
|
|
54
|
+
])
|
|
55
|
+
end
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
renders_one :menu_content, lambda { |&block|
|
|
59
|
+
tag.div(
|
|
60
|
+
class: "absolute left-1/2 top-full -translate-x-1/2 w-full md:w-auto",
|
|
61
|
+
style: "display: none;",
|
|
62
|
+
data: {
|
|
63
|
+
action: "mouseenter->m9sh--navigation-menu#onContentEnter mouseleave->m9sh--navigation-menu#onContentLeave",
|
|
64
|
+
m9sh__navigation_menu_target: "content",
|
|
65
|
+
state: "closed"
|
|
66
|
+
}
|
|
67
|
+
) do
|
|
68
|
+
tag.div(
|
|
69
|
+
class: "relative mt-1.5 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md 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",
|
|
70
|
+
data: { state: "closed" }
|
|
71
|
+
) do
|
|
72
|
+
block.call
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
renders_one :link, lambda { |href:, &block|
|
|
78
|
+
tag.a(
|
|
79
|
+
href: href,
|
|
80
|
+
class: "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50"
|
|
81
|
+
) { block.call }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def call
|
|
85
|
+
tag.div(class: "relative", data: { m9sh__navigation_menu_target: "item" }) do
|
|
86
|
+
if link?
|
|
87
|
+
link
|
|
88
|
+
else
|
|
89
|
+
safe_join([trigger, menu_content].compact)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class PopoverComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_one :trigger, lambda { |&block|
|
|
8
|
+
tag.div(
|
|
9
|
+
data: {
|
|
10
|
+
action: "click->m9sh--popover#toggle",
|
|
11
|
+
m9sh__popover_target: "trigger"
|
|
12
|
+
}
|
|
13
|
+
) { block.call }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
renders_one :popover_content, lambda { |&block|
|
|
17
|
+
tag.div(
|
|
18
|
+
class: "absolute z-50 w-72 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none hidden 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
19
|
+
data: {
|
|
20
|
+
m9sh__popover_target: "content"
|
|
21
|
+
}
|
|
22
|
+
) { block.call }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def initialize(align: "center", side: "bottom", **extra_attrs)
|
|
26
|
+
@align = align
|
|
27
|
+
@side = side
|
|
28
|
+
super(**extra_attrs)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call
|
|
32
|
+
tag.div(
|
|
33
|
+
**component_attrs("relative inline-block"),
|
|
34
|
+
data: {
|
|
35
|
+
controller: "m9sh--popover",
|
|
36
|
+
m9sh__popover_align_value: @align,
|
|
37
|
+
m9sh__popover_side_value: @side
|
|
38
|
+
}
|
|
39
|
+
) do
|
|
40
|
+
safe_join([
|
|
41
|
+
trigger,
|
|
42
|
+
popover_content
|
|
43
|
+
].compact)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class ProgressComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(value: 0, max: 100, **extra_attrs)
|
|
8
|
+
@value = value.to_f
|
|
9
|
+
@max = max.to_f
|
|
10
|
+
super(**extra_attrs)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
tag.div(
|
|
15
|
+
**component_attrs(base_classes),
|
|
16
|
+
role: "progressbar",
|
|
17
|
+
"aria-valuemin": "0",
|
|
18
|
+
"aria-valuemax": @max,
|
|
19
|
+
"aria-valuenow": @value,
|
|
20
|
+
data: { slot: "progress" }
|
|
21
|
+
) do
|
|
22
|
+
tag.div(
|
|
23
|
+
class: indicator_classes,
|
|
24
|
+
style: "transform: translateX(-#{100 - percentage}%)",
|
|
25
|
+
data: { slot: "progress-indicator" }
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def base_classes
|
|
33
|
+
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def indicator_classes
|
|
37
|
+
"bg-primary h-full w-full flex-1 transition-all"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def percentage
|
|
41
|
+
return 0 if @max.zero?
|
|
42
|
+
|
|
43
|
+
[(@value / @max * 100).round(2), 100].min
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|