1 import { isValid } from 'date-fns';
2 import { c, msgid } from 'ttag';
4 import { DateInput, IntegerInput, Option, SelectTwo } from '@proton/components';
5 import { END_TYPE, FREQUENCY_COUNT_MAX, MAXIMUM_DATE } from '@proton/shared/lib/calendar/constants';
6 import type { WeekStartsOn } from '@proton/shared/lib/date-fns-utc/interface';
7 import type { DateTimeModel, EventModelErrors, FrequencyModel } from '@proton/shared/lib/interfaces/calendar';
8 import clsx from '@proton/utils/clsx';
10 const { NEVER, UNTIL, AFTER_N_TIMES } = END_TYPE;
12 export const UNTIL_ID = 'event-occurrence-until';
13 export const COUNT_ID = 'event-occurrence-count';
16 frequencyModel: FrequencyModel;
18 displayWeekNumbers?: boolean;
19 weekStartsOn: WeekStartsOn;
20 errors: EventModelErrors;
22 onChange: (value: FrequencyModel) => void;
23 displayStacked?: boolean;
33 displayStacked = false,
35 const handleChangeEndType = (type: END_TYPE) => {
36 onChange({ ...frequencyModel, ends: { ...frequencyModel.ends, type } });
38 const handleChangeEndCount = (count: number | undefined) => {
39 if (count !== undefined && (count > FREQUENCY_COUNT_MAX || count < 1)) {
42 onChange({ ...frequencyModel, ends: { ...frequencyModel.ends, count } });
44 const handleChangeEndUntil = (until: Date | undefined) => {
45 if (!until || !isValid(until)) {
48 onChange({ ...frequencyModel, ends: { ...frequencyModel.ends, until } });
51 const safeCountPlural = frequencyModel.ends.count || 1; // Can get undefined through the input
56 text: c('Custom frequency option').t`Never`,
60 text: c('Custom frequency option').t`On date…`,
64 text: c('Custom frequency option').t`After…`,
69 <div className={clsx('flex-1', displayStacked && 'mt-4')}>
71 className={clsx(displayStacked && 'text-semibold')}
72 htmlFor="event-ends-radio"
74 >{c('Label').t`Ends`}</label>
76 <div className="flex flex-nowrap flex-1 flex-column sm:flex-row">
77 <div className="sm:flex-1 mt-2">
79 value={frequencyModel.ends.type}
80 onChange={({ value }) => {
81 const newValue = value as END_TYPE;
82 handleChangeEndType?.(newValue);
84 title={c('Title').t`Select when this event will stop happening`}
85 aria-describedby="label-event-ends"
88 {options.map(({ value, text }) => (
89 <Option key={value} value={value} title={text} />
94 {frequencyModel.ends.type === UNTIL && (
95 <div className="sm:flex-1 mt-2 ml-0 sm:ml-2">
96 <label htmlFor={UNTIL_ID} className="sr-only">{c('Title').t`Select event's last date`}</label>
99 value={frequencyModel.ends.until}
101 defaultDate={start.date}
102 onChange={handleChangeEndUntil}
103 onFocus={() => handleChangeEndType(UNTIL)}
104 displayWeekNumbers={displayWeekNumbers}
105 weekStartsOn={weekStartsOn}
106 aria-invalid={isSubmitted && !!errors.until}
107 isSubmitted={isSubmitted}
109 title={c('Title').t`Select event's last date`}
110 aria-describedby="label-event-ends event-ends"
115 {frequencyModel.ends.type === AFTER_N_TIMES && (
116 <div className="flex flex-nowrap items-center sm:flex-1 mt-2 ml-0 sm:ml-2">
117 <div className="max-w-custom" style={{ '--max-w-custom': '6em' }}>
118 <label htmlFor={COUNT_ID} className="sr-only">{c('Title')
119 .t`Choose how many times this event will repeat`}</label>
122 value={frequencyModel.ends.count}
124 onChange={handleChangeEndCount}
125 onFocus={() => handleChangeEndType(AFTER_N_TIMES)}
127 if (!frequencyModel.ends.count) {
128 handleChangeEndCount(1);
131 aria-invalid={isSubmitted && !!errors.count}
132 isSubmitted={isSubmitted}
133 title={c('Title').t`Choose how many times this event will repeat`}
136 <div className="shrink-0 ml-2">
137 {c('Custom frequency option').ngettext(msgid`time`, `times`, safeCountPlural)}
146 export default EndsRow;