anchor_view_components 0.20.1 → 0.22.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/app/assets/builds/anchor-view-components.css +1 -1
  4. data/app/assets/images/icons/calendar.svg +7 -0
  5. data/app/assets/images/icons/check.svg +4 -0
  6. data/app/assets/images/icons/paste-clipboard.svg +5 -0
  7. data/app/assets/images/icons/search.svg +5 -0
  8. data/app/assets/stylesheets/components/button.css +1 -1
  9. data/app/components/anchor/action_menu_component.html.erb +8 -10
  10. data/app/components/anchor/action_menu_component.rb +15 -6
  11. data/app/components/anchor/anchor_view_components.ts +2 -0
  12. data/app/components/anchor/autocomplete_component.html.erb +6 -7
  13. data/app/components/anchor/autocomplete_component.rb +29 -8
  14. data/app/components/anchor/badge_component.html.erb +3 -6
  15. data/app/components/anchor/badge_component.rb +0 -10
  16. data/app/components/anchor/banner_component.html.erb +1 -7
  17. data/app/components/anchor/banner_component.rb +0 -10
  18. data/app/components/anchor/button_component.html.erb +10 -17
  19. data/app/components/anchor/button_component.rb +14 -21
  20. data/app/components/anchor/component.rb +22 -5
  21. data/app/components/anchor/copy_to_clipboard_component.en.yml +2 -0
  22. data/app/components/anchor/copy_to_clipboard_component.html.erb +21 -0
  23. data/app/components/anchor/copy_to_clipboard_component.rb +15 -0
  24. data/app/components/anchor/copy_to_clipboard_controller.ts +34 -0
  25. data/app/components/anchor/dialog_component.html.erb +3 -4
  26. data/app/components/anchor/icon_component.html.erb +1 -3
  27. data/app/components/anchor/icon_component.rb +1 -1
  28. data/app/components/anchor/loading_indicator_component.html.erb +3 -4
  29. data/app/components/anchor/logo_component.html.erb +1 -4
  30. data/app/components/anchor/logo_component.rb +1 -1
  31. data/app/components/anchor/panel/body_component.html.erb +3 -5
  32. data/app/components/anchor/panel_component.html.erb +3 -1
  33. data/app/components/anchor/popover_component.html.erb +3 -7
  34. data/app/components/anchor/popover_controller.ts +21 -15
  35. data/app/components/anchor/prose_component.html.erb +5 -4
  36. data/app/components/anchor/radio_button_collection_component.html.erb +1 -0
  37. data/app/components/anchor/radio_button_collection_component.rb +35 -0
  38. data/app/components/anchor/radio_button_component.html.erb +11 -0
  39. data/app/components/anchor/radio_button_component.rb +105 -0
  40. data/app/components/anchor/side_nav_component.html.erb +8 -9
  41. data/app/components/anchor/side_nav_component.rb +4 -1
  42. data/app/components/anchor/tab_bar/tab_component.html.erb +8 -6
  43. data/app/components/anchor/tab_bar/tab_component.rb +2 -2
  44. data/app/components/anchor/tab_bar_component.html.erb +3 -4
  45. data/app/components/anchor/tab_bar_component.rb +3 -2
  46. data/app/components/anchor/table_component.rb +1 -1
  47. data/app/components/anchor/text_component.html.erb +1 -6
  48. data/app/components/anchor/text_component.rb +5 -14
  49. data/app/components/anchor/text_field_component.html.erb +19 -1
  50. data/app/components/anchor/text_field_component.rb +35 -16
  51. data/app/components/anchor/toast_component.html.erb +5 -8
  52. data/app/components/anchor/toast_component.rb +8 -12
  53. data/app/components/anchor/toast_controller.ts +5 -1
  54. data/app/helpers/anchor/fetch_or_fallback_helper.rb +26 -0
  55. data/app/helpers/anchor/form_builder.rb +52 -29
  56. data/app/helpers/anchor/tailwind_constants.rb +0 -37
  57. data/app/helpers/anchor/view_helper.rb +11 -0
  58. data/lib/anchor/view_components/engine.rb +0 -1
  59. data/lib/anchor/view_components/version.rb +1 -1
  60. data/previews/anchor/copy_to_clipboard_component_preview.rb +7 -0
  61. data/previews/anchor/forms_preview.rb +45 -0
  62. data/previews/forms/default.html.erb +6 -4
  63. data/previews/forms/radio_button_collection.html.erb +51 -0
  64. data/previews/forms/with_icons.html.erb +10 -0
  65. metadata +17 -2
@@ -0,0 +1,15 @@
1
+ module Anchor
2
+ class CopyToClipboardComponent < Component
3
+ attr_reader :label, :value
4
+
5
+ def initialize(
6
+ value: nil,
7
+ label: nil,
8
+ **kwargs
9
+ )
10
+ @label = label
11
+ @value = value
12
+ super
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller<HTMLDivElement> {
4
+ static targets = [ "initialIcon", "successIcon"];
5
+ static classes = [ "hidden" ]
6
+
7
+ declare readonly initialIconTarget: SVGElement;
8
+ declare readonly successIconTarget: SVGElement;
9
+ declare readonly notificationDelayValue: number;
10
+ declare readonly hiddenClass: string;
11
+ declare readonly textToCopyValue: string;
12
+ declare readonly hasTextToCopyValue: boolean;
13
+
14
+ static values = {
15
+ notificationDelay: { type: Number, default: 1500 },
16
+ textToCopy: String,
17
+ };
18
+
19
+ copy(): void {
20
+ if (this.hasTextToCopyValue) {
21
+ navigator.clipboard.writeText(this.textToCopyValue);
22
+ }
23
+ this.toggleIcons();
24
+
25
+ setTimeout(() => {
26
+ this.toggleIcons();
27
+ }, this.notificationDelayValue);
28
+ }
29
+
30
+ toggleIcons(): void {
31
+ this.initialIconTarget.classList.toggle(this.hiddenClass);
32
+ this.successIconTarget.classList.toggle(this.hiddenClass);
33
+ }
34
+ }
@@ -1,14 +1,13 @@
1
1
  <%= show_button if show_button? %>
2
2
 
3
- <%= tag.dialog(
3
+ <%= tag.dialog(**merge_options(wrapper_options, {
4
4
  aria: { labelledby: title_id },
5
- class: class_names("w-[36rem] rounded-lg p-0 bg-white shadow-lg backdrop:bg-grey-100/50", classes),
5
+ class: "w-[36rem] rounded-lg p-0 bg-white shadow-lg backdrop:bg-grey-100/50",
6
6
  data: {
7
7
  controller: "dialog",
8
8
  testid: title_id,
9
9
  },
10
- id: id,
11
- ) do %>
10
+ })) do %>
12
11
  <header class="p-6 flex gap-4 justify-between items-center">
13
12
  <%= render Anchor::TextComponent.new(
14
13
  variant: :heading_2xl,
@@ -1,6 +1,4 @@
1
1
  <%= anchor_svg(
2
2
  "icons/#{icon}",
3
- aria: { hidden: true },
4
- class: classes,
5
- data: data,
3
+ **merge_options(wrapper_options, { aria: { hidden: true } }),
6
4
  ) %>
@@ -3,7 +3,7 @@ module Anchor
3
3
  def initialize(icon:, **kwargs)
4
4
  @icon = icon
5
5
 
6
- super
6
+ super(**kwargs)
7
7
  end
8
8
 
9
9
  private
@@ -1,7 +1,6 @@
1
1
  <%= tag.span t(".label"), class: "sr-only" %>
2
2
 
3
- <%= anchor_icon(
3
+ <%= anchor_icon(**merge_options(wrapper_options,
4
+ class: class_names(wrapper_options[:class], "text-grey-100 animate-spin"),
4
5
  icon: "loading-indicator",
5
- classes: "text-grey-100 animate-spin",
6
- data: data,
7
- ) %>
6
+ )) %>
@@ -1,7 +1,4 @@
1
- <%= tag.div(
2
- class: classes,
3
- data: data,
4
- ) do %>
1
+ <%= tag.div(wrapper_options) do %>
5
2
  <%= tag.div(
6
3
  class: class_names(
7
4
  "w-fit max-w-full",
@@ -35,7 +35,7 @@ module Anchor
35
35
  ]
36
36
  @variant_aspect_ratio = VARIANT_ASPECT_RATIO_MAPPINGS[variant]
37
37
 
38
- super
38
+ super(**kwargs)
39
39
  end
40
40
 
41
41
  private
@@ -1,8 +1,6 @@
1
- <%= tag.div(
2
- class: class_names(
3
- "p-6",
4
- classes
5
- )
1
+ <%= tag.div(**wrapper_options.except(:class).merge({
2
+ class: class_names("p-6", wrapper_options[:class]),
3
+ }),
6
4
  ) do %>
7
5
  <%= content %>
8
6
  <% end %>
@@ -1,4 +1,6 @@
1
- <%= tag.div(class: class_names("border border-subdued", classes)) do %>
1
+ <%= tag.div(**merge_options(wrapper_options,
2
+ class: "border border-subdued",
3
+ )) do %>
2
4
  <%= header %>
3
5
 
4
6
  <% if active? %>
@@ -4,15 +4,11 @@
4
4
  ) do %>
5
5
  <%= trigger if trigger? %>
6
6
 
7
- <%= tag.div(
8
- class: class_names(
9
- "absolute -left-full -top-full m-0 bg-white rounded border border-subdued popover-open:block",
10
- classes
11
- ),
7
+ <%= tag.div(**merge_options(wrapper_options,
8
+ class: "absolute -left-full -top-full m-0 bg-white rounded border border-subdued popover-open:block",
12
9
  data: { action: "toggle->popover#positionPopover" },
13
- id:,
14
10
  popover: "auto",
15
- ) do %>
11
+ )) do %>
16
12
  <%= content %>
17
13
  <% end %>
18
14
  <% end %>
@@ -1,5 +1,5 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
- import { computePosition, autoPlacement, offset } from "@floating-ui/dom";
2
+ import { computePosition, autoPlacement, offset, autoUpdate } from "@floating-ui/dom";
3
3
 
4
4
  export default class extends Controller<HTMLDivElement> {
5
5
  static targets = ["button"];
@@ -7,20 +7,26 @@ export default class extends Controller<HTMLDivElement> {
7
7
  declare readonly buttonTarget: HTMLButtonElement;
8
8
 
9
9
  positionPopover = (event: { target: HTMLDivElement }): void => {
10
+ const trigger = this.buttonTarget
10
11
  const popover = event.target;
11
- computePosition(this.buttonTarget, popover, {
12
- placement: "bottom-start",
13
- middleware: [
14
- autoPlacement({
15
- allowedPlacements: ["bottom-start", "bottom-end", "top-start", "top-end"],
16
- }),
17
- offset(4)
18
- ],
19
- }).then(({ x, y }) => {
20
- Object.assign(popover.style, {
21
- left: `${x}px`,
22
- top: `${y}px`,
23
- });
24
- });
12
+
13
+ const updatePosition = () => {
14
+ computePosition(trigger, popover, {
15
+ placement: "bottom-start",
16
+ middleware: [
17
+ autoPlacement({
18
+ allowedPlacements: ["bottom-start", "bottom-end", "top-start", "top-end"],
19
+ }),
20
+ offset(4)
21
+ ],
22
+ }).then(({ x, y }) => {
23
+ Object.assign(popover.style, {
24
+ left: `${x}px`,
25
+ top: `${y}px`,
26
+ });
27
+ })
28
+ };
29
+
30
+ autoUpdate(trigger, popover, updatePosition);
25
31
  };
26
32
  }
@@ -1,8 +1,9 @@
1
1
  <%= tag.div(
2
2
  content,
3
- class: class_names(
4
- "prose",
5
- classes,
3
+ **wrapper_options.except(:class).merge(
4
+ class: class_names(
5
+ "prose",
6
+ wrapper_options[:class],
7
+ ),
6
8
  ),
7
- data: data
8
9
  ) %>
@@ -0,0 +1 @@
1
+ <%= tag.div content, class: "flex flex-col space-y-2 text-base" %>
@@ -0,0 +1,35 @@
1
+ module Anchor
2
+ class RadioButtonCollectionComponent < Component
3
+ attr_reader :attribute, :descriptions, :form_builder
4
+
5
+ def initialize(
6
+ form_builder:,
7
+ attribute:,
8
+ collection:,
9
+ value_method:,
10
+ text_method:,
11
+ **options
12
+ )
13
+ @form_builder = form_builder
14
+ @attribute = attribute
15
+ @collection = collection
16
+ @value_method = value_method
17
+ @text_method = text_method
18
+ @descriptions = options.delete(:descriptions)
19
+
20
+ super
21
+
22
+ def options
23
+ { class: RadioButtonComponent::INPUT_CLASSES }
24
+ end
25
+
26
+ def radio(radio:)
27
+ RadioButtonComponent.new(
28
+ radio:,
29
+ attribute:,
30
+ form_builder:
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ <div class="flex gap-1 items-start">
2
+ <%= radio.radio_button(radio_options) %>
3
+ <% if description.present? %>
4
+ <div class="flex flex-col gap-1">
5
+ <%= radio.label %>
6
+ <%= description_span %>
7
+ </div>
8
+ <% else %>
9
+ <%= tag.div radio.label, class: "self-center" %>
10
+ <% end %>
11
+ </div>
@@ -0,0 +1,105 @@
1
+ module Anchor
2
+ class RadioButtonComponent < Anchor::Component
3
+ INPUT_CLASSES = %w(
4
+ appearance-none
5
+ w-[18px]
6
+ m-[3px]
7
+ aspect-square
8
+ rounded-full
9
+ border-2
10
+ border-grey-60
11
+ hover:border-grey-80
12
+ checked:bg-gradient-radial
13
+ checked:from-blue-50
14
+ checked:from-[50%]
15
+ checked:to-transparent
16
+ checked:to-[54%]
17
+ checked:border-blue-50
18
+ checked:hover:from-blue-60
19
+ checked:hover:from-[50%]
20
+ checked:hover:to-transparent
21
+ checked:hover:to-[54%]
22
+ checked:hover:border-blue-60
23
+ disabled:border-grey-40
24
+ disabled:checked:border-grey-40
25
+ disabled:checked:from-grey-40
26
+ disabled:checked:from-[50%]
27
+ disabled:checked:to-transparent
28
+ disabled:checked:to-[54%]
29
+ disabled:checked:border-grey-40
30
+ aria-readonly:border-grey-40
31
+ aria-readonly:checked:border-grey-40
32
+ aria-readonly:checked:from-grey-40
33
+ aria-readonly:checked:from-[50%]
34
+ aria-readonly:checked:to-transparent
35
+ aria-readonly:checked:to-[54%]
36
+ aria-readonly:checked:border-grey-40
37
+ ).freeze
38
+
39
+ DESCRIPTION_CLASSES = %w(
40
+ text-secondary
41
+ text-sm
42
+ ).freeze
43
+
44
+ def initialize(radio:, attribute:, form_builder:)
45
+ @radio = radio
46
+ @attribute = attribute
47
+ @form_builder = form_builder
48
+
49
+ super
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :radio, :attribute, :form_builder
55
+
56
+ delegate :value, to: :radio
57
+
58
+ def aria_description
59
+ { describedby: description_id }
60
+ end
61
+
62
+ def description
63
+ if radio.object.is_a?(Hash)
64
+ radio.object[:description]
65
+ elsif radio.object.respond_to?(:description)
66
+ radio.object.description
67
+ elsif I18n.exists?(description_translation_scope)
68
+ human_attribute_description
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ def description_id
75
+ "#{attribute}-#{value}-description"
76
+ end
77
+
78
+ def description_span
79
+ tag.span description, id: description_id, class: DESCRIPTION_CLASSES
80
+ end
81
+
82
+ def description_text
83
+ human_attribute_description
84
+ end
85
+
86
+ def description_translation_scope
87
+ ["activerecord.values", i18n_model_key, description_id.gsub("-", ".")]
88
+ end
89
+
90
+ def human_attribute_description
91
+ I18n.t description_translation_scope.join(".")
92
+ end
93
+
94
+ def i18n_model_key
95
+ form_builder.object.class.model_name.i18n_key
96
+ end
97
+
98
+ def radio_options
99
+ {
100
+ aria: description.present? && aria_description,
101
+ data: { testid: "radio-#{attribute}-#{radio.value}".dasherize },
102
+ }
103
+ end
104
+ end
105
+ end
@@ -1,20 +1,19 @@
1
- <%= tag.div(
2
- class: "flex min-h-[100dvh] max-h-[100dvh] overflow-hidden",
1
+ <%= tag.div(**merge_options(wrapper_options, {
2
+ class: "flex min-h-[100dvh] max-h-[100dvh] overflow-hidden",
3
3
  data: {
4
4
  controller: "toggle",
5
5
  toggle_toggle_class: "side-nav-open",
6
6
  toggle_initial_label_value: t(".hide_nav"),
7
7
  toggle_intermediate_label_value: t(".show_nav"),
8
- },
9
- ) do %>
10
- <%= tag.header(
8
+ }
9
+ })) do %>
10
+ <%= tag.header(**merge_options(sidebar_options, {
11
11
  class: class_names(
12
12
  "flex flex-shrink-0 w-[15rem] relative",
13
13
  "[.side-nav-open_&]:w-[3.5rem] transition-[width] duration-500",
14
- classes
15
14
  ),
16
- data: { testid: "side-nav" }
17
- ) do %>
15
+ data: { testid: "side-nav" },
16
+ })) do %>
18
17
  <%= tag.div(
19
18
  class: class_names(
20
19
  "bg-teal text-white w-[15rem] flex flex-col divide-y divide-solid divide-white/10 duration-500",
@@ -58,7 +57,7 @@
58
57
  ),
59
58
  data: { toggle_target: "inertify" },
60
59
  ) do %>
61
- <%= render Anchor::ActionMenuComponent.new(classes: "py-4", menu_classes: "ml-4 w-[198px]") do |c| %>
60
+ <%= render Anchor::ActionMenuComponent.new(classes: "py-4", menu_options: { class: "ml-4 w-[198px]" }) do |c| %>
62
61
  <% c.with_show_button(
63
62
  classes: "text-lg text-white w-full hover:text-white hover:bg-white/[.05] px-6 justify-between rounded-none",
64
63
  full_width: true,
@@ -8,18 +8,21 @@ module Anchor
8
8
  apps_button_text:,
9
9
  nav_items:,
10
10
  employee:,
11
+ sidebar_options: {},
11
12
  **kwargs
12
13
  )
13
14
  @apps = apps
14
15
  @apps_button_text = apps_button_text
15
16
  @nav_items = nav_items
16
17
  @employee = employee
18
+ @sidebar_options = sidebar_options
17
19
 
18
20
  super
19
21
  end
20
22
 
21
23
  private
22
24
 
23
- attr_reader :apps, :apps_button_text, :employee, :nav_items
25
+ attr_reader :apps, :apps_button_text, :employee, :nav_items,
26
+ :sidebar_options
24
27
  end
25
28
  end
@@ -1,11 +1,13 @@
1
1
  <%= link_to(
2
2
  content,
3
3
  href,
4
- aria: {
5
- current: { page: active }
6
- },
7
- class: class_names(
8
- "inline-block px-5 py-4 text-sm font-semibold relative text-gray-600 hover:text-gray-900",
9
- "text-gray-900 after:block after:bg-blue-500 after:h-1 after:absolute after:-bottom-1 after:inset-x-0" => active,
4
+ **merge_options(wrapper_options,
5
+ aria: {
6
+ current: { page: active }
7
+ },
8
+ class: class_names(
9
+ "inline-block px-5 py-4 text-sm font-semibold relative text-gray-600 hover:text-gray-900",
10
+ "text-gray-900 after:block after:bg-blue-500 after:h-1 after:absolute after:-bottom-1 after:inset-x-0" => active,
11
+ )
10
12
  )
11
13
  ) %>
@@ -1,11 +1,11 @@
1
1
  module Anchor
2
2
  module TabBar
3
3
  class TabComponent < Component
4
- def initialize(href:, active: false)
4
+ def initialize(href:, active: false, **kwargs)
5
5
  @href = href
6
6
  @active = active
7
7
 
8
- super
8
+ super(**kwargs)
9
9
  end
10
10
 
11
11
  private
@@ -1,7 +1,6 @@
1
- <%= tag.nav(
2
- aria: { label: label },
3
- class: class_names("shadow-[inset_0_-1px] shadow-gray-400 pb-1", classes),
4
- ) do %>
1
+ <%= tag.nav(**merge_options(wrapper_options,
2
+ class: "shadow-[inset_0_-1px] shadow-gray-400 pb-1",
3
+ )) do %>
5
4
  <ul class="flex">
6
5
  <% tabs.each do |tab| %>
7
6
  <%= tag.li tab %>
@@ -3,9 +3,10 @@ module Anchor
3
3
  renders_many :tabs, TabBar::TabComponent
4
4
 
5
5
  def initialize(label:, **kwargs)
6
- @label = label
6
+ aria = kwargs.delete(:aria) || {}
7
+ aria[:label] = label
7
8
 
8
- super
9
+ super(**kwargs, aria:)
9
10
  end
10
11
 
11
12
  private
@@ -23,7 +23,7 @@ module Anchor
23
23
  @caption = caption
24
24
  @paginate = paginate
25
25
 
26
- super
26
+ super()
27
27
  end
28
28
 
29
29
  private
@@ -1,8 +1,3 @@
1
- <%= content_tag(
2
- tag,
3
- class: class_names(variant, classes),
4
- id: id,
5
- data: data
6
- ) do %>
1
+ <%= content_tag(tag, **wrapper_options) do %>
7
2
  <%= content %>
8
3
  <% end %>
@@ -3,7 +3,7 @@ module Anchor
3
3
  TAG_DEFAULT = :p
4
4
 
5
5
  VARIANT_DEFAULT = :body_base
6
- VARIANT_MAPPING = {
6
+ VARIANT_MAPPINGS = {
7
7
  body_lg: "text-lg",
8
8
  body_base: "text-base",
9
9
  body_sm: "text-sm",
@@ -16,25 +16,16 @@ module Anchor
16
16
  subheading_sm: "text-sm font-bold leading-4 uppercase",
17
17
  subheading_xs: "text-xs font-bold leading-4 uppercase",
18
18
  }.freeze
19
- VARIANT_OPTIONS = VARIANT_MAPPING.keys
19
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys
20
20
 
21
- def initialize(
22
- variant: VARIANT_DEFAULT,
23
- tag: TAG_DEFAULT,
24
- **kwargs
25
- )
26
- @variant = VARIANT_MAPPING[
27
- fetch_or_fallback(VARIANT_OPTIONS, variant, VARIANT_DEFAULT)
28
- ]
21
+ def initialize(tag: TAG_DEFAULT, **kwargs)
29
22
  @tag = tag
30
- @id = id
31
-
32
- super
23
+ super(**kwargs)
33
24
  end
34
25
 
35
26
  private
36
27
 
37
- attr_reader :variant, :tag, :id
28
+ attr_reader :tag
38
29
 
39
30
  def render?
40
31
  content.present?
@@ -1 +1,19 @@
1
- <%= content %>
1
+ <% if starting_icon? || ending_icon? %>
2
+ <%= tag.div class: "relative" do %>
3
+ <%= content %>
4
+ <% if starting_icon? %>
5
+ <%= anchor_icon(
6
+ icon: starting_icon,
7
+ classes: class_names(ICON_CLASSES, "left-4")
8
+ ) %>
9
+ <% end %>
10
+ <% if ending_icon? %>
11
+ <%= anchor_icon(
12
+ icon: ending_icon,
13
+ classes: class_names(ICON_CLASSES, "right-4")
14
+ ) %>
15
+ <% end %>
16
+ <% end %>
17
+ <% else %>
18
+ <%= content %>
19
+ <% end %>
@@ -1,30 +1,49 @@
1
1
  module Anchor
2
2
  class TextFieldComponent < Component
3
- INPUT_CLASSES = %w(
4
- block
5
- border
6
- form-input
7
- min-h-[40px]
8
- mt-1
9
- resize-none
10
- rounded
11
- text-base
12
- w-full
13
- [&[readonly]]:bg-grey-10
14
- [&[readonly]]:border-grey-40
15
- [&[readonly]]:shadow-none
3
+ ICON_CLASSES = %w(
4
+ absolute
5
+ h-5
6
+ w-5
7
+ top-1/2
8
+ -translate-y-1/2
9
+ text-secondary
10
+ pointer-events-none
16
11
  ).freeze
17
12
 
18
- def initialize(form_builder:, attribute:, **options)
13
+ def initialize(
14
+ form_builder:,
15
+ attribute:,
16
+ starting_icon: nil,
17
+ ending_icon: nil
18
+ )
19
19
  @form_builder = form_builder
20
20
  @attribute = attribute
21
- @options = options
21
+ @starting_icon = starting_icon
22
+ @ending_icon = ending_icon
22
23
 
23
24
  super
24
25
  end
25
26
 
27
+ attr_reader :starting_icon, :ending_icon
28
+
26
29
  def options
27
- { class: INPUT_CLASSES }
30
+ {
31
+ class: class_names(
32
+ TailwindConstants::INPUT,
33
+ "pl-11" => starting_icon?,
34
+ "pr-11" => ending_icon?
35
+ ),
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def starting_icon?
42
+ starting_icon.present?
43
+ end
44
+
45
+ def ending_icon?
46
+ ending_icon.present?
28
47
  end
29
48
  end
30
49
  end
@@ -1,12 +1,9 @@
1
1
  <%= tag.div(
2
- class: class_names(
3
- "toast",
4
- variant,
5
- ),
6
- data: { controller: "toast" },
7
- id: id,
8
- popover: "manual",
9
- role:,
2
+ **merge_options(wrapper_options, {
3
+ class: "toast",
4
+ data: { controller: "toast", testid: test_id },
5
+ popover: "manual",
6
+ })
10
7
  ) do %>
11
8
  <% if icon? %>
12
9
  <%= icon %>