ariadne_view_components 0.0.1 → 0.0.4
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/README.md +7 -6
- data/app/assets/config/manifest.js +2 -0
- data/app/assets/javascripts/ariadne_view_components.js +1 -1
- data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
- data/app/assets/stylesheets/{application.tailwind.css → application.ariadne_view_components.css} +0 -0
- data/app/components/ariadne/ariadne.ts +5 -9
- data/app/components/ariadne/base_button.rb +1 -0
- data/app/components/ariadne/blankslate_component.html.erb +26 -0
- data/app/components/ariadne/blankslate_component.rb +151 -0
- data/app/components/ariadne/button_component.html.erb +1 -1
- data/app/components/ariadne/button_component.rb +4 -1
- data/app/components/ariadne/{clipboard_copy_component.ts → clipboard-copy-component.ts} +0 -0
- data/app/components/ariadne/clipboard_copy_component.rb +5 -3
- data/app/components/ariadne/component.rb +5 -1
- data/app/components/ariadne/container_component.html.erb +3 -0
- data/app/components/ariadne/container_component.rb +25 -0
- data/app/components/ariadne/flash_component.rb +8 -8
- data/app/components/ariadne/footer_component.html.erb +7 -0
- data/app/components/ariadne/footer_component.rb +23 -0
- data/app/components/ariadne/grid_component.html.erb +17 -0
- data/app/components/ariadne/grid_component.rb +55 -0
- data/app/components/ariadne/header_component.html.erb +29 -0
- data/app/components/ariadne/header_component.rb +114 -0
- data/app/components/ariadne/heading_component.rb +1 -1
- data/app/components/ariadne/heroicon_component.html.erb +4 -5
- data/app/components/ariadne/heroicon_component.rb +5 -3
- data/app/components/ariadne/image_component.rb +5 -3
- data/app/components/ariadne/inline_flex_component.html.erb +5 -0
- data/app/components/ariadne/inline_flex_component.rb +63 -0
- data/app/components/ariadne/link_component.rb +60 -0
- data/app/components/ariadne/list_component.html.erb +17 -0
- data/app/components/ariadne/list_component.rb +97 -0
- data/app/components/ariadne/pill_component.html.erb +3 -0
- data/app/components/ariadne/pill_component.rb +30 -0
- data/app/components/ariadne/slideover-component.ts +26 -0
- data/app/components/ariadne/slideover_component.html.erb +14 -0
- data/app/components/ariadne/slideover_component.rb +77 -0
- data/app/components/ariadne/time_ago_component.rb +56 -0
- data/app/components/ariadne/time_ago_component.ts +1 -0
- data/app/components/ariadne/timeline_component.html.erb +19 -0
- data/app/components/ariadne/timeline_component.rb +34 -0
- data/app/lib/ariadne/action_view_extensions/form_helper.rb +23 -0
- data/app/lib/ariadne/form_builder.rb +71 -0
- data/lib/ariadne/classify.rb +9 -1
- data/lib/ariadne/view_components/engine.rb +6 -0
- data/lib/ariadne/view_components/version.rb +1 -1
- data/lib/rubocop/cop/ariadne/ariadne_heroicon.rb +2 -2
- data/lib/tasks/docs.rake +48 -58
- data/static/arguments.yml +204 -7
- data/static/audited_at.json +14 -0
- data/static/classes.yml +157 -1
- data/static/constants.json +171 -10
- data/static/statuses.json +14 -0
- metadata +31 -5
- data/lib/tasks/tailwind.rake +0 -31
@@ -9,8 +9,8 @@ module Ariadne
|
|
9
9
|
include IconHelper
|
10
10
|
include HeroiconsHelper
|
11
11
|
|
12
|
-
SIZE_DEFAULT = :
|
13
|
-
SIZE_MEDIUM = :
|
12
|
+
SIZE_DEFAULT = :s
|
13
|
+
SIZE_MEDIUM = :m
|
14
14
|
|
15
15
|
SIZE_MAPPINGS = {
|
16
16
|
SIZE_DEFAULT => 16,
|
@@ -39,6 +39,8 @@ module Ariadne
|
|
39
39
|
variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, },
|
40
40
|
{ name: "mail",
|
41
41
|
variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, },
|
42
|
+
{ name: "menu",
|
43
|
+
variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, },
|
42
44
|
{ name: "pencil",
|
43
45
|
variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, },
|
44
46
|
{ name: "plus-sm",
|
@@ -60,7 +62,7 @@ module Ariadne
|
|
60
62
|
# <%= render(Ariadne::HeroiconComponent.new(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_SOLID)) %>
|
61
63
|
#
|
62
64
|
# @example Medium
|
63
|
-
# <%= render(Ariadne::HeroiconComponent.new(icon: :"user-group", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, size: :
|
65
|
+
# <%= render(Ariadne::HeroiconComponent.new(icon: :"user-group", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, size: :m)) %>
|
64
66
|
#
|
65
67
|
# @example Helper
|
66
68
|
# <%= ariadne_heroicon(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE) %>
|
@@ -6,6 +6,8 @@ module Ariadne
|
|
6
6
|
# @accessibility
|
7
7
|
# Always provide a meaningful `alt`.
|
8
8
|
class ImageComponent < Ariadne::Component
|
9
|
+
DEFAULT_TAG = :img
|
10
|
+
|
9
11
|
# @example Default
|
10
12
|
#
|
11
13
|
# <%= render(Ariadne::ImageComponent.new(src: "https://github.com/github.png", alt: "GitHub")) %>
|
@@ -23,16 +25,16 @@ module Ariadne
|
|
23
25
|
# <%= render(Ariadne::ImageComponent.new(src: "https://github.com/github.png", alt: "GitHub", attributes: { height: 100, width: 100 })) %>
|
24
26
|
#
|
25
27
|
# @param tag [Symbol, String] The rendered tag name
|
26
|
-
# @param classes [String] <%= link_to_classes_docs %>
|
27
28
|
# @param src [String] The source url of the image.
|
28
29
|
# @param alt [String] Specifies an alternate text for the image.
|
29
30
|
# @param lazy [Boolean] Whether or not to lazily load the image.
|
31
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
30
32
|
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
31
|
-
def initialize(tag:
|
33
|
+
def initialize(tag: DEFAULT_TAG, src:, alt:, lazy: false, classes: "", attributes: {})
|
32
34
|
@attributes = attributes
|
33
35
|
|
34
36
|
@src = src
|
35
|
-
@tag = check_incoming_tag(
|
37
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
36
38
|
@classes = classes
|
37
39
|
|
38
40
|
@attributes[:alt] = alt
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Represents two items side-by-side. Typically, this will be an icon (of CSS classes, SVG, or a Heroicon icon)
|
5
|
+
# with optional text.
|
6
|
+
#
|
7
|
+
# InlineFlexComponent differs from HeroiconComponent in that it is intended to be
|
8
|
+
# used within (or next to) text, whereas HeroiconComponent is intended to only
|
9
|
+
# present a static list of SVG images (and can be embedded in buttons or shown alone).
|
10
|
+
class InlineFlexComponent < Ariadne::Component
|
11
|
+
DEFAULT_TAG = :span
|
12
|
+
DEFAULT_CLASSES = "inline-flex items-baseline"
|
13
|
+
|
14
|
+
STATE_OPTIONS = [:closed, :open].freeze
|
15
|
+
|
16
|
+
STATE_OPEN_SVG = <<~MSG
|
17
|
+
<svg viewBox="0 0 24 24" width="12" height="12" class="stroke-state-open" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
18
|
+
<circle cx="12" cy="12" r="10"></circle>
|
19
|
+
</svg>
|
20
|
+
MSG
|
21
|
+
STATE_CLOSED_SVG = <<~MSG
|
22
|
+
<svg viewBox="0 0 24 24" width="12" height="12" class="stroke-state-closed fill-state-closed " stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
23
|
+
<circle cx="12" cy="12" r="10"></circle>
|
24
|
+
</svg>
|
25
|
+
MSG
|
26
|
+
|
27
|
+
renders_one :icon, lambda { |icon, tag: :svg, variant:, size: Ariadne::HeroiconComponent::SIZE_DEFAULT, classes: "", attributes: {}|
|
28
|
+
Ariadne::HeroiconComponent.new(tag: tag, icon: icon, variant: variant, size: size, classes: classes, attributes: attributes)
|
29
|
+
}
|
30
|
+
|
31
|
+
renders_one :item, lambda { |tag: :span, classes: "", attributes: {}|
|
32
|
+
Ariadne::BaseComponent.new(tag: tag, classes: classes, attributes: attributes) { content }
|
33
|
+
}
|
34
|
+
|
35
|
+
DEFAULT_TEXT_OPEN_CLASSES = "text-state-open"
|
36
|
+
DEFAULT_TEXT_CLOSED_CLASSES = "text-state-closed"
|
37
|
+
DEFAULT_TEXT_CLASSES = "pl-2 text-sm font-medium text-gray-900 text-sm"
|
38
|
+
renders_one :text, lambda { |classes: "", attributes: {}|
|
39
|
+
actual_classes = class_names(DEFAULT_TEXT_CLASSES, classes)
|
40
|
+
Ariadne::BaseComponent.new(tag: :span, classes: actual_classes, attributes: attributes) { content }
|
41
|
+
}
|
42
|
+
|
43
|
+
# @example Default
|
44
|
+
#
|
45
|
+
# <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
|
46
|
+
# <% c.item { Ariadne::InlineFlexComponent::STATE_OPEN_SVG.html_safe } %>
|
47
|
+
# <% end %>
|
48
|
+
#
|
49
|
+
# <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
|
50
|
+
# <% c.icon(:check, size: :s, variant: HeroiconsHelper::Icon::VARIANT_SOLID) %>
|
51
|
+
# <% c.text { "Closed" } %>
|
52
|
+
# <% end %>
|
53
|
+
#
|
54
|
+
# @param tag [Symbol, String] The rendered tag name
|
55
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
56
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
57
|
+
def initialize(tag: DEFAULT_TAG, classes: "", attributes: {})
|
58
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
59
|
+
@classes = class_names(DEFAULT_CLASSES, classes)
|
60
|
+
@attributes = attributes
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,60 @@
|
|
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
|
+
# `Tooltip` that appears on mouse hover or keyboard focus over the link. Use tooltips sparingly and as a last resort.
|
10
|
+
# Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
|
11
|
+
#
|
12
|
+
# @param tag [Symbol, String] The rendered tag name
|
13
|
+
# @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
|
14
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
15
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
16
|
+
renders_one :tooltip, lambda { |tag: :"tool-tip", text:, classes: "", attributes: {}|
|
17
|
+
raise ArgumentError, "Links with a tooltip must have a unique `id` set on the `LinkComponent`." if @id.blank?
|
18
|
+
|
19
|
+
actual_classes = class_names(classes)
|
20
|
+
|
21
|
+
Ariadne::TooltipComponent.new(tag: tag, text: text, classes: actual_classes, attributes: attributes)
|
22
|
+
}
|
23
|
+
|
24
|
+
# @example Default
|
25
|
+
# <%= render(Ariadne::LinkComponent.new(href: "#")) { "Link" } %>
|
26
|
+
#
|
27
|
+
# @example Span as link
|
28
|
+
# <%= render(Ariadne::LinkComponent.new(tag: :span, href: "#")) { "Span as a link" } %>
|
29
|
+
#
|
30
|
+
# @example With tooltip
|
31
|
+
# @description
|
32
|
+
# Use tooltips sparingly and as a last resort. Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
|
33
|
+
# @code
|
34
|
+
# <%= render(Ariadne::LinkComponent.new(href: "#", attributes: { id: "link-with-tooltip" })) do |c| %>
|
35
|
+
# <% c.tooltip(text: "Tooltip text") %>
|
36
|
+
# Link
|
37
|
+
# <% end %>
|
38
|
+
#
|
39
|
+
# @param tag [String] <%= one_of(Ariadne::LinkComponent::TAG_OPTIONS) %>
|
40
|
+
# @param href [String] URL to be used for the link.
|
41
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
42
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
43
|
+
def initialize(tag: DEFAULT_TAG, href:, classes: "", attributes: {})
|
44
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
45
|
+
|
46
|
+
@attributes = attributes
|
47
|
+
@attributes[:href] = href
|
48
|
+
|
49
|
+
@id = @attributes[:id]
|
50
|
+
|
51
|
+
@classes = classes
|
52
|
+
end
|
53
|
+
|
54
|
+
def call
|
55
|
+
render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do
|
56
|
+
content.to_s + tooltip.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |list| %>
|
2
|
+
<% items.each do |item| %>
|
3
|
+
<% if item.linked? %>
|
4
|
+
<li data-highlight-target="highlightable" class="relative <%= selected? ? 'bg-button-bg-color' : 'hover:bg-button-hover-color' %> focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
|
5
|
+
<%= render(item.link) do %>
|
6
|
+
<%= item.header %>
|
7
|
+
<%= item.entry %>
|
8
|
+
<% end %>
|
9
|
+
</li>
|
10
|
+
<% else %>
|
11
|
+
<li data-highlight-target="highlightable" class="relative <%= selected? ? 'bg-button-bg-color' : 'hover:bg-button-hover-color' %> focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
|
12
|
+
<%= item.header %>
|
13
|
+
<%= item.entry %>
|
14
|
+
</li>
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
@@ -0,0 +1,97 @@
|
|
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
|
+
# @example With a header
|
21
|
+
# <% numbers = [1, 2, 3] %>
|
22
|
+
# <%= render(Ariadne::ListComponent.new) do |list| %>
|
23
|
+
# <% numbers.each_with_index do |number, idx| %>
|
24
|
+
# <%= list.item do |item| %>
|
25
|
+
# <%= item.header { idx } %>
|
26
|
+
# <%= item.entry { number } %>
|
27
|
+
# <% end %>
|
28
|
+
# <% end %>
|
29
|
+
# <% end %>
|
30
|
+
#
|
31
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
32
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
33
|
+
def initialize(classes: "", attributes: {})
|
34
|
+
@tag = :ul
|
35
|
+
@classes = class_names(DEFAULT_UL_CLASSES, classes)
|
36
|
+
@attributes = attributes
|
37
|
+
@attributes[:"data-controller"] = "highlight"
|
38
|
+
end
|
39
|
+
|
40
|
+
def selected?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def render?
|
45
|
+
items.any?
|
46
|
+
end
|
47
|
+
|
48
|
+
# This component is part of `ListComponent` and should not be
|
49
|
+
# used as a standalone component.
|
50
|
+
class Item < Ariadne::Component
|
51
|
+
renders_one :header, lambda { |static_content = nil, &block|
|
52
|
+
next static_content if static_content.present?
|
53
|
+
|
54
|
+
render(Ariadne::BaseComponent.new(tag: :div, classes: "flex justify-between")) do
|
55
|
+
view_context.capture { block&.call }
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
renders_one :entry, lambda { |static_content = nil, &block|
|
60
|
+
next static_content if static_content.present?
|
61
|
+
|
62
|
+
render(Ariadne::BaseComponent.new(tag: :div, classes: "mt-1")) do
|
63
|
+
view_context.capture { block&.call }
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
attr_accessor :selected
|
68
|
+
attr_reader :link, :classes, :attributes
|
69
|
+
|
70
|
+
def initialize(link: {}, classes: "", attributes: {})
|
71
|
+
if link.present?
|
72
|
+
@link = Ariadne::LinkComponent.new(**link)
|
73
|
+
end
|
74
|
+
|
75
|
+
@classes = classes
|
76
|
+
@attributes = attributes
|
77
|
+
@selected = false
|
78
|
+
end
|
79
|
+
|
80
|
+
def selected?
|
81
|
+
@selected
|
82
|
+
end
|
83
|
+
|
84
|
+
def linked?
|
85
|
+
@link.present?
|
86
|
+
end
|
87
|
+
|
88
|
+
def call
|
89
|
+
if selected
|
90
|
+
link_arguments[:"aria-current"] = "page"
|
91
|
+
end
|
92
|
+
|
93
|
+
Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
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,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,14 @@
|
|
1
|
+
<%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |slideover| %>
|
2
|
+
<div data-slideover-component-target="expandWrapper" class="flex flex-col">
|
3
|
+
<div data-slideover-component-target="expandable" class="hidden bg-filter-panel px-3 pb-4">
|
4
|
+
<%= content %>
|
5
|
+
</div>
|
6
|
+
<div class="relative top-4 inset-0 flex flex-row items-center bg-filter-panel" aria-hidden="true">
|
7
|
+
<div class="w-full border-t border-billy-purple z-10"></div>
|
8
|
+
</div>
|
9
|
+
<div data-slideover-component-target="buttonWrapper" data-slideover-component-form-id="<%= @form_id %>" class="relative flex justify-center">
|
10
|
+
<%= open_button %>
|
11
|
+
<%= close_button %>
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
@@ -0,0 +1,77 @@
|
|
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 = :y
|
12
|
+
DIRECTION_X = :x
|
13
|
+
VALID_DIRECTIONS = [DIRECTION_Y, DIRECTION_X].freeze
|
14
|
+
|
15
|
+
DEFAULT_CLASSES = ""
|
16
|
+
|
17
|
+
DEFAULT_BUTTON_CLASSES = "inline-flex items-center shadow-sm px-4 py-1.5 border border-our-purple-300 z-50 text-sm leading-5 font-medium rounded-full text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
18
|
+
renders_one :open_button, lambda { |text:, classes: "", attributes: {}|
|
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
|
+
render(Ariadne::BaseComponent.new(tag: :button, classes: actual_classes, attributes: attributes)) do
|
27
|
+
render(Ariadne::InlineFlexComponent.new(attributes: { "data-slideover-component-target" => "slide-panel" })) do |flex|
|
28
|
+
flex.icon(:"chevron-double-down", size: :s, variant: HeroiconsHelper::Icon::VARIANT_SOLID)
|
29
|
+
flex.text { text }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
renders_one :close_button, lambda { |text:, classes: "", attributes: {}|
|
35
|
+
actual_classes = class_names(DEFAULT_BUTTON_CLASSES, classes)
|
36
|
+
|
37
|
+
attributes[:id] ||= "btnClose"
|
38
|
+
attributes[:"data-slideover-component-target"] ||= "slidePanel"
|
39
|
+
attributes[:"data-action"] ||= "click->slideover-component#toggle"
|
40
|
+
attributes[:type] ||= "submit"
|
41
|
+
|
42
|
+
render(Ariadne::BaseComponent.new(tag: :button, classes: actual_classes, attributes: attributes)) do
|
43
|
+
render(Ariadne::InlineFlexComponent.new(attributes: { "data-slideover-component-target" => "slide-panel" })) do |flex|
|
44
|
+
flex.icon(:"chevron-double-up", size: :s, variant: HeroiconsHelper::Icon::VARIANT_SOLID)
|
45
|
+
flex.text { text }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
}
|
49
|
+
|
50
|
+
# @example Default
|
51
|
+
#
|
52
|
+
# <%= render(Ariadne::SlideoverComponent.new(direction: Ariadne::SlideoverComponent::DIRECTION_Y)) { "Example" } %>
|
53
|
+
#
|
54
|
+
# @param tag [Symbol, String] The rendered tag name.
|
55
|
+
# @param direction [Symbol] <%= one_of(Ariadne::SlideoverComponent::VALID_DIRECTIONS) %>
|
56
|
+
# @param form_id [String] The ID of any <form> tag to submit when the button is clicked.
|
57
|
+
# @param open_text [String] The text to use to open the slideover.
|
58
|
+
# @param close_text [String] The text to use to close the slideover.
|
59
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
60
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
61
|
+
def initialize(tag: DEFAULT_TAG, direction:, form_id: nil, open_text: "Open", close_text: "Close", classes: "", attributes: {})
|
62
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
63
|
+
@classes = class_names(
|
64
|
+
DEFAULT_CLASSES,
|
65
|
+
classes
|
66
|
+
)
|
67
|
+
|
68
|
+
@direction = fetch_or_raise(VALID_DIRECTIONS, direction)
|
69
|
+
@form_id = form_id
|
70
|
+
|
71
|
+
@open_text = open_text
|
72
|
+
@close_text = close_text
|
73
|
+
@attributes = attributes
|
74
|
+
@attributes[:"data-controller"] = "slideover-component"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -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,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
module ActionViewExtensions
|
5
|
+
# :nodoc:
|
6
|
+
module FormHelper
|
7
|
+
include ClassNameHelper
|
8
|
+
|
9
|
+
DEFAULT_FORM_CLASSES = "space-y-8 divide-y divide-gray-200 sm:space-y-5"
|
10
|
+
def ariadne_form_with(model: nil, scope: nil, url: nil, format: nil, classes: {}, attributes: {}, **options, &block)
|
11
|
+
options[:class] = class_names(DEFAULT_FORM_CLASSES, options[:class])
|
12
|
+
options[:builder] ||= Ariadne::FormBuilder
|
13
|
+
options[:html] ||= {}
|
14
|
+
options = options.merge(attributes)
|
15
|
+
form_with(model: model, scope: scope, url: url, format: format, **options, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveSupport.on_load(:action_view) do
|
22
|
+
include Ariadne::ActionViewExtensions::FormHelper
|
23
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# :nodoc:
|
5
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
6
|
+
include ClassNameHelper
|
7
|
+
|
8
|
+
DEFAULT_SECTION_CLASSES = "divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5"
|
9
|
+
def section(classes: "", attributes: {}, &block)
|
10
|
+
actual_classes = class_names(DEFAULT_SECTION_CLASSES, classes)
|
11
|
+
options = { class: actual_classes, **attributes }
|
12
|
+
@template.content_tag(:div, **options, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
DEFAULT_SECTION_HEADING_CLASSES = "text-lg leading-6 font-medium text-gray-900"
|
16
|
+
def heading(classes: "", attributes: {}, &block)
|
17
|
+
actual_classes = class_names(DEFAULT_SECTION_HEADING_CLASSES, classes)
|
18
|
+
options = { class: actual_classes, **attributes }
|
19
|
+
@template.content_tag(:h3, **options, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
DEFAULT_SECTION_SUBHEADING_CLASSES = "mt-1 max-w-2xl text-sm text-gray-500"
|
23
|
+
def subheading(classes: "", attributes: {}, &block)
|
24
|
+
actual_classes = class_names(DEFAULT_SECTION_SUBHEADING_CLASSES, classes)
|
25
|
+
options = { class: actual_classes, **attributes }
|
26
|
+
@template.content_tag(:p, **options, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
DEFAULT_LABEL_CLASSES = "block text-sm font-medium text-gray-700"
|
30
|
+
def label(method, text = nil, options = {}, &block)
|
31
|
+
options[:class] = class_names(DEFAULT_LABEL_CLASSES, options[:class])
|
32
|
+
super(method, **options)
|
33
|
+
end
|
34
|
+
|
35
|
+
DEFAULT_TEXT_CLASSES = "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
36
|
+
def text_field(method, options = {})
|
37
|
+
options[:class] = class_names(DEFAULT_TEXT_CLASSES, options[:class])
|
38
|
+
super(method, **options)
|
39
|
+
end
|
40
|
+
|
41
|
+
DEFAULT_CHECKBOX_CLASSES = "focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
42
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
43
|
+
options[:class] = class_names(DEFAULT_TEXT_CLASSES, options[:class])
|
44
|
+
super(method, **options)
|
45
|
+
end
|
46
|
+
|
47
|
+
DEFAULT_RADIO_CLASSES = "focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
48
|
+
def radio_button(method, tag_value, options = {})
|
49
|
+
options[:class] = class_names(DEFAULT_RADIO_CLASSES, options[:class])
|
50
|
+
super(method, tag_value, **options)
|
51
|
+
end
|
52
|
+
|
53
|
+
DEFAULT_TEXTAREA_CLASSES = "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border border-gray-300 rounded-md"
|
54
|
+
def text_area(method, options = {})
|
55
|
+
options[:class] = class_names(DEFAULT_TEXTAREA_CLASSES, options[:class])
|
56
|
+
super(method, **options)
|
57
|
+
end
|
58
|
+
|
59
|
+
DEFAULT_EMAIL_CLASSES = "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
60
|
+
def email_field(method, options = {})
|
61
|
+
options[:class] = class_names(DEFAULT_EMAIL_CLASSES, options[:class])
|
62
|
+
super(method, **options)
|
63
|
+
end
|
64
|
+
|
65
|
+
DEFAULT_PASSWORD_CLASSES = "appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
66
|
+
def password_field(method, options = {})
|
67
|
+
options[:class] = class_names(DEFAULT_PASSWORD_CLASSES, options[:class])
|
68
|
+
super(method, **options)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|