protos 0.6.0 → 1.0.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/README.md +62 -81
  4. data/examples/list.rb +2 -2
  5. data/examples/navbar.rb +3 -8
  6. data/lib/protos/accordion/item.rb +4 -2
  7. data/lib/protos/accordion.rb +10 -11
  8. data/lib/protos/alert.rb +4 -4
  9. data/lib/protos/avatar.rb +12 -20
  10. data/lib/protos/badge.rb +58 -0
  11. data/lib/protos/breadcrumbs.rb +2 -0
  12. data/lib/protos/card.rb +37 -19
  13. data/lib/protos/carousel.rb +15 -10
  14. data/lib/protos/chat_bubble/content.rb +9 -17
  15. data/lib/protos/chat_bubble.rb +13 -6
  16. data/lib/protos/collapse/title.rb +3 -3
  17. data/lib/protos/collapse.rb +32 -11
  18. data/lib/protos/combobox.rb +3 -3
  19. data/lib/protos/command/input.rb +4 -4
  20. data/lib/protos/command.rb +16 -2
  21. data/lib/protos/component.rb +12 -36
  22. data/lib/protos/diff/item.rb +34 -0
  23. data/lib/protos/diff/resizer.rb +19 -0
  24. data/lib/protos/diff.rb +30 -0
  25. data/lib/protos/drawer.rb +7 -3
  26. data/lib/protos/dropdown/menu.rb +2 -5
  27. data/lib/protos/dropdown.rb +10 -6
  28. data/lib/protos/hero.rb +3 -0
  29. data/lib/protos/list/item.rb +7 -3
  30. data/lib/protos/list.rb +3 -1
  31. data/lib/protos/menu/item.rb +23 -0
  32. data/lib/protos/menu.rb +56 -0
  33. data/lib/protos/mix.rb +3 -5
  34. data/lib/protos/modal/close_button.rb +8 -0
  35. data/lib/protos/modal/dialog.rb +4 -6
  36. data/lib/protos/modal.rb +6 -1
  37. data/lib/protos/popover/content.rb +1 -1
  38. data/lib/protos/popover.rb +24 -21
  39. data/lib/protos/stats.rb +7 -0
  40. data/lib/protos/status.rb +46 -0
  41. data/lib/protos/steps/step.rb +1 -12
  42. data/lib/protos/steps.rb +5 -3
  43. data/lib/protos/swap.rb +3 -0
  44. data/lib/protos/table/caption.rb +5 -3
  45. data/lib/protos/table/cell.rb +3 -3
  46. data/lib/protos/table/head.rb +3 -3
  47. data/lib/protos/table.rb +23 -13
  48. data/lib/protos/tabs/tab.rb +4 -4
  49. data/lib/protos/tabs.rb +30 -22
  50. data/lib/protos/tailwind_merge.rb +65 -0
  51. data/lib/protos/theme.rb +32 -30
  52. data/lib/protos/timeline.rb +8 -3
  53. data/lib/protos/toast.rb +5 -3
  54. data/lib/protos/types.rb +11 -0
  55. data/lib/protos/typography/paragraph.rb +3 -3
  56. data/lib/protos/typography.rb +4 -0
  57. data/lib/protos/version.rb +1 -1
  58. data/lib/protos.rb +41 -121
  59. data/protos.gemspec +4 -5
  60. metadata +19 -29
  61. data/lib/protos/command/dialog.rb +0 -40
@@ -7,29 +7,21 @@ module Protos
7
7
  # content of the message. It will be colored according to the type.
8
8
 
9
9
  STYLES = {
10
- none: "",
11
- primary: "chat-bubble-primary",
12
- secondary: "chat-bubble-secondary",
10
+ default: "",
13
11
  accent: "chat-bubble-accent",
12
+ error: "chat-bubble-error",
14
13
  info: "chat-bubble-info",
14
+ neutral: "chat-bubble-neutral",
15
+ primary: "chat-bubble-primary",
16
+ secondary: "chat-bubble-secondary",
15
17
  success: "chat-bubble-success",
16
- warning: "chat-bubble-warning",
17
- error: "chat-bubble-error"
18
+ warning: "chat-bubble-warning"
18
19
  }.freeze
19
20
 
20
21
  option :type,
21
- default: -> { :none },
22
- reader: false,
23
- type: Types::Coercible::Symbol.enum(
24
- :none,
25
- :primary,
26
- :secondary,
27
- :accent,
28
- :info,
29
- :success,
30
- :warning,
31
- :error
32
- )
22
+ default: -> { :default },
23
+ reader: false,
24
+ type: Types::Styles
33
25
 
34
26
  def view_template(&)
35
27
  div(**attrs, &)
@@ -7,18 +7,25 @@ module Protos
7
7
  # in a larger chat history.
8
8
  # https://daisyui.com/components/chat/
9
9
 
10
+ autoload :Content, "protos/chat_bubble/content"
11
+ autoload :Image, "protos/chat_bubble/image"
12
+ autoload :Header, "protos/chat_bubble/header"
13
+ autoload :Footer, "protos/chat_bubble/footer"
14
+
15
+ Positions = Types::Coercible::Symbol.enum(
16
+ :start,
17
+ :end
18
+ )
19
+
10
20
  ALIGNMENTS = {
11
21
  start: "chat-start",
12
22
  end: "chat-end"
13
23
  }.freeze
14
24
 
15
25
  option :align,
16
- default: -> { :start },
17
- reader: false,
18
- type: Types::Coercible::Symbol.enum(
19
- :start,
20
- :end
21
- )
26
+ default: -> { :start },
27
+ reader: false,
28
+ type: Positions
22
29
 
23
30
  def view_template(&)
24
31
  div(**attrs, &)
@@ -7,9 +7,9 @@ module Protos
7
7
  # visible and is used to toggle the collapse.
8
8
 
9
9
  option :input_id,
10
- type: Types::String | Types::Integer | Types::Nil,
11
- reader: false,
12
- default: -> { }
10
+ type: Types::String | Types::Integer | Types::Nil,
11
+ reader: false,
12
+ default: -> { }
13
13
 
14
14
  def view_template(&)
15
15
  if @input_id
@@ -6,20 +6,41 @@ module Protos
6
6
  # is visible at all times, and the content is only visible when expanded.
7
7
  # https://daisyui.com/components/collapse/
8
8
 
9
+ autoload :Title, "protos/collapse/title"
10
+ autoload :Content, "protos/collapse/content"
11
+
12
+ Icons = Types::Coercible::Symbol.enum(:arrow, :plus)
13
+
14
+ ICONS = {
15
+ arrow: "collapse-arrow",
16
+ plus: "collapse-plus"
17
+ }.freeze
18
+
19
+ States = Types::Coercible::Symbol.enum(:default, :open, :close)
20
+
21
+ STATES = {
22
+ default: "",
23
+ open: "collapse-open",
24
+ close: "collapse-close"
25
+ }.freeze
26
+
27
+ option :state, type: States, default: -> { :default }, reader: false
28
+ option :icon, type: Icons, default: -> { :arrow }, reader: false
9
29
  option :input_type, default: -> { :checkbox }, reader: false
10
- option :input_id,
11
- reader: false,
12
- default: -> { "collapse-#{SecureRandom.hex(4)}" },
13
- type: Types::String
30
+ option :input_name,
31
+ reader: false,
32
+ default: -> { "collapse-#{SecureRandom.hex(4)}" },
33
+ type: Types::String | Types::Integer
14
34
 
15
35
  def view_template
16
36
  div(**attrs) do
17
37
  if @input_type
18
38
  input(
19
39
  type: @input_type,
20
- id: @input_id,
21
- name: @input_id,
40
+ id: @input_name,
41
+ name: @input_name,
22
42
  autocomplete: :off,
43
+ aria_label: "Toggle accordion",
23
44
  # form: "" prevents the radio button from being submitted if its
24
45
  # within a form
25
46
  form: ""
@@ -29,7 +50,7 @@ module Protos
29
50
  end
30
51
  end
31
52
 
32
- def title(*, **, &) = render Title.new(*, input_id: @input_id, **, &)
53
+ def title(*, **, &) = render Title.new(*, input_id: @input_name, **, &)
33
54
 
34
55
  def content(...) = render Content.new(...)
35
56
 
@@ -37,10 +58,10 @@ module Protos
37
58
 
38
59
  def theme
39
60
  {
40
- container: %w[
41
- collapse
42
- collapse-arrow
43
- bg-base-100
61
+ container: [
62
+ "collapse",
63
+ ICONS.fetch(@icon),
64
+ STATES.fetch(@state)
44
65
  ]
45
66
  }
46
67
  end
@@ -7,9 +7,9 @@ module Protos
7
7
  # Comboboxes use popovers and command to create the list of options.
8
8
 
9
9
  option :trigger,
10
- default: -> { :click },
11
- reader: false,
12
- type: Popover::Triggers | Types::Array.of(Popover::Triggers)
10
+ default: -> { :click },
11
+ reader: false,
12
+ type: Popover::Triggers | Types::Array.of(Popover::Triggers)
13
13
 
14
14
  def trigger(...) = render Popover::Trigger.new(...)
15
15
 
@@ -6,10 +6,10 @@ module Protos
6
6
  # DOCS: The search input for the command palette
7
7
 
8
8
  option :placeholder,
9
- reader: :private,
10
- default: -> {
11
- "Type a command or search..."
12
- }
9
+ reader: :private,
10
+ default: -> {
11
+ "Type a command or search..."
12
+ }
13
13
 
14
14
  def view_template(&block)
15
15
  li(**attrs) do
@@ -6,6 +6,15 @@ module Protos
6
6
  # filterable list of commands. Command modals are by default closable by
7
7
  # clicking the overlay rather than a specific close button.
8
8
 
9
+ autoload :Input, "protos/command/input"
10
+ autoload :Dialog, "protos/command/dialog"
11
+ autoload :Group, "protos/command/group"
12
+ autoload :List, "protos/command/list"
13
+ autoload :Trigger, "protos/command/trigger"
14
+ autoload :Title, "protos/command/title"
15
+ autoload :Item, "protos/command/item"
16
+ autoload :Empty, "protos/command/empty"
17
+
9
18
  def view_template(&)
10
19
  div(**attrs, &)
11
20
  end
@@ -16,7 +25,9 @@ module Protos
16
25
 
17
26
  def trigger(...) = render Command::Trigger.new(...)
18
27
 
19
- def dialog(...) = render Command::Dialog.new(...)
28
+ def dialog(...) = render Modal::Dialog.new(...)
29
+
30
+ def close_button(...) = render Modal::CloseButton.new(...)
20
31
 
21
32
  def title(...) = render Command::Title.new(...)
22
33
 
@@ -30,7 +41,10 @@ module Protos
30
41
 
31
42
  def default_attrs
32
43
  {
33
- data: { controller: "protos--modal" }
44
+ data: {
45
+ controller: "protos--modal",
46
+ action: "click->protos--modal#backdropClose"
47
+ }
34
48
  }
35
49
  end
36
50
  end
@@ -5,36 +5,20 @@ module Protos
5
5
  # DOCS: Base component for all Protos::Components. You can inherit from this
6
6
  # class to gain flexible components you can style from the outside using css
7
7
  # slots, default attrs and themes.
8
- #
9
- # This component extends Dry::Initializer and Dry::Core::ClassAttributes to
10
- # make configuring each class much easier. It also enables gathering up all
11
- # undefined options and adding them to the html_options hash.
12
8
 
13
- extend Dry::Initializer
14
- extend Dry::Core::ClassAttributes
15
-
16
- # Define methods for css and attrs. Each is expected to return a hash
17
- defines :theme_method, type: Types::Symbol
18
- defines :default_attrs_method, type: Types::Symbol
19
-
20
- theme_method :theme
21
- default_attrs_method :default_attrs
9
+ extend Dry::Initializer[undefined: false]
22
10
 
23
11
  # Theme can override the css hash and add additional styles
24
12
  option :theme, as: :theme_override, default: -> { {} }, reader: false
25
13
  # Class becomes the :container key in the css hash
26
- option :class, as: :container_class, default: -> { "" }, reader: false
27
- option :html_options, default: -> { {} }, reader: false
14
+ option :class, as: :container_class, default: -> { }, reader: false
28
15
 
29
16
  # Adds non-defined options to the html_options hash
30
17
  def initialize(*, **kwargs, &)
31
- defined_keys = self.class.dry_initializer.definitions.keys
32
- defined, undefined =
33
- kwargs
34
- .partition { |key, _| defined_keys.include?(key) }
35
- .map!(&:to_h)
36
-
37
- super(*, html_options: undefined, **defined, &)
18
+ super
19
+ attributes = self.class.dry_initializer.attributes(kwargs)
20
+ extra_keys = kwargs.keys - attributes.keys
21
+ @html_options = kwargs.slice(*extra_keys)
38
22
  end
39
23
 
40
24
  private
@@ -48,35 +32,27 @@ module Protos
48
32
  end
49
33
 
50
34
  def build_attrs(...)
51
- defaults = if respond_to?(default_attrs_method, :include_private)
52
- send(default_attrs_method)
53
- end
54
-
55
35
  Attributes
56
36
  .new(...)
57
- .merge(defaults)
37
+ .merge(default_attrs)
58
38
  .merge(@html_options)
59
39
  .merge(class: css[:container])
60
40
  end
61
41
 
62
42
  def build_theme(...)
63
- component_style = if respond_to?(theme_method, :include_private)
64
- send(theme_method)
65
- end
66
-
67
43
  Theme
68
44
  .new(...)
69
- .merge(component_style)
45
+ .merge(theme)
70
46
  .merge(@theme_override)
71
47
  .merge(container: @container_class)
72
48
  end
73
49
 
74
- def theme_method
75
- self.class.theme_method
50
+ def default_attrs
51
+ {}
76
52
  end
77
53
 
78
- def default_attrs_method
79
- self.class.default_attrs_method
54
+ def theme
55
+ {}
80
56
  end
81
57
  end
82
58
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protos
4
+ class Diff
5
+ class Item < Component
6
+ Order = Types::Coercible::Symbol.enum(:one, :two)
7
+
8
+ ORDER = {
9
+ one: "diff-item-1",
10
+ two: "diff-item-2"
11
+ }.freeze
12
+
13
+ option :order, type: Order, default: -> { :one }, reader: false
14
+
15
+ def view_template(&)
16
+ div(**attrs, &)
17
+ end
18
+
19
+ private
20
+
21
+ def default_attrs
22
+ {
23
+ role: :img
24
+ }
25
+ end
26
+
27
+ def theme
28
+ {
29
+ container: ORDER.fetch(@order)
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protos
4
+ class Diff
5
+ class Resizer < Component
6
+ def view_template(&)
7
+ div(**attrs, &)
8
+ end
9
+
10
+ private
11
+
12
+ def theme
13
+ {
14
+ container: "diff-resizer"
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protos
4
+ class Diff < Component
5
+ autoload :Item, "protos/diff/item"
6
+ autoload :Resizer, "protos/diff/resizer"
7
+
8
+ def view_template(&)
9
+ figure(**attrs, &)
10
+ end
11
+
12
+ def item_one(*, **, &) = render Item.new(*, order: :one, **, &)
13
+ def item_two(*, **, &) = render Item.new(*, order: :two, **, &)
14
+ def resizer(...) = render Resizer.new(...)
15
+
16
+ private
17
+
18
+ def default_attrs
19
+ {
20
+ tabindex: 0
21
+ }
22
+ end
23
+
24
+ def theme
25
+ {
26
+ container: "diff"
27
+ }
28
+ end
29
+ end
30
+ end
data/lib/protos/drawer.rb CHANGED
@@ -7,10 +7,14 @@ module Protos
7
7
  # trigger is clicked.
8
8
  # https://daisyui.com/components/drawer/
9
9
 
10
+ autoload :Side, "protos/drawer/side"
11
+ autoload :Trigger, "protos/drawer/trigger"
12
+ autoload :Content, "protos/drawer/content"
13
+
10
14
  option :id,
11
- reader: false,
12
- type: Types::Coercible::String,
13
- default: -> { "drawer-#{SecureRandom.hex(4)}" }
15
+ reader: false,
16
+ type: Types::Coercible::String,
17
+ default: -> { "drawer-#{SecureRandom.hex(4)}" }
14
18
 
15
19
  def view_template
16
20
  div(**attrs) do
@@ -8,8 +8,8 @@ module Protos
8
8
  # dropdowns comes from there.
9
9
 
10
10
  def view_template(&block)
11
- template_tag(**template_attrs) do
12
- ul(**attrs, &block)
11
+ template(**template_attrs) do
12
+ render ::Protos::Menu.new(**attrs, &block)
13
13
  end
14
14
  end
15
15
 
@@ -18,11 +18,8 @@ module Protos
18
18
  def theme
19
19
  {
20
20
  container: %w[
21
- menu
22
21
  dropdown-content
23
22
  z-10
24
- bg-base-100
25
- rounded-box
26
23
  ]
27
24
  }
28
25
  end
@@ -10,14 +10,18 @@ module Protos
10
10
  # rather than pure CSS for accessibility. The layout of pure CSS was too
11
11
  # tricky to get right and we felt the dependency tradeoff was worthwhile.
12
12
 
13
+ autoload :Item, "protos/dropdown/item"
14
+ autoload :Menu, "protos/dropdown/menu"
15
+ autoload :Trigger, "protos/dropdown/trigger"
16
+
13
17
  option :position,
14
- type: Popover::Positions,
15
- default: -> { :bottom },
16
- reader: false
18
+ type: Popover::Positions,
19
+ default: -> { :bottom },
20
+ reader: false
17
21
  option :trigger,
18
- default: -> { :click },
19
- reader: false,
20
- type: Popover::Triggers | Types::Array.of(Popover::Triggers)
22
+ default: -> { :click },
23
+ reader: false,
24
+ type: Popover::Triggers | Types::Array.of(Popover::Triggers)
21
25
 
22
26
  def item(...) = render Item.new(...)
23
27
 
data/lib/protos/hero.rb CHANGED
@@ -6,6 +6,9 @@ module Protos
6
6
  # optionally layout an image for a responsive layout.
7
7
  # https://daisyui.com/components/hero/
8
8
 
9
+ autoload :Content, "protos/hero/content"
10
+ autoload :Overlay, "protos/hero/overlay"
11
+
9
12
  def view_template(&)
10
13
  div(**attrs, &)
11
14
  end
@@ -7,6 +7,9 @@ module Protos
7
7
  # work for list items, including border radius. E.g. only the first and
8
8
  # last items will have border radius on the top and bottom.
9
9
 
10
+ option :wrap, Types::Bool, default: -> { false }, reader: :private
11
+ option :grow, Types::Bool, default: -> { false }, reader: :private
12
+
10
13
  def view_template(&)
11
14
  li(**attrs, &)
12
15
  end
@@ -15,9 +18,10 @@ module Protos
15
18
 
16
19
  def theme
17
20
  {
18
- container: %w[
19
- join-item
20
- [&:not(:first-child)]:border-t-0
21
+ container: [
22
+ "list-row",
23
+ ("list-col-wrap" if wrap),
24
+ ("list-col-grow" if grow)
21
25
  ]
22
26
  }
23
27
  end
data/lib/protos/list.rb CHANGED
@@ -5,6 +5,8 @@ module Protos
5
5
  # DOCS: A list of items that are joined together for easier styling with
6
6
  # borders, border radius, etc.
7
7
 
8
+ autoload :Item, "protos/list/item"
9
+
8
10
  option :ordered, Types::Bool, default: -> { false }, reader: false
9
11
 
10
12
  def view_template(&)
@@ -17,7 +19,7 @@ module Protos
17
19
 
18
20
  def theme
19
21
  {
20
- container: "join join-vertical"
22
+ container: "list"
21
23
  }
22
24
  end
23
25
 
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protos
4
+ class Menu
5
+ class Item < Component
6
+ option :title, type: Types::Bool, default: -> { false }
7
+
8
+ def view_template(&)
9
+ li(**attrs, &)
10
+ end
11
+
12
+ private
13
+
14
+ def theme
15
+ {
16
+ container: [
17
+ ("menu-title" if title)
18
+ ]
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protos
4
+ class Menu < Component
5
+ # DOCS: A menu of links or actions.
6
+ # https://daisyui.com/components/menu/
7
+
8
+ Directions = Types::Coercible::Symbol.enum(:vertical, :horizontal)
9
+ Sizes = Types::Coercible::Symbol.enum(:xs, :sm, :md, :lg, :xl)
10
+
11
+ DIRECTIONS = {
12
+ vertical: "",
13
+ horizontal: "menu-horizontal"
14
+ }.freeze
15
+
16
+ SIZES = {
17
+ xs: "menu-xs",
18
+ sm: "menu-sm",
19
+ md: "menu-md",
20
+ lg: "menu-lg",
21
+ xl: "menu-xl"
22
+ }.freeze
23
+
24
+ autoload :Item, "protos/menu/item"
25
+
26
+ option :size,
27
+ type: Sizes,
28
+ default: -> { :md },
29
+ reader: :private
30
+
31
+ option :direction,
32
+ type: Directions,
33
+ default: -> { :vertical },
34
+ reader: :private
35
+
36
+ def view_template(&)
37
+ ul(**attrs, &)
38
+ end
39
+
40
+ def item(...)
41
+ render Item.new(...)
42
+ end
43
+
44
+ private
45
+
46
+ def theme
47
+ {
48
+ container: [
49
+ "menu",
50
+ DIRECTIONS[direction],
51
+ SIZES[size]
52
+ ]
53
+ }
54
+ end
55
+ end
56
+ end
data/lib/protos/mix.rb CHANGED
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Protos
4
- class Mix
4
+ module Mix
5
5
  # DOCS: This class is responsible for safely merging in both user supplied
6
6
  # and default attributes. When a user adds { data: { controller: "foo" }} to
7
7
  # their component. This will merge the value in so that any default
8
8
  # controllers do not get overridden.
9
9
 
10
- MERGEABLE_ATTRIBUTES = Set.new(%i[class data]).freeze
10
+ module_function
11
11
 
12
- def self.call(...) = new.call(...)
12
+ MERGEABLE_ATTRIBUTES = Set.new(%i[class data]).freeze
13
13
 
14
14
  def call(old_hash, *hashes)
15
15
  hashes
@@ -19,8 +19,6 @@ module Protos
19
19
  end
20
20
  end
21
21
 
22
- private
23
-
24
22
  def merge(old_hash, new_hash, top_level: false) # rubocop:disable Metrics/PerceivedComplexity
25
23
  old_hash.merge!(new_hash) do |key, old, new|
26
24
  next old unless new
@@ -10,6 +10,14 @@ module Protos
10
10
  button(**attrs, &block)
11
11
  end
12
12
  end
13
+
14
+ private
15
+
16
+ def default_attrs
17
+ {
18
+ data: { action: "protos--modal#close" }
19
+ }
20
+ end
13
21
  end
14
22
  end
15
23
  end
@@ -14,12 +14,10 @@ module Protos
14
14
 
15
15
  private
16
16
 
17
- def attrs
18
- @attrs ||= build_attrs(
19
- {
20
- data: { "protos--modal-target": "modal" }
21
- }
22
- )
17
+ def default_attrs
18
+ {
19
+ data: { "protos--modal-target": "dialog" }
20
+ }
23
21
  end
24
22
 
25
23
  def theme
data/lib/protos/modal.rb CHANGED
@@ -5,6 +5,10 @@ module Protos
5
5
  # DOCS: A modal component that can be triggered by a button or a link and
6
6
  # will open a fullscreen modal, usually with a close button.
7
7
 
8
+ autoload :CloseButton, "protos/modal/close_button"
9
+ autoload :Dialog, "protos/modal/dialog"
10
+ autoload :Trigger, "protos/modal/trigger"
11
+
8
12
  def view_template(&)
9
13
  div(**attrs, class: css[:container], &)
10
14
  end
@@ -20,7 +24,8 @@ module Protos
20
24
  def default_attrs
21
25
  {
22
26
  data: {
23
- controller: "protos--modal"
27
+ controller: "protos--modal",
28
+ action: "click->protos--modal#backdropClose"
24
29
  }
25
30
  }
26
31
  end