ariadne_view_components 0.0.43-aarch64-linux → 0.0.44-aarch64-linux
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/app/components/ariadne/ariadne-form.d.ts +22 -0
- data/app/components/ariadne/ariadne-form.js +85 -0
- data/app/components/ariadne/ariadne-form.ts +96 -0
- data/app/components/ariadne/ariadne.d.ts +2 -0
- data/app/components/ariadne/ariadne.js +16 -0
- data/app/components/ariadne/ariadne.ts +21 -0
- data/app/components/ariadne/avatar_component.rb +81 -0
- data/app/components/ariadne/avatar_stack_component/avatar_stack_component.html.erb +12 -0
- data/app/components/ariadne/avatar_stack_component.rb +75 -0
- data/app/components/ariadne/base_button.rb +70 -0
- data/app/components/ariadne/base_component.rb +37 -0
- data/app/components/ariadne/blankslate_component/blankslate_component.html.erb +26 -0
- data/app/components/ariadne/blankslate_component.rb +148 -0
- data/app/components/ariadne/body_component.rb +30 -0
- data/app/components/ariadne/button_component/button_component.html.erb +4 -0
- data/app/components/ariadne/button_component.rb +165 -0
- data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.d.ts +4 -0
- data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.js +18 -0
- data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.ts +19 -0
- data/app/components/ariadne/clipboard_copy_component/clipboard_copy_component.html.erb +9 -0
- data/app/components/ariadne/clipboard_copy_component.rb +90 -0
- data/app/components/ariadne/comment_component/comment_component.html.erb +37 -0
- data/app/components/ariadne/comment_component.rb +71 -0
- data/app/components/ariadne/component.rb +127 -0
- data/app/components/ariadne/container_component/container_component.html.erb +3 -0
- data/app/components/ariadne/container_component.rb +25 -0
- data/app/components/ariadne/content.rb +12 -0
- data/app/components/ariadne/counter_component.rb +100 -0
- data/app/components/ariadne/details_component/details_component.html.erb +4 -0
- data/app/components/ariadne/details_component.rb +81 -0
- data/app/components/ariadne/dropdown/menu_component.html.erb +20 -0
- data/app/components/ariadne/dropdown/menu_component.rb +101 -0
- data/app/components/ariadne/dropdown/menu_component.ts +1 -0
- data/app/components/ariadne/dropdown_component/dropdown_component.html.erb +8 -0
- data/app/components/ariadne/dropdown_component.rb +172 -0
- data/app/components/ariadne/flash_component/flash_component.html.erb +31 -0
- data/app/components/ariadne/flash_component.rb +128 -0
- data/app/components/ariadne/flex_component/flex_component.html.erb +5 -0
- data/app/components/ariadne/flex_component.rb +56 -0
- data/app/components/ariadne/footer_component/footer_component.html.erb +7 -0
- data/app/components/ariadne/footer_component.rb +23 -0
- data/app/components/ariadne/grid_component/grid_component.html.erb +26 -0
- data/app/components/ariadne/grid_component.rb +67 -0
- data/app/components/ariadne/header_component/header_component.html.erb +29 -0
- data/app/components/ariadne/header_component.rb +111 -0
- data/app/components/ariadne/heading_component.rb +49 -0
- data/app/components/ariadne/heroicon_component/heroicon_component.html.erb +4 -0
- data/app/components/ariadne/heroicon_component.rb +166 -0
- data/app/components/ariadne/image_component.rb +53 -0
- data/app/components/ariadne/inline_flex_component/inline_flex_component.html.erb +6 -0
- data/app/components/ariadne/inline_flex_component.rb +72 -0
- data/app/components/ariadne/link_component.rb +65 -0
- data/app/components/ariadne/list_component/list_component.html.erb +6 -0
- data/app/components/ariadne/list_component.rb +70 -0
- data/app/components/ariadne/narrow_container_component/narrow_container_component.html.erb +3 -0
- data/app/components/ariadne/narrow_container_component.rb +30 -0
- data/app/components/ariadne/panel_bar_component/panel_bar_component.html.erb +20 -0
- data/app/components/ariadne/panel_bar_component.rb +80 -0
- data/app/components/ariadne/pill_component/pill_component.html.erb +3 -0
- data/app/components/ariadne/pill_component.rb +44 -0
- data/app/components/ariadne/rich_text_area_component/rich-text-area-component.d.ts +6 -0
- data/app/components/ariadne/rich_text_area_component/rich-text-area-component.js +38 -0
- data/app/components/ariadne/rich_text_area_component/rich-text-area-component.ts +47 -0
- data/app/components/ariadne/rich_text_area_component/rich_text_area_component.html.erb +6 -0
- data/app/components/ariadne/rich_text_area_component.rb +35 -0
- data/app/components/ariadne/slideover_component/slideover-component.d.ts +9 -0
- data/app/components/ariadne/slideover_component/slideover-component.js +11 -0
- data/app/components/ariadne/slideover_component/slideover-component.ts +17 -0
- data/app/components/ariadne/slideover_component/slideover_component.html.erb +9 -0
- data/app/components/ariadne/slideover_component.rb +66 -0
- data/app/components/ariadne/tab_component/tab_component.html.erb +3 -0
- data/app/components/ariadne/tab_component.rb +98 -0
- data/app/components/ariadne/tab_container_component/tab-container-component.d.ts +1 -0
- data/app/components/ariadne/tab_container_component/tab-container-component.js +23 -0
- data/app/components/ariadne/tab_container_component/tab-container-component.ts +24 -0
- data/app/components/ariadne/tab_container_component.erb +10 -0
- data/app/components/ariadne/tab_container_component.rb +68 -0
- data/app/components/ariadne/tab_nav_component/tab-nav-component.d.ts +9 -0
- data/app/components/ariadne/tab_nav_component/tab-nav-component.js +33 -0
- data/app/components/ariadne/tab_nav_component/tab-nav-component.ts +34 -0
- data/app/components/ariadne/tab_nav_component/tab_nav_component.html.erb +7 -0
- data/app/components/ariadne/tab_nav_component.rb +72 -0
- data/app/components/ariadne/table_nav_component/table_nav_component.html.erb +52 -0
- data/app/components/ariadne/table_nav_component.rb +338 -0
- data/app/components/ariadne/text.rb +25 -0
- data/app/components/ariadne/time_ago_component/time-ago-component.d.ts +1 -0
- data/app/components/ariadne/time_ago_component/time-ago-component.js +1 -0
- data/app/components/ariadne/time_ago_component/time-ago-component.ts +1 -0
- data/app/components/ariadne/time_ago_component.rb +56 -0
- data/app/components/ariadne/timeline_component/timeline_component.html.erb +19 -0
- data/app/components/ariadne/timeline_component.rb +34 -0
- data/app/components/ariadne/tooltip_component/tooltip-component.d.ts +24 -0
- data/app/components/ariadne/tooltip_component/tooltip-component.js +43 -0
- data/app/components/ariadne/tooltip_component/tooltip-component.ts +57 -0
- data/app/components/ariadne/tooltip_component/tooltip_component.html.erb +4 -0
- data/app/components/ariadne/tooltip_component.rb +108 -0
- data/lib/ariadne/view_components/engine.rb +0 -22
- data/lib/ariadne/view_components/version.rb +1 -1
- data/tailwind.config.js +10 -15
- metadata +98 -2
@@ -0,0 +1,47 @@
|
|
1
|
+
import {Controller} from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
import {Editor} from '@tiptap/core'
|
4
|
+
|
5
|
+
import {Document} from '@tiptap/extension-document'
|
6
|
+
import {Paragraph} from '@tiptap/extension-paragraph'
|
7
|
+
import {Text} from '@tiptap/extension-text'
|
8
|
+
|
9
|
+
import DropCursor from '@tiptap/extension-dropcursor'
|
10
|
+
import GapCursor from '@tiptap/extension-gapcursor'
|
11
|
+
import {History} from '@tiptap/extension-history'
|
12
|
+
|
13
|
+
export default class RichTextArea extends Controller {
|
14
|
+
static targets = ['editor']
|
15
|
+
|
16
|
+
declare readonly editorTargets: [HTMLDivElement]
|
17
|
+
|
18
|
+
connect() {
|
19
|
+
for (const editorElement of this.editorTargets) {
|
20
|
+
const pmEditor = new Editor({
|
21
|
+
extensions: [DropCursor, GapCursor, History, Document, Paragraph, Text],
|
22
|
+
content: '',
|
23
|
+
injectCSS: false,
|
24
|
+
element: editorElement,
|
25
|
+
editorProps: {
|
26
|
+
attributes: {
|
27
|
+
class:
|
28
|
+
'ariadne-h-28 ariadne-max-h-48 ariadne-p-2 ariadne-rounded-lg ariadne-overflow-y-auto focus:ariadne-outline-none',
|
29
|
+
},
|
30
|
+
},
|
31
|
+
parseOptions: {
|
32
|
+
preserveWhitespace: true,
|
33
|
+
},
|
34
|
+
})
|
35
|
+
|
36
|
+
const tiptapValueContainer = editorElement.previousElementSibling
|
37
|
+
if (tiptapValueContainer) {
|
38
|
+
const parentForm = editorElement.closest('form')
|
39
|
+
|
40
|
+
parentForm?.addEventListener('submit', () => {
|
41
|
+
tiptapValueContainer.setAttribute('value', pmEditor.getText() || '')
|
42
|
+
pmEditor.commands.clearContent() // TODO: test this
|
43
|
+
})
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
|
2
|
+
<% if @has_form %>
|
3
|
+
<input type="hidden" data-tiptap-value-container=true name="<%= @name %>" id="<%= @name %>">
|
4
|
+
<% end %>
|
5
|
+
<div class="tiptap-editor" name="rich-text-area" data-rich-text-area-component-target="editor"></div>
|
6
|
+
<% end %>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Add a general description of component here
|
5
|
+
# Add additional usage considerations or best practices that may aid the user to use the component correctly.
|
6
|
+
# @accessibility Add any accessibility considerations
|
7
|
+
class RichTextAreaComponent < Ariadne::Component
|
8
|
+
DEFAULT_TAG = :div
|
9
|
+
DEFAULT_CLASSES = ""
|
10
|
+
|
11
|
+
# @example Default
|
12
|
+
#
|
13
|
+
# <%= render(Ariadne::RichTextAreaComponent.new(name: "bodytext", sr_label: "Enter message contents")) { "Example" } %>
|
14
|
+
#
|
15
|
+
# @param name [Symbol] Identifies the form/param name for this rich text area.
|
16
|
+
# @param sr_label [String] A label to introduce these tabs for screen readers.
|
17
|
+
# @param has_form [Boolean] Indicates whether this component is wrapped in a form.
|
18
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
19
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
20
|
+
def initialize(name:, sr_label:, has_form: true, classes: "", attributes: {})
|
21
|
+
@tag = DEFAULT_TAG
|
22
|
+
@name = name
|
23
|
+
@sr_label = sr_label
|
24
|
+
@has_form = has_form
|
25
|
+
|
26
|
+
@classes = merge_class_names(
|
27
|
+
DEFAULT_CLASSES,
|
28
|
+
classes,
|
29
|
+
)
|
30
|
+
@attributes = attributes
|
31
|
+
@attributes[:"data-controller"] = "rich-text-area-component"
|
32
|
+
@attributes[:"data-has-form"] = true if @has_form
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
export default class SlideoverComponent extends Controller {
|
3
|
+
static targets: string[];
|
4
|
+
readonly expandableTarget: HTMLDivElement;
|
5
|
+
readonly expandWrapperTarget: HTMLDivElement;
|
6
|
+
readonly slidePanelTargets: [HTMLDivElement];
|
7
|
+
readonly buttonWrapperTarget: HTMLDivElement;
|
8
|
+
toggle(): void;
|
9
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
class SlideoverComponent extends Controller {
|
3
|
+
toggle() {
|
4
|
+
this.expandableTarget.classList.toggle('ariadne-hidden');
|
5
|
+
for (const slidePanel of this.slidePanelTargets) {
|
6
|
+
slidePanel.classList.toggle('ariadne-hidden');
|
7
|
+
}
|
8
|
+
}
|
9
|
+
}
|
10
|
+
SlideoverComponent.targets = ['expandable', 'expandWrapper', 'slidePanel', 'buttonWrapper'];
|
11
|
+
export default SlideoverComponent;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import {Controller} from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class SlideoverComponent extends Controller {
|
4
|
+
static targets = ['expandable', 'expandWrapper', 'slidePanel', 'buttonWrapper']
|
5
|
+
|
6
|
+
declare readonly expandableTarget: HTMLDivElement
|
7
|
+
declare readonly expandWrapperTarget: HTMLDivElement
|
8
|
+
declare readonly slidePanelTargets: [HTMLDivElement]
|
9
|
+
declare readonly buttonWrapperTarget: HTMLDivElement
|
10
|
+
|
11
|
+
toggle() {
|
12
|
+
this.expandableTarget.classList.toggle('ariadne-hidden')
|
13
|
+
for (const slidePanel of this.slidePanelTargets) {
|
14
|
+
slidePanel.classList.toggle('ariadne-hidden')
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |slideover| %>
|
2
|
+
<div data-slideover-component-target="expandable" class="ariadne-hidden ariadne-px-3">
|
3
|
+
<%= content %>
|
4
|
+
</div>
|
5
|
+
<div data-slideover-component-target="buttonWrapper" class="ariadne-relative ariadne-flex ariadne-justify-center">
|
6
|
+
<%= open_button %>
|
7
|
+
<%= close_button %>
|
8
|
+
</div>
|
9
|
+
<% end %>
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Add a general description of component here
|
5
|
+
# Add additional usage considerations or best practices that may aid the user to use the component correctly.
|
6
|
+
# @accessibility Add any accessibility considerations
|
7
|
+
class SlideoverComponent < Ariadne::Component
|
8
|
+
DEFAULT_TAG = :div
|
9
|
+
TAG_OPTIONS = [DEFAULT_TAG].freeze
|
10
|
+
|
11
|
+
DIRECTION_Y_DOWN = :yd
|
12
|
+
DIRECTION_X_LEFT = :xl
|
13
|
+
VALID_DIRECTIONS = [DIRECTION_Y_DOWN, DIRECTION_X_LEFT].freeze
|
14
|
+
|
15
|
+
DEFAULT_CLASSES = "ariadne-flex ariadne-flex-col"
|
16
|
+
|
17
|
+
DEFAULT_BUTTON_CLASSES = "ariadne-inline-flex ariadne-items-center ariadne-shadow-sm ariadne-px-4 ariadne-py-1.5 ariadne-pb-2 ariadne-text-sm ariadne-leading-5 ariadne-font-medium ariadne-rounded-full " + Ariadne::ButtonComponent::SCHEME_CLASS_MAPPINGS[:default]
|
18
|
+
renders_one :open_button, lambda { |selected: false, classes: "", attributes: {}|
|
19
|
+
actual_classes = merge_class_names(DEFAULT_BUTTON_CLASSES, classes, selected ? "" : BASE_HIDDEN_CLASS)
|
20
|
+
|
21
|
+
attributes[:id] ||= "btnOpen"
|
22
|
+
attributes[:"data-slideover-component-target"] ||= "slidePanel"
|
23
|
+
attributes[:"data-action"] ||= "click->slideover-component#toggle"
|
24
|
+
attributes[:type] ||= "submit"
|
25
|
+
|
26
|
+
Ariadne::ButtonComponent.new(classes: actual_classes, attributes: attributes)
|
27
|
+
}
|
28
|
+
|
29
|
+
renders_one :close_button, lambda { |selected: false, classes: "", attributes: {}|
|
30
|
+
actual_classes = merge_class_names(DEFAULT_BUTTON_CLASSES, classes, selected ? "" : BASE_HIDDEN_CLASS)
|
31
|
+
|
32
|
+
attributes[:id] ||= "btnClose"
|
33
|
+
attributes[:"data-slideover-component-target"] ||= "slidePanel"
|
34
|
+
attributes[:"data-action"] ||= "click->slideover-component#toggle"
|
35
|
+
attributes[:type] ||= "submit"
|
36
|
+
|
37
|
+
Ariadne::ButtonComponent.new(classes: actual_classes, attributes: attributes)
|
38
|
+
}
|
39
|
+
|
40
|
+
# @example Default
|
41
|
+
#
|
42
|
+
# <%= render(Ariadne::SlideoverComponent.new(direction: Ariadne::SlideoverComponent::DIRECTION_Y_DOWN)) { "Example" } %>
|
43
|
+
#
|
44
|
+
# @param tag [Symbol, String] The rendered tag name.
|
45
|
+
# @param direction [Symbol] <%= one_of(Ariadne::SlideoverComponent::VALID_DIRECTIONS) %>
|
46
|
+
# @param open_text [String] The text to use to open the slideover.
|
47
|
+
# @param close_text [String] The text to use to close the slideover.
|
48
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
49
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
50
|
+
def initialize(tag: DEFAULT_TAG, direction:, open_text: "Open", close_text: "Close", classes: "", attributes: {})
|
51
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
52
|
+
@classes = merge_class_names(
|
53
|
+
DEFAULT_CLASSES,
|
54
|
+
classes,
|
55
|
+
)
|
56
|
+
|
57
|
+
@direction = fetch_or_raise(VALID_DIRECTIONS, direction)
|
58
|
+
|
59
|
+
@open_text = open_text
|
60
|
+
@close_text = close_text
|
61
|
+
@attributes = attributes
|
62
|
+
@attributes[:"data-controller"] = "slideover-component"
|
63
|
+
@attributes[:"data-slideover-component-target"] = "expandWrapper"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
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"
|
10
|
+
class TabComponent < Ariadne::Component
|
11
|
+
DEFAULT_ARIA_CURRENT_FOR_ANCHOR = :page
|
12
|
+
ARIA_CURRENT_OPTIONS_FOR_ANCHOR = [true, DEFAULT_ARIA_CURRENT_FOR_ANCHOR].freeze
|
13
|
+
|
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[:hidden] = true unless @selected
|
25
|
+
|
26
|
+
label_present = aria("label", attributes) || aria("labelledby", attributes)
|
27
|
+
unless label_present
|
28
|
+
if @id.present?
|
29
|
+
attributes[:"aria-labelledby"] = @id
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Panels must be labelled. Either set a unique `id` on the tab, or set an `aria-label` directly on the panel"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Ariadne::BaseComponent.new(tag: tag, classes: classes, attributes: attributes)
|
36
|
+
}
|
37
|
+
|
38
|
+
# The tab's text.
|
39
|
+
#
|
40
|
+
# @param kwargs [Hash] The same arguments as <%= link_to_component(Ariadne::Text) %>.
|
41
|
+
renders_one :text, Ariadne::Text
|
42
|
+
# TODO: test what hapenns with really long inbox names
|
43
|
+
|
44
|
+
attr_reader :selected, :id, :classes, :attributes
|
45
|
+
|
46
|
+
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"
|
47
|
+
|
48
|
+
# @example Default
|
49
|
+
#
|
50
|
+
# <%= render(Ariadne::TabComponent.new(id: "pub-comment")) do |tab| %>
|
51
|
+
# <% tab.text { "Public comment" } %>
|
52
|
+
# <% tab.panel { "Public comment panel" } %>
|
53
|
+
# <% end %>
|
54
|
+
# @param id [String] The unique ID of the tab.
|
55
|
+
# @param selected [Boolean] Whether the tab is selected or not.
|
56
|
+
# @param with_panel [Boolean] Whether the Tab has an associated panel.
|
57
|
+
# @param href [String] The link to navigate to when the tab is clicked.
|
58
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
59
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
60
|
+
def initialize(id: nil, selected: false, with_panel: false, href: nil, classes: "", attributes: {})
|
61
|
+
@id = id
|
62
|
+
|
63
|
+
@href = href
|
64
|
+
@selected = selected
|
65
|
+
|
66
|
+
@classes = merge_class_names(
|
67
|
+
DEFAULT_CLASSES,
|
68
|
+
classes,
|
69
|
+
)
|
70
|
+
|
71
|
+
@attributes = attributes
|
72
|
+
@attributes[:id] ||= @id
|
73
|
+
|
74
|
+
if with_panel # TODO: test
|
75
|
+
if @id.blank?
|
76
|
+
raise ArgumentError, "Tabs with panels must have a unique `id`"
|
77
|
+
end
|
78
|
+
|
79
|
+
@tag = :button
|
80
|
+
@attributes[:type] = :button
|
81
|
+
@attributes[:role] = :tab
|
82
|
+
@panel_id = "panel-#{@id}"
|
83
|
+
@attributes[:"aria-controls"] = @panel_id
|
84
|
+
else
|
85
|
+
@tag = :a
|
86
|
+
end
|
87
|
+
|
88
|
+
return unless @selected
|
89
|
+
|
90
|
+
if @tag == :a
|
91
|
+
aria_current = aria("current", attributes) || DEFAULT_ARIA_CURRENT_FOR_ANCHOR
|
92
|
+
@attributes[:"aria-current"] = fetch_or_raise(ARIA_CURRENT_OPTIONS_FOR_ANCHOR, aria_current)
|
93
|
+
else
|
94
|
+
@attributes[:"aria-selected"] = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
import '@github/tab-container-element';
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import '@github/tab-container-element';
|
2
|
+
// keep in sync with tab_container_component.rb
|
3
|
+
const DEFAULT_SELECTED_CLASSES = ['ariadne-border-slate-500', 'ariadne-text-slate-600'];
|
4
|
+
const DEFAULT_UNSELECTED_CLASSES = [
|
5
|
+
'ariadne-text-gray-500',
|
6
|
+
'hover:ariadne-text-gray-700',
|
7
|
+
'hover:ariadne-border-gray-300',
|
8
|
+
];
|
9
|
+
for (const tabContainer of document.getElementsByTagName('tab-container')) {
|
10
|
+
tabContainer.addEventListener('tab-container-change', function (event) {
|
11
|
+
var _a;
|
12
|
+
const newPanel = event.detail.relatedTarget;
|
13
|
+
const tabContainer = newPanel.closest('tab-container');
|
14
|
+
const tabList = tabContainer.firstElementChild;
|
15
|
+
const currentTab = tabList.querySelector('[aria-selected="true"]');
|
16
|
+
const tabId = (_a = newPanel.getAttribute('id')) === null || _a === void 0 ? void 0 : _a.split('-').slice(1).join('-');
|
17
|
+
const newTab = tabList.querySelector(`#${tabId}`);
|
18
|
+
currentTab.classList.remove(...DEFAULT_SELECTED_CLASSES);
|
19
|
+
currentTab.classList.add(...DEFAULT_UNSELECTED_CLASSES);
|
20
|
+
newTab.classList.add(...DEFAULT_SELECTED_CLASSES);
|
21
|
+
newTab.classList.remove(...DEFAULT_UNSELECTED_CLASSES);
|
22
|
+
});
|
23
|
+
}
|
@@ -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-slate-500', 'ariadne-text-slate-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,10 @@
|
|
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
|
+
<%= tab.panel %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
@@ -0,0 +1,68 @@
|
|
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-slate-500 ariadne-text-slate-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 = merge_class_names(selected ? DEFAULT_SELECTED_CLASSES : DEFAULT_UNSELECTED_CLASSES, classes)
|
23
|
+
|
24
|
+
Ariadne::TabComponent.new(
|
25
|
+
id: id,
|
26
|
+
selected: selected,
|
27
|
+
with_panel: true,
|
28
|
+
|
29
|
+
classes: actual_classes,
|
30
|
+
attributes: attributes,
|
31
|
+
)
|
32
|
+
}
|
33
|
+
|
34
|
+
# @example Default
|
35
|
+
#
|
36
|
+
# <%= render(Ariadne::TabContainerComponent.new(sr_label: "Comments")) do |tab_container| %>
|
37
|
+
# <%= render(Ariadne::TabComponent.new(id: "pub-comment")) do |tab| %>
|
38
|
+
# <% tab.with_text { "Tab 1" } %>
|
39
|
+
# <% tab.with_panel { "Panel 1" } %>
|
40
|
+
# <% end %>
|
41
|
+
# <%= render(Ariadne::TabComponent.new(id: "pub-comment")) do |tab| %>
|
42
|
+
# <% tab.with_text { "Tab 2" } %>
|
43
|
+
# <% tab.with_panel { "Panel 2" } %>
|
44
|
+
# <% end %>
|
45
|
+
# <% end %>
|
46
|
+
#
|
47
|
+
# %>
|
48
|
+
#
|
49
|
+
# @param sr_label [String] Sets an `aria-label` that helps assistive technology users understand the purpose of the tabs.
|
50
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
51
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
52
|
+
def initialize(sr_label:, classes: "", attributes: {})
|
53
|
+
@tag = DEFAULT_TAG
|
54
|
+
@classes = merge_class_names(
|
55
|
+
DEFAULT_CLASSES,
|
56
|
+
classes,
|
57
|
+
)
|
58
|
+
|
59
|
+
@attributes = attributes
|
60
|
+
@attributes[:"aria-label"] = sr_label
|
61
|
+
@attributes[:role] = :presentation
|
62
|
+
end
|
63
|
+
|
64
|
+
def render?
|
65
|
+
tabs?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -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
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus';
|
2
|
+
class TabNavComponent extends Controller {
|
3
|
+
constructor() {
|
4
|
+
super(...arguments);
|
5
|
+
// keep in synch with tab_nav_component
|
6
|
+
this.SELECTED_CLASSES = ['ariadne-border-slate-500', 'ariadne-text-slate-600'];
|
7
|
+
this.UNSELECTED_CLASSES = ['ariadne-text-gray-500', 'hover:ariadne-text-gray-700', 'hover:ariadne-border-gray-300'];
|
8
|
+
}
|
9
|
+
connect() {
|
10
|
+
for (const tab of this.tabTargets) {
|
11
|
+
if (tab.hasAttribute('aria-current')) {
|
12
|
+
tab.classList.add(...this.SELECTED_CLASSES);
|
13
|
+
tab.classList.remove(...this.UNSELECTED_CLASSES);
|
14
|
+
}
|
15
|
+
}
|
16
|
+
}
|
17
|
+
toggle(event) {
|
18
|
+
for (const tab of this.tabTargets) {
|
19
|
+
if (tab === event.target) {
|
20
|
+
tab.setAttribute('aria-current', 'page');
|
21
|
+
tab.classList.add(...this.SELECTED_CLASSES);
|
22
|
+
tab.classList.remove(...this.UNSELECTED_CLASSES);
|
23
|
+
}
|
24
|
+
else if (tab.hasAttribute('aria-current')) {
|
25
|
+
tab.removeAttribute('aria-current');
|
26
|
+
tab.classList.add(...this.UNSELECTED_CLASSES);
|
27
|
+
tab.classList.remove(...this.SELECTED_CLASSES);
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
TabNavComponent.targets = ['tab'];
|
33
|
+
export default TabNavComponent;
|
@@ -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-slate-500', 'ariadne-text-slate-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
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Use `TabNavComponent` to style navigation links, typically placed at the top
|
5
|
+
# of the page.
|
6
|
+
#
|
7
|
+
# For panel navigation, use <%= link_to_component(Ariadne::TabContainerComponent) %> instead.
|
8
|
+
#
|
9
|
+
# @accessibility
|
10
|
+
# - By default, `TabNavComponent` renders links within a `<nav>` element. `<nav>` has an
|
11
|
+
# implicit landmark role of `navigation` which should be reserved for main links.
|
12
|
+
# For all other set of links, set tag to `:div`.
|
13
|
+
# - See <%= link_to_component(Ariadne::TabComponent) %> for additional
|
14
|
+
# accessibility considerations.
|
15
|
+
class TabNavComponent < Ariadne::Component
|
16
|
+
DEFAULT_TAG = :nav
|
17
|
+
TAG_OPTIONS = [DEFAULT_TAG, :div].freeze
|
18
|
+
|
19
|
+
BODY_TAG_DEFAULT = :ul
|
20
|
+
|
21
|
+
# keep this consistent with tab-container-component.ts
|
22
|
+
SELECTED_CLASSES = "ariadne-border-slate-600 ariadne-text-slate-700"
|
23
|
+
UNSELECTED_CLASSES = "ariadne-text-slate-500 hover:ariadne-text-slate-700 hover:ariadne-border-slate-700"
|
24
|
+
|
25
|
+
DEFAULT_CLASSES = ""
|
26
|
+
|
27
|
+
# Tabs to be rendered. Use the tabs to list page links.
|
28
|
+
#
|
29
|
+
# @param selected [Boolean] Whether the tab is selected.
|
30
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
31
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
32
|
+
renders_many :tabs, lambda { |selected: false, href: nil, classes: "", attributes: {}|
|
33
|
+
attributes[:href] = href
|
34
|
+
attributes[:"data-tab-nav-component-target"] = "tab"
|
35
|
+
attributes[:"data-action"] = "click->tab-nav-component#toggle"
|
36
|
+
|
37
|
+
actual_classes = merge_class_names(selected ? SELECTED_CLASSES : UNSELECTED_CLASSES, classes)
|
38
|
+
Ariadne::TabComponent.new(
|
39
|
+
selected: selected,
|
40
|
+
|
41
|
+
classes: actual_classes,
|
42
|
+
attributes: attributes,
|
43
|
+
)
|
44
|
+
}
|
45
|
+
|
46
|
+
# @example Default with `<nav>`
|
47
|
+
# @description
|
48
|
+
# `<nav>` is a landmark and should be reserved for main navigation links. See <%= link_to_accessibility %>.
|
49
|
+
# @code
|
50
|
+
# <%= render(Ariadne::TabNavComponent.new(label: "Nav")) do |c| %>
|
51
|
+
# <% c.with_tab(selected: true, href: "#") { "Tab 1" } %>
|
52
|
+
# <% c.with_tab(href: "#") { "Tab 2" } %>
|
53
|
+
# <% c.with_tab(href: "#") { "Tab 3" } %>
|
54
|
+
# <% end %>
|
55
|
+
#
|
56
|
+
# @param label [String] Sets an `aria-label` that helps assistive technology users understand the purpose of the links, and distinguish it from similar elements.
|
57
|
+
# @param tag [Symbol, String] The rendered tag name.
|
58
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
59
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
60
|
+
def initialize(label:, tag: DEFAULT_TAG, classes: "", attributes: {})
|
61
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
62
|
+
@classes = merge_class_names(
|
63
|
+
DEFAULT_CLASSES,
|
64
|
+
classes,
|
65
|
+
)
|
66
|
+
|
67
|
+
@attributes = attributes
|
68
|
+
@attributes[:"aria-label"] = label
|
69
|
+
@attributes[:"data-controller"] = "tab-nav-component"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<div class="ariadne-inline-block ariadne-min-w-full ariadne-align-middle md:ariadne-px-6 lg:ariadne-px-8">
|
2
|
+
<div class="ariadne-overflow-hidden ariadne-shadow ariadne-ring-1 ariadne-ring-black ariadne-ring-opacity-5 md:ariadne-rounded-lg">
|
3
|
+
<%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do |table| %>
|
4
|
+
<% if has_header_row? %>
|
5
|
+
<thead>
|
6
|
+
<%= render(Ariadne::BaseComponent.new(tag: :tr, classes: header_row.classes, attributes: header_row.attributes)) do %>
|
7
|
+
<%= header_row.selection_cell %>
|
8
|
+
<%= header_row.main_cell %>
|
9
|
+
<% header_row.action_cells.each_with_index do |action_cell, idx| %>
|
10
|
+
<% if idx.zero? %>
|
11
|
+
<%= action_cell %>
|
12
|
+
<% elsif idx == header_row.action_cells.size - 1 %>
|
13
|
+
<%= action_cell %>
|
14
|
+
<% else %>
|
15
|
+
<%= action_cell %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
</thead>
|
20
|
+
<% end %>
|
21
|
+
<tbody>
|
22
|
+
<% rows.each do |row| %>
|
23
|
+
<%= render(Ariadne::BaseComponent.new(tag: :tr, classes: row.classes, attributes: row.attributes)) do %>
|
24
|
+
<%= row.selection_cell %>
|
25
|
+
<%= row.main_cell %>
|
26
|
+
<% row.metadata_cells.each_with_index do |metadata_cell, idx| %>
|
27
|
+
<% if idx.zero? %>
|
28
|
+
<%= metadata_cell %>
|
29
|
+
<% elsif idx == row.metadata_cells.size - 1 %>
|
30
|
+
<%= metadata_cell %>
|
31
|
+
<% else %>
|
32
|
+
<%= metadata_cell %>
|
33
|
+
<% end %>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
<% end %>
|
37
|
+
</tbody>
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
<% if has_footer? %>
|
41
|
+
<%= render(Ariadne::BaseComponent.new(tag: :div, classes: footer.classes, attributes: footer.attributes)) do %>
|
42
|
+
<%= footer.records_info %>
|
43
|
+
<%= render(Ariadne::BaseComponent.new(tag: :nav, classes: footer.pagination_bar.classes, attributes: footer.pagination_bar.attributes)) do %>
|
44
|
+
<%= footer.pagination_bar.prev_page %>
|
45
|
+
<% footer.pagination_bar.items.each do |item| %>
|
46
|
+
<%= item %>
|
47
|
+
<% end %>
|
48
|
+
<%= footer.pagination_bar.next_page %>
|
49
|
+
<% end %>
|
50
|
+
<% end %>
|
51
|
+
<% end %>
|
52
|
+
</div>
|