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
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,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
@@ -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,10 @@
1
+ module NitroKit
2
+ class Label < Component
3
+ def view_template
4
+ label(
5
+ **attrs,
6
+ class: merge(["text-sm font-medium select-none", class_list])
7
+ ) { yield }
8
+ end
9
+ end
10
+ 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