nitro_kit 0.1.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 +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
|