73 lines
3.4 KiB
Plaintext
73 lines
3.4 KiB
Plaintext
component Confetti {
|
|
outputs {
|
|
start(payload: { sourceEvent?: Event; duration: number })
|
|
complete(payload: { sourceEvent?: Event; duration: number })
|
|
}
|
|
|
|
props {
|
|
label: string = "Celebrate"
|
|
duration: number = 1200
|
|
count: number = 24
|
|
autoStart: boolean = false
|
|
disabled: boolean = false
|
|
size: string = "default"
|
|
color: string = "primary"
|
|
class: string = ""
|
|
}
|
|
|
|
state running = false
|
|
|
|
functions {
|
|
shared function pieces() { return Array.from({ length: Math.max(1, Math.min(60, count)) }) }
|
|
shared function pieceStyle(index) {
|
|
return "--wrn-confetti-index:" + index + ";--wrn-confetti-x:" + ((index * 37) % 100) + ";--wrn-confetti-delay:" + ((index * 29) % 240) + "ms;"
|
|
}
|
|
client function finish(sourceEvent) {
|
|
running = false
|
|
output.complete({ sourceEvent: sourceEvent, duration: duration })
|
|
}
|
|
client function start(sourceEvent) {
|
|
if (disabled || running) {
|
|
return
|
|
}
|
|
running = true
|
|
output.start({ sourceEvent: sourceEvent, duration: duration })
|
|
setTimeout(finish, duration, sourceEvent)
|
|
}
|
|
}
|
|
|
|
lifecycle {
|
|
mount { if (autoStart) { start(null) } }
|
|
}
|
|
|
|
view {
|
|
<div {...attrs} data-ui-component="Confetti" data-running='{running ? "true" : "false"}' class='wrn-confetti {class}' style='--wrn-confetti-duration:{duration}ms'>
|
|
<button type="button" class="wrn-confetti__trigger" disabled='{disabled}' @click='start(event)'>
|
|
<span class="icon-[lucide--party-popper]" aria-hidden="true"></span><span>{label}</span>
|
|
</button>
|
|
<div class="wrn-confetti__burst" aria-hidden="true">
|
|
{#each pieces() as piece, index}<i style='{pieceStyle(index)}'></i>{/each}
|
|
</div>
|
|
<slot />
|
|
</div>
|
|
}
|
|
|
|
style {
|
|
.wrn-confetti { position: relative; display: inline-flex; }
|
|
.wrn-confetti__trigger { display: inline-flex; min-height: 2.5rem; align-items: center; gap: 0.5rem; padding: 0.55rem 0.85rem; border: 1px solid var(--wrn-color-primary); border-radius: var(--wrn-radius); color: var(--wrn-color-on-primary); background: var(--wrn-color-primary); font: inherit; font-weight: 700; cursor: pointer; }
|
|
.wrn-confetti__trigger > span:first-child { width: 1rem; height: 1rem; }
|
|
.wrn-confetti__trigger:focus-visible { outline: 2px solid var(--wrn-color-focus); outline-offset: 2px; }
|
|
.wrn-confetti__trigger:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
.wrn-confetti__burst { position: absolute; inset: 50% 0 auto; height: 0; pointer-events: none; }
|
|
.wrn-confetti__burst > i { position: absolute; left: calc(var(--wrn-confetti-x) * 1%); width: 0.45rem; height: 0.7rem; border-radius: 0.1rem; opacity: 0; background: hsl(calc(var(--wrn-confetti-index) * 47deg) 80% 55%); transform: translate(-50%, 0) rotate(calc(var(--wrn-confetti-index) * 23deg)); }
|
|
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation: wrn-confetti-burst var(--wrn-confetti-duration) cubic-bezier(0.2, 0.7, 0.2, 1) var(--wrn-confetti-delay) both; }
|
|
@keyframes wrn-confetti-burst {
|
|
0% { opacity: 1; transform: translate(-50%, 0) rotate(0); }
|
|
100% { opacity: 0; transform: translate(calc(-50% + (var(--wrn-confetti-x) - 50) * 0.9px), 9rem) rotate(540deg); }
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.wrn-confetti[data-running="true"] .wrn-confetti__burst > i { animation-duration: 1ms; animation-delay: 0ms; }
|
|
}
|
|
}
|
|
}
|