Merged in feat/sw-3230-move-link-to-design-system (pull request #2618)

feat(SW-3230): Move Link to design-system

* Move Link to design-system

* Remove comments


Approved-by: Linus Flood
This commit is contained in:
Anton Gunnarsson
2025-08-12 12:35:20 +00:00
parent 4c9790b938
commit 8518d018f8
80 changed files with 139 additions and 164 deletions

View File

@@ -3,8 +3,8 @@
import Body from "@scandic-hotels/design-system/Body"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Link from "@scandic-hotels/design-system/Link"
import Link from "../Link"
import AlertSidepeek from "./Sidepeek"
import { IconByAlertType } from "./utils"
import { alertVariants } from "./variants"

View File

@@ -1,116 +0,0 @@
"use client"
import NextLink from "next/link"
import { usePathname, useSearchParams } from "next/navigation"
import { useCallback, useMemo } from "react"
import { useCheckIfExternalLink } from "@/hooks/useCheckIfExternalLink"
import { trackClick } from "@/utils/tracking"
import { linkVariants } from "./variants"
import type { LinkProps } from "./link"
export default function Link({
active,
className,
color,
href,
partialMatch = false,
textDecoration,
size,
scroll = true,
prefetch,
variant,
weight,
trackingId,
trackingParams,
onClick,
/**
* Decides if the link should include the current search params in the URL.
* If the given href also contains search params, they take precedence and
* override any current search params. If you need to merge them, handle that
* in your component that passes the href here.
*/
keepSearchParams,
...props
}: LinkProps) {
const currentPageSlug = usePathname()
const searchParams = useSearchParams()
let isActive = active || currentPageSlug === href
if (partialMatch && !isActive) {
isActive = currentPageSlug === href
}
const classNames = linkVariants({
active: isActive,
className,
textDecoration,
color,
size,
weight,
variant,
})
const fullUrl = useMemo(() => {
let newPath = href
if (keepSearchParams && searchParams.size) {
if (newPath.includes("?")) {
const newPathParts = newPath.split("?")
const newSearchParams = new URLSearchParams(newPathParts[1])
searchParams.forEach((v, k) => {
if (!newSearchParams.has(k)) {
newSearchParams.set(k, v)
}
})
return `${newPathParts[0]}?${newSearchParams}`
}
return `${newPath}?${searchParams}`
}
return newPath
}, [href, searchParams, keepSearchParams])
// TODO: Remove this check (and hook) and only return <Link /> when current web is deleted
const isExternal = useCheckIfExternalLink(href)
const trackClickById = useCallback(() => {
if (trackingId) {
trackClick(trackingId, trackingParams)
}
}, [trackingId, trackingParams])
const linkProps = {
href: fullUrl,
className: classNames,
}
return isExternal ? (
<a
{...linkProps}
{...props}
onClick={(e) => {
if (onClick) {
onClick(e)
}
}}
/>
) : (
<NextLink
scroll={scroll}
prefetch={prefetch}
onClick={(e) => {
if (onClick) {
onClick(e)
}
if (trackingId) {
trackClickById()
}
}}
id={trackingId}
{...props}
{...linkProps}
/>
)
}

View File

@@ -1,176 +0,0 @@
.link {
text-decoration: none;
}
.underline {
text-decoration: underline;
}
.peach50 {
color: var(--Primary-Dark-On-Surface-Accent);
}
.red {
color: var(--Primary-Strong-Button-Primary-On-Fill-Normal);
}
.white {
color: var(--Base-Button-Primary-On-Fill-Normal);
&:hover,
&:active {
color: var(--Base-Button-Primary-On-Fill-Hover);
}
&:hover *,
&:active * {
fill: var(--Base-Button-Primary-On-Fill-Hover);
}
}
.Text-Interactive-Default {
color: var(--Text-Interactive-Default);
&:hover {
color: var(--Text-Interactive-Hover);
}
}
.Text-Interactive-Secondary {
color: var(--Text-Interactive-Secondary);
&:hover {
color: var(--Text-Interactive-Secondary-Hover);
}
}
.icon {
align-items: center;
display: inline-flex;
gap: var(--Space-x05);
}
.breadcrumb {
font-family: var(--typography-Footnote-Bold-fontFamily);
font-size: var(--typography-Footnote-Bold-fontSize);
font-weight: 500;
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
line-height: var(--typography-Footnote-Bold-lineHeight);
}
.link.breadcrumb {
font-family: var(--typography-Footnote-Bold-fontFamily);
font-size: var(--typography-Footnote-Bold-fontSize);
font-weight: 500;
letter-spacing: var(--typography-Footnote-Bold-letterSpacing);
line-height: var(--typography-Footnote-Bold-lineHeight);
}
.myPageMobileDropdown {
display: flex;
align-items: center;
color: var(--Scandic-Brand-Burgundy);
font-family: var(--typography-Body-Regular-fontFamily);
font-size: var(--typography-Body-Regular-fontSize);
line-height: var(--typography-Body-Regular-lineHeight);
letter-spacing: var(--typography-Body-Regular-letterSpacing);
padding: var(--Space-x1);
border-radius: var(--Corner-radius-md);
gap: var(--Space-x1);
justify-content: space-between;
&:hover {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
border-radius: var(--Corner-radius-md);
}
}
.languageSwitcher {
color: var(--Text-Interactive-Default);
&:hover {
background-color: var(--Surface-Primary-Hover);
color: var(--Text-Interactive-Default);
}
}
.shortcut {
display: grid;
align-items: center;
font-family: var(--typography-Body-Regular-fontFamily);
font-size: var(--typography-Body-Regular-fontSize);
font-weight: var(--typography-Body-Regular-fontWeight);
letter-spacing: var(--typography-Body-Regular-letterSpacing);
line-height: var(--typography-Body-Regular-lineHeight);
gap: var(--Space-x2);
grid-template-columns: 1fr auto;
padding: var(--Space-x2) var(--Space-x3);
background-color: var(--Base-Surface-Primary-light-Normal);
transition: background-color 0.3s;
&:hover {
background-color: var(--UI-Input-Controls-Surface-Hover);
}
&:last-of-type {
border-bottom: none;
}
}
.regular {
font-family: var(--typography-Body-Regular-fontFamily);
font-size: var(--typography-Body-Regular-fontSize);
font-weight: 400;
letter-spacing: var(--typography-Body-Regular-letterSpacing);
line-height: var(--typography-Body-Regular-lineHeight);
}
.small {
font-family: var(--typography-Caption-Regular-fontFamily);
font-size: var(--typography-Caption-Regular-fontSize);
font-weight: 400;
letter-spacing: var(--typography-Caption-Regular-letterSpacing);
line-height: var(--typography-Caption-Regular-lineHeight);
}
.tiny {
font-family: var(--typography-Footnote-Regular-fontFamily);
font-size: var(--typography-Footnote-Regular-fontSize);
font-weight: var(--typography-Footnote-Regular-fontWeight);
letter-spacing: var(--typography-Footnote-Regular-letterSpacing);
line-height: var(--typography-Footnote-Regular-lineHeight);
}
.bold {
font-family: var(--typography-Body-Bold-fontFamily);
font-size: var(--typography-Body-Bold-fontSize);
font-weight: 500
/* Should be fixed when variables starts working: var(--typography-Body-Bold-fontWeight) */;
letter-spacing: var(--typography-Body-Bold-letterSpacing);
line-height: var(--typography-Body-Bold-lineHeight);
}
.menu {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: var(--Space-x1);
gap: var(--Space-x15);
border-radius: var(--Corner-radius-md);
color: var(--Text-Interactive-Default);
&:hover {
background-color: var(--Surface-Primary-Hover);
color: var(--Text-Interactive-Default);
}
}
.navigation {
padding: var(--Space-x05) var(--Space-x1);
color: var(--Text-Interactive-Default);
&:hover {
color: var(--Text-Interactive-Default);
}
}

View File

@@ -1,15 +0,0 @@
import type { VariantProps } from "class-variance-authority"
import type { linkVariants } from "./variants"
export interface LinkProps
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "color">,
VariantProps<typeof linkVariants> {
href: string
scroll?: boolean
partialMatch?: boolean
prefetch?: boolean
trackingId?: string
trackingParams?: Record<string, string>
keepSearchParams?: boolean
}

View File

@@ -1,44 +0,0 @@
import { cva } from "class-variance-authority"
import styles from "./link.module.css"
export const linkVariants = cva(styles.link, {
variants: {
active: {
true: styles.active,
},
color: {
none: "",
peach50: styles.peach50,
white: styles.white,
red: styles.red,
"Text/Interactive/Default": styles["Text-Interactive-Default"],
"Text/Interactive/Secondary": styles["Text-Interactive-Secondary"],
},
size: {
small: styles.small,
regular: styles.regular,
tiny: styles.tiny,
none: "",
},
textDecoration: {
underline: styles.underline,
},
weight: {
bold: styles.bold,
},
variant: {
icon: styles.icon,
breadcrumb: styles.breadcrumb,
myPageMobileDropdown: styles.myPageMobileDropdown,
navigation: styles.navigation,
menu: styles.menu,
shortcut: styles.shortcut,
languageSwitcher: styles.languageSwitcher,
},
},
defaultVariants: {
color: "Text/Interactive/Default",
size: "regular",
},
})

View File

@@ -1,9 +1,9 @@
import Body from "@scandic-hotels/design-system/Body"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Link from "@scandic-hotels/design-system/Link"
import Title from "@scandic-hotels/design-system/Title"
import Image from "@/components/Image"
import Link from "@/components/TempDesignSystem/Link"
import { loyaltyCardVariants } from "./variants"