ariadne_view_components 0.0.12 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) 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/tab-container-component.d.ts +1 -0
  6. data/app/assets/javascripts/tab-nav-component.d.ts +9 -0
  7. data/app/components/ariadne/ariadne.ts +3 -0
  8. data/app/components/ariadne/body_component.rb +1 -1
  9. data/app/components/ariadne/button_component.rb +2 -2
  10. data/app/components/ariadne/comment_component.html.erb +2 -6
  11. data/app/components/ariadne/comment_component.rb +1 -1
  12. data/app/components/ariadne/component.rb +3 -5
  13. data/app/components/ariadne/container_component.rb +1 -1
  14. data/app/components/ariadne/counter_component.rb +1 -1
  15. data/app/components/ariadne/flash_component.rb +1 -1
  16. data/app/components/ariadne/flex_component.rb +1 -1
  17. data/app/components/ariadne/footer_component.rb +1 -1
  18. data/app/components/ariadne/grid_component.html.erb +2 -2
  19. data/app/components/ariadne/grid_component.rb +10 -8
  20. data/app/components/ariadne/header_component.rb +1 -1
  21. data/app/components/ariadne/heroicon_component.html.erb +1 -1
  22. data/app/components/ariadne/heroicon_component.rb +1 -2
  23. data/app/components/ariadne/list_component.html.erb +3 -5
  24. data/app/components/ariadne/list_component.rb +5 -5
  25. data/app/components/ariadne/main_component.rb +1 -1
  26. data/app/components/ariadne/narrow_container_component.rb +1 -1
  27. data/app/components/ariadne/panel_bar_component.rb +2 -2
  28. data/app/components/ariadne/pill_component.rb +1 -1
  29. data/app/components/ariadne/rich_text_area_component.html.erb +1 -1
  30. data/app/components/ariadne/rich_text_area_component.rb +1 -1
  31. data/app/components/ariadne/slideover_component.rb +2 -2
  32. data/app/components/ariadne/tab-container-component.ts +24 -0
  33. data/app/components/ariadne/tab-nav-component.ts +34 -0
  34. data/app/components/ariadne/tab_component.html.erb +2 -6
  35. data/app/components/ariadne/tab_component.rb +77 -18
  36. data/app/components/ariadne/tab_container_component.erb +12 -0
  37. data/app/components/ariadne/tab_container_component.rb +61 -0
  38. data/app/components/ariadne/tab_nav_component.html.erb +7 -0
  39. data/app/components/ariadne/tab_nav_component.rb +72 -0
  40. data/app/components/ariadne/table_component.html.erb +17 -0
  41. data/app/components/ariadne/table_component.rb +281 -0
  42. data/app/components/ariadne/time_ago_component.rb +1 -1
  43. data/app/components/ariadne/timeline_component.rb +1 -1
  44. data/app/lib/ariadne/fetch_or_fallback_helper.rb +11 -3
  45. data/app/lib/ariadne/icon_helper.rb +17 -13
  46. data/lib/ariadne/view_components/constants.rb +2 -2
  47. data/lib/ariadne/view_components/statuses.rb +2 -2
  48. data/lib/ariadne/view_components/version.rb +1 -1
  49. data/lib/rubocop/config/default.yml +1 -1
  50. data/lib/tasks/docs.rake +5 -96
  51. data/static/arguments.yml +51 -15
  52. data/static/audited_at.json +9 -1
  53. data/static/classes.yml +157 -269
  54. data/static/constants.json +55 -15
  55. data/static/statuses.json +9 -1
  56. data/tailwind.config.js +11 -26
  57. metadata +13 -10
  58. data/app/components/ariadne/tab_bar_component.html.erb +0 -3
  59. data/app/components/ariadne/tab_bar_component.rb +0 -45
  60. data/app/lib/ariadne/join_style_arguments_helper.rb +0 -14
  61. data/app/lib/ariadne/tab_nav_helper.rb +0 -35
  62. data/app/lib/ariadne/tabbed_component_helper.rb +0 -39
  63. data/app/lib/ariadne/test_selector_helper.rb +0 -20
  64. data/app/lib/ariadne/underline_nav_helper.rb +0 -44
@@ -0,0 +1 @@
1
+ import '@github/tab-container-element';
@@ -0,0 +1,9 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ export default class TabNavComponent extends Controller {
3
+ static targets: string[];
4
+ readonly tabTargets: [HTMLAnchorElement];
5
+ SELECTED_CLASSES: string[];
6
+ UNSELECTED_CLASSES: string[];
7
+ connect(): void;
8
+ toggle(event: Event): void;
9
+ }
@@ -6,8 +6,10 @@ import ClipboardCopyComponent from './clipboard-copy-component'
6
6
  import CommentComponent from './comment-component'
7
7
  import RichTextAreaComponent from './rich-text-area-component'
8
8
  import SlideoverComponent from './slideover-component'
9
+ import TabNavComponent from './tab-nav-component'
9
10
  import TooltipComponent from './tooltip-component'
10
11
 
12
+ import './tab-container-component'
11
13
  import './time-ago-component'
12
14
 
13
15
  const application = Application.start()
@@ -17,4 +19,5 @@ application.register('ariadne-form', AriadneForm)
17
19
  application.register('comment-component', CommentComponent)
18
20
  application.register('rich-text-area-component', RichTextAreaComponent)
19
21
  application.register('slideover-component', SlideoverComponent)
22
+ application.register('tab-nav-component', TabNavComponent)
20
23
  application.register('tooltip-component', TooltipComponent)
@@ -17,7 +17,7 @@ module Ariadne
17
17
  @tag = :body
18
18
  @classes = class_names(
19
19
  DEFAULT_CLASSES,
20
- classes
20
+ classes,
21
21
  )
22
22
 
23
23
  @attributes = attributes
@@ -82,7 +82,7 @@ module Ariadne
82
82
  #
83
83
  # @example With leading visual
84
84
  # <%= render(Ariadne::ButtonComponent.new) do |c| %>
85
- # <% c.icon(icon: :star, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, classes: "text-yellow-600") %>
85
+ # <% c.icon(icon: :star, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, classes: "ariadne-text-yellow-600") %>
86
86
  # Button
87
87
  # <% end %>
88
88
  #
@@ -135,7 +135,7 @@ module Ariadne
135
135
 
136
136
  @classes = class_names(
137
137
  SCHEME_CLASS_MAPPINGS[@scheme],
138
- classes
138
+ classes,
139
139
  )
140
140
  end
141
141
 
@@ -2,16 +2,12 @@
2
2
  <div class="ariadne-bg-white" data-comment-component-target="commentComponent">
3
3
  <div class="ariadne-hidden sm:ariadne-block">
4
4
  <div class="ariadne-border-b ariadne-border-gray-200">
5
- <%= render(Ariadne::TabBarComponent.new(sr_label: @sr_label, attributes: { :"data-comment-component-target" => "tabBarComponent" })) do |tab_bar| %>
6
- <%= public_tab %>
7
- <%= internal_tab %>
8
- <% end %>
9
5
  </div>
10
6
  </div>
11
7
  <%= ariadne_form_with(url: @url, method: @method, classes: @classes, attributes: @attributes) do |comment_box| %>
12
8
  <div class="ariadne-overflow-hidden ariadne-border-x ariadne-border-b ariadne-border-gray-300 ariadne-shadow-sm focus-within:ariadne-border-indigo-500 focus-within:ariadne-ring-1 focus-within:ariadne-ring-indigo-500">
13
- <%= hidden_field_tag 'message_public', true %>
14
- <%= render(Ariadne::RichTextAreaComponent.new(name: :bodytext, sr_label: "Select reply type", attributes: { required: true})) %>
9
+ <%= hidden_field_tag 'message_public', true %>
10
+ <%= render(Ariadne::RichTextAreaComponent.new(name: :bodytext, sr_label: "Select reply type", attributes: { required: true})) %>
15
11
  <% comment_box.submit { @submit } %>
16
12
  </div>
17
13
  <div class="ariadne-mt-2 ariadne-flex ariadne-justify-end">
@@ -42,7 +42,7 @@ module Ariadne
42
42
  @tag = DEFAULT_TAG
43
43
  @classes = class_names(
44
44
  DEFAULT_CLASSES,
45
- classes
45
+ classes,
46
46
  )
47
47
  @url = url
48
48
  @method = method
@@ -10,15 +10,13 @@ module Ariadne
10
10
  include ViewComponent::SlotableV2 unless ViewComponent::Base < ViewComponent::SlotableV2
11
11
  include ClassNameHelper
12
12
  include FetchOrFallbackHelper
13
- include TestSelectorHelper
14
- include JoinStyleArgumentsHelper
15
13
  include ViewHelper
16
14
  include Status::Dsl
17
15
  include Audited::Dsl
18
16
  include LoggerHelper
19
17
  include Ariadne::ActionViewExtensions::FormHelper
20
18
 
21
- BASE_HTML_CLASSES = "ariadne-h-full scroll-smooth ariadne-bg-white ariadne-antialiased"
19
+ BASE_HTML_CLASSES = "ariadne-h-full ariadne-scroll-smooth ariadne-bg-white ariadne-antialiased"
22
20
  BASE_BODY_CLASSES = "ariadne-flex ariadne-h-full ariadne-flex-col"
23
21
  BASE_WRAPPER_CLASSES = "ariadne-flex ariadne-flex-col ariadne-h-screen ariadne-justify-between"
24
22
  BASE_MAIN_CLASSES = "ariadne-flex-auto"
@@ -84,7 +82,7 @@ module Ariadne
84
82
  if (denylist = attributes[denylist_name])
85
83
  check_denylist(denylist, attributes)
86
84
 
87
- # Remove :attributes_denylist key and any denied keys from system arguments
85
+ # Remove :attributes_denylist key and any denied keys from attributes
88
86
  attributes.except!(denylist_name)
89
87
  attributes.except!(*denylist.keys.flatten)
90
88
  end
@@ -109,7 +107,7 @@ module Ariadne
109
107
  deny_aria_key(
110
108
  :label,
111
109
  "Don't use `aria-label` on `#{tag}` elements. See https://www.tpgi.com/short-note-on-aria-label-aria-labelledby-and-aria-describedby/",
112
- attributes
110
+ attributes,
113
111
  )
114
112
  end
115
113
 
@@ -16,7 +16,7 @@ module Ariadne
16
16
  @tag = :div
17
17
  @classes = class_names(
18
18
  DEFAULT_CLASSES,
19
- classes
19
+ classes,
20
20
  )
21
21
 
22
22
  @attributes = attributes
@@ -50,7 +50,7 @@ module Ariadne
50
50
 
51
51
  @classes = class_names(
52
52
  DEFAULT_CLASSES,
53
- classes
53
+ classes,
54
54
  )
55
55
  @attributes[:hidden] = true if count == 0 && hide_if_zero
56
56
  end
@@ -104,7 +104,7 @@ module Ariadne
104
104
 
105
105
  @classes = class_names(
106
106
  CONTENT_SCHEME_CLASS_MAPPINGS[@scheme],
107
- classes
107
+ classes,
108
108
  )
109
109
 
110
110
  @attributes = attributes
@@ -36,7 +36,7 @@ module Ariadne
36
36
  @classes = class_names(
37
37
  DEFAULT_CLASSES,
38
38
  classes,
39
- flex_class
39
+ flex_class,
40
40
  )
41
41
 
42
42
  @attributes = attributes
@@ -14,7 +14,7 @@ module Ariadne
14
14
  def initialize(classes: "", attributes: {})
15
15
  @classes = class_names(
16
16
  DEFAULT_CLASSES,
17
- classes
17
+ classes,
18
18
  )
19
19
 
20
20
  @attributes = attributes
@@ -1,8 +1,8 @@
1
1
  <%= render Ariadne::BaseComponent.new(tag: :ul, classes: @classes, attributes: @attributes) do |grid| %>
2
2
  <% items.each do |item| %>
3
- <li class="<%= item.classes %> <%= item.has_href? ? Ariadne::GridComponent::DEFAULT_LINK_COLOR_CLASSES : "ariadne-bg-white" %>">
3
+ <li class="<%= item.classes %>">
4
4
  <% if item.has_href? %>
5
- <%= render Ariadne::LinkComponent.new(href: item.href) do %>
5
+ <%= render Ariadne::LinkComponent.new(href: item.href, classes: Ariadne::GridComponent::GridItem::DEFAULT_ACTION_LINK_COLORS) do %>
6
6
  <%= item.entry %>
7
7
  <% end %>
8
8
  <% if item.actions.any? %>
@@ -5,7 +5,7 @@ module Ariadne
5
5
  class GridComponent < Ariadne::Component
6
6
  DEFAULT_CLASSES = "ariadne-grid ariadne-gap-6 sm:ariadne-grid-cols-2 lg:ariadne-grid-cols-3"
7
7
 
8
- DEFAULT_LINK_COLOR_CLASSES = "ariadne-text-button-text-color ariadne-bg-button-bg-color focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500"
8
+ DEFAULT_LINK_COLOR_CLASSES = "focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500"
9
9
 
10
10
  # The items to show in the ariadne-grid.
11
11
  renders_many :items, "GridItem"
@@ -19,7 +19,7 @@ module Ariadne
19
19
  def initialize(classes: "", attributes: {})
20
20
  @classes = class_names(
21
21
  DEFAULT_CLASSES,
22
- classes
22
+ classes,
23
23
  )
24
24
 
25
25
  default_attributes = { role: "list" }
@@ -29,18 +29,20 @@ module Ariadne
29
29
  # This component is part of `GridComponent` and should not be
30
30
  # used as a standalone component.
31
31
  class GridItem < Ariadne::Component
32
- DEFAULT_ITEM_CLASSES = "ariadne-flex ariadne-flex-col ariadne-text-center ariadne-rounded-lg ariadne-shadow ariadne-my-4 text-black-700 ariadne-border-black"
32
+ DEFAULT_ITEM_CLASSES = "ariadne-flex ariadne-flex-col ariadne-text-center ariadne-rounded-lg ariadne-shadow text-black-700 ariadne-border-black"
33
33
 
34
- DEFAULT_ENTRY_CLASSES = "group ariadne-flex-1 ariadne-flex ariadne-flex-col ariadne-p-8 hover:ariadne-bg-button-hover-color hover:ariadne-text-gray-500 ariadne-rounded-lg"
35
- renders_one :entry, lambda { |classes: "", &block|
34
+ DEFAULT_ENTRY_CLASSES = "ariadne-group ariadne-flex-1 ariadne-flex ariadne-flex-col ariadne-p-8 hover:ariadne-text-gray-500"
35
+ renders_one :entry, lambda { |classes: "", attributes: {}, &block|
36
36
  view_context.capture do
37
- render(Ariadne::BaseComponent.new(tag: :div, classes: classes)) { block&.call }
37
+ actual_classes = class_names(DEFAULT_ENTRY_CLASSES, classes)
38
+ render(Ariadne::BaseComponent.new(tag: :div, classes: actual_classes, attributes: attributes)) { block&.call }
38
39
  end
39
40
  }
40
41
 
41
- DEFAULT_ACTION_LINK_CLASSES = "text-button-text-color ariadne-relative ariadne--mr-px ariadne-w-0 ariadne-flex-1 ariadne-inline-flex ariadne-items-center ariadne-justify-center ariadne-py-4 ariadne-text-sm ariadne-font-medium ariadne-border ariadne-border-transparent ariadne-rounded-bl-lg ariadne-rounded-lg"
42
+ DEFAULT_ACTION_LINK_CLASSES = "ariadne-relative ariadne--mr-px ariadne-w-0 ariadne-flex-1 ariadne-inline-flex ariadne-items-center ariadne-justify-center ariadne-py-4 ariadne-text-sm ariadne-font-medium ariadne-border ariadne-border-transparent"
43
+ DEFAULT_ACTION_LINK_COLORS = "hover:ariadne-text-gray-500"
42
44
  renders_many :actions, lambda { |href:, icon:, label:, size: Ariadne::HeroiconComponent::SIZE_DEFAULT, variant: HeroiconsHelper::Icon::VARIANT_SOLID, classes: "", attributes: {}, text_classes: ""|
43
- actual_classes = class_names(DEFAULT_ACTION_LINK_CLASSES, classes)
45
+ actual_classes = class_names(DEFAULT_ACTION_LINK_CLASSES, DEFAULT_ACTION_LINK_COLORS, classes)
44
46
  render(Ariadne::LinkComponent.new(href: href, classes: actual_classes, attributes: attributes)) do
45
47
  render(Ariadne::HeroiconComponent.new(icon: icon, size: size, variant: variant, text_classes: text_classes)) { label }
46
48
  end
@@ -85,7 +85,7 @@ module Ariadne
85
85
  def initialize(classes: "", attributes: {})
86
86
  @classes = class_names(
87
87
  DEFAULT_CLASSES,
88
- classes
88
+ classes,
89
89
  )
90
90
 
91
91
  @attributes = attributes
@@ -1,4 +1,4 @@
1
1
  <%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
2
- <%= @icon.path.html_safe %>
2
+ <%= @icon.path %>
3
3
  <% end %>
4
4
  <%= render(Ariadne::BaseComponent.new(tag: :span, classes: @text_classes, attributes: @text_attributes)) { content } if content.present? %>
@@ -107,10 +107,9 @@ module Ariadne
107
107
  }
108
108
 
109
109
  @icon = heroicon(icon, variant: variant, **attributes)
110
-
111
110
  @classes = class_names(
112
111
  @icon.attributes[:class],
113
- classes
112
+ classes,
114
113
  )
115
114
  @attributes.merge!(@icon.attributes.except(:class, :"aria-hidden"))
116
115
 
@@ -1,13 +1,11 @@
1
1
  <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |list| %>
2
2
  <% items.each do |item| %>
3
- <% if item.linked? %>
4
- <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
3
+ <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
4
+ <% if item.linked? %>
5
5
  <%= render Ariadne::LinkComponent.new(href: item.link[:href], classes: item.link[:classes], attributes: item.link[:attributes]) do %>
6
6
  <%= item.entry %>
7
7
  <% end %>
8
- <% end %>
9
- <% else %>
10
- <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
8
+ <% else %>
11
9
  <%= item.entry %>
12
10
  <% end %>
13
11
  <% end %>
@@ -36,11 +36,11 @@ module Ariadne
36
36
  class ListItem < Ariadne::Component
37
37
  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
38
 
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 }
39
+ DEFAULT_ENTRY_CLASSES = "hover:ariadne-text-gray-500"
40
+ renders_one :entry, lambda { |classes: "", attributes: {}, &block|
41
+ view_context.capture do
42
+ actual_classes = class_names(DEFAULT_ENTRY_CLASSES, classes)
43
+ render(Ariadne::BaseComponent.new(tag: :div, classes: actual_classes, attributes: attributes)) { block&.call }
44
44
  end
45
45
  }
46
46
 
@@ -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
@@ -20,7 +20,7 @@ module Ariadne
20
20
  @tag = check_incoming_tag(DEFAULT_TAG, tag)
21
21
  @classes = class_names(
22
22
  DEFAULT_CLASSES,
23
- classes
23
+ classes,
24
24
  )
25
25
 
26
26
  @attributes = attributes
@@ -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
5
  <textarea class="tiptap-editor"></textarea>
6
6
  <% end %>
@@ -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"
@@ -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,102 @@
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
45
  attr_reader :selected
15
46
 
16
- DEFAULT_CLASSES = "ariadne-border-gray-300 ariadne-border ariadne-shadow ariadne-py-5 ariadne-px-5 ariadne-rounded-md"
17
-
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: "", 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
+ @system_arguments[:tag] = :button
82
+ @system_arguments[:type] = :button
83
+ @system_arguments[:role] = :tab
84
+ @panel_id = "panel-#{@id}"
85
+ @attributes[:"aria-controls"] = @panel_id
86
+ # https://www.w3.org/TR/wai-aria-practices/#presentation_role
87
+ @wrapper_arguments[:role] = :presentation
88
+ else
89
+ @tag = :a
90
+ end
91
+
92
+ return unless @selected
93
+
94
+ if @tag == :a
95
+ aria_current = aria("current", attributes) || DEFAULT_ARIA_CURRENT_FOR_ANCHOR
96
+ @attributes[:"aria-current"] = fetch_or_raise(ARIA_CURRENT_OPTIONS_FOR_ANCHOR, aria_current)
97
+ else
98
+ @attributes[:"aria-selected"] = true
99
+ end
41
100
  end
42
101
  end
43
102
  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 %>