This component no longer exists. It was in ruixen-ui’s manifest when we first indexed it and is not there now — the registry served its inventory on 2026-08-12 and this item was not on it. The install command below will fail. It is kept here because a tutorial somewhere still tells people to run it.
Calendar Twin
ruixen-ui/calendar-twinRemovedComponent
Dual-month range picker — click start, hover to preview, click end. Continuous band with smart rounding.
Live preview
live · rendered by the registry
Rendered by ruixen-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://ruixen.com/r/calendar-twin.jsonSource
1"use client";23import { useRef, useState, useCallback } from "react";4import { motion, AnimatePresence } from "motion/react";5import { cn } from "@/lib/utils";67/**8 * Calendar Twin — dual-month range picker.9 *10 * Two months sit side by side. Click a day to start11 * a range, hover to preview, click again to confirm.12 * A continuous band connects start to end — rounding13 * at row edges, brightening at endpoints.14 *15 * The band IS the selection.16 */1718/* ── Types ── */1920export interface CalendarTwinProps {21 defaultStart?: string;22 defaultEnd?: string;23 onRangeChange?: (start: string | null, end: string | null) => void;24 sound?: boolean;25}2627/* ── Constants ── */2829const CELL = 36;30const DOW = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];3132/* ── Helpers ── */3334function pad2(n: number): string {35 return String(n).padStart(2, "0");36}3738function toKey(y: number, m: number, d: number): string {39 return `${y}-${pad2(m + 1)}-${pad2(d)}`;40}4142function parseKey(key: string): [number, number, number] {43 const [y, m, d] = key.split("-").map(Number);44 return [y, m - 1, d];45}4647function formatDate(key: string): string {48 const [y, m, d] = parseKey(key);49 return new Date(y, m, d).toLocaleDateString("en-US", {50 month: "short",51 day: "numeric",52 });53}5455function daysBetween(a: string, b: string): number {56 const [ay, am, ad] = parseKey(a);57 const [by, bm, bd] = parseKey(b);58 const da = new Date(ay, am, ad);59 const db = new Date(by, bm, bd);60 return Math.round((db.getTime() - da.getTime()) / 86400000) + 1;61}6263function ordered(a: string, b: string): [string, string] {64 return a <= b ? [a, b] : [b, a];65}6667/* ── Audio ── */6869let _ctx: AudioContext | null = null;70let _buf: AudioBuffer | null = null;7172function audioCtx() {73 if (!_ctx) {74 _ctx = new (window.AudioContext ||75 (window as unknown as { webkitAudioContext: typeof AudioContext })76 .webkitAudioContext)();77 }78 if (_ctx.state === "suspended") _ctx.resume();79 return _ctx;80}8182function ensureBuf(ac: AudioContext): AudioBuffer {83 if (_buf && _buf.sampleRate === ac.sampleRate) return _buf;84 const rate = ac.sampleRate;85 const len = Math.floor(rate * 0.003);86 const buf = ac.createBuffer(1, len, rate);87 const ch = buf.getChannelData(0);88 for (let i = 0; i < len; i++) {89 const t = i / len;90 ch[i] = (Math.random() * 2 - 1) * (1 - t) ** 4;91 }92 _buf = buf;93 return buf;94}9596function playTick(last: React.MutableRefObject<number>) {97// … truncated
What it pulls in
npm packages
