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,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class SliderComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(min: 0, max: 100, step: 1, value: 50, name: nil, **extra_attrs)
|
|
8
|
+
@min = min
|
|
9
|
+
@max = max
|
|
10
|
+
@step = step
|
|
11
|
+
@value = value
|
|
12
|
+
@name = name
|
|
13
|
+
super(**extra_attrs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
tag.div(
|
|
18
|
+
**component_attrs("relative flex w-full touch-none select-none items-center"),
|
|
19
|
+
data: {
|
|
20
|
+
controller: "m9sh--slider",
|
|
21
|
+
m9sh__slider_min_value: @min,
|
|
22
|
+
m9sh__slider_max_value: @max,
|
|
23
|
+
m9sh__slider_step_value: @step,
|
|
24
|
+
m9sh__slider_value_value: @value
|
|
25
|
+
}
|
|
26
|
+
) do
|
|
27
|
+
safe_join([
|
|
28
|
+
render_track,
|
|
29
|
+
render_range,
|
|
30
|
+
render_thumb,
|
|
31
|
+
render_hidden_input
|
|
32
|
+
])
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def render_track
|
|
39
|
+
tag.span(
|
|
40
|
+
class: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary",
|
|
41
|
+
data: {
|
|
42
|
+
m9sh__slider_target: "track",
|
|
43
|
+
action: "click->m9sh--slider#handleTrackClick"
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def render_range
|
|
49
|
+
tag.span(
|
|
50
|
+
class: "absolute h-full bg-primary",
|
|
51
|
+
data: { m9sh__slider_target: "range" }
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render_thumb
|
|
56
|
+
tag.span(
|
|
57
|
+
class: "block h-5 w-5 rounded-full border-2 border-primary bg-background 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 cursor-pointer absolute top-1/2 -translate-y-1/2 -translate-x-1/2",
|
|
58
|
+
data: {
|
|
59
|
+
m9sh__slider_target: "thumb",
|
|
60
|
+
action: "mousedown->m9sh--slider#startDrag touchstart->m9sh--slider#startDrag"
|
|
61
|
+
},
|
|
62
|
+
tabindex: "0",
|
|
63
|
+
role: "slider",
|
|
64
|
+
aria: {
|
|
65
|
+
valuenow: @value,
|
|
66
|
+
valuemin: @min,
|
|
67
|
+
valuemax: @max
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def render_hidden_input
|
|
73
|
+
return unless @name
|
|
74
|
+
|
|
75
|
+
tag.input(
|
|
76
|
+
type: "hidden",
|
|
77
|
+
name: @name,
|
|
78
|
+
value: @value,
|
|
79
|
+
data: { m9sh__slider_target: "input" }
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class SpinnerComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
SIZES = {
|
|
8
|
+
sm: "size-3",
|
|
9
|
+
default: "size-4",
|
|
10
|
+
md: "size-6",
|
|
11
|
+
lg: "size-8"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(size: :default, **extra_attrs)
|
|
15
|
+
@size = size
|
|
16
|
+
super(**extra_attrs)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
tag.svg(
|
|
21
|
+
**component_attrs(class_names("animate-spin", size_classes)),
|
|
22
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
23
|
+
width: "24",
|
|
24
|
+
height: "24",
|
|
25
|
+
viewBox: "0 0 24 24",
|
|
26
|
+
fill: "none",
|
|
27
|
+
stroke: "currentColor",
|
|
28
|
+
stroke_width: "2",
|
|
29
|
+
stroke_linecap: "round",
|
|
30
|
+
stroke_linejoin: "round",
|
|
31
|
+
role: "status",
|
|
32
|
+
aria: { label: "Loading" }
|
|
33
|
+
) do
|
|
34
|
+
safe_join([
|
|
35
|
+
tag.path(d: "M21 12a9 9 0 1 1-6.219-8.56")
|
|
36
|
+
])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def size_classes
|
|
43
|
+
SIZES[@size] || SIZES[:default]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class SwitchComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(name: nil, checked: false, disabled: false, **extra_attrs)
|
|
8
|
+
@name = name
|
|
9
|
+
@checked = checked
|
|
10
|
+
@disabled = disabled
|
|
11
|
+
super(**extra_attrs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
tag.button(
|
|
16
|
+
**component_attrs(base_classes),
|
|
17
|
+
type: "button",
|
|
18
|
+
role: "switch",
|
|
19
|
+
"aria-checked": @checked.to_s,
|
|
20
|
+
data: {
|
|
21
|
+
controller: "m9sh--switch",
|
|
22
|
+
action: "click->m9sh--switch#toggle",
|
|
23
|
+
m9sh__switch_checked_value: @checked.to_s
|
|
24
|
+
},
|
|
25
|
+
disabled: @disabled
|
|
26
|
+
) do
|
|
27
|
+
tag.span(
|
|
28
|
+
class: thumb_classes,
|
|
29
|
+
data: { m9sh__switch_target: "thumb" }
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def base_classes
|
|
37
|
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def thumb_classes
|
|
41
|
+
cn(
|
|
42
|
+
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform",
|
|
43
|
+
@checked ? "translate-x-4" : "translate-x-0"
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class TableComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_one :header, "M9sh::TableComponent::HeaderComponent"
|
|
8
|
+
renders_one :body, "M9sh::TableComponent::BodyComponent"
|
|
9
|
+
renders_one :footer, "M9sh::TableComponent::FooterComponent"
|
|
10
|
+
renders_one :caption, "M9sh::TableComponent::CaptionComponent"
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
tag.div(
|
|
14
|
+
class: "relative w-full overflow-auto"
|
|
15
|
+
) do
|
|
16
|
+
tag.table(
|
|
17
|
+
**component_attrs("w-full caption-bottom text-sm")
|
|
18
|
+
) do
|
|
19
|
+
concat(header) if header?
|
|
20
|
+
concat(body) if body?
|
|
21
|
+
concat(footer) if footer?
|
|
22
|
+
concat(caption) if caption?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class HeaderComponent < BaseComponent
|
|
28
|
+
include Utilities
|
|
29
|
+
|
|
30
|
+
def call
|
|
31
|
+
tag.thead(
|
|
32
|
+
**component_attrs("[&_tr]:border-b")
|
|
33
|
+
) do
|
|
34
|
+
content
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class BodyComponent < BaseComponent
|
|
40
|
+
include Utilities
|
|
41
|
+
|
|
42
|
+
def call
|
|
43
|
+
tag.tbody(
|
|
44
|
+
**component_attrs("[&_tr:last-child]:border-0")
|
|
45
|
+
) do
|
|
46
|
+
content
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class FooterComponent < BaseComponent
|
|
52
|
+
include Utilities
|
|
53
|
+
|
|
54
|
+
def call
|
|
55
|
+
tag.tfoot(
|
|
56
|
+
**component_attrs("border-t bg-muted font-medium [&>tr]:last:border-b-0")
|
|
57
|
+
) do
|
|
58
|
+
content
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class RowComponent < BaseComponent
|
|
64
|
+
include Utilities
|
|
65
|
+
|
|
66
|
+
def call
|
|
67
|
+
tag.tr(
|
|
68
|
+
**component_attrs("border-b transition-colors hover:bg-muted data-[state=selected]:bg-muted")
|
|
69
|
+
) do
|
|
70
|
+
content
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class HeadComponent < BaseComponent
|
|
76
|
+
include Utilities
|
|
77
|
+
|
|
78
|
+
def call
|
|
79
|
+
tag.th(
|
|
80
|
+
**component_attrs("h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0")
|
|
81
|
+
) do
|
|
82
|
+
content
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class CellComponent < BaseComponent
|
|
88
|
+
include Utilities
|
|
89
|
+
|
|
90
|
+
def call
|
|
91
|
+
tag.td(
|
|
92
|
+
**component_attrs("p-4 align-middle [&:has([role=checkbox])]:pr-0")
|
|
93
|
+
) do
|
|
94
|
+
content
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class CaptionComponent < BaseComponent
|
|
100
|
+
include Utilities
|
|
101
|
+
|
|
102
|
+
def call
|
|
103
|
+
tag.caption(
|
|
104
|
+
**component_attrs("mt-4 text-sm text-muted-foreground")
|
|
105
|
+
) do
|
|
106
|
+
content
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class TabsComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
renders_many :tabs, "TabComponent"
|
|
8
|
+
|
|
9
|
+
def initialize(default_value: nil, **extra_attrs)
|
|
10
|
+
@default_value = default_value
|
|
11
|
+
super(**extra_attrs)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
tag.div(
|
|
16
|
+
**component_attrs(base_classes),
|
|
17
|
+
data: {
|
|
18
|
+
controller: "m9sh--tabs",
|
|
19
|
+
m9sh__tabs_default_value: @default_value
|
|
20
|
+
}
|
|
21
|
+
) do
|
|
22
|
+
safe_join([
|
|
23
|
+
render_tab_list,
|
|
24
|
+
render_tab_panels
|
|
25
|
+
])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def base_classes
|
|
32
|
+
"w-full"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def render_tab_list
|
|
36
|
+
tag.div(
|
|
37
|
+
class: "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
|
38
|
+
role: "tablist",
|
|
39
|
+
data: { m9sh__tabs_target: "list" }
|
|
40
|
+
) do
|
|
41
|
+
tabs.each_with_index.map do |tab, index|
|
|
42
|
+
tag.button(
|
|
43
|
+
tab.label,
|
|
44
|
+
type: "button",
|
|
45
|
+
role: "tab",
|
|
46
|
+
class: "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
|
|
47
|
+
data: {
|
|
48
|
+
m9sh__tabs_target: "trigger",
|
|
49
|
+
action: "click->m9sh--tabs#selectTab",
|
|
50
|
+
value: tab.value
|
|
51
|
+
},
|
|
52
|
+
"aria-selected": @default_value == tab.value ? "true" : "false"
|
|
53
|
+
)
|
|
54
|
+
end.join.html_safe
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def render_tab_panels
|
|
59
|
+
tag.div(class: "mt-2") do
|
|
60
|
+
tabs.map do |tab|
|
|
61
|
+
tag.div(
|
|
62
|
+
tab.panel,
|
|
63
|
+
role: "tabpanel",
|
|
64
|
+
class: "ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
65
|
+
data: {
|
|
66
|
+
m9sh__tabs_target: "panel",
|
|
67
|
+
value: tab.value
|
|
68
|
+
},
|
|
69
|
+
style: @default_value == tab.value ? "" : "display: none;"
|
|
70
|
+
)
|
|
71
|
+
end.join.html_safe
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class TabComponent < BaseComponent
|
|
76
|
+
attr_reader :value, :label
|
|
77
|
+
|
|
78
|
+
renders_one :panel
|
|
79
|
+
|
|
80
|
+
def initialize(value:, label:, **extra_attrs)
|
|
81
|
+
@value = value
|
|
82
|
+
@label = label
|
|
83
|
+
super(**extra_attrs)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def call
|
|
87
|
+
# Tabs are rendered by parent component
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class TextareaComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(
|
|
8
|
+
name: nil,
|
|
9
|
+
value: nil,
|
|
10
|
+
placeholder: nil,
|
|
11
|
+
rows: 3,
|
|
12
|
+
disabled: false,
|
|
13
|
+
readonly: false,
|
|
14
|
+
**extra_attrs
|
|
15
|
+
)
|
|
16
|
+
@name = name
|
|
17
|
+
@value = value
|
|
18
|
+
@placeholder = placeholder
|
|
19
|
+
@rows = rows
|
|
20
|
+
@disabled = disabled
|
|
21
|
+
@readonly = readonly
|
|
22
|
+
super(**extra_attrs)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
tag.textarea(
|
|
27
|
+
@value,
|
|
28
|
+
**component_attrs(base_classes),
|
|
29
|
+
name: @name,
|
|
30
|
+
placeholder: @placeholder,
|
|
31
|
+
rows: @rows,
|
|
32
|
+
disabled: @disabled,
|
|
33
|
+
readonly: @readonly,
|
|
34
|
+
data: { slot: "textarea" }
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def base_classes
|
|
41
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base text-foreground shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class ThemeToggleComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
def initialize(**extra_attrs)
|
|
8
|
+
super(**extra_attrs)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
tag.div(
|
|
13
|
+
**component_attrs("inline-flex items-center gap-2"),
|
|
14
|
+
data: { controller: "m9sh--theme" }
|
|
15
|
+
) do
|
|
16
|
+
tag.button(
|
|
17
|
+
class: button_classes,
|
|
18
|
+
data: { action: "click->m9sh--theme#toggle" },
|
|
19
|
+
aria: { label: "Toggle theme" }
|
|
20
|
+
) do
|
|
21
|
+
safe_join([
|
|
22
|
+
sun_icon,
|
|
23
|
+
moon_icon,
|
|
24
|
+
tag.span("Toggle theme", class: "sr-only")
|
|
25
|
+
])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def button_classes
|
|
33
|
+
cn(
|
|
34
|
+
"inline-flex items-center justify-center",
|
|
35
|
+
"rounded-md text-sm font-medium",
|
|
36
|
+
"h-9 w-9",
|
|
37
|
+
"border border-input bg-background",
|
|
38
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
39
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
40
|
+
"transition-colors"
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sun_icon
|
|
45
|
+
tag.svg(
|
|
46
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
47
|
+
width: "16",
|
|
48
|
+
height: "16",
|
|
49
|
+
viewBox: "0 0 24 24",
|
|
50
|
+
fill: "none",
|
|
51
|
+
stroke: "currentColor",
|
|
52
|
+
stroke_width: "2",
|
|
53
|
+
stroke_linecap: "round",
|
|
54
|
+
stroke_linejoin: "round",
|
|
55
|
+
class: "h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
|
|
56
|
+
) do
|
|
57
|
+
safe_join([
|
|
58
|
+
tag.circle(cx: "12", cy: "12", r: "4"),
|
|
59
|
+
tag.path(d: "M12 2v2"),
|
|
60
|
+
tag.path(d: "M12 20v2"),
|
|
61
|
+
tag.path(d: "m4.93 4.93 1.41 1.41"),
|
|
62
|
+
tag.path(d: "m17.66 17.66 1.41 1.41"),
|
|
63
|
+
tag.path(d: "M2 12h2"),
|
|
64
|
+
tag.path(d: "M20 12h2"),
|
|
65
|
+
tag.path(d: "m6.34 17.66-1.41 1.41"),
|
|
66
|
+
tag.path(d: "m19.07 4.93-1.41 1.41")
|
|
67
|
+
])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def moon_icon
|
|
72
|
+
tag.svg(
|
|
73
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
74
|
+
width: "16",
|
|
75
|
+
height: "16",
|
|
76
|
+
viewBox: "0 0 24 24",
|
|
77
|
+
fill: "none",
|
|
78
|
+
stroke: "currentColor",
|
|
79
|
+
stroke_width: "2",
|
|
80
|
+
stroke_linecap: "round",
|
|
81
|
+
stroke_linejoin: "round",
|
|
82
|
+
class: "absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
|
|
83
|
+
) do
|
|
84
|
+
tag.path(d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class ToastComponent < BaseComponent
|
|
5
|
+
attr_reader :variant, :title, :description, :duration
|
|
6
|
+
|
|
7
|
+
VARIANTS = {
|
|
8
|
+
default: "bg-background text-foreground border",
|
|
9
|
+
success: "bg-green-50 text-green-900 border",
|
|
10
|
+
error: "bg-red-50 text-red-900 border",
|
|
11
|
+
warning: "bg-yellow-50 text-yellow-900 border",
|
|
12
|
+
info: "bg-blue-50 text-blue-900 border"
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(title:, description: nil, variant: :default, duration: 5000, **extra_attrs)
|
|
16
|
+
@title = title
|
|
17
|
+
@description = description
|
|
18
|
+
@variant = variant.to_sym
|
|
19
|
+
@duration = duration
|
|
20
|
+
super(**extra_attrs)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
tag.div(
|
|
25
|
+
data: {
|
|
26
|
+
controller: "m9sh--toast",
|
|
27
|
+
m9sh__toast_duration_value: duration,
|
|
28
|
+
transition_enter: "transition ease-out duration-300",
|
|
29
|
+
transition_enter_start: "transform translate-x-full opacity-0",
|
|
30
|
+
transition_enter_end: "transform translate-x-0 opacity-100",
|
|
31
|
+
transition_leave: "transition ease-in duration-200",
|
|
32
|
+
transition_leave_start: "transform translate-x-0 opacity-100",
|
|
33
|
+
transition_leave_end: "transform translate-x-full opacity-0"
|
|
34
|
+
},
|
|
35
|
+
class: "pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 #{variant_classes}",
|
|
36
|
+
**(@extra_attrs || {})
|
|
37
|
+
) do
|
|
38
|
+
tag.div(class: "p-4") do
|
|
39
|
+
safe_join([
|
|
40
|
+
title_element,
|
|
41
|
+
description_element,
|
|
42
|
+
close_button
|
|
43
|
+
].compact)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def variant_classes
|
|
51
|
+
VARIANTS[@variant] || VARIANTS[:default]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def title_element
|
|
55
|
+
return unless title
|
|
56
|
+
|
|
57
|
+
tag.p(class: "text-sm font-semibold") { title }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def description_element
|
|
61
|
+
return unless description
|
|
62
|
+
|
|
63
|
+
tag.p(class: "mt-1 text-sm opacity-90") { description }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def close_button
|
|
67
|
+
tag.button(
|
|
68
|
+
type: "button",
|
|
69
|
+
data: { action: "click->m9sh--toast#close" },
|
|
70
|
+
class: "absolute top-2 right-2 inline-flex rounded-md p-1.5 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-300"
|
|
71
|
+
) do
|
|
72
|
+
tag.span(class: "sr-only") { "Dismiss" }
|
|
73
|
+
tag.svg(
|
|
74
|
+
class: "h-5 w-5",
|
|
75
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
76
|
+
viewBox: "0 0 20 20",
|
|
77
|
+
fill: "currentColor"
|
|
78
|
+
) do
|
|
79
|
+
tag.path(
|
|
80
|
+
d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class ToasterComponent < BaseComponent
|
|
5
|
+
def call
|
|
6
|
+
tag.div(
|
|
7
|
+
id: "toast-container",
|
|
8
|
+
data: { controller: "m9sh--toaster" },
|
|
9
|
+
class: "fixed inset-0 z-50 pointer-events-none",
|
|
10
|
+
"aria-live": "polite",
|
|
11
|
+
**(@extra_attrs || {})
|
|
12
|
+
) do
|
|
13
|
+
tag.div(
|
|
14
|
+
class: "fixed bottom-0 right-0 flex flex-col gap-2 p-4 sm:bottom-4 sm:right-4 sm:flex-col sm:items-end",
|
|
15
|
+
data: { m9sh__toaster_target: "container" }
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M9sh
|
|
4
|
+
class ToggleComponent < BaseComponent
|
|
5
|
+
include Utilities
|
|
6
|
+
|
|
7
|
+
VARIANTS = {
|
|
8
|
+
default: "bg-transparent",
|
|
9
|
+
outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
SIZES = {
|
|
13
|
+
sm: "h-8 px-1.5 min-w-8",
|
|
14
|
+
default: "h-9 px-2 min-w-9",
|
|
15
|
+
lg: "h-10 px-2.5 min-w-10"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def initialize(
|
|
19
|
+
variant: :default,
|
|
20
|
+
size: :default,
|
|
21
|
+
pressed: false,
|
|
22
|
+
disabled: false,
|
|
23
|
+
**extra_attrs
|
|
24
|
+
)
|
|
25
|
+
@variant = variant.to_sym
|
|
26
|
+
@size = size.to_sym
|
|
27
|
+
@pressed = pressed
|
|
28
|
+
@disabled = disabled
|
|
29
|
+
super(**extra_attrs)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
tag.button(
|
|
34
|
+
content,
|
|
35
|
+
**component_attrs(class_names(base_classes, variant_classes, size_classes)),
|
|
36
|
+
type: "button",
|
|
37
|
+
role: "button",
|
|
38
|
+
"aria-pressed": @pressed.to_s,
|
|
39
|
+
disabled: @disabled,
|
|
40
|
+
data: {
|
|
41
|
+
slot: "toggle",
|
|
42
|
+
state: @pressed ? "on" : "off",
|
|
43
|
+
controller: "m9sh--toggle",
|
|
44
|
+
action: "click->m9sh--toggle#toggle",
|
|
45
|
+
m9sh__toggle_pressed_value: @pressed.to_s
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def base_classes
|
|
53
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def variant_classes
|
|
57
|
+
VARIANTS[@variant] || VARIANTS[:default]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def size_classes
|
|
61
|
+
SIZES[@size] || SIZES[:default]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|