diff --git a/src/elements/flip-card/flip-card-details/flip-card-details.scss b/src/elements/flip-card/flip-card-details/flip-card-details.scss
index 6b7b02b25f..2d8343758e 100644
--- a/src/elements/flip-card/flip-card-details/flip-card-details.scss
+++ b/src/elements/flip-card/flip-card-details/flip-card-details.scss
@@ -19,18 +19,16 @@
}
.sbb-flip-card-details--wrapper {
- display: grid;
- grid-template-rows: 0fr;
+ position: absolute;
pointer-events: none;
color: var(--sbb-color-milk);
opacity: var(--sbb-flip-card-details-opacity);
transform: translateY(var(--sbb-flip-card-details-translate-y));
- transition-property: grid-template-rows, transform, opacity;
+ transition-property: transform, opacity;
transition-duration: var(--sbb-flip-card-details-transition-duration);
:host([data-flipped]) & {
- grid-template-rows: 1fr;
- transition-delay: 0s, var(--sbb-flip-card-details-transition-delay),
+ transition-delay: var(--sbb-flip-card-details-transition-delay),
var(--sbb-flip-card-details-transition-delay);
}
}
diff --git a/src/elements/flip-card/flip-card-details/flip-card-details.visual.spec.ts b/src/elements/flip-card/flip-card-details/flip-card-details.visual.spec.ts
deleted file mode 100644
index c916225c13..0000000000
--- a/src/elements/flip-card/flip-card-details/flip-card-details.visual.spec.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { html } from 'lit';
-
-import { describeViewports, visualDiffDefault } from '../../core/testing/private.js';
-
-import './flip-card-details.js';
-import '../../link.js';
-
-describe(`sbb-flip-card-details`, () => {
- describeViewports({ viewports: ['medium'] }, () => {
- it(
- visualDiffDefault.name,
- visualDiffDefault.with(async (setup) => {
- await setup.withFixture(
- html`
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam luctus ornare
- condimentum. Vivamus turpis elit, dapibus eget fringilla pellentesque, lobortis in
- nibh. Duis dapibus vitae tortor ullamcorper maximus. In convallis consectetur felis.
- Link
-
- `,
- {
- backgroundColor: 'var(--sbb-color-midnight)',
- },
- );
- }),
- );
- });
-});
diff --git a/src/elements/flip-card/flip-card-summary/flip-card-summary.scss b/src/elements/flip-card/flip-card-summary/flip-card-summary.scss
index a5d9df6b27..64699826c4 100644
--- a/src/elements/flip-card/flip-card-summary/flip-card-summary.scss
+++ b/src/elements/flip-card/flip-card-summary/flip-card-summary.scss
@@ -12,13 +12,13 @@
.sbb-flip-card-summary {
display: grid;
- position: absolute;
+ min-height: var(--sbb-flip-card-min-height);
pointer-events: var(--sbb-flip-card-summary-pointer-events);
opacity: var(--sbb-flip-card-summary-opacity);
border-radius: var(--sbb-flip-card-border-radius);
overflow: hidden;
- grid-template-columns: 1fr;
- grid-template-rows: auto 1fr;
+ grid-template-columns: minmax(0, 1fr);
+ grid-template-rows: auto minmax(0, 1fr);
width: 100%;
height: 100%;
transition: all var(--sbb-flip-card-summary-transition-duration) ease-out;
@@ -26,12 +26,12 @@
:host([image-alignment='after']) & {
@include sbb.mq($from: small) {
- grid-template-columns: repeat(3, 1fr);
+ grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: 1fr;
}
@include sbb.mq($from: medium) {
- grid-template-columns: repeat(2, 1fr);
+ grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: 1fr;
}
}
@@ -61,7 +61,6 @@
}
.sbb-flip-card-summary--image-wrapper {
- max-height: var(--sbb-flip-card-min-height);
grid-area: 2 / 1 / 3 / 2;
:host([image-alignment='after']) & {
diff --git a/src/elements/flip-card/flip-card/flip-card.scss b/src/elements/flip-card/flip-card/flip-card.scss
index 7b92c5b2cf..745afa79fc 100644
--- a/src/elements/flip-card/flip-card/flip-card.scss
+++ b/src/elements/flip-card/flip-card/flip-card.scss
@@ -75,10 +75,16 @@
min-height: var(--sbb-flip-card-min-height);
background-color: var(--sbb-flip-card-background-color);
border-radius: var(--sbb-flip-card-border-radius);
- transition: var(--sbb-flip-card-summary-transition-duration) ease-out;
+ transition:
+ all var(--sbb-flip-card-summary-transition-duration) ease-out,
+ min-height var(--sbb-flip-card-details-transition-duration);
transition-delay: var(--sbb-flip-card-summary-transition-delay);
cursor: pointer;
+ :host([data-flipped]) & {
+ min-height: max(var(--sbb-flip-card-min-height), var(--sbb-flip-card-details-height));
+ }
+
@include sbb.if-forced-colors {
&::after {
content: '';
diff --git a/src/elements/flip-card/flip-card/flip-card.stories.ts b/src/elements/flip-card/flip-card/flip-card.stories.ts
index 3062040609..101635a45f 100644
--- a/src/elements/flip-card/flip-card/flip-card.stories.ts
+++ b/src/elements/flip-card/flip-card/flip-card.stories.ts
@@ -3,6 +3,7 @@ import type { InputType } from '@storybook/types';
import type { Args, ArgTypes, Meta, StoryObj, Decorator } from '@storybook/web-components';
import type { TemplateResult } from 'lit';
import { html, nothing } from 'lit';
+import { styleMap } from 'lit/directives/style-map.js';
import sampleImages from '../../core/images.js';
@@ -107,6 +108,39 @@ const LongContentTemplate = (args: Args): TemplateResult =>
`;
+const GridTemplate = (args: Args): TemplateResult =>
+ html`
+
+ ${cardSummary(args.label, args.imageAlignment, true)} ${cardDetails()}
+
+
+ ${cardSummary(args.label, args.imageAlignment, true)}
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam luctus ornare condimentum.
+ Vivamus turpis elit, dapibus eget fringilla pellentesque, lobortis in nibh. Duis dapibus
+ vitae tortor ullamcorper maximus. In convallis consectetur felis. Lorem ipsum dolor sit
+ amet, consectetur adipiscing elit. Nam luctus ornare condimentum. Vivamus turpis elit,
+ dapibus eget fringilla pellentesque, lobortis in nibh. Duis dapibus vitae tortor ullamcorper
+ maximus. In convallis consectetur felis.
+ Link
+
+
+
+ ${cardSummary(args.label, args.imageAlignment, true)} ${cardDetails()}
+
+
+ ${cardSummary(args.label, args.imageAlignment, true)} ${cardDetails()}
+
+
`;
+
export const ImageAfter: StoryObj = {
render: DefaultTemplate,
argTypes: defaultArgTypes,
@@ -140,6 +174,12 @@ export const LongTitle: StoryObj = {
},
};
+export const Grid: StoryObj = {
+ render: GridTemplate,
+ argTypes: defaultArgTypes,
+ args: { ...defaultArgs, imageAlignment: imageAlignment.options![1] },
+};
+
const meta: Meta = {
decorators: [
(story) => html`${story()}
`,
diff --git a/src/elements/flip-card/flip-card/flip-card.ts b/src/elements/flip-card/flip-card/flip-card.ts
index e7badb1317..c225fa1f46 100644
--- a/src/elements/flip-card/flip-card/flip-card.ts
+++ b/src/elements/flip-card/flip-card/flip-card.ts
@@ -1,3 +1,4 @@
+import { ResizeController } from '@lit-labs/observers/resize-controller.js';
import { type CSSResultGroup, html, isServer, LitElement, type TemplateResult } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { until } from 'lit/directives/until.js';
@@ -52,6 +53,11 @@ class SbbFlipCardElement extends SbbHydrationMixin(LitElement) {
return this.querySelector?.('sbb-flip-card-details');
}
+ /** Returns the card details content element wrapper. */
+ private get _detailsContentElement(): HTMLElement | null {
+ return this.details!.shadowRoot!.firstElementChild as HTMLElement;
+ }
+
/** Whether the flip card is flipped. */
public get isFlipped(): boolean {
return this._flipped;
@@ -62,6 +68,11 @@ class SbbFlipCardElement extends SbbHydrationMixin(LitElement) {
private _abort = new SbbConnectedAbortController(this);
private _language = new SbbLanguageController(this);
+ private _cardDetailsResizeObserver = new ResizeController(this, {
+ target: null,
+ skipInitial: true,
+ callback: () => this._setCardDetailsHeight(),
+ });
public override connectedCallback(): void {
super.connectedCallback();
@@ -82,6 +93,12 @@ class SbbFlipCardElement extends SbbHydrationMixin(LitElement) {
/** Toggles the state of the sbb-flip-card. */
public toggle(): void {
this._flipped = !this._flipped;
+ if (this._flipped) {
+ this._setCardDetailsHeight();
+ this._cardDetailsResizeObserver.observe(this._detailsContentElement!);
+ } else {
+ this._cardDetailsResizeObserver.unobserve(this._detailsContentElement!);
+ }
this.toggleAttribute('data-flipped', this._flipped);
this.details!.toggleAttribute('data-flipped', this._flipped);
this.summary!.inert = this._flipped;
@@ -89,6 +106,11 @@ class SbbFlipCardElement extends SbbHydrationMixin(LitElement) {
this.flip.emit();
}
+ private _setCardDetailsHeight(): any {
+ const contentHeight = Math.floor(this._detailsContentElement!.offsetHeight);
+ this.style?.setProperty('--sbb-flip-card-details-height', `${contentHeight}px`);
+ }
+
private async _accessibilityLabel(): Promise {
if (isServer) {
return '';
diff --git a/src/elements/flip-card/flip-card/flip-card.visual.spec.ts b/src/elements/flip-card/flip-card/flip-card.visual.spec.ts
index 2fe8894c54..4b4cc92a4c 100644
--- a/src/elements/flip-card/flip-card/flip-card.visual.spec.ts
+++ b/src/elements/flip-card/flip-card/flip-card.visual.spec.ts
@@ -1,4 +1,5 @@
import { html, nothing, type TemplateResult } from 'lit';
+import { styleMap } from 'lit/directives/style-map.js';
import {
describeViewports,
@@ -9,6 +10,8 @@ import {
import { waitForImageReady } from '../../core/testing/wait-for-image-ready.js';
import type { SbbFlipCardImageAlignment } from '../flip-card-summary.js';
+import type { SbbFlipCardElement } from './flip-card.js';
+
import './flip-card.js';
import '../flip-card-summary.js';
import '../flip-card-details.js';
@@ -22,13 +25,12 @@ const content = (
title: string = 'Summary',
imageAlignment: SbbFlipCardImageAlignment = 'after',
longContent: boolean = false,
- flipped = false,
): TemplateResult =>
html`
${title}
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam luctus ornare condimentum.
Vivamus turpis elit, dapibus eget fringilla pellentesque, lobortis in nibh.
${longContent
@@ -68,10 +70,13 @@ describe(`sbb-flip-card`, () => {
'flipped',
visualDiffDefault.with(async (setup) => {
await setup.withFixture(
- html`
- ${content('Summary', 'after', false, true)}`,
+ html` ${content('Summary', 'after', false)}`,
);
+ setup.withPostSetupAction(() => {
+ const flipCard =
+ setup.snapshotElement.querySelector('sbb-flip-card')!;
+ flipCard.click();
+ });
await waitForImageReady(setup.snapshotElement.querySelector('sbb-image')!);
}),
);
@@ -104,6 +109,44 @@ describe(`sbb-flip-card`, () => {
);
}
+ for (const imageAlignment of ['after', 'below']) {
+ it(
+ `grid`,
+ visualDiffDefault.with(async (setup) => {
+ await setup.withFixture(
+ html`
+
+ ${content('Summary', imageAlignment as SbbFlipCardImageAlignment, true)}
+
+
+ ${content('Summary', imageAlignment as SbbFlipCardImageAlignment, true)}
+
+
+ ${content('Summary', imageAlignment as SbbFlipCardImageAlignment, true)}
+
+
+ ${content('Summary', imageAlignment as SbbFlipCardImageAlignment, true)}
+
+
`,
+ );
+ setup.withPostSetupAction(() => {
+ const flipCard =
+ setup.snapshotElement.querySelector('sbb-flip-card')!;
+ flipCard.click();
+ });
+ await waitForImageReady(setup.snapshotElement.querySelector('sbb-image')!);
+ }),
+ );
+ }
+
describe('forcedColors=true', () => {
for (const state of [visualDiffDefault, visualDiffHover, visualDiffFocus]) {
it(
diff --git a/src/elements/flip-card/flip-card/readme.md b/src/elements/flip-card/flip-card/readme.md
index 44483bc260..5591b7fd17 100644
--- a/src/elements/flip-card/flip-card/readme.md
+++ b/src/elements/flip-card/flip-card/readme.md
@@ -11,6 +11,32 @@ It's meant to be used together with [sbb-flip-card-summary](/docs/elements-sbb-f
```
+## Style
+
+The `sbb-flip-card` component has a predefined minimum height that can be customized by specifying the `min-height` property directly in the style of the host element with a custom height. Alternatively, when used within a CSS grid layout alongside other cards, the height can be adjusted using the `grid-template-rows` property. For consistent behavior and flexibility, it is recommended to use the `minmax()` function, for example: `grid-template-rows: minmax(320px, 1fr)`.
+
+```html
+
+
+
+ Card Title
+
+
+ Some additional text.
+
+
+
+
+ Card Title
+
+
+ Some additional text.
+
+
+```
+
## Slots
The component will display the content slotted in the `summary` slot in the main view, and the content slotted inside the `details` slot after the card has been flipped.