style: format recurrence helpers

This commit is contained in:
2026-04-10 15:40:56 -04:00
parent f3350e0124
commit 42989b1437
5 changed files with 106 additions and 43 deletions

View File

@@ -15,11 +15,11 @@ import {
getRecurrencePreview,
getWeekdayOptions,
parseRecurrenceRule,
type RecurrenceFormValue,
recurrenceFrequencyLabels,
type SupportedRecurrenceFrequency,
serializeRecurrenceRule,
validateRecurrence,
type RecurrenceFormValue,
type SupportedRecurrenceFrequency,
type Weekday,
} from "@/lib/recurrence";
@@ -39,10 +39,7 @@ const getStartWeekday = (start?: string): Weekday => {
return weekdays[jsDay] ?? "MO";
};
const updateWeekdays = (
current: Weekday[],
day: Weekday,
): Weekday[] => {
const updateWeekdays = (current: Weekday[], day: Weekday): Weekday[] => {
return current.includes(day)
? current.filter((existingDay) => existingDay !== day)
: [...current, day];
@@ -95,11 +92,13 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(recurrenceFrequencyLabels).map(([optionValue, label]) => (
<SelectItem key={optionValue} value={optionValue}>
{label}
</SelectItem>
))}
{Object.entries(recurrenceFrequencyLabels).map(
([optionValue, label]) => (
<SelectItem key={optionValue} value={optionValue}>
{label}
</SelectItem>
),
)}
</SelectContent>
</Select>
</div>
@@ -108,7 +107,12 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
<>
<div>
<Label htmlFor="interval" className="text-xs text-muted-foreground">
Interval (every {recurrence.interval} {recurrence.freq === "DAILY" ? "day" : recurrence.freq === "WEEKLY" ? "week" : "month"}
Interval (every {recurrence.interval}{" "}
{recurrence.freq === "DAILY"
? "day"
: recurrence.freq === "WEEKLY"
? "week"
: "month"}
{recurrence.interval > 1 ? "s" : ""})
</Label>
<Input
@@ -117,7 +121,9 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
min={1}
value={recurrence.interval}
onChange={(event) =>
update({ interval: Number.parseInt(event.target.value, 10) || 1 })
update({
interval: Number.parseInt(event.target.value, 10) || 1,
})
}
className="mt-1.5 w-24"
/>
@@ -125,7 +131,9 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
{recurrence.freq === "WEEKLY" && (
<div>
<Label className="text-xs text-muted-foreground">Days of the week</Label>
<Label className="text-xs text-muted-foreground">
Days of the week
</Label>
<div className="mt-1.5 flex flex-wrap gap-3">
{weekdayOptions.map(({ value: day, label }) => (
<div key={day} className="flex items-center gap-1.5">
@@ -136,7 +144,10 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
update({ byDay: updateWeekdays(recurrence.byDay, day) })
}
/>
<Label htmlFor={day} className="cursor-pointer text-xs font-normal">
<Label
htmlFor={day}
className="cursor-pointer text-xs font-normal"
>
{label}
</Label>
</div>
@@ -186,10 +197,14 @@ export function RecurrencePicker({ value, start, onChange }: Props) {
</div>
{validation.errors.count && (
<p className="text-xs text-destructive">{validation.errors.count}</p>
<p className="text-xs text-destructive">
{validation.errors.count}
</p>
)}
{validation.errors.until && (
<p className="text-xs text-destructive">{validation.errors.until}</p>
<p className="text-xs text-destructive">
{validation.errors.until}
</p>
)}
{validation.errors.rule && (
<p className="text-xs text-destructive">{validation.errors.rule}</p>

View File

@@ -1,6 +1,10 @@
import { format, parseISO } from "date-fns";
import { Badge } from "@/components/ui/badge";
import { formatRecurrenceText, getRecurrencePreview, parseRecurrenceRule } from "@/lib/recurrence";
import {
formatRecurrenceText,
getRecurrencePreview,
parseRecurrenceRule,
} from "@/lib/recurrence";
interface RRuleDisplayProps {
rrule?: string;
@@ -12,17 +16,25 @@ export function RRuleDisplay({ rrule, className, start }: RRuleDisplayProps) {
if (!rrule) return null;
const humanText = formatRecurrenceText(rrule);
const preview = start ? getRecurrencePreview(parseRecurrenceRule(rrule), start, 3) : [];
const preview = start
? getRecurrencePreview(parseRecurrenceRule(rrule), start, 3)
: [];
return (
<div className={className}>
<div className="flex flex-wrap items-center gap-1.5">
<Badge variant="secondary" className="h-5 text-[10px] font-normal capitalize">
<Badge
variant="secondary"
className="h-5 text-[10px] font-normal capitalize"
>
{humanText ?? rrule}
</Badge>
{preview.length > 0 && (
<Badge variant="outline" className="h-5 text-[10px] font-normal">
Next: {preview.map((value) => format(parseISO(value), "MMM d")).join(", ")}
Next:{" "}
{preview
.map((value) => format(parseISO(value), "MMM d"))
.join(", ")}
</Badge>
)}
</div>