ariadne_view_components 0.0.10-x86_64-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.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +49 -0
  3. data/README.md +73 -0
  4. data/app/assets/config/manifest.js +2 -0
  5. data/app/assets/javascripts/ariadne-form-with.d.ts +20 -0
  6. data/app/assets/javascripts/ariadne-form.d.ts +22 -0
  7. data/app/assets/javascripts/ariadne.d.ts +1 -0
  8. data/app/assets/javascripts/ariadne_view_components.js +8 -0
  9. data/app/assets/javascripts/ariadne_view_components.js.map +1 -0
  10. data/app/assets/javascripts/clipboard-copy-component.d.ts +4 -0
  11. data/app/assets/javascripts/comment-component.d.ts +13 -0
  12. data/app/assets/javascripts/rich-text-area-component.d.ts +4 -0
  13. data/app/assets/javascripts/slideover-component.d.ts +9 -0
  14. data/app/assets/javascripts/time-ago-component.d.ts +1 -0
  15. data/app/assets/javascripts/time_ago_component.d.ts +1 -0
  16. data/app/assets/javascripts/tooltip-component.d.ts +24 -0
  17. data/app/assets/stylesheets/ariadne_view_components.css +6 -0
  18. data/app/assets/stylesheets/prosemirror.css +323 -0
  19. data/app/assets/stylesheets/tooltip-component.css +37 -0
  20. data/app/components/ariadne/ariadne-form.ts +96 -0
  21. data/app/components/ariadne/ariadne.ts +20 -0
  22. data/app/components/ariadne/base_button.rb +61 -0
  23. data/app/components/ariadne/base_component.rb +37 -0
  24. data/app/components/ariadne/blankslate_component.html.erb +26 -0
  25. data/app/components/ariadne/blankslate_component.rb +146 -0
  26. data/app/components/ariadne/body_component.rb +30 -0
  27. data/app/components/ariadne/button_component.html.erb +4 -0
  28. data/app/components/ariadne/button_component.rb +157 -0
  29. data/app/components/ariadne/clipboard-copy-component.ts +19 -0
  30. data/app/components/ariadne/clipboard_copy_component.html.erb +9 -0
  31. data/app/components/ariadne/clipboard_copy_component.rb +90 -0
  32. data/app/components/ariadne/comment-component.ts +55 -0
  33. data/app/components/ariadne/comment_component.html.erb +22 -0
  34. data/app/components/ariadne/comment_component.rb +57 -0
  35. data/app/components/ariadne/component.rb +128 -0
  36. data/app/components/ariadne/container_component.html.erb +3 -0
  37. data/app/components/ariadne/container_component.rb +25 -0
  38. data/app/components/ariadne/content.rb +12 -0
  39. data/app/components/ariadne/counter_component.rb +100 -0
  40. data/app/components/ariadne/flash_component.html.erb +31 -0
  41. data/app/components/ariadne/flash_component.rb +125 -0
  42. data/app/components/ariadne/flex_component.rb +49 -0
  43. data/app/components/ariadne/footer_component.html.erb +7 -0
  44. data/app/components/ariadne/footer_component.rb +23 -0
  45. data/app/components/ariadne/grid_component.html.erb +26 -0
  46. data/app/components/ariadne/grid_component.rb +66 -0
  47. data/app/components/ariadne/header_component.html.erb +29 -0
  48. data/app/components/ariadne/header_component.rb +114 -0
  49. data/app/components/ariadne/heading_component.rb +49 -0
  50. data/app/components/ariadne/heroicon_component.html.erb +4 -0
  51. data/app/components/ariadne/heroicon_component.rb +129 -0
  52. data/app/components/ariadne/image_component.rb +53 -0
  53. data/app/components/ariadne/inline_flex_component.html.erb +5 -0
  54. data/app/components/ariadne/inline_flex_component.rb +65 -0
  55. data/app/components/ariadne/link_component.rb +65 -0
  56. data/app/components/ariadne/list_component.html.erb +15 -0
  57. data/app/components/ariadne/list_component.rb +68 -0
  58. data/app/components/ariadne/main_component.rb +32 -0
  59. data/app/components/ariadne/narrow_container_component.html.erb +3 -0
  60. data/app/components/ariadne/narrow_container_component.rb +30 -0
  61. data/app/components/ariadne/panel_bar_component.html.erb +20 -0
  62. data/app/components/ariadne/panel_bar_component.rb +79 -0
  63. data/app/components/ariadne/pill_component.html.erb +3 -0
  64. data/app/components/ariadne/pill_component.rb +30 -0
  65. data/app/components/ariadne/rich-text-area-component.ts +32 -0
  66. data/app/components/ariadne/rich_text_area_component.html.erb +6 -0
  67. data/app/components/ariadne/rich_text_area_component.rb +35 -0
  68. data/app/components/ariadne/slideover-component.ts +26 -0
  69. data/app/components/ariadne/slideover_component.html.erb +11 -0
  70. data/app/components/ariadne/slideover_component.rb +81 -0
  71. data/app/components/ariadne/tab_bar_component.html.erb +3 -0
  72. data/app/components/ariadne/tab_bar_component.rb +45 -0
  73. data/app/components/ariadne/tab_component.html.erb +7 -0
  74. data/app/components/ariadne/tab_component.rb +43 -0
  75. data/app/components/ariadne/text.rb +25 -0
  76. data/app/components/ariadne/time-ago-component.ts +1 -0
  77. data/app/components/ariadne/time_ago_component.rb +56 -0
  78. data/app/components/ariadne/timeline_component.html.erb +19 -0
  79. data/app/components/ariadne/timeline_component.rb +34 -0
  80. data/app/components/ariadne/tooltip-component.ts +57 -0
  81. data/app/components/ariadne/tooltip_component.html.erb +4 -0
  82. data/app/components/ariadne/tooltip_component.rb +108 -0
  83. data/app/lib/ariadne/action_view_extensions/form_helper.rb +26 -0
  84. data/app/lib/ariadne/audited/dsl.rb +32 -0
  85. data/app/lib/ariadne/class_name_helper.rb +22 -0
  86. data/app/lib/ariadne/fetch_or_fallback_helper.rb +102 -0
  87. data/app/lib/ariadne/form_builder.rb +71 -0
  88. data/app/lib/ariadne/icon_helper.rb +47 -0
  89. data/app/lib/ariadne/join_style_arguments_helper.rb +14 -0
  90. data/app/lib/ariadne/logger_helper.rb +23 -0
  91. data/app/lib/ariadne/status/dsl.rb +41 -0
  92. data/app/lib/ariadne/tab_nav_helper.rb +35 -0
  93. data/app/lib/ariadne/tabbed_component_helper.rb +39 -0
  94. data/app/lib/ariadne/test_selector_helper.rb +20 -0
  95. data/app/lib/ariadne/underline_nav_helper.rb +44 -0
  96. data/app/lib/ariadne/view_helper.rb +22 -0
  97. data/exe/tailwindcss +21 -0
  98. data/exe/x86_64-linux/tailwindcss +0 -0
  99. data/lib/ariadne/view_components/commands.rb +90 -0
  100. data/lib/ariadne/view_components/constants.rb +53 -0
  101. data/lib/ariadne/view_components/engine.rb +75 -0
  102. data/lib/ariadne/view_components/linters.rb +3 -0
  103. data/lib/ariadne/view_components/statuses.rb +14 -0
  104. data/lib/ariadne/view_components/upstream.rb +20 -0
  105. data/lib/ariadne/view_components/version.rb +7 -0
  106. data/lib/ariadne/view_components.rb +61 -0
  107. data/lib/rubocop/config/default.yml +8 -0
  108. data/lib/rubocop/cop/ariadne/base_cop.rb +26 -0
  109. data/lib/rubocop/cop/ariadne/no_tag_memoize.rb +44 -0
  110. data/lib/rubocop/cop/ariadne.rb +3 -0
  111. data/lib/tasks/ariadne_view_components.rake +48 -0
  112. data/lib/tasks/build.rake +30 -0
  113. data/lib/tasks/coverage.rake +19 -0
  114. data/lib/tasks/custom_utilities.yml +310 -0
  115. data/lib/tasks/docs.rake +524 -0
  116. data/lib/tasks/helpers/ast_processor.rb +44 -0
  117. data/lib/tasks/helpers/ast_traverser.rb +77 -0
  118. data/lib/tasks/static.rake +15 -0
  119. data/lib/yard/docs_helper.rb +83 -0
  120. data/lib/yard/renders_many_handler.rb +19 -0
  121. data/lib/yard/renders_one_handler.rb +19 -0
  122. data/static/arguments.yml +619 -0
  123. data/static/assets/view-components.svg +18 -0
  124. data/static/audited_at.json +38 -0
  125. data/static/classes.yml +291 -0
  126. data/static/constants.json +426 -0
  127. data/static/statuses.json +38 -0
  128. data/static/tailwindcss.yml +727 -0
  129. data/tailwind.config.js +65 -0
  130. metadata +264 -0
@@ -0,0 +1,79 @@
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 PanelBarComponent < Ariadne::Component
8
+ DEFAULT_TAG = :ol
9
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
10
+
11
+ DEFAULT_CLASSES = "ariadne-border ariadne-border-gray-300 ariadne-rounded-md ariadne-divide-y ariadne-divide-gray-300 md:ariadne-flex md:ariadne-divide-y-0"
12
+
13
+ renders_many :panels, "PanelItem"
14
+
15
+ # @example Default
16
+ #
17
+ # <%= render(Ariadne::PanelBarComponent.new) { "Example" } %>
18
+ #
19
+ # @param tag [Symbol, String] The rendered tag name.
20
+ # @param classes [String] <%= link_to_classes_docs %>
21
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
22
+ def initialize(classes: "", attributes: {})
23
+ @tag = DEFAULT_TAG
24
+ @classes = class_names(
25
+ DEFAULT_CLASSES,
26
+ classes
27
+ )
28
+
29
+ @attributes = attributes
30
+ @attributes[:role] ||= "list"
31
+ end
32
+
33
+ # def render?
34
+ # items.any?
35
+ # end
36
+
37
+ # This component is part of `PanelBarComponent` and should not be
38
+ # used as a standalone component.
39
+ class PanelItem < Ariadne::Component
40
+ DEFAULT_ITEM_CLASSES = "ariadne-relative md:ariadne-flex-1 md:ariadne-flex"
41
+ DEFAULT_WRAPPER_CLASSES = "group ariadne-flex ariadne-items-center ariadne-w-full"
42
+
43
+ renders_one :icon, lambda { |static_content = nil, &block|
44
+ next static_content if static_content.present?
45
+
46
+ view_context.capture { block&.call }
47
+ }
48
+
49
+ renders_one :label, lambda { |static_content = nil, &block|
50
+ next static_content if static_content.present?
51
+
52
+ view_context.capture { block&.call }
53
+ }
54
+
55
+ attr_reader :link, :classes, :attributes
56
+
57
+ def initialize(link: {}, classes: "", attributes: {})
58
+ @link = link
59
+ if @link.present?
60
+ @link["classes"] = class_names(DEFAULT_WRAPPER_CLASSES, @link["classes"])
61
+ end
62
+ @classes = class_names(DEFAULT_ITEM_CLASSES, classes)
63
+ @attributes = attributes
64
+ end
65
+
66
+ def selected?
67
+ @selected
68
+ end
69
+
70
+ def linked?
71
+ @link.present?
72
+ end
73
+
74
+ def call
75
+ Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Creates a ariadne-rounded label that resembles a medicine pill.
5
+ class PillComponent < Ariadne::Component
6
+ DEFAULT_TAG = :span
7
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
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"
10
+
11
+ # @example Default
12
+ #
13
+ # <%= render(Ariadne::PillComponent.new(color: "FF0000")) { "Admin" } %>
14
+ #
15
+ # @param tag [Symbol, String] The rendered tag name.
16
+ # @param color [String] The hex color of the pill.
17
+ # @param classes [String] <%= link_to_classes_docs %>
18
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
19
+ def initialize(tag: DEFAULT_TAG, color:, classes: "", attributes: {})
20
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
21
+ @classes = class_names(
22
+ DEFAULT_CLASSES,
23
+ classes
24
+ )
25
+
26
+ @attributes = attributes
27
+ @attributes["style"] = "background-color: ##{color};"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+
3
+ import {Editor} from '@tiptap/core'
4
+ import {StarterKit} from '@tiptap/starter-kit'
5
+
6
+ export default class RichTextArea extends Controller {
7
+ connect() {
8
+ const editorElement = document.querySelector('.tiptap-editor')
9
+ if (editorElement) {
10
+ new Editor({
11
+ element: editorElement,
12
+ extensions: [StarterKit],
13
+ content: 'Hello World!',
14
+ editorProps: {
15
+ attributes: {
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'
18
+ }
19
+ }
20
+ })
21
+
22
+ const tiptapValueContainer = document.querySelector('input[data-tiptap-value-container=true]')
23
+ if (tiptapValueContainer) {
24
+ const parentForm = editorElement.closest('form')
25
+
26
+ parentForm?.addEventListener('submit', () => {
27
+ tiptapValueContainer.setAttribute('value', editorElement.textContent || '')
28
+ })
29
+ }
30
+ }
31
+ }
32
+ }
@@ -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
+ <textarea class="tiptap-editor"></textarea>
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 = "ariadne-block ariadne-w-full ariadne-border-0 ariadne-py-3 focus:ariadne-ring-0 sm:ariadne-text-sm"
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 = 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,26 @@
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
+ this.expandWrapperTarget.classList.toggle('bg-filter-panel')
14
+ for (const slidePanel of this.slidePanelTargets) {
15
+ slidePanel.classList.toggle('ariadne-hidden')
16
+ }
17
+ this.buttonWrapperTarget.classList.toggle('bg-filter-panel')
18
+ if (document.getElementById('btnClose')?.classList.contains('ariadne-hidden')) {
19
+ const formID = this.buttonWrapperTarget.getAttribute('data-slideover-component-form-id')
20
+ if (formID) {
21
+ const form = <HTMLFormElement>document.getElementById(formID)
22
+ form?.submit()
23
+ }
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,11 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |slideover| %>
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">
4
+ <%= content %>
5
+ </div>
6
+ <div data-slideover-component-target="buttonWrapper" data-slideover-component-form-id="<%= @form_id %>" class="ariadne-relative ariadne-flex ariadne-justify-center">
7
+ <%= open_button %>
8
+ <%= close_button %>
9
+ </div>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,81 @@
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 = ""
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 { |label:, classes: "", attributes: {}, text_classes: ""|
19
+ actual_classes = class_names(DEFAULT_BUTTON_CLASSES, classes)
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
+ icon = case @direction
27
+ when DIRECTION_Y_DOWN
28
+ :"chevron-double-down"
29
+ end
30
+
31
+ render(Ariadne::BaseComponent.new(tag: :button, classes: actual_classes, attributes: attributes)) do
32
+ render(Ariadne::HeroiconComponent.new(icon: icon, size: :sm, variant: HeroiconsHelper::Icon::VARIANT_SOLID, attributes: { "data-slideover-component-target" => "slide-panel" }, text_classes: text_classes)) { label }
33
+ end
34
+ }
35
+
36
+ renders_one :close_button, lambda { |label:, classes: "", attributes: {}, text_classes: ""|
37
+ actual_classes = class_names(DEFAULT_BUTTON_CLASSES, classes)
38
+
39
+ attributes[:id] ||= "btnClose"
40
+ attributes[:"data-slideover-component-target"] ||= "slidePanel"
41
+ attributes[:"data-action"] ||= "click->slideover-component#toggle"
42
+ attributes[:type] ||= "submit"
43
+
44
+ icon = case @direction
45
+ when DIRECTION_Y_DOWN
46
+ :"chevron-double-up"
47
+ end
48
+
49
+ render(Ariadne::BaseComponent.new(tag: :button, classes: actual_classes, attributes: attributes)) do
50
+ render(Ariadne::HeroiconComponent.new(icon: icon, size: :sm, variant: HeroiconsHelper::Icon::VARIANT_SOLID, attributes: { "data-slideover-component-target" => "slide-panel" }, text_classes: text_classes)) { label }
51
+ end
52
+ }
53
+
54
+ # @example Default
55
+ #
56
+ # <%= render(Ariadne::SlideoverComponent.new(direction: Ariadne::SlideoverComponent::DIRECTION_Y_DOWN)) { "Example" } %>
57
+ #
58
+ # @param tag [Symbol, String] The rendered tag name.
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.
61
+ # @param open_text [String] The text to use to open the slideover.
62
+ # @param close_text [String] The text to use to close the slideover.
63
+ # @param classes [String] <%= link_to_classes_docs %>
64
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
65
+ def initialize(tag: DEFAULT_TAG, direction:, form_id: nil, open_text: "Open", close_text: "Close", classes: "", attributes: {})
66
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
67
+ @classes = class_names(
68
+ DEFAULT_CLASSES,
69
+ classes
70
+ )
71
+
72
+ @direction = fetch_or_raise(VALID_DIRECTIONS, direction)
73
+ @form_id = form_id
74
+
75
+ @open_text = open_text
76
+ @close_text = close_text
77
+ @attributes = attributes
78
+ @attributes[:"data-controller"] = "slideover-component"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # A container for a row of tags.
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 TabBarComponent < Ariadne::Component
8
+ DEFAULT_TAG = :nav
9
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
10
+
11
+ DEFAULT_CLASSES = "ariadne--mb-px ariadne-flex ariadne-space-x-8 ariadne-bg-transparent"
12
+
13
+ # Tabs to be rendered. For more information, refer to <%= link_to_component(Ariadne::TabComponent) %>.
14
+ #
15
+ # @param selected [Boolean] Whether the tab is selected.
16
+ # @param classes [String] <%= link_to_classes_docs %>
17
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
18
+ renders_many :tabs, lambda { |selected: false, classes: "", attributes: {}|
19
+ Ariadne::TabComponent.new(
20
+ selected: selected,
21
+ classes: classes,
22
+ attributes: attributes
23
+ )
24
+ }
25
+
26
+ # @example Default
27
+ #
28
+ # <%= render(Ariadne::TabBarComponent.new(sr_label: "Navigation tabs")) { "Example" } %>
29
+ #
30
+ # @param tag [Symbol, String] The rendered tag name.
31
+ # @param sr_label [String] A label to introduce these tabs for screen readers.
32
+ # @param classes [String] <%= link_to_classes_docs %>
33
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
34
+ def initialize(tag: DEFAULT_TAG, sr_label:, classes: "", attributes: {})
35
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
36
+ @classes = class_names(
37
+ DEFAULT_CLASSES,
38
+ classes
39
+ )
40
+ @sr_label = sr_label
41
+ @attributes = attributes
42
+ @attributes[:"aria-label"] ||= "Tabs"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,7 @@
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 %>
7
+ <% end %>
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Represents a sequence of tabs which, when clicked, swap between panel contents.
5
+ class TabComponent < Ariadne::Component
6
+ DEFAULT_TAG = :button
7
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
8
+
9
+ # The Tab's text.
10
+ #
11
+ # @param kwargs [Hash] The same arguments as <%= link_to_component(Ariadne::Text) %>.
12
+ renders_one :text, Ariadne::Text
13
+
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"
17
+
18
+ BASE_TAB_CLASSES = "ariadne-whitespace-nowrap ariadne-py-4 ariadne-px-1 ariadne-border-b-2 ariadne-font-medium ariadne-text-sm"
19
+
20
+ # @example Default
21
+ #
22
+ # <%= render(Ariadne::TabComponent.new) { "Example" } %>
23
+ #
24
+ # @param tag [Symbol, String] The rendered tag name.
25
+ # @param selected [Boolean] Whether the tab is selected or not.
26
+ # @param classes [String] <%= link_to_classes_docs %>
27
+ # @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)
30
+ @classes = class_names(
31
+ DEFAULT_CLASSES,
32
+ classes
33
+ )
34
+
35
+ @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)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # `Text` is a wrapper component that will apply typography styles to the text inside.
5
+ class Text < Ariadne::Component
6
+ DEFAULT_TAG = :span
7
+
8
+ # @example Default
9
+ # <%= render(Ariadne::Text.new(tag: :p, attributes: { font_weight: :bold })) { "Bold Text" } %>
10
+ # <%= render(Ariadne::Text.new(tag: :p, attributes: { color: :danger })) { "Danger Text" } %>
11
+ #
12
+ # @param tag [Symbol, String] The rendered tag name
13
+ # @param classes [String] <%= link_to_classes_docs %>
14
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
15
+ def initialize(tag: DEFAULT_TAG, classes: "", attributes: {})
16
+ @tag = tag
17
+ @classes = classes
18
+ @attributes = attributes
19
+ end
20
+
21
+ def call
22
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) { content }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1 @@
1
+ import '@github/time-elements'
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Displays a time ariadne-relative to how long ago it was. This component requires JavaScript.
5
+ class TimeAgoComponent < Ariadne::Component
6
+ DEFAULT_TAG = :"time-ago"
7
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
8
+
9
+ DEFAULT_CLASSES = "ariadne-whitespace-nowrap"
10
+
11
+ # @example Default
12
+ #
13
+ # <%= render(Ariadne::TimeAgoComponent.new(time: Time.now)) %>
14
+ #
15
+ # @param tag [Symbol, String] The rendered tag name.
16
+ # @param time [Time] The time to be formatted
17
+ # @param micro [Boolean] If true then the text will be formatted in "micro" mode, using as few characters as possible
18
+ # @param classes [String] <%%= link_to_classes_docs %>
19
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
20
+ def initialize(tag: DEFAULT_TAG, time:, micro: false, classes: "", attributes: {})
21
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
22
+ @classes = class_names(
23
+ DEFAULT_CLASSES,
24
+ classes
25
+ )
26
+
27
+ @time = time
28
+ @micro = micro
29
+ @attributes = attributes
30
+ @attributes[:datetime] = @time.utc.iso8601
31
+ @attributes[:format] = "micro" if @micro
32
+ end
33
+
34
+ def call
35
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) { time_in_words }
36
+ end
37
+
38
+ private def time_in_words
39
+ return @time.in_time_zone.strftime("%b %-d, %Y") unless @micro
40
+
41
+ seconds_ago = Time.current - @time
42
+
43
+ if seconds_ago < 1.minute
44
+ "1m"
45
+ elsif seconds_ago >= 1.minute && seconds_ago < 1.hour
46
+ "#{(seconds_ago / 60).floor}m"
47
+ elsif seconds_ago >= 1.hour && seconds_ago < 1.day
48
+ "#{(seconds_ago / 60 / 60).floor}h"
49
+ elsif seconds_ago >= 1.day && seconds_ago < 1.year
50
+ "#{(seconds_ago / 60 / 60 / 24).floor}d"
51
+ elsif seconds_ago >= 1.year
52
+ "#{(seconds_ago / 60 / 60 / 24 / 365).floor}y"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do %>
2
+ <div>
3
+ <div class="ariadne-divide-y ariadne-divide-gray-200">
4
+ <div class="ariadne-pb-4">
5
+ <h2 id="activity-title" class="ariadne-text-lg ariadne-font-medium ariadne-text-gray-900">Timeline</h2>
6
+ </div>
7
+ <div class="ariadne-pt-6">
8
+ <!-- Activity feed-->
9
+ <div class="ariadne-flow-root">
10
+ <ul role="list" class="ariadne--mb-8">
11
+ <% items.each do %>
12
+ <%= items %>
13
+ <% end %>
14
+ </ul>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ <% end %>
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Represents a linear timeline of events. Typically, this is shown
5
+ # as part of the Conversation component.
6
+ class TimelineComponent < Ariadne::Component
7
+ DEFAULT_TAG = :div
8
+ DEFAULT_CLASSES = ""
9
+
10
+ # The sub-items(s) to render
11
+ renders_many :items, lambda { |static_content = nil, &block|
12
+ next static_content if static_content.present?
13
+
14
+ view_context.capture { block&.call }
15
+ }
16
+
17
+ # @example Default
18
+ #
19
+ # <%= render(Ariadne::TimelineComponent.new) { "Example" } %>
20
+ #
21
+ # @param tag [Symbol, String] The rendered tag name
22
+ # @param classes [String] <%= link_to_classes_docs %>
23
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
24
+ def initialize(tag: DEFAULT_TAG, classes: "", attributes: {})
25
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
26
+ @classes = class_names(
27
+ DEFAULT_CLASSES,
28
+ classes
29
+ )
30
+
31
+ @attributes = attributes
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+ import {createPopper} from '@popperjs/core'
3
+ import type {Instance, Placement} from '@popperjs/core'
4
+
5
+ export default class TooltipComponent extends Controller {
6
+ static targets = ['trigger', 'tooltip']
7
+ declare readonly triggerTarget: HTMLElement
8
+ declare readonly tooltipTarget: HTMLElement
9
+
10
+ static values = {
11
+ placement: {type: String, default: 'top'},
12
+ offset: {type: Array, default: [0, 8]}
13
+ }
14
+ declare readonly placementValue: Placement
15
+ declare readonly offsetValue: Array<number>
16
+
17
+ popperInstance: Instance
18
+
19
+ // Create a new Popper instance
20
+ connect() {
21
+ this.popperInstance = createPopper(this.triggerTarget, this.tooltipTarget, {
22
+ placement: this.placementValue,
23
+ modifiers: [
24
+ {
25
+ name: 'offset',
26
+ options: {
27
+ offset: this.offsetValue
28
+ }
29
+ }
30
+ ]
31
+ })
32
+ }
33
+
34
+ // Destroy the Popper instance
35
+ disconnect() {
36
+ if (this.popperInstance) {
37
+ this.popperInstance.destroy()
38
+ }
39
+ }
40
+
41
+ show() {
42
+ this.tooltipTarget.setAttribute('data-tooltip-show', '')
43
+ this.tooltipTarget.classList.remove('ariadne-invisible')
44
+
45
+ // We need to tell Popper to update the tooltip position
46
+ // after we show the tooltip, otherwise it will be incorrect
47
+ this.popperInstance.update()
48
+ this.dispatch('shown', {detail: {trigger: this.triggerTarget, tooltip: this.tooltipTarget}})
49
+ }
50
+
51
+ hide() {
52
+ this.tooltipTarget.removeAttribute('data-tooltip-show')
53
+ this.tooltipTarget.classList.add('ariadne-invisible')
54
+
55
+ this.dispatch('ariadne-hidden', {detail: {trigger: this.triggerTarget, tooltip: this.tooltipTarget}})
56
+ }
57
+ }
@@ -0,0 +1,4 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do %>
2
+ <span><%= @text %></span>
3
+ <span class="tooltip-arrow ariadne-absolute ariadne-block" data-popper-arrow></span>
4
+ <% end %>