ariadne_view_components 0.0.10-arm64-darwin

Sign up to get free protection for your applications and to get access to all the features.
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/arm64-darwin/tailwindcss +0 -0
  98. data/exe/tailwindcss +21 -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 %>