nitro_kit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +1 -0
- data/Rakefile +6 -0
- data/app/components/nitro_kit/accordion.rb +60 -0
- data/app/components/nitro_kit/badge.rb +34 -0
- data/app/components/nitro_kit/button.rb +109 -0
- data/app/components/nitro_kit/button_group.rb +19 -0
- data/app/components/nitro_kit/card.rb +23 -0
- data/app/components/nitro_kit/checkbox.rb +62 -0
- data/app/components/nitro_kit/component.rb +31 -0
- data/app/components/nitro_kit/dropdown.rb +110 -0
- data/app/components/nitro_kit/field.rb +37 -0
- data/app/components/nitro_kit/field_group.rb +14 -0
- data/app/components/nitro_kit/fieldset.rb +16 -0
- data/app/components/nitro_kit/form_builder.rb +43 -0
- data/app/components/nitro_kit/icon.rb +23 -0
- data/app/components/nitro_kit/label.rb +10 -0
- data/app/components/nitro_kit/radio_button.rb +63 -0
- data/app/components/nitro_kit/radio_group.rb +35 -0
- data/app/components/nitro_kit/switch.rb +66 -0
- data/app/helpers/application_helper.rb +89 -0
- data/app/helpers/nitro_kit/accordion_helper.rb +7 -0
- data/app/helpers/nitro_kit/badge_helper.rb +15 -0
- data/app/helpers/nitro_kit/button_group_helper.rb +7 -0
- data/app/helpers/nitro_kit/button_helper.rb +40 -0
- data/app/helpers/nitro_kit/card_helper.rb +7 -0
- data/app/helpers/nitro_kit/checkbox_helper.rb +24 -0
- data/app/helpers/nitro_kit/dropdown_helper.rb +7 -0
- data/app/helpers/nitro_kit/field_helper.rb +7 -0
- data/app/helpers/nitro_kit/icon_helper.rb +7 -0
- data/app/helpers/nitro_kit/label_helper.rb +15 -0
- data/app/helpers/nitro_kit/radio_button_helper.rb +20 -0
- data/app/helpers/nitro_kit/switch_helper.rb +15 -0
- data/lib/generators/nitro_kit/add_generator.rb +87 -0
- data/lib/generators/nitro_kit/install_generator.rb +9 -0
- data/lib/nitro_kit/railtie.rb +8 -0
- data/lib/nitro_kit/schema_builder.rb +47 -0
- data/lib/nitro_kit/variants.rb +21 -0
- data/lib/nitro_kit/version.rb +3 -0
- data/lib/nitro_kit.rb +8 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 738be78aa004f1a57e3b2f1b4f4c72b7e11b055e0e8a543db2205c414e785b60
|
4
|
+
data.tar.gz: 60d0b5bf5afa57cf8710838f13f5f586c18dc48ad04f7e503ec362c3ea372cc0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6a9e5e794e437dc0e27765affc79414ca3caf90c4aa5747929d93db3b4e7a2ef2fda41f01758af31ca5ea6c0431f9615de3c241faca1fc90be0b243499884bdc
|
7
|
+
data.tar.gz: ed4904af0e0499b2c947baf1e0c7016e2a134a8045288311b05eefe8aaac30985ebc0da85cbf5e47b97548dd650eec0155ba1ee6b3ef84c23e51a45203bbbc23
|
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
_Nothing to see here yet, move along_
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Accordion < Component
|
3
|
+
ITEM = "divide-y"
|
4
|
+
|
5
|
+
TRIGGER = [
|
6
|
+
"flex w-full items-center justify-between py-4 font-medium cursor-pointer",
|
7
|
+
"group/accordion-trigger hover:underline transition-colors",
|
8
|
+
"[&[aria-expanded='true']>svg]:rotate-180",
|
9
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
CONTENT = [
|
13
|
+
"overflow-hidden transition-all duration-200",
|
14
|
+
"[&[aria-hidden='true']]:h-0 [&[aria-hidden='false']]:h-auto"
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
ARROW = "transition-transform duration-200 text-muted-foreground group-hover/accordion-trigger:text-primary"
|
18
|
+
|
19
|
+
def view_template
|
20
|
+
div(
|
21
|
+
**attrs,
|
22
|
+
class: merge([ITEM, class_list]),
|
23
|
+
data: { controller: "nk--accordion" }
|
24
|
+
) do
|
25
|
+
yield
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def item(**attrs)
|
30
|
+
div(**attrs) do
|
31
|
+
yield
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def trigger(text = nil, **attrs)
|
36
|
+
button(
|
37
|
+
type: "button",
|
38
|
+
class: TRIGGER,
|
39
|
+
data: {
|
40
|
+
action: "nk--accordion#toggle",
|
41
|
+
"nk--accordion-target": "trigger"
|
42
|
+
},
|
43
|
+
aria: { expanded: "false", controls: "content" }
|
44
|
+
) do
|
45
|
+
div(**attrs) { text || yield }
|
46
|
+
render NitroKit::Icon.new(name: "chevron-down", class: ARROW)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def content(**attrs)
|
51
|
+
div(
|
52
|
+
class: merge(CONTENT),
|
53
|
+
data: { "nk--accordion-target": "content" },
|
54
|
+
aria: { hidden: "true" }
|
55
|
+
) do
|
56
|
+
div(class: "pb-4") { yield }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Badge < Component
|
3
|
+
BASE = "inline-flex items-center gap-x-1.5 rounded-md font-medium"
|
4
|
+
|
5
|
+
VARIANTS = {
|
6
|
+
default: "border border-transparent bg-zinc-200 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300",
|
7
|
+
outline: "border"
|
8
|
+
}
|
9
|
+
|
10
|
+
SIZES = {
|
11
|
+
sm: "text-xs px-1.5 py-0.5",
|
12
|
+
md: "text-sm px-2 py-0.5"
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(variant: :default, size: :md, **attrs)
|
16
|
+
@attrs = attrs
|
17
|
+
|
18
|
+
@class_list = merge(
|
19
|
+
[
|
20
|
+
BASE,
|
21
|
+
VARIANTS[variant],
|
22
|
+
SIZES[size],
|
23
|
+
attrs[:class]
|
24
|
+
]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :color, :attrs, :class_list
|
29
|
+
|
30
|
+
def view_template(&block)
|
31
|
+
span(**attrs, class: class_list, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Button < Component
|
3
|
+
BASE = [
|
4
|
+
"inline-flex items-center cursor-pointer shrink-0 justify-center rounded-md border gap-2 font-medium",
|
5
|
+
# Disabled
|
6
|
+
"disabled:opacity-70 disabled:pointer-events-none",
|
7
|
+
# Focus
|
8
|
+
"focus:outline-none focus:ring-[3px] focus:ring-offset-2 focus:ring-ring ring-offset-background",
|
9
|
+
# Icon
|
10
|
+
"[&_svg]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
11
|
+
# If icon only, make square
|
12
|
+
"[&_svg:first-child:last-child]:-mx-2"
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
VARIANTS = {
|
16
|
+
default: [
|
17
|
+
"bg-background text-foreground",
|
18
|
+
"hover:bg-zinc-50 dark:hover:bg-zinc-900"
|
19
|
+
],
|
20
|
+
primary: [
|
21
|
+
"bg-primary text-white dark:text-zinc-950 border-primary",
|
22
|
+
"hover:bg-primary/90 dark:hover:bg-primary/90"
|
23
|
+
],
|
24
|
+
destructive: [
|
25
|
+
"bg-destructive text-white border-destructive",
|
26
|
+
"hover:bg-destructive/90 dark:hover:bg-destructive/90",
|
27
|
+
"disabled:text-white/80"
|
28
|
+
],
|
29
|
+
ghost: [
|
30
|
+
"bg-transparent text-foreground border-transparent",
|
31
|
+
"hover:bg-zinc-50 dark:hover:bg-zinc-900",
|
32
|
+
"disabled:text-muted-foreground"
|
33
|
+
]
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
SIZES = {
|
37
|
+
base: "px-4 h-9",
|
38
|
+
sm: "px-2.5 h-7 text-sm",
|
39
|
+
xs: "px-1.5 h-6 text-xs"
|
40
|
+
}
|
41
|
+
|
42
|
+
def initialize(
|
43
|
+
href: nil,
|
44
|
+
icon: nil,
|
45
|
+
icon_right: nil,
|
46
|
+
size: :base,
|
47
|
+
type: :button,
|
48
|
+
variant: :default,
|
49
|
+
**attrs
|
50
|
+
)
|
51
|
+
@href = href
|
52
|
+
@icon = icon
|
53
|
+
@icon_right = icon_right
|
54
|
+
@size = size
|
55
|
+
@type = type
|
56
|
+
@variant = variant
|
57
|
+
@attrs = attrs
|
58
|
+
|
59
|
+
@class_list = merge(
|
60
|
+
[
|
61
|
+
BASE,
|
62
|
+
VARIANTS[variant],
|
63
|
+
SIZES[size],
|
64
|
+
attrs[:class]
|
65
|
+
]
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
attr_reader(
|
70
|
+
:class_list,
|
71
|
+
:href,
|
72
|
+
:icon,
|
73
|
+
:icon_right,
|
74
|
+
:size,
|
75
|
+
:type,
|
76
|
+
:variant,
|
77
|
+
:attrs
|
78
|
+
)
|
79
|
+
|
80
|
+
def view_template(&block)
|
81
|
+
if href
|
82
|
+
a(href:, **attrs, class: class_list) do
|
83
|
+
contents(&block)
|
84
|
+
end
|
85
|
+
else
|
86
|
+
button(type:, **attrs, class: class_list) do
|
87
|
+
contents(&block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def contents
|
95
|
+
text = safe(capture { yield })
|
96
|
+
has_text = text.to_s.present?
|
97
|
+
|
98
|
+
if !icon && has_text && !icon_right
|
99
|
+
return text
|
100
|
+
elsif icon && !has_text && !icon_right
|
101
|
+
return render(Icon.new(name: icon))
|
102
|
+
end
|
103
|
+
|
104
|
+
render(Icon.new(name: icon)) if icon
|
105
|
+
span { text }
|
106
|
+
render(Icon.new(name: icon_right)) if icon_right
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class ButtonGroup < Component
|
3
|
+
def view_template(&block)
|
4
|
+
div(
|
5
|
+
class: merge(
|
6
|
+
[
|
7
|
+
"flex -space-x-px isolate",
|
8
|
+
# Remove rounded corners from middle buttons
|
9
|
+
"[&>*:not(:first-child):not(:last-child)]:rounded-none [&>*:first-child:not(:last-child)]:rounded-r-none [&>*:last-child:not(:first-child)]:rounded-l-none",
|
10
|
+
# Put focused button on top
|
11
|
+
"[&>*]:focus:z-10",
|
12
|
+
attrs[:class]
|
13
|
+
]
|
14
|
+
),
|
15
|
+
&block
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Card < Component
|
3
|
+
def initialize(**attrs)
|
4
|
+
@attrs = attrs
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template(&block)
|
8
|
+
div(class: "rounded-lg border p-6 space-y-6 shadow-sm", &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def title(**attrs)
|
12
|
+
h2(**attrs, class: merge(["text-lg font-bold", attrs[:class]])) { yield }
|
13
|
+
end
|
14
|
+
|
15
|
+
def body(**attrs)
|
16
|
+
div(**attrs) { yield }
|
17
|
+
end
|
18
|
+
|
19
|
+
def footer(**attrs)
|
20
|
+
div(**attrs, class: merge(["flex gap-2", attrs[:class]])) { yield }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Checkbox < Component
|
3
|
+
include ActionView::Helpers::FormTagHelper
|
4
|
+
|
5
|
+
def initialize(name, value: "1", label: nil, **attrs)
|
6
|
+
super(**attrs)
|
7
|
+
|
8
|
+
@name = name
|
9
|
+
@label_text = label
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader(
|
13
|
+
:name,
|
14
|
+
:value,
|
15
|
+
:label_text
|
16
|
+
)
|
17
|
+
|
18
|
+
def view_template
|
19
|
+
div(class: merge(["isolate inline-flex items-center gap-2", class_list])) do
|
20
|
+
label(class: "relative flex shrink-0") do
|
21
|
+
input(
|
22
|
+
**attrs,
|
23
|
+
type: "checkbox",
|
24
|
+
class: class_names(
|
25
|
+
"peer appearance-none shadow size-4 rounded 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
|
+
)
|
30
|
+
checkmark
|
31
|
+
end
|
32
|
+
|
33
|
+
if label_text.present?
|
34
|
+
render(Label.new(for: attrs[:id])) { label_text }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def checkmark
|
42
|
+
span(
|
43
|
+
class: class_names(
|
44
|
+
"absolute w-full h-full top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2",
|
45
|
+
"text-zinc-50 dark:text-zinc-950 opacity-0 peer-checked:opacity-100 pointer-events-none"
|
46
|
+
)
|
47
|
+
) do
|
48
|
+
svg(
|
49
|
+
class: "size-full",
|
50
|
+
viewbox: "0 0 20 20",
|
51
|
+
fill: "currentColor",
|
52
|
+
stroke: "currentColor",
|
53
|
+
stroke_width: 1
|
54
|
+
) do |svg|
|
55
|
+
svg.path(
|
56
|
+
"d" => "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Component < Phlex::HTML
|
3
|
+
attr_reader :attrs, :class_list
|
4
|
+
|
5
|
+
def initialize(**attrs)
|
6
|
+
@class_list = attrs.delete(:class)
|
7
|
+
@attrs = attrs.symbolize_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :class_list, :attrs
|
11
|
+
|
12
|
+
def merge(*args)
|
13
|
+
self.class.merge(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.merge(*args)
|
17
|
+
@merger ||= TailwindMerge::Merger.new
|
18
|
+
@merger.merge(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def data_merge(data = {}, new_data = {})
|
22
|
+
return data if new_data.blank?
|
23
|
+
return new_data if data.blank?
|
24
|
+
|
25
|
+
data.deep_merge(new_data) do |_key, old_val, new_val|
|
26
|
+
# Put new value first so overrides can stopPropagation to old value
|
27
|
+
[new_val, old_val].compact.join(" ")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Dropdown < Component
|
3
|
+
include Phlex::Rails::Helpers::LinkTo
|
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
|
+
ITEM = [
|
13
|
+
"px-3 py-1.5 rounded",
|
14
|
+
"font-medium truncate",
|
15
|
+
"cursor-default"
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
ITEM_VARIANTS = {
|
19
|
+
default: ["hover:bg-muted"],
|
20
|
+
destructive: ["text-destructive-foreground hover:bg-destructive hover:text-white"]
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
SEPARATOR = "border-t my-1 -mx-1"
|
24
|
+
|
25
|
+
def initialize(placement: nil, **attrs)
|
26
|
+
@placement = placement
|
27
|
+
@attrs = attrs
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :placement
|
31
|
+
|
32
|
+
def view_template(&block)
|
33
|
+
div(data: {:controller => "nk--dropdown", :"nk--dropdown-placement-value" => placement}, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def trigger(**attrs, &block)
|
37
|
+
class_list = "inline-block"
|
38
|
+
data = {
|
39
|
+
:"nk--dropdown-target" => "trigger",
|
40
|
+
:action => "click->nk--dropdown#toggle",
|
41
|
+
**attrs.fetch(:data, {})
|
42
|
+
}
|
43
|
+
div(
|
44
|
+
**attrs,
|
45
|
+
class: class_list,
|
46
|
+
data:,
|
47
|
+
aria: {haspopup: "true", expanded: "false"},
|
48
|
+
&block
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def content(**attrs, &block)
|
53
|
+
class_list = merge([CONTENT, attrs[:class]])
|
54
|
+
|
55
|
+
data = {
|
56
|
+
:"nk--dropdown-target" => "content",
|
57
|
+
**attrs.fetch(:data, {})
|
58
|
+
}
|
59
|
+
div(
|
60
|
+
**attrs,
|
61
|
+
class: class_list,
|
62
|
+
data:,
|
63
|
+
role: "menu",
|
64
|
+
aria: {hidden: "true"},
|
65
|
+
&block
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def title(text = nil, **attrs, &block)
|
70
|
+
class_list = merge(["px-3 pt-2 pb-1.5 text-muted-foreground text-sm", attrs[:class]])
|
71
|
+
div(**attrs, class: class_list) { text || block.call }
|
72
|
+
end
|
73
|
+
|
74
|
+
def item(
|
75
|
+
text = nil,
|
76
|
+
href = nil,
|
77
|
+
variant: :default,
|
78
|
+
**attrs,
|
79
|
+
&block
|
80
|
+
)
|
81
|
+
class_list = merge([ITEM, ITEM_VARIANTS[variant], attrs[:class]])
|
82
|
+
|
83
|
+
common_attrs = {
|
84
|
+
**attrs,
|
85
|
+
class: class_list,
|
86
|
+
role: "menuitem",
|
87
|
+
tabindex: "-1"
|
88
|
+
}
|
89
|
+
|
90
|
+
if href
|
91
|
+
link_to(
|
92
|
+
href,
|
93
|
+
**common_attrs
|
94
|
+
) {
|
95
|
+
text || block.call
|
96
|
+
}
|
97
|
+
else
|
98
|
+
div(**common_attrs) { text || block.call }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def destructive_item(*args, **attrs, &block)
|
103
|
+
item(*args, **attrs, variant: :destructive, &block)
|
104
|
+
end
|
105
|
+
|
106
|
+
def separator
|
107
|
+
div(class: SEPARATOR)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Field < Component
|
3
|
+
FIELD_BASE = [
|
4
|
+
"flex flex-col gap-2 align-start",
|
5
|
+
"[&:has([data-slot='error'])_[data-slot='control']]:border-destructive"
|
6
|
+
].freeze
|
7
|
+
LABEL_BASE = "text-sm font-medium"
|
8
|
+
DESCRIPTION_BASE = "text-sm text-muted-foreground"
|
9
|
+
ERROR_BASE = "text-sm text-destructive"
|
10
|
+
INPUT_BASE = [
|
11
|
+
"rounded-md border bg-background border-border text-base px-3 py-2",
|
12
|
+
"focus:outline-none focus:ring-2 focus:ring-primary",
|
13
|
+
""
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
def initialize(attribute, as: :string, label: nil, description: nil, errors: nil, **attrs)
|
17
|
+
@attribute = attribute
|
18
|
+
@as = as
|
19
|
+
@label_text = label
|
20
|
+
@description_text = description
|
21
|
+
@errors = errors || []
|
22
|
+
@attrs = attrs
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :attribute, :as, :label_text, :description_text, :errors, :attrs
|
26
|
+
|
27
|
+
def view_template(&block)
|
28
|
+
div(**attrs, class: FIELD_BASE) do
|
29
|
+
label(**attrs, data: {slot: "label"}, class: LABEL_BASE) { label_text }
|
30
|
+
input(**attrs, data: {slot: "control"}, class: INPUT_BASE)
|
31
|
+
errors.each do |msg|
|
32
|
+
div(**attrs, data: {slot: "error"}, class: ERROR_BASE) { msg }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class FieldGroup < Component
|
3
|
+
FIELD_GROUP_BASE = "space-y-6"
|
4
|
+
|
5
|
+
def initialize(**attrs)
|
6
|
+
@attrs = attrs
|
7
|
+
@class_list = merge([FIELD_GROUP_BASE, attrs[:class]])
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template(&block)
|
11
|
+
div(**attrs, class: class_list, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Fieldset < Component
|
3
|
+
FIELDSET_BASE = "space-y-6"
|
4
|
+
|
5
|
+
def initialize(legend, **attrs)
|
6
|
+
@legend = legend
|
7
|
+
@attrs = attrs
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :legend, :attrs
|
11
|
+
|
12
|
+
def view_template(&block)
|
13
|
+
fieldset(**attrs, class: FIELDSET_BASE, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
# Fields
|
4
|
+
|
5
|
+
def fieldset(options = {}, &block)
|
6
|
+
content = @template.capture(&block)
|
7
|
+
@template.render(NitroKit::Fieldset.new(options)) { content }
|
8
|
+
end
|
9
|
+
|
10
|
+
def field_group(options = {}, &block)
|
11
|
+
content = @template.capture(&block)
|
12
|
+
@template.render(NitroKit::FieldGroup.new(**options)) { content }
|
13
|
+
end
|
14
|
+
|
15
|
+
def field(object_name, **options)
|
16
|
+
label = options.fetch(:label, object_name.to_s.humanize)
|
17
|
+
errors = object.errors.include?(object_name) ? object.errors.full_messages_for(object_name) : nil
|
18
|
+
|
19
|
+
@template.render(NitroKit::Field.new(object_name, label:, errors:, **options))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Inputs
|
23
|
+
|
24
|
+
def label(object_name, method, content_or_options = nil, options = nil, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def checkbox(method, options = {})
|
28
|
+
@template.checkbox(@object_name, method, objectify_options(options), label: options[:label])
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :check_box, :checkbox
|
32
|
+
|
33
|
+
def submit(value = "Save changes", **options)
|
34
|
+
content = value || @template.capture(&block)
|
35
|
+
@template.render(NitroKit::Button.new(variant: :primary, type: :submit, **options)) { content }
|
36
|
+
end
|
37
|
+
|
38
|
+
def button(value = "Save changes", **options)
|
39
|
+
content = value || @template.capture(&block)
|
40
|
+
@template.render(NitroKit::Button.new(**options)) { content }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class Icon < Component
|
3
|
+
include Phlex::Rails::Helpers::ContentTag
|
4
|
+
include LucideRails::RailsHelper
|
5
|
+
|
6
|
+
SIZE = {
|
7
|
+
sm: "size-4",
|
8
|
+
base: "size-5",
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(name:, size: :base, **attrs)
|
12
|
+
@name = name
|
13
|
+
@size = size
|
14
|
+
@attrs = attrs
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :name, :size
|
18
|
+
|
19
|
+
def view_template
|
20
|
+
lucide_icon(name, **attrs, class: merge([SIZE[size], attrs[:class]]))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class RadioButton < Component
|
3
|
+
include ActionView::Helpers::FormTagHelper
|
4
|
+
|
5
|
+
def initialize(name, value:, label: nil, **attrs)
|
6
|
+
super(**attrs)
|
7
|
+
|
8
|
+
@name = name
|
9
|
+
@value = value
|
10
|
+
@label_text = label
|
11
|
+
@id = "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}"
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader(
|
15
|
+
:name,
|
16
|
+
:value,
|
17
|
+
:id,
|
18
|
+
:label_text
|
19
|
+
)
|
20
|
+
|
21
|
+
def view_template
|
22
|
+
div(class: merge(["inline-flex items-center gap-2", class_list])) do
|
23
|
+
label(class: "grid grid-cols-1 place-items-center shrink-0") do
|
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 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
|
+
)
|
37
|
+
dot
|
38
|
+
end
|
39
|
+
|
40
|
+
if label_text.present?
|
41
|
+
render(Label.new(for: id)) { label_text }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def dot
|
49
|
+
svg(
|
50
|
+
class: class_names(
|
51
|
+
"row-start-1 col-start-1",
|
52
|
+
"size-2.5 text-primay opacity-0 pointer-events-none",
|
53
|
+
"peer-checked:opacity-100"
|
54
|
+
),
|
55
|
+
viewbox: "0 0 20 20",
|
56
|
+
fill: "currentColor",
|
57
|
+
stroke: "none"
|
58
|
+
) do |svg|
|
59
|
+
svg.circle(cx: 10, cy: 10, r: 10)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|