Skip to content

Commit 14dd0d3

Browse files
HDingercamertron
andauthoredNov 22, 2024··
Add support for trailing and leading visual icons in Primer::Beta::Link (second try) (#3041)
Co-authored-by: Cameron Dutro <camertron@gmail.com> Co-authored-by: camertron <camertron@users.noreply.github.com>
1 parent 6e35af0 commit 14dd0d3

File tree

14 files changed

+117
-42
lines changed

14 files changed

+117
-42
lines changed
 

‎.changeset/odd-dots-laugh.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/view-components': minor
3+
---
4+
5+
Support leading and trailing icons for Links

‎app/components/primer/base_component.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,16 @@ class BaseComponent < Primer::Component
151151
# | :- | :- | :- |
152152
# | classes | String | CSS class name value to be concatenated with generated Primer CSS classes. |
153153
# | test_selector | String | Adds `data-test-selector='given value'` in non-Production environments for testing purposes. |
154+
# | trim | Boolean | Calls `strip` on the content to remove trailing and leading white spaces. |
154155
def initialize(tag:, classes: nil, **system_arguments)
155156
@tag = tag
156157

157158
@system_arguments = validate_arguments(tag: tag, **system_arguments)
158159

159160
@result = Primer::Classify.call(**@system_arguments.merge(classes: classes))
160161

162+
@trim = !!@system_arguments.delete(:trim)
163+
161164
@system_arguments[:"data-view-component"] = true
162165
# Filter out Primer keys so they don't get assigned as HTML attributes
163166
@content_tag_args = add_test_selector(@system_arguments).except(*Primer::Classify::Utilities::UTILITIES.keys)
@@ -167,7 +170,7 @@ def call
167170
if SELF_CLOSING_TAGS.include?(@tag)
168171
tag(@tag, @content_tag_args.merge(@result))
169172
else
170-
content_tag(@tag, content, @content_tag_args.merge(@result))
173+
content_tag(@tag, @trim ? trimmed_content : content, @content_tag_args.merge(@result))
171174
end
172175
end
173176
end

‎app/components/primer/beta/button.rb

-11
Original file line numberDiff line numberDiff line change
@@ -159,17 +159,6 @@ def before_render
159159
"Button--invisible-noVisuals"
160160
)
161161
end
162-
163-
def trimmed_content
164-
return if content.blank?
165-
166-
trimmed_content = content.strip
167-
168-
return trimmed_content unless content.html_safe?
169-
170-
# strip unsets `html_safe`, so we have to set it back again to guarantee that HTML blocks won't break
171-
trimmed_content.html_safe # rubocop:disable Rails/OutputSafety
172-
end
173162
end
174163
end
175164
end
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<%= render Primer::ConditionalWrapper.new(condition: tooltip?, trim: true, tag: :span, position: :relative) do %>
2+
<%= render(Primer::BaseComponent.new(trim: true, **@system_arguments)) do %>
3+
<%= render(Primer::BaseComponent.new(tag: :span, classes: "Link-content", trim: true)) do %>
4+
<% if leading_visual %>
5+
<%= leading_visual %>
6+
<% end %>
7+
<%= content %>
8+
<% if trailing_visual %>
9+
<%= trailing_visual %>
10+
<% end %>
11+
<% end %>
12+
<% end %>
13+
<%= tooltip if tooltip? %>
14+
<% end %>

‎app/components/primer/beta/link.pcss

+9
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,12 @@
6767
color: inherit !important;
6868
}
6969
}
70+
71+
.Link-content {
72+
display: inline-flex;
73+
align-items: center;
74+
/* stylelint-disable-next-line primer/typography */
75+
line-height: normal;
76+
gap: var(--base-size-4);
77+
text-decoration: inherit;
78+
}

‎app/components/primer/beta/link.rb

+26-14
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@ class Link < Primer::Component
3030
Primer::Alpha::Tooltip.new(**system_arguments)
3131
}
3232

33+
# Leading visuals appear to the left of the link text.
34+
#
35+
# Use:
36+
#
37+
# - `leading_visual_icon` which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Octicon) %>.
38+
#
39+
# @param system_arguments [Hash] Same arguments as <%= link_to_component(Primer::Beta::Octicon) %>.
40+
renders_one :leading_visual, types: {
41+
icon: lambda { |**system_arguments|
42+
Primer::Beta::Octicon.new(**system_arguments)
43+
}
44+
}
45+
46+
# Trailing visuals appear to the right of the link text.
47+
#
48+
# Use:
49+
#
50+
# - `trailing_visual_icon` which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Octicon) %>.
51+
#
52+
# @param system_arguments [Hash] Same arguments as <%= link_to_component(Primer::Beta::Octicon) %>.
53+
renders_one :trailing_visual, types: {
54+
icon: lambda { |**system_arguments|
55+
Primer::Beta::Octicon.new(**system_arguments)
56+
}
57+
}
58+
3359
# @param href [String] URL to be used for the Link. Required. If the requirements are not met an error will be raised in non production environments. In production, an empty link element will be rendered.
3460
# @param scheme [Symbol] <%= one_of(Primer::Beta::Link::SCHEME_MAPPINGS.keys) %>
3561
# @param muted [Boolean] Uses light gray for Link color, and blue on hover.
@@ -54,20 +80,6 @@ def initialize(href: nil, scheme: DEFAULT_SCHEME, muted: false, underline: false
5480
def before_render
5581
raise ArgumentError, "href is required" if @system_arguments[:href].nil? && !Rails.env.production?
5682
end
57-
58-
def call
59-
if tooltip.present?
60-
render Primer::BaseComponent.new(tag: :span, position: :relative) do
61-
render(Primer::BaseComponent.new(**@system_arguments)) do
62-
content
63-
end.to_s + tooltip.to_s
64-
end
65-
else
66-
render(Primer::BaseComponent.new(**@system_arguments)) do
67-
content
68-
end
69-
end
70-
end
7183
end
7284
end
7385
end

‎app/components/primer/button_component.rb

-11
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,5 @@ def initialize(
111111
def link?
112112
@scheme == LINK_SCHEME
113113
end
114-
115-
def trimmed_content
116-
return if content.blank?
117-
118-
trimmed_content = content.strip
119-
120-
return trimmed_content unless content.html_safe?
121-
122-
# strip unsets `html_safe`, so we have to set it back again to guarantee that HTML blocks won't break
123-
trimmed_content.html_safe # rubocop:disable Rails/OutputSafety
124-
end
125114
end
126115
end

‎app/components/primer/component.rb

+7
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,12 @@ def shouldnt_raise_error?
148148
def should_raise_aria_error?
149149
!Rails.env.production? && raise_on_invalid_aria? && !ENV["PRIMER_WARNINGS_DISABLED"]
150150
end
151+
152+
def trimmed_content
153+
return content unless content.present?
154+
155+
# strip unsets `html_safe`, so we have to set it back again to guarantee that HTML blocks won't break
156+
content.html_safe? ? content.strip.html_safe : content.strip # rubocop:disable Rails/OutputSafety
157+
end
151158
end
152159
end

‎app/components/primer/conditional_wrapper.rb

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ class ConditionalWrapper < Primer::Component
1010
def initialize(condition:, **base_component_arguments)
1111
@condition = condition
1212
@base_component_arguments = base_component_arguments
13+
@trim = !!@base_component_arguments.delete(:trim)
1314
end
1415

1516
def call
16-
return content unless @condition
17+
unless @condition
18+
return @trim ? trimmed_content : content
19+
end
1720

18-
BaseComponent.new(**@base_component_arguments).render_in(self) { content }
21+
BaseComponent.new(trim: @trim, **@base_component_arguments).render_in(self) { content }
1922
end
2023
end
2124
end

‎previews/primer/beta/link_preview.rb

+24-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ class LinkPreview < ViewComponent::Preview
99
# @param underline [Boolean]
1010
# @param muted [Boolean]
1111
# @param scheme [Symbol] select [default, primary, secondary]
12-
def playground(scheme: :default, muted: false, underline: true)
13-
render(Primer::Beta::Link.new(href: "#", scheme: scheme, muted: muted, underline: underline)) { "This is a link!" }
12+
# @param leading_visual_icon [Symbol] octicon
13+
# @param trailing_visual_icon [Symbol] octicon
14+
def playground(scheme: :default, muted: false, underline: true, leading_visual_icon: nil, trailing_visual_icon: nil)
15+
render(Primer::Beta::Link.new(href: "#", scheme: scheme, muted: muted, underline: underline)) do |link|
16+
link.with_leading_visual_icon(icon: leading_visual_icon) if leading_visual_icon && leading_visual_icon != :none
17+
link.with_trailing_visual_icon(icon: trailing_visual_icon) if trailing_visual_icon && trailing_visual_icon != :none
18+
"This is a link!"
19+
end
1420
end
1521

1622
# @label Default Options
@@ -66,6 +72,22 @@ def color_scheme_secondary_muted
6672
render(Primer::Beta::Link.new(href: "#", scheme: :secondary, muted: true)) { "This is a muted secondary link color." }
6773
end
6874
# @!endgroup
75+
76+
# @label With leading icon
77+
def with_leading_icon
78+
render(Primer::Beta::Link.new(href: "#")) do |component|
79+
component.with_leading_visual_icon(icon: :"mark-github")
80+
"Link with leading icon"
81+
end
82+
end
83+
84+
# @label With trailing icon
85+
def with_trailing_icon
86+
render(Primer::Beta::Link.new(href: "#")) do |component|
87+
component.with_trailing_visual_icon(icon: :"link-external")
88+
"Link with trailing icon"
89+
end
90+
end
6991
end
7092
end
7193
end

‎test/components/beta/link_test.rb

+23-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_renders_content_and_not_muted_link
1515
def test_renders_no_additional_whitespace
1616
result = render_inline(Primer::Beta::Link.new(href: "http://joe-jonas-shirtless.com")) { "content" }
1717

18-
assert_match(%r{^<a[^>]+>content</a>$}, result.to_s)
18+
assert_match(%r{^<a[^>]+><span[^>]+>content</span></a>$}, result.to_s)
1919
end
2020

2121
def test_renders_without_trailing_newline
@@ -92,4 +92,26 @@ def test_renders_with_tooltip_sibling
9292

9393
assert_selector("a[href='http://google.com'] + tool-tip", text: "Tooltip text", visible: false)
9494
end
95+
96+
def test_renders_leading_visual_icon
97+
render_inline(Primer::Beta::Link.new(href: "http://google.com")) do |component|
98+
component.with_leading_visual_icon(icon: "plus")
99+
"content"
100+
end
101+
102+
assert_selector("a[href='http://google.com']")
103+
assert_selector(".octicon-plus")
104+
end
105+
106+
def test_renders_trailing_visual_icon
107+
render_inline(Primer::Beta::Link.new(href: "http://google.com")) do |component|
108+
component.with_leading_visual_icon(icon: "plus")
109+
component.with_trailing_visual_icon(icon: "alert")
110+
"content"
111+
end
112+
113+
assert_selector("a[href='http://google.com']")
114+
assert_selector("a svg:first-child.octicon-plus")
115+
assert_selector("a svg:nth-child(2).octicon-alert")
116+
end
95117
end

0 commit comments

Comments
 (0)
Please sign in to comment.