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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/Rakefile +6 -0
  4. data/app/components/nitro_kit/accordion.rb +60 -0
  5. data/app/components/nitro_kit/badge.rb +34 -0
  6. data/app/components/nitro_kit/button.rb +109 -0
  7. data/app/components/nitro_kit/button_group.rb +19 -0
  8. data/app/components/nitro_kit/card.rb +23 -0
  9. data/app/components/nitro_kit/checkbox.rb +62 -0
  10. data/app/components/nitro_kit/component.rb +31 -0
  11. data/app/components/nitro_kit/dropdown.rb +110 -0
  12. data/app/components/nitro_kit/field.rb +37 -0
  13. data/app/components/nitro_kit/field_group.rb +14 -0
  14. data/app/components/nitro_kit/fieldset.rb +16 -0
  15. data/app/components/nitro_kit/form_builder.rb +43 -0
  16. data/app/components/nitro_kit/icon.rb +23 -0
  17. data/app/components/nitro_kit/label.rb +10 -0
  18. data/app/components/nitro_kit/radio_button.rb +63 -0
  19. data/app/components/nitro_kit/radio_group.rb +35 -0
  20. data/app/components/nitro_kit/switch.rb +66 -0
  21. data/app/helpers/application_helper.rb +89 -0
  22. data/app/helpers/nitro_kit/accordion_helper.rb +7 -0
  23. data/app/helpers/nitro_kit/badge_helper.rb +15 -0
  24. data/app/helpers/nitro_kit/button_group_helper.rb +7 -0
  25. data/app/helpers/nitro_kit/button_helper.rb +40 -0
  26. data/app/helpers/nitro_kit/card_helper.rb +7 -0
  27. data/app/helpers/nitro_kit/checkbox_helper.rb +24 -0
  28. data/app/helpers/nitro_kit/dropdown_helper.rb +7 -0
  29. data/app/helpers/nitro_kit/field_helper.rb +7 -0
  30. data/app/helpers/nitro_kit/icon_helper.rb +7 -0
  31. data/app/helpers/nitro_kit/label_helper.rb +15 -0
  32. data/app/helpers/nitro_kit/radio_button_helper.rb +20 -0
  33. data/app/helpers/nitro_kit/switch_helper.rb +15 -0
  34. data/lib/generators/nitro_kit/add_generator.rb +87 -0
  35. data/lib/generators/nitro_kit/install_generator.rb +9 -0
  36. data/lib/nitro_kit/railtie.rb +8 -0
  37. data/lib/nitro_kit/schema_builder.rb +47 -0
  38. data/lib/nitro_kit/variants.rb +21 -0
  39. data/lib/nitro_kit/version.rb +3 -0
  40. data/lib/nitro_kit.rb +8 -0
  41. 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,7 @@
1
+ module NitroKit
2
+ module AccordionHelper
3
+ def nk_accordion(**attrs, &block)
4
+ render(Accordion.new(**attrs), &block)
5
+ end
6
+ end
7
+ 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,7 @@
1
+ module NitroKit
2
+ module ButtonGroupHelper
3
+ def nk_button_group(**attrs, &block)
4
+ render(ButtonGroup.new(**attrs), &block)
5
+ end
6
+ end
7
+ 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,7 @@
1
+ module NitroKit
2
+ module CardHelper
3
+ def nk_card(**attrs, &block)
4
+ render(Card.new(**attrs), &block)
5
+ end
6
+ end
7
+ 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,7 @@
1
+ module NitroKit
2
+ module DropdownHelper
3
+ def nk_dropdown(**attrs, &block)
4
+ render(NitroKit::Dropdown.new(**attrs), &block)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module NitroKit
2
+ module FieldHelper
3
+ def nk_field(attribute, **options)
4
+ render(NitroKit::Field.new(attribute, **options))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module NitroKit
2
+ module IconHelper
3
+ def nk_icon(name, **attrs)
4
+ render(NitroKit::Icon.new(name: name, **attrs))
5
+ end
6
+ end
7
+ 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,9 @@
1
+ module NitroKit
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ def one
5
+ puts("hi")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module NitroKit
2
+ class Railtie < ::Rails::Railtie
3
+ initializer("nitro_kit.initialize") do
4
+ # Ignore our generators as they confuse Zeitwerk
5
+ Rails.autoloaders.main.ignore(File.expand_path("../generators", __dir__))
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,3 @@
1
+ module NitroKit
2
+ VERSION = "0.1.0"
3
+ end
data/lib/nitro_kit.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "tailwind_merge"
2
+ require "phlex/rails"
3
+
4
+ require "nitro_kit/variants"
5
+ require "nitro_kit/railtie"
6
+
7
+ module NitroKit
8
+ end