Deadline Countdown
atroui/deadline-countdownFreeBlock
Days-to-deadline band with segmented progress and CountUp.
Install it
npx shadcn@latest add https://atroui.com/r/deadline-countdown.jsonSource
1"use client"23import * as React from "react"45const TOTAL_SEGMENTS = 246const DAY_MS = 24 * 60 * 60 * 100078/** Edit dates and labels after install. */9const CONTENT = {10 title: "Deadline",11 targetDate: "2026-12-31",12 startDate: "2026-01-01",13 unitLabel: "days",14 progressLabel: "to deadline",15}1617function parseIso(iso: string) {18 return new Date(iso.length <= 10 ? `${iso}T00:00:00Z` : iso)19}2021function daysBetween(fromIso: string, toIso: string) {22 return Math.round(23 (parseIso(toIso).getTime() - parseIso(fromIso).getTime()) / DAY_MS24 )25}2627function daysFromNow(iso: string) {28 const now = new Date()29 const target = parseIso(iso)30 return Math.max(0, Math.ceil((target.getTime() - now.getTime()) / DAY_MS))31}3233function formatFullDate(iso: string) {34 return new Intl.DateTimeFormat("en-US", {35 year: "numeric",36 month: "long",37 day: "numeric",38 timeZone: "UTC",39 }).format(parseIso(iso))40}4142/** Inlined CountUp helper — self-contained registry block. */43function CountUp({44 value,45 duration = 1200,46 className,47 ariaLabel,48}: {49 value: number50 duration?: number51 className?: string52 ariaLabel?: string53}) {54 const ref = React.useRef<HTMLSpanElement | null>(null)55 const [display, setDisplay] = React.useState(0)56 const startedRef = React.useRef(false)5758 React.useEffect(() => {59 const node = ref.current60 if (!node) return6162 const reduced =63 window.matchMedia?.("(prefers-reduced-motion: reduce)").matches64 if (reduced) {65 setDisplay(value)66 return67 }6869 let raf = 070 const start = () => {71 if (startedRef.current) return72 startedRef.current = true73 const t0 = performance.now()74 const from = 075 const to = value76 const ease = (t: number) => 1 - Math.pow(1 - t, 4)7778 const step = (now: number) => {79 const t = Math.min(1, (now - t0) / duration)80 setDisplay(Math.round(from + (to - from) * ease(t)))81 if (t < 1) raf = requestAnimationFrame(step)82 }83 raf = requestAnimationFrame(step)84 }8586 if (typeof IntersectionObserver === "undefined") {87 start()88 return () => cancelAnimationFrame(raf)89 }9091 const io = new IntersectionObserver(92 (entries) => {93 if (entries[0]?.isIntersecting) {94 start()95 io.disconnect()96 }97 },98 { threshold: 0.3 }99 )100 io.observe(node)101 return () => {102 io.disconnect()103 cancelAnimationFrame(raf)104 }105 }, [value, duration])106107 return (108 <span109// … truncated
Files it writes
More from atroui
All 82 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Analytics Provideranalytics-provider | atroui | Blocks | Free | 1 dep | |
| AR Portfolioar-portfolio | atroui | Blocks | Free | 1 dep | |
| Before / Afterbefore-after-slider | atroui | Blocks | Free | no deps | |
| Calendly Embedcalendly-embed | atroui | Blocks | Free | 1 dep | |
| Changelogchangelog | atroui | Blocks | Free | no deps | |
| Command Menucommand-menu | atroui | Blocks | Free | 4 deps | |
| Contact Formcontact-form | atroui | Blocks | Free | 2 deps | |
| Contextual CTAcontextual-cta | atroui | Blocks | Free | 2 deps |
