ariadne_view_components 0.0.43 → 0.0.44

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/app/components/ariadne/ariadne-form.d.ts +22 -0
  4. data/app/components/ariadne/ariadne-form.js +85 -0
  5. data/app/components/ariadne/ariadne-form.ts +96 -0
  6. data/app/components/ariadne/ariadne.d.ts +2 -0
  7. data/app/components/ariadne/ariadne.js +16 -0
  8. data/app/components/ariadne/ariadne.ts +21 -0
  9. data/app/components/ariadne/avatar_component.rb +81 -0
  10. data/app/components/ariadne/avatar_stack_component/avatar_stack_component.html.erb +12 -0
  11. data/app/components/ariadne/avatar_stack_component.rb +75 -0
  12. data/app/components/ariadne/base_button.rb +70 -0
  13. data/app/components/ariadne/base_component.rb +37 -0
  14. data/app/components/ariadne/blankslate_component/blankslate_component.html.erb +26 -0
  15. data/app/components/ariadne/blankslate_component.rb +148 -0
  16. data/app/components/ariadne/body_component.rb +30 -0
  17. data/app/components/ariadne/button_component/button_component.html.erb +4 -0
  18. data/app/components/ariadne/button_component.rb +165 -0
  19. data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.d.ts +4 -0
  20. data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.js +18 -0
  21. data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.ts +19 -0
  22. data/app/components/ariadne/clipboard_copy_component/clipboard_copy_component.html.erb +9 -0
  23. data/app/components/ariadne/clipboard_copy_component.rb +90 -0
  24. data/app/components/ariadne/comment_component/comment_component.html.erb +37 -0
  25. data/app/components/ariadne/comment_component.rb +71 -0
  26. data/app/components/ariadne/component.rb +127 -0
  27. data/app/components/ariadne/container_component/container_component.html.erb +3 -0
  28. data/app/components/ariadne/container_component.rb +25 -0
  29. data/app/components/ariadne/content.rb +12 -0
  30. data/app/components/ariadne/counter_component.rb +100 -0
  31. data/app/components/ariadne/details_component/details_component.html.erb +4 -0
  32. data/app/components/ariadne/details_component.rb +81 -0
  33. data/app/components/ariadne/dropdown/menu_component.html.erb +20 -0
  34. data/app/components/ariadne/dropdown/menu_component.rb +101 -0
  35. data/app/components/ariadne/dropdown/menu_component.ts +1 -0
  36. data/app/components/ariadne/dropdown_component/dropdown_component.html.erb +8 -0
  37. data/app/components/ariadne/dropdown_component.rb +172 -0
  38. data/app/components/ariadne/flash_component/flash_component.html.erb +31 -0
  39. data/app/components/ariadne/flash_component.rb +128 -0
  40. data/app/components/ariadne/flex_component/flex_component.html.erb +5 -0
  41. data/app/components/ariadne/flex_component.rb +56 -0
  42. data/app/components/ariadne/footer_component/footer_component.html.erb +7 -0
  43. data/app/components/ariadne/footer_component.rb +23 -0
  44. data/app/components/ariadne/grid_component/grid_component.html.erb +26 -0
  45. data/app/components/ariadne/grid_component.rb +67 -0
  46. data/app/components/ariadne/header_component/header_component.html.erb +29 -0
  47. data/app/components/ariadne/header_component.rb +111 -0
  48. data/app/components/ariadne/heading_component.rb +49 -0
  49. data/app/components/ariadne/heroicon_component/heroicon_component.html.erb +4 -0
  50. data/app/components/ariadne/heroicon_component.rb +166 -0
  51. data/app/components/ariadne/image_component.rb +53 -0
  52. data/app/components/ariadne/inline_flex_component/inline_flex_component.html.erb +6 -0
  53. data/app/components/ariadne/inline_flex_component.rb +72 -0
  54. data/app/components/ariadne/link_component.rb +65 -0
  55. data/app/components/ariadne/list_component/list_component.html.erb +6 -0
  56. data/app/components/ariadne/list_component.rb +70 -0
  57. data/app/components/ariadne/narrow_container_component/narrow_container_component.html.erb +3 -0
  58. data/app/components/ariadne/narrow_container_component.rb +30 -0
  59. data/app/components/ariadne/panel_bar_component/panel_bar_component.html.erb +20 -0
  60. data/app/components/ariadne/panel_bar_component.rb +80 -0
  61. data/app/components/ariadne/pill_component/pill_component.html.erb +3 -0
  62. data/app/components/ariadne/pill_component.rb +44 -0
  63. data/app/components/ariadne/rich_text_area_component/rich-text-area-component.d.ts +6 -0
  64. data/app/components/ariadne/rich_text_area_component/rich-text-area-component.js +38 -0
  65. data/app/components/ariadne/rich_text_area_component/rich-text-area-component.ts +47 -0
  66. data/app/components/ariadne/rich_text_area_component/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/slideover-component.d.ts +9 -0
  69. data/app/components/ariadne/slideover_component/slideover-component.js +11 -0
  70. data/app/components/ariadne/slideover_component/slideover-component.ts +17 -0
  71. data/app/components/ariadne/slideover_component/slideover_component.html.erb +9 -0
  72. data/app/components/ariadne/slideover_component.rb +66 -0
  73. data/app/components/ariadne/tab_component/tab_component.html.erb +3 -0
  74. data/app/components/ariadne/tab_component.rb +98 -0
  75. data/app/components/ariadne/tab_container_component/tab-container-component.d.ts +1 -0
  76. data/app/components/ariadne/tab_container_component/tab-container-component.js +23 -0
  77. data/app/components/ariadne/tab_container_component/tab-container-component.ts +24 -0
  78. data/app/components/ariadne/tab_container_component.erb +10 -0
  79. data/app/components/ariadne/tab_container_component.rb +68 -0
  80. data/app/components/ariadne/tab_nav_component/tab-nav-component.d.ts +9 -0
  81. data/app/components/ariadne/tab_nav_component/tab-nav-component.js +33 -0
  82. data/app/components/ariadne/tab_nav_component/tab-nav-component.ts +34 -0
  83. data/app/components/ariadne/tab_nav_component/tab_nav_component.html.erb +7 -0
  84. data/app/components/ariadne/tab_nav_component.rb +72 -0
  85. data/app/components/ariadne/table_nav_component/table_nav_component.html.erb +52 -0
  86. data/app/components/ariadne/table_nav_component.rb +338 -0
  87. data/app/components/ariadne/text.rb +25 -0
  88. data/app/components/ariadne/time_ago_component/time-ago-component.d.ts +1 -0
  89. data/app/components/ariadne/time_ago_component/time-ago-component.js +1 -0
  90. data/app/components/ariadne/time_ago_component/time-ago-component.ts +1 -0
  91. data/app/components/ariadne/time_ago_component.rb +56 -0
  92. data/app/components/ariadne/timeline_component/timeline_component.html.erb +19 -0
  93. data/app/components/ariadne/timeline_component.rb +34 -0
  94. data/app/components/ariadne/tooltip_component/tooltip-component.d.ts +24 -0
  95. data/app/components/ariadne/tooltip_component/tooltip-component.js +43 -0
  96. data/app/components/ariadne/tooltip_component/tooltip-component.ts +57 -0
  97. data/app/components/ariadne/tooltip_component/tooltip_component.html.erb +4 -0
  98. data/app/components/ariadne/tooltip_component.rb +108 -0
  99. data/lib/ariadne/view_components/engine.rb +0 -22
  100. data/lib/ariadne/view_components/version.rb +1 -1
  101. data/tailwind.config.js +10 -15
  102. metadata +98 -2
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "heroicons_helper"
4
+
5
+ module Ariadne
6
+ # `Heroicon` renders an <%= link_to_heroicons %> with <%= link_to_attributes_docs %>.
7
+ # `Heroicon` can also be rendered with the `heroicon` helper.
8
+ class HeroiconComponent < Ariadne::Component
9
+ DEFAULT_TEXT_CLASSES = "ariadne-pl-2"
10
+
11
+ include IconHelper
12
+ include HeroiconsHelper
13
+
14
+ SIZE_XSMALL = :xs
15
+ SIZE_SMALL = :sm
16
+ SIZE_DEFAULT = :md
17
+ SIZE_LARGE = :lg
18
+
19
+ SIZE_MAPPINGS = {
20
+ SIZE_XSMALL => 16,
21
+ SIZE_SMALL => 20,
22
+ SIZE_DEFAULT => 24,
23
+ SIZE_LARGE => 128,
24
+ }.freeze
25
+ SIZE_OPTIONS = SIZE_MAPPINGS.keys
26
+
27
+ PRELOADED_ICONS = [
28
+ {
29
+ name: "bell",
30
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
31
+ },
32
+ {
33
+ name: "check",
34
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
35
+ },
36
+ {
37
+ name: "chevron-down",
38
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
39
+ },
40
+ {
41
+ name: "clipboard",
42
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
43
+ },
44
+ {
45
+ name: "clock",
46
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
47
+ },
48
+ {
49
+ name: "information-circle",
50
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
51
+ },
52
+ {
53
+ name: "dots-horizontal",
54
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
55
+ },
56
+ {
57
+ name: "link",
58
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
59
+ },
60
+ {
61
+ name: "lock-closed",
62
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
63
+ },
64
+ {
65
+ name: "mail",
66
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
67
+ },
68
+ {
69
+ name: "menu",
70
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
71
+ },
72
+ {
73
+ name: "pencil",
74
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
75
+ },
76
+ {
77
+ name: "plus-sm",
78
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
79
+ },
80
+ {
81
+ name: "question-mark-circle",
82
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
83
+ },
84
+ {
85
+ name: "search",
86
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
87
+ },
88
+ {
89
+ name: "search",
90
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
91
+ },
92
+ {
93
+ name: "trash",
94
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
95
+ },
96
+ {
97
+ name: "x-mark",
98
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
99
+ },
100
+ ].freeze
101
+
102
+ # @example Default
103
+ # <%= render(Ariadne::HeroiconComponent.new(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE)) %>
104
+ # <%= render(Ariadne::HeroiconComponent.new(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_SOLID)) %>
105
+ #
106
+ # @example Medium
107
+ # <%= render(Ariadne::HeroiconComponent.new(icon: :"user-group", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, size: :md)) %>
108
+ #
109
+ # @example Helper
110
+ # <%= ariadne_heroicon(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE) %>
111
+ #
112
+ # @param tag [Symbol, String] The rendered tag name
113
+ # @param classes [String] <%= link_to_classes_docs %>
114
+ # @param icon [Symbol, String] Name of <%= link_to_heroicons %> to use.
115
+ # @param variant [String] <%= one_of(HeroiconsHelper::Icon::VALID_VARIANTS, sort: false) %>
116
+ # @param size [Symbol] <%= one_of(Ariadne::HeroiconComponent::SIZE_MAPPINGS, sort: false) %>
117
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
118
+ # @param text_classes [String] <%= link_to_classes_docs %>
119
+ # @param text_attributes [Hash] <%= link_to_attributes_docs %>
120
+ def initialize(tag: :svg, icon:, variant:, size: SIZE_DEFAULT, classes: "", attributes: {}, text_classes: "", text_attributes: {})
121
+ @tag = check_incoming_tag(:svg, tag)
122
+
123
+ check_icon_presence!(icon, variant)
124
+
125
+ @attributes = attributes
126
+ @attributes[:aria] ||= {}
127
+
128
+ if @attributes[:aria][:label] || @attributes[:"aria-label"]
129
+ @attributes[:role] = "img"
130
+ else
131
+ @attributes[:aria][:hidden] = true
132
+ end
133
+
134
+ # Don't allow sizes under 16px
135
+ if attributes[:height].present? && attributes[:height].to_i < 16 || attributes[:width].present? && attributes[:width].to_i < 16
136
+ attributes.delete(:height)
137
+ attributes.delete(:width)
138
+ end
139
+
140
+ # Filter out classify options to prevent them from becoming invalid html attributes.
141
+ # Note height and width are both classify options and valid html attributes.
142
+ attributes = {
143
+ height: @attributes[:height] || SIZE_MAPPINGS[fetch_or_raise(SIZE_OPTIONS, size)],
144
+ width: @attributes[:width],
145
+ }
146
+
147
+ @icon = heroicon(icon, variant: variant, **attributes)
148
+ @classes = merge_class_names(
149
+ @icon.attributes[:class],
150
+ classes,
151
+ )
152
+ @attributes.merge!(@icon.attributes.except(:class, :"aria-hidden"))
153
+
154
+ @text_classes = merge_class_names(DEFAULT_TEXT_CLASSES, text_classes)
155
+ @text_attributes = text_attributes
156
+ end
157
+
158
+ class << self
159
+ def _after_compile
160
+ HeroiconsHelper::Cache.preload!(PRELOADED_ICONS) do |found, icon|
161
+ HeroiconComponent.new(icon: icon[:name], variant: icon[:variant]) unless found
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `Image` to render images.
5
+ #
6
+ # @accessibility
7
+ # Always provide a meaningful `alt`.
8
+ class ImageComponent < Ariadne::Component
9
+ DEFAULT_TAG = :img
10
+
11
+ # @example Default
12
+ #
13
+ # <%= render(Ariadne::ImageComponent.new(src: "https://github.com/github.png", alt: "GitHub")) %>
14
+ #
15
+ # @example Helper
16
+ #
17
+ # <%= ariadne_image(src: "https://github.com/github.png", alt: "GitHub") %>
18
+ #
19
+ # @example Lazy loading
20
+ #
21
+ # <%= render(Ariadne::ImageComponent.new(src: "https://github.com/github.png", alt: "GitHub", lazy: true)) %>
22
+ #
23
+ # @example Custom size
24
+ #
25
+ # <%= render(Ariadne::ImageComponent.new(src: "https://github.com/github.png", alt: "GitHub", attributes: { height: 100, width: 100 })) %>
26
+ #
27
+ # @param tag [Symbol, String] The rendered tag name
28
+ # @param src [String] The source url of the image.
29
+ # @param alt [String] Specifies an alternate text for the image.
30
+ # @param lazy [Boolean] Whether or not to lazily load the image.
31
+ # @param classes [String] <%= link_to_classes_docs %>
32
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
33
+ def initialize(tag: DEFAULT_TAG, src:, alt:, lazy: false, classes: "", attributes: {})
34
+ @attributes = attributes
35
+
36
+ @src = src
37
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
38
+ @classes = classes
39
+
40
+ @attributes[:alt] = alt
41
+ @attributes[:src] = @src
42
+
43
+ return unless lazy
44
+
45
+ @attributes[:loading] = :lazy
46
+ @attributes[:decoding] = :async
47
+ end
48
+
49
+ def call
50
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes))
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ <%= render(Ariadne::BaseComponent.new(tag: :span, classes: @classes, attributes: @attributes)) do |component| %>
2
+ <%= icon %>
3
+ <%= item %>
4
+ <%= text %>
5
+ <%= dropdown %>
6
+ <% end %>
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Represents two items side-by-side. Typically, this will be an icon (of CSS classes, SVG, or a Heroicon icon)
5
+ # with optional text.
6
+ #
7
+ # InlineFlexComponent differs from HeroiconComponent in that it is intended to be
8
+ # used within (or next to) text, whereas HeroiconComponent is intended to only
9
+ # present a static list of SVG images (and can be embedded in buttons or shown alone).
10
+ class InlineFlexComponent < Ariadne::Component
11
+ DEFAULT_TAG = :span
12
+ DEFAULT_CLASSES = "ariadne-inline-flex ariadne-items-baseline"
13
+
14
+ STATE_OPTIONS = [:closed, :open].freeze
15
+
16
+ STATE_OPEN_SVG = <<~MSG
17
+ <svg viewBox="0 0 24 24" width="12" height="12" class="ariadne-stroke-state-open" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
18
+ <circle cx="12" cy="12" r="10"></circle>
19
+ </svg>
20
+ MSG
21
+ STATE_CLOSED_SVG = <<~MSG
22
+ <svg viewBox="0 0 24 24" width="12" height="12" class="ariadne-stroke-state-closed ariadne-fill-state-closed " stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
23
+ <circle cx="12" cy="12" r="10"></circle>
24
+ </svg>
25
+ MSG
26
+
27
+ DEFAULT_TEXT_OPEN_CLASSES = "ariadne-text-state-open"
28
+ DEFAULT_TEXT_CLOSED_CLASSES = "ariadne-text-state-closed"
29
+ DEFAULT_TEXT_CLASSES = "ariadne-pl-2 ariadne-text-sm ariadne-font-medium"
30
+ renders_one :icon, lambda { |tag: :svg, icon:, variant:, size: Ariadne::HeroiconComponent::SIZE_DEFAULT, classes: "", attributes: {}, text_classes: "", text_attributes: {}|
31
+ actual_text_classes = merge_class_names(DEFAULT_TEXT_CLASSES, text_classes)
32
+ Ariadne::HeroiconComponent.new(tag: tag, icon: icon, variant: variant, size: size, classes: classes, attributes: attributes, text_classes: actual_text_classes, text_attributes: text_attributes) { content }
33
+ }
34
+
35
+ renders_one :item, lambda { |classes: "", attributes: {}|
36
+ Ariadne::BaseComponent.new(tag: :span, classes: classes, attributes: attributes) { content }
37
+ }
38
+
39
+ DEFAULT_LABEL_CLASSES = "ariadne-pl-2 ariadne-text-sm ariadne-font-medium"
40
+ renders_one :text, lambda { |classes: "", attributes: {}|
41
+ actual_classes = merge_class_names(DEFAULT_LABEL_CLASSES, classes)
42
+ Ariadne::BaseComponent.new(tag: :span, classes: actual_classes, attributes: attributes) { content }
43
+ }
44
+
45
+ renders_one :dropdown, "Ariadne::DropdownComponent"
46
+
47
+ # @example Default
48
+ #
49
+ # <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
50
+ # <% c.with_item { Ariadne::InlineFlexComponent::STATE_OPEN_SVG.html_safe } %>
51
+ # <% end %>
52
+ #
53
+ # # TODO: STATE_CLOSED_SVG colors didn't show until it was listed in an example
54
+ # <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
55
+ # <% c.with_item { Ariadne::InlineFlexComponent::STATE_CLOSED_SVG.html_safe } %>
56
+ # <% end %>
57
+ #
58
+ # <%= render(Ariadne::InlineFlexComponent.new) do |c| %>
59
+ # <% c.with_icon(icon: :check, size: :sm, variant: HeroiconsHelper::Icon::VARIANT_SOLID) %>
60
+ # <% c.with_text { "Closed" } %>
61
+ # <% end %>
62
+ #
63
+ # @param tag [Symbol, String] The rendered tag name
64
+ # @param classes [String] <%= link_to_classes_docs %>
65
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
66
+ def initialize(tag: DEFAULT_TAG, classes: "", attributes: {})
67
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
68
+ @classes = merge_class_names(DEFAULT_CLASSES, classes)
69
+ @attributes = attributes
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Use `Link` for navigating from one page to another. `Link` styles anchor tags with default styling and hover text-decoration.
5
+ class LinkComponent < Ariadne::Component
6
+ DEFAULT_TAG = :a
7
+ TAG_OPTIONS = [DEFAULT_TAG, :span].freeze
8
+
9
+ DEFAULT_CLASSES = "ariadne-cursor-pointer hover:ariadne-text-button-text-color focus:ariadne-outline-none focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500"
10
+ DEFAULT_ACTIONABLE_CLASSES = "ariadne-cursor-pointer ariadne-font-semibold ariadne-underline ariadne-decoration-double"
11
+
12
+ # `Tooltip` that appears on mouse hover or keyboard focus over the button. Use tooltips sparingly and as a last resort.
13
+ # **Important:** This tooltip defaults to `type: :description`. In a few scenarios, `type: :label` may be more appropriate.
14
+ # Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
15
+ #
16
+ # @param tag [Symbol, String] The rendered tag name
17
+ # @param text [String] The text content of the tooltip. This should be brief and no longer than a sentence.
18
+ # @param direction [Symbol] <%= one_of(Ariadne::TooltipComponent::VALID_PLACEMENTS) %>
19
+ # @param classes [String] <%= link_to_classes_docs %>
20
+ # @param attributes [Hash] Same arguments as <%= link_to_component(Ariadne::TooltipComponent) %>.
21
+ renders_one :tooltip, lambda { |tag: Ariadne::TooltipComponent::DEFAULT_TAG, text:, direction: Ariadne::TooltipComponent::DEFAULT_PLACEMENT, type: Ariadne::TooltipComponent::TYPE_DEFAULT, classes: "", attributes: {}|
22
+ raise ArgumentError, "Links with a tooltip must have a unique `id` set on the `LinkComponent`." if @id.blank?
23
+
24
+ Ariadne::TooltipComponent.new(tag: tag, for_id: @id, text: text, direction: direction, type: type, classes: classes, attributes: attributes)
25
+ }
26
+
27
+ # @example Default
28
+ # <%= render(Ariadne::LinkComponent.new(href: "#")) { "Link" } %>
29
+ #
30
+ # @example Span as link
31
+ # <%= render(Ariadne::LinkComponent.new(tag: :span, href: "#")) { "Span as a link" } %>
32
+ #
33
+ # @example With tooltip
34
+ # @description
35
+ # Use tooltips sparingly and as a last resort. Consult the <%= link_to_component(Ariadne::TooltipComponent) %> documentation for more information.
36
+ # @code
37
+ # <%= render(Ariadne::LinkComponent.new(href: "#", attributes: { id: "link-with-tooltip" })) do |c| %>
38
+ # <% c.with_tooltip(text: "Tooltip text") %>
39
+ # Link
40
+ # <% end %>
41
+ #
42
+ # @param tag [String] <%= one_of(Ariadne::LinkComponent::TAG_OPTIONS) %>
43
+ # @param href [String] URL to be used for the link.
44
+ # @param actionable [Boolean] If true, adds additional classes to the link to make it more aware.
45
+ # @param classes [String] <%= link_to_classes_docs %>
46
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
47
+ def initialize(tag: DEFAULT_TAG, href:, actionable: false, classes: "", attributes: {})
48
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
49
+
50
+ @attributes = attributes
51
+ @attributes[:href] = href
52
+
53
+ @id = @attributes[:id]
54
+
55
+ @classes = merge_class_names(DEFAULT_CLASSES, classes)
56
+ @classes << DEFAULT_ACTIONABLE_CLASSES if actionable
57
+ end
58
+
59
+ def call
60
+ render(Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes)) do
61
+ content.to_s + tooltip.to_s
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,6 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |list| %>
2
+ <%= content %>
3
+ <% items.each do |item| %>
4
+ <%= item %>
5
+ <% end %>
6
+ <% end %>
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # `List` is used to show a list of items in a vertical format.
5
+ class ListComponent < Ariadne::Component
6
+ DEFAULT_TAG = :ul
7
+ DEFAULT_UL_CLASSES = "ariadne-divide-y ariadne-divide-gray-300"
8
+
9
+ renders_many :items, "ListItem"
10
+
11
+ # @example Basic
12
+ # <% numbers = [1, 2, 3] %>
13
+ # <%= render(Ariadne::ListComponent.new) do |list| %>
14
+ # <% numbers.each do |number| %>
15
+ # <%= list.with_item do |item| %>
16
+ # <%= number %>
17
+ # <% end %>
18
+ # <% end %>
19
+ # <% end %>
20
+ #
21
+ #
22
+ # @param classes [String] <%= link_to_classes_docs %>
23
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
24
+ def initialize(classes: "", attributes: {})
25
+ @tag = DEFAULT_TAG
26
+ @classes = merge_class_names(DEFAULT_UL_CLASSES, classes)
27
+ @attributes = attributes
28
+ end
29
+
30
+ def render?
31
+ items?
32
+ end
33
+
34
+ # This component is part of `ListComponent` and should not be
35
+ # used as a standalone component.
36
+ class ListItem < Ariadne::Component
37
+ DEFAULT_ITEM_CLASSES = "ariadne-relative ariadne-p-1.5 focus:ariadne-ring-2 focus:ariadne-ring-offset-2 focus:ariadne-ring-purple-500 hover:ariadne-bg-button-hover-color"
38
+
39
+ attr_reader :link, :classes, :attributes
40
+
41
+ def initialize(link: {}, classes: "", attributes: {})
42
+ @link = link
43
+ @classes = merge_class_names(DEFAULT_ITEM_CLASSES, classes)
44
+ @attributes = attributes
45
+ end
46
+
47
+ def selected?
48
+ @selected
49
+ end
50
+
51
+ private def linked?
52
+ @link.present?
53
+ end
54
+
55
+ def call
56
+ render(Ariadne::BaseComponent.new(tag: :li, classes: @classes, attributes: @attributes)) do
57
+ if linked?
58
+ classes = @link[:classes] || ""
59
+ attributes = @link[:attributes] || {}
60
+ render(Ariadne::LinkComponent.new(href: @link[:href], classes: classes, attributes: attributes)) do
61
+ content
62
+ end
63
+ else
64
+ content
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ 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,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 NarrowContainerComponent < Ariadne::Component
8
+ DEFAULT_TAG = :div
9
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
10
+
11
+ DEFAULT_CLASSES = "ariadne-max-w-7xl ariadne-mx-auto ariadne-py-12 ariadne-px-4 sm:ariadne-px-6 lg:ariadne-py-16 lg:ariadne-px-8"
12
+
13
+ # @example Default
14
+ # <%= render(Ariadne::NarrowContainerComponent.new) do |container| %>
15
+ # <%= render(Ariadne::ButtonComponent.new) { "Click me!" } %>
16
+ # <% end %>
17
+ #
18
+ # @param classes [String] <%= link_to_classes_docs %>
19
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
20
+ def initialize(classes: "", attributes: {})
21
+ @tag = :div
22
+ @classes = merge_class_names(
23
+ DEFAULT_CLASSES,
24
+ classes,
25
+ )
26
+
27
+ @attributes = attributes
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do |list| %>
2
+ <% panels.each_with_index do |panel, idx| %>
3
+ <%= render Ariadne::BaseComponent.new(tag: :li, classes: panel.classes, attributes: panel.attributes) do %>
4
+ <%= render Ariadne::BaseComponent.new(tag: :div, classes: Ariadne::PanelBarComponent::PanelItem::DEFAULT_WRAPPER_CLASSES) do %>
5
+ <span class="ariadne-px-6 ariadne-py-4 ariadne-flex ariadne-items-center ariadne-text-sm ariadne-font-medium">
6
+ <%= panel.icon %>
7
+ <span class="ariadne-ml-4 ariadne-text-sm ariadne-font-medium ariadne-text-gray-900"><%= panel.label %></span>
8
+ <!-- TODO: fix this -->
9
+ <% if idx + 1 < panels.size %>
10
+ <div class="md:ariadne-block ariadne-hidden ariadne-absolute ariadne-top-0 ariadne-right-0 ariadne-h-full ariadne-w-5" aria-hidden="true">
11
+ <svg class="ariadne-h-full ariadne-w-full ariadne-text-gray-300" viewBox="0 0 22 80" fill="none" preserveAspectRatio="none">
12
+ <path d="M0 -2L20 40L0 82" vector-effect="non-scaling-stroke" stroke="currentcolor" stroke-linejoin="round"></path>
13
+ </svg>
14
+ </div>
15
+ <% end %>
16
+ </span>
17
+ <% end %>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
@@ -0,0 +1,80 @@
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 PanelBarComponent < Ariadne::Component
8
+ DEFAULT_TAG = :ol
9
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
10
+
11
+ DEFAULT_CLASSES = "ariadne-border ariadne-border-gray-300 ariadne-rounded-md ariadne-divide-y ariadne-divide-gray-300 md:ariadne-flex md:ariadne-divide-y-0"
12
+
13
+ renders_many :panels, "PanelItem"
14
+
15
+ # @example Default
16
+ #
17
+ # <%= render(Ariadne::PanelBarComponent.new) { "Example" } %>
18
+ #
19
+ # @param tag [Symbol, String] The rendered tag name.
20
+ # @param classes [String] <%= link_to_classes_docs %>
21
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
22
+ def initialize(classes: "", attributes: {})
23
+ @tag = DEFAULT_TAG
24
+ @classes = merge_class_names(
25
+ DEFAULT_CLASSES,
26
+ classes,
27
+ )
28
+
29
+ @attributes = attributes
30
+ @attributes[:role] ||= "list"
31
+ end
32
+
33
+ # def render?
34
+ # items.any?
35
+ # end
36
+
37
+ # This component is part of `PanelBarComponent` and should not be
38
+ # used as a standalone component.
39
+ class PanelItem < Ariadne::Component
40
+ DEFAULT_ITEM_CLASSES = "ariadne-relative md:ariadne-flex-1 md:ariadne-flex"
41
+ DEFAULT_WRAPPER_CLASSES = "group ariadne-flex ariadne-items-center ariadne-w-full"
42
+
43
+ # TODO: fix this
44
+ renders_one :icon, lambda { |static_content = nil, &block|
45
+ next static_content if static_content.present?
46
+
47
+ view_context.capture { block&.call }
48
+ }
49
+
50
+ renders_one :label, lambda { |static_content = nil, &block|
51
+ next static_content if static_content.present?
52
+
53
+ view_context.capture { block&.call }
54
+ }
55
+
56
+ attr_reader :link, :classes, :attributes
57
+
58
+ def initialize(link: {}, classes: "", attributes: {})
59
+ @link = link
60
+ if @link.present?
61
+ @link["classes"] = merge_class_names(DEFAULT_WRAPPER_CLASSES, @link["classes"])
62
+ end
63
+ @classes = merge_class_names(DEFAULT_ITEM_CLASSES, classes)
64
+ @attributes = attributes
65
+ end
66
+
67
+ def selected?
68
+ @selected
69
+ end
70
+
71
+ def linked?
72
+ @link.present?
73
+ end
74
+
75
+ def call
76
+ render(Ariadne::BaseComponent.new(tag: :div, classes: @classes, attributes: @attributes))
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,3 @@
1
+ <%= render Ariadne::BaseComponent.new(tag: @tag, classes: @classes, attributes: @attributes) do %>
2
+ <%= content %>
3
+ <% end %>
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ariadne
4
+ # Creates a ariadne-rounded label that resembles a medicine pill.
5
+ class PillComponent < Ariadne::Component
6
+ DEFAULT_TAG = :span
7
+ TAG_OPTIONS = [DEFAULT_TAG].freeze
8
+
9
+ DEFAULT_CLASSES = "ariadne-flex-shrink-0 ariadne-inline-block ariadne-px-2 ariadne-py-1 ariadne-text-xs ariadne-font-medium ariadne-rounded-full ariadne-whitespace-nowrap"
10
+
11
+ # @example Default
12
+ #
13
+ # <%= render(Ariadne::PillComponent.new(color: [49, 186, 115, 1.0])) { "Admin" } %>
14
+ #
15
+ # @param tag [Symbol, String] The rendered tag name.
16
+ # @param color [String] The rgba color of the pill.
17
+ # @param classes [String] <%= link_to_classes_docs %>
18
+ # @param attributes [Hash] <%= link_to_attributes_docs %>
19
+ def initialize(tag: DEFAULT_TAG, color:, classes: "", attributes: {})
20
+ @tag = check_incoming_tag(DEFAULT_TAG, tag)
21
+
22
+ @red = color[0]
23
+ @green = color[1]
24
+ @blue = color[2]
25
+ @alpha = color[3]
26
+
27
+ @attributes = attributes
28
+ @attributes["style"] = "background-color: rgba(#{@red}, #{@green}, #{@blue}, #{@alpha});"
29
+ @text_color = contrast_of(@red, @green, @blue)
30
+
31
+ @classes = merge_class_names(
32
+ DEFAULT_CLASSES,
33
+ classes,
34
+ @text_color,
35
+ )
36
+ end
37
+
38
+ private def contrast_of(red, green, blue)
39
+ luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255
40
+
41
+ luminance > 0.5 ? "ariadne-text-black" : "ariadne-text-white"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,6 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ export default class RichTextArea extends Controller {
3
+ static targets: string[];
4
+ readonly editorTargets: [HTMLDivElement];
5
+ connect(): void;
6
+ }
@@ -0,0 +1,38 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+ import { Editor } from '@tiptap/core';
3
+ import { Document } from '@tiptap/extension-document';
4
+ import { Paragraph } from '@tiptap/extension-paragraph';
5
+ import { Text } from '@tiptap/extension-text';
6
+ import DropCursor from '@tiptap/extension-dropcursor';
7
+ import GapCursor from '@tiptap/extension-gapcursor';
8
+ import { History } from '@tiptap/extension-history';
9
+ class RichTextArea extends Controller {
10
+ connect() {
11
+ for (const editorElement of this.editorTargets) {
12
+ const pmEditor = new Editor({
13
+ extensions: [DropCursor, GapCursor, History, Document, Paragraph, Text],
14
+ content: '',
15
+ injectCSS: false,
16
+ element: editorElement,
17
+ editorProps: {
18
+ attributes: {
19
+ class: 'ariadne-h-28 ariadne-max-h-48 ariadne-p-2 ariadne-rounded-lg ariadne-overflow-y-auto focus:ariadne-outline-none',
20
+ },
21
+ },
22
+ parseOptions: {
23
+ preserveWhitespace: true,
24
+ },
25
+ });
26
+ const tiptapValueContainer = editorElement.previousElementSibling;
27
+ if (tiptapValueContainer) {
28
+ const parentForm = editorElement.closest('form');
29
+ parentForm === null || parentForm === void 0 ? void 0 : parentForm.addEventListener('submit', () => {
30
+ tiptapValueContainer.setAttribute('value', pmEditor.getText() || '');
31
+ pmEditor.commands.clearContent(); // TODO: test this
32
+ });
33
+ }
34
+ }
35
+ }
36
+ }
37
+ RichTextArea.targets = ['editor'];
38
+ export default RichTextArea;