Skip to content

Commit

Permalink
feat(sbb-sticky-bar): introduce controllable slide and out animation (#…
Browse files Browse the repository at this point in the history
…3073)

This change allows controlling the position: sticky property and also handles slide in on initial load.

Closes #3072
  • Loading branch information
jeripeierSBB authored and github-actions committed Nov 20, 2024
1 parent acdabcb commit ea04e08
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
export const snapshots = {};

snapshots["sbb-sticky-bar renders DOM"] =
`<sbb-sticky-bar slot="sticky-bar">
`<sbb-sticky-bar
data-initialized=""
data-state="sticky"
slot="sticky-bar"
>
</sbb-sticky-bar>
`;
/* end snapshot sbb-sticky-bar renders DOM */
Expand Down
29 changes: 29 additions & 0 deletions src/elements/container/sticky-bar/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ It is displayed with sticky positioning at the bottom of the container that cont
</sbb-container>
```

## Animate from sticky to normal content flow and vice versa

By default, the sticky bar is set to `position: sticky`. In certain cases, the consumer needs
to control the sliding out (or sliding in) of the sticky bar.
By calling the `stick()` or `unstick()` methods, the position property is toggled
between `position: sticky` and `position: relative` by displaying a slide animation. When the sticky bar is `unstick`,
the `sbb-sticky-bar` will behave like a normal container without any sticky behavior.
Whenever the sticky bar is currently not sticky (e.g. scrolled down),
calling `stick()` or `unstick()` won't have any visual effect.

An example use case is to call `unstick()`, which visually slides out the sticky bar, and
then the consumer can remove it from the DOM by listening to the `didUnstick` event.

## Slots

The `sbb-sticky-bar` content is provided via an unnamed slot.
Expand All @@ -26,6 +39,22 @@ Optionally the user can set the `color` property on the `sbb-sticky-bar` in orde
| ------- | --------- | ------- | --------------------------- | ------- | ---------------------------------------------------- |
| `color` | `color` | public | `'white' \| 'milk' \| null` | `null` | Color of the container, like transparent, white etc. |

## Methods

| Name | Privacy | Description | Parameters | Return | Inherited From |
| --------- | ------- | ----------------------------------------------------------------- | ---------- | ------ | -------------- |
| `stick` | public | Animates from normal content flow position to `position: sticky`. | | `void` | |
| `unstick` | public | Animates `position: sticky` to normal content flow position. | | `void` | |

## Events

| Name | Type | Description | Inherited From |
| ------------- | ------------------- | ------------------------------------------------------------------------------------------------ | -------------- |
| `didStick` | `CustomEvent<void>` | Emits when the animation from normal content flow to `position: sticky` ends. | |
| `didUnstick` | `CustomEvent<void>` | Emits when the animation from `position: sticky` to normal content flow ends. | |
| `willStick` | `CustomEvent<void>` | Emits when the animation from normal content flow to `position: sticky` starts. Can be canceled. | |
| `willUnstick` | `CustomEvent<void>` | Emits when the animation from `position: sticky` to normal content flow starts. Can be canceled. | |

## CSS Properties

| Name | Default | Description |
Expand Down
122 changes: 96 additions & 26 deletions src/elements/container/sticky-bar/sticky-bar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@

$intersector-overlapping: 1px;

// `data-sticking` v.s. `data-state='sticky'`:
// While `data-sticking` is representing whether the sticky bar is currently sticking (due scrolling),
// the `data-state` holds information whether the `position: sticky` is applied or not
// but not considering the real sticking state.

:host {
--sbb-sticky-bar-position: sticky;
--sbb-sticky-bar-padding-block: var(--sbb-spacing-responsive-xs);
--sbb-sticky-bar-border-radius: var(--sbb-border-radius-8x);
--sbb-sticky-bar-animation-easing: var(--sbb-animation-easing);
--sbb-sticky-bar-fade-in-animation-duration: var(
--sbb-disable-animation-zero-time,
var(--sbb-animation-duration-5x)
Expand All @@ -15,34 +23,90 @@ $intersector-overlapping: 1px;
--sbb-disable-animation-zero-time,
var(--sbb-animation-duration-2x)
);
--sbb-sticky-bar-animation-easing: var(--sbb-animation-easing);
--sbb-sticky-bar-border-radius: var(--sbb-border-radius-8x);
--sbb-sticky-bar-slide-vertically-animation-duration: var(
--sbb-disable-animation-zero-duration,
var(--sbb-animation-duration-4x)
);
--sbb-sticky-bar-slide-vertically-animation-easing: ease-out;
--sbb-sticky-bar-slide-vertically-animation-delay: 0s;
--sbb-sticky-bar-slide-vertically-animation-name: unset;
--_sbb-sticky-bar-background-animation-duration: var(
--sbb-sticky-bar-fade-out-animation-duration
);
--_sbb-sticky-bar-intersector-background-color: transparent;
--_sbb-sticky-bar-forced-colors-border: none;

// Display contents needed to get the sticky bar sticky.
display: contents;
}

:host([data-sticking]) {
:host([data-sticking]:not([data-state='unsticky'])) {
--sbb-sticky-bar-sticky-background-color: var(
--sbb-container-background-color,
var(--sbb-color-white)
);
--_sbb-sticky-bar-intersector-background-color: var(--sbb-sticky-bar-sticky-background-color);
--_sbb-sticky-bar-background-animation-duration: var(--sbb-sticky-bar-fade-in-animation-duration);

@include sbb.if-forced-colors {
--_sbb-sticky-bar-forced-colors-border: var(--sbb-border-width-1x) solid CanvasText;
}
}

:host([data-sticking][color='white']) {
:host([data-sticking]:not([data-state='unsticky'])[color='white']) {
--sbb-sticky-bar-sticky-background-color: var(--sbb-color-white);
}

:host([data-sticking][color='milk']) {
:host([data-sticking]:not([data-state='unsticky'])[color='milk']) {
--sbb-sticky-bar-sticky-background-color: var(--sbb-color-milk);
}

:host(
:is(
[data-sticking]:is(
[data-slide-vertically],
[data-state='sticking'],
[data-state='unsticking']
),
[data-state='unsticky']
)
) {
--_sbb-sticky-bar-background-animation-duration: 0s;
}

:host(
[data-sticking]:is(
[data-slide-vertically]:not([data-state='unsticky'], [data-state='unsticking']),
[data-state='sticking']
)
) {
--sbb-sticky-bar-slide-vertically-animation-name: slide-in;
}

:host([data-sticking][data-state='unsticking']) {
--sbb-sticky-bar-slide-vertically-animation-name: slide-out;
}

:host(:is(:not([data-initialized]), [data-state='unsticky'])) {
--sbb-sticky-bar-position: relative;
}

.sbb-sticky-bar__wrapper {
position: sticky;
position: var(--sbb-sticky-bar-position);
inset-block-end: 0;
display: block;
z-index: var(--sbb-sticky-bar-z-index);

animation: {
name: var(--sbb-sticky-bar-slide-vertically-animation-name);
duration: var(--sbb-sticky-bar-slide-vertically-animation-duration);
timing-function: var(--sbb-sticky-bar-slide-vertically-animation-easing);
delay: var(--sbb-sticky-bar-slide-vertically-animation-delay);

// Fill mode needed to enable delay
fill-mode: backwards;
}

&::after,
&::before {
content: '';
Expand All @@ -68,20 +132,13 @@ $intersector-overlapping: 1px;
// Color and border radius when sticky
&::after {
background-color: var(--sbb-sticky-bar-sticky-background-color, transparent);
transition: background-color var(--sbb-sticky-bar-fade-out-animation-duration)
var(--sbb-sticky-bar-animation-easing);
border-start-start-radius: var(--sbb-sticky-bar-border-radius);
border-start-end-radius: var(--sbb-sticky-bar-border-radius);
transition: background-color var(--_sbb-sticky-bar-background-animation-duration)
var(--sbb-sticky-bar-animation-easing);

// Display a border on high contrast mode.
:host([data-sticking]) & {
transition-duration: var(--sbb-sticky-bar-fade-in-animation-duration);

@include sbb.if-forced-colors {
border-block-start: var(--sbb-border-width-1x) solid CanvasText;
border-radius: 0;
}
}
border: var(--_sbb-sticky-bar-forced-colors-border);
}
}

Expand All @@ -100,7 +157,7 @@ $intersector-overlapping: 1px;
z-index: -1;
border-start-start-radius: var(--sbb-sticky-bar-border-radius);
border-start-end-radius: var(--sbb-sticky-bar-border-radius);
transition: box-shadow var(--sbb-sticky-bar-fade-out-animation-duration)
transition: box-shadow var(--_sbb-sticky-bar-background-animation-duration)
var(--sbb-sticky-bar-animation-easing);
clip-path: polygon(
-50% calc(-1 * var(--sbb-shadow-elevation-level-11-shadow-1-blur)),
Expand All @@ -109,10 +166,8 @@ $intersector-overlapping: 1px;
-50% 50%
);

:host([data-sticking]) & {
:host([data-sticking]:not([data-state='unsticky'])) & {
@include sbb.shadow-level-11-soft;

transition-duration: var(--sbb-sticky-bar-fade-in-animation-duration);
}
}

Expand All @@ -135,14 +190,29 @@ $intersector-overlapping: 1px;
position: absolute;
width: 100%;
height: calc(var(--sbb-sticky-bar-bottom-overlapping-height, 0) + $intersector-overlapping);
background-color: transparent;
background-color: var(--_sbb-sticky-bar-intersector-background-color);
pointer-events: none;
transition: background-color var(--sbb-sticky-bar-fade-out-animation-duration)
transition: background-color var(--_sbb-sticky-bar-background-animation-duration)
var(--sbb-sticky-bar-animation-easing);
}
}

:host([data-sticking]) & {
transition-duration: var(--sbb-sticky-bar-fade-in-animation-duration);
background-color: var(--sbb-sticky-bar-sticky-background-color);
}
@keyframes slide-in {
from {
transform: translateY(100%);
}

to {
transform: translateY(0%);
}
}

@keyframes slide-out {
from {
transform: translateY(0%);
}

to {
transform: translateY(100%);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe(`sbb-sticky-bar`, () => {
let element: SbbStickyBarElement;

beforeEach(async () => {
element = await fixture(html` <sbb-sticky-bar></sbb-sticky-bar> `);
element = await fixture(html`<sbb-sticky-bar></sbb-sticky-bar>`);
});

it('DOM', async () => {
Expand Down
Loading

0 comments on commit ea04e08

Please sign in to comment.