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.
- checksums.yaml +7 -0
- data/LICENSE.txt +49 -0
- data/README.md +73 -0
- data/app/assets/config/manifest.js +2 -0
- data/app/assets/javascripts/ariadne-form-with.d.ts +20 -0
- data/app/assets/javascripts/ariadne-form.d.ts +22 -0
- data/app/assets/javascripts/ariadne.d.ts +1 -0
- data/app/assets/javascripts/ariadne_view_components.js +8 -0
- data/app/assets/javascripts/ariadne_view_components.js.map +1 -0
- data/app/assets/javascripts/clipboard-copy-component.d.ts +4 -0
- data/app/assets/javascripts/comment-component.d.ts +13 -0
- data/app/assets/javascripts/rich-text-area-component.d.ts +4 -0
- data/app/assets/javascripts/slideover-component.d.ts +9 -0
- data/app/assets/javascripts/time-ago-component.d.ts +1 -0
- data/app/assets/javascripts/time_ago_component.d.ts +1 -0
- data/app/assets/javascripts/tooltip-component.d.ts +24 -0
- data/app/assets/stylesheets/ariadne_view_components.css +6 -0
- data/app/assets/stylesheets/prosemirror.css +323 -0
- data/app/assets/stylesheets/tooltip-component.css +37 -0
- data/app/components/ariadne/ariadne-form.ts +96 -0
- data/app/components/ariadne/ariadne.ts +20 -0
- data/app/components/ariadne/base_button.rb +61 -0
- data/app/components/ariadne/base_component.rb +37 -0
- data/app/components/ariadne/blankslate_component.html.erb +26 -0
- data/app/components/ariadne/blankslate_component.rb +146 -0
- data/app/components/ariadne/body_component.rb +30 -0
- data/app/components/ariadne/button_component.html.erb +4 -0
- data/app/components/ariadne/button_component.rb +157 -0
- data/app/components/ariadne/clipboard-copy-component.ts +19 -0
- data/app/components/ariadne/clipboard_copy_component.html.erb +9 -0
- data/app/components/ariadne/clipboard_copy_component.rb +90 -0
- data/app/components/ariadne/comment-component.ts +55 -0
- data/app/components/ariadne/comment_component.html.erb +22 -0
- data/app/components/ariadne/comment_component.rb +57 -0
- data/app/components/ariadne/component.rb +128 -0
- data/app/components/ariadne/container_component.html.erb +3 -0
- data/app/components/ariadne/container_component.rb +25 -0
- data/app/components/ariadne/content.rb +12 -0
- data/app/components/ariadne/counter_component.rb +100 -0
- data/app/components/ariadne/flash_component.html.erb +31 -0
- data/app/components/ariadne/flash_component.rb +125 -0
- data/app/components/ariadne/flex_component.rb +49 -0
- 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 +26 -0
- data/app/components/ariadne/grid_component.rb +66 -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 +49 -0
- data/app/components/ariadne/heroicon_component.html.erb +4 -0
- data/app/components/ariadne/heroicon_component.rb +129 -0
- data/app/components/ariadne/image_component.rb +53 -0
- data/app/components/ariadne/inline_flex_component.html.erb +5 -0
- data/app/components/ariadne/inline_flex_component.rb +65 -0
- data/app/components/ariadne/link_component.rb +65 -0
- data/app/components/ariadne/list_component.html.erb +15 -0
- data/app/components/ariadne/list_component.rb +68 -0
- data/app/components/ariadne/main_component.rb +32 -0
- data/app/components/ariadne/narrow_container_component.html.erb +3 -0
- data/app/components/ariadne/narrow_container_component.rb +30 -0
- data/app/components/ariadne/panel_bar_component.html.erb +20 -0
- data/app/components/ariadne/panel_bar_component.rb +79 -0
- data/app/components/ariadne/pill_component.html.erb +3 -0
- data/app/components/ariadne/pill_component.rb +30 -0
- data/app/components/ariadne/rich-text-area-component.ts +32 -0
- data/app/components/ariadne/rich_text_area_component.html.erb +6 -0
- data/app/components/ariadne/rich_text_area_component.rb +35 -0
- data/app/components/ariadne/slideover-component.ts +26 -0
- data/app/components/ariadne/slideover_component.html.erb +11 -0
- data/app/components/ariadne/slideover_component.rb +81 -0
- data/app/components/ariadne/tab_bar_component.html.erb +3 -0
- data/app/components/ariadne/tab_bar_component.rb +45 -0
- data/app/components/ariadne/tab_component.html.erb +7 -0
- data/app/components/ariadne/tab_component.rb +43 -0
- data/app/components/ariadne/text.rb +25 -0
- data/app/components/ariadne/time-ago-component.ts +1 -0
- data/app/components/ariadne/time_ago_component.rb +56 -0
- data/app/components/ariadne/timeline_component.html.erb +19 -0
- data/app/components/ariadne/timeline_component.rb +34 -0
- data/app/components/ariadne/tooltip-component.ts +57 -0
- data/app/components/ariadne/tooltip_component.html.erb +4 -0
- data/app/components/ariadne/tooltip_component.rb +108 -0
- data/app/lib/ariadne/action_view_extensions/form_helper.rb +26 -0
- data/app/lib/ariadne/audited/dsl.rb +32 -0
- data/app/lib/ariadne/class_name_helper.rb +22 -0
- data/app/lib/ariadne/fetch_or_fallback_helper.rb +102 -0
- data/app/lib/ariadne/form_builder.rb +71 -0
- data/app/lib/ariadne/icon_helper.rb +47 -0
- data/app/lib/ariadne/join_style_arguments_helper.rb +14 -0
- data/app/lib/ariadne/logger_helper.rb +23 -0
- data/app/lib/ariadne/status/dsl.rb +41 -0
- data/app/lib/ariadne/tab_nav_helper.rb +35 -0
- data/app/lib/ariadne/tabbed_component_helper.rb +39 -0
- data/app/lib/ariadne/test_selector_helper.rb +20 -0
- data/app/lib/ariadne/underline_nav_helper.rb +44 -0
- data/app/lib/ariadne/view_helper.rb +22 -0
- data/exe/tailwindcss +21 -0
- data/exe/x86_64-linux/tailwindcss +0 -0
- data/lib/ariadne/view_components/commands.rb +90 -0
- data/lib/ariadne/view_components/constants.rb +53 -0
- data/lib/ariadne/view_components/engine.rb +75 -0
- data/lib/ariadne/view_components/linters.rb +3 -0
- data/lib/ariadne/view_components/statuses.rb +14 -0
- data/lib/ariadne/view_components/upstream.rb +20 -0
- data/lib/ariadne/view_components/version.rb +7 -0
- data/lib/ariadne/view_components.rb +61 -0
- data/lib/rubocop/config/default.yml +8 -0
- data/lib/rubocop/cop/ariadne/base_cop.rb +26 -0
- data/lib/rubocop/cop/ariadne/no_tag_memoize.rb +44 -0
- data/lib/rubocop/cop/ariadne.rb +3 -0
- data/lib/tasks/ariadne_view_components.rake +48 -0
- data/lib/tasks/build.rake +30 -0
- data/lib/tasks/coverage.rake +19 -0
- data/lib/tasks/custom_utilities.yml +310 -0
- data/lib/tasks/docs.rake +524 -0
- data/lib/tasks/helpers/ast_processor.rb +44 -0
- data/lib/tasks/helpers/ast_traverser.rb +77 -0
- data/lib/tasks/static.rake +15 -0
- data/lib/yard/docs_helper.rb +83 -0
- data/lib/yard/renders_many_handler.rb +19 -0
- data/lib/yard/renders_one_handler.rb +19 -0
- data/static/arguments.yml +619 -0
- data/static/assets/view-components.svg +18 -0
- data/static/audited_at.json +38 -0
- data/static/classes.yml +291 -0
- data/static/constants.json +426 -0
- data/static/statuses.json +38 -0
- data/static/tailwindcss.yml +727 -0
- data/tailwind.config.js +65 -0
- metadata +264 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# `Tooltip` only appears on mouse hover or keyboard focus and contain a label or description text.
|
5
|
+
# Use tooltips sparingly and as a last resort.
|
6
|
+
#
|
7
|
+
# When using a tooltip, follow the provided guidelines to avoid accessibility issues.
|
8
|
+
#
|
9
|
+
# - Tooltip text should be brief and to the point. The tooltip content must be a string.
|
10
|
+
# - Tooltips should contain only **non-essential text**. Tooltips can easily be missed and are not accessible on touch devices so never
|
11
|
+
# use tooltips to convey critical information.
|
12
|
+
#
|
13
|
+
# @accessibility
|
14
|
+
# - **Never set tooltips on static elements.** Tooltips should only be used on interactive elements like buttons or links to avoid excluding keyboard-only users
|
15
|
+
# and screen reader users.
|
16
|
+
# - Place `Tooltip` adjacent after its trigger element in the DOM. This allows screen reader users to navigate to and copy the tooltip
|
17
|
+
# content.
|
18
|
+
# ### Which `type` should I set?
|
19
|
+
# Setting `:description` establishes an `aria-describedby` relationship, while `:label` establishes an `aria-labelledby` relationship between the trigger element and the tooltip,
|
20
|
+
#
|
21
|
+
# The `type` drastically changes semantics and screen reader behavior so follow these guidelines carefully:
|
22
|
+
# - When there is already a visible label text on the trigger element, the tooltip is likely intended to supplement the existing text, so set `type: :description`.
|
23
|
+
# The majority of tooltips will fall under this category.
|
24
|
+
# - When there is no visible text on the trigger element and the tooltip content is appropriate as a label for the element, set `type: :label`.
|
25
|
+
# This type is usually only appropriate for an icon-only control.
|
26
|
+
class TooltipComponent < Ariadne::Component
|
27
|
+
DEFAULT_TAG = :tooltip
|
28
|
+
DEFAULT_PLACEMENT = :top
|
29
|
+
VALID_PLACEMENTS = [DEFAULT_PLACEMENT, :right, :bottom, :left].freeze
|
30
|
+
|
31
|
+
DEFAULT_CLASSES = "ariadne-invisible ariadne-absolute ariadne-bg-slate-900 ariadne-text-white ariadne-font-semibold ariadne-max-w-xs ariadne-py-1 ariadne-px-2 ariadne-rounded z-max"
|
32
|
+
|
33
|
+
DATA_CONTROLLER = "tooltip-component"
|
34
|
+
DATA_ACTION = "mouseover->tooltip-component#show mouseout->tooltip-component#hide"
|
35
|
+
|
36
|
+
TYPE_DEFAULT = :description
|
37
|
+
TYPE_OPTIONS = [:label, TYPE_DEFAULT].freeze
|
38
|
+
|
39
|
+
# DEFAULT_DATA_ATTRIBUTES = {
|
40
|
+
# "data-controller": DATA_CONTROLLER,
|
41
|
+
# "data-action": "mouseover->tooltip-component#show mouseout->tooltip-component#hide",
|
42
|
+
# "data-tooltip-component-placement": DEFAULT_PLACEMENT,
|
43
|
+
# }
|
44
|
+
|
45
|
+
# @example As a description for an icon-only button
|
46
|
+
# @description
|
47
|
+
# If the tooltip content provides supplementary description, set `type: :description` to establish an `aria-describedby` relationship.
|
48
|
+
# The trigger element should also have a _concise_ accessible label via `aria-label`.
|
49
|
+
# @code
|
50
|
+
# <%= render(Ariadne::HeroiconComponent.new(icon: :moon, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, attributes: { id: "bold-button-0" })) %>
|
51
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "bold-button-0", type: :description, text: "Add bold text", direction: :top)) %>
|
52
|
+
# @example As a label for an icon-only button
|
53
|
+
# @description
|
54
|
+
# If the tooltip labels the icon-only button, set `type: :label`. This tooltip content becomes the accessible name for the button.
|
55
|
+
# @code
|
56
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: { id: "like-button" })) { "👍" } %>
|
57
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "like-button", type: :label, text: "Like", direction: :top)) %>
|
58
|
+
#
|
59
|
+
# @example As a description for a button with visible label
|
60
|
+
# @description
|
61
|
+
# If the button already has visible label text, the tooltip content is likely supplementary so set `type: :description`.
|
62
|
+
# @code
|
63
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: {id: "save-button"}, scheme: :success)) { "Save" } %>
|
64
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "save-button", type: :description, text: "This will immediately impact all organization members", direction: :right)) %>
|
65
|
+
# @example With direction
|
66
|
+
# @description
|
67
|
+
# Set direction of tooltip with `direction`. The tooltip is responsive and will automatically adjust direction to avoid cutting off.
|
68
|
+
# @code
|
69
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: {id: "North", m: 2})) { "North" } %>
|
70
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "North", type: :description, text: "This is a North-facing tooltip, and is responsive.", direction: :top)) %>
|
71
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: {id: "South", m: 2})) { "South" } %>
|
72
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "South", type: :description, text: "This is a South-facing tooltip and is responsive.", direction: :bottom)) %>
|
73
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: {id: "East", m: 2})) { "East" } %>
|
74
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "East", type: :description, text: "This is a East-facing tooltip and is responsive.", direction: :right)) %>
|
75
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: {id: "West", m: 2})) { "West" } %>
|
76
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "West""", type: :description, text: "This is a West-facing tooltip and is responsive.", direction: :left)) %>
|
77
|
+
# @example With relative parent
|
78
|
+
# @description
|
79
|
+
# When the tooltip and trigger element have a parent container with `relative: position`, it should not affect width of the tooltip.
|
80
|
+
# @code
|
81
|
+
# <span style="position: relative;">
|
82
|
+
# <%= render(Ariadne::ButtonComponent.new(attributes: {id: "test-button"}, scheme: :info)) { "Test" } %>
|
83
|
+
# <%= render(Ariadne::TooltipComponent.new(for_id: "test-button", type: :description, text: "This tooltip should take up the full width", direction: :bottom)) %>
|
84
|
+
# </span>
|
85
|
+
# @param tag [Symbol, String] The rendered tag name
|
86
|
+
# @param for_id [String] The ID of the element that the tooltip should be attached to.
|
87
|
+
# @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
|
88
|
+
# @param type [Symbol] <%= one_of(Ariadne::TooltipComponent::TYPE_OPTIONS) %>
|
89
|
+
# @param direction [Symbol] <%= one_of(Ariadne::TooltipComponent::VALID_PLACEMENTS) %>
|
90
|
+
# @param classes [String] <%= link_to_classes_docs %>
|
91
|
+
# @param attributes [Hash] <%= link_to_attributes_docs %>
|
92
|
+
def initialize(tag: DEFAULT_TAG, for_id:, text:, type: TYPE_DEFAULT, direction: DEFAULT_PLACEMENT, classes: "", attributes: {})
|
93
|
+
raise TypeError, "tooltip text must be a string" unless text.is_a?(String)
|
94
|
+
|
95
|
+
@tag = check_incoming_tag(DEFAULT_TAG, tag)
|
96
|
+
|
97
|
+
@text = text
|
98
|
+
@classes = class_names(DEFAULT_CLASSES, classes)
|
99
|
+
|
100
|
+
@attributes = attributes
|
101
|
+
@attributes[:for] = for_id
|
102
|
+
|
103
|
+
@attributes[:"data-tooltip-component-placement"] = fetch_or_raise(VALID_PLACEMENTS, direction)
|
104
|
+
@attributes[:"data-type"] = fetch_or_raise(TYPE_OPTIONS, type)
|
105
|
+
@attributes[:"data-tooltip-component-target"] = "tooltip"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
data = {
|
16
|
+
controller: "ariadne-form",
|
17
|
+
}
|
18
|
+
form_with(model: model, scope: scope, url: url, format: format, data: data, **options, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveSupport.on_load(:action_view) do
|
25
|
+
include Ariadne::ActionViewExtensions::FormHelper
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Ariadne
|
6
|
+
# :nodoc:
|
7
|
+
module Audited
|
8
|
+
# DSL to register when a component has passed an accessibility audit.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# class MyComponent < ViewComponent::Base
|
13
|
+
# include Ariadne::Audited::Dsl
|
14
|
+
# audited_at 'YYYY-MM-DD'
|
15
|
+
# end
|
16
|
+
module Dsl
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
included do
|
20
|
+
class_attribute :audit_date, instance_writer: false
|
21
|
+
end
|
22
|
+
|
23
|
+
class_methods do
|
24
|
+
def audited_at(date = nil)
|
25
|
+
return audit_date if date.nil?
|
26
|
+
|
27
|
+
self.audit_date = date
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Helps build a list of conditional class names
|
4
|
+
module Ariadne
|
5
|
+
# :nodoc:
|
6
|
+
module ClassNameHelper
|
7
|
+
def class_names(*args)
|
8
|
+
[].tap do |classes|
|
9
|
+
args.each do |class_name|
|
10
|
+
next if class_name.blank?
|
11
|
+
|
12
|
+
case class_name
|
13
|
+
when String
|
14
|
+
classes << class_name
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Expected String class name, got #{class_name.class}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end.join(" ")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Ariadne::FetchOrFallbackHelper
|
4
|
+
# A little helper to enable graceful fallbacks
|
5
|
+
#
|
6
|
+
# Use this helper to quietly ensure a value is
|
7
|
+
# one that you expect:
|
8
|
+
#
|
9
|
+
# allowed_values - allowed options for *value*
|
10
|
+
# given_value - input being coerced
|
11
|
+
# fallback - returned if *given_value* is not included in *allowed_values*
|
12
|
+
#
|
13
|
+
# fetch_or_raise([1,2,3], 5) => 2
|
14
|
+
# fetch_or_raise([1,2,3], 1) => 1
|
15
|
+
# fetch_or_raise([1,2,3], nil) => 2
|
16
|
+
module Ariadne
|
17
|
+
# :nodoc:
|
18
|
+
module FetchOrFallbackHelper
|
19
|
+
include LoggerHelper
|
20
|
+
|
21
|
+
mattr_accessor :fallback_raises, default: true
|
22
|
+
|
23
|
+
InvalidValueError = Class.new(StandardError)
|
24
|
+
|
25
|
+
TRUE_OR_FALSE = [true, false].freeze
|
26
|
+
|
27
|
+
def fetch_or_raise(allowed_values, given_value)
|
28
|
+
if !allowed_values.is_a?(Array) && !allowed_values.is_a?(Set)
|
29
|
+
raise ArgumentError, "allowed_values must be an array or a set; it was #{allowed_values.class}"
|
30
|
+
end
|
31
|
+
|
32
|
+
if allowed_values.include?(given_value)
|
33
|
+
given_value
|
34
|
+
else
|
35
|
+
raise InvalidValueError, <<~MSG
|
36
|
+
fetch_or_raise was called with an invalid value.
|
37
|
+
|
38
|
+
Expected one of: #{allowed_values.inspect}
|
39
|
+
Got: #{given_value.inspect}
|
40
|
+
MSG
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_or_raise_boolean(given_value)
|
45
|
+
fetch_or_raise(TRUE_OR_FALSE, given_value)
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO: test this
|
49
|
+
def check_incoming_tag(preferred_tag, given_tag)
|
50
|
+
return preferred_tag if given_tag.blank? || preferred_tag == given_tag
|
51
|
+
|
52
|
+
unless silence_warnings?
|
53
|
+
message = <<~MSG
|
54
|
+
Ariadne: note that `#{preferred_tag}` is the preferred tag here;
|
55
|
+
you passed `#{given_tag}` (which will still be used)
|
56
|
+
MSG
|
57
|
+
|
58
|
+
logger.warn(message)
|
59
|
+
end
|
60
|
+
|
61
|
+
given_tag
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: test this
|
65
|
+
def check_incoming_attribute(preferred_attribute, given_attribute)
|
66
|
+
return preferred_attribute if given_attribute.blank? || preferred_attribute != given_attribute
|
67
|
+
|
68
|
+
unless silence_warnings?
|
69
|
+
message = <<~MSG
|
70
|
+
Ariadne: note that `#{preferred_attribute}` is the preferred attribute here;
|
71
|
+
you passed `#{given_attribute}` (which will still be used)
|
72
|
+
MSG
|
73
|
+
|
74
|
+
logger.warn(message)
|
75
|
+
end
|
76
|
+
|
77
|
+
given_attribute
|
78
|
+
end
|
79
|
+
|
80
|
+
# TODO: test this
|
81
|
+
def check_incoming_value(preferred_value, given_pair)
|
82
|
+
return preferred_value if given_pair.blank? || !given_pair.is_a?(Hash)
|
83
|
+
|
84
|
+
given_key = given_pair.keys.first
|
85
|
+
given_value = given_pair.values.first
|
86
|
+
|
87
|
+
return preferred_value if given_value.blank?
|
88
|
+
|
89
|
+
unless silence_warnings?
|
90
|
+
|
91
|
+
message = <<~MSG
|
92
|
+
Ariadne: note that `#{preferred_value}` is the preferred value for `#{given_key}` here;
|
93
|
+
you passed `#{given_value}` (which will still be used)
|
94
|
+
MSG
|
95
|
+
|
96
|
+
logger.warn(message)
|
97
|
+
end
|
98
|
+
|
99
|
+
given_value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
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 = "ariadne-pt-8 ariadne-space-y-6 sm:ariadne-pt-10 sm:ariadne-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 = "ariadne-text-lg ariadne-leading-6 ariadne-font-medium ariadne-text-gray-900"
|
16
|
+
def heading(tag: :h3, classes: "", attributes: {}, &block)
|
17
|
+
actual_classes = class_names(DEFAULT_SECTION_HEADING_CLASSES, classes)
|
18
|
+
options = { class: actual_classes, **attributes }
|
19
|
+
@template.content_tag(tag, **options, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
DEFAULT_SECTION_SUBHEADING_CLASSES = "ariadne-mt-1 ariadne-max-w-2xl ariadne-text-sm ariadne-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 = "ariadne-block ariadne-text-sm ariadne-font-medium ariadne-text-gray-700 ariadne-pl-2"
|
30
|
+
def label(object_name, content, ptions = {}, &block)
|
31
|
+
options[:class] = class_names(DEFAULT_LABEL_CLASSES, options.delete(:classes))
|
32
|
+
super(object_name, content, options, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
DEFAULT_TEXT_CLASSES = "ariadne-shadow-sm focus:ariadne-ring-indigo-500 focus:ariadne-border-indigo-500 ariadne-block ariadne-w-full sm:ariadne-text-sm ariadne-border-gray-300 ariadne-rounded-md"
|
36
|
+
def text_field(method, options = {})
|
37
|
+
options[:class] = class_names(DEFAULT_TEXT_CLASSES, options.delete(:classes))
|
38
|
+
super(method, **options)
|
39
|
+
end
|
40
|
+
|
41
|
+
DEFAULT_CHECKBOX_CLASSES = "focus:ariadne-ring-indigo-500 ariadne-h-4 ariadne-w-4 ariadne-text-indigo-600 ariadne-border-gray-300 ariadne-rounded"
|
42
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
43
|
+
options[:class] = class_names(DEFAULT_CHECKBOX_CLASSES, options.delete(:classes))
|
44
|
+
super(method, options, checked_value, unchecked_value)
|
45
|
+
end
|
46
|
+
|
47
|
+
DEFAULT_RADIO_CLASSES = "focus:ariadne-ring-indigo-500 ariadne-h-4 ariadne-w-4 ariadne-text-indigo-600 ariadne-border-gray-300 ariadne-rounded"
|
48
|
+
def radio_button(method, tag_value, options = {})
|
49
|
+
options[:class] = class_names(DEFAULT_RADIO_CLASSES, options.delete(:classes))
|
50
|
+
super(method, tag_value, **options)
|
51
|
+
end
|
52
|
+
|
53
|
+
DEFAULT_TEXTAREA_CLASSES = "ariadne-shadow-sm focus:ariadne-ring-indigo-500 focus:ariadne-border-indigo-500 ariadne-block ariadne-w-full sm:ariadne-text-sm ariadne-border ariadne-border-gray-300 ariadne-rounded-md"
|
54
|
+
def text_area(method, options = {})
|
55
|
+
options[:class] = class_names(DEFAULT_TEXTAREA_CLASSES, options.delete(:classes))
|
56
|
+
super(method, **options)
|
57
|
+
end
|
58
|
+
|
59
|
+
DEFAULT_EMAIL_CLASSES = "ariadne-shadow-sm focus:ariadne-ring-indigo-500 focus:ariadne-border-indigo-500 ariadne-block ariadne-w-full sm:ariadne-text-sm ariadne-border-gray-300 ariadne-rounded-md"
|
60
|
+
def email_field(method, options = {})
|
61
|
+
options[:class] = class_names(DEFAULT_EMAIL_CLASSES, options.delete(:classes))
|
62
|
+
super(method, **options)
|
63
|
+
end
|
64
|
+
|
65
|
+
DEFAULT_PASSWORD_CLASSES = "ariadne-appearance-none ariadne-block ariadne-w-full ariadne-px-3 ariadne-py-2 ariadne-border ariadne-border-gray-300 ariadne-rounded-md ariadne-shadow-sm ariadne-placeholder-gray-400 focus:ariadne-outline-none focus:ariadne-ring-indigo-500 focus:ariadne-border-indigo-500 sm:ariadne-text-sm"
|
66
|
+
def password_field(method, options = {})
|
67
|
+
options[:class] = class_names(DEFAULT_PASSWORD_CLASSES, options.delete(:classes))
|
68
|
+
super(method, **options)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Ariadne::FetchOrFallbackHelper
|
4
|
+
# A little helper to enable graceful fallbacks
|
5
|
+
#
|
6
|
+
# Use this helper to quietly ensure a value is
|
7
|
+
# one that you expect:
|
8
|
+
#
|
9
|
+
# allowed_values - allowed options for *value*
|
10
|
+
# given_value - input being coerced
|
11
|
+
# fallback - returned if *given_value* is not included in *allowed_values*
|
12
|
+
#
|
13
|
+
# fetch_or_raise([1,2,3], 5) => 2
|
14
|
+
# fetch_or_raise([1,2,3], 1) => 1
|
15
|
+
# fetch_or_raise([1,2,3], nil) => 2
|
16
|
+
module Ariadne
|
17
|
+
# :nodoc:
|
18
|
+
module IconHelper
|
19
|
+
include FetchOrFallbackHelper
|
20
|
+
|
21
|
+
def check_icon_presence!(icon, variant)
|
22
|
+
return true unless has_partial_icon?(icon, variant)
|
23
|
+
|
24
|
+
icon_presence!(icon, variant)
|
25
|
+
variant_presence!(icon, variant)
|
26
|
+
fetch_or_raise(HeroiconsHelper::Icon::VALID_VARIANTS, variant)
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_partial_icon?(icon, variant)
|
32
|
+
icon.present? || variant.present?
|
33
|
+
end
|
34
|
+
|
35
|
+
def icon_presence!(icon, variant)
|
36
|
+
raise(ArgumentError, "You must provide an `icon` when providing a `variant`.") if icon.blank? && variant.present?
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def variant_presence!(icon, variant)
|
42
|
+
raise(ArgumentError, "You must provide a `variant` when providing an `icon`.") if icon.present? && variant.blank?
|
43
|
+
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# :nodoc:
|
5
|
+
module JoinStyleArgumentsHelper
|
6
|
+
# Join two `style` arguments
|
7
|
+
#
|
8
|
+
# join_style_arguments("width: 100%", "height: 100%") =>
|
9
|
+
# "width: 100%;height: 100%"
|
10
|
+
def join_style_arguments(*args)
|
11
|
+
args.compact.join(";")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# :nodoc:
|
5
|
+
module LoggerHelper
|
6
|
+
def logger
|
7
|
+
return Rails.logger if defined?(Rails) && Rails.logger
|
8
|
+
|
9
|
+
require "logger"
|
10
|
+
Logger.new($stderr)
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: test
|
14
|
+
def silence_deprecations?
|
15
|
+
Rails.application.config.ariadne_view_components.silence_deprecations
|
16
|
+
end
|
17
|
+
|
18
|
+
# TODO: test
|
19
|
+
def silence_warnings?
|
20
|
+
Rails.application.config.ariadne_view_components.silence_warnings
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Ariadne
|
6
|
+
# :nodoc:
|
7
|
+
module Status
|
8
|
+
# DSL to allow components to register their status.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# class MyComponent < ViewComponent::Base
|
13
|
+
# include Ariadne::Status::Dsl
|
14
|
+
# status :experimental
|
15
|
+
# end
|
16
|
+
module Dsl
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
|
19
|
+
STATUSES = {
|
20
|
+
experimental: :experimental,
|
21
|
+
stable: :stable,
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
class UnknownStatusError < StandardError; end
|
25
|
+
|
26
|
+
included do
|
27
|
+
class_attribute :component_status, instance_writer: false, default: STATUSES[:stable]
|
28
|
+
end
|
29
|
+
|
30
|
+
class_methods do
|
31
|
+
def status(status = nil)
|
32
|
+
return component_status if status.nil?
|
33
|
+
|
34
|
+
raise UnknownStatusError, "status #{status} does not exist" if STATUSES[status].nil?
|
35
|
+
|
36
|
+
self.component_status = STATUSES[status]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Ariadne
|
6
|
+
# Helper to share tab validation logic between components.
|
7
|
+
# The component will raise an error if there are 0 or 2+ selected tabs.
|
8
|
+
module TabNavHelper
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
EXTRA_ALIGN_DEFAULT = :left
|
12
|
+
EXTRA_ALIGN_OPTIONS = [EXTRA_ALIGN_DEFAULT, :right].freeze
|
13
|
+
|
14
|
+
def tab_nav_tab_classes(classes)
|
15
|
+
class_names(
|
16
|
+
"tabnav-tab",
|
17
|
+
classes
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def tab_nav_classes(classes)
|
22
|
+
class_names(
|
23
|
+
"tabnav",
|
24
|
+
classes
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def tab_nav_body_classes(classes)
|
29
|
+
class_names(
|
30
|
+
"tabnav-tabs",
|
31
|
+
classes
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Ariadne
|
6
|
+
# Helper to share tab validation logic between components.
|
7
|
+
# The component will raise an error if there are 0 or 2+ selected tabs.
|
8
|
+
module TabbedComponentHelper
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
class MultipleSelectedTabsError < StandardError; end
|
12
|
+
|
13
|
+
def before_render
|
14
|
+
validate_single_selected_tab unless Rails.env.production?
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def aria_label_for_page_nav(label)
|
20
|
+
@attributes[:tag] == :nav ? @attributes[:"aria-label"] = label : @body_arguments[:"aria-label"] = label
|
21
|
+
end
|
22
|
+
|
23
|
+
def tab_container_wrapper(with_panel:, **attributes)
|
24
|
+
return yield unless with_panel
|
25
|
+
|
26
|
+
render(Ariadne::TabContainerComponent.new(**attributes)) do
|
27
|
+
yield if block_given?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_single_selected_tab
|
32
|
+
raise MultipleSelectedTabsError, "only one tab can be selected" if selected_tabs_count > 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def selected_tabs_count
|
36
|
+
@selected_tabs_count ||= tabs.count(&:selected)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ariadne
|
4
|
+
# Module to allow components to deal with the `test_selector` argument.
|
5
|
+
# It will only add the selector if env is not Production.
|
6
|
+
#
|
7
|
+
# test_selector: "foo" => data-test-selector="foo"
|
8
|
+
module TestSelectorHelper
|
9
|
+
TEST_SELECTOR_TAG = :test_selector
|
10
|
+
|
11
|
+
def add_test_selector(args)
|
12
|
+
if args.key?(TEST_SELECTOR_TAG)
|
13
|
+
args[:data] ||= {}
|
14
|
+
args[:data][TEST_SELECTOR_TAG] = args[TEST_SELECTOR_TAG]
|
15
|
+
end
|
16
|
+
|
17
|
+
args.except(TEST_SELECTOR_TAG)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Ariadne
|
6
|
+
# Helper to share tab validation logic between components.
|
7
|
+
# The component will raise an error if there are 0 or 2+ selected tabs.
|
8
|
+
module UnderlineNavHelper
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
ALIGN_DEFAULT = :left
|
12
|
+
ALIGN_OPTIONS = [ALIGN_DEFAULT, :right].freeze
|
13
|
+
|
14
|
+
ACTIONS_TAG_DEFAULT = :div
|
15
|
+
ACTIONS_TAG_OPTIONS = [ACTIONS_TAG_DEFAULT, :span].freeze
|
16
|
+
|
17
|
+
def underline_nav_classes(classes, align)
|
18
|
+
class_names(
|
19
|
+
classes,
|
20
|
+
"UnderlineNav",
|
21
|
+
"UnderlineNav--right" => align == :right
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def underline_nav_body_classes(classes)
|
26
|
+
class_names(
|
27
|
+
"UnderlineNav-body",
|
28
|
+
classes,
|
29
|
+
"list-style-none"
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def underline_nav_action_classes(classes)
|
34
|
+
class_names("UnderlineNav-actions", classes)
|
35
|
+
end
|
36
|
+
|
37
|
+
def underline_nav_tab_classes(classes)
|
38
|
+
class_names(
|
39
|
+
"UnderlineNav-item",
|
40
|
+
classes
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :nocov:
|
4
|
+
module Ariadne
|
5
|
+
# Module to allow shorthand calls for Ariadne components
|
6
|
+
module ViewHelper
|
7
|
+
class ViewHelperNotFound < StandardError; end
|
8
|
+
|
9
|
+
HELPERS = {
|
10
|
+
heroicon: "Ariadne::HeroiconComponent",
|
11
|
+
heading: "Ariadne::HeadingComponent",
|
12
|
+
time_ago: "Ariadne::TimeAgoComponent",
|
13
|
+
image: "Ariadne::ImageComponent",
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
HELPERS.each do |name, component|
|
17
|
+
define_method "ariadne_#{name}" do |*args, **kwargs, &block|
|
18
|
+
render component.constantize.new(*args, **kwargs), &block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/exe/tailwindcss
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# because rubygems shims assume a gem's executables are Ruby
|
5
|
+
|
6
|
+
require "ariadne/view_components/commands"
|
7
|
+
|
8
|
+
begin
|
9
|
+
command = [Ariadne::ViewComponents::Commands.executable, *ARGV]
|
10
|
+
puts command.inspect
|
11
|
+
if Gem.win_platform?
|
12
|
+
# use system rather than exec as exec inexplicably fails to find the executable on Windows
|
13
|
+
# see related https://github.com/rubys/sprockets-esbuild/pull/4
|
14
|
+
system(*command, exception: true)
|
15
|
+
else
|
16
|
+
exec(*command)
|
17
|
+
end
|
18
|
+
rescue Ariadne::ViewComponents::Commands::UnsupportedPlatformException, Ariadne::ViewComponents::Commands::ExecutableNotFoundException => e
|
19
|
+
$stderr.puts("ERROR: " + e.message)
|
20
|
+
exit(1)
|
21
|
+
end
|