Skip to content

Commit

Permalink
feat(sbb-teaser-product): initial implementation (#2976)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Davide Mininni <[email protected]>
Co-authored-by: Jeremias Peier <[email protected]>
  • Loading branch information
3 people authored Aug 15, 2024
1 parent 25dbd78 commit 79601d2
Show file tree
Hide file tree
Showing 24 changed files with 1,427 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/elements/teaser-product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './teaser-product/teaser-product.js';
export * from './teaser-product/teaser-product-static.js';
export * from './teaser-product/common.js';
3 changes: 3 additions & 0 deletions src/elements/teaser-product/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './common/teaser-product-common.js';

export { default as teaserProductCommonStyle } from './common/teaser-product-common.scss?lit&inline';
157 changes: 157 additions & 0 deletions src/elements/teaser-product/common/teaser-product-common.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
@use '../../core/styles/index' as sbb;

// Box-sizing rules contained in typography are not traversing Shadow DOM boundaries. We need to include box-sizing mixin in every component.
@include sbb.box-sizing;

:host {
display: block;

--sbb-teaser-product-background-color: var(--sbb-color-cloud);
--sbb-teaser-product-background-gradient-direction: to right;
--sbb-teaser-product-background: var(--sbb-teaser-product-background-color);
--sbb-teaser-product-border-radius: var(--sbb-border-radius-4x);
--sbb-teaser-product-content-color: var(--sbb-color-iron);
--sbb-teaser-product-footer-color: var(--sbb-color-anthracite);
--sbb-teaser-product-container-padding-block: var(--sbb-spacing-responsive-l);
--sbb-teaser-product-min-height: #{sbb.px-to-rem-build(600)};
--sbb-teaser-product-background-gradient-start: 25%;
--sbb-teaser-product-background-gradient-end: 75%;

@include sbb.mq($from: large) {
--sbb-teaser-product-background: linear-gradient(
var(--sbb-teaser-product-background-gradient-direction),
var(--sbb-teaser-product-background-color) var(--sbb-teaser-product-background-gradient-start),
transparent var(--sbb-teaser-product-background-gradient-end)
);
}
}

:host([negative]) {
--sbb-teaser-product-background-color: var(--sbb-color-midnight);
--sbb-teaser-product-content-color: var(--sbb-color-cloud);
--sbb-teaser-product-footer-color: var(--sbb-color-cloud);
--sbb-focus-outline-color: var(--sbb-focus-outline-color-dark);
--sbb-title-text-color-normal-override: var(--sbb-color-milk);
}

:host([image-alignment='before']) {
--sbb-teaser-product-background-gradient-direction: to left;
}

.sbb-teaser-product__image-container {
display: block;
overflow: hidden;

// We have to remove the image bottom border-radius when stacked
border-radius: var(--sbb-teaser-product-border-radius) var(--sbb-teaser-product-border-radius) 0 0;

@include sbb.mq($from: large) {
position: absolute;
inset: 0;
border-radius: var(--sbb-teaser-product-border-radius);
}
}

::slotted(img) {
display: flex;
width: 100%;
height: 100%;
object-fit: cover;
aspect-ratio: 16 / 9;
}

// Reset sbb-image border radius in order to control it from teaser product.
::slotted(sbb-image) {
--sbb-image-border-radius: 0;

height: 100%;
}

::slotted(p.sbb-teaser-product--spacing) {
margin: 0;
}

::slotted(sbb-title.sbb-teaser-product--spacing) {
--sbb-title-margin-block-start: 0;
}

::slotted(:is(sbb-action-group, [data-action]).sbb-teaser-product--spacing) {
margin-block-start: var(--sbb-spacing-responsive-xxs);
}

.sbb-action-base {
display: block;
position: relative;
text-decoration: none;

@include sbb.if-forced-colors {
// Apply a visual border for forced color mode
&::after {
content: '';
position: absolute;
display: block;
inset: 0;
pointer-events: none;
border: var(--sbb-border-width-2x) solid CanvasText;
border-radius: var(--sbb-teaser-product-border-radius);
}
}
}

.sbb-teaser-product__container {
display: block;
background: var(--sbb-teaser-product-background);
border-radius: 0 0 var(--sbb-teaser-product-border-radius) var(--sbb-teaser-product-border-radius);
padding: var(--sbb-spacing-responsive-s);

@include sbb.mq($from: large) {
display: grid;
grid:
'content .' 1fr
'footnote .' auto / 1fr 1fr;
column-gap: var(--sbb-spacing-responsive-xxl);
background: var(--sbb-teaser-product-background);
border-radius: var(--sbb-teaser-product-border-radius);
padding-block: var(--sbb-teaser-product-container-padding-block) 0;
padding-inline: var(--sbb-spacing-responsive-xl);
position: relative;

:host([image-alignment='before']) & {
grid-template-areas:
'. content'
'. footnote';
}
}
}

.sbb-teaser-product__content {
grid-area: content;
align-self: center;
margin: 0;
color: var(--sbb-teaser-product-content-color);

@include sbb.mq($from: large) {
align-content: center;
min-height: calc(
var(--sbb-teaser-product-min-height) - 2 * var(--sbb-teaser-product-container-padding-block)
);
}
}

.sbb-teaser-product__footnote {
grid-area: footnote;
display: block;
padding-block-start: var(--sbb-spacing-responsive-s);
color: var(--sbb-teaser-product-footer-color);

@include sbb.text-xxs--regular;

:host(:not([data-slot-names~='footnote'])) & {
padding-block-start: 0;
}

@include sbb.mq($from: large) {
min-height: var(--sbb-teaser-product-container-padding-block);
padding-block: var(--sbb-spacing-responsive-xs);
}
}
50 changes: 50 additions & 0 deletions src/elements/teaser-product/common/teaser-product-common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { html, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';

import type { SbbActionBaseElement } from '../../core/base-elements.js';
import { slotState } from '../../core/decorators.js';
import {
SbbNegativeMixin,
type SbbNegativeMixinType,
type AbstractConstructor,
} from '../../core/mixins.js';

export declare class SbbTeaserProductCommonElementMixinType extends SbbNegativeMixinType {
public imageAlignment?: 'after' | 'before';
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const SbbTeaserProductCommonElementMixin = <
T extends AbstractConstructor<SbbActionBaseElement>,
>(
superClass: T,
): AbstractConstructor<SbbTeaserProductCommonElementMixinType> & T => {
@slotState()
abstract class SbbTeaserProductCommonElement
extends SbbNegativeMixin(superClass)
implements SbbTeaserProductCommonElementMixinType
{
/**
* Whether the fully visible part of the image is aligned 'before' or 'after' the content.
* Only relevant starting from large breakpoint.
*/
@property({ attribute: 'image-alignment', reflect: true })
public imageAlignment: 'after' | 'before' = 'after';

protected override renderTemplate(): TemplateResult {
return html`
<span class="sbb-teaser-product__image-container"><slot name="image"></slot></span>
<span class="sbb-teaser-product__container">
<span class="sbb-teaser-product__content">
<slot></slot>
</span>
<span class="sbb-teaser-product__footnote">
<slot name="footnote"></slot>
</span>
</span>
`;
}
}
return SbbTeaserProductCommonElement as AbstractConstructor<SbbTeaserProductCommonElementMixinType> &
T;
};
1 change: 1 addition & 0 deletions src/elements/teaser-product/teaser-product-static.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './teaser-product-static/teaser-product-static.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* @web/test-runner snapshot v1 */
export const snapshots = {};

snapshots["sbb-teaser-product-static renders DOM"] =
`<sbb-teaser-product-static
data-action=""
data-slot-names="footnote image unnamed"
dir="ltr"
image-alignment="after"
>
<sbb-image
aspect-ratio="16-9"
border-radius="default"
image-src="http://localhost:8000/src/elements/core/testing/assets/placeholder-image.png"
slot="image"
>
</sbb-image>
<p class="sbb-teaser-product--spacing">
Content
</p>
<p
class="sbb-teaser-product--spacing"
slot="footnote"
>
Footnote
</p>
</sbb-teaser-product-static>
`;
/* end snapshot sbb-teaser-product-static renders DOM */

snapshots["sbb-teaser-product-static renders Shadow DOM"] =
`<span class="sbb-action-base sbb-teaser-product-static">
<span class="sbb-teaser-product__image-container">
<slot name="image">
</slot>
</span>
<span class="sbb-teaser-product__container">
<span class="sbb-teaser-product__content">
<slot>
</slot>
</span>
<span class="sbb-teaser-product__footnote">
<slot name="footnote">
</slot>
</span>
</span>
</span>
`;
/* end snapshot sbb-teaser-product-static renders Shadow DOM */

snapshots["sbb-teaser-product-static renders A11y tree Chrome"] =
`<p>
{
"role": "WebArea",
"name": "",
"children": [
{
"role": "text",
"name": "Content"
},
{
"role": "text",
"name": "Footnote"
}
]
}
</p>
`;
/* end snapshot sbb-teaser-product-static renders A11y tree Chrome */

snapshots["sbb-teaser-product-static renders A11y tree Firefox"] =
`<p>
{
"role": "document",
"name": "",
"children": [
{
"role": "text leaf",
"name": "Content"
},
{
"role": "text leaf",
"name": "Footnote"
}
]
}
</p>
`;
/* end snapshot sbb-teaser-product-static renders A11y tree Firefox */

87 changes: 87 additions & 0 deletions src/elements/teaser-product/teaser-product-static/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
The `sbb-teaser-product-static` is a component that can display a text and a footnote,
combined with an image as background, to tease a product.
It should be used if there is more than one interactive action,
otherwise, see [sbb-teaser-product](/docs/elements-sbb-teaser-sbb-teaser-product--docs).

```html
<sbb-teaser-product-static>
<sbb-image slot="image" image-src="..."></sbb-image>

<p class="sbb-teaser-product--spacing">Content ...</p>

<p slot="footnote" class="sbb-teaser-product--spacing">...</p>
</sbb-teaser-product-static>
```

## Slots

Use the `image` slot to pass a `sbb-image` or an `img` that will be used as a background,
and use the optional `footnote` slot to add a text anchored to the bottom-end of the component.

The default slot is reserved for the main content: it could be a simple text or a text combined with more elements,
like a `sbb-title` or some interactive elements, like buttons or links within the `sbb-action-group` component.

```html
<sbb-teaser-product-static>
<sbb-image slot="image" image-src="..."></sbb-image>
<p class="sbb-teaser-product--spacing">Content ...</p>
</sbb-teaser-product-static>
```

If paragraphs, title and/or button are used, consumers can apply the helper class `sbb-teaser-product--spacing`
to display the components with the correct spacings.

```html
<sbb-teaser-product-static>
<sbb-image slot="image" image-src="..."></sbb-image>
<sbb-title level="3" class="sbb-teaser-product--spacing">
Benefit from up to 70% discount
</sbb-title>
<p class="sbb-teaser-product--spacing">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent pretium felis sit amet felis
viverra lacinia. Donec et enim mi. Aliquam erat volutpat. Proin ut odio tellus.
</p>
<sbb-action-group class="sbb-teaser-product--spacing">
<sbb-button>Label</sbb-button>
<sbb-secondary-button>Label</sbb-secondary-button>
</sbb-action-group>
</sbb-teaser-product-static>
```

## Style

Use the `image-alignment` attribute to anchor the content `after` (on the left) or `before` (on the right).

```html
<sbb-teaser-product-static image-alignment="before"> ... </sbb-teaser-product-static>
```

Add the `negative` attribute to enable the negative variant.

```html
<sbb-teaser-product-static negative> ... </sbb-teaser-product-static>
```

<!-- Auto Generated Below -->

## Properties

| Name | Attribute | Privacy | Type | Default | Description |
| ---------------- | ----------------- | ------- | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `imageAlignment` | `image-alignment` | public | `'after' \| 'before'` | `'after'` | Whether the fully visible part of the image is aligned 'before' or 'after' the content. Only relevant starting from large breakpoint. |
| `negative` | `negative` | public | `boolean` | `false` | Negative coloring variant flag. |

## CSS Properties

| Name | Default | Description |
| ------------------------------------------------ | ------- | -------------------------------------------------------------------- |
| `--sbb-teaser-product-background-gradient-end` | `75%` | At which percentage the background should be fully transparent. |
| `--sbb-teaser-product-background-gradient-start` | `25%` | At which percentage the background should start getting transparent. |

## Slots

| Name | Description |
| ---------- | ------------------------------------------------------------------- |
| | Use this slot to provide the main content. |
| `footnote` | Use this slot to provide a footnote. |
| `image` | Use this slot to provide an image or a `sbb-image` as a background. |
Loading

0 comments on commit 79601d2

Please sign in to comment.