nitro_kit 0.2.0 → 0.3.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/MIT-LICENSE +20 -0
- data/Rakefile +6 -4
- data/app/components/nitro_kit/accordion.rb +68 -32
- data/app/components/nitro_kit/alert.rb +69 -0
- data/app/components/nitro_kit/avatar.rb +52 -0
- data/app/components/nitro_kit/badge.rb +46 -19
- data/app/components/nitro_kit/button.rb +99 -66
- data/app/components/nitro_kit/button_group.rb +18 -13
- data/app/components/nitro_kit/card.rb +49 -9
- data/app/components/nitro_kit/checkbox.rb +59 -41
- data/app/components/nitro_kit/checkbox_group.rb +38 -0
- data/app/components/nitro_kit/combobox.rb +138 -0
- data/app/components/nitro_kit/component.rb +45 -14
- data/app/components/nitro_kit/datepicker.rb +9 -0
- data/app/components/nitro_kit/dialog.rb +95 -0
- data/app/components/nitro_kit/dropdown.rb +112 -70
- data/app/components/nitro_kit/field.rb +221 -56
- data/app/components/nitro_kit/field_group.rb +12 -6
- data/app/components/nitro_kit/fieldset.rb +42 -7
- data/app/components/nitro_kit/form_builder.rb +45 -22
- data/app/components/nitro_kit/icon.rb +29 -8
- data/app/components/nitro_kit/input.rb +20 -10
- data/app/components/nitro_kit/label.rb +18 -5
- data/app/components/nitro_kit/pagination.rb +98 -0
- data/app/components/nitro_kit/radio_button.rb +28 -27
- data/app/components/nitro_kit/radio_button_group.rb +53 -0
- data/app/components/nitro_kit/select.rb +72 -0
- data/app/components/nitro_kit/switch.rb +49 -39
- data/app/components/nitro_kit/table.rb +56 -0
- data/app/components/nitro_kit/tabs.rb +98 -0
- data/app/components/nitro_kit/textarea.rb +26 -0
- data/app/components/nitro_kit/toast.rb +104 -0
- data/app/components/nitro_kit/tooltip.rb +53 -0
- data/app/helpers/nitro_kit/accordion_helper.rb +2 -0
- data/app/helpers/nitro_kit/alert_helper.rb +11 -0
- data/app/helpers/nitro_kit/avatar_helper.rb +9 -0
- data/app/helpers/nitro_kit/badge_helper.rb +3 -5
- data/app/helpers/nitro_kit/button_group_helper.rb +2 -0
- data/app/helpers/nitro_kit/button_helper.rb +37 -28
- data/app/helpers/nitro_kit/card_helper.rb +2 -0
- data/app/helpers/nitro_kit/checkbox_helper.rb +19 -16
- data/app/helpers/nitro_kit/combobox_helper.rb +9 -0
- data/app/helpers/nitro_kit/datepicker_helper.rb +9 -0
- data/app/helpers/nitro_kit/dialog_helper.rb +9 -0
- data/app/helpers/nitro_kit/dropdown_helper.rb +3 -1
- data/app/helpers/nitro_kit/field_group_helper.rb +9 -0
- data/app/helpers/nitro_kit/field_helper.rb +4 -2
- data/app/helpers/nitro_kit/fieldset_helper.rb +9 -0
- data/app/helpers/nitro_kit/form_helper.rb +13 -0
- data/app/helpers/nitro_kit/icon_helper.rb +3 -1
- data/app/helpers/nitro_kit/input_helper.rb +35 -0
- data/app/helpers/nitro_kit/label_helper.rb +12 -9
- data/app/helpers/nitro_kit/pagination_helper.rb +42 -0
- data/app/helpers/nitro_kit/radio_button_helper.rb +15 -12
- data/app/helpers/nitro_kit/select_helper.rb +24 -0
- data/app/helpers/nitro_kit/switch_helper.rb +4 -10
- data/app/helpers/nitro_kit/table_helper.rb +9 -0
- data/app/helpers/nitro_kit/tabs_helper.rb +9 -0
- data/app/helpers/nitro_kit/textarea_helper.rb +9 -0
- data/app/helpers/nitro_kit/toast_helper.rb +36 -0
- data/app/helpers/nitro_kit/tooltip_helper.rb +9 -0
- data/lib/generators/nitro_kit/add_generator.rb +38 -41
- data/lib/generators/nitro_kit/install_generator.rb +2 -1
- data/lib/nitro_kit/engine.rb +4 -0
- data/lib/nitro_kit/schema_builder.rb +90 -16
- data/lib/nitro_kit/version.rb +1 -1
- data/lib/nitro_kit.rb +39 -1
- data/lib/tasks/nitro_kit_tasks.rake +4 -0
- metadata +37 -10
- data/app/components/nitro_kit/radio_group.rb +0 -35
- data/app/helpers/application_helper.rb +0 -109
- data/lib/nitro_kit/railtie.rb +0 -8
@@ -1,37 +1,33 @@
|
|
1
1
|
module NitroKit
|
2
2
|
class Checkbox < Component
|
3
|
-
|
3
|
+
def initialize(label: nil, id: nil, **attrs)
|
4
|
+
@id = id || "nk--" + SecureRandom.hex(4)
|
5
|
+
@label = label
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
super(
|
8
|
+
attrs,
|
9
|
+
id: @id,
|
10
|
+
type: "checkbox",
|
11
|
+
class: input_class
|
12
|
+
)
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
:label_text
|
16
|
-
)
|
15
|
+
alias :html_label :label
|
16
|
+
|
17
|
+
attr_reader :label, :id
|
17
18
|
|
18
19
|
def view_template
|
19
|
-
div(class:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
class: class_names(
|
25
|
-
"peer appearance-none shadow-sm size-4 rounded-sm border text-foreground",
|
26
|
-
"checked:bg-primary checked:border-primary",
|
27
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
28
|
-
)
|
29
|
-
)
|
20
|
+
div(class: wrapper_class) do
|
21
|
+
html_label(
|
22
|
+
class: "inline-grid *:[grid-area:1/1] shrink-0 place-items-center group/checkbox"
|
23
|
+
) do
|
24
|
+
input(**attrs)
|
30
25
|
checkmark
|
26
|
+
dash
|
31
27
|
end
|
32
28
|
|
33
|
-
if
|
34
|
-
render(Label.new(for:
|
29
|
+
if label.present? || block_given?
|
30
|
+
render(Label.new(for: id)) { label || yield }
|
35
31
|
end
|
36
32
|
end
|
37
33
|
end
|
@@ -39,24 +35,46 @@ module NitroKit
|
|
39
35
|
private
|
40
36
|
|
41
37
|
def checkmark
|
42
|
-
|
43
|
-
class:
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
38
|
+
svg(
|
39
|
+
class: merge_class(svg_class, "group-has-[:checked]/checkbox:visible"),
|
40
|
+
viewbox: "0 0 16 16",
|
41
|
+
fill: "none",
|
42
|
+
stroke: "currentColor",
|
43
|
+
stroke_linecap: "round",
|
44
|
+
stroke_linejoin: "round",
|
45
|
+
stroke_width: 3
|
46
|
+
) do |svg|
|
47
|
+
svg.path(d: "M 3 8 L 6 12 L 12 5")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def dash
|
52
|
+
svg(
|
53
|
+
class: merge_class(svg_class, "group-has-[:indeterminate]/checkbox:visible"),
|
54
|
+
viewbox: "0 0 16 16",
|
55
|
+
fill: "none",
|
56
|
+
stroke: "currentColor",
|
57
|
+
stroke_linecap: "round",
|
58
|
+
stroke_width: 3
|
59
|
+
) do |svg|
|
60
|
+
svg.line(x1: "3", y1: "8", x2: "12", y2: "8")
|
59
61
|
end
|
60
62
|
end
|
63
|
+
|
64
|
+
def input_class
|
65
|
+
[
|
66
|
+
"appearance-none shadow-sm size-4 rounded-sm border text-foreground",
|
67
|
+
"checked:bg-primary checked:border-primary indeterminate:bg-primary indeterminate:border-primary",
|
68
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ring-offset-2 ring-offset-background"
|
69
|
+
]
|
70
|
+
end
|
71
|
+
|
72
|
+
def svg_class
|
73
|
+
"size-3 text-zinc-50 [&>svg]:size-full dark:text-zinc-950 pointer-events-none invisible"
|
74
|
+
end
|
75
|
+
|
76
|
+
def wrapper_class
|
77
|
+
"isolate inline-flex items-center gap-2"
|
78
|
+
end
|
61
79
|
end
|
62
80
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class CheckboxGroup < Component
|
5
|
+
def initialize(options = nil, **attrs)
|
6
|
+
@options = options
|
7
|
+
|
8
|
+
super(
|
9
|
+
attrs,
|
10
|
+
class: "flex items-start flex-col gap-2"
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
def view_template
|
17
|
+
div(**attrs) do
|
18
|
+
if block_given?
|
19
|
+
yield
|
20
|
+
else
|
21
|
+
options.map { |option| item(*option) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def title(text = nil, **attrs, &block)
|
27
|
+
render(Label.new(**attrs)) do
|
28
|
+
text_or_block(text, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def item(text = nil, **attrs, &block)
|
33
|
+
render(Checkbox.new(**attrs)) do
|
34
|
+
text_or_block(text, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Combobox < Component
|
5
|
+
def initialize(
|
6
|
+
options: [],
|
7
|
+
id: nil,
|
8
|
+
|
9
|
+
placement: "bottom",
|
10
|
+
tab_inserts_suggestions: true,
|
11
|
+
first_option_selection_mode: "selected",
|
12
|
+
scroll_into_view_options: {block: "nearest", inline: "nearest"},
|
13
|
+
|
14
|
+
**attrs
|
15
|
+
)
|
16
|
+
# floating-ui options
|
17
|
+
@placement = placement
|
18
|
+
|
19
|
+
# combobox-nav options
|
20
|
+
@tab_inserts_suggestions = tab_inserts_suggestions
|
21
|
+
@first_option_selection_mode = first_option_selection_mode
|
22
|
+
@scroll_into_view_options = scroll_into_view_options
|
23
|
+
|
24
|
+
@id = id || "nk--combobox-" + SecureRandom.hex(4)
|
25
|
+
|
26
|
+
@options = options
|
27
|
+
|
28
|
+
super(
|
29
|
+
attrs,
|
30
|
+
type: "text",
|
31
|
+
class: input_class,
|
32
|
+
data: {
|
33
|
+
nk__combobox_target: "input",
|
34
|
+
action: %w[
|
35
|
+
focusin->nk--combobox#open
|
36
|
+
focusin@window->nk--combobox#focusShift
|
37
|
+
click@window->nk--combobox#windowClick
|
38
|
+
input->nk--combobox#input
|
39
|
+
keydown.esc->nk--combobox#clear
|
40
|
+
keydown.down->nk--combobox#open
|
41
|
+
]
|
42
|
+
},
|
43
|
+
aria: {
|
44
|
+
controls: id(:listbox)
|
45
|
+
}
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_reader(
|
50
|
+
:options,
|
51
|
+
:placement,
|
52
|
+
:tab_inserts_suggestions,
|
53
|
+
:first_option_selection_mode,
|
54
|
+
:scroll_into_view_options
|
55
|
+
)
|
56
|
+
|
57
|
+
def view_template
|
58
|
+
div(
|
59
|
+
data: {
|
60
|
+
class: "isolate",
|
61
|
+
slot: "control",
|
62
|
+
controller: "nk--combobox",
|
63
|
+
nk__combobox_placement_value: placement,
|
64
|
+
nk__combobox_tab_inserts_suggestions_value: tab_inserts_suggestions.to_s,
|
65
|
+
nk__combobox_first_option_selection_mode_value: first_option_selection_mode.to_s,
|
66
|
+
nk__combobox_scroll_into_view_options_value: scroll_into_view_options&.to_json
|
67
|
+
}
|
68
|
+
) do
|
69
|
+
span(class: wrapper_class) do
|
70
|
+
render(Input.new(**attrs))
|
71
|
+
chevron_icon
|
72
|
+
end
|
73
|
+
|
74
|
+
# Since a combobox can function like a <select> element where the displayed
|
75
|
+
# value and the form value differ, include the value in a hidden field
|
76
|
+
input(
|
77
|
+
type: "hidden",
|
78
|
+
value: attrs[:value],
|
79
|
+
data: {nk__combobox_target: "hiddenField"}
|
80
|
+
)
|
81
|
+
|
82
|
+
ul(
|
83
|
+
role: "listbox",
|
84
|
+
id: id(:listbox),
|
85
|
+
class: list_class,
|
86
|
+
data: {nk__combobox_target: "list", state: "closed"}
|
87
|
+
) do
|
88
|
+
options.each do |(key, value)|
|
89
|
+
li(
|
90
|
+
role: "option",
|
91
|
+
data: {value:},
|
92
|
+
class: merge_class(option_class)
|
93
|
+
) { key }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def id(suffix)
|
102
|
+
"#{@id}-#{suffix}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def wrapper_class
|
106
|
+
"inline-grid *:[grid-area:1/1] group/combobox"
|
107
|
+
end
|
108
|
+
|
109
|
+
def input_class
|
110
|
+
"pr-8"
|
111
|
+
end
|
112
|
+
|
113
|
+
def list_class
|
114
|
+
[
|
115
|
+
"absolute top-0 left-0 p-1 bg-background rounded-md border shadow-sm w-fit max-w-sm flex-col flex z-10",
|
116
|
+
"max-h-60 overflow-y-auto",
|
117
|
+
"data-[state=closed]:hidden [&:not(:has([role=option]))]:hidden",
|
118
|
+
"[&_[aria-selected]]:bg-muted"
|
119
|
+
]
|
120
|
+
end
|
121
|
+
|
122
|
+
def option_class
|
123
|
+
"hidden flex-none px-2 py-1 rounded font-medium truncate cursor-pointer hover:bg-muted [&[role=option]]:block"
|
124
|
+
end
|
125
|
+
|
126
|
+
def chevron_icon
|
127
|
+
svg(
|
128
|
+
class: "size-4 self-center place-self-end mr-2 pointer-events-none text-muted-foreground group-hover/combobox:text-foreground",
|
129
|
+
viewbox: "0 0 24 24",
|
130
|
+
fill: "none",
|
131
|
+
stroke: "currentColor",
|
132
|
+
stroke_width: 1
|
133
|
+
) do |svg|
|
134
|
+
svg.path(d: "m6 9 6 6 6-6")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -1,29 +1,60 @@
|
|
1
|
-
|
2
|
-
Merger = TailwindMerge::Merger.new
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
module NitroKit
|
4
4
|
class Component < Phlex::HTML
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(**attrs)
|
8
|
-
@attrs = attrs.symbolize_keys
|
5
|
+
def initialize(*hashes, **defaults)
|
6
|
+
@attrs = merge_attrs(*hashes, **defaults)
|
9
7
|
end
|
10
8
|
|
11
9
|
attr_reader :attrs
|
12
10
|
|
13
|
-
|
14
|
-
|
11
|
+
private
|
12
|
+
|
13
|
+
# Merge attributes with some special cases for matching keys
|
14
|
+
def merge_attrs(*hashes, **defaults)
|
15
|
+
defaults.merge(*hashes) do |key, old_value, new_value|
|
16
|
+
case key
|
17
|
+
when :class
|
18
|
+
# Use TailwindMerge to merge class names
|
19
|
+
merge_class(old_value, new_value)
|
20
|
+
when :data
|
21
|
+
# Merge data hashes with some special cases for Stimulus
|
22
|
+
merge_data(old_value, new_value)
|
23
|
+
else
|
24
|
+
new_value
|
25
|
+
end
|
26
|
+
end
|
15
27
|
end
|
16
28
|
|
17
|
-
|
18
|
-
|
29
|
+
alias :mattr :merge_attrs
|
30
|
+
|
31
|
+
def merge_class(*args)
|
32
|
+
@@merger ||= TailwindMerge::Merger.new
|
33
|
+
@@merger.merge(args)
|
19
34
|
end
|
20
35
|
|
21
|
-
def
|
22
|
-
hashes.compact.
|
23
|
-
acc.deep_merge(hash) do |
|
24
|
-
|
36
|
+
def merge_data(*hashes)
|
37
|
+
hashes.compact.reduce({}) do |acc, hash|
|
38
|
+
acc.deep_merge(hash) do |key, old_val, new_val|
|
39
|
+
# Concat Stimulus actions
|
40
|
+
case key
|
41
|
+
when :action, :controller
|
42
|
+
[new_val, old_val].compact.join(" ")
|
43
|
+
else
|
44
|
+
new_val
|
45
|
+
end
|
25
46
|
end
|
26
47
|
end
|
27
48
|
end
|
49
|
+
|
50
|
+
def text_or_block(text = nil, &block)
|
51
|
+
if text
|
52
|
+
plain(text)
|
53
|
+
elsif block_given?
|
54
|
+
yield
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
28
59
|
end
|
29
60
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Dialog < Component
|
5
|
+
def initialize(identifier: nil, **attrs)
|
6
|
+
@identifier = identifier || SecureRandom.hex(6)
|
7
|
+
|
8
|
+
super(
|
9
|
+
attrs,
|
10
|
+
data: {controller: "nk--dialog", action: "click->nk--dialog#clickOutside"}
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :identifier
|
15
|
+
|
16
|
+
def view_template
|
17
|
+
div(**attrs) do
|
18
|
+
yield
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def trigger(text = nil, **attrs, &block)
|
23
|
+
render(
|
24
|
+
NitroKit::Button.new(**mattr(attrs, data: {nk__dialog_target: "trigger", action: "click->nk--dialog#open"}))
|
25
|
+
) do
|
26
|
+
text_or_block(text, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
alias :html_dialog :dialog
|
31
|
+
|
32
|
+
def dialog(**attrs)
|
33
|
+
html_dialog(
|
34
|
+
**mattr(
|
35
|
+
attrs,
|
36
|
+
class: dialog_class,
|
37
|
+
data: {nk__dialog_target: "dialog"},
|
38
|
+
aria: {
|
39
|
+
labelledby: id(:title),
|
40
|
+
describedby: id(:description)
|
41
|
+
}
|
42
|
+
)
|
43
|
+
) do
|
44
|
+
yield
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def close_button(**attrs)
|
49
|
+
render(
|
50
|
+
Button.new(
|
51
|
+
**mattr(
|
52
|
+
attrs,
|
53
|
+
variant: :ghost,
|
54
|
+
size: :sm,
|
55
|
+
class: "absolute top-2 right-2",
|
56
|
+
data: {action: "nk--dialog#close"}
|
57
|
+
)
|
58
|
+
)
|
59
|
+
) do
|
60
|
+
render(Icon.new(:x))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def title(text = nil, **attrs, &block)
|
65
|
+
h2(**mattr(attrs, id: id(:title), class: "text-lg font-semibold mb-2")) do
|
66
|
+
text_or_block(text, &block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def description(text = nil, **attrs, &block)
|
71
|
+
div(
|
72
|
+
**mattr(
|
73
|
+
attrs,
|
74
|
+
id: id(:description),
|
75
|
+
class: "text-muted-foreground mb-6 text-sm leading-relaxed"
|
76
|
+
)
|
77
|
+
) do
|
78
|
+
text_or_block(text, &block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def id(suffix = nil)
|
85
|
+
"nk-#{identifier}#{suffix ? "-#{suffix}" : ""}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def dialog_class
|
89
|
+
[
|
90
|
+
"border rounded-xl max-w-lg w-full bg-background text-foreground shadow-lg m-auto p-6",
|
91
|
+
"dark:backdrop:bg-black/50"
|
92
|
+
]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -1,111 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NitroKit
|
2
4
|
class Dropdown < Component
|
3
|
-
|
4
|
-
|
5
|
-
CONTENT = [
|
6
|
-
"w-max-content absolute top-0 left-0",
|
7
|
-
"p-1 bg-background rounded-md border shadow-sm",
|
8
|
-
"w-fit max-w-sm flex-col text-left",
|
9
|
-
"[&[aria-hidden=true]]:hidden flex"
|
10
|
-
].freeze
|
11
|
-
|
12
|
-
TRIGGER = "inline-block"
|
13
|
-
|
14
|
-
TITLE = "px-3 pt-2 pb-1.5 text-muted-foreground text-sm"
|
15
|
-
|
16
|
-
ITEM = [
|
17
|
-
"px-3 py-1.5 rounded",
|
18
|
-
"font-medium truncate",
|
19
|
-
"cursor-default"
|
20
|
-
].freeze
|
5
|
+
ITEM_VARIANTS = %i[default destructive]
|
21
6
|
|
22
|
-
|
23
|
-
default: ["hover:bg-muted"],
|
24
|
-
destructive: ["text-destructive-foreground hover:bg-destructive hover:text-white"]
|
25
|
-
}.freeze
|
26
|
-
|
27
|
-
SEPARATOR = "border-t my-1 -mx-1"
|
7
|
+
include Phlex::Rails::Helpers::LinkTo
|
28
8
|
|
29
9
|
def initialize(placement: nil, **attrs)
|
30
10
|
@placement = placement
|
31
|
-
|
11
|
+
|
12
|
+
super(
|
13
|
+
attrs,
|
14
|
+
data: {
|
15
|
+
controller: "nk--dropdown",
|
16
|
+
nk__dropdown_placement_value: placement
|
17
|
+
}
|
18
|
+
)
|
32
19
|
end
|
33
20
|
|
34
21
|
attr_reader :placement
|
35
22
|
|
36
|
-
def view_template
|
37
|
-
div(
|
38
|
-
|
39
|
-
|
40
|
-
attrs[:data]
|
41
|
-
),
|
42
|
-
&block
|
43
|
-
)
|
23
|
+
def view_template
|
24
|
+
div(**mattr(attrs)) do
|
25
|
+
yield
|
26
|
+
end
|
44
27
|
end
|
45
28
|
|
46
|
-
def trigger(**attrs, &block)
|
47
|
-
|
29
|
+
def trigger(text = nil, as: NitroKit::Button, **attrs, &block)
|
30
|
+
trigger_attrs = mattr(
|
31
|
+
attrs,
|
48
32
|
aria: {haspopup: "true", expanded: "false"},
|
49
|
-
|
50
|
-
class: merge([TRIGGER, attrs[:class]]),
|
51
|
-
data: data_merge(
|
52
|
-
{:"nk--dropdown-target" => "trigger", :action => "click->nk--dropdown#toggle"},
|
53
|
-
attrs[:data]
|
54
|
-
),
|
55
|
-
&block
|
33
|
+
data: {nk__dropdown_target: "trigger", action: "click->nk--dropdown#toggle"}
|
56
34
|
)
|
57
|
-
end
|
58
35
|
|
59
|
-
|
36
|
+
case as
|
37
|
+
when Symbol
|
38
|
+
send(as, **trigger_attrs) do
|
39
|
+
text_or_block(text, &block)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
render(as.new(**trigger_attrs)) do
|
43
|
+
text_or_block(text, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
60
47
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
)
|
48
|
+
def content(as: :div, **attrs)
|
49
|
+
div(
|
50
|
+
**mattr(
|
51
|
+
attrs,
|
52
|
+
role: "menu",
|
53
|
+
aria: {hidden: "true"},
|
54
|
+
class: content_class,
|
55
|
+
data: {nk__dropdown_target: "content"},
|
56
|
+
popover: true
|
57
|
+
)
|
58
|
+
) do
|
59
|
+
yield
|
60
|
+
end
|
72
61
|
end
|
73
62
|
|
74
63
|
def title(text = nil, **attrs, &block)
|
75
|
-
|
76
|
-
|
64
|
+
div(**mattr(attrs, class: title_class)) do
|
65
|
+
text_or_block(text, &block)
|
66
|
+
end
|
77
67
|
end
|
78
68
|
|
79
|
-
def item(
|
80
|
-
|
81
|
-
|
82
|
-
variant: :default,
|
83
|
-
**attrs
|
84
|
-
)
|
85
|
-
common_attrs = {
|
69
|
+
def item(text = nil, href: nil, variant: :default, **attrs, &block)
|
70
|
+
common_attrs = mattr(
|
71
|
+
attrs,
|
86
72
|
role: "menuitem",
|
87
73
|
tabindex: "-1",
|
88
|
-
|
89
|
-
|
90
|
-
}
|
74
|
+
class: [item_class, item_variant_class(variant)]
|
75
|
+
)
|
91
76
|
|
92
77
|
if href
|
93
78
|
link_to(href, **common_attrs) do
|
94
|
-
text
|
79
|
+
text_or_block(text, &block)
|
95
80
|
end
|
96
81
|
else
|
97
82
|
div(**common_attrs) do
|
98
|
-
text
|
83
|
+
text_or_block(text, &block)
|
99
84
|
end
|
100
85
|
end
|
101
86
|
end
|
102
87
|
|
88
|
+
def item_to(
|
89
|
+
text_or_href,
|
90
|
+
href = nil,
|
91
|
+
**attrs,
|
92
|
+
&block
|
93
|
+
)
|
94
|
+
href = text_or_href if block_given?
|
95
|
+
item(text_or_href, href: href, **attrs, &block)
|
96
|
+
end
|
97
|
+
|
103
98
|
def destructive_item(*args, **attrs, &block)
|
104
99
|
item(*args, **attrs, variant: :destructive, &block)
|
105
100
|
end
|
106
101
|
|
102
|
+
def destructive_item_to(text_or_block, href = nil, **attrs, &block)
|
103
|
+
href = args.shift if block_given?
|
104
|
+
destructive_item(text_or_block, href: href, **attrs, &block)
|
105
|
+
end
|
106
|
+
|
107
107
|
def separator
|
108
|
-
|
108
|
+
hr(class: separator_class)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def content_class
|
114
|
+
[
|
115
|
+
"z-10 w-max-content absolute top-0 left-0",
|
116
|
+
"p-1 bg-background text-foreground rounded-md border shadow-sm",
|
117
|
+
"w-fit max-w-sm flex-col text-left",
|
118
|
+
"[&[aria-hidden=true]]:hidden flex"
|
119
|
+
]
|
120
|
+
end
|
121
|
+
|
122
|
+
def trigger_class
|
123
|
+
""
|
124
|
+
end
|
125
|
+
|
126
|
+
def title_class
|
127
|
+
"px-3 pt-2 pb-1.5 text-muted-foreground text-sm"
|
128
|
+
end
|
129
|
+
|
130
|
+
def item_class
|
131
|
+
[
|
132
|
+
"px-3 py-1.5 rounded",
|
133
|
+
"font-medium truncate",
|
134
|
+
"cursor-default"
|
135
|
+
]
|
136
|
+
end
|
137
|
+
|
138
|
+
def item_variant_class(variant)
|
139
|
+
case variant
|
140
|
+
when :default
|
141
|
+
"[&[href]]:hover:bg-muted"
|
142
|
+
when :destructive
|
143
|
+
"text-destructive-foreground [&[href]]:hover:bg-destructive [&[href]]:hover:text-white"
|
144
|
+
else
|
145
|
+
raise ArgumentError, "Unknown variant: #{variant.inspect}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def separator_class
|
150
|
+
"border-t my-1 -mx-1"
|
109
151
|
end
|
110
152
|
end
|
111
153
|
end
|