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