Merged in feat/responsiveness (pull request #26)

[WEB-87] Feat/responsiveness
This commit is contained in:
Christel Westerberg
2024-02-14 09:41:23 +00:00
29 changed files with 361 additions and 112 deletions

View File

@@ -1,21 +1,21 @@
import { previewRequest } from "@/lib/previewRequest" import { previewRequest } from "@/lib/previewRequest"
import { GetCurrentBlockPage } from "@/lib/graphql/Query/CurrentBlockPage.graphql" import { GetCurrentBlockPage } from "@/lib/graphql/Query/CurrentBlockPage.graphql"
import type { PageArgs, LangParams, UriParams } from "@/types/params" import type { PageArgs, LangParams, PreviewParams } from "@/types/params"
import type { GetCurrentBlockPageData } from "@/types/requests/currentBlockPage" import type { GetCurrentBlockPageData } from "@/types/requests/currentBlockPage"
import ContentstackLivePreview from "@contentstack/live-preview-utils" import ContentstackLivePreview from "@contentstack/live-preview-utils"
import LoadingPreview from "@/components/Current/LoadingPreview" import LoadingSpinner from "@/components/Current/LoadingSpinner"
import ContentPage from "@/components/Current/ContentPage" import ContentPage from "@/components/Current/ContentPage"
export default async function CurrentContentPage({ export default async function CurrentContentPage({
params, params,
searchParams, searchParams,
}: PageArgs<LangParams, UriParams>) { }: PageArgs<LangParams, PreviewParams>) {
try { try {
ContentstackLivePreview.setConfigFromParams(searchParams) ContentstackLivePreview.setConfigFromParams(searchParams)
if (!searchParams.uri) { if (!searchParams.uri || !searchParams.live_preview) {
return <LoadingPreview /> return <LoadingSpinner />
} }
const response = await previewRequest<GetCurrentBlockPageData>( const response = await previewRequest<GetCurrentBlockPageData>(

View File

@@ -0,0 +1,82 @@
import styles from "./contact.module.css"
import { langEnum } from "@/types/lang"
import type { Lang } from "@/types/lang"
import type { ContactNode } from "@/types/requests/asides/contact"
export default function Contact({
title,
mailing_address,
visiting_address,
phone,
system: { locale },
}: ContactNode) {
const visitingAddressMessage = getVisitingAddressMessage(locale)
return (
<div>
<div>
<div>
<div>
<h2 className={styles.heading}>{title}</h2>
<p>
{mailing_address.name}
<br />
{mailing_address.street}
<br />
{mailing_address.zip} {mailing_address.city}
<br />
{mailing_address.country}
</p>
<p>
{visitingAddressMessage}: {visiting_address.street}{" "}
</p>
</div>
</div>
<div>
<div className={styles.highlightBlock}>
<h3>{phone.title}</h3>
<div className={styles.phoneContainer}>
<svg
focusable="false"
className={styles.phoneIcon}
viewBox="0 0 32 32"
>
<use
xmlnsXlink="http://www.w3.org/1999/xlink"
xlinkHref="/Static/img/icons/sprites.svg#icon-phone"
></use>
</svg>
<a
href={phone.number}
itemProp="telephone"
className={styles.phoneNumberLink}
>
{phone.number}
</a>
</div>
</div>
</div>
</div>
</div>
)
}
function getVisitingAddressMessage(lang: Lang) {
switch (lang) {
case langEnum.sv:
return "Besöksadress"
case langEnum.en:
return "Visiting address"
case langEnum.da:
return "Besøgsadresse"
case langEnum.de:
return "Besuchsadresse"
case langEnum.fi:
return "Vierailuosoite"
case langEnum.no:
return "Besøksadresse"
default:
return ""
}
}

View File

@@ -0,0 +1,62 @@
.highlightBlock {
padding: 10px 10px 15px;
background: #fff;
overflow: hidden;
}
.heading {
font-family: BrandonText-Bold, Arial, Helvetica, sans-serif;
font-size: 1.375rem;
line-height: 1.1em;
text-transform: uppercase;
font-weight: 400;
color: #483729;
margin-bottom: 1rem;
}
.phoneContainer {
display: flex;
gap: 10px;
align-items: center;
}
.phoneNumberLink {
line-height: 1.1em;
font-family: Helvetica, Arial, sans-serif;
font-weight: 400;
text-transform: none;
font-size: 1.125rem;
color: #00838e;
text-decoration: none;
cursor: pointer;
}
.phoneIcon {
width: 42px;
height: 42px;
fill: #00838e;
}
.p {
color: #333;
line-height: 1.3;
margin-bottom: 1em;
padding-top: 7px;
text-decoration: none;
}
.phoneNumberLink:active,
.phoneNumberLink:hover {
outline: 0;
text-decoration: underline;
}
@media screen and (min-width: 950px) {
.heading {
font-size: 1.625rem;
}
.phoneNumberLink {
line-height: 1.1em;
font-size: 1.375rem;
}
}

View File

@@ -0,0 +1,8 @@
import type { ContactsProps } from "@/types/components/current/asides/contact"
import Contact from "./Contact"
export default function Contacts({ contacts }: ContactsProps) {
return contacts.map((contact) => (
<Contact key={contact.node.system.uid} {...contact.node} />
))
}

View File

@@ -8,7 +8,14 @@ import styles from "./puff.module.css"
import type { PuffProps } from "@/types/components/current/asides/puff" import type { PuffProps } from "@/types/components/current/asides/puff"
import Image from "@/components/Image" import Image from "@/components/Image"
export default function Puff({ imageConnection, is_internal, link, pageConnection, text, title }: PuffProps) { export default function Puff({
imageConnection,
is_internal,
link,
pageConnection,
text,
title,
}: PuffProps) {
if (is_internal) { if (is_internal) {
const page = pageConnection.edges[0] const page = pageConnection.edges[0]
if (!page?.node?.url) { if (!page?.node?.url) {
@@ -35,11 +42,14 @@ export default function Puff({ imageConnection, is_internal, link, pageConnectio
) )
} }
function PuffContent({
function PuffContent({ imageConnection, text, title }: Pick<PuffProps, "imageConnection" | "text" | "title">) { imageConnection,
text,
title,
}: Pick<PuffProps, "imageConnection" | "text" | "title">) {
return ( return (
<article> <article>
{imageConnection.edges.map(image => ( {imageConnection.edges.map((image) => (
<Image <Image
alt={image.node.title} alt={image.node.title}
className={styles.image} className={styles.image}
@@ -50,10 +60,8 @@ function PuffContent({ imageConnection, text, title }: Pick<PuffProps, "imageCon
/> />
))} ))}
<section className={styles.content}> <section className={styles.content}>
<header className="content-teaser__body-wrapper"> <header>
<h3 className={styles.heading}> <h3 className={styles.heading}>{title}</h3>
{title}
</h3>
</header> </header>
<JsonToHtml <JsonToHtml
embeds={text.embedded_itemsConnection.edges} embeds={text.embedded_itemsConnection.edges}
@@ -63,4 +71,4 @@ function PuffContent({ imageConnection, text, title }: Pick<PuffProps, "imageCon
</section> </section>
</article> </article>
) )
} }

View File

@@ -12,10 +12,12 @@
height: auto; height: auto;
object-fit: contain; object-fit: contain;
object-position: center; object-position: center;
width: 100%;
} }
.content { .content {
padding: 20px 0px; padding: 10px;
background-color: #fff;
} }
.heading { .heading {
@@ -40,6 +42,12 @@
text-decoration: none; text-decoration: none;
} }
@media screen and (min-width: 740px) {
.content {
padding: 20px 0px;
}
}
@media screen and (min-width: 950px) { @media screen and (min-width: 950px) {
.heading { .heading {
font-size: 1.375rem; font-size: 1.375rem;

View File

@@ -0,0 +1,3 @@
.wrapper {
padding: 20px 10px 5px;
}

View File

@@ -1,22 +1,35 @@
import Puffs from "./Asides/Puffs" import Puffs from "./Puffs"
import Contacts from "./Contacts"
import { AsideTypenameEnum } from "@/types/requests/utils/typename" import { AsideTypenameEnum } from "@/types/requests/utils/typename"
import type { AsideProps } from "@/types/components/current/aside" import type { AsideProps } from "@/types/components/current/aside"
import styles from "./aside.module.css"
export default function Aside({ blocks }: AsideProps) { export default function Aside({ blocks }: AsideProps) {
if (!blocks?.length) { if (!blocks?.length) {
return null return null
} }
return ( return (
<aside> <aside className={styles.wrapper}>
{blocks.map((block, idx) => { {blocks.map((block, idx) => {
const type = block.__typename const type = block.__typename
switch (type) { switch (type) {
case AsideTypenameEnum.CurrentBlocksPageAsideContact: case AsideTypenameEnum.CurrentBlocksPageAsideContact:
return null return (
<Contacts
contacts={block.contact.contactConnection.edges}
key={`block-${idx}`}
/>
)
case AsideTypenameEnum.CurrentBlocksPageAsidePuff: case AsideTypenameEnum.CurrentBlocksPageAsidePuff:
return <Puffs key={`block-${idx}`} puffs={block.puff.puffConnection.edges} /> return (
<Puffs
key={`block-${idx}`}
puffs={block.puff.puffConnection.edges}
/>
)
default: default:
return null return null
} }

View File

@@ -1,7 +1,9 @@
import Link from "next/link" import Link from "next/link"
import { cva } from "class-variance-authority" import { cva } from "class-variance-authority"
import { BlockListItemsEnum } from "@/types/requests/blocks/list" import { BlockListItemsEnum } from "@/types/requests/blocks/list"
import type { ListItem } from "@/types/requests/blocks/list" import type { ListItem } from "@/types/requests/blocks/list"
import styles from "./list.module.css" import styles from "./list.module.css"
const config = { const config = {

View File

@@ -1,11 +1,11 @@
.title { .title {
font-family: Helvetica, Arial, sans-serif; font-family: BrandonText-Bold,Arial,Helvetica,sans-serif;
font-size: 1.375rem;
line-height: 1.1em;
text-transform: uppercase;
font-weight: 400; font-weight: 400;
line-height: normal; color: #483729;
margin-top: 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
text-decoration: none;
text-transform: none;
} }
.ul { .ul {
@@ -60,6 +60,6 @@
@media screen and (min-width: 950px) { @media screen and (min-width: 950px) {
.title { .title {
font-size: 1.375rem; font-size: 1.625rem;
} }
} }

View File

@@ -0,0 +1,11 @@
.wrapper {
background-color: #fff;
padding: 20px 10px 5px;
}
@media screen and (min-width: 740px) {
.wrapper {
padding:20px 0 0;
}
}

View File

@@ -1,8 +1,10 @@
import List from "./Blocks/List" import List from "./List"
import Puffs from "./Blocks/Puffs" import Puffs from "./Puffs"
import Text from "./Blocks/Text" import Text from "./Text"
import { BlocksTypenameEnum } from "@/types/requests/utils/typename" import { BlocksTypenameEnum } from "@/types/requests/utils/typename"
import styles from "./blocks.module.css"
import type { BlocksProps } from "@/types/components/current/blocks" import type { BlocksProps } from "@/types/components/current/blocks"
export default function Blocks({ blocks }: BlocksProps) { export default function Blocks({ blocks }: BlocksProps) {
@@ -11,8 +13,8 @@ export default function Blocks({ blocks }: BlocksProps) {
} }
return ( return (
<section id="mainbody_personalized"> <section className={styles.wrapper}>
{blocks.map(block => { {blocks.map((block) => {
const type = block.__typename const type = block.__typename
switch (type) { switch (type) {
case BlocksTypenameEnum.CurrentBlocksPageBlocksList: case BlocksTypenameEnum.CurrentBlocksPageBlocksList:

View File

@@ -0,0 +1,8 @@
.wrapper {
width: 100%;
position: relative;
z-index: 10;
padding-bottom: 50px;
background: #f3f2f1;
display: block;
}

View File

@@ -6,8 +6,9 @@ import Preamble from "@/components/Current/Preamble"
import Section from "@/components/Current/Section" import Section from "@/components/Current/Section"
import SubnavMobile from "@/components/Current/SubnavMobile" import SubnavMobile from "@/components/Current/SubnavMobile"
import type { ContentPageProps } from "@/types/components/current/contentPage" import styles from "./contentPage.module.css"
import type { ContentPageProps } from "@/types/components/current/contentPage"
export default function ContentPage({ data, lang, uri }: ContentPageProps) { export default function ContentPage({ data, lang, uri }: ContentPageProps) {
const page = data.all_current_blocks_page.items[0] const page = data.all_current_blocks_page.items[0]
@@ -19,7 +20,7 @@ export default function ContentPage({ data, lang, uri }: ContentPageProps) {
<> <>
<Header lang={lang} pathname={uri} /> <Header lang={lang} pathname={uri} />
{images?.totalCount ? <Hero images={images.edges} /> : null} {images?.totalCount ? <Hero images={images.edges} /> : null}
<main className="main l-sections-wrapper" id="maincontent" role="main"> <main className={styles.wrapper} id="maincontent" role="main">
<input <input
id="lbl-personalized-areas" id="lbl-personalized-areas"
name="lbl-personalized-areas" name="lbl-personalized-areas"

View File

@@ -14,7 +14,6 @@ export default async function Footer({ lang }: LangParams) {
}) })
const footerData = response.data.all_footer.items[0] const footerData = response.data.all_footer.items[0]
return ( return (
<footer className="global-footer"> <footer className="global-footer">
<div className="global-footer__content"> <div className="global-footer__content">

View File

@@ -1,4 +1,26 @@
.wrapper {
z-index: 0;
}
.picture {
visibility: visible;
opacity: 1;
transition: opacity 400ms ease-in-out 0s;
transform: translate(-50%, -50%) !important;
}
.heroImage { .heroImage {
object-fit: cover;
width: 100%; width: 100%;
} height: auto;
max-height: 600px;
min-height: 100px;
object-fit: cover;
object-position: center;
aspect-ratio: 1/1;
}
@media screen and (min-width: 740px) {
.heroImage {
aspect-ratio: auto;
}
}

View File

@@ -1,5 +1,3 @@
"use client"
import Image from "@/components/Image" import Image from "@/components/Image"
import styles from "./hero.module.css" import styles from "./hero.module.css"
@@ -8,47 +6,18 @@ import type { HeroProps } from "@/types/components/current/hero"
export default function Hero({ images }: HeroProps) { export default function Hero({ images }: HeroProps) {
return ( return (
<div className="hero-content-overlay hero-content-widget"> <div className={styles.wrapper} aria-label="Hero" tabIndex={0}>
<div className="hero-content-overlay__img-container"> {images.map(({ node: image }) => (
<div className="hero-fixed hero-fixed--deemphasized" style={{ marginTop: "0px" }}> <picture className={styles.picture} key={image.title}>
<div className="hero" style={{ top: "113px" }}> <Image
<div className="hero__img-container"> alt={image.title}
<div id="full-width-slider" className="royalSlider royalSlider--hero rsDefault rsHor rsFade rsWithBullets" data-js="hero-slider" tabIndex={0} aria-label="Carousel. Change slides with keyboard left and right arrows." style={{ touchAction: "pan-y" }}> className={styles.heroImage}
<div className="rsOverflow" style={{ width: "1555px", height: "650px" }}> height={image.dimension.height}
<div className="rsContainer"> src={image.url}
<div style={{ zIndex: 0 }} className="rsSlide "> width={image.dimension.width}
<picture style={{ visibility: "visible", opacity: 1, transition: "opacity 400ms ease-in-out 0s" }}> />
{images.map(({ node: image }) => ( </picture>
<Image ))}
alt={image.title}
className={`rsImg-x slider-plchldr ${styles.heroImage}`}
data-aspectratio="1.50"
height={image.dimension.height}
key={image.title}
src={image.url}
width={image.dimension.width}
/>
))}
</picture>
</div>
</div>
<div className="rsArrow rsArrowLeft" style={{ display: "none" }}>
<div className="rsArrowIcn" tabIndex={0} />
</div>
<div className="rsArrow rsArrowRight" style={{ display: "none" }}>
<div className="rsArrowIcn" tabIndex={0} />
</div>
</div>
<div className="rsNav rsBullets">
<div className="rsNavItem rsBullet rsNavSelected">
<span></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
) )
} }

View File

@@ -1,6 +1,6 @@
import styles from "./loading.module.css" import styles from "./loading.module.css"
export default function LoadingPreview() { export default function LoadingSpinner() {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.spinner}> <div className={styles.spinner}>

View File

@@ -1,7 +1,12 @@
.nav { .nav {
display: none;
padding-bottom: 8px; padding-bottom: 8px;
} }
.parent {
display: none;
}
.list { .list {
align-items: center; align-items: center;
display: grid; display: grid;
@@ -29,4 +34,11 @@
.currentPage::before { .currentPage::before {
content: ""; content: "";
margin-right: 4px; margin-right: 4px;
}
@media (min-width: 740px){
.nav {
display: block;
}
} }

View File

@@ -9,14 +9,18 @@ export default function Breadcrumbs({ breadcrumbs, parent, title }: BreadcrumbsP
<nav className={styles.nav}> <nav className={styles.nav}>
<ul className={styles.list}> <ul className={styles.list}>
{parent ? ( {parent ? (
<li className="breadcrumb-list__parent hidden-medium hidden-large"> <li className={styles.parent}>
<Link href={parent.node.url}> <Link href={parent.node.url}>
{parent.node.breadcrumbs?.title ?? parent.node.title} {parent.node.breadcrumbs?.title ?? parent.node.title}
</Link> </Link>
</li> </li>
) : null} ) : null}
{breadcrumbs.edges.map(breadcrumb => ( {breadcrumbs.edges.map((breadcrumb) => (
<li className={styles.li} itemProp="breadcrumb" key={breadcrumb.node.title}> <li
className={styles.li}
itemProp="breadcrumb"
key={breadcrumb.node.title}
>
<Link className={styles.link} href={breadcrumb.node.url}> <Link className={styles.link} href={breadcrumb.node.url}>
{breadcrumb.node.breadcrumbs?.title ?? breadcrumb.node.title} {breadcrumb.node.breadcrumbs?.title ?? breadcrumb.node.title}
</Link> </Link>

View File

@@ -1,10 +1,10 @@
.container { .container {
display: grid; display: grid;
gap: 60px; gap: 60px;
grid-template-columns: 2fr 1fr;
margin: 0 auto; margin: 0 auto;
max-width: 1200px; max-width: 1200px;
padding: 30px 0px 45px; padding: 20px 10px 25px;
background: #fff;
} }
.preamble { .preamble {
@@ -13,8 +13,16 @@
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 300; font-weight: 300;
line-height: normal; line-height: normal;
margin-bottom: 0px;
text-transform: none; text-transform: none;
margin-bottom: 0;
}
@media (min-width: 740px){
.container {
background: transparent;
padding: 20px 30px 35px;
grid-template-columns: 2fr 1fr;
}
} }
@media screen and (min-width: 950px) { @media screen and (min-width: 950px) {
@@ -22,4 +30,5 @@
font-size: 1.5rem; font-size: 1.5rem;
line-height: 2.25rem; line-height: 2.25rem;
} }
} }

View File

@@ -1,20 +1,20 @@
.wrapper {
background-color: #fff;
}
.section { .section {
box-sizing: content-box; box-sizing: content-box;
display: grid;
gap: 70px;
grid-template-columns: 2fr 1fr;
margin: 0 auto; margin: 0 auto;
max-width: 1200px;
padding: 20px 10px 5px;
} }
@media screen and (min-width: 740px) { @media screen and (min-width: 740px) {
.section { .section {
padding: 30px 30px 15px; padding: 30px 30px 15px;
grid-template-columns: 2fr 1fr;
display: grid;
gap: 70px;
max-width: 1200px;
}
.wrapper {
background-color: #fff;
} }
} }

View File

@@ -6,7 +6,14 @@ fragment Contact on ContactBlock {
street street
zip zip
} }
phone system {
uid
locale
}
phone {
number
title
}
title title
visiting_address { visiting_address {
city city

View File

@@ -0,0 +1,4 @@
import type { ContactNode } from "@/types/requests/asides/contact"
import type { Node } from "@/types/requests/utils/edges"
export type ContactsProps = { contacts: Node<ContactNode>[] }

View File

@@ -16,6 +16,11 @@ export type UriParams = {
uri?: string; uri?: string;
}; };
export type PreviewParams = {
uri?: string
live_preview?: string
}
export type LayoutArgs<P = undefined> = P extends undefined ? {} : Params<P>; export type LayoutArgs<P = undefined> = P extends undefined ? {} : Params<P>;
export type PageArgs<P = undefined, S = undefined> = LayoutArgs<P> & export type PageArgs<P = undefined, S = undefined> = LayoutArgs<P> &

View File

@@ -1,23 +1,33 @@
import { Lang } from "@/types/lang"
import type { Edges } from "../utils/edges" import type { Edges } from "../utils/edges"
export type ContactNode = {
mailing_address: {
city: string
country: string
name: string
street: string
zip: string
}
system: {
uid: string
locale: Lang
}
phone: {
number: string
title: string
}
title: string
visiting_address: {
city: string
country: string
street: string
zip: string
}
}
export type Contact = { export type Contact = {
contact: { contact: {
contactConnection: Edges<{ contactConnection: Edges<ContactNode>
mailing_address: {
city: string
country: string
name: string
street: string
zip: string
}
phone: string
title: string
visiting_address: {
city: string
country: string
street: string
zip: string
}
}>
} }
} }