Merged in feat/SW-1383-content-card-start-page (pull request #1252)
feat(SW-1383): Implement ContentCard for the Start Page * feat(SW-1383): Implement ContentCard - Add ContentCard component - Use within CarouselCards component * fix(SW-1383): adjust carousel and content card styling * refactor(SW-1383): optimize ContentCard component styling and props * feat(SW-1383): move ContentCard image check out of component * feat(SW-1383): Add optional link prop to ContentCard component * refactor(SW-1383): Make ContentCard component linkable Approved-by: Christian Andolf Approved-by: Erik Tiekstra
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import ContentCard from "@/components/ContentCard"
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
|
||||
import styles from "./carouselCards.module.css"
|
||||
|
||||
import type { CarouselCardsProps } from "@/types/components/blocks/carouselCards"
|
||||
|
||||
export default function CarouselCards({ carousel_cards }: CarouselCardsProps) {
|
||||
@@ -19,17 +22,27 @@ export default function CarouselCards({ carousel_cards }: CarouselCardsProps) {
|
||||
{enableFilters && (
|
||||
<details>
|
||||
<summary>Filter data</summary>
|
||||
<p>Todo: Add filter component here</p>
|
||||
<pre>
|
||||
{JSON.stringify({ filterCategories, defaultFilter }, null, 2)}
|
||||
</pre>
|
||||
<div>
|
||||
{/* Filter component will go here */}
|
||||
<pre className={styles.code}>
|
||||
{JSON.stringify({ filterCategories, defaultFilter }, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
<details>
|
||||
<summary>Carousel cards</summary>
|
||||
<p>Todo: Add carousel cards component here</p>
|
||||
<pre>{JSON.stringify({ cards }, null, 2)}</pre>
|
||||
</details>
|
||||
{/* Carousel functionality will go here */}
|
||||
<div className={styles.cardsContainer}>
|
||||
{cards.map((card) => (
|
||||
<ContentCard
|
||||
link={card.link}
|
||||
key={card.heading}
|
||||
heading={card.heading}
|
||||
bodyText={card.bodyText}
|
||||
image={card.image}
|
||||
promoText={card.promoText}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
36
components/Blocks/carouselCards.module.css
Normal file
36
components/Blocks/carouselCards.module.css
Normal file
@@ -0,0 +1,36 @@
|
||||
.code {
|
||||
padding: var(--Spacing-x2);
|
||||
background: var(--Base-Surface-Secondary-light-Normal);
|
||||
}
|
||||
|
||||
/*
|
||||
Mock styles for the carousel cards. Will be removed/replaced
|
||||
when the carousel functionality is implemented (SW-1542).
|
||||
*/
|
||||
.cardsContainer {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3);
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 100%;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
|
||||
.cardsContainer > * {
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
/* Show 2 cards on tablet */
|
||||
@media (min-width: 768px) {
|
||||
.cardsContainer {
|
||||
grid-auto-columns: calc((100% - var(--Spacing-x3)) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Show 3 cards on desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.cardsContainer {
|
||||
grid-auto-columns: calc((100% - var(--Spacing-x3) * 2) / 3);
|
||||
}
|
||||
}
|
||||
60
components/ContentCard/contentCard.module.css
Normal file
60
components/ContentCard/contentCard.module.css
Normal file
@@ -0,0 +1,60 @@
|
||||
.card {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
position: relative;
|
||||
aspect-ratio: 16/9;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
overflow: hidden;
|
||||
transition: border-radius 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.image {
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
transition:
|
||||
transform 0.3s ease-in-out,
|
||||
border-radius 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:hover .imageContainer,
|
||||
.card:hover .image {
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
}
|
||||
|
||||
.card:hover .image {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.promoTag {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 14px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
padding: var(--Spacing-x-one-and-half);
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.card {
|
||||
max-width: 413px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x2) 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.card {
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
21
components/ContentCard/contentCard.ts
Normal file
21
components/ContentCard/contentCard.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
||||
|
||||
export interface ContentCardProps {
|
||||
link?: {
|
||||
href: string
|
||||
openInNewTab?: boolean
|
||||
isExternal?: boolean
|
||||
}
|
||||
heading: string
|
||||
image: ImageVaultAsset
|
||||
bodyText: string
|
||||
promoText?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export interface ContentCardLinkProps {
|
||||
href: string
|
||||
openInNewTab?: boolean
|
||||
isExternal?: boolean
|
||||
children: React.ReactNode
|
||||
}
|
||||
70
components/ContentCard/index.tsx
Normal file
70
components/ContentCard/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import Image from "@/components/Image"
|
||||
import Chip from "@/components/TempDesignSystem/Chip"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import styles from "./contentCard.module.css"
|
||||
|
||||
import type { ContentCardLinkProps,ContentCardProps } from "./contentCard"
|
||||
|
||||
export default function ContentCard({
|
||||
heading,
|
||||
image,
|
||||
bodyText,
|
||||
promoText,
|
||||
className = "",
|
||||
link,
|
||||
}: ContentCardProps) {
|
||||
const card = (
|
||||
<article className={`${styles.card} ${className}`}>
|
||||
<div className={styles.imageContainer}>
|
||||
<Image
|
||||
src={image.url}
|
||||
alt={image.meta.alt || image.meta.caption || ""}
|
||||
className={styles.image}
|
||||
fill
|
||||
sizes="(min-width: 768px) 413px, 100vw"
|
||||
focalPoint={image.focalPoint}
|
||||
/>
|
||||
{promoText ? (
|
||||
<Chip className={styles.promoTag}>{promoText}</Chip>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<Subtitle type="two">{heading}</Subtitle>
|
||||
<Body>{bodyText}</Body>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
|
||||
if (!link) return card
|
||||
|
||||
return (
|
||||
<ContentCardLink
|
||||
href={link.href}
|
||||
openInNewTab={link.openInNewTab}
|
||||
isExternal={link.isExternal}
|
||||
>
|
||||
{card}
|
||||
</ContentCardLink>
|
||||
)
|
||||
}
|
||||
|
||||
function ContentCardLink({
|
||||
children,
|
||||
href,
|
||||
openInNewTab,
|
||||
isExternal,
|
||||
}: ContentCardLinkProps) {
|
||||
const Component = isExternal ? "a" : Link
|
||||
const linkProps = {
|
||||
href,
|
||||
...(openInNewTab && {
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}),
|
||||
}
|
||||
|
||||
return <Component {...linkProps}>{children}</Component>
|
||||
}
|
||||
@@ -42,6 +42,7 @@
|
||||
display: grid;
|
||||
gap: var(--Spacing-x6);
|
||||
padding: calc(var(--Spacing-x5) * 2) 0 calc(var(--Spacing-x5) * 4);
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
|
||||
Reference in New Issue
Block a user