nitro_kit 0.1.0 → 0.3.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 (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