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.
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