ruby_ui 1.0.0 → 1.0.2
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/README.md +5 -0
- data/lib/generators/ruby_ui/component/all_generator.rb +22 -0
- data/lib/generators/ruby_ui/component_generator.rb +4 -3
- data/lib/generators/ruby_ui/install/install_generator.rb +1 -7
- data/lib/generators/ruby_ui/install/templates/tailwind.css.erb +0 -3
- data/lib/generators/ruby_ui/javascript_utils.rb +4 -0
- data/lib/ruby_ui/alert_dialog/alert_dialog_content.rb +2 -3
- data/lib/ruby_ui/button/button.rb +32 -16
- data/lib/ruby_ui/checkbox/checkbox.rb +7 -1
- data/lib/ruby_ui/combobox/combobox.rb +3 -2
- data/lib/ruby_ui/combobox/combobox_checkbox.rb +4 -2
- data/lib/ruby_ui/combobox/combobox_controller.js +4 -4
- data/lib/ruby_ui/combobox/combobox_item.rb +2 -1
- data/lib/ruby_ui/combobox/combobox_radio.rb +8 -1
- data/lib/ruby_ui/combobox/combobox_search_input.rb +11 -5
- data/lib/ruby_ui/combobox/combobox_toggle_all_checkbox.rb +2 -1
- data/lib/ruby_ui/combobox/combobox_trigger.rb +7 -1
- data/lib/ruby_ui/command/command_controller.js +0 -1
- data/lib/ruby_ui/dialog/dialog_content.rb +2 -3
- data/lib/ruby_ui/dialog/dialog_controller.js +1 -1
- data/lib/ruby_ui/dropdown_menu/dropdown_menu.rb +9 -0
- data/lib/ruby_ui/dropdown_menu/dropdown_menu_content.rb +17 -2
- data/lib/ruby_ui/dropdown_menu/dropdown_menu_controller.js +43 -14
- data/lib/ruby_ui/form/form_field_label.rb +7 -1
- data/lib/ruby_ui/input/input.rb +8 -1
- data/lib/ruby_ui/link/link.rb +32 -16
- data/lib/ruby_ui/radio_button/radio_button.rb +3 -1
- data/lib/ruby_ui/select/select_item.rb +14 -5
- data/lib/ruby_ui/select/select_trigger.rb +9 -4
- data/lib/ruby_ui/sidebar/collapsible_sidebar.rb +99 -0
- data/lib/ruby_ui/sidebar/mobile_sidebar.rb +45 -0
- data/lib/ruby_ui/sidebar/non_collapsible_sidebar.rb +17 -0
- data/lib/ruby_ui/sidebar/sidebar.rb +29 -0
- data/lib/ruby_ui/sidebar/sidebar_content.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_controller.js +67 -0
- data/lib/ruby_ui/sidebar/sidebar_footer.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_group.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_group_action.rb +33 -0
- data/lib/ruby_ui/sidebar/sidebar_group_content.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_group_label.rb +26 -0
- data/lib/ruby_ui/sidebar/sidebar_header.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_input.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_inset.rb +23 -0
- data/lib/ruby_ui/sidebar/sidebar_menu.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_action.rb +48 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_badge.rb +30 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_button.rb +63 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_item.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_skeleton.rb +36 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_sub.rb +24 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_sub_button.rb +50 -0
- data/lib/ruby_ui/sidebar/sidebar_menu_sub_item.rb +9 -0
- data/lib/ruby_ui/sidebar/sidebar_rail.rb +36 -0
- data/lib/ruby_ui/sidebar/sidebar_separator.rb +20 -0
- data/lib/ruby_ui/sidebar/sidebar_trigger.rb +42 -0
- data/lib/ruby_ui/sidebar/sidebar_wrapper.rb +24 -0
- data/lib/ruby_ui/switch/switch.rb +12 -2
- data/lib/ruby_ui/table/table_footer.rb +1 -1
- data/lib/ruby_ui/table/table_row.rb +1 -1
- data/lib/ruby_ui/tabs/tabs_trigger.rb +7 -1
- data/lib/ruby_ui/textarea/textarea.rb +8 -1
- data/lib/ruby_ui/theme_toggle/set_dark_mode.rb +16 -0
- data/lib/ruby_ui/theme_toggle/set_light_mode.rb +16 -0
- data/lib/ruby_ui/theme_toggle/theme_toggle.rb +0 -32
- data/lib/ruby_ui/tooltip/tooltip_controller.js +5 -4
- data/lib/ruby_ui.rb +1 -1
- metadata +34 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0caf3d89b3375e334dc257510c34bf7f168b295534dc8bf2bfc4dde95d3a6a04
|
4
|
+
data.tar.gz: 55dcc86d6feb87270a5c82915fa635e752b1a7ae1a91d5b30e12af61f91fc13f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abcfe605c2dc17b11f2c3cc8dbe5e2db054c95f5e0dac319c883762add2985fb0b7ddd8547c8148504e75ef83971ee7c55c874bffa3505deefe50eb7e8e09f6a
|
7
|
+
data.tar.gz: fbe88cc5c6c8e327209588628340d1225773dcd0a6ce064a657ccaa9eaa163c98d4d2280e966932bdf2c9e7da910ab0a6913cb65093cb4091c026690f88039ac
|
data/README.md
CHANGED
@@ -26,6 +26,9 @@ Use this as a reference to build your own component libraries.
|
|
26
26
|
|
27
27
|
## Installation 🚀
|
28
28
|
|
29
|
+
> [!NOTE]
|
30
|
+
> RubyUI 1.0 requires Ruby 3.2 or later
|
31
|
+
|
29
32
|
### 1. Install the gem
|
30
33
|
|
31
34
|
```bash
|
@@ -52,6 +55,8 @@ You can generate your components using `ruby_ui:component` generator.
|
|
52
55
|
bin/rails g ruby_ui:component Accordion
|
53
56
|
```
|
54
57
|
|
58
|
+
You also can generate all components using `ruby_ui:component:all` generator
|
59
|
+
|
55
60
|
## Documentation 📖
|
56
61
|
|
57
62
|
Visit https://rubyui.com/docs/introduction to view the full documentation, including:
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RubyUI
|
2
|
+
module Generators
|
3
|
+
module Component
|
4
|
+
class AllGenerator < Rails::Generators::Base
|
5
|
+
namespace "ruby_ui:component:all"
|
6
|
+
|
7
|
+
source_root File.expand_path("../../../ruby_ui", __dir__)
|
8
|
+
class_option :force, type: :boolean, default: false
|
9
|
+
|
10
|
+
def generate_components
|
11
|
+
say "Generating all components..."
|
12
|
+
|
13
|
+
Dir.children(self.class.source_root).each do |folder_name|
|
14
|
+
next if folder_name.ends_with?(".rb")
|
15
|
+
|
16
|
+
run "bin/rails generate ruby_ui:component #{folder_name} --force #{options["force"]}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -8,6 +8,7 @@ module RubyUI
|
|
8
8
|
|
9
9
|
source_root File.expand_path("../../ruby_ui", __dir__)
|
10
10
|
argument :component_name, type: :string, required: true
|
11
|
+
class_option :force, type: :boolean, default: false
|
11
12
|
|
12
13
|
def generate_component
|
13
14
|
if component_not_found?
|
@@ -23,7 +24,7 @@ module RubyUI
|
|
23
24
|
|
24
25
|
components_file_paths.each do |file_path|
|
25
26
|
component_file_name = file_path.split("/").last
|
26
|
-
copy_file file_path, Rails.root.join("app/components/ruby_ui", component_folder_name, component_file_name)
|
27
|
+
copy_file file_path, Rails.root.join("app/components/ruby_ui", component_folder_name, component_file_name), force: options["force"]
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -34,7 +35,7 @@ module RubyUI
|
|
34
35
|
|
35
36
|
js_controller_file_paths.each do |file_path|
|
36
37
|
controller_file_name = file_path.split("/").last
|
37
|
-
copy_file file_path, Rails.root.join("app/javascript/controllers/ruby_ui", controller_file_name)
|
38
|
+
copy_file file_path, Rails.root.join("app/javascript/controllers/ruby_ui", controller_file_name), force: options["force"]
|
38
39
|
end
|
39
40
|
|
40
41
|
# Importmap doesn't have controller manifest, instead it uses `eagerLoadControllersFrom("controllers", application)`
|
@@ -68,7 +69,7 @@ module RubyUI
|
|
68
69
|
|
69
70
|
def install_components_dependencies(components)
|
70
71
|
components&.each do |component|
|
71
|
-
run "bin/rails generate ruby_ui:component #{component}"
|
72
|
+
run "bin/rails generate ruby_ui:component #{component} --force #{options["force"]}"
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
@@ -42,7 +42,7 @@ module RubyUI
|
|
42
42
|
|
43
43
|
def add_ruby_ui_module_to_components_base
|
44
44
|
say "Adding RubyUI Kit to Components::Base"
|
45
|
-
insert_into_file Rails.root.join("app/components/base.rb"), after: "
|
45
|
+
insert_into_file Rails.root.join("app/components/base.rb"), after: "class Components::Base < Phlex::HTML" do
|
46
46
|
"\n include RubyUI"
|
47
47
|
end
|
48
48
|
end
|
@@ -62,12 +62,6 @@ module RubyUI
|
|
62
62
|
def install_tailwind_plugins
|
63
63
|
say "Installing tw-animate-css plugin"
|
64
64
|
install_js_package("tw-animate-css")
|
65
|
-
|
66
|
-
say "Installing @tailwindcss/forms plugin"
|
67
|
-
install_js_package("@tailwindcss/forms")
|
68
|
-
|
69
|
-
say "Installing @tailwindcss/typography plugin"
|
70
|
-
install_js_package("@tailwindcss/typography")
|
71
65
|
end
|
72
66
|
|
73
67
|
def add_ruby_ui_base
|
@@ -8,6 +8,8 @@ module RubyUI
|
|
8
8
|
run "yarn add #{package}"
|
9
9
|
elsif using_npm?
|
10
10
|
run "npm install #{package}"
|
11
|
+
elsif using_pnpm?
|
12
|
+
run "pnpm install #{package}"
|
11
13
|
else
|
12
14
|
say "Could not detect the package manager, you need to install '#{package}' manually", :red
|
13
15
|
end
|
@@ -30,6 +32,8 @@ module RubyUI
|
|
30
32
|
|
31
33
|
def using_npm? = File.exist?(Rails.root.join("package-lock.json"))
|
32
34
|
|
35
|
+
def using_pnpm? = File.exist?(Rails.root.join("pnpm-lock.yaml"))
|
36
|
+
|
33
37
|
def using_yarn? = File.exist?(Rails.root.join("yarn.lock"))
|
34
38
|
|
35
39
|
def pin_motion
|
@@ -14,8 +14,7 @@ module RubyUI
|
|
14
14
|
def background
|
15
15
|
div(
|
16
16
|
data_state: "open",
|
17
|
-
class:
|
18
|
-
"fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
17
|
+
class: "fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in",
|
19
18
|
style: "pointer-events:auto",
|
20
19
|
data_aria_hidden: "true",
|
21
20
|
aria_hidden: "true"
|
@@ -26,7 +25,7 @@ module RubyUI
|
|
26
25
|
div(
|
27
26
|
role: "alertdialog",
|
28
27
|
data_state: "open",
|
29
|
-
class: "flex flex-col fixed left-[50%] top-[50%] z-50 w-full max-w-lg max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=
|
28
|
+
class: "flex flex-col fixed left-[50%] top-[50%] z-50 w-full max-w-lg max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:rounded-lg md:w-full",
|
30
29
|
style: "pointer-events:auto",
|
31
30
|
&
|
32
31
|
)
|
@@ -2,6 +2,13 @@
|
|
2
2
|
|
3
3
|
module RubyUI
|
4
4
|
class Button < Base
|
5
|
+
BASE_CLASSES = [
|
6
|
+
"whitespace-nowrap inline-flex items-center justify-center rounded-md font-medium transition-colors",
|
7
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
8
|
+
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
9
|
+
"aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:cursor-not-allowed"
|
10
|
+
].freeze
|
11
|
+
|
5
12
|
def initialize(type: :button, variant: :primary, size: :md, icon: false, **attrs)
|
6
13
|
@type = type
|
7
14
|
@variant = variant.to_sym
|
@@ -36,43 +43,55 @@ module RubyUI
|
|
36
43
|
|
37
44
|
def primary_classes
|
38
45
|
[
|
39
|
-
|
40
|
-
size_classes
|
46
|
+
BASE_CLASSES,
|
47
|
+
size_classes,
|
48
|
+
"bg-primary text-primary-foreground shadow",
|
49
|
+
"hover:bg-primary/90"
|
41
50
|
]
|
42
51
|
end
|
43
52
|
|
44
53
|
def link_classes
|
45
54
|
[
|
46
|
-
|
47
|
-
size_classes
|
55
|
+
BASE_CLASSES,
|
56
|
+
size_classes,
|
57
|
+
"text-primary underline-offset-4",
|
58
|
+
"hover:underline"
|
48
59
|
]
|
49
60
|
end
|
50
61
|
|
51
62
|
def secondary_classes
|
52
63
|
[
|
53
|
-
|
54
|
-
size_classes
|
64
|
+
BASE_CLASSES,
|
65
|
+
size_classes,
|
66
|
+
"bg-secondary text-secondary-foreground",
|
67
|
+
"hover:bg-opacity-80"
|
55
68
|
]
|
56
69
|
end
|
57
70
|
|
58
71
|
def destructive_classes
|
59
72
|
[
|
60
|
-
|
61
|
-
size_classes
|
73
|
+
BASE_CLASSES,
|
74
|
+
size_classes,
|
75
|
+
"bg-destructive text-white shadow-sm",
|
76
|
+
"[a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20",
|
77
|
+
"dark:focus-visible:ring-destructive/40 dark:bg-destructive/60"
|
62
78
|
]
|
63
79
|
end
|
64
80
|
|
65
81
|
def outline_classes
|
66
82
|
[
|
67
|
-
|
68
|
-
size_classes
|
83
|
+
BASE_CLASSES,
|
84
|
+
size_classes,
|
85
|
+
"border border-input bg-background shadow-sm",
|
86
|
+
"hover:bg-accent hover:text-accent-foreground"
|
69
87
|
]
|
70
88
|
end
|
71
89
|
|
72
90
|
def ghost_classes
|
73
91
|
[
|
74
|
-
|
75
|
-
size_classes
|
92
|
+
BASE_CLASSES,
|
93
|
+
size_classes,
|
94
|
+
"hover:bg-accent hover:text-accent-foreground"
|
76
95
|
]
|
77
96
|
end
|
78
97
|
|
@@ -88,10 +107,7 @@ module RubyUI
|
|
88
107
|
end
|
89
108
|
|
90
109
|
def default_attrs
|
91
|
-
{
|
92
|
-
type: @type,
|
93
|
-
class: default_classes
|
94
|
-
}
|
110
|
+
{type: @type, class: default_classes}
|
95
111
|
end
|
96
112
|
end
|
97
113
|
end
|
@@ -16,7 +16,13 @@ module RubyUI
|
|
16
16
|
ruby_ui__checkbox_group_target: "checkbox",
|
17
17
|
action: "change->ruby-ui--checkbox-group#onChange change->ruby-ui--form-field#onInput invalid->ruby-ui--form-field#onInvalid"
|
18
18
|
},
|
19
|
-
class:
|
19
|
+
class: [
|
20
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background accent-primary",
|
21
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
22
|
+
"checked:bg-primary checked:text-primary-foreground",
|
23
|
+
"aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none",
|
24
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
25
|
+
]
|
20
26
|
}
|
21
27
|
end
|
22
28
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module RubyUI
|
4
4
|
class Combobox < Base
|
5
|
-
def initialize(term:
|
5
|
+
def initialize(term: nil, **)
|
6
6
|
@term = term
|
7
7
|
super(**)
|
8
8
|
end
|
@@ -18,7 +18,8 @@ module RubyUI
|
|
18
18
|
role: "combobox",
|
19
19
|
data: {
|
20
20
|
controller: "ruby-ui--combobox",
|
21
|
-
ruby_ui__combobox_term_value: @term
|
21
|
+
ruby_ui__combobox_term_value: @term,
|
22
|
+
action: "turbo:morph@window->ruby-ui--combobox#updateTriggerContent"
|
22
23
|
}
|
23
24
|
}
|
24
25
|
end
|
@@ -12,8 +12,10 @@ module RubyUI
|
|
12
12
|
{
|
13
13
|
class: [
|
14
14
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background accent-primary",
|
15
|
-
"
|
16
|
-
"
|
15
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
16
|
+
"checked:bg-primary checked:text-primary-foreground",
|
17
|
+
"aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none",
|
18
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
17
19
|
],
|
18
20
|
data: {
|
19
21
|
ruby_ui__combobox_target: "input",
|
@@ -53,12 +53,12 @@ export default class extends Controller {
|
|
53
53
|
updateTriggerContent() {
|
54
54
|
const checkedInputs = this.inputTargets.filter(input => input.checked)
|
55
55
|
|
56
|
-
if (checkedInputs.length
|
56
|
+
if (checkedInputs.length === 0) {
|
57
57
|
this.triggerContentTarget.innerText = this.triggerTarget.dataset.placeholder
|
58
|
-
} else if (checkedInputs.length
|
59
|
-
this.triggerContentTarget.innerText = this.inputContent(checkedInputs[0])
|
60
|
-
} else {
|
58
|
+
} else if (this.termValue && checkedInputs.length > 1) {
|
61
59
|
this.triggerContentTarget.innerText = `${checkedInputs.length} ${this.termValue}`
|
60
|
+
} else {
|
61
|
+
this.triggerContentTarget.innerText = checkedInputs.map((input) => this.inputContent(input)).join(", ")
|
62
62
|
}
|
63
63
|
}
|
64
64
|
|
@@ -13,7 +13,8 @@ module RubyUI
|
|
13
13
|
class: [
|
14
14
|
"flex flex-row w-full text-wrap [&>span,&>div]:truncate gap-2 items-center rounded-sm px-2 py-1 text-sm outline-none cursor-pointer",
|
15
15
|
"select-none has-[:checked]:bg-accent hover:bg-accent p-2",
|
16
|
-
"[&>svg]:pointer-events-none [&>svg]:size-4 [&>svg]:shrink-0 aria-[current=true]:bg-accent aria-[current=true]:ring aria-[current=true]:ring-offset-2"
|
16
|
+
"[&>svg]:pointer-events-none [&>svg]:size-4 [&>svg]:shrink-0 aria-[current=true]:bg-accent aria-[current=true]:ring aria-[current=true]:ring-offset-2",
|
17
|
+
"has-disabled:opacity-50 has-disabled:cursor-not-allowed"
|
17
18
|
],
|
18
19
|
role: "option",
|
19
20
|
data: {
|
@@ -10,7 +10,14 @@ module RubyUI
|
|
10
10
|
|
11
11
|
def default_attrs
|
12
12
|
{
|
13
|
-
class:
|
13
|
+
class: [
|
14
|
+
"aspect-square h-4 w-4 rounded-full border border-primary accent-primary text-primary shadow",
|
15
|
+
"focus:outline-none",
|
16
|
+
"focus-visible:ring-1 focus-visible:ring-ring",
|
17
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
18
|
+
"checked:bg-primary checked:text-primary-foreground",
|
19
|
+
"aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none"
|
20
|
+
],
|
14
21
|
data: {
|
15
22
|
ruby_ui__combobox_target: "input",
|
16
23
|
ruby_ui__form_field_target: "input",
|
@@ -19,16 +19,22 @@ module RubyUI
|
|
19
19
|
def default_attrs
|
20
20
|
{
|
21
21
|
type: "search",
|
22
|
-
class: "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none border-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
23
22
|
role: "searchbox",
|
23
|
+
autocorrect: "off",
|
24
|
+
autocomplete: "off",
|
25
|
+
spellcheck: "false",
|
24
26
|
placeholder: @placeholder,
|
27
|
+
class: [
|
28
|
+
"flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none border-none",
|
29
|
+
"focus:ring-0",
|
30
|
+
"placeholder:text-muted-foreground",
|
31
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
32
|
+
"aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none"
|
33
|
+
],
|
25
34
|
data: {
|
26
35
|
ruby_ui__combobox_target: "searchInput",
|
27
36
|
action: "keyup->ruby-ui--combobox#filterItems search->ruby-ui--combobox#filterItems"
|
28
|
-
}
|
29
|
-
autocomplete: "off",
|
30
|
-
autocorrect: "off",
|
31
|
-
spellcheck: "false"
|
37
|
+
}
|
32
38
|
}
|
33
39
|
end
|
34
40
|
|
@@ -13,7 +13,8 @@ module RubyUI
|
|
13
13
|
class: [
|
14
14
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background accent-primary",
|
15
15
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
16
|
-
"disabled:cursor-not-allowed disabled:opacity-50"
|
16
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
17
|
+
"aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none"
|
17
18
|
],
|
18
19
|
data: {
|
19
20
|
ruby_ui__combobox_target: "toggleAll",
|
@@ -21,7 +21,13 @@ module RubyUI
|
|
21
21
|
def default_attrs
|
22
22
|
{
|
23
23
|
type: "button",
|
24
|
-
class:
|
24
|
+
class: [
|
25
|
+
"flex h-full w-full items-center whitespace-nowrap rounded-md text-sm ring-offset-background transition-colors border border-input bg-background h-9 px-4 py-2 justify-between",
|
26
|
+
"hover:bg-accent hover:text-accent-foreground",
|
27
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
28
|
+
"aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:cursor-not-allowed",
|
29
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
30
|
+
],
|
25
31
|
data: {
|
26
32
|
placeholder: @placeholder,
|
27
33
|
ruby_ui__combobox_target: "trigger",
|
@@ -34,7 +34,7 @@ module RubyUI
|
|
34
34
|
{
|
35
35
|
data_state: "open",
|
36
36
|
class: [
|
37
|
-
"fixed flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=
|
37
|
+
"fixed flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:rounded-lg md:w-full",
|
38
38
|
SIZES[@size]
|
39
39
|
]
|
40
40
|
}
|
@@ -70,8 +70,7 @@ module RubyUI
|
|
70
70
|
div(
|
71
71
|
data_state: "open",
|
72
72
|
data_action: "click->ruby-ui--dialog#dismiss esc->ruby-ui--dialog#dismiss",
|
73
|
-
class:
|
74
|
-
"fixed pointer-events-auto inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
73
|
+
class: "fixed pointer-events-auto inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=open]:fade-in-0"
|
75
74
|
)
|
76
75
|
end
|
77
76
|
end
|
@@ -17,7 +17,7 @@ export default class extends Controller {
|
|
17
17
|
}
|
18
18
|
|
19
19
|
open(e) {
|
20
|
-
e
|
20
|
+
e?.preventDefault();
|
21
21
|
document.body.insertAdjacentHTML('beforeend', this.contentTarget.innerHTML)
|
22
22
|
// prevent scroll on body
|
23
23
|
document.body.classList.add('overflow-hidden')
|
@@ -15,6 +15,11 @@ module RubyUI
|
|
15
15
|
|
16
16
|
def default_attrs
|
17
17
|
{
|
18
|
+
class: [
|
19
|
+
"z-50",
|
20
|
+
"group/dropdown-menu",
|
21
|
+
(strategy == "absolute") ? "is-absolute" : "is-fixed"
|
22
|
+
],
|
18
23
|
data: {
|
19
24
|
controller: "ruby-ui--dropdown-menu",
|
20
25
|
action: "click@window->ruby-ui--dropdown-menu#onClickOutside",
|
@@ -22,5 +27,9 @@ module RubyUI
|
|
22
27
|
}
|
23
28
|
}
|
24
29
|
end
|
30
|
+
|
31
|
+
def strategy
|
32
|
+
@_strategy ||= @options[:strategy] || "absolute"
|
33
|
+
end
|
25
34
|
end
|
26
35
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module RubyUI
|
4
4
|
class DropdownMenuContent < Base
|
5
5
|
def view_template(&block)
|
6
|
-
div(
|
6
|
+
div(**wrapper_attrs) do
|
7
7
|
div(**attrs, &block)
|
8
8
|
end
|
9
9
|
end
|
@@ -15,7 +15,22 @@ module RubyUI
|
|
15
15
|
data: {
|
16
16
|
state: :open
|
17
17
|
},
|
18
|
-
class: "z-50 min-w-[8rem]
|
18
|
+
class: "z-50 min-w-[8rem] rounded-md border bg-background p-1 text-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 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 w-56"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def wrapper_attrs
|
23
|
+
{
|
24
|
+
class: [
|
25
|
+
"z-50 hidden group-[.is-absolute]/dropdown-menu:absolute",
|
26
|
+
"group-[.is-fixed]/dropdown-menu:fixed"
|
27
|
+
],
|
28
|
+
data: {ruby_ui__dropdown_menu_target: "content"},
|
29
|
+
style: {
|
30
|
+
width: "max-content",
|
31
|
+
top: "0",
|
32
|
+
left: "0"
|
33
|
+
}
|
19
34
|
}
|
20
35
|
end
|
21
36
|
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
computePosition,
|
4
|
+
flip,
|
5
|
+
shift,
|
6
|
+
offset,
|
7
|
+
autoUpdate,
|
8
|
+
} from "@floating-ui/dom";
|
3
9
|
|
4
10
|
export default class extends Controller {
|
5
11
|
static targets = ["trigger", "content", "menuItem"];
|
@@ -12,17 +18,34 @@ export default class extends Controller {
|
|
12
18
|
type: Object,
|
13
19
|
default: {},
|
14
20
|
},
|
15
|
-
}
|
21
|
+
};
|
16
22
|
|
17
23
|
connect() {
|
18
24
|
this.boundHandleKeydown = this.#handleKeydown.bind(this); // Bind the function so we can remove it later
|
19
25
|
this.selectedIndex = -1;
|
26
|
+
|
27
|
+
this.#setupAutoUpdate();
|
28
|
+
}
|
29
|
+
|
30
|
+
disconnect() {
|
31
|
+
if (this.autoUpdateCleanup) {
|
32
|
+
this.autoUpdateCleanup();
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
#setupAutoUpdate() {
|
37
|
+
this.autoUpdateCleanup = autoUpdate(
|
38
|
+
this.triggerTarget,
|
39
|
+
this.contentTarget,
|
40
|
+
this.#computeTooltip.bind(this),
|
41
|
+
);
|
20
42
|
}
|
21
43
|
|
22
44
|
#computeTooltip() {
|
23
45
|
computePosition(this.triggerTarget, this.contentTarget, {
|
24
46
|
placement: this.optionsValue.placement || "top",
|
25
47
|
middleware: [flip(), shift(), offset(8)],
|
48
|
+
strategy: this.optionsValue.strategy || "absolute",
|
26
49
|
}).then(({ x, y }) => {
|
27
50
|
Object.assign(this.contentTarget.style, {
|
28
51
|
left: `${x}px`,
|
@@ -40,14 +63,16 @@ export default class extends Controller {
|
|
40
63
|
}
|
41
64
|
|
42
65
|
toggle() {
|
43
|
-
this.contentTarget.classList.contains("hidden")
|
66
|
+
this.contentTarget.classList.contains("hidden")
|
67
|
+
? this.#open()
|
68
|
+
: this.close();
|
44
69
|
}
|
45
70
|
|
46
71
|
#open() {
|
47
72
|
this.openValue = true;
|
48
73
|
this.#deselectAll();
|
49
74
|
this.#addEventListeners();
|
50
|
-
this.#computeTooltip()
|
75
|
+
this.#computeTooltip();
|
51
76
|
this.contentTarget.classList.remove("hidden");
|
52
77
|
}
|
53
78
|
|
@@ -59,15 +84,17 @@ export default class extends Controller {
|
|
59
84
|
|
60
85
|
#handleKeydown(e) {
|
61
86
|
// return if no menu items (one line fix for)
|
62
|
-
if (this.menuItemTargets.length === 0) {
|
87
|
+
if (this.menuItemTargets.length === 0) {
|
88
|
+
return;
|
89
|
+
}
|
63
90
|
|
64
|
-
if (e.key ===
|
91
|
+
if (e.key === "ArrowDown") {
|
65
92
|
e.preventDefault();
|
66
93
|
this.#updateSelectedItem(1);
|
67
|
-
} else if (e.key ===
|
94
|
+
} else if (e.key === "ArrowUp") {
|
68
95
|
e.preventDefault();
|
69
96
|
this.#updateSelectedItem(-1);
|
70
|
-
} else if (e.key ===
|
97
|
+
} else if (e.key === "Enter" && this.selectedIndex !== -1) {
|
71
98
|
e.preventDefault();
|
72
99
|
this.menuItemTargets[this.selectedIndex].click();
|
73
100
|
}
|
@@ -76,7 +103,7 @@ export default class extends Controller {
|
|
76
103
|
#updateSelectedItem(direction) {
|
77
104
|
// Check if any of the menuItemTargets have aria-selected="true" and set the selectedIndex to that index
|
78
105
|
this.menuItemTargets.forEach((item, index) => {
|
79
|
-
if (item.getAttribute(
|
106
|
+
if (item.getAttribute("aria-selected") === "true") {
|
80
107
|
this.selectedIndex = index;
|
81
108
|
}
|
82
109
|
});
|
@@ -99,22 +126,24 @@ export default class extends Controller {
|
|
99
126
|
#toggleAriaSelected(element, isSelected) {
|
100
127
|
// Add or remove attribute
|
101
128
|
if (isSelected) {
|
102
|
-
element.setAttribute(
|
129
|
+
element.setAttribute("aria-selected", "true");
|
103
130
|
} else {
|
104
|
-
element.removeAttribute(
|
131
|
+
element.removeAttribute("aria-selected");
|
105
132
|
}
|
106
133
|
}
|
107
134
|
|
108
135
|
#deselectAll() {
|
109
|
-
this.menuItemTargets.forEach(item =>
|
136
|
+
this.menuItemTargets.forEach((item) =>
|
137
|
+
this.#toggleAriaSelected(item, false),
|
138
|
+
);
|
110
139
|
this.selectedIndex = -1;
|
111
140
|
}
|
112
141
|
|
113
142
|
#addEventListeners() {
|
114
|
-
document.addEventListener(
|
143
|
+
document.addEventListener("keydown", this.boundHandleKeydown);
|
115
144
|
}
|
116
145
|
|
117
146
|
#removeEventListeners() {
|
118
|
-
document.removeEventListener(
|
147
|
+
document.removeEventListener("keydown", this.boundHandleKeydown);
|
119
148
|
}
|
120
149
|
}
|
@@ -9,7 +9,13 @@ module RubyUI
|
|
9
9
|
private
|
10
10
|
|
11
11
|
def default_attrs
|
12
|
-
{
|
12
|
+
{
|
13
|
+
class: [
|
14
|
+
"text-sm font-medium leading-none",
|
15
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
16
|
+
"peer-aria-disabled:cursor-not-allowed peer-aria-disabled:opacity-70 peer-aria-disabled:pointer-events-none"
|
17
|
+
]
|
18
|
+
}
|
13
19
|
end
|
14
20
|
end
|
15
21
|
end
|