fix ui package, use sv to create
recreated package using sv, marking correctly as a library. seems I was missing some sveltekit tooling somewhere along the way otherwise.
This commit is contained in:
150
src/lib/Button.svelte
Normal file
150
src/lib/Button.svelte
Normal file
@@ -0,0 +1,150 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import type { MouseEventHandler } from 'svelte/elements';
|
||||
import Spinner from './Spinner.svelte';
|
||||
|
||||
let {
|
||||
icon,
|
||||
animate = true,
|
||||
loading,
|
||||
children,
|
||||
onclick
|
||||
}: {
|
||||
icon?: string;
|
||||
animate?: boolean;
|
||||
loading?: boolean;
|
||||
children: Snippet;
|
||||
onclick?: MouseEventHandler<HTMLButtonElement>;
|
||||
} = $props();
|
||||
|
||||
let iconElement = $state<HTMLSpanElement | null>(null);
|
||||
|
||||
const handleButtonClick: MouseEventHandler<HTMLButtonElement> = (event) => {
|
||||
if (animate) {
|
||||
animateLoop();
|
||||
animateRipple(event);
|
||||
}
|
||||
|
||||
if (loading) return;
|
||||
|
||||
onclick?.(event);
|
||||
};
|
||||
|
||||
const triggerAnimation = (className: string) => {
|
||||
if (icon && iconElement) {
|
||||
iconElement.classList.remove(className);
|
||||
void iconElement.offsetWidth;
|
||||
iconElement.classList.add(className);
|
||||
}
|
||||
};
|
||||
|
||||
export const animateLoop = () => triggerAnimation('animate');
|
||||
export const animateBounce = () => triggerAnimation('bounce');
|
||||
export const animateRipple: MouseEventHandler<HTMLButtonElement> = (event) => {
|
||||
const button = event.currentTarget;
|
||||
const circle = document.createElement('span');
|
||||
const diameter = Math.max(button.clientWidth, button.clientHeight);
|
||||
const radius = diameter / 2;
|
||||
|
||||
const rect = button.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left - radius;
|
||||
const y = event.clientY - rect.top - radius;
|
||||
|
||||
circle.style.width = circle.style.height = `${diameter}px`;
|
||||
circle.style.left = `${x}px`;
|
||||
circle.style.top = `${y}px`;
|
||||
circle.classList.add('ripple');
|
||||
|
||||
const ripples = button.getElementsByClassName('ripple');
|
||||
for (let i = 0; i < ripples.length; i++) {
|
||||
ripples[i].remove();
|
||||
}
|
||||
|
||||
button.appendChild(circle);
|
||||
};
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={[
|
||||
'button group relative flex gap-3 overflow-hidden rounded-sm px-5',
|
||||
'text-background py-3 font-medium transition-colors',
|
||||
!loading ? ' bg-primary hover:bg-secondary' : 'bg-primary/50 cursor-not-allowed '
|
||||
]}
|
||||
onclick={handleButtonClick}
|
||||
>
|
||||
{@render children()}
|
||||
|
||||
{#if icon && !loading}
|
||||
<span class="material-symbols-outlined" bind:this={iconElement}>{icon}</span>
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<div class="w-[1rem]"></div>
|
||||
<div class="absolute top-1/2 right-4 translate-y-[-40%]"><Spinner size="1.3rem" /></div>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
@keyframes loop {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
opacity: 100%;
|
||||
}
|
||||
40% {
|
||||
opacity: 100%;
|
||||
}
|
||||
50% {
|
||||
transform: translateX(3rem);
|
||||
opacity: 0%;
|
||||
}
|
||||
51% {
|
||||
transform: translateX(-2rem);
|
||||
opacity: 0%;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-0.25rem);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(0.25rem);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
to {
|
||||
transform: scale(4);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:global(button span.animate) {
|
||||
animation-name: loop;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
:global(button span.bounce) {
|
||||
animation-name: bounce;
|
||||
animation-duration: 180ms;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: 3;
|
||||
}
|
||||
|
||||
:global(span.ripple) {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
animation: ripple 0.5s linear;
|
||||
transform: scale(0);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user