ariadne_view_components 0.0.12-x64-mingw-ucrt → 0.0.14-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ariadne.d.ts +1 -0
  3. data/app/assets/javascripts/ariadne_view_components.js +2 -2
  4. data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
  5. data/app/assets/javascripts/comment-component.d.ts +1 -2
  6. data/app/assets/javascripts/tab-container-component.d.ts +1 -0
  7. data/app/assets/javascripts/tab-nav-component.d.ts +9 -0
  8. data/app/assets/stylesheets/ariadne_view_components.css +1 -0
  9. data/app/assets/stylesheets/dropdown.css +46 -0
  10. data/app/assets/stylesheets/tooltip-component.css +8 -8
  11. data/app/components/ariadne/ariadne.ts +3 -2
  12. data/app/components/ariadne/avatar_component.rb +81 -0
  13. data/app/components/ariadne/avatar_stack_component.html.erb +12 -0
  14. data/app/components/ariadne/avatar_stack_component.rb +75 -0
  15. data/app/components/ariadne/base_button.rb +13 -4
  16. data/app/components/ariadne/blankslate_component.rb +4 -4
  17. data/app/components/ariadne/body_component.rb +1 -1
  18. data/app/components/ariadne/button_component.html.erb +1 -1
  19. data/app/components/ariadne/button_component.rb +11 -5
  20. data/app/components/ariadne/comment-component.ts +32 -50
  21. data/app/components/ariadne/comment_component.html.erb +31 -13
  22. data/app/components/ariadne/comment_component.rb +18 -6
  23. data/app/components/ariadne/component.rb +3 -5
  24. data/app/components/ariadne/container_component.rb +1 -1
  25. data/app/components/ariadne/counter_component.rb +1 -1
  26. data/app/components/ariadne/details_component.html.erb +4 -0
  27. data/app/components/ariadne/details_component.rb +80 -0
  28. data/app/components/ariadne/dropdown/menu_component.html.erb +20 -0
  29. data/app/components/ariadne/dropdown/menu_component.rb +101 -0
  30. data/app/components/ariadne/dropdown/menu_component.ts +1 -0
  31. data/app/components/ariadne/dropdown_component.html.erb +8 -0
  32. data/app/components/ariadne/dropdown_component.rb +172 -0
  33. data/app/components/ariadne/flash_component.rb +1 -1
  34. data/app/components/ariadne/flex_component.rb +2 -2
  35. data/app/components/ariadne/footer_component.html.erb +1 -1
  36. data/app/components/ariadne/footer_component.rb +1 -1
  37. data/app/components/ariadne/grid_component.html.erb +2 -2
  38. data/app/components/ariadne/grid_component.rb +10 -8
  39. data/app/components/ariadne/header_component.rb +3 -3
  40. data/app/components/ariadne/heroicon_component.html.erb +1 -1
  41. data/app/components/ariadne/heroicon_component.rb +7 -6
  42. data/app/components/ariadne/inline_flex_component.html.erb +1 -0
  43. data/app/components/ariadne/inline_flex_component.rb +8 -1
  44. data/app/components/ariadne/link_component.rb +2 -2
  45. data/app/components/ariadne/list_component.html.erb +2 -11
  46. data/app/components/ariadne/list_component.rb +11 -15
  47. data/app/components/ariadne/main_component.rb +2 -2
  48. data/app/components/ariadne/narrow_container_component.rb +1 -1
  49. data/app/components/ariadne/panel_bar_component.rb +2 -2
  50. data/app/components/ariadne/pill_component.rb +20 -6
  51. data/app/components/ariadne/rich-text-area-component.ts +4 -4
  52. data/app/components/ariadne/rich_text_area_component.html.erb +2 -2
  53. data/app/components/ariadne/rich_text_area_component.rb +2 -2
  54. data/app/components/ariadne/slideover_component.html.erb +1 -1
  55. data/app/components/ariadne/slideover_component.rb +2 -2
  56. data/app/components/ariadne/tab-container-component.ts +24 -0
  57. data/app/components/ariadne/tab-nav-component.ts +34 -0
  58. data/app/components/ariadne/tab_component.html.erb +2 -6
  59. data/app/components/ariadne/tab_component.rb +75 -19
  60. data/app/components/ariadne/tab_container_component.erb +12 -0
  61. data/app/components/ariadne/tab_container_component.rb +67 -0
  62. data/app/components/ariadne/tab_nav_component.html.erb +7 -0
  63. data/app/components/ariadne/tab_nav_component.rb +72 -0
  64. data/app/components/ariadne/table_nav_component.html.erb +52 -0
  65. data/app/components/ariadne/table_nav_component.rb +338 -0
  66. data/app/components/ariadne/time_ago_component.rb +1 -1
  67. data/app/components/ariadne/timeline_component.rb +1 -1
  68. data/app/components/ariadne/tooltip_component.html.erb +1 -1
  69. data/app/components/ariadne/tooltip_component.rb +4 -4
  70. data/app/lib/ariadne/action_view_extensions/form_helper.rb +21 -7
  71. data/app/lib/ariadne/fetch_or_fallback_helper.rb +11 -3
  72. data/app/lib/ariadne/form_builder.rb +2 -2
  73. data/app/lib/ariadne/icon_helper.rb +17 -13
  74. data/lib/ariadne/view_components/constants.rb +2 -2
  75. data/lib/ariadne/view_components/statuses.rb +2 -2
  76. data/lib/ariadne/view_components/version.rb +1 -1
  77. data/lib/rubocop/config/default.yml +1 -1
  78. data/lib/tasks/docs.rake +22 -97
  79. data/static/arguments.yml +151 -23
  80. data/static/audited_at.json +17 -1
  81. data/static/classes.yml +160 -268
  82. data/static/constants.json +208 -40
  83. data/static/statuses.json +17 -1
  84. data/tailwind.config.js +28 -19
  85. metadata +24 -10
  86. data/app/components/ariadne/tab_bar_component.html.erb +0 -3
  87. data/app/components/ariadne/tab_bar_component.rb +0 -45
  88. data/app/lib/ariadne/join_style_arguments_helper.rb +0 -14
  89. data/app/lib/ariadne/tab_nav_helper.rb +0 -35
  90. data/app/lib/ariadne/tabbed_component_helper.rb +0 -39
  91. data/app/lib/ariadne/test_selector_helper.rb +0 -20
  92. data/app/lib/ariadne/underline_nav_helper.rb +0 -44
@@ -11,13 +11,15 @@ module Ariadne
11
11
  include IconHelper
12
12
  include HeroiconsHelper
13
13
 
14
- SIZE_DEFAULT = :sm
15
- SIZE_MEDIUM = :md
14
+ SIZE_MICRO = :mu
15
+ SIZE_SMALL = :sm
16
+ SIZE_DEFAULT = :md
16
17
  SIZE_LARGE = :lg
17
18
 
18
19
  SIZE_MAPPINGS = {
19
- SIZE_DEFAULT => 16,
20
- SIZE_MEDIUM => 24,
20
+ SIZE_MICRO => 16,
21
+ SIZE_SMALL => 20,
22
+ SIZE_DEFAULT => 24,
21
23
  SIZE_LARGE => 128,
22
24
  }.freeze
23
25
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
@@ -107,10 +109,9 @@ module Ariadne
107
109
  }
108
110
 
109
111
  @icon = heroicon(icon, variant: variant, **attributes)
110
-
111
112
  @classes = class_names(
112
113
  @icon.attributes[:class],
113
- classes
114
+ classes,
114
115
  )
115
116
  @attributes.merge!(@icon.attributes.except(:class, :"aria-hidden"))
116
117
 
@@ -2,4 +2,5 @@
2
2
  <%= icon %>
3
3
  <%= item %>
4
4
  <%= text %>
5
+ <%= dropdown %>
5
6
  <% end %>
@@ -19,7 +19,7 @@ module Ariadne
19
19
  </svg>
20
20
  MSG
21
21
  STATE_CLOSED_SVG = <<~MSG
22
- <svg viewBox="0 0 24 24" width="12" height="12" class="ariadne-stroke-state-closed fill-state-closed " stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
22
+ <svg viewBox="0 0 24 24" width="12" height="12" class="ariadne-stroke-state-closed ariadne-fill-state-closed " stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
23
23
  <circle cx="12" cy="12" r="10"></circle>
24
24
  </svg>
25
25
  MSG
@@ -42,12 +42,19 @@ module Ariadne
42
42
  Ariadne::BaseComponent.new(tag: :span, classes: actual_classes, attributes: attributes) { content }
43
43
  }
44
44
 
45
+ renders_one :dropdown, "Ariadne::DropdownComponent"
46
+
45
47
  # @example Default
46
48
  #
47
49
  # <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
48
50
  # <% c.item { Ariadne::InlineFlexComponent::STATE_OPEN_SVG.html_safe } %>
49
51
  # <% end %>
50
52
  #
53
+ # # TODO: STATE_CLOSED_SVG colors didn't show until it was listed in an example
54
+ # <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
55
+ # <% c.item { Ariadne::InlineFlexComponent::STATE_CLOSED_SVG.html_safe } %>
56
+ # <% end %>
57
+ #
51
58
  # <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
52
59
  # <% c.icon(icon: :check, size: :sm, variant: HeroiconsHelper::Icon::VARIANT_SOLID) %>
53
60
  # <% c.text { "Closed" } %>
@@ -6,8 +6,8 @@ module Ariadne
6
6
  DEFAULT_TAG = :a
7
7
  TAG_OPTIONS = [DEFAULT_TAG, :span].freeze
8
8
 
9
- DEFAULT_CLASSES = "ariadne-cursor-pointer"
10
- DEFAULT_ACTIONABLE_CLASSES = " ariadne-cursor-pointer ariadne-underline ariadne-decoration-double ariadne-font-semibold hover:ariadne-text-button-text-color focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500"
9
+ DEFAULT_CLASSES = "ariadne-cursor-pointer ariadne-font-semibold hover:ariadne-text-button-text-color focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500"
10
+ DEFAULT_ACTIONABLE_CLASSES = "ariadne-underline ariadne-decoration-double"
11
11
 
12
12
  # `Tooltip` that appears on mouse hover or keyboard focus over the button. Use tooltips sparingly and as a last resort.
13
13
  # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
@@ -1,15 +1,6 @@
1
1
  <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |list| %>
2
+ <%= content %>
2
3
  <% items.each do |item| %>
3
- <% if item.linked? %>
4
- <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
5
- <%= render Ariadne::LinkComponent.new(href: item.link[:href], classes: item.link[:classes], attributes: item.link[:attributes]) do %>
6
- <%= item.entry %>
7
- <% end %>
8
- <% end %>
9
- <% else %>
10
- <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
11
- <%= item.entry %>
12
- <% end %>
13
- <% end %>
4
+ <%= item %>
14
5
  <% end %>
15
6
  <% end %>
@@ -13,7 +13,7 @@ module Ariadne
13
13
  # <%= render(Ariadne::ListComponent.new) do |list| %>
14
14
  # <% numbers.each do |number| %>
15
15
  # <%= list.item do |item| %>
16
- # <%= item.entry { number } %>
16
+ # <%= number %>
17
17
  # <% end %>
18
18
  # <% end %>
19
19
  # <% end %>
@@ -27,23 +27,11 @@ module Ariadne
27
27
  @attributes = attributes
28
28
  end
29
29
 
30
- def render?
31
- items.any?
32
- end
33
-
34
30
  # This component is part of `ListComponent` and should not be
35
31
  # used as a standalone component.
36
32
  class ListItem < Ariadne::Component
37
33
  DEFAULT_ITEM_CLASSES = "ariadne-relative ariadne-p-1.5 focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500 hover:ariadne-bg-button-hover-color"
38
34
 
39
- renders_one :entry, lambda { |static_content = nil, &block|
40
- next static_content if static_content.present?
41
-
42
- render(Ariadne::BaseComponent.new(tag: :div)) do
43
- view_context.capture { block&.call }
44
- end
45
- }
46
-
47
35
  attr_reader :link, :classes, :attributes
48
36
 
49
37
  def initialize(link: {}, classes: "", attributes: {})
@@ -56,12 +44,20 @@ module Ariadne
56
44
  @selected
57
45
  end
58
46
 
59
- def linked?
47
+ private def linked?
60
48
  @link.present?
61
49
  end
62
50
 
63
51
  def call
64
- Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes)
52
+ render(Ariadne::BaseComponent.new(tag: :li, classes: @classes, attributes: @attributes)) do
53
+ if linked?
54
+ render(Ariadne::LinkComponent.new(href: @link[:href], classes: @link[:classes], attributes: @link[:attributes])) do
55
+ content
56
+ end
57
+ else
58
+ content
59
+ end
60
+ end
65
61
  end
66
62
  end
67
63
  end
@@ -5,7 +5,7 @@ module Ariadne
5
5
  # Add additional usage considerations or best practices that may aid the user to use the component correctly.
6
6
  # @accessibility Add any accessibility considerations
7
7
  class MainComponent < Ariadne::Component
8
- DEFAULT_CLASSES = "flex-auto"
8
+ DEFAULT_CLASSES = "ariadne-flex-auto"
9
9
 
10
10
  # @example Default
11
11
  #
@@ -17,7 +17,7 @@ module Ariadne
17
17
  @tag = :main
18
18
  @classes = class_names(
19
19
  DEFAULT_CLASSES,
20
- classes
20
+ classes,
21
21
  )
22
22
 
23
23
  @attributes = attributes
@@ -21,7 +21,7 @@ module Ariadne
21
21
  @tag = :div
22
22
  @classes = class_names(
23
23
  DEFAULT_CLASSES,
24
- classes
24
+ classes,
25
25
  )
26
26
 
27
27
  @attributes = attributes
@@ -23,7 +23,7 @@ module Ariadne
23
23
  @tag = DEFAULT_TAG
24
24
  @classes = class_names(
25
25
  DEFAULT_CLASSES,
26
- classes
26
+ classes,
27
27
  )
28
28
 
29
29
  @attributes = attributes
@@ -72,7 +72,7 @@ module Ariadne
72
72
  end
73
73
 
74
74
  def call
75
- Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes)
75
+ render(Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes))
76
76
  end
77
77
  end
78
78
  end
@@ -6,25 +6,39 @@ module Ariadne
6
6
  DEFAULT_TAG = :span
7
7
  TAG_OPTIONS = [DEFAULT_TAG].freeze
8
8
 
9
- DEFAULT_CLASSES = "ariadne-flex-shrink-0 ariadne-inline-block ariadne-px-2 ariadne-py-0.5 ariadne-text-xs ariadne-font-medium ariadne-rounded-full"
9
+ DEFAULT_CLASSES = "ariadne-flex-shrink-0 ariadne-inline-block ariadne-px-2 ariadne-py-0.5 ariadne-text-xs ariadne-font-medium ariadne-rounded-full ariadne-whitespace-nowrap"
10
10
 
11
11
  # @example Default
12
12
  #
13
- # <%= render(Ariadne::PillComponent.new(color: "FF0000")) { "Admin" } %>
13
+ # <%= render(Ariadne::PillComponent.new(color: [49, 186, 115, 1.0])) { "Admin" } %>
14
14
  #
15
15
  # @param tag [Symbol, String] The rendered tag name.
16
- # @param color [String] The hex color of the pill.
16
+ # @param color [String] The rgba color of the pill.
17
17
  # @param classes [String] <%= link_to_classes_docs %>
18
18
  # @param attributes [Hash] <%= link_to_attributes_docs %>
19
19
  def initialize(tag: DEFAULT_TAG, color:, classes: "", attributes: {})
20
20
  @tag = check_incoming_tag(DEFAULT_TAG, tag)
21
+
22
+ @red = color[0]
23
+ @green = color[1]
24
+ @blue = color[2]
25
+ @alpha = color[3]
26
+
27
+ @attributes = attributes
28
+ @attributes["style"] = "background-color: rgba(#{@red}, #{@green}, #{@blue}, #{@alpha});"
29
+ @text_color = contrast_of(@red, @green, @blue)
30
+
21
31
  @classes = class_names(
22
32
  DEFAULT_CLASSES,
23
- classes
33
+ classes,
34
+ @text_color,
24
35
  )
36
+ end
25
37
 
26
- @attributes = attributes
27
- @attributes["style"] = "background-color: ##{color};"
38
+ private def contrast_of(red, green, blue)
39
+ luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255
40
+
41
+ luminance > 0.5 ? "ariadne-text-black" : "ariadne-text-white"
28
42
  end
29
43
  end
30
44
  end
@@ -5,16 +5,16 @@ import {StarterKit} from '@tiptap/starter-kit'
5
5
 
6
6
  export default class RichTextArea extends Controller {
7
7
  connect() {
8
- const editorElement = document.querySelector('.tiptap-editor')
9
- if (editorElement) {
8
+ const editorElements = document.getElementsByClassName('tiptap-editor')
9
+ for (const editorElement of editorElements) {
10
10
  new Editor({
11
11
  element: editorElement,
12
12
  extensions: [StarterKit],
13
- content: 'Hello World!',
13
+ content: '',
14
14
  editorProps: {
15
15
  attributes: {
16
16
  class:
17
- 'ariadne-prose ariadne-prose-sm sm:ariadne-prose lg:ariadne-prose-lg xl:ariadne-prose-2xl ariadne-m-5 focus:ariadne-outline-none'
17
+ 'ariadne-m-5 focus:ariadne-outline-none ariadne-block ariadne-w-full ariadne-resize-none ariadne-p-0 ariadne-pb-2 ariadne-border-none focus:ariadne-ring-0 sm:ariadne-text-sm'
18
18
  }
19
19
  }
20
20
  })
@@ -1,6 +1,6 @@
1
1
  <%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
2
2
  <% if @has_form %>
3
- <input type="hidden" data-tiptap-value-container=true name="<%= @name %>" id="<%= @name %>" />
3
+ <input type="hidden" data-tiptap-value-container=true name="<%= @name %>" id="<%= @name %>">
4
4
  <% end %>
5
- <textarea class="tiptap-editor"></textarea>
5
+ <div class="tiptap-editor"></div>
6
6
  <% end %>
@@ -6,7 +6,7 @@ module Ariadne
6
6
  # @accessibility Add any accessibility considerations
7
7
  class RichTextAreaComponent < Ariadne::Component
8
8
  DEFAULT_TAG = :div
9
- DEFAULT_CLASSES = "ariadne-block ariadne-w-full ariadne-border-0 ariadne-py-3 focus:ariadne-ring-0 sm:ariadne-text-sm"
9
+ DEFAULT_CLASSES = "ariadne-block"
10
10
 
11
11
  # @example Default
12
12
  #
@@ -25,7 +25,7 @@ module Ariadne
25
25
 
26
26
  @classes = class_names(
27
27
  DEFAULT_CLASSES,
28
- classes
28
+ classes,
29
29
  )
30
30
  @attributes = attributes
31
31
  @attributes[:"data-controller"] = "rich-text-area-component"
@@ -1,6 +1,6 @@
1
1
  <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |slideover| %>
2
2
  <div data-slideover-component-target="expandWrapper" class="ariadne-flex ariadne-flex-col ariadne-z-10">
3
- <div data-slideover-component-target="expandable" class="ariadne-hidden bg-filter-panel ariadne-px-3 ariadne-pb-4">
3
+ <div data-slideover-component-target="expandable" class="ariadne-hidden ariadne-px-3 ariadne-pb-4">
4
4
  <%= content %>
5
5
  </div>
6
6
  <div data-slideover-component-target="buttonWrapper" data-slideover-component-form-id="<%= @form_id %>" class="ariadne-relative ariadne-flex ariadne-justify-center">
@@ -57,7 +57,7 @@ module Ariadne
57
57
  #
58
58
  # @param tag [Symbol, String] The rendered tag name.
59
59
  # @param direction [Symbol] <%= one_of(Ariadne::SlideoverComponent::VALID_DIRECTIONS) %>
60
- # @param form_id [String] The ID of any <form> tag to submit when the button is clicked.
60
+ # @param form_id [String] The ID of any `form` tag to submit when the button is clicked.
61
61
  # @param open_text [String] The text to use to open the slideover.
62
62
  # @param close_text [String] The text to use to close the slideover.
63
63
  # @param classes [String] <%= link_to_classes_docs %>
@@ -66,7 +66,7 @@ module Ariadne
66
66
  @tag = check_incoming_tag(DEFAULT_TAG, tag)
67
67
  @classes = class_names(
68
68
  DEFAULT_CLASSES,
69
- classes
69
+ classes,
70
70
  )
71
71
 
72
72
  @direction = fetch_or_raise(VALID_DIRECTIONS, direction)
@@ -0,0 +1,24 @@
1
+ import '@github/tab-container-element'
2
+
3
+ // keep in sync with tab_container_component.rb
4
+ const DEFAULT_SELECTED_CLASSES: string[] = ['ariadne-border-indigo-500', 'ariadne-text-indigo-600']
5
+ const DEFAULT_UNSELECTED_CLASSES: string[] = [
6
+ 'ariadne-text-gray-500',
7
+ 'hover:ariadne-text-gray-700',
8
+ 'hover:ariadne-border-gray-300'
9
+ ]
10
+
11
+ for (const tabContainer of document.getElementsByTagName('tab-container')) {
12
+ tabContainer.addEventListener('tab-container-change', function (event: Event) {
13
+ const newPanel = (event as CustomEvent).detail.relatedTarget as HTMLElement
14
+ const tabContainer = newPanel.closest('tab-container') as HTMLElement
15
+ const tabList = tabContainer.firstElementChild as HTMLElement
16
+ const currentTab = tabList.querySelector('[aria-selected="true"]') as HTMLElement
17
+ const tabId = newPanel.getAttribute('id')?.split('-').slice(1).join('-')
18
+ const newTab = tabList.querySelector(`#${tabId}`) as HTMLElement
19
+ currentTab.classList.remove(...DEFAULT_SELECTED_CLASSES)
20
+ currentTab.classList.add(...DEFAULT_UNSELECTED_CLASSES)
21
+ newTab.classList.add(...DEFAULT_SELECTED_CLASSES)
22
+ newTab.classList.remove(...DEFAULT_UNSELECTED_CLASSES)
23
+ })
24
+ }
@@ -0,0 +1,34 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+
3
+ export default class TabNavComponent extends Controller {
4
+ static targets = ['tab']
5
+
6
+ declare readonly tabTargets: [HTMLAnchorElement]
7
+
8
+ // keep in synch with tab_nav_component
9
+ SELECTED_CLASSES = ['ariadne-border-indigo-500', 'ariadne-text-indigo-600']
10
+ UNSELECTED_CLASSES = ['ariadne-text-gray-500', 'hover:ariadne-text-gray-700', 'hover:ariadne-border-gray-300']
11
+
12
+ connect() {
13
+ for (const tab of this.tabTargets) {
14
+ if (tab.hasAttribute('aria-current')) {
15
+ tab.classList.add(...this.SELECTED_CLASSES)
16
+ tab.classList.remove(...this.UNSELECTED_CLASSES)
17
+ }
18
+ }
19
+ }
20
+
21
+ toggle(event: Event) {
22
+ for (const tab of this.tabTargets) {
23
+ if (tab === event.target) {
24
+ tab.setAttribute('aria-current', 'page')
25
+ tab.classList.add(...this.SELECTED_CLASSES)
26
+ tab.classList.remove(...this.UNSELECTED_CLASSES)
27
+ } else if (tab.hasAttribute('aria-current')) {
28
+ tab.removeAttribute('aria-current')
29
+ tab.classList.add(...this.UNSELECTED_CLASSES)
30
+ tab.classList.remove(...this.SELECTED_CLASSES)
31
+ }
32
+ }
33
+ }
34
+ }
@@ -1,7 +1,3 @@
1
- <%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do |component| %>
2
- <% if text.present? %>
3
- <%= text %>
4
- <% else %>
5
- <%= content %>
6
- <% end %>
1
+ <%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
2
+ <%= text || content %>
7
3
  <% end %>
@@ -1,43 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ariadne
4
- # Represents a sequence of tabs which, when clicked, swap between panel contents.
4
+ # This component is part of navigation components such as `Ariadne::TabContainerComponent`.
5
+ # and `Ariadne::TabNavComponent` and should not be used by itself.
6
+ #
7
+ # @accessibility
8
+ # `TabComponent` renders the selected anchor tab with `aria-current="page"` by default.
9
+ # When the selected tab does not correspond to the current page, such as in a nested inner tab, make sure to use aria-current="true"
5
10
  class TabComponent < Ariadne::Component
6
- DEFAULT_TAG = :button
7
- TAG_OPTIONS = [DEFAULT_TAG].freeze
11
+ DEFAULT_ARIA_CURRENT_FOR_ANCHOR = :page
12
+ ARIA_CURRENT_OPTIONS_FOR_ANCHOR = [true, DEFAULT_ARIA_CURRENT_FOR_ANCHOR].freeze
8
13
 
9
- # The Tab's text.
14
+ # Panel controlled by the Tab. This will not render anything in the tab itself.
15
+ # It will provide a accessor for the Tab's parent to call and render the panel
16
+ # content in the appropriate place.
17
+ #
18
+ # @param classes [String] <%= link_to_classes_docs %>
19
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
20
+ renders_one :panel, lambda { |classes: "", attributes: {}|
21
+ attributes[:id] = @panel_id
22
+ tag = :div
23
+ attributes[:role] ||= :tabpanel
24
+ attributes[:tabindex] = 0
25
+ attributes[:hidden] = true unless @selected
26
+
27
+ label_present = aria("label", attributes) || aria("labelledby", attributes)
28
+ unless label_present
29
+ if @id.present?
30
+ attributes[:"aria-labelledby"] = @id
31
+ else
32
+ raise ArgumentError, "Panels must be labelled. Either set a unique `id` on the tab, or set an `aria-label` directly on the panel"
33
+ end
34
+ end
35
+
36
+ Ariadne::BaseComponent.new(tag: tag, classes: classes, attributes: attributes)
37
+ }
38
+
39
+ # The tab's text.
10
40
  #
11
41
  # @param kwargs [Hash] The same arguments as <%= link_to_component(Ariadne::Text) %>.
12
42
  renders_one :text, Ariadne::Text
43
+ # TODO: test what hapenns with really long inbox names
13
44
 
14
- attr_reader :selected
15
-
16
- DEFAULT_CLASSES = "ariadne-border-gray-300 ariadne-border ariadne-shadow ariadne-py-5 ariadne-px-5 ariadne-rounded-md"
45
+ attr_reader :selected, :id, :classes, :attributes
17
46
 
18
- BASE_TAB_CLASSES = "ariadne-whitespace-nowrap ariadne-py-4 ariadne-px-1 ariadne-border-b-2 ariadne-font-medium ariadne-text-sm"
47
+ DEFAULT_CLASSES = "ariadne-border-transparent ariadne-text-gray-500 hover:ariadne-text-gray-700 hover:ariadne-border-gray-300 ariadne-whitespace-nowrap ariadne-py-4 ariadne-px-1 ariadne-border-b-2 ariadne-font-medium ariadne-text-sm"
19
48
 
20
49
  # @example Default
21
50
  #
22
- # <%= render(Ariadne::TabComponent.new) { "Example" } %>
23
- #
24
- # @param tag [Symbol, String] The rendered tag name.
51
+ # <%= render(Ariadne::TabComponent.new(id: "pub-comment")) do |tab| %>
52
+ # <% tab.text { "Public comment" } %>
53
+ # <% tab.panel { "Public comment panel" } %>
54
+ # <% end %>
55
+ # @param id [String] The unique ID of the tab.
25
56
  # @param selected [Boolean] Whether the tab is selected or not.
57
+ # @param with_panel [Boolean] Whether the Tab has an associated panel.
58
+ # @param href [String] The link to navigate to when the tab is clicked.
26
59
  # @param classes [String] <%= link_to_classes_docs %>
27
60
  # @param attributes [Hash] <%= link_to_attributes_docs %>
28
- def initialize(tag: DEFAULT_TAG, selected: false, classes: "", attributes: {})
29
- @tag = check_incoming_tag(DEFAULT_TAG, tag)
61
+ def initialize(id: nil, selected: false, with_panel: false, href: nil, classes: "", attributes: {})
62
+ @id = id
63
+
64
+ @href = href
65
+ @selected = selected
66
+
30
67
  @classes = class_names(
31
68
  DEFAULT_CLASSES,
32
- classes
69
+ classes,
33
70
  )
34
71
 
35
72
  @attributes = attributes
36
- @attributes[:id] ||= "tabs-tab-#{@tab_idx}"
37
- @attributes[:"aria-controls"] = "tabs-panel-#{@tab_idx}"
38
- @attributes[:"aria-current"] = "page" if selected
39
- @attributes[:selected] = selected
40
- @classes = class_names(BASE_TAB_CLASSES, classes)
73
+ @attributes[:id] ||= @id
74
+
75
+ if with_panel # TODO: test
76
+ if @id.blank?
77
+ raise ArgumentError, "Tabs with panels must have a unique `id`"
78
+ end
79
+
80
+ @tag = :button
81
+ @attributes[:type] = :button
82
+ @attributes[:role] = :tab
83
+ @panel_id = "panel-#{@id}"
84
+ @attributes[:"aria-controls"] = @panel_id
85
+ else
86
+ @tag = :a
87
+ end
88
+
89
+ return unless @selected
90
+
91
+ if @tag == :a
92
+ aria_current = aria("current", attributes) || DEFAULT_ARIA_CURRENT_FOR_ANCHOR
93
+ @attributes[:"aria-current"] = fetch_or_raise(ARIA_CURRENT_OPTIONS_FOR_ANCHOR, aria_current)
94
+ else
95
+ @attributes[:"aria-selected"] = true
96
+ end
41
97
  end
42
98
  end
43
99
  end
@@ -0,0 +1,12 @@
1
+ <%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do |component| %>
2
+ <div role="tablist">
3
+ <% tabs.each do |tab| %>
4
+ <%= tab %>
5
+ <% end %>
6
+ </div>
7
+ <% tabs.each do |tab| %>
8
+ <div class="ariadne-pt-2">
9
+ <%= tab.panel %>
10
+ </div>
11
+ <% end %>
12
+ <% end %>
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # A container for a row of tags. Keyboard support is provided.
5
+ # @accessibility This component requires you to pass in a `sr_label`
6
+ # attribute, which will be used to label the tabs for screen readers.
7
+ class TabContainerComponent < Ariadne::Component
8
+ DEFAULT_TAG = :"tab-container"
9
+
10
+ DEFAULT_CLASSES = ""
11
+
12
+ DEFAULT_SELECTED_CLASSES = "ariadne-border-indigo-500 ariadne-text-indigo-600"
13
+ DEFAULT_UNSELECTED_CLASSES = "ariadne-text-gray-500 hover:ariadne-text-gray-700 hover:ariadne-border-gray-300"
14
+
15
+ # Tabs and panels to be rendered.
16
+ #
17
+ # @param id [String] The unique ID of the tab.
18
+ # @param selected [Boolean] Whether the tab is selected.
19
+ # @param classes [String] <%= link_to_classes_docs %>
20
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
21
+ renders_many :tabs, lambda { |id: "", selected: false, classes: "", attributes: {}|
22
+ actual_classes = class_names(selected ? DEFAULT_SELECTED_CLASSES : DEFAULT_UNSELECTED_CLASSES, classes)
23
+ Ariadne::TabComponent.new(
24
+ id: id,
25
+ selected: selected,
26
+ with_panel: true,
27
+
28
+ classes: actual_classes,
29
+ attributes: attributes,
30
+ )
31
+ }
32
+
33
+ # @example Default
34
+ #
35
+ # <%= render(Ariadne::TabContainerComponent.new(sr_label: "Comments")) do |tab_container| %>
36
+ # <%= render(Ariadne::TabComponent.new(id: "pub-comment")) do |tab| %>
37
+ # <% tab.text { "Tab 1" } %>
38
+ # <% tab.panel { "Panel 1" } %>
39
+ # <% end %>
40
+ # <%= render(Ariadne::TabComponent.new(id: "pub-comment")) do |tab| %>
41
+ # <% tab.text { "Tab 2" } %>
42
+ # <% tab.panel { "Panel 2" } %>
43
+ # <% end %>
44
+ # <% end %>
45
+ #
46
+ # %>
47
+ #
48
+ # @param sr_label [String] Sets an `aria-label` that helps assistive technology users understand the purpose of the tabs.
49
+ # @param classes [String] <%= link_to_classes_docs %>
50
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
51
+ def initialize(sr_label:, classes: "", attributes: {})
52
+ @tag = DEFAULT_TAG
53
+ @classes = class_names(
54
+ DEFAULT_CLASSES,
55
+ classes,
56
+ )
57
+
58
+ @attributes = attributes
59
+ @attributes[:"aria-label"] = sr_label
60
+ @attributes[:role] = :presentation
61
+ end
62
+
63
+ def render?
64
+ tabs.present?
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
2
+ <ul class="ariadne-list-none">
3
+ <% tabs.each do |tab| %>
4
+ <li class="ariadne-inline-flex"><%= tab %></li>
5
+ <% end %>
6
+ </ul>
7
+ <% end %>