feat: create Toast component

This commit is contained in:
2023-06-11 22:39:37 +02:00
parent 738a7728c0
commit 3b8d499171
2 changed files with 231 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
<style lang="scss">
@import "../../style/molecules/toast";
</style>
<script lang="ts">
import { onMount } from 'svelte'
import { fade, fly } from 'svelte/transition'
import { quartOut } from 'svelte/easing'
import { browser } from '$app/environment'
import { cx } from 'classix'
// Components
import Button from '$components/atoms/Button.svelte'
import Image from '$components/atoms/Image.svelte'
export let id: string
export let type: 'global' | 'local'
export let text: string
export let cta: {
label: string
url: string
color: string
} = undefined
export let images: { id: string, title: string }[] = undefined
export let show = false
$: if (browser) {
show = !localStorage.getItem(`toast-${id}`)
}
// Image rotation
let imagesLoop: ReturnType<typeof setTimeout>
let currentImageIndex = 0
const incrementCurrentImageIndex = () => {
currentImageIndex = currentImageIndex === images.length - 1 ? 0 : currentImageIndex + 1
imagesLoop = setTimeout(() => requestAnimationFrame(incrementCurrentImageIndex), 3000)
}
// Close toast
const close = () => {
localStorage.setItem(`toast-${id}`, 'closed')
show = false
}
$: classes = cx(
'toast',
`toast--${type}`,
'shadow-small',
$$props.class,
)
onMount(() => {
if (images.length > 1) {
incrementCurrentImageIndex()
}
return () => {
// Clear rotating words timeout
if (imagesLoop) {
clearTimeout(imagesLoop)
}
}
})
</script>
{#if show}
<div
class={classes}
in:fly={{ y: '10%', duration: 1200, easing: quartOut }}
out:fade={{ duration: 800, easing: quartOut }}
>
{#if images}
<div class="media">
<a href={cta.url}>
{#each images as { id, title }, index}
<Image
class={index === currentImageIndex ? 'is-visible' : null}
{id}
sizeKey="square"
sizes={{
small: { width: 200, height: 200 },
large: { width: 350, height: 350 },
}}
alt={title}
/>
{/each}
</a>
</div>
{/if}
<div class="content">
<p class="text">{@html text}</p>
{#if cta}
<Button
size="xsmall"
text={cta.label}
url={cta.url}
color="pink"
/>
{/if}
</div>
<button class="close" on:click={close} title="Close">
<svg width="10" height="10">
<use xlink:href="#cross" />
</svg>
</button>
</div>
{/if}

View File

@@ -0,0 +1,120 @@
.toast {
display: flex;
align-items: center;
padding: 8px;
padding-right: 28px;
background: #fff;
border-radius: 8px;
@include bp (md) {
padding-right: 32px;
}
}
// Media
.media {
position: relative;
overflow: hidden;
flex-shrink: 0;
flex: 0 0 clamp(40px, 14vw, 64px);
aspect-ratio: 1 / 1.25;
height: 100%;
margin-right: 16px;
border-radius: 4px;
@include bp (md) {
flex: 0 0 min(7vw, 104px);
margin-right: 24px;
}
a {
display: block;
width: 100%;
height: 100%;
}
:global(picture) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transform: scale(1.075);
transition: opacity 0.8s, transform 1.6s var(--ease-quart);
}
:global(img) {
object-position: center 32%;
}
:global(.is-visible) {
opacity: 1;
transform: scale(1);
}
:global(img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
@include bp (sm) {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
}
}
// Content
.content {
color: $color-text;
font-size: rem(14px);
@include bp (md) {
font-size: rem(16px);
}
.text {
margin-bottom: 12px;
@include bp (md) {
margin-bottom: 20px;
}
}
:global(strong) {
font-weight: normal;
color: $color-secondary;
}
}
// Close
.close {
--size: 28px;
position: absolute;
top: 0;
right: 0;
width: var(--size);
height: var(--size);
display: flex;
align-items: center;
justify-content: center;
@include bp (sm) {
--size: 32px;
}
:global(svg) {
transition: transform 0.6s var(--ease-quart);
}
// Hover
&:hover {
:global(svg) {
transform: rotate(90deg) translateZ(0);
}
}
}