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
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Pagination < Component
|
5
|
+
def initialize(**attrs)
|
6
|
+
super(
|
7
|
+
attrs,
|
8
|
+
class: merge_class(nav_class, attrs[:class]),
|
9
|
+
aria: {label: "Pagination"}
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def view_template
|
14
|
+
nav(**attrs) do
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def prev(text = nil, **attrs, &block)
|
20
|
+
page_link(**mattr(attrs, aria: {label: "Previous page"})) do
|
21
|
+
if text || block_given?
|
22
|
+
text_or_block(text, &block)
|
23
|
+
else
|
24
|
+
render(Icon.new("arrow-left"))
|
25
|
+
plain("Previous")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def next(text = nil, **attrs, &block)
|
31
|
+
page_link(**mattr(attrs, aria: {label: "Next page"})) do
|
32
|
+
if text || block_given?
|
33
|
+
text_or_block(text, &block)
|
34
|
+
else
|
35
|
+
plain("Next")
|
36
|
+
render(Icon.new("arrow-right"))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def page(text = nil, current: false, **attrs, &block)
|
42
|
+
page_link(
|
43
|
+
**mattr(
|
44
|
+
attrs,
|
45
|
+
aria: {
|
46
|
+
current: current ? "page" : nil
|
47
|
+
},
|
48
|
+
disabled: current,
|
49
|
+
class: [page_class, current && "bg-zinc-200/50 dark:bg-zinc-800/50"]
|
50
|
+
)
|
51
|
+
) do
|
52
|
+
text_or_block(text, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def ellipsis(**attrs)
|
57
|
+
render(
|
58
|
+
Button.new(
|
59
|
+
**mattr(
|
60
|
+
attrs,
|
61
|
+
variant: :ghost,
|
62
|
+
disabled: true,
|
63
|
+
class: page_class
|
64
|
+
)
|
65
|
+
)
|
66
|
+
) do
|
67
|
+
"…"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def page_link(text = nil, disabled: nil, **attrs)
|
74
|
+
a(
|
75
|
+
**mattr(
|
76
|
+
attrs,
|
77
|
+
role: "link",
|
78
|
+
aria: {disabled: disabled.to_s},
|
79
|
+
class: link_class
|
80
|
+
)
|
81
|
+
) do
|
82
|
+
yield
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def nav_class
|
87
|
+
"flex items-center justify-center gap-1 text-sm font-medium"
|
88
|
+
end
|
89
|
+
|
90
|
+
def link_class
|
91
|
+
"inline-flex items-center justify-center rounded-md border font-medium h-9 px-3 gap-2 border-transparent aria-disabled:text-muted-foreground [&>svg]:size-4"
|
92
|
+
end
|
93
|
+
|
94
|
+
def page_class
|
95
|
+
"w-9 px-0"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -1,44 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NitroKit
|
2
4
|
class RadioButton < Component
|
3
5
|
include ActionView::Helpers::FormTagHelper
|
4
6
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
+
def initialize(label: nil, **attrs)
|
8
|
+
@label = label
|
9
|
+
@id = id || SecureRandom.hex(4)
|
10
|
+
|
11
|
+
@class = attrs.delete(:class)
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
13
|
+
super(
|
14
|
+
attrs,
|
15
|
+
type: "radio",
|
16
|
+
id: @id,
|
17
|
+
class: [
|
18
|
+
"peer appearance-none size-5 shadow-sm rounded-full border text-foreground bg-background",
|
19
|
+
"[&[aria-checked='true']]:bg-primary",
|
20
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
21
|
+
]
|
22
|
+
)
|
12
23
|
end
|
13
24
|
|
25
|
+
alias :html_label :label
|
26
|
+
|
14
27
|
attr_reader(
|
15
|
-
:name,
|
16
|
-
:value,
|
17
28
|
:id,
|
18
|
-
:
|
29
|
+
:label
|
19
30
|
)
|
20
31
|
|
21
32
|
def view_template
|
22
|
-
div(class:
|
23
|
-
|
24
|
-
input(
|
25
|
-
**attrs,
|
26
|
-
type: "radio",
|
27
|
-
name:,
|
28
|
-
value:,
|
29
|
-
id:,
|
30
|
-
class: class_names(
|
31
|
-
"peer row-start-1 col-start-1",
|
32
|
-
"appearance-none size-5 shadow-sm rounded-full border text-foreground bg-background",
|
33
|
-
"[&[aria-checked='true']]:bg-primary",
|
34
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
35
|
-
)
|
36
|
-
)
|
33
|
+
div(class: merge_class("inline-flex items-center gap-2", @class)) do
|
34
|
+
html_label(class: "inline-grid *:[grid-area:1/1] place-items-center") do
|
35
|
+
input(**attrs)
|
37
36
|
dot
|
38
37
|
end
|
39
38
|
|
40
|
-
if
|
41
|
-
render(Label.new(for: id))
|
39
|
+
if label.present? || block_given?
|
40
|
+
render(Label.new(for: id)) do
|
41
|
+
label || (block_given? ? yield : nil)
|
42
|
+
end
|
42
43
|
end
|
43
44
|
end
|
44
45
|
end
|
@@ -47,7 +48,7 @@ module NitroKit
|
|
47
48
|
|
48
49
|
def dot
|
49
50
|
svg(
|
50
|
-
class:
|
51
|
+
class: merge_class(
|
51
52
|
"row-start-1 col-start-1",
|
52
53
|
"size-2.5 text-primay opacity-0 pointer-events-none",
|
53
54
|
"peer-checked:opacity-100"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class RadioButtonGroup < Component
|
5
|
+
def initialize(options = nil, name: nil, value: nil, **attrs)
|
6
|
+
@options = options
|
7
|
+
|
8
|
+
@name = name
|
9
|
+
@group_value = value
|
10
|
+
|
11
|
+
super(
|
12
|
+
attrs,
|
13
|
+
name: @name,
|
14
|
+
class: "flex items-start flex-col gap-2"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :name, :group_value, :options
|
19
|
+
|
20
|
+
def view_template
|
21
|
+
div(**attrs) do
|
22
|
+
if block_given?
|
23
|
+
yield
|
24
|
+
else
|
25
|
+
options.map { |o| item(*o) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def title(text = nil, **attrs, &block)
|
31
|
+
render(Label.new(**attrs)) do
|
32
|
+
text_or_block(text, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def item(text = nil, value_as_arg = nil, value: nil, **attrs, &block)
|
37
|
+
value ||= value_as_arg
|
38
|
+
|
39
|
+
render(
|
40
|
+
RadioButton.new(
|
41
|
+
**mattr(
|
42
|
+
attrs,
|
43
|
+
name: attrs.fetch(:name, name),
|
44
|
+
value:,
|
45
|
+
checked: group_value.presence == value
|
46
|
+
)
|
47
|
+
)
|
48
|
+
) do
|
49
|
+
text_or_block(text, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Select < Component
|
5
|
+
def initialize(options = nil, value: nil, include_empty: false, prompt: nil, index: nil, **attrs)
|
6
|
+
@options = options
|
7
|
+
@value = value
|
8
|
+
@include_empty = include_empty
|
9
|
+
@prompt = prompt
|
10
|
+
@index = index
|
11
|
+
|
12
|
+
super(
|
13
|
+
attrs,
|
14
|
+
class: wrapper_class
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :value, :options, :include_empty, :prompts, :index
|
19
|
+
|
20
|
+
def view_template
|
21
|
+
span(class: wrapper_class, data: {slot: "control"}) do
|
22
|
+
select(**attrs, class: select_class) do
|
23
|
+
option if include_empty
|
24
|
+
options ? options.map { |o| option(*o) } : yield
|
25
|
+
end
|
26
|
+
|
27
|
+
chevron_icon
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :html_option :option
|
32
|
+
|
33
|
+
def option(key_or_value = nil, value = nil, **attrs, &block)
|
34
|
+
value ||= key_or_value
|
35
|
+
|
36
|
+
html_option(**attrs, selected: @value == value) do
|
37
|
+
if block_given?
|
38
|
+
yield
|
39
|
+
else
|
40
|
+
key_or_value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def wrapper_class
|
48
|
+
"w-fit inline-grid *:[grid-area:1/1] group/select"
|
49
|
+
end
|
50
|
+
|
51
|
+
def select_class
|
52
|
+
[
|
53
|
+
"appearance-none bg-background text-foreground rounded-md border px-3 py-2 pr-10 w-full",
|
54
|
+
# Focus
|
55
|
+
"focus:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2"
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
def chevron_icon
|
60
|
+
svg(
|
61
|
+
class: "size-4 self-center place-self-end mr-1.5 text-muted-foreground pointer-events-none group-hover/select:text-foreground",
|
62
|
+
viewbox: "0 0 24 24",
|
63
|
+
fill: "none",
|
64
|
+
stroke: "currentColor",
|
65
|
+
stroke_width: 1
|
66
|
+
) do |svg|
|
67
|
+
svg.path(d: "m7 15 5 5 5-5")
|
68
|
+
svg.path(d: "m7 9 5-5 5 5")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,56 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module NitroKit
|
2
4
|
class Switch < Component
|
3
|
-
BUTTON = [
|
4
|
-
"inline-flex items-center shrink-0",
|
5
|
-
"bg-background rounded-full border",
|
6
|
-
"transition-colors duration-200 ease-in-out",
|
7
|
-
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ring-offset-background",
|
8
|
-
|
9
|
-
# Checked
|
10
|
-
"[&[aria-checked='true']]:bg-foreground [&[aria-checked='true']]:border-foreground",
|
11
|
-
|
12
|
-
# Checked > Handle
|
13
|
-
"[&[aria-checked='false']_[data-slot='handle']]:bg-primary",
|
14
|
-
"[&[aria-checked='true']_[data-slot='handle']]:translate-x-[calc(theme(spacing.5)-1px)] [&[aria-checked='true']_[data-slot='handle']]:bg-background"
|
15
|
-
].freeze
|
16
|
-
|
17
|
-
HANDLE = [
|
18
|
-
"pointer-events-none inline-block rounded-full shadow-sm ring-0",
|
19
|
-
"transition translate-x-[3px] duration-200 ease-in-out"
|
20
|
-
].freeze
|
21
|
-
|
22
|
-
SIZE = {
|
23
|
-
base: "h-6 w-10 [&_[data-slot=handle]]:size-4 [&[aria-checked='true']_[data-slot='handle']]:translate-x-[calc(theme(spacing.5)-1px)]",
|
24
|
-
sm: "h-5 w-8 [&_[data-slot=handle]]:size-3 [&[aria-checked='true']_[data-slot='handle']]:translate-x-[calc(theme(spacing.4)-1px)]"
|
25
|
-
}
|
26
|
-
|
27
5
|
def initialize(
|
28
|
-
name,
|
29
6
|
checked: false,
|
30
|
-
|
31
|
-
size: :base,
|
7
|
+
size: :md,
|
32
8
|
description: nil,
|
33
9
|
**attrs
|
34
10
|
)
|
35
|
-
super(**attrs)
|
36
|
-
|
37
|
-
@name = name
|
38
11
|
@checked = checked
|
39
|
-
@disabled = disabled
|
40
12
|
@size = size
|
41
13
|
@description = description
|
14
|
+
|
15
|
+
super(**attrs)
|
42
16
|
end
|
43
17
|
|
44
|
-
attr_reader :
|
18
|
+
attr_reader :checked, :description, :size
|
45
19
|
|
46
20
|
def view_template
|
47
21
|
button(
|
48
|
-
**
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
22
|
+
**mattr(
|
23
|
+
**attrs,
|
24
|
+
type: "button",
|
25
|
+
class: [base_class, size_class],
|
26
|
+
data: {controller: "nk--switch", action: "nk--switch#toggle"},
|
27
|
+
role: "switch",
|
28
|
+
aria: {checked: checked.to_s}
|
29
|
+
)
|
54
30
|
) do
|
55
31
|
span(class: "sr-only") { description }
|
56
32
|
handle
|
@@ -60,7 +36,41 @@ module NitroKit
|
|
60
36
|
private
|
61
37
|
|
62
38
|
def handle
|
63
|
-
span(aria: {hidden: true}, class:
|
39
|
+
span(aria: {hidden: true}, class: handle_class, data: {slot: "handle"})
|
40
|
+
end
|
41
|
+
|
42
|
+
def base_class
|
43
|
+
[
|
44
|
+
"inline-flex items-center shrink-0",
|
45
|
+
"bg-background rounded-full border",
|
46
|
+
"transition-colors duration-200 ease-in-out",
|
47
|
+
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ring-offset-background",
|
48
|
+
|
49
|
+
# Checked
|
50
|
+
"[&[aria-checked=true]]:bg-foreground [&[aria-checked=true]]:border-foreground",
|
51
|
+
|
52
|
+
# Checked > Handle
|
53
|
+
"[&[aria-checked=false]_[data-slot=handle]]:bg-primary",
|
54
|
+
"[&[aria-checked=true]_[data-slot=handle]]:bg-background"
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_class
|
59
|
+
[
|
60
|
+
"pointer-events-none inline-block rounded-full shadow-sm ring-0",
|
61
|
+
"transition translate-x-[3px] duration-200 ease-in-out"
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
65
|
+
def size_class
|
66
|
+
case size
|
67
|
+
when :md
|
68
|
+
"h-6 w-10 [&_[data-slot=handle]]:size-4 [&[aria-checked=true]_[data-slot=handle]]:translate-x-[calc(theme(spacing.5)-1px)]"
|
69
|
+
when :sm
|
70
|
+
"h-5 w-8 [&_[data-slot=handle]]:size-3 [&[aria-checked=true]_[data-slot=handle]]:translate-x-[calc(theme(spacing.4)-1px)]"
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Unknown size: #{size}"
|
73
|
+
end
|
64
74
|
end
|
65
75
|
end
|
66
76
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Table < Component
|
5
|
+
def initialize(**attrs)
|
6
|
+
super(
|
7
|
+
attrs,
|
8
|
+
class: "w-full caption-bottom text-sm divide-y"
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_template
|
13
|
+
div(class: "relative w-full overflow-auto") do
|
14
|
+
table(**attrs) do
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
alias :html_thead :thead
|
21
|
+
alias :html_tbody :tbody
|
22
|
+
alias :html_tr :tr
|
23
|
+
alias :html_th :th
|
24
|
+
alias :html_td :td
|
25
|
+
|
26
|
+
def thead(**attrs)
|
27
|
+
html_thead(**attrs) { yield }
|
28
|
+
end
|
29
|
+
|
30
|
+
def tbody(**attrs)
|
31
|
+
html_tbody(**mattr(attrs, class: "[&_tr:last-child]:border-0")) { yield }
|
32
|
+
end
|
33
|
+
|
34
|
+
def tr(**attrs)
|
35
|
+
html_tr(**mattr(attrs, class: "border-b")) { yield }
|
36
|
+
end
|
37
|
+
|
38
|
+
def th(text = nil, **attrs, &block)
|
39
|
+
html_th(**mattr(attrs, class: [cell_classes, "font-medium text-left"])) do
|
40
|
+
text_or_block(text, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def td(text = nil, **attrs, &block)
|
45
|
+
html_td(**mattr(attrs, class: cell_classes)) do
|
46
|
+
text_or_block(text, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def cell_classes
|
53
|
+
"py-3 px-2"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Tabs < Component
|
5
|
+
def initialize(default: nil, **attrs)
|
6
|
+
@default = default
|
7
|
+
@id = attrs[:id] || SecureRandom.hex(6)
|
8
|
+
|
9
|
+
super(
|
10
|
+
attrs,
|
11
|
+
data: {controller: "nk--tabs", nk__tabs_active_value: default},
|
12
|
+
class: base_class
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :default, :id
|
17
|
+
|
18
|
+
def view_template
|
19
|
+
div(**attrs) do
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def tabs(**attrs)
|
25
|
+
div(**mattr, role: "tabtabs", class: tabs_class) do
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def tab(key, text = nil, **attrs, &block)
|
31
|
+
button(
|
32
|
+
**mattr(
|
33
|
+
attrs,
|
34
|
+
aria: {
|
35
|
+
selected: (default == key).to_s,
|
36
|
+
controls: tab_id(key, :panel)
|
37
|
+
},
|
38
|
+
class: tab_class,
|
39
|
+
data: {
|
40
|
+
action: "nk--tabs#setActiveTab keydown.left->nk--tabs#prevTab keydown.right->nk--tabs#nextTab",
|
41
|
+
key:,
|
42
|
+
nk__tabs_key_param: key,
|
43
|
+
nk__tabs_target: "tab"
|
44
|
+
},
|
45
|
+
id: tab_id(key, :tab),
|
46
|
+
role: "tab",
|
47
|
+
tabindex: default == key ? 0 : -1
|
48
|
+
)
|
49
|
+
) do
|
50
|
+
text_or_block(text, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def panel(key, **attrs)
|
55
|
+
div(
|
56
|
+
**mattr(
|
57
|
+
attrs,
|
58
|
+
aria: {
|
59
|
+
hidden: (default != key).to_s,
|
60
|
+
labelledby: tab_id(key, :tab)
|
61
|
+
},
|
62
|
+
class: panel_class,
|
63
|
+
data: {
|
64
|
+
key:,
|
65
|
+
nk__tabs_target: "panel"
|
66
|
+
},
|
67
|
+
id: tab_id(key, :panel),
|
68
|
+
name: key,
|
69
|
+
role: "tabpanel"
|
70
|
+
)
|
71
|
+
) do
|
72
|
+
yield
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def tab_id(key, suffix)
|
79
|
+
"#{id}-#{key}-#{suffix}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def base_class
|
83
|
+
"w-full"
|
84
|
+
end
|
85
|
+
|
86
|
+
def tabs_class
|
87
|
+
"flex gap-4 border-b h-10"
|
88
|
+
end
|
89
|
+
|
90
|
+
def tab_class
|
91
|
+
"border-b-2 border-transparent hover:border-primary focus-visible:border-primary cursor-pointer text-muted-foreground aria-[selected=true]:text-foreground font-medium aria-[selected=true]:border-primary -mb-px px-2"
|
92
|
+
end
|
93
|
+
|
94
|
+
def panel_class
|
95
|
+
"aria-[hidden=true]:hidden py-4"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Textarea < Component
|
5
|
+
def initialize(**attrs)
|
6
|
+
super(
|
7
|
+
attrs,
|
8
|
+
class: default_class
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_template
|
13
|
+
textarea(**attrs)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def default_class
|
19
|
+
[
|
20
|
+
"rounded-md border bg-background border-border text-base px-3 py-2",
|
21
|
+
# Focus
|
22
|
+
"focus:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2"
|
23
|
+
]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class Toast < Component
|
5
|
+
class Item < Component
|
6
|
+
VARIANTS = %i[default warning error success]
|
7
|
+
|
8
|
+
def initialize(title: nil, description: nil, variant: :default, **attrs)
|
9
|
+
@title = title
|
10
|
+
@description = description
|
11
|
+
@variant = variant
|
12
|
+
|
13
|
+
super(
|
14
|
+
**mattr(
|
15
|
+
attrs,
|
16
|
+
class: [
|
17
|
+
base_class,
|
18
|
+
variant_class
|
19
|
+
],
|
20
|
+
role: "status",
|
21
|
+
aria: {live: "off", atomic: "true"},
|
22
|
+
tabindex: "0",
|
23
|
+
data: {state: "closed"}
|
24
|
+
)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :title, :description, :variant
|
29
|
+
|
30
|
+
def view_template(&block)
|
31
|
+
li(**attrs) do
|
32
|
+
div(class: "grid gap-1") do
|
33
|
+
div(class: "text-sm font-semibold", data: {slot: "title"}) do
|
34
|
+
title && plain(title)
|
35
|
+
end
|
36
|
+
|
37
|
+
div(class: "text-sm opacity-90", data: {slot: "description"}) do
|
38
|
+
text_or_block(description, &block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def base_class
|
47
|
+
"shrink-0 pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md p-4 pr-8 shadow-lg transition-all border opacity-0 transition-all data-[state=open]:opacity-100 data-[state=open]:-translate-y-full"
|
48
|
+
end
|
49
|
+
|
50
|
+
def variant_class
|
51
|
+
case variant
|
52
|
+
when :default
|
53
|
+
"border-border bg-background text-foreground"
|
54
|
+
when :warning
|
55
|
+
"bg-yellow-50 dark:bg-yellow-950 text-yellow-900 dark:text-yellow-100 border-yellow-500/80 dark:border-yellow-400/50"
|
56
|
+
when :success
|
57
|
+
"bg-green-50 dark:bg-green-950 text-green-900 dark:text-green-100 border-green-500/80 dark:border-green-400/50"
|
58
|
+
when :error
|
59
|
+
"bg-red-50 dark:bg-red-950 text-red-900 dark:text-red-100 border-red-400/80 dark:border-red-400/50"
|
60
|
+
else
|
61
|
+
raise ArgumentError, "Invalid variant `#{variant}'"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(**attrs)
|
67
|
+
super(
|
68
|
+
attrs,
|
69
|
+
role: "region",
|
70
|
+
tabindex: "-1",
|
71
|
+
aria: {label: "Notifications"},
|
72
|
+
class: "pointer-events-none"
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def view_template
|
77
|
+
div(**attrs) do
|
78
|
+
ol(class: list_class, data: {nk__toast_target: "list"})
|
79
|
+
end
|
80
|
+
|
81
|
+
flash_sink
|
82
|
+
|
83
|
+
template(data: {nk__toast_target: "template"}) do
|
84
|
+
item
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def item(title: nil, description: nil, **attrs, &block)
|
89
|
+
render(Item.new(title:, description:, **attrs), &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def flash_sink
|
93
|
+
div(id: "nk--toast-sink", data: {nk__toast_target: "sink"}, hidden: true) do
|
94
|
+
helpers.nk_toast_flash_messages
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def list_class
|
101
|
+
"fixed z-[100] flex max-h-screen w-full p-5 bottom-0 right-0 flex-col h-0 md:max-w-[420px]"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|