feat(SW-1806): Implemented design systems button inside buttonLink component and changed teasercard buttons

Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-03-28 06:56:08 +00:00
parent 2f0224cfd5
commit 45c992dcef
14 changed files with 152 additions and 91 deletions

View File

@@ -14,7 +14,10 @@ export default function ShortcutsListItems({
return ( return (
<ul className={`${styles.list} ${className}`}> <ul className={`${styles.list} ${className}`}>
{shortcutsListItems.map((shortcut) => ( {shortcutsListItems.map((shortcut) => (
<li key={shortcut.title} className={styles.listItem}> <li
key={`${shortcut.text}_${shortcut.url}`}
className={styles.listItem}
>
<Link <Link
href={shortcut.url} href={shortcut.url}
target={shortcut.openInNewTab ? "_blank" : undefined} target={shortcut.openInNewTab ? "_blank" : undefined}

View File

@@ -0,0 +1,9 @@
.buttonLink {
border-radius: var(--Corner-radius-rounded);
border-width: 2px;
border-style: solid;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}

View File

@@ -1,25 +1,47 @@
"use client" "use client"
import NextLink from "next/link" import Link from "next/link"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { useMemo } from "react" import { type ComponentProps, type PropsWithChildren, useMemo } from "react"
import Button from "@/components/TempDesignSystem/Button"
import { trackClick } from "@/utils/tracking" import { trackClick } from "@/utils/tracking"
import type { ButtonLinkProps } from "@/types/components/buttonLink" import { variants } from "./variants"
import type { VariantProps } from "class-variance-authority"
export interface ButtonLinkProps
extends PropsWithChildren,
Omit<ComponentProps<typeof Link>, "color">,
VariantProps<typeof variants> {
trackingId?: string
trackingParams?: Record<string, string>
appendToCurrentPath?: boolean
}
export default function ButtonLink({ export default function ButtonLink({
children, variant,
color,
size,
typography,
className,
href, href,
target, target,
onClick = () => {},
trackingId, trackingId,
trackingParams, trackingParams,
onClick = () => {},
appendToCurrentPath, appendToCurrentPath,
...buttonProps ...props
}: ButtonLinkProps) { }: ButtonLinkProps) {
const currentPageSlug = usePathname() const currentPageSlug = usePathname()
const classNames = variants({
variant,
color,
size,
typography,
className,
})
const fullUrl = useMemo(() => { const fullUrl = useMemo(() => {
let newPath = href let newPath = href
@@ -31,19 +53,17 @@ export default function ButtonLink({
}, [href, appendToCurrentPath, currentPageSlug]) }, [href, appendToCurrentPath, currentPageSlug])
return ( return (
<Button {...buttonProps} asChild> <Link
<NextLink className={classNames}
href={fullUrl} href={fullUrl}
target={target} target={target}
onClick={(e) => { onClick={(e) => {
onClick(e) onClick(e)
if (trackingId) { if (trackingId) {
trackClick(trackingId, trackingParams) trackClick(trackingId, trackingParams)
} }
}} }}
> {...props}
{children} />
</NextLink>
</Button>
) )
} }

View File

@@ -0,0 +1,7 @@
import { cva } from "class-variance-authority"
import { withButton } from "@scandic-hotels/design-system/Button"
import styles from "./buttonLink.module.css"
export const variants = cva(styles.buttonLink, withButton({}))

View File

@@ -30,7 +30,10 @@ export default async function AccessibilityAmenity({
{accessibilityPageUrl && ( {accessibilityPageUrl && (
<ButtonLink <ButtonLink
href={`/${accessibilityPageUrl}`} href={`/${accessibilityPageUrl}`}
intent="secondary" variant="Secondary"
color="Primary"
size="Medium"
typography="Body/Paragraph/mdBold"
appendToCurrentPath appendToCurrentPath
> >
{intl.formatMessage({ id: "About accessibility" })} {intl.formatMessage({ id: "About accessibility" })}

View File

@@ -30,7 +30,10 @@ export default async function ParkingAmenity({
{parkingPageUrl && ( {parkingPageUrl && (
<ButtonLink <ButtonLink
href={`/${parkingPageUrl}`} href={`/${parkingPageUrl}`}
intent="secondary" variant="Secondary"
color="Primary"
size="Medium"
typography="Body/Paragraph/mdBold"
appendToCurrentPath appendToCurrentPath
> >
{intl.formatMessage({ id: "About parking" })} {intl.formatMessage({ id: "About parking" })}

View File

@@ -102,9 +102,10 @@ export default async function RestaurantBarItem({
<div className={styles.ctaWrapper}> <div className={styles.ctaWrapper}>
{bookTableUrl ? ( {bookTableUrl ? (
<ButtonLink <ButtonLink
fullWidth variant="Primary"
theme="base" color="Primary"
intent="primary" size="Medium"
typography="Body/Paragraph/mdBold"
href={bookTableUrl} href={bookTableUrl}
target="_blank" target="_blank"
trackingId="book a table" trackingId="book a table"
@@ -117,9 +118,10 @@ export default async function RestaurantBarItem({
) : null} ) : null}
{restaurantPage && mainBody?.length ? ( {restaurantPage && mainBody?.length ? (
<ButtonLink <ButtonLink
fullWidth variant="Secondary"
theme="base" color="Primary"
intent="secondary" size="Medium"
typography="Body/Paragraph/mdBold"
href={`/${restaurant.nameInUrl}`} href={`/${restaurant.nameInUrl}`}
appendToCurrentPath appendToCurrentPath
> >

View File

@@ -2,12 +2,12 @@
import { useState } from "react" import { useState } from "react"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons" import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
import ButtonLink from "@/components/ButtonLink"
import JsonToHtml from "@/components/JsonToHtml" import JsonToHtml from "@/components/JsonToHtml"
import Button from "@/components/TempDesignSystem/Button"
import Link from "../../Link"
import SidePeek from "../../SidePeek" import SidePeek from "../../SidePeek"
import styles from "./sidepeek.module.css" import styles from "./sidepeek.module.css"
@@ -22,14 +22,13 @@ export default function TeaserCardSidepeek({
const { heading, content, primary_button, secondary_button } = sidePeekContent const { heading, content, primary_button, secondary_button } = sidePeekContent
return ( return (
<div> <div className={styles.teaserCardSidepeek}>
<Button <Button
onPress={() => setSidePeekIsOpen(true)} onPress={() => setSidePeekIsOpen(true)}
theme="base" variant="Secondary"
variant="icon" color="Primary"
intent="text" size="Small"
size="small" typography="Body/Supporting text (caption)/smBold"
wrapping
> >
{button.call_to_action_text} {button.call_to_action_text}
<MaterialIcon icon="chevron_right" size={20} color="CurrentColor" /> <MaterialIcon icon="chevron_right" size={20} color="CurrentColor" />
@@ -46,25 +45,28 @@ export default function TeaserCardSidepeek({
/> />
<div className={styles.ctaContainer}> <div className={styles.ctaContainer}>
{primary_button && ( {primary_button && (
<Button asChild theme="base" intent="primary" size="small"> <ButtonLink
<Link variant="Primary"
href={primary_button.href} color="Primary"
target={primary_button.openInNewTab ? "_blank" : undefined} size="Small"
color="none" typography="Body/Supporting text (caption)/smBold"
> href={primary_button.href}
{primary_button.title} target={primary_button.openInNewTab ? "_blank" : undefined}
</Link> >
</Button> {primary_button.title}
</ButtonLink>
)} )}
{secondary_button && ( {secondary_button && (
<Button asChild intent="secondary" size="small"> <ButtonLink
<Link variant="Secondary"
href={secondary_button.href} color="Primary"
target={secondary_button.openInNewTab ? "_blank" : undefined} size="Small"
> typography="Body/Supporting text (caption)/smBold"
{secondary_button.title} href={secondary_button.href}
</Link> target={secondary_button.openInNewTab ? "_blank" : undefined}
</Button> >
{secondary_button.title}
</ButtonLink>
)} )}
</div> </div>
</SidePeek> </SidePeek>

View File

@@ -1,3 +1,7 @@
.teaserCardSidepeek {
display: grid;
}
.ctaContainer { .ctaContainer {
display: grid; display: grid;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);

View File

@@ -1,6 +1,5 @@
import ButtonLink from "@/components/ButtonLink"
import Image from "@/components/Image" import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Subtitle from "../Text/Subtitle" import Subtitle from "../Text/Subtitle"
@@ -51,37 +50,30 @@ export default function TeaserCard({
) : ( ) : (
<div className={styles.ctaContainer}> <div className={styles.ctaContainer}>
{primaryButton && ( {primaryButton && (
<Button <ButtonLink
asChild variant="Tertiary"
intent="primary" color="Primary"
size="small" size="Small"
typography="Body/Supporting text (caption)/smBold"
className={styles.ctaButton} className={styles.ctaButton}
href={primaryButton.href}
target={primaryButton.openInNewTab ? "_blank" : undefined}
> >
<Link {primaryButton.title}
href={primaryButton.href} </ButtonLink>
target={primaryButton.openInNewTab ? "_blank" : undefined}
color="none"
>
{primaryButton.title}
</Link>
</Button>
)} )}
{secondaryButton && ( {secondaryButton && (
<Button <ButtonLink
asChild variant="Secondary"
intent="secondary" color="Primary"
size="small" size="Small"
typography="Body/Supporting text (caption)/smBold"
className={styles.ctaButton} className={styles.ctaButton}
href={secondaryButton.href}
target={secondaryButton.openInNewTab ? "_blank" : undefined}
> >
<Link {secondaryButton.title}
href={secondaryButton.href} </ButtonLink>
target={secondaryButton.openInNewTab ? "_blank" : undefined}
color="burgundy"
weight="bold"
>
{secondaryButton.title}
</Link>
</Button>
)} )}
</div> </div>
)} )}

View File

@@ -48,10 +48,6 @@
width: 100%; width: 100%;
} }
.ctaButton {
width: 100%;
}
@media (min-width: 1367px) { @media (min-width: 1367px) {
.card:not(.alwaysStack) .ctaContainer { .card:not(.alwaysStack) .ctaContainer {
grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); grid-template-columns: repeat(auto-fit, minmax(0, 1fr));

View File

@@ -3,6 +3,9 @@
border-width: 2px; border-width: 2px;
border-style: solid; border-style: solid;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
} }
.size-large { .size-large {
@@ -85,10 +88,6 @@
color: inherit; color: inherit;
padding: 0; padding: 0;
margin: 0; margin: 0;
display: flex;
align-items: center;
justify-content: center;
} }
.color-icon-default { .color-icon-default {

View File

@@ -1 +1,3 @@
export { Button } from './Button' export { Button } from './Button'
// eslint-disable-next-line react-refresh/only-export-components
export { withButton } from './variants'

View File

@@ -1,7 +1,11 @@
import { cva } from 'class-variance-authority' import { cva } from 'class-variance-authority'
import { withTypography } from '../Typography/variants' import {
config as typographyConfig,
withTypography,
} from '../Typography/variants'
import { deepmerge } from 'deepmerge-ts'
import styles from './button.module.css' import styles from './button.module.css'
export const config = { export const config = {
@@ -32,3 +36,18 @@ export const config = {
} as const } as const
export const variants = cva(styles.button, withTypography(config)) export const variants = cva(styles.button, withTypography(config))
const buttonConfig = {
variants: {
...config.variants,
typography: typographyConfig.variants.variant,
},
defaultVariants: {
...config.defaultVariants,
typography: typographyConfig.defaultVariants.variant,
},
} as const
export function withButton<T>(config: T) {
return deepmerge(buttonConfig, config)
}