Merged in feat/BOOK-61-refactor-hotel-page-css-variables (pull request #3014)
Feat/BOOK-61 refactor hotel page css variables * feat(BOOK-61): Breadcrumbs * feat(BOOK-61): intro section * feat(BOOK-61): show more button * feat(BOOK-61): rooms section * feat(BOOK-61): sidepeeks * feat(BOOK-61): deprecated old Link component * feat(BOOK-61): added new TextLink component to the design-system * feat(BOOK-61): replaced deprecated links with new TextLink component * feat(BOOK-61): miscellaneous changes Approved-by: Bianca Widstam Approved-by: Christel Westerberg
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
|
||||
import { expect } from 'storybook/test'
|
||||
import OldDSLink from '.'
|
||||
|
||||
const meta: Meta<typeof OldDSLink> = {
|
||||
title: 'Components/OldDSLink',
|
||||
component: OldDSLink,
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['small', 'regular', 'tiny', 'none'],
|
||||
},
|
||||
scroll: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
prefetch: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
partialMatch: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj<typeof OldDSLink>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
active: false,
|
||||
href: 'https://www.scandichotels.com/en',
|
||||
},
|
||||
render: (args) => <OldDSLink {...args}>{args.href}</OldDSLink>,
|
||||
play: async ({ canvasElement }) => {
|
||||
const link = canvasElement.querySelector('a')
|
||||
if (!link) throw new Error('Link not found')
|
||||
expect(link).toBeInTheDocument()
|
||||
},
|
||||
}
|
||||
|
||||
export const Focused: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: Default.render,
|
||||
play: async ({ canvasElement }) => {
|
||||
const link = canvasElement.querySelector('a')
|
||||
if (!link) throw new Error('Link not found')
|
||||
expect(link).toBeInTheDocument()
|
||||
|
||||
expect(link).not.toHaveFocus()
|
||||
let styles = getComputedStyle(link)
|
||||
expect(styles.outlineStyle).toBe('none')
|
||||
expect(parseFloat(styles.outlineWidth)).toBe(0)
|
||||
|
||||
link?.focus()
|
||||
|
||||
expect(link).toHaveFocus()
|
||||
styles = getComputedStyle(link)
|
||||
expect(styles.outlineStyle).not.toBe('none')
|
||||
expect(parseFloat(styles.outlineWidth)).toBeGreaterThan(0)
|
||||
},
|
||||
}
|
||||
122
packages/design-system/lib/components/OldDSLink/index.tsx
Normal file
122
packages/design-system/lib/components/OldDSLink/index.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
'use client'
|
||||
import NextLink from 'next/link'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { linkVariants } from './variants'
|
||||
|
||||
import type { LinkProps } from './link'
|
||||
|
||||
export { LinkProps }
|
||||
|
||||
/**
|
||||
* @deprecated Use `@scandic-hotels/design-system/TextLink` instead.
|
||||
*/
|
||||
export default function Link({
|
||||
active,
|
||||
className,
|
||||
color,
|
||||
href,
|
||||
partialMatch = false,
|
||||
textDecoration,
|
||||
size,
|
||||
scroll = true,
|
||||
prefetch,
|
||||
variant,
|
||||
weight,
|
||||
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(() => {
|
||||
const 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 linkProps = {
|
||||
href: fullUrl,
|
||||
className: classNames,
|
||||
}
|
||||
|
||||
return isExternal ? (
|
||||
<a
|
||||
{...linkProps}
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
if (onClick) {
|
||||
onClick(e)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<NextLink
|
||||
scroll={scroll}
|
||||
prefetch={prefetch}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
{...linkProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const useCheckIfExternalLink = (url: string) => {
|
||||
return useMemo(() => {
|
||||
if (typeof window !== 'undefined' && url?.length) {
|
||||
try {
|
||||
const hostName = window.location.hostname
|
||||
const newURL = new URL(url)
|
||||
|
||||
const hostsMatch = hostName === newURL.hostname
|
||||
const langRouteRegex = /^\/[a-zA-Z]{2}\//
|
||||
|
||||
return !hostsMatch || !langRouteRegex.test(newURL.pathname)
|
||||
} catch {
|
||||
// Don't care. Expecting internal url (#, /my-pages/overview, etc)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, [url])
|
||||
}
|
||||
177
packages/design-system/lib/components/OldDSLink/link.module.css
Normal file
177
packages/design-system/lib/components/OldDSLink/link.module.css
Normal file
@@ -0,0 +1,177 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.large {
|
||||
font-family: var(--Link-md-Font-family);
|
||||
font-size: var(--Link-md-Size);
|
||||
font-weight: var(--Link-md-Font-weight);
|
||||
letter-spacing: var(--Link-md-Letter-spacing);
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-family: var(--Link-sm-Font-family);
|
||||
font-size: var(--Link-sm-Size);
|
||||
font-weight: var(--Link-sm-Font-weight);
|
||||
letter-spacing: var(--Link-sm-Letter-spacing);
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
/* Tiny should be removed, it's not a variant of the Link*/
|
||||
.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);
|
||||
}
|
||||
}
|
||||
13
packages/design-system/lib/components/OldDSLink/link.ts
Normal file
13
packages/design-system/lib/components/OldDSLink/link.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
keepSearchParams?: boolean
|
||||
}
|
||||
44
packages/design-system/lib/components/OldDSLink/variants.ts
Normal file
44
packages/design-system/lib/components/OldDSLink/variants.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
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,
|
||||
large: styles.large,
|
||||
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: 'large',
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user