ariadne_view_components 0.0.10-x64-mingw-ucrt

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/x64-mingw-ucrt/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 %>