consolidate all external links through external_link component
All checks were successful
deploy / deploy (push) Successful in 1m32s
All checks were successful
deploy / deploy (push) Successful in 1m32s
Add icon={false} option to external_link for links with their own
visual indicator. Migrate remaining manual target="_blank" links:
email settings adapter links, product show provider edit, card radio
links, social link cards/icons, page renderer tracking and video
fallback. Every external link in the codebase now goes through the
single component — one place to change rel, target, or sr-only text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
156a23da16
commit
e139a75b69
@ -426,9 +426,12 @@ defmodule BerrypodWeb.CoreComponents do
|
|||||||
@doc """
|
@doc """
|
||||||
Renders a link to an external site with proper security attributes,
|
Renders a link to an external site with proper security attributes,
|
||||||
an external-link icon, and screen reader context.
|
an external-link icon, and screen reader context.
|
||||||
|
|
||||||
|
Set `icon={false}` when the link already contains its own visual indicator.
|
||||||
"""
|
"""
|
||||||
attr :href, :string, required: true
|
attr :href, :string, required: true
|
||||||
attr :class, :string, default: nil
|
attr :class, :string, default: nil
|
||||||
|
attr :icon, :boolean, default: true
|
||||||
attr :rest, :global
|
attr :rest, :global
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
||||||
@ -436,7 +439,7 @@ defmodule BerrypodWeb.CoreComponents do
|
|||||||
~H"""
|
~H"""
|
||||||
<a href={@href} target="_blank" rel="noopener noreferrer" class={@class} {@rest}>
|
<a href={@href} target="_blank" rel="noopener noreferrer" class={@class} {@rest}>
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
<.icon name="hero-arrow-top-right-on-square" class="external-link-icon" />
|
<.icon :if={@icon} name="hero-arrow-top-right-on-square" class="external-link-icon" />
|
||||||
<span class="sr-only">(opens in new tab)</span>
|
<span class="sr-only">(opens in new tab)</span>
|
||||||
</a>
|
</a>
|
||||||
"""
|
"""
|
||||||
@ -623,17 +626,16 @@ defmodule BerrypodWeb.CoreComponents do
|
|||||||
{@option.description}
|
{@option.description}
|
||||||
</span>
|
</span>
|
||||||
<span :if={@option[:badge]} class="card-radio-badge">{@option.badge}</span>
|
<span :if={@option[:badge]} class="card-radio-badge">{@option.badge}</span>
|
||||||
<a
|
<.external_link
|
||||||
:if={@option[:url]}
|
:if={@option[:url]}
|
||||||
href={@option.url}
|
href={@option.url}
|
||||||
target="_blank"
|
icon={false}
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="card-radio-link"
|
class="card-radio-link"
|
||||||
onclick="event.stopPropagation();"
|
onclick="event.stopPropagation();"
|
||||||
aria-label={@option.name <> " (opens in new tab)"}
|
aria-label={@option.name}
|
||||||
>
|
>
|
||||||
{@option.name} ↗
|
{@option.name} ↗
|
||||||
</a>
|
</.external_link>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ defmodule BerrypodWeb.ShopComponents.Content do
|
|||||||
|
|
||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
|
|
||||||
|
import BerrypodWeb.CoreComponents, only: [external_link: 1]
|
||||||
import BerrypodWeb.ShopComponents.Base
|
import BerrypodWeb.ShopComponents.Base
|
||||||
|
|
||||||
@default_social_links [
|
@default_social_links [
|
||||||
@ -421,18 +422,16 @@ defmodule BerrypodWeb.ShopComponents.Content do
|
|||||||
<h3 class="card-heading">{@title}</h3>
|
<h3 class="card-heading">{@title}</h3>
|
||||||
<div class="social-link-card-list">
|
<div class="social-link-card-list">
|
||||||
<%= for link <- @links do %>
|
<%= for link <- @links do %>
|
||||||
<a
|
<.external_link
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target="_blank"
|
icon={false}
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="social-link-card-item themed-button-outline"
|
class="social-link-card-item themed-button-outline"
|
||||||
>
|
>
|
||||||
<span class="social-link-card-icon">
|
<span class="social-link-card-icon">
|
||||||
<.social_icon platform={link.platform} />
|
<.social_icon platform={link.platform} />
|
||||||
</span>
|
</span>
|
||||||
<span>{link.label}</span>
|
<span>{link.label}</span>
|
||||||
<span class="sr-only">(opens in new tab)</span>
|
</.external_link>
|
||||||
</a>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</.shop_card>
|
</.shop_card>
|
||||||
@ -458,15 +457,14 @@ defmodule BerrypodWeb.ShopComponents.Content do
|
|||||||
~H"""
|
~H"""
|
||||||
<div class="social-links">
|
<div class="social-links">
|
||||||
<%= for link <- @links do %>
|
<%= for link <- @links do %>
|
||||||
<a
|
<.external_link
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target="_blank"
|
icon={false}
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="social-link"
|
class="social-link"
|
||||||
aria-label={link.label}
|
aria-label={link.label}
|
||||||
>
|
>
|
||||||
<.social_icon platform={link.platform} />
|
<.social_icon platform={link.platform} />
|
||||||
</a>
|
</.external_link>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -268,16 +268,15 @@ defmodule BerrypodWeb.Admin.EmailSettings do
|
|||||||
<div>
|
<div>
|
||||||
<h3 class="admin-section-subheading">
|
<h3 class="admin-section-subheading">
|
||||||
{adapter.name}
|
{adapter.name}
|
||||||
<a
|
<.external_link
|
||||||
:if={adapter.url}
|
:if={adapter.url}
|
||||||
href={adapter.url}
|
href={adapter.url}
|
||||||
target="_blank"
|
icon={false}
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="admin-link-subtle admin-adapter-link"
|
class="admin-link-subtle admin-adapter-link"
|
||||||
aria-label={adapter.name <> " website (opens in new tab)"}
|
aria-label={adapter.name <> " website"}
|
||||||
>
|
>
|
||||||
↗
|
↗
|
||||||
</a>
|
</.external_link>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="admin-section-desc">{adapter.description}</p>
|
<p class="admin-section-desc">{adapter.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -98,17 +98,15 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
|||||||
<.status_badge status={@product.status} />
|
<.status_badge status={@product.status} />
|
||||||
</div>
|
</div>
|
||||||
<:actions>
|
<:actions>
|
||||||
<.link
|
<.external_link
|
||||||
:if={provider_edit_url(@product)}
|
:if={provider_edit_url(@product)}
|
||||||
href={provider_edit_url(@product)}
|
href={provider_edit_url(@product)}
|
||||||
target="_blank"
|
icon={false}
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="admin-btn admin-btn-ghost admin-btn-sm"
|
class="admin-btn admin-btn-ghost admin-btn-sm"
|
||||||
>
|
>
|
||||||
Edit on {provider_label(@product)}
|
Edit on {provider_label(@product)}
|
||||||
<.icon name="hero-arrow-top-right-on-square-mini" class="size-4" />
|
<.icon name="hero-arrow-top-right-on-square-mini" class="size-4" />
|
||||||
<span class="sr-only">(opens in new tab)</span>
|
</.external_link>
|
||||||
</.link>
|
|
||||||
<.link
|
<.link
|
||||||
navigate={~p"/products/#{@product.slug}"}
|
navigate={~p"/products/#{@product.slug}"}
|
||||||
class="admin-btn admin-btn-ghost admin-btn-sm"
|
class="admin-btn admin-btn-ghost admin-btn-sm"
|
||||||
|
|||||||
@ -16,7 +16,7 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
statics: BerrypodWeb.static_paths()
|
statics: BerrypodWeb.static_paths()
|
||||||
|
|
||||||
import BerrypodWeb.BlockEditorComponents
|
import BerrypodWeb.BlockEditorComponents
|
||||||
import BerrypodWeb.CoreComponents, only: [icon: 1]
|
import BerrypodWeb.CoreComponents, only: [icon: 1, external_link: 1]
|
||||||
|
|
||||||
alias Berrypod.Cart
|
alias Berrypod.Cart
|
||||||
|
|
||||||
@ -883,15 +883,12 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<%= if assigns[:order].tracking_url do %>
|
<%= if assigns[:order].tracking_url do %>
|
||||||
<a
|
<.external_link
|
||||||
href={assigns[:order].tracking_url}
|
href={assigns[:order].tracking_url}
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="order-detail-tracking-btn themed-button"
|
class="order-detail-tracking-btn themed-button"
|
||||||
aria-label="Track parcel (opens in new tab)"
|
|
||||||
>
|
>
|
||||||
Track parcel
|
Track parcel
|
||||||
</a>
|
</.external_link>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</.shop_card>
|
</.shop_card>
|
||||||
@ -1078,9 +1075,9 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
</iframe>
|
</iframe>
|
||||||
</div>
|
</div>
|
||||||
<p :if={@provider == :unknown && @raw_url != ""} class="video-embed-fallback">
|
<p :if={@provider == :unknown && @raw_url != ""} class="video-embed-fallback">
|
||||||
<a href={@raw_url} target="_blank" rel="noopener noreferrer">
|
<.external_link href={@raw_url}>
|
||||||
{if @caption != "", do: @caption, else: "Watch video"}
|
{if @caption != "", do: @caption, else: "Watch video"}
|
||||||
</a>
|
</.external_link>
|
||||||
</p>
|
</p>
|
||||||
<p :if={@caption != "" && @provider != :unknown} class="video-embed-caption">{@caption}</p>
|
<p :if={@caption != "" && @provider != :unknown} class="video-embed-caption">{@caption}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user