nitro_kit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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