Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 117 additions & 13 deletions libs/web-components/src/components/link/Link.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@

export let leadingicon: GoAIconType | null = null;
export let trailingicon: GoAIconType | null = null;
export let color: "interactive" | "light" = "interactive";
export let color: "interactive" | "dark" | "light" = "interactive";
export let size: "xsmall" | "small" | "medium" | "large" = "medium";

export let action: string = "";
export let actionArg: string = "";
Expand All @@ -30,6 +31,13 @@

let _rootEl: HTMLElement;

$: _iconSize = {
xsmall: "2xsmall", // 12px
small: "xsmall", // 16px
medium: "small", // 18px
large: "medium", // 20px
}[size];

onMount(() => {
if (action) {
_rootEl.addEventListener("click", handleClick);
Expand All @@ -45,44 +53,140 @@
<div
class="link"
class:interactive={color === "interactive"}
class:dark={color === "dark"}
class:light={color === "light"}
class:xsmall={size === "xsmall"}
class:small={size === "small"}
class:medium={size === "medium"}
class:large={size === "large"}
bind:this={_rootEl}
style={styles(calculateMargin(mt, mr, mb, ml))}
data-testid={testid}
>
{#if leadingicon}<goa-icon data-testid="leading-icon" type={leadingicon} />{/if}
{#if leadingicon}<goa-icon data-testid="leading-icon" type={leadingicon} size={_iconSize} />{/if}
<slot />
{#if trailingicon}<goa-icon data-testid="trailing-icon" type={trailingicon} />{/if}
{#if trailingicon}<goa-icon data-testid="trailing-icon" type={trailingicon} size={_iconSize} />{/if}
</div>

<style>
:global(::slotted(a)) {
color: var(--goa-color-interactive-default);
}

/* Base link styles */
.link {
display: inline-flex;
align-items: center;
padding: 0;
border: none;
background: none;
cursor: pointer;
font: var(--goa-typography-body-m);
text-decoration: underline;
gap: 8px;
/* V1: Default gap fallback (4px) */
gap: var(--goa-link-gap, 0.25rem);
/* V2: Size-specific gaps override below */
}

/* Size variants - Typography and Gap */
.link.xsmall {
font: var(--goa-link-typography-xsmall);
gap: var(--goa-link-gap-xsmall, 0.125rem);
}

.link.small {
font: var(--goa-link-typography-small);
gap: var(--goa-link-gap-small, 0.1875rem);
}

.link.medium {
font: var(--goa-link-typography-medium);
gap: var(--goa-link-gap-medium, 0.25rem);
}

.link.large {
font: var(--goa-link-typography-large);
gap: var(--goa-link-gap-large, 0.3125rem);
}

/* Color variant: Interactive (Blue) */
.link.interactive {
color: var(--goa-color-interactive-default);
color: var(--goa-link-color-interactive-default, var(--goa-color-interactive-default));
}

.link.interactive :global(::slotted(a)) {
color: var(--goa-link-color-interactive-default, var(--goa-color-interactive-default)) !important;
}

.link.interactive:hover {
color: var(--goa-color-interactive-hover);
color: var(--goa-link-color-interactive-hover, var(--goa-color-interactive-hover));
}

.link.interactive:hover :global(::slotted(a)) {
color: var(--goa-link-color-interactive-hover, var(--goa-color-interactive-hover)) !important;
}

.link.interactive :global(::slotted(a:visited)) {
color: var(--goa-link-color-interactive-visited, var(--goa-color-interactive-visited)) !important;
}

.link.interactive:has(:global(::slotted(a:visited))) {
color: var(--goa-link-color-interactive-visited, var(--goa-color-interactive-visited));
}

/* Color variant: Dark (Black) */
.link.dark {
color: var(--goa-link-color-dark-default, var(--goa-color-greyscale-black));
}

.link.dark :global(::slotted(a)) {
color: var(--goa-link-color-dark-default, var(--goa-color-greyscale-black)) !important;
}

.link.dark:hover {
color: var(--goa-link-color-dark-hover, var(--goa-color-greyscale-700));
}

.link.dark:hover :global(::slotted(a)) {
color: var(--goa-link-color-dark-hover, var(--goa-color-greyscale-700)) !important;
}

.link.dark :global(::slotted(a:visited)) {
color: var(--goa-link-color-dark-visited, var(--goa-color-interactive-visited)) !important;
}

.link.dark:has(:global(::slotted(a:visited))) {
color: var(--goa-link-color-dark-visited, var(--goa-color-interactive-visited));
}

/* Color variant: Light (White) */
.link.light {
color: var(--goa-color-text-light);
color: var(--goa-link-color-light-default, var(--goa-color-text-light));
}

.link.light :global(::slotted(a)) {
color: var(--goa-link-color-light-default, var(--goa-color-text-light)) !important;
}

.link.light:hover {
color: var(--goa-color-text-light);
color: var(--goa-link-color-light-hover, var(--goa-color-greyscale-200));
}

.link.light:hover :global(::slotted(a)) {
color: var(--goa-link-color-light-hover, var(--goa-color-greyscale-200)) !important;
}

.link.light :global(::slotted(a:visited)) {
color: var(--goa-link-color-light-visited, #9D8EBB) !important;
}

.link.light:has(:global(::slotted(a:visited))) {
color: var(--goa-link-color-light-visited, #9D8EBB);
}

/* Focus */
.link:focus-within {
border-radius: var(--goa-link-border-radius-focus, var(--goa-border-radius-s));
outline: var(--goa-link-border-focus, var(--goa-border-width-l) solid var(--goa-color-interactive-focus));
outline-offset: var(--goa-link-focus-offset, var(--goa-space-3xs));
}

.link :global(::slotted(a:focus-visible)) {
outline: none;
}
</style>