import { parse } from "@datasert/cronjs-parser"; import { AlertCircle, Bolt, Check, Code2, Copy, Settings, Type, } from "lucide-react"; import { type FC, useCallback, useEffect, useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/presentation/utils"; import { CronBuilder } from "./cron-builder.js"; import { CronDisplay } from "./cron-display.js"; import { CronInput } from "./cron-input.js"; import { CronMode, type CronPrimitiveMode, type CronProps, type CronValidationResult, } from "./types.js"; const PLACEHOLDER = "0 0 * * * *"; const Cron: FC = ({ value = "", onChange, activeMode = "input", onActiveModeChange, onValidate, className, mode = "both", disabled = false, placeholder = PLACEHOLDER, showPreview = true, showDescription = true, timezone = "UTC", error, children, showHelp = true, displayPeriods, defaultTab, presets, showPresets, withCard = true, isFirstSibling = false, // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: false }) => { const [internalValue, setInternalValue] = useState(value || ""); const [internalActiveMode, setInternalActiveMode] = useState( mode === CronMode.Both ? activeMode : (mode as CronPrimitiveMode) ); const [copied, setCopied] = useState(false); const validationResult = useMemo((): CronValidationResult => { if (!internalValue.trim()) { return { isValid: false, error: "Expression is required", isEmpty: true }; } try { parse(`${internalValue} *`, { hasSeconds: true }); return { isValid: true }; } catch (parseError) { return { isValid: false, error: parseError instanceof Error ? parseError.message : "Invalid cron expression", }; } }, [internalValue]); useEffect(() => { setInternalValue(value || ""); }, [value]); useEffect(() => { onValidate?.(validationResult.isValid); }, [validationResult.isValid, onValidate]); useEffect(() => { if (mode === "both") { setInternalActiveMode(activeMode); } }, [activeMode, mode]); const handleChange = useCallback( (newValue: string) => { setInternalValue(newValue); onChange?.(newValue); }, [onChange] ); const handleActiveModeChange = useCallback( (m: CronPrimitiveMode) => { setInternalActiveMode(m); onActiveModeChange?.(m); }, [onActiveModeChange] ); const handleCopy = useCallback(async () => { if (!internalValue) { return; } try { await navigator.clipboard.writeText(internalValue); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (e) { console.warn("Failed to copy to clipboard:", e); } }, [internalValue]); const hasError = !!error || !!(!validationResult.isValid && internalValue.trim()); if (mode === "input") { return (
{showPreview && (validationResult.isValid || validationResult.isEmpty) && ( )} {children}
); } if (mode === "builder") { return (
{children}
); } return (
Cron Expression Builder Create and validate cron expressions using visual builder or text input
{internalValue && (
{internalValue}
)}
{hasError && (
{error || validationResult.error}
)}
handleActiveModeChange(v as "input" | "builder") } > Text Input Visual Build
{/* Preview Section */} {showPreview && (validationResult.isValid || validationResult.isEmpty) && ( <> {!withCard && } )} {/* Help Section */} {showHelp && ( <> {!withCard && } Cron Expression Format
Second
0-59
Minute
0-59
Hour
0-23
Day
1-31
Month
1-12
Weekday
0-6
* Any value
Matches all possible values
5 Specific value
Matches exactly this value
1-5 Range
Matches values 1 through 5
1,3,5 List
Matches values 1, 3, and 5
*/5 Step
Every 5th value
0-10/2 Range + Step
Even values 0-10
? No specific
Used when day/weekday conflicts
L Last
Last day of month/week

Common Examples:

0 0 * * * * Every hour
0 */15 * * * * Every 15 minutes
0 0 0 * * * Daily at midnight
0 30 9 * * 1-5 Weekdays at 9:30 AM
)} {children}
); }; export { Cron };