ariadne_view_components 0.0.3 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -7
  3. data/app/assets/builds/ariadne_view_components.css +1874 -0
  4. data/app/assets/config/manifest.js +2 -0
  5. data/app/assets/javascripts/ariadne.d.ts +1 -0
  6. data/app/assets/javascripts/ariadne_view_components.js +1 -1
  7. data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
  8. data/app/assets/javascripts/clipboard-copy-component.d.ts +4 -0
  9. data/app/assets/javascripts/slideover-component.d.ts +9 -0
  10. data/app/assets/javascripts/time_ago_component.d.ts +1 -0
  11. data/app/assets/javascripts/tooltip-component.d.ts +24 -0
  12. data/app/assets/stylesheets/application.ariadne_view_components.css +5 -0
  13. data/app/assets/stylesheets/tooltip-component.css +37 -0
  14. data/app/components/ariadne/ariadne.d.ts +1 -0
  15. data/app/components/ariadne/ariadne.js +9 -0
  16. data/app/components/ariadne/ariadne.ts +8 -9
  17. data/app/components/ariadne/base_button.rb +10 -8
  18. data/app/components/ariadne/blankslate_component.html.erb +26 -0
  19. data/app/components/ariadne/blankslate_component.rb +151 -0
  20. data/app/components/ariadne/body_component.rb +30 -0
  21. data/app/components/ariadne/button_component.html.erb +1 -1
  22. data/app/components/ariadne/button_component.rb +9 -11
  23. data/app/components/ariadne/clipboard-copy-component.d.ts +4 -0
  24. data/app/components/ariadne/clipboard-copy-component.js +18 -0
  25. data/app/components/ariadne/{clipboard_copy_component.ts → clipboard-copy-component.ts} +0 -0
  26. data/app/components/ariadne/clipboard_copy_component.d.ts +4 -0
  27. data/app/components/ariadne/clipboard_copy_component.html.erb +2 -2
  28. data/app/components/ariadne/clipboard_copy_component.js +18 -0
  29. data/app/components/ariadne/clipboard_copy_component.rb +45 -5
  30. data/app/components/ariadne/comment_component.html.erb +25 -0
  31. data/app/components/ariadne/comment_component.rb +45 -0
  32. data/app/components/ariadne/component.rb +6 -1
  33. data/app/components/ariadne/container_component.html.erb +3 -0
  34. data/app/components/ariadne/container_component.rb +25 -0
  35. data/app/components/ariadne/flash_component.rb +9 -9
  36. data/app/components/ariadne/flex_component.rb +51 -0
  37. data/app/components/ariadne/footer_component.html.erb +7 -0
  38. data/app/components/ariadne/footer_component.rb +23 -0
  39. data/app/components/ariadne/grid_component.html.erb +26 -0
  40. data/app/components/ariadne/grid_component.rb +66 -0
  41. data/app/components/ariadne/header_component.html.erb +29 -0
  42. data/app/components/ariadne/header_component.rb +114 -0
  43. data/app/components/ariadne/heading_component.rb +3 -3
  44. data/app/components/ariadne/heroicon_component.html.erb +2 -5
  45. data/app/components/ariadne/heroicon_component.rb +20 -7
  46. data/app/components/ariadne/image_component.rb +5 -3
  47. data/app/components/ariadne/inline_flex_component.html.erb +5 -0
  48. data/app/components/ariadne/inline_flex_component.rb +65 -0
  49. data/app/components/ariadne/link_component.rb +65 -0
  50. data/app/components/ariadne/list_component.html.erb +15 -0
  51. data/app/components/ariadne/list_component.rb +67 -0
  52. data/app/components/ariadne/main_component.rb +32 -0
  53. data/app/components/ariadne/pill_component.html.erb +3 -0
  54. data/app/components/ariadne/pill_component.rb +30 -0
  55. data/app/components/ariadne/slideover-component.d.ts +9 -0
  56. data/app/components/ariadne/slideover-component.js +20 -0
  57. data/app/components/ariadne/slideover-component.ts +26 -0
  58. data/app/components/ariadne/slideover_component.d.ts +9 -0
  59. data/app/components/ariadne/slideover_component.html.erb +11 -0
  60. data/app/components/ariadne/slideover_component.js +19 -0
  61. data/app/components/ariadne/slideover_component.rb +81 -0
  62. data/app/components/ariadne/time_ago_component.d.ts +1 -0
  63. data/app/components/ariadne/time_ago_component.js +1 -0
  64. data/app/components/ariadne/time_ago_component.rb +56 -0
  65. data/app/components/ariadne/time_ago_component.ts +1 -0
  66. data/app/components/ariadne/timeline_component.html.erb +19 -0
  67. data/app/components/ariadne/timeline_component.rb +34 -0
  68. data/app/components/ariadne/tooltip-component.d.ts +24 -0
  69. data/app/components/ariadne/tooltip-component.js +42 -0
  70. data/app/components/ariadne/tooltip-component.ts +57 -0
  71. data/app/components/ariadne/tooltip_component.html.erb +4 -0
  72. data/app/components/ariadne/tooltip_component.rb +34 -31
  73. data/app/lib/ariadne/action_view_extensions/form_helper.rb +23 -0
  74. data/app/lib/ariadne/form_builder.rb +71 -0
  75. data/lib/ariadne/classify.rb +4 -90
  76. data/lib/ariadne/view_components/engine.rb +8 -0
  77. data/lib/ariadne/view_components/version.rb +1 -1
  78. data/lib/ariadne/view_components.rb +31 -29
  79. data/lib/rubocop/cop/ariadne/ariadne_heroicon.rb +2 -2
  80. data/lib/tasks/docs.rake +52 -58
  81. data/static/arguments.yml +290 -17
  82. data/static/audited_at.json +18 -0
  83. data/static/classes.yml +189 -1
  84. data/static/constants.json +173 -30
  85. data/static/statuses.json +18 -0
  86. metadata +77 -9
  87. data/app/assets/stylesheets/application.tailwind.css +0 -3
  88. data/lib/tasks/tailwind.rake +0 -31
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `Link` for navigating from one page to another. `Link` styles anchor tags with default styling and hover text-decoration.
5
+ class LinkComponent < Ariadne::Component
6
+ DEFAULT_TAG = :a
7
+ TAG_OPTIONS = [DEFAULT_TAG, :span].freeze
8
+
9
+ DEFAULT_CLASSES = "cursor-pointer"
10
+ DEFAULT_ACTIONABLE_CLASSES = " cursor-pointer underline decoration-double font-semibold hover:text-button-text-color focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
11
+
12
+ # `Tooltip` that appears on mouse hover or keyboard focus over the button. Use tooltips sparingly and as a last resort.
13
+ # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
14
+ # Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
15
+ #
16
+ # @param tag [Symbol, String] The rendered tag name
17
+ # @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
18
+ # @param direction [Symbol] <%= one_of(Ariadne::TooltipComponent::VALID_PLACEMENTS) %>
19
+ # @param classes [String] <%= link_to_classes_docs %>
20
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::TooltipComponent) %>.
21
+ renders_one :tooltip, lambda { |tag: Ariadne::TooltipComponent::DEFAULT_TAG, text:, direction: Ariadne::TooltipComponent::DEFAULT_PLACEMENT, type: Ariadne::TooltipComponent::TYPE_DEFAULT, classes: "", attributes: {}|
22
+ raise ArgumentError, "Links with a tooltip must have a unique `id` set on the `LinkComponent`." if @id.blank?
23
+
24
+ Ariadne::TooltipComponent.new(tag: tag, for_id: @id, text: text, direction: direction, type: type, classes: classes, attributes: attributes)
25
+ }
26
+
27
+ # @example Default
28
+ # <%= render(Ariadne::LinkComponent.new(href: "#")) { "Link" } %>
29
+ #
30
+ # @example Span as link
31
+ # <%= render(Ariadne::LinkComponent.new(tag: :span, href: "#")) { "Span as a link" } %>
32
+ #
33
+ # @example With tooltip
34
+ # @description
35
+ # Use tooltips sparingly and as a last resort. Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
36
+ # @code
37
+ # <%= render(Ariadne::LinkComponent.new(href: "#", attributes: { id: "link-with-tooltip" })) do |c| %>
38
+ # <% c.tooltip(text: "Tooltip text") %>
39
+ # Link
40
+ # <% end %>
41
+ #
42
+ # @param tag [String] <%= one_of(Ariadne::LinkComponent::TAG_OPTIONS) %>
43
+ # @param href [String] URL to be used for the link.
44
+ # @param actionable [Boolean] If true, adds additional classes to the link to make it more aware.
45
+ # @param classes [String] <%= link_to_classes_docs %>
46
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
47
+ def initialize(tag: DEFAULT_TAG, href:, actionable: false, classes: "", attributes: {})
48
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
49
+
50
+ @attributes = attributes
51
+ @attributes[:href] = href
52
+
53
+ @id = @attributes[:id]
54
+
55
+ @classes = class_names(DEFAULT_CLASSES, classes)
56
+ @classes << DEFAULT_ACTIONABLE_CLASSES if actionable
57
+ end
58
+
59
+ def call
60
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do
61
+ content.to_s + tooltip.to_s
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,15 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |list| %>
2
+ <% items.each do |item| %>
3
+ <% if item.linked? %>
4
+ <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
5
+ <%= render Ariadne::LinkComponent.new(href: item.link[:href], classes: item.link[:classes], attributes: item.link[:attributes]) do %>
6
+ <%= item.entry %>
7
+ <% end %>
8
+ <% end %>
9
+ <% else %>
10
+ <%= render Ariadne::BaseComponent.new(tag: :li, classes: item.classes, attributes: item.attributes) do %>
11
+ <%= item.entry %>
12
+ <% end %>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # `List` is used to show a list of items in a vertical format.
5
+ class ListComponent < Ariadne::Component
6
+ DEFAULT_UL_CLASSES = "divide-y divide-gray-300"
7
+
8
+ renders_many :items, "Item"
9
+
10
+ # @example Basic
11
+ # <% numbers = [1, 2, 3] %>
12
+ # <%= render(Ariadne::ListComponent.new) do |list| %>
13
+ # <% numbers.each do |number| %>
14
+ # <%= list.item do |item| %>
15
+ # <%= item.entry { number } %>
16
+ # <% end %>
17
+ # <% end %>
18
+ # <% end %>
19
+ #
20
+ #
21
+ # @param classes [String] <%= link_to_classes_docs %>
22
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
23
+ def initialize(classes: "", attributes: {})
24
+ @tag = :ul
25
+ @classes = class_names(DEFAULT_UL_CLASSES, classes)
26
+ @attributes = attributes
27
+ end
28
+
29
+ def render?
30
+ items.any?
31
+ end
32
+
33
+ # This component is part of `ListComponent` and should not be
34
+ # used as a standalone component.
35
+ class Item < Ariadne::Component
36
+ DEFAULT_ITEM_CLASSES = "relative p-1.5 focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 hover:bg-button-hover-color"
37
+
38
+ renders_one :entry, lambda { |static_content = nil, &block|
39
+ next static_content if static_content.present?
40
+
41
+ render(Ariadne::BaseComponent.new(tag: :div)) do
42
+ view_context.capture { block&.call }
43
+ end
44
+ }
45
+
46
+ attr_reader :link, :classes, :attributes
47
+
48
+ def initialize(link: {}, classes: "", attributes: {})
49
+ @link = link
50
+ @classes = class_names(DEFAULT_ITEM_CLASSES, classes)
51
+ @attributes = attributes
52
+ end
53
+
54
+ def selected?
55
+ @selected
56
+ end
57
+
58
+ def linked?
59
+ @link.present?
60
+ end
61
+
62
+ def call
63
+ Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
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 MainComponent < Ariadne::Component
8
+ DEFAULT_CLASSES = "flex-auto"
9
+
10
+ # @example Default
11
+ #
12
+ # <%= render(Ariadne::MainComponent.new) { "Example" } %>
13
+ #
14
+ # @param classes [String] <%= link_to_classes_docs %>
15
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
16
+ def initialize(classes: "", attributes: {})
17
+ @tag = :main
18
+ @classes = class_names(
19
+ DEFAULT_CLASSES,
20
+ classes
21
+ )
22
+
23
+ @attributes = attributes
24
+ end
25
+
26
+ def call
27
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do
28
+ render(Ariadne::ContainerComponent.new) { content }
29
+ end
30
+ end
31
+ end
32
+ 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 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 = "flex-shrink-0 inline-block px-2 py-0.5 text-xs font-medium 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,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,20 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ export default class SlideoverComponent extends Controller {
3
+ toggle() {
4
+ var _a;
5
+ this.expandableTarget.classList.toggle('hidden');
6
+ this.expandWrapperTarget.classList.toggle('bg-filter-panel');
7
+ for (const slidePanel of this.slidePanelTargets) {
8
+ slidePanel.classList.toggle('hidden');
9
+ }
10
+ this.buttonWrapperTarget.classList.toggle('bg-filter-panel');
11
+ if ((_a = document.getElementById('btnClose')) === null || _a === void 0 ? void 0 : _a.classList.contains('hidden')) {
12
+ const formID = this.buttonWrapperTarget.getAttribute('data-slideover-component-form-id');
13
+ if (formID) {
14
+ const form = document.getElementById(formID);
15
+ form === null || form === void 0 ? void 0 : form.submit();
16
+ }
17
+ }
18
+ }
19
+ }
20
+ SlideoverComponent.targets = ['expandable', 'expandWrapper', 'slidePanel', 'buttonWrapper'];
@@ -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('hidden')
13
+ this.expandWrapperTarget.classList.toggle('bg-filter-panel')
14
+ for (const slidePanel of this.slidePanelTargets) {
15
+ slidePanel.classList.toggle('hidden')
16
+ }
17
+ this.buttonWrapperTarget.classList.toggle('bg-filter-panel')
18
+ if (document.getElementById('btnClose')?.classList.contains('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,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
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |slideover| %>
2
+ <div data-slideover-component-target="expandWrapper" class="flex flex-col z-10">
3
+ <div data-slideover-component-target="expandable" class="hidden bg-filter-panel px-3 pb-4">
4
+ <%= content %>
5
+ </div>
6
+ <div data-slideover-component-target="buttonWrapper" data-slideover-component-form-id="<%= @form_id %>" class="relative flex justify-center">
7
+ <%= open_button %>
8
+ <%= close_button %>
9
+ </div>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,19 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ export default class SlideoverComponent extends Controller {
3
+ toggle() {
4
+ var _a;
5
+ // eslint-disable-next-line no-debugger
6
+ debugger;
7
+ this.expandableTarget.classList.toggle('hidden');
8
+ this.expandWrapperTarget.classList.toggle('bg-filter-panel');
9
+ for (const slidePanel of this.slidePanelTargets) {
10
+ slidePanel.classList.toggle('hidden');
11
+ }
12
+ this.buttonWrapperTarget.classList.toggle('bg-filter-panel');
13
+ if ((_a = document.getElementById('btnClose')) === null || _a === void 0 ? void 0 : _a.classList.contains('hidden')) {
14
+ const form = this.buttonWrapperTarget.closest('form');
15
+ form === null || form === void 0 ? void 0 : form.submit();
16
+ }
17
+ }
18
+ }
19
+ SlideoverComponent.targets = ['expandable', 'expandWrapper', 'slidePanel', 'buttonWrapper'];
@@ -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 = "inline-flex items-center shadow-sm px-4 py-1.5 pb-2 text-sm leading-5 font-medium 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 @@
1
+ import '@github/time-elements';
@@ -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 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 = "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 @@
1
+ import '@github/time-elements'
@@ -0,0 +1,19 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do %>
2
+ <div>
3
+ <div class="divide-y divide-gray-200">
4
+ <div class="pb-4">
5
+ <h2 id="activity-title" class="text-lg font-medium text-gray-900">Timeline</h2>
6
+ </div>
7
+ <div class="pt-6">
8
+ <!-- Activity feed-->
9
+ <div class="flow-root">
10
+ <ul role="list" class="-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,24 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import type { Instance, Placement } from '@popperjs/core';
3
+ export default class TooltipComponent extends Controller {
4
+ static targets: string[];
5
+ readonly triggerTarget: HTMLElement;
6
+ readonly tooltipTarget: HTMLElement;
7
+ static values: {
8
+ placement: {
9
+ type: StringConstructor;
10
+ default: string;
11
+ };
12
+ offset: {
13
+ type: ArrayConstructor;
14
+ default: number[];
15
+ };
16
+ };
17
+ readonly placementValue: Placement;
18
+ readonly offsetValue: Array<number>;
19
+ popperInstance: Instance;
20
+ connect(): void;
21
+ disconnect(): void;
22
+ show(): void;
23
+ hide(): void;
24
+ }
@@ -0,0 +1,42 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import { createPopper } from '@popperjs/core';
3
+ export default class TooltipComponent extends Controller {
4
+ // Create a new Popper instance
5
+ connect() {
6
+ this.popperInstance = createPopper(this.triggerTarget, this.tooltipTarget, {
7
+ placement: this.placementValue,
8
+ modifiers: [
9
+ {
10
+ name: 'offset',
11
+ options: {
12
+ offset: this.offsetValue
13
+ }
14
+ }
15
+ ]
16
+ });
17
+ }
18
+ // Destroy the Popper instance
19
+ disconnect() {
20
+ if (this.popperInstance) {
21
+ this.popperInstance.destroy();
22
+ }
23
+ }
24
+ show() {
25
+ this.tooltipTarget.setAttribute('data-tooltip-show', '');
26
+ this.tooltipTarget.classList.remove('invisible');
27
+ // We need to tell Popper to update the tooltip position
28
+ // after we show the tooltip, otherwise it will be incorrect
29
+ this.popperInstance.update();
30
+ this.dispatch('shown', { detail: { trigger: this.triggerTarget, tooltip: this.tooltipTarget } });
31
+ }
32
+ hide() {
33
+ this.tooltipTarget.removeAttribute('data-tooltip-show');
34
+ this.tooltipTarget.classList.add('invisible');
35
+ this.dispatch('hidden', { detail: { trigger: this.triggerTarget, tooltip: this.tooltipTarget } });
36
+ }
37
+ }
38
+ TooltipComponent.targets = ['trigger', 'tooltip'];
39
+ TooltipComponent.values = {
40
+ placement: { type: String, default: 'top' },
41
+ offset: { type: Array, default: [0, 8] }
42
+ };
@@ -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('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('invisible')
54
+
55
+ this.dispatch('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 absolute block" data-popper-arrow></span>
4
+ <% end %>