nitro_kit 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -1
  4. data/Rakefile +6 -4
  5. data/app/components/nitro_kit/accordion.rb +69 -33
  6. data/app/components/nitro_kit/alert.rb +69 -0
  7. data/app/components/nitro_kit/avatar.rb +52 -0
  8. data/app/components/nitro_kit/badge.rb +47 -23
  9. data/app/components/nitro_kit/button.rb +97 -65
  10. data/app/components/nitro_kit/button_group.rb +18 -13
  11. data/app/components/nitro_kit/card.rb +49 -9
  12. data/app/components/nitro_kit/checkbox.rb +59 -41
  13. data/app/components/nitro_kit/checkbox_group.rb +38 -0
  14. data/app/components/nitro_kit/combobox.rb +138 -0
  15. data/app/components/nitro_kit/component.rb +46 -17
  16. data/app/components/nitro_kit/datepicker.rb +9 -0
  17. data/app/components/nitro_kit/dialog.rb +95 -0
  18. data/app/components/nitro_kit/dropdown.rb +116 -73
  19. data/app/components/nitro_kit/field.rb +281 -30
  20. data/app/components/nitro_kit/field_group.rb +10 -5
  21. data/app/components/nitro_kit/fieldset.rb +42 -7
  22. data/app/components/nitro_kit/form_builder.rb +45 -22
  23. data/app/components/nitro_kit/icon.rb +29 -8
  24. data/app/components/nitro_kit/input.rb +26 -0
  25. data/app/components/nitro_kit/label.rb +18 -5
  26. data/app/components/nitro_kit/pagination.rb +98 -0
  27. data/app/components/nitro_kit/radio_button.rb +28 -27
  28. data/app/components/nitro_kit/radio_button_group.rb +53 -0
  29. data/app/components/nitro_kit/select.rb +72 -0
  30. data/app/components/nitro_kit/switch.rb +49 -39
  31. data/app/components/nitro_kit/table.rb +56 -0
  32. data/app/components/nitro_kit/tabs.rb +98 -0
  33. data/app/components/nitro_kit/textarea.rb +26 -0
  34. data/app/components/nitro_kit/toast.rb +104 -0
  35. data/app/components/nitro_kit/tooltip.rb +53 -0
  36. data/app/helpers/nitro_kit/accordion_helper.rb +3 -1
  37. data/app/helpers/nitro_kit/alert_helper.rb +11 -0
  38. data/app/helpers/nitro_kit/avatar_helper.rb +9 -0
  39. data/app/helpers/nitro_kit/badge_helper.rb +3 -5
  40. data/app/helpers/nitro_kit/button_group_helper.rb +2 -0
  41. data/app/helpers/nitro_kit/button_helper.rb +37 -28
  42. data/app/helpers/nitro_kit/card_helper.rb +2 -0
  43. data/app/helpers/nitro_kit/checkbox_helper.rb +19 -16
  44. data/app/helpers/nitro_kit/combobox_helper.rb +9 -0
  45. data/app/helpers/nitro_kit/datepicker_helper.rb +9 -0
  46. data/app/helpers/nitro_kit/dialog_helper.rb +9 -0
  47. data/app/helpers/nitro_kit/dropdown_helper.rb +3 -1
  48. data/app/helpers/nitro_kit/field_group_helper.rb +9 -0
  49. data/app/helpers/nitro_kit/field_helper.rb +4 -2
  50. data/app/helpers/nitro_kit/fieldset_helper.rb +9 -0
  51. data/app/helpers/nitro_kit/form_helper.rb +13 -0
  52. data/app/helpers/nitro_kit/icon_helper.rb +3 -1
  53. data/app/helpers/nitro_kit/input_helper.rb +35 -0
  54. data/app/helpers/nitro_kit/label_helper.rb +12 -8
  55. data/app/helpers/nitro_kit/pagination_helper.rb +42 -0
  56. data/app/helpers/nitro_kit/radio_button_helper.rb +15 -12
  57. data/app/helpers/nitro_kit/select_helper.rb +24 -0
  58. data/app/helpers/nitro_kit/switch_helper.rb +4 -10
  59. data/app/helpers/nitro_kit/table_helper.rb +9 -0
  60. data/app/helpers/nitro_kit/tabs_helper.rb +9 -0
  61. data/app/helpers/nitro_kit/textarea_helper.rb +9 -0
  62. data/app/helpers/nitro_kit/toast_helper.rb +36 -0
  63. data/app/helpers/nitro_kit/tooltip_helper.rb +9 -0
  64. data/lib/generators/nitro_kit/add_generator.rb +38 -41
  65. data/lib/generators/nitro_kit/install_generator.rb +2 -1
  66. data/lib/nitro_kit/engine.rb +4 -0
  67. data/lib/nitro_kit/schema_builder.rb +90 -16
  68. data/lib/nitro_kit/version.rb +1 -1
  69. data/lib/nitro_kit.rb +39 -1
  70. data/lib/tasks/nitro_kit_tasks.rake +4 -0
  71. metadata +40 -12
  72. data/app/components/nitro_kit/radio_group.rb +0 -35
  73. data/app/helpers/application_helper.rb +0 -89
  74. data/lib/nitro_kit/railtie.rb +0 -8
@@ -1,43 +1,66 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class FormBuilder < ActionView::Helpers::FormBuilder
3
5
  # Fields
4
6
 
5
- def fieldset(options = {}, &block)
6
- content = @template.capture(&block)
7
- @template.render(NitroKit::Fieldset.new(options)) { content }
7
+ def fieldset(**attrs, &block)
8
+ @template.render(NitroKit::Fieldset.new(**attrs), &block)
8
9
  end
9
10
 
10
- def field_group(options = {}, &block)
11
- content = @template.capture(&block)
12
- @template.render(NitroKit::FieldGroup.new(**options)) { content }
13
- end
11
+ def field(field_name, **attrs, &block)
12
+ label = attrs.fetch(:label, field_name.to_s.humanize)
14
13
 
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
14
+ errors = object && object.errors.include?(field_name) ? object.errors.full_messages_for(field_name) : nil
18
15
 
19
- @template.render(NitroKit::Field.new(object_name, label:, errors:, **options))
16
+ @template.render(NitroKit::Field.new(self, field_name, label:, errors:, **attrs), &block)
20
17
  end
21
18
 
22
- # Inputs
23
-
24
- def label(object_name, method, content_or_options = nil, options = nil, &block)
19
+ def group(**attrs, &block)
20
+ @template.render(FieldGroup.new(**attrs), &block)
25
21
  end
26
22
 
27
- def checkbox(method, options = {})
28
- @template.checkbox(@object_name, method, objectify_options(options), label: options[:label])
29
- end
23
+ # Inputs
24
+
25
+ %i[
26
+ checkbox
27
+ color_field
28
+ date_field
29
+ datetime_field
30
+ datetime_local_field
31
+ email_field
32
+ file_field
33
+ hidden_field
34
+ month_field
35
+ number_field
36
+ password_field
37
+ phone_field
38
+ radio_button
39
+ range_field
40
+ search_field
41
+ telephone_field
42
+ text_area
43
+ text_field
44
+ time_field
45
+ url_field
46
+ week_field
47
+ ]
48
+ .each do |method|
49
+ define_method(method) do |*args, **attrs, &block|
50
+ @template.send("nk_#{method}", *args, **attrs, &block)
51
+ end
52
+ end
30
53
 
31
- alias_method :check_box, :checkbox
54
+ # Buttons
32
55
 
33
- def submit(value = "Save changes", **options)
56
+ def submit(value = "Save changes", **attrs)
34
57
  content = value || @template.capture(&block)
35
- @template.render(NitroKit::Button.new(variant: :primary, type: :submit, **options)) { content }
58
+ @template.render(NitroKit::Button.new(variant: :primary, type: :submit, **attrs)) { content }
36
59
  end
37
60
 
38
- def button(value = "Save changes", **options)
61
+ def button(value = "Save changes", **attrs)
39
62
  content = value || @template.capture(&block)
40
- @template.render(NitroKit::Button.new(**options)) { content }
63
+ @template.render(NitroKit::Button.new(**attrs)) { content }
41
64
  end
42
65
  end
43
66
  end
@@ -1,23 +1,44 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class Icon < Component
3
5
  include Phlex::Rails::Helpers::ContentTag
4
6
  include LucideRails::RailsHelper
5
7
 
6
- SIZE = {
7
- sm: "size-4",
8
- base: "size-5",
9
- }
10
-
11
- def initialize(name:, size: :base, **attrs)
8
+ def initialize(name, size: :md, **attrs)
12
9
  @name = name
13
10
  @size = size
14
- @attrs = attrs
11
+
12
+ super(
13
+ attrs,
14
+ class: size_class,
15
+ stroke_width: 1.5
16
+ )
15
17
  end
16
18
 
17
19
  attr_reader :name, :size
18
20
 
19
21
  def view_template
20
- lucide_icon(name, **attrs, class: merge([SIZE[size], attrs[:class]]))
22
+ lucide_icon(name, **dasherized_attrs)
23
+ end
24
+
25
+ private
26
+
27
+ def size_class
28
+ case size
29
+ when :sm
30
+ "size-4"
31
+ when :md
32
+ "size-5"
33
+ when :lg
34
+ "size-7"
35
+ else
36
+ raise ArgumentError, "Unknown size `#{size}'"
37
+ end
38
+ end
39
+
40
+ def dasherized_attrs
41
+ attrs.transform_keys { |k| k.to_s.dasherize }
21
42
  end
22
43
  end
23
44
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Input < Component
5
+ def initialize(**attrs)
6
+ super(
7
+ attrs,
8
+ class: base_class
9
+ )
10
+ end
11
+
12
+ def view_template
13
+ input(**attrs)
14
+ end
15
+
16
+ private
17
+
18
+ def base_class
19
+ [
20
+ "block rounded-md border bg-background border-border text-base px-3 py-2 h-10",
21
+ # Focus
22
+ "focus:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2"
23
+ ]
24
+ end
25
+ end
26
+ end
@@ -1,10 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class Label < Component
3
- def view_template
4
- label(
5
- **attrs,
6
- class: merge(["text-sm font-medium select-none", class_list])
7
- ) { yield }
5
+ def initialize(text = nil, **attrs)
6
+ @text = text
7
+
8
+ super(
9
+ attrs,
10
+ class: "text-sm font-medium select-none",
11
+ data: {slot: "label"}
12
+ )
13
+ end
14
+
15
+ attr_reader :text
16
+
17
+ def view_template(&block)
18
+ label(**attrs) do
19
+ text_or_block(text, &block)
20
+ end
8
21
  end
9
22
  end
10
23
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Pagination < Component
5
+ def initialize(**attrs)
6
+ super(
7
+ attrs,
8
+ class: merge_class(nav_class, attrs[:class]),
9
+ aria: {label: "Pagination"}
10
+ )
11
+ end
12
+
13
+ def view_template
14
+ nav(**attrs) do
15
+ yield
16
+ end
17
+ end
18
+
19
+ def prev(text = nil, **attrs, &block)
20
+ page_link(**mattr(attrs, aria: {label: "Previous page"})) do
21
+ if text || block_given?
22
+ text_or_block(text, &block)
23
+ else
24
+ render(Icon.new("arrow-left"))
25
+ plain("Previous")
26
+ end
27
+ end
28
+ end
29
+
30
+ def next(text = nil, **attrs, &block)
31
+ page_link(**mattr(attrs, aria: {label: "Next page"})) do
32
+ if text || block_given?
33
+ text_or_block(text, &block)
34
+ else
35
+ plain("Next")
36
+ render(Icon.new("arrow-right"))
37
+ end
38
+ end
39
+ end
40
+
41
+ def page(text = nil, current: false, **attrs, &block)
42
+ page_link(
43
+ **mattr(
44
+ attrs,
45
+ aria: {
46
+ current: current ? "page" : nil
47
+ },
48
+ disabled: current,
49
+ class: [page_class, current && "bg-zinc-200/50 dark:bg-zinc-800/50"]
50
+ )
51
+ ) do
52
+ text_or_block(text, &block)
53
+ end
54
+ end
55
+
56
+ def ellipsis(**attrs)
57
+ render(
58
+ Button.new(
59
+ **mattr(
60
+ attrs,
61
+ variant: :ghost,
62
+ disabled: true,
63
+ class: page_class
64
+ )
65
+ )
66
+ ) do
67
+ "…"
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def page_link(text = nil, disabled: nil, **attrs)
74
+ a(
75
+ **mattr(
76
+ attrs,
77
+ role: "link",
78
+ aria: {disabled: disabled.to_s},
79
+ class: link_class
80
+ )
81
+ ) do
82
+ yield
83
+ end
84
+ end
85
+
86
+ def nav_class
87
+ "flex items-center justify-center gap-1 text-sm font-medium"
88
+ end
89
+
90
+ def link_class
91
+ "inline-flex items-center justify-center rounded-md border font-medium h-9 px-3 gap-2 border-transparent aria-disabled:text-muted-foreground [&>svg]:size-4"
92
+ end
93
+
94
+ def page_class
95
+ "w-9 px-0"
96
+ end
97
+ end
98
+ end
@@ -1,44 +1,45 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  class RadioButton < Component
3
5
  include ActionView::Helpers::FormTagHelper
4
6
 
5
- def initialize(name, value:, label: nil, **attrs)
6
- super(**attrs)
7
+ def initialize(label: nil, **attrs)
8
+ @label = label
9
+ @id = id || SecureRandom.hex(4)
10
+
11
+ @class = attrs.delete(:class)
7
12
 
8
- @name = name
9
- @value = value
10
- @label_text = label
11
- @id = "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}"
13
+ super(
14
+ attrs,
15
+ type: "radio",
16
+ id: @id,
17
+ class: [
18
+ "peer appearance-none size-5 shadow-sm rounded-full border text-foreground bg-background",
19
+ "[&[aria-checked='true']]:bg-primary",
20
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
21
+ ]
22
+ )
12
23
  end
13
24
 
25
+ alias :html_label :label
26
+
14
27
  attr_reader(
15
- :name,
16
- :value,
17
28
  :id,
18
- :label_text
29
+ :label
19
30
  )
20
31
 
21
32
  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
- )
33
+ div(class: merge_class("inline-flex items-center gap-2", @class)) do
34
+ html_label(class: "inline-grid *:[grid-area:1/1] place-items-center") do
35
+ input(**attrs)
37
36
  dot
38
37
  end
39
38
 
40
- if label_text.present?
41
- render(Label.new(for: id)) { label_text }
39
+ if label.present? || block_given?
40
+ render(Label.new(for: id)) do
41
+ label || (block_given? ? yield : nil)
42
+ end
42
43
  end
43
44
  end
44
45
  end
@@ -47,7 +48,7 @@ module NitroKit
47
48
 
48
49
  def dot
49
50
  svg(
50
- class: class_names(
51
+ class: merge_class(
51
52
  "row-start-1 col-start-1",
52
53
  "size-2.5 text-primay opacity-0 pointer-events-none",
53
54
  "peer-checked:opacity-100"
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class RadioButtonGroup < Component
5
+ def initialize(options = nil, name: nil, value: nil, **attrs)
6
+ @options = options
7
+
8
+ @name = name
9
+ @group_value = value
10
+
11
+ super(
12
+ attrs,
13
+ name: @name,
14
+ class: "flex items-start flex-col gap-2"
15
+ )
16
+ end
17
+
18
+ attr_reader :name, :group_value, :options
19
+
20
+ def view_template
21
+ div(**attrs) do
22
+ if block_given?
23
+ yield
24
+ else
25
+ options.map { |o| item(*o) }
26
+ end
27
+ end
28
+ end
29
+
30
+ def title(text = nil, **attrs, &block)
31
+ render(Label.new(**attrs)) do
32
+ text_or_block(text, &block)
33
+ end
34
+ end
35
+
36
+ def item(text = nil, value_as_arg = nil, value: nil, **attrs, &block)
37
+ value ||= value_as_arg
38
+
39
+ render(
40
+ RadioButton.new(
41
+ **mattr(
42
+ attrs,
43
+ name: attrs.fetch(:name, name),
44
+ value:,
45
+ checked: group_value.presence == value
46
+ )
47
+ )
48
+ ) do
49
+ text_or_block(text, &block)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Select < Component
5
+ def initialize(options = nil, value: nil, include_empty: false, prompt: nil, index: nil, **attrs)
6
+ @options = options
7
+ @value = value
8
+ @include_empty = include_empty
9
+ @prompt = prompt
10
+ @index = index
11
+
12
+ super(
13
+ attrs,
14
+ class: wrapper_class
15
+ )
16
+ end
17
+
18
+ attr_reader :value, :options, :include_empty, :prompts, :index
19
+
20
+ def view_template
21
+ span(class: wrapper_class, data: {slot: "control"}) do
22
+ select(**attrs, class: select_class) do
23
+ option if include_empty
24
+ options ? options.map { |o| option(*o) } : yield
25
+ end
26
+
27
+ chevron_icon
28
+ end
29
+ end
30
+
31
+ alias :html_option :option
32
+
33
+ def option(key_or_value = nil, value = nil, **attrs, &block)
34
+ value ||= key_or_value
35
+
36
+ html_option(**attrs, selected: @value == value) do
37
+ if block_given?
38
+ yield
39
+ else
40
+ key_or_value
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def wrapper_class
48
+ "w-fit inline-grid *:[grid-area:1/1] group/select"
49
+ end
50
+
51
+ def select_class
52
+ [
53
+ "appearance-none bg-background text-foreground rounded-md border px-3 py-2 pr-10 w-full",
54
+ # Focus
55
+ "focus:outline-none ring-ring ring-offset-2 ring-offset-background focus-visible:ring-2"
56
+ ]
57
+ end
58
+
59
+ def chevron_icon
60
+ svg(
61
+ class: "size-4 self-center place-self-end mr-1.5 text-muted-foreground pointer-events-none group-hover/select:text-foreground",
62
+ viewbox: "0 0 24 24",
63
+ fill: "none",
64
+ stroke: "currentColor",
65
+ stroke_width: 1
66
+ ) do |svg|
67
+ svg.path(d: "m7 15 5 5 5-5")
68
+ svg.path(d: "m7 9 5-5 5 5")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,56 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NitroKit
2
4
  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
5
  def initialize(
28
- name,
29
6
  checked: false,
30
- disabled: false,
31
- size: :base,
7
+ size: :md,
32
8
  description: nil,
33
9
  **attrs
34
10
  )
35
- super(**attrs)
36
-
37
- @name = name
38
11
  @checked = checked
39
- @disabled = disabled
40
12
  @size = size
41
13
  @description = description
14
+
15
+ super(**attrs)
42
16
  end
43
17
 
44
- attr_reader :name, :checked, :disabled, :description, :size
18
+ attr_reader :checked, :description, :size
45
19
 
46
20
  def view_template
47
21
  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}
22
+ **mattr(
23
+ **attrs,
24
+ type: "button",
25
+ class: [base_class, size_class],
26
+ data: {controller: "nk--switch", action: "nk--switch#toggle"},
27
+ role: "switch",
28
+ aria: {checked: checked.to_s}
29
+ )
54
30
  ) do
55
31
  span(class: "sr-only") { description }
56
32
  handle
@@ -60,7 +36,41 @@ module NitroKit
60
36
  private
61
37
 
62
38
  def handle
63
- span(aria: {hidden: true}, class: HANDLE, data: {slot: "handle"})
39
+ span(aria: {hidden: true}, class: handle_class, data: {slot: "handle"})
40
+ end
41
+
42
+ def base_class
43
+ [
44
+ "inline-flex items-center shrink-0",
45
+ "bg-background rounded-full border",
46
+ "transition-colors duration-200 ease-in-out",
47
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ring-offset-background",
48
+
49
+ # Checked
50
+ "[&[aria-checked=true]]:bg-foreground [&[aria-checked=true]]:border-foreground",
51
+
52
+ # Checked > Handle
53
+ "[&[aria-checked=false]_[data-slot=handle]]:bg-primary",
54
+ "[&[aria-checked=true]_[data-slot=handle]]:bg-background"
55
+ ]
56
+ end
57
+
58
+ def handle_class
59
+ [
60
+ "pointer-events-none inline-block rounded-full shadow-sm ring-0",
61
+ "transition translate-x-[3px] duration-200 ease-in-out"
62
+ ]
63
+ end
64
+
65
+ def size_class
66
+ case size
67
+ when :md
68
+ "h-6 w-10 [&_[data-slot=handle]]:size-4 [&[aria-checked=true]_[data-slot=handle]]:translate-x-[calc(theme(spacing.5)-1px)]"
69
+ when :sm
70
+ "h-5 w-8 [&_[data-slot=handle]]:size-3 [&[aria-checked=true]_[data-slot=handle]]:translate-x-[calc(theme(spacing.4)-1px)]"
71
+ else
72
+ raise ArgumentError, "Unknown size: #{size}"
73
+ end
64
74
  end
65
75
  end
66
76
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Table < Component
5
+ def initialize(**attrs)
6
+ super(
7
+ attrs,
8
+ class: "w-full caption-bottom text-sm divide-y"
9
+ )
10
+ end
11
+
12
+ def view_template
13
+ div(class: "relative w-full overflow-auto") do
14
+ table(**attrs) do
15
+ yield
16
+ end
17
+ end
18
+ end
19
+
20
+ alias :html_thead :thead
21
+ alias :html_tbody :tbody
22
+ alias :html_tr :tr
23
+ alias :html_th :th
24
+ alias :html_td :td
25
+
26
+ def thead(**attrs)
27
+ html_thead(**attrs) { yield }
28
+ end
29
+
30
+ def tbody(**attrs)
31
+ html_tbody(**mattr(attrs, class: "[&_tr:last-child]:border-0")) { yield }
32
+ end
33
+
34
+ def tr(**attrs)
35
+ html_tr(**mattr(attrs, class: "border-b")) { yield }
36
+ end
37
+
38
+ def th(text = nil, **attrs, &block)
39
+ html_th(**mattr(attrs, class: [cell_classes, "font-medium text-left"])) do
40
+ text_or_block(text, &block)
41
+ end
42
+ end
43
+
44
+ def td(text = nil, **attrs, &block)
45
+ html_td(**mattr(attrs, class: cell_classes)) do
46
+ text_or_block(text, &block)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def cell_classes
53
+ "py-3 px-2"
54
+ end
55
+ end
56
+ end