ariadne_view_components 0.0.10-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +49 -0
  3. data/README.md +73 -0
  4. data/app/assets/config/manifest.js +2 -0
  5. data/app/assets/javascripts/ariadne-form-with.d.ts +20 -0
  6. data/app/assets/javascripts/ariadne-form.d.ts +22 -0
  7. data/app/assets/javascripts/ariadne.d.ts +1 -0
  8. data/app/assets/javascripts/ariadne_view_components.js +8 -0
  9. data/app/assets/javascripts/ariadne_view_components.js.map +1 -0
  10. data/app/assets/javascripts/clipboard-copy-component.d.ts +4 -0
  11. data/app/assets/javascripts/comment-component.d.ts +13 -0
  12. data/app/assets/javascripts/rich-text-area-component.d.ts +4 -0
  13. data/app/assets/javascripts/slideover-component.d.ts +9 -0
  14. data/app/assets/javascripts/time-ago-component.d.ts +1 -0
  15. data/app/assets/javascripts/time_ago_component.d.ts +1 -0
  16. data/app/assets/javascripts/tooltip-component.d.ts +24 -0
  17. data/app/assets/stylesheets/ariadne_view_components.css +6 -0
  18. data/app/assets/stylesheets/prosemirror.css +323 -0
  19. data/app/assets/stylesheets/tooltip-component.css +37 -0
  20. data/app/components/ariadne/ariadne-form.ts +96 -0
  21. data/app/components/ariadne/ariadne.ts +20 -0
  22. data/app/components/ariadne/base_button.rb +61 -0
  23. data/app/components/ariadne/base_component.rb +37 -0
  24. data/app/components/ariadne/blankslate_component.html.erb +26 -0
  25. data/app/components/ariadne/blankslate_component.rb +146 -0
  26. data/app/components/ariadne/body_component.rb +30 -0
  27. data/app/components/ariadne/button_component.html.erb +4 -0
  28. data/app/components/ariadne/button_component.rb +157 -0
  29. data/app/components/ariadne/clipboard-copy-component.ts +19 -0
  30. data/app/components/ariadne/clipboard_copy_component.html.erb +9 -0
  31. data/app/components/ariadne/clipboard_copy_component.rb +90 -0
  32. data/app/components/ariadne/comment-component.ts +55 -0
  33. data/app/components/ariadne/comment_component.html.erb +22 -0
  34. data/app/components/ariadne/comment_component.rb +57 -0
  35. data/app/components/ariadne/component.rb +128 -0
  36. data/app/components/ariadne/container_component.html.erb +3 -0
  37. data/app/components/ariadne/container_component.rb +25 -0
  38. data/app/components/ariadne/content.rb +12 -0
  39. data/app/components/ariadne/counter_component.rb +100 -0
  40. data/app/components/ariadne/flash_component.html.erb +31 -0
  41. data/app/components/ariadne/flash_component.rb +125 -0
  42. data/app/components/ariadne/flex_component.rb +49 -0
  43. data/app/components/ariadne/footer_component.html.erb +7 -0
  44. data/app/components/ariadne/footer_component.rb +23 -0
  45. data/app/components/ariadne/grid_component.html.erb +26 -0
  46. data/app/components/ariadne/grid_component.rb +66 -0
  47. data/app/components/ariadne/header_component.html.erb +29 -0
  48. data/app/components/ariadne/header_component.rb +114 -0
  49. data/app/components/ariadne/heading_component.rb +49 -0
  50. data/app/components/ariadne/heroicon_component.html.erb +4 -0
  51. data/app/components/ariadne/heroicon_component.rb +129 -0
  52. data/app/components/ariadne/image_component.rb +53 -0
  53. data/app/components/ariadne/inline_flex_component.html.erb +5 -0
  54. data/app/components/ariadne/inline_flex_component.rb +65 -0
  55. data/app/components/ariadne/link_component.rb +65 -0
  56. data/app/components/ariadne/list_component.html.erb +15 -0
  57. data/app/components/ariadne/list_component.rb +68 -0
  58. data/app/components/ariadne/main_component.rb +32 -0
  59. data/app/components/ariadne/narrow_container_component.html.erb +3 -0
  60. data/app/components/ariadne/narrow_container_component.rb +30 -0
  61. data/app/components/ariadne/panel_bar_component.html.erb +20 -0
  62. data/app/components/ariadne/panel_bar_component.rb +79 -0
  63. data/app/components/ariadne/pill_component.html.erb +3 -0
  64. data/app/components/ariadne/pill_component.rb +30 -0
  65. data/app/components/ariadne/rich-text-area-component.ts +32 -0
  66. data/app/components/ariadne/rich_text_area_component.html.erb +6 -0
  67. data/app/components/ariadne/rich_text_area_component.rb +35 -0
  68. data/app/components/ariadne/slideover-component.ts +26 -0
  69. data/app/components/ariadne/slideover_component.html.erb +11 -0
  70. data/app/components/ariadne/slideover_component.rb +81 -0
  71. data/app/components/ariadne/tab_bar_component.html.erb +3 -0
  72. data/app/components/ariadne/tab_bar_component.rb +45 -0
  73. data/app/components/ariadne/tab_component.html.erb +7 -0
  74. data/app/components/ariadne/tab_component.rb +43 -0
  75. data/app/components/ariadne/text.rb +25 -0
  76. data/app/components/ariadne/time-ago-component.ts +1 -0
  77. data/app/components/ariadne/time_ago_component.rb +56 -0
  78. data/app/components/ariadne/timeline_component.html.erb +19 -0
  79. data/app/components/ariadne/timeline_component.rb +34 -0
  80. data/app/components/ariadne/tooltip-component.ts +57 -0
  81. data/app/components/ariadne/tooltip_component.html.erb +4 -0
  82. data/app/components/ariadne/tooltip_component.rb +108 -0
  83. data/app/lib/ariadne/action_view_extensions/form_helper.rb +26 -0
  84. data/app/lib/ariadne/audited/dsl.rb +32 -0
  85. data/app/lib/ariadne/class_name_helper.rb +22 -0
  86. data/app/lib/ariadne/fetch_or_fallback_helper.rb +102 -0
  87. data/app/lib/ariadne/form_builder.rb +71 -0
  88. data/app/lib/ariadne/icon_helper.rb +47 -0
  89. data/app/lib/ariadne/join_style_arguments_helper.rb +14 -0
  90. data/app/lib/ariadne/logger_helper.rb +23 -0
  91. data/app/lib/ariadne/status/dsl.rb +41 -0
  92. data/app/lib/ariadne/tab_nav_helper.rb +35 -0
  93. data/app/lib/ariadne/tabbed_component_helper.rb +39 -0
  94. data/app/lib/ariadne/test_selector_helper.rb +20 -0
  95. data/app/lib/ariadne/underline_nav_helper.rb +44 -0
  96. data/app/lib/ariadne/view_helper.rb +22 -0
  97. data/exe/tailwindcss +21 -0
  98. data/exe/x86_64-linux/tailwindcss +0 -0
  99. data/lib/ariadne/view_components/commands.rb +90 -0
  100. data/lib/ariadne/view_components/constants.rb +53 -0
  101. data/lib/ariadne/view_components/engine.rb +75 -0
  102. data/lib/ariadne/view_components/linters.rb +3 -0
  103. data/lib/ariadne/view_components/statuses.rb +14 -0
  104. data/lib/ariadne/view_components/upstream.rb +20 -0
  105. data/lib/ariadne/view_components/version.rb +7 -0
  106. data/lib/ariadne/view_components.rb +61 -0
  107. data/lib/rubocop/config/default.yml +8 -0
  108. data/lib/rubocop/cop/ariadne/base_cop.rb +26 -0
  109. data/lib/rubocop/cop/ariadne/no_tag_memoize.rb +44 -0
  110. data/lib/rubocop/cop/ariadne.rb +3 -0
  111. data/lib/tasks/ariadne_view_components.rake +48 -0
  112. data/lib/tasks/build.rake +30 -0
  113. data/lib/tasks/coverage.rake +19 -0
  114. data/lib/tasks/custom_utilities.yml +310 -0
  115. data/lib/tasks/docs.rake +524 -0
  116. data/lib/tasks/helpers/ast_processor.rb +44 -0
  117. data/lib/tasks/helpers/ast_traverser.rb +77 -0
  118. data/lib/tasks/static.rake +15 -0
  119. data/lib/yard/docs_helper.rb +83 -0
  120. data/lib/yard/renders_many_handler.rb +19 -0
  121. data/lib/yard/renders_one_handler.rb +19 -0
  122. data/static/arguments.yml +619 -0
  123. data/static/assets/view-components.svg +18 -0
  124. data/static/audited_at.json +38 -0
  125. data/static/classes.yml +291 -0
  126. data/static/constants.json +426 -0
  127. data/static/statuses.json +38 -0
  128. data/static/tailwindcss.yml +727 -0
  129. data/tailwind.config.js +65 -0
  130. metadata +264 -0
@@ -0,0 +1,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