Schedule Builder
moumenlab/schedule-builderFreeComponent
A recurrence rule as a live English sentence over real occurrences, with month-end and DST traps.
Live preview
live · rendered by the registry
Rendered by moumenlab on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://lab.moumen.dev/r/schedule-builder.jsonSource
1"use client";23import { useEffect, useMemo, useRef, useState } from "react";4import { MotionConfig, motion } from "motion/react";56// Schedule builder - a recurrence rule assembled as a live English sentence,7// with the next N REAL occurrences underneath.8//9// The domain is the hard part: recurrence looks like dropdowns but is full of10// calendar traps, and this build refuses to fake any of them:11//12// · Month boundaries. "Every month on day 31" cannot run in September.13// RRULE semantics SKIP the month (and the list shows a ghost row saying14// so); the `clamp` prop switches to the other real-world policy, where15// day 31 becomes Sep 30.16// · DST. Runs are built from local wall-clock components (year, month,17// day, hour), so "9:00 AM" stays 9:00 AM across a daylight-saving jump -18// the UTC offset is printed on every row and flagged when it differs19// from the first run's.20// · Nth-weekday math. "The 2nd Tuesday" and "the last Friday" are computed21// from the month's first/last day, never by scanning.22//23// The sentence morphs word-by-word: words are keyed by text + occurrence and24// carry motion's `layout` - surviving words glide to their new positions while25// new words blur in. Change "week" to "month" and "at 9:00 AM" slides left26// instead of re-rendering; the sentence reads as one object being edited, not27// a string being replaced.28//29// Read the rule out through `onRuleChange` (it is the component's value).30// Animation via motion/react; honours prefers-reduced-motion. Requires the31// lab-theme tokens. Fully Tailwind, no CSS files.3233const EASE = [0.22, 1, 0.36, 1] as const;3435const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];36const ORDINALS: { value: number | "last"; label: string }[] = [37 { value: 1, label: "1st" },38 { value: 2, label: "2nd" },39 { value: 3, label: "3rd" },40 { value: 4, label: "4th" },41 { value: "last", label: "last" },42];4344export interface ScheduleRule {45 freq: "daily" | "weekly" | "monthly";46 interval: number;47 weekdays: number[];48 monthMode: "date" | "nth";49 monthDay: number;50 ordinal: number | "last";51 weekday: number;52 hour: number;53 minute: number;54}5556export interface ScheduleBuilderState {57 sentence: string;58 next: string | null;59 skips: number;60 dstChanges: number;61 freq: ScheduleRule["freq"];62}6364type RunEntry =65 | { kind: "run"; date: Date; clamped?: boolean }66// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from moumenlab
All 14 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Caret-Anchored Mention Popovercaret-mention-popover | moumenlab | Components | Free | 1 dep | |
| Command Palette with Argument Chipscommand-palette | moumenlab | Components | Free | 1 dep | |
| Drag-to-Reorder Listdrag-to-reorder-list | moumenlab | Components | Free | 1 dep | |
| File Upload Staging Areafile-upload-staging | moumenlab | Components | Free | 1 dep | |
| Hover-Expand Icon Striphover-expand-icon-strip | moumenlab | Components | Free | 1 dep | |
| Inertial Wheel Listinertial-wheel-list | moumenlab | Components | Free | 1 dep | |
| Morphing Checkout Flowmorphing-checkout | moumenlab | Components | Free | 1 dep | |
| OTP Segmented Inputotp-segmented-input | moumenlab | Components | Free | 1 dep |
