Skip to content

Commit d01579a

Browse files
committed
feat: add product-sticky-bar component
1 parent 50684a5 commit d01579a

File tree

5 files changed

+203
-8
lines changed

5 files changed

+203
-8
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { LitElement, html, CSSResultGroup } from 'lit';
2+
import { property } from 'lit/decorators.js';
3+
import componentStyles from '../../styles/component.styles.js';
4+
import styles from './product-sticky-bar.styles.js';
5+
6+
export default class ProductStickyBar extends LitElement {
7+
static styles: CSSResultGroup = [componentStyles, styles];
8+
9+
targetElement: HTMLElement | null = null;
10+
11+
@property({ type: String, attribute: 'target-selector' })
12+
targetSelector = '';
13+
14+
@property({ reflect: true })
15+
placement: 'end' | 'start' = 'end';
16+
17+
@property({ type: Boolean, reflect: true })
18+
reveal = false;
19+
20+
connectedCallback(): void {
21+
super.connectedCallback();
22+
23+
this.targetElement = document.querySelector('.' + this.targetSelector);
24+
if (this.targetElement) {
25+
this.createObserver();
26+
27+
if (this.placement === 'end') {
28+
document.body.classList.add('product-sticky-bar');
29+
}
30+
}
31+
}
32+
33+
disconnectedCallback(): void {
34+
super.disconnectedCallback();
35+
36+
if (this.placement === 'end') {
37+
document.body.classList.remove('product-sticky-bar');
38+
}
39+
}
40+
41+
createObserver() {
42+
new IntersectionObserver(entries => {
43+
entries[0].intersectionRatio === 0 ? this.show() : this.hide();
44+
}).observe(this.targetElement!);
45+
}
46+
47+
hide() {
48+
this.reveal = false;
49+
}
50+
51+
show() {
52+
this.reveal = true;
53+
}
54+
55+
render() {
56+
return html`<slot></slot>`;
57+
}
58+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import type { Meta, StoryObj } from '@storybook/web-components';
2+
import { html } from 'lit';
3+
import { repeat } from 'lit/directives/repeat.js';
4+
import './product-sticky-bar';
5+
import '../skeleton/skeleton';
6+
7+
type Story = StoryObj;
8+
9+
const meta: Meta = {
10+
title: 'Core/zx-product-sticky-bar',
11+
12+
parameters: {
13+
layout: 'fullscreen',
14+
backgrounds: {
15+
default: 'light',
16+
},
17+
story: {
18+
//height: '500px',
19+
},
20+
},
21+
};
22+
23+
const addToCartButton = html`
24+
<button
25+
type="button"
26+
class="border border-gray-950 bg-gray-900 text-white rounded text-sm px-3 py-2 hover:shadow"
27+
>
28+
Ajouter au panier - 42 €
29+
</button>
30+
`;
31+
32+
export const PlacementStart: Story = {
33+
render: () => html`
34+
<div class="relative">
35+
<div class="sticky top-0 z-10">
36+
<header class="relative z-20 bg-white p-4 border-b">Header</header>
37+
38+
<zx-product-sticky-bar placement="start" target-selector="product-main">
39+
<div class="flex items-center justify-end gap-8 p-4 bg-white border-b">
40+
<span class="font-semibold text-sm">Magic Mouse</span>
41+
${addToCartButton}
42+
</div>
43+
</zx-product-sticky-bar>
44+
</div>
45+
46+
<div class="grid gap-8">
47+
${repeat(
48+
Array.from({ length: 6 }),
49+
() => html`
50+
<zx-skeleton
51+
class="product-main"
52+
shape="rect"
53+
height="400"
54+
style="width: 100%"
55+
></zx-skeleton>
56+
`,
57+
)}
58+
</div>
59+
</div>
60+
`,
61+
};
62+
63+
export const PlacementEnd: Story = {
64+
render: () => html`
65+
<div class="relative">
66+
<div class="sticky top-0 z-10">
67+
<header class="relative z-20 bg-white p-4 border-b">Header</header>
68+
69+
<zx-product-sticky-bar placement="end" target-selector="product-main">
70+
<div class="flex items-center justify-end gap-8 p-4 bg-white border-t">
71+
<span class="font-semibold text-sm">Magic Mouse</span>
72+
${addToCartButton}
73+
</div>
74+
</zx-product-sticky-bar>
75+
</div>
76+
77+
<div class="grid gap-8">
78+
${repeat(
79+
Array.from({ length: 6 }),
80+
() => html`
81+
<zx-skeleton
82+
class="product-main"
83+
shape="rect"
84+
height="400"
85+
style="width: 100%"
86+
></zx-skeleton>
87+
`,
88+
)}
89+
</div>
90+
</div>
91+
`,
92+
};
93+
94+
export default meta;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { css } from 'lit';
2+
3+
export default css`
4+
:host {
5+
--transition-duration: 300ms;
6+
--translateY: 100%;
7+
8+
display: block;
9+
left: 0;
10+
right: 0;
11+
z-index: 1;
12+
transition: transform var(--transition-duration) ease-out;
13+
}
14+
15+
:host([placement='start']) {
16+
--translateY: -100%;
17+
position: sticky;
18+
top: 0;
19+
}
20+
21+
:host([placement='end']) {
22+
position: fixed;
23+
bottom: 0;
24+
}
25+
26+
:host(:not([reveal])) {
27+
transform: translateY(var(--translateY));
28+
}
29+
`;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import ProductStickyBar from './product-sticky-bar.component';
2+
3+
export * from './product-sticky-bar.component';
4+
export default ProductStickyBar;
5+
6+
if (!customElements.get('zx-product-sticky-bar')) {
7+
customElements.define('zx-product-sticky-bar', ProductStickyBar);
8+
}
9+
10+
declare global {
11+
interface HTMLElementTagNameMap {
12+
'zx-product-sticky-bar': ProductStickyBar;
13+
}
14+
}

src/index.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Components
2-
export { default as CloseButton } from './components/close-button/close-button';
3-
export { default as Details } from './components/details/details';
4-
export { default as Drawer } from './components/drawer/drawer';
5-
export { default as Dialog } from './components/dialog/dialog';
1+
export { default as CloseButton } from './components/close-button/close-button.js';
2+
export { default as Details } from './components/details/details.js';
3+
export { default as Drawer } from './components/drawer/drawer.js';
4+
export { default as Dialog } from './components/dialog/dialog.js';
65
export { default as InputStepper } from './components/input-stepper/input-stepper.js';
7-
export { default as Popover } from './components/popover/popover';
6+
export { default as Popover } from './components/popover/popover.js';
7+
export { default as ProductStickyBar } from './components/product-sticky-bar/product-sticky-bar.js';
88
export { default as Rating } from './components/rating/rating.js';
9-
export { default as Skeleton } from './components/skeleton/skeleton';
9+
export { default as Skeleton } from './components/skeleton/skeleton.js';
1010
export { default as Spinner } from './components/spinner/spinner.js';
11-
export { default as Tooltip } from './components/tooltip/tooltip';
11+
export { default as Tooltip } from './components/tooltip/tooltip.js';

0 commit comments

Comments
 (0)