nitro_kit 0.2.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +6 -4
  4. data/app/components/nitro_kit/accordion.rb +68 -32
  5. data/app/components/nitro_kit/alert.rb +69 -0
  6. data/app/components/nitro_kit/avatar.rb +52 -0
  7. data/app/components/nitro_kit/badge.rb +46 -19
  8. data/app/components/nitro_kit/button.rb +99 -66
  9. data/app/components/nitro_kit/button_group.rb +18 -13
  10. data/app/components/nitro_kit/card.rb +49 -9
  11. data/app/components/nitro_kit/checkbox.rb +59 -41
  12. data/app/components/nitro_kit/checkbox_group.rb +38 -0
  13. data/app/components/nitro_kit/combobox.rb +138 -0
  14. data/app/components/nitro_kit/component.rb +45 -14
  15. data/app/components/nitro_kit/datepicker.rb +9 -0
  16. data/app/components/nitro_kit/dialog.rb +95 -0
  17. data/app/components/nitro_kit/dropdown.rb +112 -70
  18. data/app/components/nitro_kit/field.rb +221 -56
  19. data/app/components/nitro_kit/field_group.rb +12 -6
  20. data/app/components/nitro_kit/fieldset.rb +42 -7
  21. data/app/components/nitro_kit/form_builder.rb +45 -22
  22. data/app/components/nitro_kit/icon.rb +29 -8
  23. data/app/components/nitro_kit/input.rb +20 -10
  24. data/app/components/nitro_kit/label.rb +18 -5
  25. data/app/components/nitro_kit/pagination.rb +98 -0
  26. data/app/components/nitro_kit/radio_button.rb +28 -27
  27. data/app/components/nitro_kit/radio_button_group.rb +53 -0
  28. data/app/components/nitro_kit/select.rb +72 -0
  29. data/app/components/nitro_kit/switch.rb +49 -39
  30. data/app/components/nitro_kit/table.rb +56 -0
  31. data/app/components/nitro_kit/tabs.rb +98 -0
  32. data/app/components/nitro_kit/textarea.rb +26 -0
  33. data/app/components/nitro_kit/toast.rb +104 -0
  34. data/app/components/nitro_kit/tooltip.rb +53 -0
  35. data/app/helpers/nitro_kit/accordion_helper.rb +2 -0
  36. data/app/helpers/nitro_kit/alert_helper.rb +11 -0
  37. data/app/helpers/nitro_kit/avatar_helper.rb +9 -0
  38. data/app/helpers/nitro_kit/badge_helper.rb +3 -5
  39. data/app/helpers/nitro_kit/button_group_helper.rb +2 -0
  40. data/app/helpers/nitro_kit/button_helper.rb +37 -28
  41. data/app/helpers/nitro_kit/card_helper.rb +2 -0
  42. data/app/helpers/nitro_kit/checkbox_helper.rb +19 -16
  43. data/app/helpers/nitro_kit/combobox_helper.rb +9 -0
  44. data/app/helpers/nitro_kit/datepicker_helper.rb +9 -0
  45. data/app/helpers/nitro_kit/dialog_helper.rb +9 -0
  46. data/app/helpers/nitro_kit/dropdown_helper.rb +3 -1
  47. data/app/helpers/nitro_kit/field_group_helper.rb +9 -0
  48. data/app/helpers/nitro_kit/field_helper.rb +4 -2
  49. data/app/helpers/nitro_kit/fieldset_helper.rb +9 -0
  50. data/app/helpers/nitro_kit/form_helper.rb +13 -0
  51. data/app/helpers/nitro_kit/icon_helper.rb +3 -1
  52. data/app/helpers/nitro_kit/input_helper.rb +35 -0
  53. data/app/helpers/nitro_kit/label_helper.rb +12 -9
  54. data/app/helpers/nitro_kit/pagination_helper.rb +42 -0
  55. data/app/helpers/nitro_kit/radio_button_helper.rb +15 -12
  56. data/app/helpers/nitro_kit/select_helper.rb +24 -0
  57. data/app/helpers/nitro_kit/switch_helper.rb +4 -10
  58. data/app/helpers/nitro_kit/table_helper.rb +9 -0
  59. data/app/helpers/nitro_kit/tabs_helper.rb +9 -0
  60. data/app/helpers/nitro_kit/textarea_helper.rb +9 -0
  61. data/app/helpers/nitro_kit/toast_helper.rb +36 -0
  62. data/app/helpers/nitro_kit/tooltip_helper.rb +9 -0
  63. data/lib/generators/nitro_kit/add_generator.rb +38 -41
  64. data/lib/generators/nitro_kit/install_generator.rb +2 -1
  65. data/lib/nitro_kit/engine.rb +4 -0
  66. data/lib/nitro_kit/schema_builder.rb +90 -16
  67. data/lib/nitro_kit/version.rb +1 -1
  68. data/lib/nitro_kit.rb +39 -1
  69. data/lib/tasks/nitro_kit_tasks.rake +4 -0
  70. metadata +37 -10
  71. data/app/components/nitro_kit/radio_group.rb +0 -35
  72. data/app/helpers/application_helper.rb +0 -109
  73. data/lib/nitro_kit/railtie.rb +0 -8
@@ -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", attrs[:class])) 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-sm 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-sm 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#toggle"}),
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
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Tabs < Component
5
+ def initialize(default: nil, **attrs)
6
+ @default = default
7
+ @id = attrs[:id] || SecureRandom.hex(6)
8
+
9
+ super(
10
+ attrs,
11
+ data: {controller: "nk--tabs", nk__tabs_active_value: default},
12
+ class: base_class
13
+ )
14
+ end
15
+
16
+ attr_reader :default, :id
17
+
18
+ def view_template
19
+ div(**attrs) do
20
+ yield
21
+ end
22
+ end
23
+
24
+ def tabs(**attrs)
25
+ div(**mattr, role: "tabtabs", class: tabs_class) do
26
+ yield
27
+ end
28
+ end
29
+
30
+ def tab(key, text = nil, **attrs, &block)
31
+ button(
32
+ **mattr(
33
+ attrs,
34
+ aria: {
35
+ selected: (default == key).to_s,
36
+ controls: tab_id(key, :panel)
37
+ },
38
+ class: tab_class,
39
+ data: {
40
+ action: "nk--tabs#setActiveTab keydown.left->nk--tabs#prevTab keydown.right->nk--tabs#nextTab",
41
+ key:,
42
+ nk__tabs_key_param: key,
43
+ nk__tabs_target: "tab"
44
+ },
45
+ id: tab_id(key, :tab),
46
+ role: "tab",
47
+ tabindex: default == key ? 0 : -1
48
+ )
49
+ ) do
50
+ text_or_block(text, &block)
51
+ end
52
+ end
53
+
54
+ def panel(key, **attrs)
55
+ div(
56
+ **mattr(
57
+ attrs,
58
+ aria: {
59
+ hidden: (default != key).to_s,
60
+ labelledby: tab_id(key, :tab)
61
+ },
62
+ class: panel_class,
63
+ data: {
64
+ key:,
65
+ nk__tabs_target: "panel"
66
+ },
67
+ id: tab_id(key, :panel),
68
+ name: key,
69
+ role: "tabpanel"
70
+ )
71
+ ) do
72
+ yield
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def tab_id(key, suffix)
79
+ "#{id}-#{key}-#{suffix}"
80
+ end
81
+
82
+ def base_class
83
+ "w-full"
84
+ end
85
+
86
+ def tabs_class
87
+ "flex gap-4 border-b h-10"
88
+ end
89
+
90
+ def tab_class
91
+ "border-b-2 border-transparent hover:border-primary focus-visible:border-primary cursor-pointer text-muted-foreground aria-[selected=true]:text-foreground font-medium aria-[selected=true]:border-primary -mb-px px-2"
92
+ end
93
+
94
+ def panel_class
95
+ "aria-[hidden=true]:hidden py-4"
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Textarea < Component
5
+ def initialize(**attrs)
6
+ super(
7
+ attrs,
8
+ class: default_class
9
+ )
10
+ end
11
+
12
+ def view_template
13
+ textarea(**attrs)
14
+ end
15
+
16
+ private
17
+
18
+ def default_class
19
+ [
20
+ "rounded-md border bg-background border-border text-base px-3 py-2",
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
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class Toast < Component
5
+ class Item < Component
6
+ VARIANTS = %i[default warning error success]
7
+
8
+ def initialize(title: nil, description: nil, variant: :default, **attrs)
9
+ @title = title
10
+ @description = description
11
+ @variant = variant
12
+
13
+ super(
14
+ **mattr(
15
+ attrs,
16
+ class: [
17
+ base_class,
18
+ variant_class
19
+ ],
20
+ role: "status",
21
+ aria: {live: "off", atomic: "true"},
22
+ tabindex: "0",
23
+ data: {state: "closed"}
24
+ )
25
+ )
26
+ end
27
+
28
+ attr_reader :title, :description, :variant
29
+
30
+ def view_template(&block)
31
+ li(**attrs) do
32
+ div(class: "grid gap-1") do
33
+ div(class: "text-sm font-semibold", data: {slot: "title"}) do
34
+ title && plain(title)
35
+ end
36
+
37
+ div(class: "text-sm opacity-90", data: {slot: "description"}) do
38
+ text_or_block(description, &block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def base_class
47
+ "shrink-0 pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md p-4 pr-8 shadow-lg transition-all border opacity-0 transition-all data-[state=open]:opacity-100 data-[state=open]:-translate-y-full"
48
+ end
49
+
50
+ def variant_class
51
+ case variant
52
+ when :default
53
+ "border-border bg-background text-foreground"
54
+ when :warning
55
+ "bg-yellow-50 dark:bg-yellow-950 text-yellow-900 dark:text-yellow-100 border-yellow-500/80 dark:border-yellow-400/50"
56
+ when :success
57
+ "bg-green-50 dark:bg-green-950 text-green-900 dark:text-green-100 border-green-500/80 dark:border-green-400/50"
58
+ when :error
59
+ "bg-red-50 dark:bg-red-950 text-red-900 dark:text-red-100 border-red-400/80 dark:border-red-400/50"
60
+ else
61
+ raise ArgumentError, "Invalid variant `#{variant}'"
62
+ end
63
+ end
64
+ end
65
+
66
+ def initialize(**attrs)
67
+ super(
68
+ attrs,
69
+ role: "region",
70
+ tabindex: "-1",
71
+ aria: {label: "Notifications"},
72
+ class: "pointer-events-none"
73
+ )
74
+ end
75
+
76
+ def view_template
77
+ div(**attrs) do
78
+ ol(class: list_class, data: {nk__toast_target: "list"})
79
+ end
80
+
81
+ flash_sink
82
+
83
+ template(data: {nk__toast_target: "template"}) do
84
+ item
85
+ end
86
+ end
87
+
88
+ def item(title: nil, description: nil, **attrs, &block)
89
+ render(Item.new(title:, description:, **attrs), &block)
90
+ end
91
+
92
+ def flash_sink
93
+ div(id: "nk--toast-sink", data: {nk__toast_target: "sink"}, hidden: true) do
94
+ helpers.nk_toast_flash_messages
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def list_class
101
+ "fixed z-[100] flex max-h-screen w-full p-5 bottom-0 right-0 flex-col h-0 md:max-w-[420px]"
102
+ end
103
+ end
104
+ end