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
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