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
@@ -0,0 +1,35 @@
|
|
1
|
+
module NitroKit
|
2
|
+
class RadioGroup < Component
|
3
|
+
include ActionView::Helpers::FormTagHelper
|
4
|
+
|
5
|
+
def initialize(name, value: nil, **options)
|
6
|
+
super(**options)
|
7
|
+
@name = name
|
8
|
+
@group_value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :name, :id, :group_value
|
12
|
+
|
13
|
+
def view_template
|
14
|
+
div(class: "flex items-start flex-col gap-2") do
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def title(text = nil, **options)
|
20
|
+
render(Label.new(**options)) { text || yield }
|
21
|
+
end
|
22
|
+
|
23
|
+
def item(value, text = nil, **options)
|
24
|
+
render(
|
25
|
+
RadioButton.new(
|
26
|
+
name,
|
27
|
+
id:,
|
28
|
+
value:,
|
29
|
+
checked: group_value.presence == value,
|
30
|
+
**options
|
31
|
+
)
|
32
|
+
) { text || yield }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module NitroKit
|
2
|
+
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 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
|
+
def initialize(
|
28
|
+
name,
|
29
|
+
checked: false,
|
30
|
+
disabled: false,
|
31
|
+
size: :base,
|
32
|
+
description: nil,
|
33
|
+
**attrs
|
34
|
+
)
|
35
|
+
super(**attrs)
|
36
|
+
|
37
|
+
@name = name
|
38
|
+
@checked = checked
|
39
|
+
@disabled = disabled
|
40
|
+
@size = size
|
41
|
+
@description = description
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :name, :checked, :disabled, :description, :size
|
45
|
+
|
46
|
+
def view_template
|
47
|
+
button(
|
48
|
+
**attrs,
|
49
|
+
type: "button",
|
50
|
+
class: merge([BUTTON, SIZE[size], attrs[:class]]),
|
51
|
+
data: data_merge(attrs[:data], {controller: "nk--switch", action: "nk--switch#switch"}),
|
52
|
+
role: "switch",
|
53
|
+
aria: {checked: checked.to_s}
|
54
|
+
) do
|
55
|
+
span(class: "sr-only") { description }
|
56
|
+
handle
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def handle
|
63
|
+
span(aria: {hidden: true}, class: HANDLE, data: {slot: "handle"})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ApplicationHelper
|
2
|
+
def component_page(&block)
|
3
|
+
tag.div(class: "p-5") do
|
4
|
+
capture(&block)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def title(text)
|
9
|
+
tag.h1(class: "text-3xl font-semibold mb-6") { text }
|
10
|
+
end
|
11
|
+
|
12
|
+
def lead(text)
|
13
|
+
tag.p(class: "text-lg text-gray-600 dark:text-gray-400 mb-6") { text }
|
14
|
+
end
|
15
|
+
|
16
|
+
def section(**attrs)
|
17
|
+
tag.div(**attrs, class: class_names("mt-8 space-y-4", attrs[:class])) { yield }
|
18
|
+
end
|
19
|
+
|
20
|
+
def section_title(text)
|
21
|
+
tag.h2(class: "text-2xl font-semibold mb-4") { text }
|
22
|
+
end
|
23
|
+
|
24
|
+
def example(**attrs)
|
25
|
+
tag
|
26
|
+
.div(
|
27
|
+
**attrs,
|
28
|
+
class: class_names(
|
29
|
+
["flex gap-2 justify-center items-center min-h-[200px] py-12 p-5 w-full rounded border", attrs[:class]]
|
30
|
+
)
|
31
|
+
) do
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def code_example(str)
|
37
|
+
formatter = Rouge::Formatters::HTML.new
|
38
|
+
lexer = Rouge::Lexers::ERB.new
|
39
|
+
|
40
|
+
tag
|
41
|
+
.div(
|
42
|
+
class: "bg-[#f7f8fa] dark:bg-[#161b22] divide-y border rounded overflow-hidden",
|
43
|
+
data: {controller: "copy-to-clipboard"}
|
44
|
+
) do
|
45
|
+
concat(
|
46
|
+
tag.div(class: "px-1 py-1 flex w-full") do
|
47
|
+
concat(
|
48
|
+
nk_ghost_button(
|
49
|
+
icon: :copy,
|
50
|
+
size: :xs,
|
51
|
+
data: {copy_to_clipboard_target: "button", action: "copy-to-clipboard#copy"}
|
52
|
+
) { "Copy" }
|
53
|
+
)
|
54
|
+
concat(
|
55
|
+
nk_ghost_button(
|
56
|
+
icon: :check,
|
57
|
+
size: :xs,
|
58
|
+
data: {copy_to_clipboard_target: "successMessage"},
|
59
|
+
class: "hidden"
|
60
|
+
) { "Copied!" }
|
61
|
+
)
|
62
|
+
end
|
63
|
+
)
|
64
|
+
|
65
|
+
concat(
|
66
|
+
tag
|
67
|
+
.pre(
|
68
|
+
class: "text-sm highlight py-3 px-4 font-mono overflow-scroll",
|
69
|
+
data: {copy_to_clipboard_target: "source"}
|
70
|
+
) do
|
71
|
+
tag.code do
|
72
|
+
formatter.format(lexer.lex(str.strip_heredoc.strip)).html_safe
|
73
|
+
end
|
74
|
+
end
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def markdown(str)
|
80
|
+
Commonmarker
|
81
|
+
.to_html(
|
82
|
+
str.strip_heredoc,
|
83
|
+
options: {
|
84
|
+
parse: {smart: true}
|
85
|
+
}
|
86
|
+
)
|
87
|
+
.html_safe
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module BadgeHelper
|
3
|
+
include Variants
|
4
|
+
|
5
|
+
automatic_variants(Badge::VARIANTS, :nk_badge)
|
6
|
+
|
7
|
+
def nk_badge(text = nil, **attrs, &block)
|
8
|
+
content = text || capture(&block)
|
9
|
+
|
10
|
+
render(NitroKit::Badge.new(**attrs)) do
|
11
|
+
content
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module ButtonHelper
|
3
|
+
include Variants
|
4
|
+
|
5
|
+
automatic_variants(Button::VARIANTS, :nk_button)
|
6
|
+
|
7
|
+
def nk_button(
|
8
|
+
text_or_href = nil,
|
9
|
+
href = nil,
|
10
|
+
icon: nil,
|
11
|
+
icon_right: nil,
|
12
|
+
size: :base,
|
13
|
+
type: :button,
|
14
|
+
variant: :default,
|
15
|
+
**attrs,
|
16
|
+
&block
|
17
|
+
)
|
18
|
+
content = block_given? ? capture(&block) : text_or_href
|
19
|
+
href = text_or_href if href.nil? && block_given?
|
20
|
+
|
21
|
+
if href && !href.is_a?(String)
|
22
|
+
href = url_for(href)
|
23
|
+
end
|
24
|
+
|
25
|
+
render(
|
26
|
+
NitroKit::Button.new(
|
27
|
+
href:,
|
28
|
+
icon:,
|
29
|
+
icon_right:,
|
30
|
+
size:,
|
31
|
+
type:,
|
32
|
+
variant:,
|
33
|
+
**attrs
|
34
|
+
)
|
35
|
+
) do
|
36
|
+
content
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module CheckboxHelper
|
3
|
+
# Match the signature of the Rails `check_box` helper
|
4
|
+
def nk_checkbox(name, *args)
|
5
|
+
if args.length >= 4
|
6
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length + 1}, expected 1..4)"
|
7
|
+
end
|
8
|
+
|
9
|
+
options = args.extract_options!
|
10
|
+
value, checked = args.empty? ? ["1", false] : [*args, false]
|
11
|
+
|
12
|
+
attrs = {
|
13
|
+
:type => "checkbox",
|
14
|
+
:name => name,
|
15
|
+
:id => sanitize_to_id(name),
|
16
|
+
:value => value
|
17
|
+
}.update(options.symbolize_keys)
|
18
|
+
|
19
|
+
attrs[:checked] = "checked" if checked
|
20
|
+
|
21
|
+
render(NitroKit::Checkbox.new(name, value:, **attrs))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module LabelHelper
|
3
|
+
def nk_label(name = nil, content_or_options = nil, options = nil, &block)
|
4
|
+
if block_given? && content_or_options.is_a?(Hash)
|
5
|
+
options = content_or_options = content_or_options.symbolize_keys
|
6
|
+
else
|
7
|
+
options ||= {}
|
8
|
+
options = options.symbolize_keys
|
9
|
+
end
|
10
|
+
|
11
|
+
options[:for] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
|
12
|
+
render(Label.new(**options)) { content_or_options || name.to_s.humanize || yield }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module RadioButtonHelper
|
3
|
+
def nk_radio_button(name, value, *args)
|
4
|
+
if args.length >= 3
|
5
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length + 2}, expected 2..4)"
|
6
|
+
end
|
7
|
+
|
8
|
+
options = args.extract_options!
|
9
|
+
checked = args.empty? ? false : args.first
|
10
|
+
|
11
|
+
options[:checked] = "checked" if checked
|
12
|
+
|
13
|
+
render(RadioButton.new(name, value:, name:, value:, **options))
|
14
|
+
end
|
15
|
+
|
16
|
+
def nk_radio_group(name, **options, &block)
|
17
|
+
render(RadioGroup.new(name, **options, &block))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module SwitchHelper
|
3
|
+
def nk_switch(
|
4
|
+
name,
|
5
|
+
checked: false,
|
6
|
+
disabled: false,
|
7
|
+
size: :base,
|
8
|
+
description: nil,
|
9
|
+
**attrs,
|
10
|
+
&block
|
11
|
+
)
|
12
|
+
render(NitroKit::Switch.new(name, checked:, disabled:, size:, description:, **attrs), &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "nitro_kit/schema_builder"
|
2
|
+
|
3
|
+
module NitroKit
|
4
|
+
class AddGenerator < Rails::Generators::Base
|
5
|
+
argument :component_names, type: :array
|
6
|
+
|
7
|
+
source_root File.expand_path("../../../", __dir__)
|
8
|
+
|
9
|
+
extend SchemaBuilder
|
10
|
+
|
11
|
+
SCHEMA = build_schema do |s|
|
12
|
+
s.add(:badge)
|
13
|
+
s.add(
|
14
|
+
:button,
|
15
|
+
components: [:button, :button_group],
|
16
|
+
helpers: [:button, :button_group]
|
17
|
+
)
|
18
|
+
s.add(
|
19
|
+
:dropdown,
|
20
|
+
js: [:dropdown],
|
21
|
+
modules: ["@floating-ui/core", "@floating-ui/dom"]
|
22
|
+
)
|
23
|
+
s.add(
|
24
|
+
:icon,
|
25
|
+
gems: ["lucide-rails"]
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def copy_base_component
|
30
|
+
copy_file("app/components/nitro_kit/component.rb", "app/components/nitro_kit/component.rb")
|
31
|
+
end
|
32
|
+
|
33
|
+
def copy_component_files
|
34
|
+
components.each do |component|
|
35
|
+
component.files.each do |path|
|
36
|
+
copy_file(path, path)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_gems
|
42
|
+
gems = components.flat_map(&:gems)
|
43
|
+
|
44
|
+
return unless gems.any?
|
45
|
+
|
46
|
+
gems.each do |name|
|
47
|
+
gem(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
run("bundle install")
|
51
|
+
end
|
52
|
+
|
53
|
+
def install_modules
|
54
|
+
modules = components.flat_map(&:modules)
|
55
|
+
|
56
|
+
return unless modules.any?
|
57
|
+
|
58
|
+
if importmaps?
|
59
|
+
run("bin/importmap pin #{modules.join(" ")}")
|
60
|
+
else
|
61
|
+
say("oh hai npm/yarn/bun")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def components
|
68
|
+
list = component_names
|
69
|
+
|
70
|
+
if list == ["all"]
|
71
|
+
list = SCHEMA.all
|
72
|
+
end
|
73
|
+
|
74
|
+
list.map do |name|
|
75
|
+
unless component = SCHEMA[name.to_sym]
|
76
|
+
raise "Unknown component `#{name}'"
|
77
|
+
end
|
78
|
+
|
79
|
+
component
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def importmaps?
|
84
|
+
File.exist?(File.expand_path("bin/importmap", Rails.root))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module SchemaBuilder
|
3
|
+
Component = Struct.new(:files, :modules, :gems)
|
4
|
+
|
5
|
+
class Builder
|
6
|
+
def initialize
|
7
|
+
@schema = {}
|
8
|
+
yield self
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(
|
12
|
+
name,
|
13
|
+
components: nil,
|
14
|
+
helpers: nil,
|
15
|
+
js: [],
|
16
|
+
modules: [],
|
17
|
+
gems: []
|
18
|
+
)
|
19
|
+
# Default is one component, one helper with same name
|
20
|
+
components ||= [name]
|
21
|
+
helpers ||= [name]
|
22
|
+
|
23
|
+
@schema[name] = Component.new(
|
24
|
+
[
|
25
|
+
components.map { |c| "app/components/nitro_kit/#{c}.rb" },
|
26
|
+
helpers.map { |c| "app/helpers/nitro_kit/#{c}_helper.rb" },
|
27
|
+
js.map { |c| "app/javascript/controllers/nk/#{c}_controller.js" }
|
28
|
+
].flatten,
|
29
|
+
modules,
|
30
|
+
gems
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](key)
|
35
|
+
@schema[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
def all
|
39
|
+
@schema.keys.map(&:to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_schema(&block)
|
44
|
+
Builder.new(&block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module NitroKit
|
2
|
+
module Variants
|
3
|
+
module ClassMethods
|
4
|
+
def automatic_variants(variants, method_name)
|
5
|
+
_, prefix, original = method_name.match(/(nk_)(.+)/).to_a
|
6
|
+
|
7
|
+
variants.each do |variant, class_name|
|
8
|
+
variant_method_name = "#{prefix}#{variant}_#{original}"
|
9
|
+
|
10
|
+
define_method(variant_method_name) do |*args, **kwargs, &block|
|
11
|
+
send(method_name, *args, variant:, **kwargs, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|