ariadne_view_components 0.0.10-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +49 -0
  3. data/README.md +73 -0
  4. data/app/assets/config/manifest.js +2 -0
  5. data/app/assets/javascripts/ariadne-form-with.d.ts +20 -0
  6. data/app/assets/javascripts/ariadne-form.d.ts +22 -0
  7. data/app/assets/javascripts/ariadne.d.ts +1 -0
  8. data/app/assets/javascripts/ariadne_view_components.js +8 -0
  9. data/app/assets/javascripts/ariadne_view_components.js.map +1 -0
  10. data/app/assets/javascripts/clipboard-copy-component.d.ts +4 -0
  11. data/app/assets/javascripts/comment-component.d.ts +13 -0
  12. data/app/assets/javascripts/rich-text-area-component.d.ts +4 -0
  13. data/app/assets/javascripts/slideover-component.d.ts +9 -0
  14. data/app/assets/javascripts/time-ago-component.d.ts +1 -0
  15. data/app/assets/javascripts/time_ago_component.d.ts +1 -0
  16. data/app/assets/javascripts/tooltip-component.d.ts +24 -0
  17. data/app/assets/stylesheets/ariadne_view_components.css +6 -0
  18. data/app/assets/stylesheets/prosemirror.css +323 -0
  19. data/app/assets/stylesheets/tooltip-component.css +37 -0
  20. data/app/components/ariadne/ariadne-form.ts +96 -0
  21. data/app/components/ariadne/ariadne.ts +20 -0
  22. data/app/components/ariadne/base_button.rb +61 -0
  23. data/app/components/ariadne/base_component.rb +37 -0
  24. data/app/components/ariadne/blankslate_component.html.erb +26 -0
  25. data/app/components/ariadne/blankslate_component.rb +146 -0
  26. data/app/components/ariadne/body_component.rb +30 -0
  27. data/app/components/ariadne/button_component.html.erb +4 -0
  28. data/app/components/ariadne/button_component.rb +157 -0
  29. data/app/components/ariadne/clipboard-copy-component.ts +19 -0
  30. data/app/components/ariadne/clipboard_copy_component.html.erb +9 -0
  31. data/app/components/ariadne/clipboard_copy_component.rb +90 -0
  32. data/app/components/ariadne/comment-component.ts +55 -0
  33. data/app/components/ariadne/comment_component.html.erb +22 -0
  34. data/app/components/ariadne/comment_component.rb +57 -0
  35. data/app/components/ariadne/component.rb +128 -0
  36. data/app/components/ariadne/container_component.html.erb +3 -0
  37. data/app/components/ariadne/container_component.rb +25 -0
  38. data/app/components/ariadne/content.rb +12 -0
  39. data/app/components/ariadne/counter_component.rb +100 -0
  40. data/app/components/ariadne/flash_component.html.erb +31 -0
  41. data/app/components/ariadne/flash_component.rb +125 -0
  42. data/app/components/ariadne/flex_component.rb +49 -0
  43. data/app/components/ariadne/footer_component.html.erb +7 -0
  44. data/app/components/ariadne/footer_component.rb +23 -0
  45. data/app/components/ariadne/grid_component.html.erb +26 -0
  46. data/app/components/ariadne/grid_component.rb +66 -0
  47. data/app/components/ariadne/header_component.html.erb +29 -0
  48. data/app/components/ariadne/header_component.rb +114 -0
  49. data/app/components/ariadne/heading_component.rb +49 -0
  50. data/app/components/ariadne/heroicon_component.html.erb +4 -0
  51. data/app/components/ariadne/heroicon_component.rb +129 -0
  52. data/app/components/ariadne/image_component.rb +53 -0
  53. data/app/components/ariadne/inline_flex_component.html.erb +5 -0
  54. data/app/components/ariadne/inline_flex_component.rb +65 -0
  55. data/app/components/ariadne/link_component.rb +65 -0
  56. data/app/components/ariadne/list_component.html.erb +15 -0
  57. data/app/components/ariadne/list_component.rb +68 -0
  58. data/app/components/ariadne/main_component.rb +32 -0
  59. data/app/components/ariadne/narrow_container_component.html.erb +3 -0
  60. data/app/components/ariadne/narrow_container_component.rb +30 -0
  61. data/app/components/ariadne/panel_bar_component.html.erb +20 -0
  62. data/app/components/ariadne/panel_bar_component.rb +79 -0
  63. data/app/components/ariadne/pill_component.html.erb +3 -0
  64. data/app/components/ariadne/pill_component.rb +30 -0
  65. data/app/components/ariadne/rich-text-area-component.ts +32 -0
  66. data/app/components/ariadne/rich_text_area_component.html.erb +6 -0
  67. data/app/components/ariadne/rich_text_area_component.rb +35 -0
  68. data/app/components/ariadne/slideover-component.ts +26 -0
  69. data/app/components/ariadne/slideover_component.html.erb +11 -0
  70. data/app/components/ariadne/slideover_component.rb +81 -0
  71. data/app/components/ariadne/tab_bar_component.html.erb +3 -0
  72. data/app/components/ariadne/tab_bar_component.rb +45 -0
  73. data/app/components/ariadne/tab_component.html.erb +7 -0
  74. data/app/components/ariadne/tab_component.rb +43 -0
  75. data/app/components/ariadne/text.rb +25 -0
  76. data/app/components/ariadne/time-ago-component.ts +1 -0
  77. data/app/components/ariadne/time_ago_component.rb +56 -0
  78. data/app/components/ariadne/timeline_component.html.erb +19 -0
  79. data/app/components/ariadne/timeline_component.rb +34 -0
  80. data/app/components/ariadne/tooltip-component.ts +57 -0
  81. data/app/components/ariadne/tooltip_component.html.erb +4 -0
  82. data/app/components/ariadne/tooltip_component.rb +108 -0
  83. data/app/lib/ariadne/action_view_extensions/form_helper.rb +26 -0
  84. data/app/lib/ariadne/audited/dsl.rb +32 -0
  85. data/app/lib/ariadne/class_name_helper.rb +22 -0
  86. data/app/lib/ariadne/fetch_or_fallback_helper.rb +102 -0
  87. data/app/lib/ariadne/form_builder.rb +71 -0
  88. data/app/lib/ariadne/icon_helper.rb +47 -0
  89. data/app/lib/ariadne/join_style_arguments_helper.rb +14 -0
  90. data/app/lib/ariadne/logger_helper.rb +23 -0
  91. data/app/lib/ariadne/status/dsl.rb +41 -0
  92. data/app/lib/ariadne/tab_nav_helper.rb +35 -0
  93. data/app/lib/ariadne/tabbed_component_helper.rb +39 -0
  94. data/app/lib/ariadne/test_selector_helper.rb +20 -0
  95. data/app/lib/ariadne/underline_nav_helper.rb +44 -0
  96. data/app/lib/ariadne/view_helper.rb +22 -0
  97. data/exe/tailwindcss +21 -0
  98. data/exe/x86_64-darwin/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,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `Blankslate` when there is a lack of content within a page or section. Use as placeholder to tell users why something isn't there.
5
+ #
6
+ # @accessibility
7
+ # - The blankslate uses a semantic heading that must be set at the appropriate level based on the hierarchy of the page.
8
+ # - All blankslate visuals have been programmed as decorative images, using `aria-hidden=”true”` and `img alt=””`, which will hide the visual from screen reader users.
9
+ # - The blankslate supports a primary and secondary action. Both actions have been built as semantic links with primary and secondary styling.
10
+ # - `secondary_action` text should be meaningful out of context and clearly describe the destination. Avoid using vague text like, "Learn more" or "Click here".
11
+ class BlankslateComponent < Ariadne::Component
12
+ DEFAULT_TAG = :div
13
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
14
+
15
+ DEFAULT_CLASSES = "ariadne-text-center"
16
+
17
+ # Optional visual that renders an <%= link_to_component(Ariadne::HeroiconComponent) %>.
18
+ #
19
+ # @param tag [Symbol, String] The rendered tag name
20
+ # @param icon [String] Name of <%= link_to_heroicons %> to use.
21
+ # @param size [Symbol] <%= one_of(Ariadne::HeroiconComponent::SIZE_MAPPINGS, sort: false) %>
22
+ # @param variant [String] <%= one_of(HeroiconsHelper::Icon::VALID_VARIANTS, sort: false) %>
23
+ # @param classes [String] <%= link_to_classes_docs %>
24
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
25
+ renders_one :icon, lambda { |tag: :svg, icon:, size: Ariadne::HeroiconComponent::SIZE_DEFAULT, variant: HeroiconsHelper::Icon::VARIANT_SOLID, classes: "", attributes: {}|
26
+ @icon = icon
27
+ @variant = variant
28
+ tag = check_incoming_tag(:svg, tag)
29
+ Ariadne::HeroiconComponent.new(tag: tag, icon: icon, size: size, variant: variant, classes: classes, attributes: attributes)
30
+ }
31
+
32
+ # Optional visual that renders an <%= link_to_component(Ariadne::ImageComponent) %>.
33
+ #
34
+ # @param tag [Symbol, String] The rendered tag name
35
+ # @param src [String] The source url of the image.
36
+ # @param alt [String] Specifies an alternate text for the image.
37
+ # @param lazy [Boolean] Whether or not to lazily load the image.
38
+ # @param classes [String] <%= link_to_classes_docs %>
39
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
40
+ DEFAULT_IMAGE_SIZE = 56
41
+ renders_one :image, lambda { |tag: Ariadne::ImageComponent::DEFAULT_TAG, src:, alt:, lazy: false, classes: "", attributes: {}|
42
+ attributes[:height] = DEFAULT_IMAGE_SIZE
43
+ attributes[:width] = DEFAULT_IMAGE_SIZE
44
+
45
+ Ariadne::ImageComponent.new(tag: tag, src: src, alt: alt, lazy: lazy, classes: classes, attributes: attributes)
46
+ }
47
+
48
+ # Required heading.
49
+ #
50
+ # @param tag [Symbol, String] <%= one_of(Ariadne::HeadingComponent::TAG_OPTIONS) %>
51
+ # @param classes [String] <%= link_to_classes_docs %>
52
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
53
+ DEFAULT_HEADING_CLASSES = "text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl"
54
+ renders_one :heading, lambda { |tag: :h1, classes: "", attributes: {}|
55
+ actual_classes = class_names(DEFAULT_HEADING_CLASSES, classes)
56
+
57
+ Ariadne::HeadingComponent.new(tag: tag, classes: actual_classes, attributes: attributes)
58
+ }
59
+
60
+ # Optional description.
61
+ #
62
+ # - The description should always be informative and actionable.
63
+ # - Don't use phrases like "You can".
64
+ #
65
+ # @param tag [Symbol, String] The rendered tag name
66
+ # @param classes [String] <%= link_to_classes_docs %>
67
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
68
+ renders_one :description, lambda { |tag: :p, classes: "", attributes: {}|
69
+ actual_tag = check_incoming_tag(:p, tag)
70
+
71
+ Ariadne::BaseComponent.new(tag: actual_tag, classes: classes, attributes: attributes)
72
+ }
73
+
74
+ # Optional primary action
75
+ #
76
+ # The `primary_action` slot renders an anchor link which is visually styled as a button to provide more emphasis to the
77
+ # Blankslate's primary action.
78
+ #
79
+ # @param href [String] URL to be used for the primary action.
80
+ # @param classes [String] <%= link_to_classes_docs %>
81
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
82
+ renders_one :primary_action, lambda { |href: nil, tag: :a, method: nil, size: :md, scheme: :default, classes: "", attributes: {}|
83
+ attributes[:href] = href
84
+ attributes[:method] = method
85
+
86
+ Ariadne::ButtonComponent.new(tag: tag,
87
+ type: Ariadne::BaseButton::DEFAULT_TYPE,
88
+ scheme: scheme,
89
+ size: size,
90
+ classes: classes,
91
+ attributes: attributes)
92
+ }
93
+
94
+ # Optional secondary action
95
+ #
96
+ # The `secondary_action` slot renders a normal anchor link, which can be used to redirect the user to additional information
97
+ # (e.g. Help documentation).
98
+ #
99
+ # @param href [String] URL to be used for the secondary action.
100
+ # @param classes [String] <%= link_to_classes_docs %>
101
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
102
+ renders_one :secondary_action, lambda { |href:, classes: "", attributes: {}|
103
+ attributes[:href] = href
104
+
105
+ Ariadne::LinkComponent.new(href: href, classes: classes, attributes: attributes)
106
+ }
107
+
108
+ # @example Basic
109
+ # <%= render Ariadne::BlankslateComponent.new do |c| %>
110
+ # <% c.heading(tag: :h2).with_content("heading") %>
111
+ # <% c.description { "Description"} %>
112
+ # <% end %>
113
+ #
114
+ # @example Icon
115
+ # @description
116
+ # Add an `icon` to give additional context. Refer to the [Heroicons](https://heroicons.com) documentation to choose an icon.
117
+ # @code
118
+ # <%= render Ariadne::BlankslateComponent.new do |c| %>
119
+ # <% c.icon(icon: :"globe-europe-africa") %>
120
+ # <% c.heading(tag: :h2).with_content("heading") %>
121
+ # <% c.description { "Description"} %>
122
+ # <% end %>
123
+ #
124
+ # @example Using an image
125
+ # @description
126
+ # Add an `image` to give context that an Octicon couldn't.
127
+ # @code
128
+ # <%= render Ariadne::BlankslateComponent.new do |c| %>
129
+ # <% c.image(src: "https://github.githubassets.com/images/modules/site/features/security-icon.svg", alt: "Security - secure vault") %>
130
+ # <% c.heading(tag: :h2).with_content("heading") %>
131
+ # <% end %>
132
+ #
133
+ # @param tag [Symbol, String] The rendered tag name
134
+ # @param classes [String] <%= link_to_classes_docs %>
135
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
136
+ def initialize(tag: DEFAULT_TAG, classes: "", attributes: {})
137
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
138
+ @classes = class_names(DEFAULT_CLASSES, classes)
139
+ @attributes = attributes
140
+ end
141
+
142
+ def render?
143
+ heading.present?
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Add a general description of component here
5
+ # Add additional usage considerations or best practices that may aid the user to use the component correctly.
6
+ # @accessibility Add any accessibility considerations
7
+ class BodyComponent < Ariadne::Component
8
+ DEFAULT_CLASSES = "ariadne-flex ariadne-flex-col ariadne-min-h-screen"
9
+
10
+ # @example Default
11
+ #
12
+ # <%= render(Ariadne::BodyComponent.new) { "Example" } %>
13
+ #
14
+ # @param classes [String] <%= link_to_classes_docs %>
15
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
16
+ def initialize(classes: "", attributes: {})
17
+ @tag = :body
18
+ @classes = class_names(
19
+ DEFAULT_CLASSES,
20
+ classes
21
+ )
22
+
23
+ @attributes = attributes
24
+ end
25
+
26
+ def call
27
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) { content }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ <%= render Ariadne::BaseButton.new(tag: @tag, type: @type, classes: @classes, attributes: @attributes) do -%>
2
+ <%= icon %><%= trimmed_content %><%= counter %>
3
+ <%= tooltip %>
4
+ <% end -%>
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `Button` for actions (e.g. in forms). Use links for destinations, or moving from one page to another.
5
+ class ButtonComponent < Ariadne::Component
6
+ include IconHelper
7
+
8
+ DEFAULT_SCHEME = :default
9
+ LINK_SCHEME = :link
10
+
11
+ SCHEME_CLASS_MAPPINGS = {
12
+ none: "",
13
+ default: "ariadne-text-purple-800 ariadne-bg-purple-50 hover:ariadne-bg-purple-100 ariadne-border-purple-300 focus:ariadne-ring-offset-purple-50 focus:ariadne-ring-purple-600",
14
+ info: "ariadne-text-blue-800 ariadne-bg-blue-50 hover:ariadne-bg-blue-100 ariadne-border-blue-300 focus:ariadne-ring-offset-blue-50 focus:ariadne-ring-blue-600",
15
+ success: "ariadne-text-green-800 ariadne-bg-green-50 hover:ariadne-bg-green-100 ariadne-border-green-300 focus:ariadne-ring-offset-green-50 focus:ariadne-ring-green-600",
16
+ warning: "ariadne-text-yellow-800 ariadne-bg-yellow-50 hover:ariadne-bg-yellow-100 ariadne-border-yellow-300 focus:ariadne-ring-offset-yellow-50 focus:ariadne-ring-yellow-600",
17
+ danger: "ariadne-text-red-800 ariadne-bg-red-50 hover:ariadne-bg-red-100 ariadne-border-red-300 focus:ariadne-ring-offset-red-50 focus:ariadne-ring-red-600",
18
+ }.freeze
19
+ VALID_SCHEMES = SCHEME_CLASS_MAPPINGS.keys.freeze
20
+
21
+ # Leading visuals appear to the left of the button text.
22
+ #
23
+ # Use:
24
+ #
25
+ # - `icon` for a <%= link_to_component(Ariadne::HeroiconComponent) %>.
26
+ #
27
+ # @param tag [Symbol, String] The rendered tag name
28
+ # @param classes [String] <%= link_to_classes_docs %>
29
+ # @param icon [String] Name of <%= link_to_heroicons %> to use.
30
+ # @param variant [String] <%= one_of(HeroiconsHelper::Icon::VALID_VARIANTS, sort: false) %>
31
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::HeroiconComponent) %>.
32
+ renders_one :icon, lambda { |tag: :svg, icon:, variant:, classes: "", attributes: {}|
33
+ @icon = icon
34
+ @variant = variant
35
+ tag = check_incoming_tag(:svg, tag)
36
+ Ariadne::HeroiconComponent.new(tag: tag, icon: icon, variant: variant, classes: classes, attributes: attributes)
37
+ }
38
+
39
+ # Trailing visuals appear to the right of the button text.
40
+ #
41
+ # Use:
42
+ #
43
+ # - `counter` for a <%= link_to_component(Ariadne::CounterComponent) %>.
44
+ #
45
+ # @param tag [Symbol, String] The rendered tag name
46
+ # @param classes [String] <%= link_to_classes_docs %>
47
+ # @param counter [Number] The starting counter value
48
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::CounterComponent) %>.
49
+ renders_one :counter, lambda { |tag: :span, count: 0, classes: "", attributes: {}|
50
+ tag = check_incoming_tag(:span, tag)
51
+ attributes[:ml] = check_incoming_value(2, ml: attributes[:ml])
52
+
53
+ Ariadne::CounterComponent.new(tag: tag, count: count, classes: classes, attributes: attributes)
54
+ }
55
+
56
+ # `Tooltip` that appears on mouse hover or keyboard focus over the button. Use tooltips sparingly and as a last resort.
57
+ # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
58
+ # Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
59
+ #
60
+ # @param tag [Symbol, String] The rendered tag name
61
+ # @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
62
+ # @param direction [Symbol] <%= one_of(Ariadne::TooltipComponent::VALID_PLACEMENTS) %>
63
+ # @param classes [String] <%= link_to_classes_docs %>
64
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::TooltipComponent) %>.
65
+ renders_one :tooltip, lambda { |tag: Ariadne::TooltipComponent::DEFAULT_TAG, text:, direction: Ariadne::TooltipComponent::DEFAULT_PLACEMENT, type: Ariadne::TooltipComponent::TYPE_DEFAULT, classes: "", attributes: {}|
66
+ raise ArgumentError, "Buttons with a tooltip must have a unique `id` set on the `Button`." if @id.blank?
67
+
68
+ Ariadne::TooltipComponent.new(for_id: @id, tag: tag, text: text, direction: direction, type: type, classes: classes, attributes: attributes)
69
+ }
70
+
71
+ # @example Schemes
72
+ # <%= render(Ariadne::ButtonComponent.new) { "Default" } %>
73
+ # <%= render(Ariadne::ButtonComponent.new(scheme: :default)) { "Default" } %>
74
+ # <%= render(Ariadne::ButtonComponent.new(scheme: :info)) { "Info" } %>
75
+ # <%= render(Ariadne::ButtonComponent.new(scheme: :success)) { "Success" } %>
76
+ # <%= render(Ariadne::ButtonComponent.new(scheme: :warning)) { "Warning" } %>
77
+ # <%= render(Ariadne::ButtonComponent.new(scheme: :danger)) { "Danger" } %>
78
+ #
79
+ # @example Sizes
80
+ # <%= render(Ariadne::ButtonComponent.new(size: :sm)) { "Small" } %>
81
+ # <%= render(Ariadne::ButtonComponent.new(size: :md)) { "Medium" } %>
82
+ #
83
+ # @example With leading visual
84
+ # <%= render(Ariadne::ButtonComponent.new) do |c| %>
85
+ # <% c.icon(icon: :star, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, classes: "text-yellow-600") %>
86
+ # Button
87
+ # <% end %>
88
+ #
89
+ # @example With trailing visual
90
+ # <%= render(Ariadne::ButtonComponent.new) do |c| %>
91
+ # <% c.counter(count: 15) %>
92
+ # Button
93
+ # <% end %>
94
+ #
95
+ # @example With leading and trailing visuals
96
+ # <%= render(Ariadne::ButtonComponent.new) do |c| %>
97
+ # <% c.icon(icon: :star, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE) %>
98
+ # <% c.counter(count: 15) %>
99
+ # Button
100
+ # <% end %>
101
+ #
102
+ # @example With tooltip
103
+ # @description
104
+ # Use tooltips sparingly and as a last resort. Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
105
+ # @code
106
+ # <%= render(Ariadne::ButtonComponent.new(attributes: { id: "button-with-tooltip" })) do |c| %>
107
+ # <% c.tooltip(text: "Tooltip text") %>
108
+ # Button
109
+ # <% end %>
110
+ #
111
+ # @param tag [Symbol] <%= one_of(Ariadne::BaseButton::TAG_OPTIONS) %>
112
+ # @param type [Symbol] <%= one_of(Ariadne::BaseButton::TYPE_OPTIONS) %>
113
+ # @param scheme [Symbol] <%= one_of(Ariadne::ButtonComponent::VALID_SCHEMES) %>
114
+ # @param size [Symbol] <%= one_of(Ariadne::BaseButton::VALID_SIZES) %>
115
+ # @param type [Symbol] <%= one_of(Ariadne::BaseButton::TYPE_OPTIONS) %>
116
+ # @param classes [String] <%= link_to_classes_docs %>
117
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
118
+ def initialize(
119
+ tag: Ariadne::BaseButton::DEFAULT_TAG,
120
+ type: Ariadne::BaseButton::DEFAULT_TYPE,
121
+ scheme: DEFAULT_SCHEME,
122
+ size: BaseButton::DEFAULT_SIZE,
123
+ classes: "",
124
+ attributes: {}
125
+ )
126
+ @tag = tag
127
+ @type = type
128
+ @scheme = scheme
129
+
130
+ @attributes = attributes
131
+ @id = @attributes[:id]
132
+
133
+ @size = fetch_or_raise(Ariadne::BaseButton::VALID_SIZES, size)
134
+ @scheme = fetch_or_raise(VALID_SCHEMES, scheme)
135
+
136
+ @classes = class_names(
137
+ SCHEME_CLASS_MAPPINGS[@scheme],
138
+ classes
139
+ )
140
+ end
141
+
142
+ private def trimmed_content
143
+ return if content.blank?
144
+
145
+ trimmed_content = content.strip
146
+
147
+ return trimmed_content unless content.html_safe?
148
+
149
+ # strip unsets `html_safe`, so we have to set it back again to guarantee that HTML blocks won't break
150
+ trimmed_content.html_safe # rubocop:disable Rails/OutputSafety
151
+ end
152
+
153
+ private def link?
154
+ @scheme == LINK_SCHEME
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,19 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+
3
+ export default class ClipboardCopyComponent extends Controller {
4
+ copy() {
5
+ const value = this.element.attributes.getNamedItem('value')
6
+
7
+ const forNode = this.element.attributes.getNamedItem('for')
8
+
9
+ if (value) {
10
+ navigator.clipboard.writeText(value.value)
11
+ } else if (forNode) {
12
+ const node = document.getElementById(forNode.value)
13
+ navigator.clipboard.writeText(node?.textContent || '')
14
+ } else {
15
+ // just copy inner text
16
+ navigator.clipboard.writeText(this.element.textContent || '')
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,9 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes.merge(controller_data)) do %>
2
+ <% if content.present? %>
3
+ <%= content.to_s + tooltip.to_s %>
4
+ <% else %>
5
+ <%= render Ariadne::HeroiconComponent.new(icon: :"document-duplicate", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE) %>
6
+ <!-- TODO: fix, check should replace on click -->
7
+ <%= render Ariadne::HeroiconComponent.new(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, classes: "ariadne-text-green-600", attributes: { style: "display: none;" }) %>
8
+ <% end %>
9
+ <% end %>
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `ClipboardCopyComponent` to copy element text content or input values to the clipboard.
5
+ #
6
+ # @accessibility
7
+ # Always set an accessible label to help the user interact with the component.
8
+ class ClipboardCopyComponent < Ariadne::Component
9
+ DEFAULT_TAG = :"clipboard-copy"
10
+
11
+ DEFAULT_CLASSES = LinkComponent::DEFAULT_ACTIONABLE_CLASSES
12
+
13
+ DATA_CONTROLLER = "clipboard-copy-component"
14
+ DATA_ACTION = "click->clipboard-copy-component#copy"
15
+
16
+ # `Tooltip` that appears on mouse hover or keyboard focus over the button. Use tooltips sparingly and as a last resort.
17
+ # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
18
+ # Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
19
+ #
20
+ # @param tag [Symbol, String] The rendered tag name
21
+ # @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
22
+ # @param direction [Symbol] <%= one_of(Ariadne::TooltipComponent::VALID_PLACEMENTS) %>
23
+ # @param classes [String] <%= link_to_classes_docs %>
24
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::TooltipComponent) %>.
25
+ renders_one :tooltip, lambda { |tag: Ariadne::TooltipComponent::DEFAULT_TAG, text:, direction: Ariadne::TooltipComponent::DEFAULT_PLACEMENT, type: Ariadne::TooltipComponent::TYPE_DEFAULT, classes: "", attributes: {}|
26
+ raise ArgumentError, "CopyClipboardComponents with a tooltip must have a unique `id` set on the `CopyClipboardComponent`." if @id.blank?
27
+
28
+ @data_tooltip_direction = { "data-tooltip-component-direction": direction }
29
+
30
+ Ariadne::TooltipComponent.new(tag: tag, for_id: @id, text: text, direction: direction, type: type, classes: classes, attributes: attributes)
31
+ }
32
+
33
+ # @example Default
34
+ # <%= render(Ariadne::ClipboardCopyComponent.new(value: "Text to copy", aria_label: "Copy text to the system clipboard" )) %>
35
+ #
36
+ # @example With text instead of icons
37
+ # <%= render(Ariadne::ClipboardCopyComponent.new(value: "Text to copy", aria_label: "Copy text to the system clipboard" )) do %>
38
+ # Click to copy!
39
+ # <% end %>
40
+ #
41
+ # @example Copying from an element
42
+ # <%= render(Ariadne::ClipboardCopyComponent.new(for_id: "blob-path", aria_label: "Copy text to the system clipboard" )) %>
43
+ # <div id="blob-path">src/index.js</div>
44
+ #
45
+ # @param tag [Symbol, String] The rendered tag name
46
+ # @param classes [String] <%= link_to_classes_docs %>
47
+ # @param value [String] Text to copy into the users clipboard when they click the component.
48
+ # @param for_id [String] If `value` is not provided, the element with this id will be copied.
49
+ # @param aria_label [String] Text for accessibility. Can also be passed in as part of `attributes`, but it must be present.
50
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
51
+ def initialize(tag: DEFAULT_TAG, value: "", for_id: nil, aria_label: "", classes: "", attributes: {})
52
+ @attributes = attributes
53
+ @value = value
54
+
55
+ @attributes[:"aria-label"] = aria_label
56
+ @attributes[:for] ||= for_id
57
+ @id = @attributes[:id]
58
+
59
+ validate!
60
+
61
+ @classes = class_names(DEFAULT_CLASSES, classes)
62
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
63
+ @attributes[:value] = value if value.present?
64
+ end
65
+
66
+ private def validate!
67
+ validate_aria_label!
68
+ raise ArgumentError, "Must provide either `value` or `for`" if @value.blank? && @attributes[:for].blank?
69
+ raise ArgumentError, "Must provide only `value` or `for`, not both" if @value.present? && @attributes[:for].present?
70
+ end
71
+
72
+ DATA_CONTROLLERS_WITH_TOOLTIPS = {
73
+ "data-controller": "#{DATA_CONTROLLER} #{Ariadne::TooltipComponent::DATA_CONTROLLER}",
74
+ "data-action": "#{DATA_ACTION} #{Ariadne::TooltipComponent::DATA_ACTION}",
75
+ "data-tooltip-component-target": "trigger",
76
+ }
77
+
78
+ DATA_CONTROLLERS =
79
+ {
80
+ "data-controller": DATA_CONTROLLER.to_s,
81
+ "data-action": DATA_ACTION,
82
+ }
83
+
84
+ def controller_data
85
+ return DATA_CONTROLLERS_WITH_TOOLTIPS if tooltip.present?
86
+
87
+ DATA_CONTROLLERS
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,55 @@
1
+ import {Controller} from '@hotwired/stimulus'
2
+
3
+ export default class CommentComponent extends Controller {
4
+ static targets = ['tab', 'tabBarComponent']
5
+
6
+ declare readonly commentComponentTarget: HTMLDivElement
7
+ declare readonly tabBarComponentTarget: HTMLElement // technically a `nav but typescript can't find it?
8
+ declare readonly tabTargets: [HTMLButtonElement]
9
+
10
+ SELECTED_TAB_CLASSES = ['ariadne-border-indigo-500', 'ariadne-text-indigo-600']
11
+ PUBLIC_BACKGROUND_COLOR = 'ariadne-bg-white'
12
+ INTERNAL_BACKGROUND_COLOR = 'ariadne-bg-internal-message'
13
+
14
+ connect() {
15
+ for (const tab of this.tabTargets) {
16
+ if (tab.hasAttribute('selected')) {
17
+ tab.classList.add(...this.SELECTED_TAB_CLASSES)
18
+ }
19
+ }
20
+ }
21
+
22
+ toggleTab() {
23
+ for (const tab of this.tabTargets) {
24
+ if (tab.hasAttribute('selected')) {
25
+ tab.removeAttribute('selected')
26
+ tab.classList.remove(...this.SELECTED_TAB_CLASSES)
27
+ this.toggleBackgrounds(tab)
28
+ } else {
29
+ tab.setAttribute('selected', 'true')
30
+ tab.classList.add(...this.SELECTED_TAB_CLASSES)
31
+ this.toggleBackgrounds(tab)
32
+ if (tab.hasAttribute('data-public')) {
33
+ document.getElementById('message_public')?.setAttribute('value', 'true')
34
+ } else {
35
+ document.getElementById('message_public')?.setAttribute('value', 'false')
36
+ }
37
+ }
38
+ }
39
+ }
40
+ toggleBackgrounds(tab: HTMLButtonElement) {
41
+ if (tab.hasAttribute('selected')) {
42
+ if (tab.hasAttribute('data-public')) {
43
+ // this.commentComponentTarget.classList.add(this.PUBLIC_BACKGROUND_COLOR)
44
+ // this.commentComponentTarget.classList.remove(this.INTERNAL_BACKGROUND_COLOR)
45
+ this.tabBarComponentTarget.classList.add(this.PUBLIC_BACKGROUND_COLOR)
46
+ this.tabBarComponentTarget.classList.remove(this.INTERNAL_BACKGROUND_COLOR)
47
+ } else {
48
+ // this.commentComponentTarget.classList.remove(this.PUBLIC_BACKGROUND_COLOR)
49
+ // this.commentComponentTarget.classList.add(this.INTERNAL_BACKGROUND_COLOR)
50
+ this.tabBarComponentTarget.classList.remove(this.PUBLIC_BACKGROUND_COLOR)
51
+ this.tabBarComponentTarget.classList.add(this.INTERNAL_BACKGROUND_COLOR)
52
+ }
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,22 @@
1
+ <%= render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do %>
2
+ <div class="ariadne-bg-white" data-comment-component-target="commentComponent">
3
+ <div class="ariadne-hidden sm:ariadne-block">
4
+ <div class="ariadne-border-b ariadne-border-gray-200">
5
+ <%= render(Ariadne::TabBarComponent.new(sr_label: @sr_label, attributes: { :"data-comment-component-target" => "tabBarComponent" })) do |tab_bar| %>
6
+ <%= public_tab %>
7
+ <%= internal_tab %>
8
+ <% end %>
9
+ </div>
10
+ </div>
11
+ <%= ariadne_form_with(url: @url, method: @method, classes: @classes, attributes: @attributes) do |comment_box| %>
12
+ <div class="ariadne-overflow-hidden ariadne-border-x ariadne-border-b ariadne-border-gray-300 ariadne-shadow-sm focus-within:ariadne-border-indigo-500 focus-within:ariadne-ring-1 focus-within:ariadne-ring-indigo-500">
13
+ <%= hidden_field_tag 'message_public', true %>
14
+ <%= render(Ariadne::RichTextAreaComponent.new(name: :bodytext, sr_label: "Select reply type", attributes: { required: true})) %>
15
+ <% comment_box.submit { @submit } %>
16
+ </div>
17
+ <div class="ariadne-mt-2 ariadne-flex ariadne-justify-end">
18
+ <%= submit %>
19
+ </div>
20
+ <% end %>
21
+ </div>
22
+ <% end %>
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Defines a submittable form for adding comments to a conversation.
5
+ # @accessibility This component requires you to pass in a `sr_label`
6
+ # attribute, which will be used to label the tabs for screen readers.
7
+ class CommentComponent < Ariadne::Component
8
+ DEFAULT_TAG = :div
9
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
10
+
11
+ DEFAULT_CLASSES = "ariadne-bg-white ariadne-border-gray-300 ariadne-border ariadne-shadow ariadne-py-5 ariadne-px-5 ariadne-rounded-md "
12
+
13
+ renders_one :public_tab, lambda { |selected: false, text:, classes: "", attributes: {}|
14
+ attributes[:"data-comment-component-target"] ||= "tab"
15
+ attributes[:"data-action"] ||= "click->comment-component#toggleTab"
16
+ attributes[:"data-public"] = true
17
+ @tab_idx += 1
18
+ render(Ariadne::TabComponent.new(selected: selected, classes: classes, attributes: attributes)) { text }
19
+ }
20
+
21
+ renders_one :internal_tab, lambda { |selected: false, text:, classes: "", attributes: {}|
22
+ attributes[:"data-comment-component-target"] ||= "tab"
23
+ attributes[:"data-action"] ||= "click->comment-component#toggleTab"
24
+ @tab_idx += 1
25
+ render(Ariadne::TabComponent.new(selected: selected, classes: classes, attributes: attributes)) { text }
26
+ }
27
+
28
+ renders_one :submit, lambda { |classes: "", attributes: {}|
29
+ Ariadne::ButtonComponent.new(type: Ariadne::BaseButton::TYPE_SUBMIT, scheme: Ariadne::ButtonComponent::DEFAULT_SCHEME, classes: classes, attributes: attributes)
30
+ }
31
+
32
+ # @example Default
33
+ #
34
+ # <%= render(Ariadne::CommentComponent.new(url: "/messages", method: :post, sr_label: "Select delivery time")) { "Example" } %>
35
+ #
36
+ # @param url [String] The URL to take action against.
37
+ # @param method [String] The method to use when submitting the form.
38
+ # @param sr_label [String] A label to introduce these tabs for screen readers.
39
+ # @param classes [String] <%= link_to_classes_docs %>
40
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
41
+ def initialize(url:, method:, sr_label:, classes: "", attributes: {})
42
+ @tag = DEFAULT_TAG
43
+ @classes = class_names(
44
+ DEFAULT_CLASSES,
45
+ classes
46
+ )
47
+ @url = url
48
+ @method = method
49
+ @sr_label = sr_label
50
+
51
+ @tab_idx = -1
52
+ @attributes = attributes
53
+ @attributes[:"data-controller"] = "comment-component"
54
+ attributes[:"data-comment-component-target"] = "commentComponent"
55
+ end
56
+ end
57
+ end