nitro_kit 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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