Nasaq

CronBuilder

A schedule editor with simple settings or a cron expression, a plain-language reading in English and Arabic, field-level validation, and the next runs in a chosen time zone. Ships a schedule list with ran, failed and missed status.

PreviewOpen ↗

Code

import { CronBuilder } from "@fadymondy/nasaq/web";import { useState } from "react";export function Trigger() {  const [cron, setCron] = useState("0 9 * * 1-5");  const [zone, setZone] = useState("Asia/Riyadh");  return <CronBuilder value={cron} onValueChange={(v) => setCron(v)} timeZone={zone} onTimeZoneChange={setZone} />;}

Workflow · beta

Live examples and controls: CronBuilder in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/cron-builder.json

Pick "every weekday at 09:00" from simple settings, or type the cron expression itself. Either way the builder says the schedule in words (English or Arabic), names the field that is wrong when the expression does not parse, and lists the next runs in the time zone you chose, so nobody has to trust their own reading of 0 9 * * 1-5. The value is always the cron string. The parser is built in (no cron library): five fields, *, lists, ranges, steps, month and day names, @daily style macros, daylight saving gaps skipped. CronScheduleList is the companion list of saved schedules.

When to use

  • A trigger step in a workflow, a backup or report schedule, any recurring job the user configures.
  • A settings page listing schedules with their last outcome (CronScheduleList).

When not to use

  • Picking a single date and time: use date-picker. Calendar-style booking: use scheduler.
  • Second-level or Quartz seven-field expressions: only the five standard fields are supported.

Import

import { CronBuilder, CronScheduleList } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { CronBuilder } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Trigger() {
  const [cron, setCron] = useState("0 9 * * 1-5");
  const [zone, setZone] = useState("Asia/Riyadh");
  return <CronBuilder value={cron} onValueChange={(v) => setCron(v)} timeZone={zone} onTimeZoneChange={setZone} />;
}

onValueChange fires on every keystroke with valid, so a half-typed expression is still the value. Check valid (or call isValidCron) before you save.

Anatomy

CronBuilder                     data-slot="cron-builder"
├─ presets                      Buttons with aria-pressed
├─ Tabs: Simple | Cron expression
│  ├─ simple: repeat, every N, minute, time, weekdays (ToggleGroup), day of month
│  └─ cron: monospace Input, hint, alert with the failing field
├─ summary                      data-slot="cron-summary", aria-live polite
├─ time zone Select
└─ next runs                    data-slot="cron-next-runs"

CronScheduleList                data-slot="cron-schedule-list"
└─ row                          Switch, name and words, last run Status, next run, run now / edit / delete

API

CronBuilder

Every div prop except children, plus:

PropTypeDefaultDescription
value / defaultValuestring"0 9 * * 1-5"The cron expression or a macro.
onValueChange(value: string, valid: boolean) => voidnoneEvery change; valid says whether it parses.
timeZone / defaultTimeZonestring"UTC"IANA zone the schedule is read in. Unknown names fall back to UTC.
onTimeZoneChange(timeZone: string) => voidnoneZone picked.
timeZonesreadonly string[]DEFAULT_TIME_ZONESZones in the picker; the current one is always added.
hideTimeZonebooleanfalseHide the picker.
presetsreadonly CronPreset[] | falsesix common ones{ id, value, label? }.
previewCountnumber5Upcoming runs to list.
nowDate | numbernowThe moment previews count from (use it in tests and stories).
disabledbooleanfalseRead only.
labelstring"Schedule"Accessible name of the group.
labelsPartial<CronBuilderLabels>noneOverride any English or Arabic string.

CronScheduleList

PropTypeDefaultDescription
schedulesreadonly CronSchedule[]required{ id, name, cron, timeZone?, enabled, lastRun?: { at, status: "ok" | "failed" | "missed", message? } }.
onToggle(schedule, enabled) => Promise<void | { error?: string }>nonePause or resume; without it the switch is disabled.
onRunNow / onDelete(schedule) => Promise<void | { error?: string }>noneShow the buttons. Delete asks first.
onEdit(schedule) => voidnoneShows the edit button.
now / loading / labels / classNamenonenonePreview origin, skeleton, string overrides.

Helpers

parseCron(expr) returns { ok: true, value } or { ok: false, error: { code, field, token } }. nextRuns(expr, { from, count, timeZone }) returns Dates. describeCron(expr, "en" | "ar") returns a sentence or null. simpleToCron and cronToSimple convert the simple form. isValidCron, isValidTimeZone and DEFAULT_CRON_PRESETS are exported too.

Examples

Saved schedules with statuses:

import { CronScheduleList } from "@fadymondy/nasaq/web";

export const Schedules = () => (
  <CronScheduleList
    schedules={[
      { id: "1", name: "Weekly report", cron: "0 9 * * 1", timeZone: "Asia/Riyadh", enabled: true, lastRun: { at: Date.now() - 3600_000, status: "ok" } },
      { id: "2", name: "Sync CRM", cron: "*/15 * * * *", enabled: true, lastRun: { at: Date.now() - 900_000, status: "failed" } },
    ]}
    onToggle={async () => {}}
    onRunNow={async () => {}}
  />
);

Accessibility

KeyAction
TabMoves through presets, tabs, fields, days and zone.
Arrow keysSwitch tab; move between weekday toggles.
Space / EnterPress a preset or weekday, open the zone list.

The summary is a polite live region. A wrong expression is announced with role="alert" and aria-invalid. Weekday toggles carry the full day name. Schedule status is an icon plus a word, never colour alone. Localise label and any custom labels.

RTL & i18n

  • Cron text, time zone names and times stay left-to-right (dir="ltr"); the sentence around them follows the language.
  • Digits are Latin, like the rest of Nasaq. Arabic plurals (دقيقتين، 5 دقائق، 15 دقيقة) are handled.
  • The weekday toggles run in reading order, so Sunday sits at the start in both languages.

Styling & tokens

  • Uses --nq-surface-soft, --nq-danger-text, border and card tokens. Extend with className; never pass raw hex.

Do / Don't

  • Do show the time zone the schedule runs in; a schedule without one is a bug waiting for daylight saving.
  • Do show the next runs before saving.
  • Don't accept a value without checking valid.

Lab

https://docs.nasaqui.com/?path=/docs/components-workflow-cron-builder--docs

On this page