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,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "view_component/version"
4
+
5
+ require "heroicons_helper"
6
+
7
+ module Ariadne
8
+ # @private
9
+ class Component < ViewComponent::Base
10
+ include ViewComponent::SlotableV2 unless ViewComponent::Base < ViewComponent::SlotableV2
11
+ include ClassNameHelper
12
+ include FetchOrFallbackHelper
13
+ include TestSelectorHelper
14
+ include JoinStyleArgumentsHelper
15
+ include ViewHelper
16
+ include Status::Dsl
17
+ include Audited::Dsl
18
+ include LoggerHelper
19
+ include Ariadne::ActionViewExtensions::FormHelper
20
+
21
+ BASE_HTML_CLASSES = "ariadne-h-full scroll-smooth ariadne-bg-white ariadne-antialiased"
22
+ BASE_BODY_CLASSES = "ariadne-flex ariadne-h-full ariadne-flex-col"
23
+ BASE_WRAPPER_CLASSES = "ariadne-flex ariadne-flex-col ariadne-h-screen ariadne-justify-between"
24
+ BASE_MAIN_CLASSES = "ariadne-flex-auto"
25
+
26
+ INVALID_ARIA_LABEL_TAGS = [:div, :span, :p].freeze
27
+
28
+ private def raise_on_invalid_options?
29
+ Rails.application.config.ariadne_view_components.raise_on_invalid_options
30
+ end
31
+
32
+ private def raise_on_invalid_aria?
33
+ Rails.application.config.ariadne_view_components.raise_on_invalid_aria
34
+ end
35
+
36
+ private def deprecated_component_warning(new_class: nil, version: nil)
37
+ return if silence_deprecations?
38
+
39
+ message = "#{self.class.name} is deprecated"
40
+ message += " and will be removed in v#{version}." if version
41
+ message += " Use #{new_class.name} instead." if new_class
42
+
43
+ ActiveSupport::Deprecation.warn(message)
44
+ end
45
+
46
+ private def aria(val, attributes)
47
+ attributes[:"aria-#{val}"] || attributes.dig(:aria, val.to_sym)
48
+ end
49
+
50
+ private def validate_aria_label!
51
+ aria_label = aria("label", @attributes)
52
+ raise ArgumentError, "`aria-label` is required." if aria_label.blank?
53
+ end
54
+
55
+ private def check_denylist(denylist = [], attributes = {})
56
+ if should_raise_error?
57
+
58
+ # Convert denylist from:
59
+ # { [:p, :pt] => "message" } to:
60
+ # { p: "message", pt: "message" }
61
+ unpacked_denylist =
62
+ denylist.each_with_object({}) do |(keys, value), memo|
63
+ keys.each { |key| memo[key] = value }
64
+ end
65
+
66
+ violations = unpacked_denylist.keys & attributes.keys
67
+
68
+ if violations.any?
69
+ message = "Found #{violations.count} #{"violation".pluralize(violations)}:"
70
+ violations.each do |violation|
71
+ message += "\n The #{violation} argument is not allowed here. #{unpacked_denylist[violation]}"
72
+ end
73
+
74
+ raise(ArgumentError, message)
75
+ end
76
+ end
77
+
78
+ attributes
79
+ end
80
+
81
+ private def validate_attributes(tag:, denylist_name: :attributes_denylist, attributes: {})
82
+ deny_single_argument(:class, "Use `classes` instead.", attributes)
83
+
84
+ if (denylist = attributes[denylist_name])
85
+ check_denylist(denylist, attributes)
86
+
87
+ # Remove :attributes_denylist key and any denied keys from system arguments
88
+ attributes.except!(denylist_name)
89
+ attributes.except!(*denylist.keys.flatten)
90
+ end
91
+
92
+ deny_aria_label(tag: tag, attributes: attributes)
93
+
94
+ attributes
95
+ end
96
+
97
+ private def deny_single_argument(key, help_text, attributes)
98
+ raise ArgumentError, "`#{key}` is an invalid argument. #{help_text}" \
99
+ if should_raise_error? && attributes.key?(key)
100
+
101
+ attributes.except!(key)
102
+ end
103
+
104
+ private def deny_aria_label(tag:, attributes:)
105
+ return attributes.except!(:skip_aria_label_check) if attributes[:skip_aria_label_check]
106
+ return if attributes[:role]
107
+ return unless INVALID_ARIA_LABEL_TAGS.include?(tag)
108
+
109
+ deny_aria_key(
110
+ :label,
111
+ "Don't use `aria-label` on `#{tag}` elements. See https://www.tpgi.com/short-note-on-aria-label-aria-labelledby-and-aria-describedby/",
112
+ attributes
113
+ )
114
+ end
115
+
116
+ private def deny_aria_key(key, help_text, attributes)
117
+ raise ArgumentError, help_text if should_raise_aria_error? && aria(key, attributes)
118
+ end
119
+
120
+ private def should_raise_error?
121
+ raise_on_invalid_options? && !ENV["ARIADNE_WARNINGS_DISABLED"]
122
+ end
123
+
124
+ private def should_raise_aria_error?
125
+ raise_on_invalid_aria? && !ENV["ARIADNE_WARNINGS_DISABLED"]
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,3 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |component| %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # The container wraps the majority, if not all, of the content on a page.
5
+ class ContainerComponent < Ariadne::Component
6
+ DEFAULT_CLASSES = "ariadne-px-4 sm:ariadne-px-6 lg:ariadne-px-8"
7
+
8
+ # @example Default
9
+ # <%= render(Ariadne::ContainerComponent.new) do |container| %>
10
+ # <%= render(Ariadne::ButtonComponent.new) { "Click me!" } %>
11
+ # <% end %>
12
+ #
13
+ # @param classes [String] <%= link_to_classes_docs %>
14
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
15
+ def initialize(classes: "", attributes: {})
16
+ @tag = :div
17
+ @classes = class_names(
18
+ DEFAULT_CLASSES,
19
+ classes
20
+ )
21
+
22
+ @attributes = attributes
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ #
5
+ # Use `Content` as a helper to render content passed to a slot without adding any tags.
6
+ #
7
+ class Content < Ariadne::Component
8
+ def call
9
+ content
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `CounterComponent` to add a count to navigational elements and buttons.
5
+ #
6
+ # @accessibility
7
+ # Always use `CounterComponent` with adjacent text that provides supplementary information regarding what the count is for. For instance, `Counter`
8
+ # should be accompanied with text such as `issues` or `pull requests`.
9
+ #
10
+ class CounterComponent < Ariadne::Component
11
+ DEFAULT_CLASSES = "ariadne-inline-flex ariadne-items-center ariadne-p-1 ariadne-border ariadne-border-transparent ariadne-rounded-full ariadne-shadow-sm focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2"
12
+
13
+ # @example Default
14
+ # <%= render(Ariadne::CounterComponent.new(count: 25)) %>
15
+ #
16
+ # @example Schemes
17
+ # <%= render(Ariadne::CounterComponent.new(count: 25)) %>
18
+ # <%= render(Ariadne::CounterComponent.new(count: 25)) %>
19
+ #
20
+ # @param tag [Symbol, String] The rendered tag name
21
+ # @param classes [String] <%= link_to_classes_docs %>
22
+ # @param count [Integer, Float::INFINITY, nil] The number to be displayed (e.x. # of issues, pull requests)
23
+ # @param limit [Integer, nil] Maximum value to display. Pass `nil` for no limit. (e.x. if `count` == 6,000 and `limit` == 5000, counter will display "5,000+")
24
+ # @param hide_if_zero [Boolean] If true, a `hidden` attribute is added to the counter if `count` is zero.
25
+ # @param text [String] Text to display instead of count.
26
+ # @param round [Boolean] Whether to apply rounding logic to value.
27
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
28
+ def initialize(
29
+ tag: :span,
30
+ count: 0,
31
+ limit: 9_000,
32
+ hide_if_zero: false,
33
+ text: "",
34
+ round: false,
35
+ classes: "",
36
+ attributes: {}
37
+ )
38
+ @count = count
39
+ @limit = limit
40
+ @hide_if_zero = hide_if_zero
41
+ @text = text
42
+ @round = round
43
+ @attributes = attributes
44
+
45
+ @has_limit = !@limit.nil?
46
+
47
+ @tag = check_incoming_tag(:span, tag)
48
+
49
+ @attributes[:title] = title
50
+
51
+ @classes = class_names(
52
+ DEFAULT_CLASSES,
53
+ classes
54
+ )
55
+ @attributes[:hidden] = true if count == 0 && hide_if_zero
56
+ end
57
+
58
+ def call
59
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) { value }
60
+ end
61
+
62
+ private def title
63
+ if @text.present?
64
+ @text
65
+ elsif @count.nil?
66
+ "Not available"
67
+ elsif @count == Float::INFINITY
68
+ "Infinity"
69
+ else
70
+ count = @count.to_i
71
+ str = number_with_delimiter(@has_limit ? [count, @limit].min : count)
72
+ str += "+" if @has_limit && count > @limit
73
+ str
74
+ end
75
+ end
76
+
77
+ private def value
78
+ if @text.present?
79
+ @text
80
+ elsif @count.nil?
81
+ "" # CSS will hide it
82
+ elsif @count == Float::INFINITY
83
+ "∞"
84
+ else
85
+ if @round
86
+ count = @has_limit ? [@count.to_i, @limit].min : @count.to_i
87
+ precision = count.between?(100_000, 999_999) ? 0 : 1
88
+ units = { thousand: "k", million: "m", billion: "b" }
89
+ str = number_to_human(count, precision: precision, significant: false, units: units, format: "%n%u")
90
+ else
91
+ @count = @count.to_i
92
+ str = number_with_delimiter(@has_limit ? [@count, @limit].min : @count)
93
+ end
94
+
95
+ str += "+" if @has_limit && @count.to_i > @limit
96
+ str
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,31 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do %>
2
+ <div class="ariadne-rounded-md ariadne-p-4 <%= BG_SCHEME_CLASS_MAPPINGS[@scheme] %>">
3
+ <div class="ariadne-flex">
4
+ <div class="ariadne-flex-shrink-0">
5
+ <%= icon %>
6
+ </div>
7
+ <div class="ariadne-ml-3">
8
+ <div class="ariadne-mt-2 ariadne-text-sm <%= CONTENT_SCHEME_CLASS_MAPPINGS[@scheme] %>">
9
+ <p><%= content %></p>
10
+ </div>
11
+ <% if has_action? %>
12
+ <div class="ariadne-mt-4 ariadne-pt-5">
13
+ <div class="ariadne--mx-2 ariadne--my-1.5 ariadne-flex">
14
+ <%= action %>
15
+ </div>
16
+ </div>
17
+ <% end %>
18
+ </div>
19
+ <% if dismissible? %>
20
+ <div class="ariadne-pl-3">
21
+ <div class="ariadne--mx-1.5 ariadne--my-1.5">
22
+ <button type="button" class="ariadne-inline-flex ariadne-rounded-md ariadne-p-1.5 focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 <%= dismissible_classes %>">
23
+ <span class="ariadne-sr-only">Dismiss</span>
24
+ <%= ariadne_heroicon icon: :"x-mark", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE %>
25
+ </button>
26
+ </div>
27
+ </div>
28
+ <% end %>
29
+ </div>
30
+ </div>
31
+ <% end %>
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `FlashComponent` to inform users of successful messages, pending actions, or urgent notices
5
+ class FlashComponent < Ariadne::Component
6
+ include IconHelper
7
+
8
+ DEFAULT_SCHEME = :default
9
+
10
+ DISMISSIBLE_SCHEME_CLASS_MAPPINGS = {
11
+ default: "ariadne-text-blue-500 ariadne-bg-blue-50 hover:ariadne-bg-blue-100 focus:ariadne-ring-offset-blue-50 focus:ariadne-ring-blue-600",
12
+ info: "ariadne-text-blue-500 ariadne-bg-blue-50 hover:ariadne-bg-blue-100 focus:ariadne-ring-offset-blue-50 focus:ariadne-ring-blue-600",
13
+ success: "ariadne-text-green-500 ariadne-bg-green-50 hover:ariadne-bg-green-100 focus:ariadne-ring-offset-green-50 focus:ariadne-ring-green-600",
14
+ warning: "ariadne-text-yellow-500 ariadne-bg-yellow-50 hover:ariadne-bg-yellow-100 focus:ariadne-ring-offset-yellow-50 focus:ariadne-ring-yellow-600",
15
+ danger: "ariadne-text-red-500 ariadne-bg-red-50 hover:ariadne-bg-red-100 focus:ariadne-ring-offset-red-50 focus:ariadne-ring-red-600",
16
+ }.freeze
17
+ VALID_DISMISSIBLE_SCHEMES = DISMISSIBLE_SCHEME_CLASS_MAPPINGS.keys.freeze
18
+
19
+ BG_SCHEME_CLASS_MAPPINGS = {
20
+ default: "ariadne-bg-blue-50",
21
+ info: "ariadne-bg-blue-50",
22
+ success: "ariadne-bg-green-50",
23
+ warning: "ariadne-bg-yellow-50",
24
+ danger: "ariadne-bg-red-50",
25
+ }.freeze
26
+ VALID_BG_SCHEMES = BG_SCHEME_CLASS_MAPPINGS.keys.freeze
27
+
28
+ CONTENT_SCHEME_CLASS_MAPPINGS = {
29
+ default: "ariadne-text-blue-700",
30
+ info: "ariadne-text-blue-700",
31
+ success: "ariadne-text-green-700",
32
+ warning: "ariadne-text-yellow-700",
33
+ danger: "ariadne-text-red-700",
34
+ }.freeze
35
+ VALID_CONTENT_SCHEMES = CONTENT_SCHEME_CLASS_MAPPINGS.keys.freeze
36
+
37
+ # Optional visuals appearing to the left of the flash banner.
38
+ #
39
+ # Use:
40
+ #
41
+ # - `icon` for a <%= link_to_component(Ariadne::HeroiconComponent) %>.
42
+ #
43
+ # @option params [Symbol] :icon The rendered tag name
44
+ # @option params [Symbol] :icon Name of <%= link_to_heroicons %> to use.
45
+ # @option params [Symbol] :variant <%= one_of(HeroiconsHelper::Icon::VALID_VARIANTS, sort: false) %>
46
+ # @option params [String] :classes <%= link_to_classes_docs %>
47
+ # @option params [Hash] :attributes Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
48
+ renders_one :icon, lambda { |tag: :svg, icon:, variant:, classes: "", attributes: {}|
49
+ @icon = icon
50
+ @variant = variant
51
+
52
+ tag = check_incoming_tag(:svg, tag)
53
+ Ariadne::HeroiconComponent.new(tag: tag, icon: icon, variant: variant, classes: classes, attributes: attributes)
54
+ }
55
+
56
+ # Optional action content showed at the bottom of the component.
57
+ #
58
+ # @param tag [Symbol, String] The rendered tag name
59
+ # @param classes [String] <%= link_to_classes_docs %>
60
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
61
+ renders_one :action, lambda { |tag: :div, classes: "", attributes: {}|
62
+ tag = check_incoming_tag(:div, tag)
63
+
64
+ actual_classes = class_names(classes)
65
+
66
+ Ariadne::BaseComponent.new(tag: tag, classes: actual_classes, attributes: attributes)
67
+ }
68
+
69
+ # @example Schemes
70
+ # <%= render(Ariadne::FlashComponent.new) { "This is a flash message!" } %>
71
+ # <%= render(Ariadne::FlashComponent.new(scheme: :warning)) { "This is a warning flash message!" } %>
72
+ # <%= render(Ariadne::FlashComponent.new(scheme: :danger)) { "This is a danger flash message!" } %>
73
+ # <%= render(Ariadne::FlashComponent.new(scheme: :success)) { "This is a success flash message!" } %>
74
+ #
75
+ # @example Dismissible
76
+ # <%= render(Ariadne::FlashComponent.new(dismissible: true)) { "This is a dismissible flash message!" } %>
77
+ #
78
+ # @example Icon
79
+ # <%= render(Ariadne::FlashComponent.new) do |component| %>
80
+ # <% component.icon(icon: :"user-group", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE) %>
81
+ # Look at this icon.
82
+ # <% end %>
83
+ #
84
+ # @example With actions
85
+ # <%= render(Ariadne::FlashComponent.new) do |component| %>
86
+ # <% component.action do %>
87
+ # <%= render(Ariadne::ButtonComponent.new(size: :sm)) { "Take action" } %>
88
+ # <% end %>
89
+ # This is a flash message with actions!
90
+ # <% end %>
91
+ #
92
+ # @param tag [Symbol, String] The rendered tag name.
93
+ # @param dismissible [Boolean] Whether the component can be dismissed with an X button.
94
+ # @param icon [Symbol, String] Name of <%= link_to_heroicons %> to use.
95
+ # @param scheme [Symbol] <%= one_of(Ariadne::FlashComponent::VALID_CONTENT_SCHEMES) %>
96
+ # @param classes [String] <%= link_to_classes_docs %>
97
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
98
+ def initialize(tag: :div, dismissible: false, scheme: DEFAULT_SCHEME, classes: "", attributes: {})
99
+ @dismissible = dismissible
100
+
101
+ @tag = check_incoming_tag(:div, tag)
102
+
103
+ @scheme = fetch_or_raise(VALID_CONTENT_SCHEMES, scheme)
104
+
105
+ @classes = class_names(
106
+ CONTENT_SCHEME_CLASS_MAPPINGS[@scheme],
107
+ classes
108
+ )
109
+
110
+ @attributes = attributes
111
+ end
112
+ # TODO: test this
113
+ private def has_action?
114
+ action.present?
115
+ end
116
+ # TODO: test this
117
+ private def dismissible?
118
+ @dismissible.present?
119
+ end
120
+
121
+ private def dismissible_classes
122
+ DISMISSIBLE_SCHEME_CLASS_MAPPINGS[@scheme]
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Adds a flex container to the page.
5
+ class FlexComponent < Ariadne::Component
6
+ DEFAULT_TAG = :div
7
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
8
+
9
+ DEFAULT_CLASSES = "flex"
10
+
11
+ VALID_TYPES = [:row, :column, :row_reverse, :column_reverse].freeze
12
+
13
+ # @example Default
14
+ #
15
+ # <%= render(Ariadne::FlexComponent.new(type: :column)) { "Example" } %>
16
+ #
17
+ # @param tag [Symbol, String] The rendered tag name.
18
+ # @param type [Symbol] <%= one_of(Ariadne::FlexComponent::VALID_TYPES) %>
19
+ # @param classes [String] <%= link_to_classes_docs %>
20
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
21
+ def initialize(tag: DEFAULT_TAG, type:, classes: "", attributes: {})
22
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
23
+ @type = fetch_or_raise(VALID_TYPES, type)
24
+
25
+ flex_class = case @type
26
+ when :row
27
+ "ariadne-flex-row"
28
+ when :column
29
+ "ariadne-flex-col"
30
+ when :row_reverse
31
+ "ariadne-flex-row-reverse"
32
+ when :column_reverse
33
+ "ariadne-flex-col-reverse"
34
+ end
35
+
36
+ @classes = class_names(
37
+ DEFAULT_CLASSES,
38
+ classes,
39
+ flex_class
40
+ )
41
+
42
+ @attributes = attributes
43
+ end
44
+
45
+ def call
46
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) { content }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: :footer, classes: @classes, attributes: @attributes) do |footer| %>
2
+ <%= render Ariadne::ContainerComponent.new do |container| %>
3
+ <p class="ariadne-mt-6 ariadne-text-sm text-slate-500 sm:ariadne-mt-0">
4
+ <%= content %>
5
+ </p>
6
+ <% end %>
7
+ <% end %>
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Adds a footer to the bottom of every page.
5
+ class FooterComponent < Ariadne::Component
6
+ DEFAULT_CLASSES = "ariadne-py-5"
7
+
8
+ # @example Default
9
+ #
10
+ # <%= render(Ariadne::FooterComponent.new) %>
11
+ #
12
+ # @param classes [String] <%= link_to_classes_docs %>
13
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
14
+ def initialize(classes: "", attributes: {})
15
+ @classes = class_names(
16
+ DEFAULT_CLASSES,
17
+ classes
18
+ )
19
+
20
+ @attributes = attributes
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: :ul, classes: @classes, attributes: @attributes) do |grid| %>
2
+ <% items.each do |item| %>
3
+ <li class="<%= item.classes %> <%= item.has_href? ? Ariadne::GridComponent::DEFAULT_LINK_COLOR_CLASSES : "ariadne-bg-white" %>">
4
+ <% if item.has_href? %>
5
+ <%= render Ariadne::LinkComponent.new(href: item.href) do %>
6
+ <%= item.entry %>
7
+ <% end %>
8
+ <% if item.actions.any? %>
9
+ <div>
10
+ <div class="ariadne--mt-px ariadne-flex ariadne-divide-x">
11
+ <% item.actions.each_with_index do |action, idx| %>
12
+ <div class="<%= idx.zero? ? '' : 'ariadne--ml-px ' %>w-0 ariadne-flex-1 ariadne-flex">
13
+ <%= action %>
14
+ </div>
15
+ <% end %>
16
+ </div>
17
+ </div>
18
+ <% end %>
19
+ <% else %>
20
+ <div class="ariadne-flex-1 ariadne-flex ariadne-flex-col ariadne-p-8">
21
+ <%= item.entry %>
22
+ </div>
23
+ <% end %>
24
+ </li>
25
+ <% end %>
26
+ <% end %>
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Arranges items as a ariadne-grid.
5
+ class GridComponent < Ariadne::Component
6
+ DEFAULT_CLASSES = "ariadne-grid ariadne-gap-6 sm:ariadne-grid-cols-2 lg:ariadne-grid-cols-3"
7
+
8
+ DEFAULT_LINK_COLOR_CLASSES = "ariadne-text-button-text-color ariadne-bg-button-bg-color focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500"
9
+
10
+ # The items to show in the ariadne-grid.
11
+ renders_many :items, "GridItem"
12
+
13
+ # @example Default
14
+ #
15
+ # <%= render(Ariadne::GridComponent.new) { "Example" } %>
16
+ #
17
+ # @param classes [String] <%= link_to_classes_docs %>
18
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
19
+ def initialize(classes: "", attributes: {})
20
+ @classes = class_names(
21
+ DEFAULT_CLASSES,
22
+ classes
23
+ )
24
+
25
+ default_attributes = { role: "list" }
26
+ @attributes = default_attributes.merge(attributes)
27
+ end
28
+
29
+ # This component is part of `GridComponent` and should not be
30
+ # used as a standalone component.
31
+ class GridItem < Ariadne::Component
32
+ DEFAULT_ITEM_CLASSES = "ariadne-flex ariadne-flex-col ariadne-text-center ariadne-rounded-lg ariadne-shadow ariadne-my-4 text-black-700 ariadne-border-black"
33
+
34
+ DEFAULT_ENTRY_CLASSES = "group ariadne-flex-1 ariadne-flex ariadne-flex-col ariadne-p-8 hover:ariadne-bg-button-hover-color hover:ariadne-text-gray-500 ariadne-rounded-lg"
35
+ renders_one :entry, lambda { |classes: "", &block|
36
+ view_context.capture do
37
+ render(Ariadne::BaseComponent.new(tag: :div, classes: classes)) { block&.call }
38
+ end
39
+ }
40
+
41
+ DEFAULT_ACTION_LINK_CLASSES = "text-button-text-color ariadne-relative ariadne--mr-px ariadne-w-0 ariadne-flex-1 ariadne-inline-flex ariadne-items-center ariadne-justify-center ariadne-py-4 ariadne-text-sm ariadne-font-medium ariadne-border ariadne-border-transparent ariadne-rounded-bl-lg ariadne-rounded-lg"
42
+ renders_many :actions, lambda { |href:, icon:, label:, size: Ariadne::HeroiconComponent::SIZE_DEFAULT, variant: HeroiconsHelper::Icon::VARIANT_SOLID, classes: "", attributes: {}, text_classes: ""|
43
+ actual_classes = class_names(DEFAULT_ACTION_LINK_CLASSES, classes)
44
+ render(Ariadne::LinkComponent.new(href: href, classes: actual_classes, attributes: attributes)) do
45
+ render(Ariadne::HeroiconComponent.new(icon: icon, size: size, variant: variant, text_classes: text_classes)) { label }
46
+ end
47
+ }
48
+
49
+ attr_reader :href, :classes, :attributes
50
+
51
+ def initialize(href: nil, classes: "", attributes: {})
52
+ @href = href
53
+ @classes = class_names(DEFAULT_ITEM_CLASSES, classes)
54
+ @attributes = attributes
55
+ end
56
+
57
+ def has_href?
58
+ @href.present?
59
+ end
60
+
61
+ def call
62
+ render(Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes))
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,29 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: :header, classes: @classes, attributes: @attributes) do |header| %>
2
+ <%= render Ariadne::ContainerComponent.new do |container| %>
3
+ <nav class="ariadne-relative ariadne-z-50 ariadne-flex ariadne-justify-between">
4
+ <div class="ariadne-flex ariadne-items-center md:ariadne-gap-x-12">
5
+ <% if has_logo? %>
6
+ <%= render Ariadne::LinkComponent.new(href: @href, classes: "ariadne-text-billy-purple") do %>
7
+ <% if has_image_logo? %>
8
+ <span class="ariadne-sr-only"><%= content %></span>
9
+ <% end %>
10
+ <%= logo %>
11
+ <% end %>
12
+ <% end %>
13
+ <div class="md:ariadne-flex md:ariadne-gap-x-6">
14
+ <% navigation_links.each do |link| %>
15
+ <%= link %>
16
+ <% end %>
17
+ </div>
18
+ </div>
19
+ <div class="ariadne-flex ariadne-items-center ariadne-gap-x-5 md:ariadne-gap-x-8">
20
+ <div class="md:ariadne-block">
21
+ <% action_links.each do |link| %>
22
+ <%= link %>
23
+ <% end %>
24
+ </div>
25
+ <%= signup_link if has_signup_link? %>
26
+ <%= profile_link if has_profile_link? %>
27
+ </nav>
28
+ <% end %>
29
+ <% end %>