From 52b461fbb4007b1434bc403fdbf627d27d9676e4 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Thu, 27 Mar 2025 17:18:51 +0100 Subject: [PATCH 1/4] fix(SW-1877): new responsive breadcrumbs --- .../components/Breadcrumbs/index.tsx | 1 + .../MobileMapToggle/mobileToggle.module.css | 4 +- .../Breadcrumbs/breadcrumbs.module.css | 83 ++++++- .../Breadcrumbs/breadcrumbs.ts | 2 +- .../TempDesignSystem/Breadcrumbs/index.tsx | 226 ++++++++++++++---- .../Popover/Arrow/arrow.module.css | 29 ++- .../TempDesignSystem/Popover/Arrow/arrow.ts | 7 + .../TempDesignSystem/Popover/Arrow/index.tsx | 22 +- .../Popover/Arrow/variants.ts | 20 ++ .../contentstack/breadcrumbs/output.ts | 38 +-- 10 files changed, 333 insertions(+), 99 deletions(-) create mode 100644 apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.ts create mode 100644 apps/scandic-web/components/TempDesignSystem/Popover/Arrow/variants.ts diff --git a/apps/scandic-web/components/Breadcrumbs/index.tsx b/apps/scandic-web/components/Breadcrumbs/index.tsx index 228c892c0..e9a616561 100644 --- a/apps/scandic-web/components/Breadcrumbs/index.tsx +++ b/apps/scandic-web/components/Breadcrumbs/index.tsx @@ -3,6 +3,7 @@ import { serverClient } from "@/lib/trpc/server" import BreadcrumbsComp from "@/components/TempDesignSystem/Breadcrumbs" import { generateBreadcrumbsSchema } from "@/utils/jsonSchemas" +import type { Breadcrumbs } from "@/types/trpc/routers/contentstack/breadcrumbs" import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" export default async function Breadcrumbs({ diff --git a/apps/scandic-web/components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css b/apps/scandic-web/components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css index 2765d6958..afbc001d8 100644 --- a/apps/scandic-web/components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css +++ b/apps/scandic-web/components/ContentType/HotelPage/Map/MobileMapToggle/mobileToggle.module.css @@ -1,5 +1,7 @@ .mobileToggle { - position: sticky; + position: fixed; + left: 50%; + transform: translate(-50%, 0); bottom: var(--Spacing-x5); z-index: var(--hotel-mobile-map-toggle-button-z-index); margin: 0 auto; diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css index bc607a43c..08a7149fe 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css @@ -1,5 +1,4 @@ .breadcrumbs { - display: block; padding: var(--Spacing-x4) 0 var(--Spacing-x3); margin: 0 auto; width: 100%; @@ -23,21 +22,91 @@ } .list { - align-items: center; display: flex; gap: var(--Spacing-x-quarter); - justify-content: flex-start; - list-style: none; - margin: 0 auto; - max-width: var(--max-width-page); + padding-inline-start: 0; } .listItem { align-items: center; display: flex; gap: var(--Spacing-x-quarter); + flex-shrink: 0; + flex-grow: 0; } -.homeLink { +.listItem > a { display: flex; } + +.listItem > svg { + flex-shrink: 0; +} + +.last { + flex: 1; + max-width: 100%; + min-width: 0; +} + +.last > button { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.button { + border: none; + background: transparent; + height: 100%; + /* a bit of a hack of centering the popover and its arrow */ + padding: 0 10px; + margin: 0 -10px; +} + +.dialog { + background-color: var(--Base-Surface-Primary-light-Normal); + padding: var(--Spacing-x1); + display: flex; + flex-direction: column; + gap: var(--Spacing-x-half); + border-radius: var(--Corner-radius-Medium); + min-width: 169px; + outline: none; + box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1); +} + +.dialogLink { + display: block; + border-radius: var(--Corner-radius-Medium); + padding: var(--Spacing-x1); +} + +.dialogLink:focus, +.dialogLink:hover { + background-color: var(--Base-Surface-Primary-light-Hover); +} + +@media screen and (max-width: 767px) { + .desktop { + display: none; + } +} + +@media screen and (min-width: 768px) { + .mobile { + display: none; + } + .desktop { + display: flex; + } +} + +.tooltip { + background-color: var(--Surface-UI-Fill-Intense); + padding: var(--Spacing-x-half) var(--Spacing-x1); + border-radius: var(--Corner-radius-sm); + color: var(--Text-Inverted); + outline: none; +} diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts index 99ec65a6f..25cf14250 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts @@ -2,7 +2,7 @@ import type { VariantProps } from "class-variance-authority" import type { breadcrumbsVariants } from "./variants" -type Breadcrumb = { +export type Breadcrumb = { title: string uid: string href?: string diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx index adfbc170b..e73049e59 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx @@ -1,18 +1,61 @@ +"use client" + +import { cx } from "class-variance-authority" +import { type PropsWithChildren, useEffect, useState } from "react" +import { + Breadcrumb as AriaBreadcrumb, + Breadcrumbs as AriaBreadCrumbs, + Button, + Dialog, + DialogTrigger, + OverlayArrow, + Popover, +} from "react-aria-components" + import { MaterialIcon } from "@scandic-hotels/design-system/Icons" +import { Typography } from "@scandic-hotels/design-system/Typography" import Link from "@/components/TempDesignSystem/Link" import Footnote from "@/components/TempDesignSystem/Text/Footnote" +import { debounce } from "@/utils/debounce" +import { Arrow } from "../Popover/Arrow" import { breadcrumbsVariants } from "./variants" import styles from "./breadcrumbs.module.css" -import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" +import type { + Breadcrumb, + BreadcrumbsProps, +} from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" export default function Breadcrumbs({ breadcrumbs, variant, }: BreadcrumbsProps) { + // using a ref instead to detect when the element is available and forcing a render + const [element, attachRef] = useState(null) + const [isTooltipDisabled, setIsTooltipDisabled] = useState(false) + + useEffect(() => { + const observer = new ResizeObserver( + debounce(([entry]) => { + const el = entry.target + setIsTooltipDisabled(el.clientWidth >= el.scrollWidth) + }, 200) + ) + + if (element) { + observer.observe(element) + } + + return () => { + if (element) { + observer.unobserve(element) + } + } + }, [element]) + if (!breadcrumbs?.length) { return null } @@ -21,60 +64,137 @@ export default function Breadcrumbs({ variant, }) - const homeBreadcrumb = breadcrumbs.shift() + const [homeBreadcrumb, remainingBreadcrumbs, lastBreadcrumb] = + splitBreadcrumbs(breadcrumbs) + return ( - ) } + +function Breadcrumb({ + className = "", + href, + children, + ...props +}: PropsWithChildren<{ + className?: string + href?: string +}>) { + return ( + + {href ? ( + <> + + {children} + + + ) +} + +function splitBreadcrumbs( + breadcrumbs: Breadcrumb[] +): [Breadcrumb, Breadcrumb[], Breadcrumb] { + const copy = breadcrumbs.slice(0) + const first = copy.shift()! + const last = copy.pop()! + return [first, copy, last] +} diff --git a/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.module.css b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.module.css index 522b5aff8..2a0d21ac2 100644 --- a/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.module.css +++ b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.module.css @@ -1,22 +1,39 @@ -.arrow { - transform-origin: center; - transform: translateY(-2px); +.arrow > svg { + display: block; } [data-placement="left"] .arrow, [data-placement="left top"] .arrow, [data-placement="left bottom"] .arrow { - transform: rotate(270deg) translateY(-6px); + transform: rotate(270deg); } [data-placement="right"] .arrow, [data-placement="right top"] .arrow, [data-placement="right bottom"] .arrow { - transform: rotate(90deg) translateY(-6px); + transform: rotate(90deg); } [data-placement="bottom"] .arrow, [data-placement="bottom left"] .arrow, [data-placement="bottom right"] .arrow { - transform: rotate(180deg) translateY(-2px); + transform: rotate(180deg); +} + +.small { + width: 14px; + height: 8px; +} + +.medium { + width: 27px; + height: 13px; +} + +.black { + fill: var(--Surface-UI-Fill-Intense); +} + +.white { + fill: var(--Surface-UI-Fill-Default); } diff --git a/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.ts b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.ts new file mode 100644 index 000000000..67e279e12 --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/arrow.ts @@ -0,0 +1,7 @@ +import type { VariantProps } from "class-variance-authority" + +import type { arrowVariants } from "./variants" + +export interface ArrowProps + extends Omit, "color">, + VariantProps {} diff --git a/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/index.tsx b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/index.tsx index 4c67b059b..eff65398a 100644 --- a/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/index.tsx @@ -1,18 +1,14 @@ -import styles from "./arrow.module.css" +import { arrowVariants } from "./variants" + +import type { ArrowProps } from "./arrow" + +export function Arrow({ color, size }: ArrowProps) { + const className = arrowVariants({ color, size }) -export function Arrow() { return ( -
- - +
+ +
) diff --git a/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/variants.ts b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/variants.ts new file mode 100644 index 000000000..c0baaa0d4 --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Popover/Arrow/variants.ts @@ -0,0 +1,20 @@ +import { cva } from "class-variance-authority" + +import styles from "./arrow.module.css" + +export const arrowVariants = cva(styles.arrow, { + variants: { + color: { + black: styles.black, + white: styles.white, + }, + size: { + small: styles.small, + medium: styles.medium, + }, + }, + defaultVariants: { + color: "white", + size: "medium", + }, +}) diff --git a/apps/scandic-web/server/routers/contentstack/breadcrumbs/output.ts b/apps/scandic-web/server/routers/contentstack/breadcrumbs/output.ts index 586bf3ccd..992aae57e 100644 --- a/apps/scandic-web/server/routers/contentstack/breadcrumbs/output.ts +++ b/apps/scandic-web/server/routers/contentstack/breadcrumbs/output.ts @@ -52,24 +52,26 @@ export const rawBreadcrumbsDataSchema = z.object({ system: systemSchema, }) -export const breadcrumbsSchema = rawBreadcrumbsDataSchema.transform((data) => { - const { parentsConnection, title } = data.web.breadcrumbs - const parentBreadcrumbs = parentsConnection.edges.map((breadcrumb) => { - return { - href: removeMultipleSlashes( - `/${breadcrumb.node.system.locale}/${breadcrumb.node.url}` - ), - title: breadcrumb.node.web.breadcrumbs.title, - uid: breadcrumb.node.system.uid, +export const breadcrumbsSchema = rawBreadcrumbsDataSchema.transform( + (data): { title: string; href: string; uid: string }[] => { + const { parentsConnection, title } = data.web.breadcrumbs + const parentBreadcrumbs = parentsConnection.edges.map((breadcrumb) => { + return { + href: removeMultipleSlashes( + `/${breadcrumb.node.system.locale}/${breadcrumb.node.url}` + ), + title: breadcrumb.node.web.breadcrumbs.title, + uid: breadcrumb.node.system.uid, + } + }) + + const pageBreadcrumb = { + title, + uid: data.system.uid, + href: removeMultipleSlashes(`/${data.system.locale}/${data.url}`), } - }) + const homeBreadcrumb = homeBreadcrumbs[data.system.locale] - const pageBreadcrumb = { - title, - uid: data.system.uid, - href: removeMultipleSlashes(`/${data.system.locale}/${data.url}`), + return [homeBreadcrumb, parentBreadcrumbs, pageBreadcrumb].flat() } - const homeBreadcrumb = homeBreadcrumbs[data.system.locale] - - return [homeBreadcrumb, parentBreadcrumbs, pageBreadcrumb].flat() -}) +) From 19723856c3af3aa469dd9a5a1b2df7735e1a53a1 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Mon, 31 Mar 2025 11:47:53 +0200 Subject: [PATCH 2/4] refactor(SW-1877): a bit of cleanup of code to maintain patterns and separate components and types --- .../components/Breadcrumbs/index.tsx | 10 +-- .../Breadcrumbs/Breadcrumb.tsx | 37 ++++++++++ .../Breadcrumbs/breadcrumbs.module.css | 26 +++---- .../Breadcrumbs/breadcrumbs.ts | 15 ++-- .../TempDesignSystem/Breadcrumbs/index.tsx | 70 ++++--------------- .../TempDesignSystem/Breadcrumbs/utils.ts | 12 ++++ 6 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 apps/scandic-web/components/TempDesignSystem/Breadcrumbs/Breadcrumb.tsx create mode 100644 apps/scandic-web/components/TempDesignSystem/Breadcrumbs/utils.ts diff --git a/apps/scandic-web/components/Breadcrumbs/index.tsx b/apps/scandic-web/components/Breadcrumbs/index.tsx index e9a616561..b804b31b5 100644 --- a/apps/scandic-web/components/Breadcrumbs/index.tsx +++ b/apps/scandic-web/components/Breadcrumbs/index.tsx @@ -3,13 +3,13 @@ import { serverClient } from "@/lib/trpc/server" import BreadcrumbsComp from "@/components/TempDesignSystem/Breadcrumbs" import { generateBreadcrumbsSchema } from "@/utils/jsonSchemas" -import type { Breadcrumbs } from "@/types/trpc/routers/contentstack/breadcrumbs" import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" -export default async function Breadcrumbs({ - variant, - subpageTitle, -}: Pick) { +interface Props extends Pick { + subpageTitle?: string +} + +export default async function Breadcrumbs({ variant, subpageTitle }: Props) { const breadcrumbs = await serverClient().contentstack.breadcrumbs.get() if (!breadcrumbs?.length) { return null diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/Breadcrumb.tsx b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/Breadcrumb.tsx new file mode 100644 index 000000000..0c4cf8800 --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/Breadcrumb.tsx @@ -0,0 +1,37 @@ +import { cx } from "class-variance-authority" +import { Breadcrumb as AriaBreadcrumb } from "react-aria-components" + +import { MaterialIcon } from "@scandic-hotels/design-system/Icons" + +import Link from "@/components/TempDesignSystem/Link" + +import styles from "./breadcrumbs.module.css" + +import type { BreadcrumbProps } from "./breadcrumbs" + +export function Breadcrumb({ + className = "", + href, + children, + ...props +}: BreadcrumbProps) { + return ( + + {href ? ( + <> + + {children} + + + ) +} diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css index 08a7149fe..d90869754 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css @@ -27,6 +27,12 @@ padding-inline-start: 0; } +.list .listItem:last-of-type { + flex: 1; + max-width: 100%; + min-width: 0; +} + .listItem { align-items: center; display: flex; @@ -43,19 +49,6 @@ flex-shrink: 0; } -.last { - flex: 1; - max-width: 100%; - min-width: 0; -} - -.last > button { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: left; -} - .button { border: none; background: transparent; @@ -103,6 +96,13 @@ } } +.tooltipTrigger { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + .tooltip { background-color: var(--Surface-UI-Fill-Intense); padding: var(--Spacing-x-half) var(--Spacing-x1); diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts index 25cf14250..7f42e146d 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/breadcrumbs.ts @@ -1,15 +1,22 @@ import type { VariantProps } from "class-variance-authority" +import type { PropsWithChildren } from "react" +import type { BreadcrumbProps as AriaBreadcrumbProps } from "react-aria-components" import type { breadcrumbsVariants } from "./variants" -export type Breadcrumb = { +export type { Breadcrumb, BreadcrumbProps, BreadcrumbsProps } + +type Breadcrumb = { title: string uid: string href?: string } -export interface BreadcrumbsProps - extends VariantProps { - subpageTitle?: string +interface BreadcrumbsProps extends VariantProps { breadcrumbs: Breadcrumb[] } + +interface BreadcrumbProps extends PropsWithChildren { + className?: string + href?: string +} diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx index e73049e59..7c9311b7c 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/index.tsx @@ -1,10 +1,9 @@ "use client" import { cx } from "class-variance-authority" -import { type PropsWithChildren, useEffect, useState } from "react" +import { useEffect, useState } from "react" import { - Breadcrumb as AriaBreadcrumb, - Breadcrumbs as AriaBreadCrumbs, + Breadcrumbs as AriaBreadcrumbs, Button, Dialog, DialogTrigger, @@ -15,19 +14,18 @@ import { import { MaterialIcon } from "@scandic-hotels/design-system/Icons" import { Typography } from "@scandic-hotels/design-system/Typography" -import Link from "@/components/TempDesignSystem/Link" -import Footnote from "@/components/TempDesignSystem/Text/Footnote" import { debounce } from "@/utils/debounce" +import Link from "../Link" import { Arrow } from "../Popover/Arrow" +import Footnote from "../Text/Footnote" +import { Breadcrumb } from "./Breadcrumb" +import { splitBreadcrumbs } from "./utils" import { breadcrumbsVariants } from "./variants" import styles from "./breadcrumbs.module.css" -import type { - Breadcrumb, - BreadcrumbsProps, -} from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs" +import type { BreadcrumbsProps } from "./breadcrumbs" export default function Breadcrumbs({ breadcrumbs, @@ -69,7 +67,7 @@ export default function Breadcrumbs({ return ( ) } - -function Breadcrumb({ - className = "", - href, - children, - ...props -}: PropsWithChildren<{ - className?: string - href?: string -}>) { - return ( - - {href ? ( - <> - - {children} - - - ) -} - -function splitBreadcrumbs( - breadcrumbs: Breadcrumb[] -): [Breadcrumb, Breadcrumb[], Breadcrumb] { - const copy = breadcrumbs.slice(0) - const first = copy.shift()! - const last = copy.pop()! - return [first, copy, last] -} diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/utils.ts b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/utils.ts new file mode 100644 index 000000000..53c765f11 --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/utils.ts @@ -0,0 +1,12 @@ +import type { Breadcrumb } from "./breadcrumbs" + +export { splitBreadcrumbs } + +function splitBreadcrumbs( + breadcrumbs: Breadcrumb[] +): [Breadcrumb, Breadcrumb[], Breadcrumb] { + const copy = breadcrumbs.slice(0) + const first = copy.shift()! + const last = copy.pop()! + return [first, copy, last] +} From f5f12c2f1889c442cc98d6d9f837ce6a8f6f5611 Mon Sep 17 00:00:00 2001 From: Christian Andolf Date: Mon, 31 Mar 2025 12:03:04 +0200 Subject: [PATCH 3/4] fix(SW-1877): fixed size on home icon in skeleton fixed width and padding for other pages --- .../Breadcrumbs/BreadcrumbsSkeleton/index.tsx | 8 ++++--- .../Breadcrumbs/breadcrumbs.module.css | 21 ++++++++++--------- .../TempDesignSystem/Breadcrumbs/index.tsx | 6 +++++- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx index aa334ff45..aba532db7 100644 --- a/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton/index.tsx @@ -9,9 +9,11 @@ export default function BreadcrumbsSkeleton() {