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 {
} 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; } } } }