ruby_ui 1.2.0 → 1.4.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/README.md +6 -0
- data/lib/generators/ruby_ui/component/all_generator.rb +6 -4
- data/lib/generators/ruby_ui/component_generator.rb +51 -31
- data/lib/generators/ruby_ui/dependencies.yml +32 -10
- data/lib/generators/ruby_ui/install/templates/tailwind.css.erb +1 -1
- data/lib/generators/ruby_ui/javascript_utils.rb +24 -7
- data/lib/ruby_ui/accordion/accordion_content.rb +4 -2
- data/lib/ruby_ui/accordion/accordion_controller.js +19 -4
- data/lib/ruby_ui/avatar/avatar.rb +3 -0
- data/lib/ruby_ui/avatar/avatar_controller.js +33 -0
- data/lib/ruby_ui/avatar/avatar_fallback.rb +3 -0
- data/lib/ruby_ui/avatar/avatar_image.rb +9 -1
- data/lib/ruby_ui/base.rb +6 -0
- data/lib/ruby_ui/calendar/calendar.rb +3 -1
- data/lib/ruby_ui/calendar/calendar_controller.js +66 -7
- data/lib/ruby_ui/calendar/calendar_days.rb +20 -0
- data/lib/ruby_ui/calendar/calendar_docs.rb +9 -0
- data/lib/ruby_ui/combobox/combobox.rb +1 -7
- data/lib/ruby_ui/combobox/combobox_checkbox.rb +7 -1
- data/lib/ruby_ui/combobox/combobox_controller.js +56 -244
- data/lib/ruby_ui/combobox/combobox_item.rb +7 -5
- data/lib/ruby_ui/combobox/combobox_list_group.rb +1 -1
- data/lib/ruby_ui/combobox/combobox_popover.rb +5 -0
- data/lib/ruby_ui/combobox/combobox_radio.rb +8 -1
- data/lib/ruby_ui/combobox/combobox_toggle_all_checkbox.rb +7 -1
- data/lib/ruby_ui/combobox/combobox_trigger.rb +19 -19
- data/lib/ruby_ui/command/command_controller.js +10 -19
- data/lib/ruby_ui/command/command_dialog.rb +4 -1
- data/lib/ruby_ui/command/command_dialog_content.rb +2 -2
- data/lib/ruby_ui/command/command_dialog_controller.js +34 -0
- data/lib/ruby_ui/command/command_dialog_trigger.rb +2 -2
- data/lib/ruby_ui/data_table/data_table_docs.rb +1 -1
- data/lib/ruby_ui/date_picker/date_picker.rb +85 -0
- data/lib/ruby_ui/date_picker/date_picker_docs.rb +23 -0
- data/lib/ruby_ui/dialog/dialog_content.rb +7 -19
- data/lib/ruby_ui/dialog/dialog_controller.js +22 -10
- data/lib/ruby_ui/dropdown_menu/dropdown_menu_item.rb +10 -4
- data/lib/ruby_ui/form/form_docs.rb +89 -0
- data/lib/ruby_ui/masked_input/masked_input.rb +1 -11
- data/lib/ruby_ui/masked_input/masked_input_controller.js +0 -13
- data/lib/ruby_ui/select/select_value.rb +2 -1
- data/lib/ruby_ui/sheet/sheet.rb +9 -1
- data/lib/ruby_ui/sheet/sheet_controller.js +6 -0
- data/lib/ruby_ui/table/table_docs.rb +2 -2
- data/lib/ruby_ui/tabs/tabs_docs.rb +1 -1
- data/lib/ruby_ui/tabs/tabs_trigger.rb +10 -4
- data/lib/ruby_ui/theme_toggle/theme_toggle.rb +14 -2
- data/lib/ruby_ui/theme_toggle/theme_toggle_controller.js +27 -19
- data/lib/ruby_ui/theme_toggle/theme_toggle_docs.rb +12 -42
- data/lib/ruby_ui/toast/toast.rb +18 -0
- data/lib/ruby_ui/toast/toast_action.rb +27 -0
- data/lib/ruby_ui/toast/toast_cancel.rb +27 -0
- data/lib/ruby_ui/toast/toast_close.rb +40 -0
- data/lib/ruby_ui/toast/toast_controller.js +151 -0
- data/lib/ruby_ui/toast/toast_description.rb +18 -0
- data/lib/ruby_ui/toast/toast_docs.rb +12 -0
- data/lib/ruby_ui/toast/toast_icon.rb +65 -0
- data/lib/ruby_ui/toast/toast_item.rb +72 -0
- data/lib/ruby_ui/toast/toast_region.rb +124 -0
- data/lib/ruby_ui/toast/toast_title.rb +18 -0
- data/lib/ruby_ui/toast/toaster_controller.js +306 -0
- data/lib/ruby_ui/toggle/toggle.rb +101 -0
- data/lib/ruby_ui/toggle/toggle_controller.js +33 -0
- data/lib/ruby_ui/toggle_group/toggle_group.rb +119 -0
- data/lib/ruby_ui/toggle_group/toggle_group_controller.js +126 -0
- data/lib/ruby_ui/toggle_group/toggle_group_item.rb +67 -0
- data/lib/ruby_ui/tooltip/tooltip_content.rb +12 -5
- data/lib/ruby_ui/tooltip/tooltip_controller.js +58 -22
- data/lib/ruby_ui/tooltip/tooltip_docs.rb +13 -0
- data/lib/ruby_ui/tooltip/tooltip_trigger.rb +10 -3
- data/lib/ruby_ui.rb +3 -1
- metadata +30 -14
- data/lib/ruby_ui/theme_toggle/set_dark_mode.rb +0 -16
- data/lib/ruby_ui/theme_toggle/set_light_mode.rb +0 -16
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="ruby-ui--command-dialog"
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static targets = ["content"];
|
|
6
|
+
static outlets = ["ruby-ui--command"];
|
|
7
|
+
|
|
8
|
+
rubyUiCommandOutletConnected(controller) {
|
|
9
|
+
this.openOutlet = controller;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
rubyUiCommandOutletDisconnected() {
|
|
13
|
+
this.openOutlet = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
open(e) {
|
|
17
|
+
if (e) {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!this.hasContentTarget) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (this.openOutlet) {
|
|
26
|
+
this.openOutlet.focusInput();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML);
|
|
31
|
+
// prevent scroll on body
|
|
32
|
+
document.body.classList.add("overflow-hidden");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -8,7 +8,7 @@ module RubyUI
|
|
|
8
8
|
].freeze
|
|
9
9
|
|
|
10
10
|
def initialize(keybindings: DEFAULT_KEYBINDINGS, **attrs)
|
|
11
|
-
@keybindings = keybindings.map { |kb| "#{kb}->ruby-ui--command#open" }
|
|
11
|
+
@keybindings = keybindings.map { |kb| "#{kb}->ruby-ui--command-dialog#open" }
|
|
12
12
|
super(**attrs)
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ module RubyUI
|
|
|
21
21
|
def default_attrs
|
|
22
22
|
{
|
|
23
23
|
data: {
|
|
24
|
-
action: ["click->ruby-ui--command#open", @keybindings.join(" ")]
|
|
24
|
+
action: ["click->ruby-ui--command-dialog#open", @keybindings.join(" ")]
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Views::Docs::DataTable < Views::Base
|
|
4
|
-
Row = Struct.new(:id, :name, :email, :salary, :status
|
|
4
|
+
Row = Struct.new(:id, :name, :email, :salary, :status)
|
|
5
5
|
|
|
6
6
|
SAMPLE_ROWS = [
|
|
7
7
|
Row.new(id: 1, name: "Alice", email: "alice@example.com", salary: 90_000, status: "Active"),
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module RubyUI
|
|
6
|
+
class DatePicker < Base
|
|
7
|
+
def initialize(
|
|
8
|
+
id: nil,
|
|
9
|
+
name: nil,
|
|
10
|
+
label: "Select a date",
|
|
11
|
+
value: nil,
|
|
12
|
+
placeholder: "Select a date",
|
|
13
|
+
selected_date: value,
|
|
14
|
+
date_format: "yyyy-MM-dd",
|
|
15
|
+
popover_options: {},
|
|
16
|
+
input_attrs: {},
|
|
17
|
+
calendar_attrs: {},
|
|
18
|
+
trigger_attrs: {},
|
|
19
|
+
content_attrs: {},
|
|
20
|
+
**attrs
|
|
21
|
+
)
|
|
22
|
+
@id = id || "date-picker-#{SecureRandom.hex(4)}"
|
|
23
|
+
@name = name
|
|
24
|
+
@label = label
|
|
25
|
+
@value = value || selected_date&.to_s
|
|
26
|
+
@placeholder = placeholder
|
|
27
|
+
@selected_date = selected_date
|
|
28
|
+
@date_format = date_format
|
|
29
|
+
@popover_options = {trigger: "click"}.merge(popover_options)
|
|
30
|
+
@input_attrs = input_attrs
|
|
31
|
+
@calendar_attrs = calendar_attrs
|
|
32
|
+
@trigger_attrs = trigger_attrs
|
|
33
|
+
@content_attrs = content_attrs
|
|
34
|
+
super(**attrs)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def view_template
|
|
38
|
+
div(**attrs) do
|
|
39
|
+
RubyUI.Popover(options: @popover_options) do
|
|
40
|
+
RubyUI.PopoverTrigger(**trigger_attrs) do
|
|
41
|
+
div(class: "grid w-full max-w-sm items-center gap-1.5") do
|
|
42
|
+
label(for: @id) { @label } if @label
|
|
43
|
+
RubyUI.Input(**input_attrs)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
RubyUI.PopoverContent(**content_attrs) do
|
|
47
|
+
RubyUI.Calendar(input_id: "##{@id}", selected_date: @selected_date, date_format: @date_format, **calendar_attrs)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def default_attrs
|
|
56
|
+
{
|
|
57
|
+
class: "space-y-4 w-[260px]"
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def trigger_attrs
|
|
62
|
+
mix({class: "w-full"}, @trigger_attrs)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def input_attrs
|
|
66
|
+
mix({
|
|
67
|
+
type: "string",
|
|
68
|
+
placeholder: @placeholder,
|
|
69
|
+
id: @id,
|
|
70
|
+
name: @name,
|
|
71
|
+
value: @value,
|
|
72
|
+
data_controller: "ruby-ui--calendar-input",
|
|
73
|
+
class: "rounded-md border shadow"
|
|
74
|
+
}.compact, @input_attrs)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def calendar_attrs
|
|
78
|
+
mix({}, @calendar_attrs)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def content_attrs
|
|
82
|
+
mix({}, @content_attrs)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Views::Docs::DatePicker < Views::Base
|
|
4
|
+
def view_template
|
|
5
|
+
component = "DatePicker"
|
|
6
|
+
|
|
7
|
+
div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
|
|
8
|
+
render Docs::Header.new(title: "Date Picker", description: "A date picker component with input.")
|
|
9
|
+
|
|
10
|
+
Heading(level: 2) { "Usage" }
|
|
11
|
+
|
|
12
|
+
render Docs::VisualCodeExample.new(title: "Single Date", context: self) do
|
|
13
|
+
<<~RUBY
|
|
14
|
+
DatePicker(id: "date")
|
|
15
|
+
RUBY
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
render Components::ComponentSetup::Tabs.new(component_name: component)
|
|
19
|
+
|
|
20
|
+
render Docs::ComponentsTable.new(component_files(component))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -17,14 +17,9 @@ module RubyUI
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def view_template
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
div(**attrs) do
|
|
24
|
-
yield
|
|
25
|
-
close_button
|
|
26
|
-
end
|
|
27
|
-
end
|
|
20
|
+
dialog(**attrs) do
|
|
21
|
+
yield
|
|
22
|
+
close_button
|
|
28
23
|
end
|
|
29
24
|
end
|
|
30
25
|
|
|
@@ -32,9 +27,10 @@ module RubyUI
|
|
|
32
27
|
|
|
33
28
|
def default_attrs
|
|
34
29
|
{
|
|
35
|
-
|
|
30
|
+
data_ruby_ui__dialog_target: "dialog",
|
|
31
|
+
data_action: "click->ruby-ui--dialog#backdropClick",
|
|
36
32
|
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
|
|
33
|
+
"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 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full",
|
|
38
34
|
SIZES[@size]
|
|
39
35
|
]
|
|
40
36
|
}
|
|
@@ -43,7 +39,7 @@ module RubyUI
|
|
|
43
39
|
def close_button
|
|
44
40
|
button(
|
|
45
41
|
type: "button",
|
|
46
|
-
class: "absolute end-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
|
|
42
|
+
class: "absolute end-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",
|
|
47
43
|
data_action: "click->ruby-ui--dialog#dismiss"
|
|
48
44
|
) do
|
|
49
45
|
svg(
|
|
@@ -65,13 +61,5 @@ module RubyUI
|
|
|
65
61
|
span(class: "sr-only") { "Close" }
|
|
66
62
|
end
|
|
67
63
|
end
|
|
68
|
-
|
|
69
|
-
def backdrop
|
|
70
|
-
div(
|
|
71
|
-
data_state: "open",
|
|
72
|
-
data_action: "click->ruby-ui--dialog#dismiss esc->ruby-ui--dialog#dismiss",
|
|
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"
|
|
74
|
-
)
|
|
75
|
-
end
|
|
76
64
|
end
|
|
77
65
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
|
-
// Connects to data-controller="dialog"
|
|
3
|
+
// Connects to data-controller="ruby-ui--dialog"
|
|
4
4
|
export default class extends Controller {
|
|
5
|
-
static targets = ["
|
|
5
|
+
static targets = ["dialog"]
|
|
6
6
|
static values = {
|
|
7
7
|
open: {
|
|
8
8
|
type: Boolean,
|
|
@@ -11,22 +11,34 @@ export default class extends Controller {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
connect() {
|
|
14
|
+
this.dialogTarget.addEventListener("close", this.handleClose)
|
|
14
15
|
if (this.openValue) {
|
|
15
16
|
this.open()
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
disconnect() {
|
|
21
|
+
this.dialogTarget.removeEventListener("close", this.handleClose)
|
|
22
|
+
document.body.classList.remove("overflow-hidden")
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
open(e) {
|
|
20
|
-
e?.preventDefault()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
document.body.classList.add('overflow-hidden')
|
|
26
|
+
e?.preventDefault()
|
|
27
|
+
this.dialogTarget.showModal()
|
|
28
|
+
document.body.classList.add("overflow-hidden")
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
dismiss() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
this.dialogTarget.close()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
backdropClick(e) {
|
|
36
|
+
if (e.target === this.dialogTarget) {
|
|
37
|
+
this.dismiss()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handleClose = () => {
|
|
42
|
+
document.body.classList.remove("overflow-hidden")
|
|
31
43
|
}
|
|
32
44
|
}
|
|
@@ -2,20 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyUI
|
|
4
4
|
class DropdownMenuItem < Base
|
|
5
|
-
def initialize(href: "#", **attrs)
|
|
5
|
+
def initialize(as: :a, href: "#", **attrs)
|
|
6
|
+
@as = as
|
|
6
7
|
@href = href
|
|
7
8
|
super(**attrs)
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def view_template(&)
|
|
11
|
-
|
|
12
|
+
if @as == :div
|
|
13
|
+
div(**attrs, &)
|
|
14
|
+
else
|
|
15
|
+
a(**attrs, &)
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
private
|
|
15
20
|
|
|
16
21
|
def default_attrs
|
|
17
|
-
{
|
|
18
|
-
href: @href,
|
|
22
|
+
base = {
|
|
19
23
|
role: "menuitem",
|
|
20
24
|
class: "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 aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
21
25
|
data_action: "click->ruby-ui--dropdown-menu#close",
|
|
@@ -23,6 +27,8 @@ module RubyUI
|
|
|
23
27
|
tabindex: "-1",
|
|
24
28
|
data_orientation: "vertical"
|
|
25
29
|
}
|
|
30
|
+
base[:href] = @href unless @as == :div
|
|
31
|
+
base
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
34
|
end
|
|
@@ -170,6 +170,95 @@ class Views::Docs::Form < Views::Base
|
|
|
170
170
|
RUBY
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
+
Heading(level: 2) { "Rails Integration" }
|
|
174
|
+
|
|
175
|
+
Text do
|
|
176
|
+
plain "RubyUI Form components are plain HTML — they work with any form submission strategy. "
|
|
177
|
+
plain "The recommended approach for Rails apps is to use "
|
|
178
|
+
InlineLink(href: "https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with", target: "_blank") { "form_with" }
|
|
179
|
+
plain " to generate the "
|
|
180
|
+
code(class: "font-mono text-sm") { "action" }
|
|
181
|
+
plain " URL and CSRF token, then pass explicit "
|
|
182
|
+
code(class: "font-mono text-sm") { "name" }
|
|
183
|
+
plain " / "
|
|
184
|
+
code(class: "font-mono text-sm") { "id" }
|
|
185
|
+
plain " attributes to each RubyUI input so the browser serialises them correctly. "
|
|
186
|
+
plain "Server-side errors can be surfaced by rendering "
|
|
187
|
+
code(class: "font-mono text-sm") { "FormFieldError" }
|
|
188
|
+
plain " with content from "
|
|
189
|
+
code(class: "font-mono text-sm") { "model.errors.full_messages_for(:attr)" }
|
|
190
|
+
plain "."
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
Heading(level: 3) { "Minimal Rails form" }
|
|
194
|
+
Codeblock(<<~RUBY, syntax: :ruby)
|
|
195
|
+
# In your Phlex view, call form_with via helpers:
|
|
196
|
+
# form_with(url: users_path, method: :post) passes action + CSRF automatically.
|
|
197
|
+
#
|
|
198
|
+
# You can also set action and the CSRF token manually:
|
|
199
|
+
Form(action: helpers.users_path, method: "post", class: "w-2/3 space-y-6") do
|
|
200
|
+
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)
|
|
201
|
+
|
|
202
|
+
FormField do
|
|
203
|
+
FormFieldLabel(for: "user_email") { "Email" }
|
|
204
|
+
Input(
|
|
205
|
+
type: "email",
|
|
206
|
+
id: "user_email",
|
|
207
|
+
name: "user[email]",
|
|
208
|
+
placeholder: "you@example.com",
|
|
209
|
+
required: true
|
|
210
|
+
)
|
|
211
|
+
FormFieldError()
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
Button(type: "submit") { "Continue" }
|
|
215
|
+
end
|
|
216
|
+
RUBY
|
|
217
|
+
|
|
218
|
+
Heading(level: 3) { "Devise-style login form" }
|
|
219
|
+
Codeblock(<<~RUBY, syntax: :ruby)
|
|
220
|
+
# Full sign-in form mirroring Devise session[email] / session[password] params.
|
|
221
|
+
# Pass backend errors (e.g. "Invalid email or password") into FormFieldError.
|
|
222
|
+
Form(action: helpers.user_session_path, method: "post", class: "space-y-6") do
|
|
223
|
+
input(type: "hidden", name: "authenticity_token", value: helpers.form_authenticity_token)
|
|
224
|
+
|
|
225
|
+
FormField do
|
|
226
|
+
FormFieldLabel(for: "session_email") { "Email" }
|
|
227
|
+
Input(
|
|
228
|
+
type: "email",
|
|
229
|
+
id: "session_email",
|
|
230
|
+
name: "session[email]",
|
|
231
|
+
placeholder: "you@example.com",
|
|
232
|
+
autocomplete: "email",
|
|
233
|
+
required: true
|
|
234
|
+
)
|
|
235
|
+
FormFieldError { @error_message }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
FormField do
|
|
239
|
+
FormFieldLabel(for: "session_password") { "Password" }
|
|
240
|
+
Input(
|
|
241
|
+
type: "password",
|
|
242
|
+
id: "session_password",
|
|
243
|
+
name: "session[password]",
|
|
244
|
+
autocomplete: "current-password",
|
|
245
|
+
required: true,
|
|
246
|
+
minlength: "8"
|
|
247
|
+
)
|
|
248
|
+
FormFieldError()
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
FormField do
|
|
252
|
+
div(class: "flex items-center gap-2") do
|
|
253
|
+
Checkbox(id: "session_remember_me", name: "session[remember_me]", value: "1")
|
|
254
|
+
FormFieldLabel(for: "session_remember_me") { "Remember me" }
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
Button(type: "submit", class: "w-full") { "Sign in" }
|
|
259
|
+
end
|
|
260
|
+
RUBY
|
|
261
|
+
|
|
173
262
|
render Components::ComponentSetup::Tabs.new(component_name: component)
|
|
174
263
|
|
|
175
264
|
render Docs::ComponentsTable.new(component_files(component))
|
|
@@ -2,18 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyUI
|
|
4
4
|
class MaskedInput < Base
|
|
5
|
-
def initialize(save_unmasked: false, **attrs)
|
|
6
|
-
@save_unmasked = save_unmasked
|
|
7
|
-
super(**attrs)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
5
|
def view_template
|
|
11
|
-
|
|
12
|
-
Input(type: "text", **attrs.merge(name: "#{attrs[:name]}-masked"))
|
|
13
|
-
input(type: "hidden", name: attrs[:name], value: attrs[:value])
|
|
14
|
-
else
|
|
15
|
-
Input(type: "text", **attrs)
|
|
16
|
-
end
|
|
6
|
+
Input(type: "text", **attrs)
|
|
17
7
|
end
|
|
18
8
|
|
|
19
9
|
private
|
|
@@ -5,18 +5,5 @@ import { MaskInput } from "maska";
|
|
|
5
5
|
export default class extends Controller {
|
|
6
6
|
connect() {
|
|
7
7
|
new MaskInput(this.element)
|
|
8
|
-
this.#boundSync = this.#sync.bind(this);
|
|
9
|
-
this.element.addEventListener("maska", this.#boundSync);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
disconnect() {
|
|
13
|
-
this.element.removeEventListener("maska", this.#boundSync);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
#boundSync = null;
|
|
17
|
-
|
|
18
|
-
#sync(event) {
|
|
19
|
-
const hidden = this.element.nextElementSibling;
|
|
20
|
-
if (hidden?.type === "hidden") hidden.value = event.detail.unmasked;
|
|
21
8
|
}
|
|
22
9
|
}
|
data/lib/ruby_ui/sheet/sheet.rb
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyUI
|
|
4
4
|
class Sheet < Base
|
|
5
|
+
def initialize(open: false, **attrs)
|
|
6
|
+
@open = open
|
|
7
|
+
super(**attrs)
|
|
8
|
+
end
|
|
9
|
+
|
|
5
10
|
def view_template(&)
|
|
6
11
|
div(**attrs, &)
|
|
7
12
|
end
|
|
@@ -10,7 +15,10 @@ module RubyUI
|
|
|
10
15
|
|
|
11
16
|
def default_attrs
|
|
12
17
|
{
|
|
13
|
-
data: {
|
|
18
|
+
data: {
|
|
19
|
+
controller: "ruby-ui--sheet",
|
|
20
|
+
ruby_ui__sheet_open_value: @open.to_s
|
|
21
|
+
}
|
|
14
22
|
}
|
|
15
23
|
end
|
|
16
24
|
end
|
|
@@ -3,6 +3,12 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
static targets = ["content"]
|
|
5
5
|
|
|
6
|
+
static values = { open: false }
|
|
7
|
+
|
|
8
|
+
connect() {
|
|
9
|
+
if (this.openValue) this.open()
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
open() {
|
|
7
13
|
document.body.insertAdjacentHTML("beforeend", this.contentTarget.innerHTML)
|
|
8
14
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Views::Docs::Table < Views::Base
|
|
4
|
-
Invoice = Struct.new(:identifier, :status, :method, :amount
|
|
5
|
-
User = Struct.new(:avatar_url, :name, :username, :commits, :github_url
|
|
4
|
+
Invoice = Struct.new(:identifier, :status, :method, :amount)
|
|
5
|
+
User = Struct.new(:avatar_url, :name, :username, :commits, :github_url)
|
|
6
6
|
|
|
7
7
|
def view_template
|
|
8
8
|
component = "Table"
|
|
@@ -2,20 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyUI
|
|
4
4
|
class TabsTrigger < Base
|
|
5
|
-
def initialize(value:, **attrs)
|
|
5
|
+
def initialize(value:, as: :button, **attrs)
|
|
6
6
|
@value = value
|
|
7
|
+
@as = as
|
|
7
8
|
super(**attrs)
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def view_template(&)
|
|
11
|
-
|
|
12
|
+
if @as == :a
|
|
13
|
+
a(**attrs, &)
|
|
14
|
+
else
|
|
15
|
+
button(**attrs, &)
|
|
16
|
+
end
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
private
|
|
15
20
|
|
|
16
21
|
def default_attrs
|
|
17
|
-
{
|
|
18
|
-
type: :button,
|
|
22
|
+
base = {
|
|
19
23
|
data: {
|
|
20
24
|
ruby_ui__tabs_target: "trigger",
|
|
21
25
|
action: "click->ruby-ui--tabs#show",
|
|
@@ -29,6 +33,8 @@ module RubyUI
|
|
|
29
33
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
30
34
|
]
|
|
31
35
|
}
|
|
36
|
+
base[:type] = :button unless @as == :a
|
|
37
|
+
base
|
|
32
38
|
end
|
|
33
39
|
end
|
|
34
40
|
end
|
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyUI
|
|
4
4
|
class ThemeToggle < Base
|
|
5
|
-
def view_template(&)
|
|
6
|
-
|
|
5
|
+
def view_template(&block)
|
|
6
|
+
RubyUI.Toggle(
|
|
7
|
+
variant: :default,
|
|
8
|
+
size: :default,
|
|
9
|
+
aria: {label: "Toggle theme"},
|
|
10
|
+
wrapper: {
|
|
11
|
+
data: {
|
|
12
|
+
controller: "ruby-ui--theme-toggle",
|
|
13
|
+
action: "ruby-ui--toggle:change->ruby-ui--theme-toggle#apply"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
**attrs,
|
|
17
|
+
&block
|
|
18
|
+
)
|
|
7
19
|
end
|
|
8
20
|
end
|
|
9
21
|
end
|
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
|
+
// Connects to data-controller="ruby-ui--theme-toggle"
|
|
4
|
+
// Sits on the same wrapper as ruby-ui--toggle. Listens for the toggle's
|
|
5
|
+
// ruby-ui--toggle:change event. pressed = dark mode.
|
|
3
6
|
export default class extends Controller {
|
|
4
|
-
|
|
5
|
-
this.
|
|
7
|
+
connect() {
|
|
8
|
+
this.applyTheme(this.currentTheme())
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} else {
|
|
14
|
-
document.documentElement.classList.remove('dark')
|
|
15
|
-
document.documentElement.classList.add('light')
|
|
16
|
-
}
|
|
11
|
+
apply(event) {
|
|
12
|
+
const pressed = event.detail?.pressed
|
|
13
|
+
const theme = pressed ? "dark" : "light"
|
|
14
|
+
localStorage.theme = theme
|
|
15
|
+
this.applyTheme(theme)
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
localStorage.theme
|
|
22
|
-
|
|
18
|
+
currentTheme() {
|
|
19
|
+
if (localStorage.theme === "dark") return "dark"
|
|
20
|
+
if (localStorage.theme === "light") return "light"
|
|
21
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
applyTheme(theme) {
|
|
25
|
+
const html = document.documentElement
|
|
26
|
+
if (theme === "dark") {
|
|
27
|
+
html.classList.add("dark")
|
|
28
|
+
html.classList.remove("light")
|
|
29
|
+
} else {
|
|
30
|
+
html.classList.add("light")
|
|
31
|
+
html.classList.remove("dark")
|
|
32
|
+
}
|
|
33
|
+
// Flip the sibling Toggle controller's pressed value; it will propagate
|
|
34
|
+
// aria-pressed / data-state to the button target.
|
|
35
|
+
const dark = theme === "dark"
|
|
36
|
+
this.element.setAttribute("data-ruby-ui--toggle-pressed-value", dark ? "true" : "false")
|
|
29
37
|
}
|
|
30
38
|
}
|