@rapidmx/web-client 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/admin/audit-log/index.tsx +35 -8
- package/apps/admin/data-requests/index.tsx +480 -485
- package/apps/admin/distribution-lists/[uid].tsx +19 -1
- package/apps/admin/escrow-scopes/[uid].tsx +348 -237
- package/apps/admin/escrow-scopes/new/index.tsx +8 -2
- package/apps/admin/index.tsx +19 -1
- package/apps/admin/ingest-queue/index.tsx +44 -5
- package/apps/admin/mailboxes/[uid].tsx +211 -184
- package/apps/admin/quarantine/index.tsx +46 -14
- package/apps/admin/transport-rules/[uid].tsx +30 -1
- package/apps/admin/transport-rules/_transportRuleConfig.tsx +38 -2
- package/apps/admin/transport-rules/index.tsx +24 -6
- package/apps/admin/transport-rules/new/index.tsx +30 -1
- package/apps/escrow/_layout.tsx +2 -3
- package/apps/escrow/audit-log/index.tsx +196 -168
- package/apps/escrow/matters/[uid].tsx +621 -521
- package/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.tsx +22 -1
- package/apps/shared/components/admin/layout/AdminShell.tsx +3 -1
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -0
- package/apps/shared/components/admin/mailboxes/ResourceSettingsCard.tsx +3 -3
- package/apps/shared/components/admin/mailboxes/ShareAccessCard.tsx +55 -11
- package/apps/shared/components/admin/settings/BrandingForm.tsx +15 -4
- package/apps/shared/components/admin/settings/DomainDnsSetup.tsx +18 -7
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +10 -2
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +6 -0
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +45 -12
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1235 -595
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +174 -98
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +49 -13
- package/apps/shared/components/admin/setup/SetupWizard.tsx +150 -34
- package/apps/shared/components/admin/signOut.ts +79 -0
- package/apps/shared/components/admin/usePagedList.tsx +129 -0
- package/apps/shared/components/calendar/EventModal.tsx +717 -549
- package/apps/shared/components/calendar/MonthView.tsx +173 -139
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +222 -180
- package/apps/shared/components/calendar/SplitDayView.tsx +143 -137
- package/apps/shared/components/calendar/TimeGridView.tsx +244 -198
- package/apps/shared/components/calendar/allDay.ts +124 -0
- package/apps/shared/components/calendar/layout/CalendarShell.tsx +1 -1
- package/apps/shared/components/contacts/ContactDetailPane.tsx +100 -21
- package/apps/shared/components/contacts/ContactForm.tsx +377 -348
- package/apps/shared/components/contacts/ContactsSidebar.tsx +6 -2
- package/apps/shared/components/contacts/KeyChangeReview.tsx +192 -0
- package/apps/shared/components/contacts/contactKeys.ts +78 -0
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +148 -134
- package/apps/shared/components/layout/AppShell.tsx +87 -15
- package/apps/shared/components/layout/BrandingChrome.tsx +48 -10
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +389 -298
- package/apps/shared/components/layout/MailboxProvisioning.tsx +19 -3
- package/apps/shared/components/layout/RecoveryCodeUnlock.tsx +350 -0
- package/apps/shared/components/layout/UnlockPromptProvider.tsx +257 -128
- package/apps/shared/components/mail/ConversationThreadPane.tsx +216 -181
- package/apps/shared/components/mail/MessageDetailPane.tsx +1318 -630
- package/apps/shared/components/mail/compose/ComposeContext.tsx +20 -15
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +854 -132
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +12 -2
- package/apps/shared/components/mail/pinnedSigners.ts +124 -0
- package/apps/shared/components/mail/verificationSeals.ts +125 -0
- package/apps/shared/components/mail/writableMailboxes.ts +101 -0
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -276
- package/apps/shared/mail/listAllPages.ts +39 -0
- package/apps/shared/search/LocalIndexLifecycle.tsx +64 -55
- package/apps/shared/search/localIndexBuilder.ts +481 -179
- package/apps/shared/search/localIndexRpcClient.ts +228 -28
- package/apps/shared/search/localIndexSchema.ts +15 -6
- package/apps/shared/search/localIndexStorage.ts +98 -0
- package/apps/shared/search/localIndexVFS.ts +32 -1
- package/apps/shared/search/localIndexWorker.ts +648 -186
- package/apps/www/calendar/index.tsx +438 -417
- package/apps/www/contacts/[uid].tsx +21 -2
- package/apps/www/contacts/index.tsx +116 -12
- package/apps/www/index.tsx +1314 -1041
- package/apps/www/messages/[uid].tsx +18 -6
- package/apps/www/settings/auto-reply/index.tsx +134 -129
- package/apps/www/settings/booking-types/[uid].tsx +306 -300
- package/apps/www/settings/encryption/index.tsx +739 -216
- package/apps/www/settings/filters/[uid].tsx +171 -155
- package/apps/www/settings/filters/new/index.tsx +8 -1
- package/apps/www/settings/focused-inbox/index.tsx +150 -148
- package/apps/www/settings/privacy/index.tsx +494 -436
- package/apps/www/settings/read-receipts/index.tsx +148 -125
- package/apps/www/settings/sharing/index.tsx +17 -14
- package/apps/www/tasks/index.tsx +638 -579
- package/dist/apps/admin/audit-log/index.js +38 -8
- package/dist/apps/admin/data-requests/index.js +52 -72
- package/dist/apps/admin/distribution-lists/[uid].js +1 -1
- package/dist/apps/admin/escrow-scopes/[uid].js +91 -12
- package/dist/apps/admin/escrow-scopes/new/index.js +8 -3
- package/dist/apps/admin/index.js +4 -1
- package/dist/apps/admin/ingest-queue/index.js +24 -6
- package/dist/apps/admin/mailboxes/[uid].js +20 -4
- package/dist/apps/admin/quarantine/index.js +20 -11
- package/dist/apps/admin/transport-rules/[uid].js +18 -2
- package/dist/apps/admin/transport-rules/_transportRuleConfig.js +19 -0
- package/dist/apps/admin/transport-rules/index.js +22 -6
- package/dist/apps/admin/transport-rules/new/index.js +18 -2
- package/dist/apps/escrow/_layout.js +3 -3
- package/dist/apps/escrow/audit-log/index.js +26 -1
- package/dist/apps/escrow/matters/[uid].js +77 -42
- package/dist/apps/shared/components/admin/escrowScopes/EscrowScopeKeyAndHoldersFields.js +11 -2
- package/dist/apps/shared/components/admin/layout/AdminShell.js +3 -1
- package/dist/apps/shared/components/admin/mailboxes/EscrowScopeCard.js +72 -0
- package/dist/apps/shared/components/admin/mailboxes/ResourceSettingsCard.js +3 -3
- package/dist/apps/shared/components/admin/mailboxes/ShareAccessCard.js +22 -4
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +15 -4
- package/dist/apps/shared/components/admin/settings/DomainDnsSetup.js +13 -7
- package/dist/apps/shared/components/admin/settings/EncryptionPolicyForm.js +7 -2
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +6 -0
- package/dist/apps/shared/components/admin/settings/MailboxPolicyForm.js +34 -12
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +390 -78
- package/dist/apps/shared/components/admin/settings/RetentionPolicyForm.js +40 -11
- package/dist/apps/shared/components/admin/setup/EscrowSetupStep.js +29 -13
- package/dist/apps/shared/components/admin/setup/SetupWizard.js +87 -21
- package/dist/apps/shared/components/admin/signOut.js +75 -0
- package/dist/apps/shared/components/admin/usePagedList.js +99 -0
- package/dist/apps/shared/components/calendar/EventModal.js +142 -21
- package/dist/apps/shared/components/calendar/MonthView.js +12 -5
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +40 -5
- package/dist/apps/shared/components/calendar/SplitDayView.js +10 -4
- package/dist/apps/shared/components/calendar/TimeGridView.js +23 -7
- package/dist/apps/shared/components/calendar/allDay.js +111 -0
- package/dist/apps/shared/components/calendar/layout/CalendarShell.js +1 -1
- package/dist/apps/shared/components/contacts/ContactDetailPane.js +43 -5
- package/dist/apps/shared/components/contacts/ContactForm.js +32 -10
- package/dist/apps/shared/components/contacts/ContactsSidebar.js +2 -2
- package/dist/apps/shared/components/contacts/KeyChangeReview.js +63 -0
- package/dist/apps/shared/components/contacts/contactKeys.js +64 -0
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +24 -5
- package/dist/apps/shared/components/layout/AppShell.js +83 -14
- package/dist/apps/shared/components/layout/BrandingChrome.js +45 -10
- package/dist/apps/shared/components/layout/KeyEnrollmentGate.js +49 -12
- package/dist/apps/shared/components/layout/MailboxProvisioning.js +10 -2
- package/dist/apps/shared/components/layout/RecoveryCodeUnlock.js +174 -0
- package/dist/apps/shared/components/layout/UnlockPromptProvider.js +73 -14
- package/dist/apps/shared/components/mail/ConversationThreadPane.js +50 -12
- package/dist/apps/shared/components/mail/MessageDetailPane.js +441 -36
- package/dist/apps/shared/components/mail/compose/ComposeContext.js +9 -6
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +699 -128
- package/dist/apps/shared/components/mail/compose/composeFlushRegistry.js +50 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +12 -3
- package/dist/apps/shared/components/mail/pinnedSigners.js +98 -0
- package/dist/apps/shared/components/mail/verificationSeals.js +112 -0
- package/dist/apps/shared/components/mail/writableMailboxes.js +87 -0
- package/dist/apps/shared/components/rules/RuleBuilder.js +34 -6
- package/dist/apps/shared/mail/listAllPages.js +25 -0
- package/dist/apps/shared/search/LocalIndexLifecycle.js +58 -54
- package/dist/apps/shared/search/localIndexBuilder.js +292 -37
- package/dist/apps/shared/search/localIndexRpcClient.js +188 -25
- package/dist/apps/shared/search/localIndexSchema.js +12 -6
- package/dist/apps/shared/search/localIndexStorage.js +86 -0
- package/dist/apps/shared/search/localIndexVFS.js +68 -40
- package/dist/apps/shared/search/localIndexWorker.js +519 -173
- package/dist/apps/www/calendar/index.js +28 -10
- package/dist/apps/www/contacts/[uid].js +13 -2
- package/dist/apps/www/contacts/index.js +80 -10
- package/dist/apps/www/index.js +339 -108
- package/dist/apps/www/messages/[uid].js +18 -6
- package/dist/apps/www/settings/auto-reply/index.js +9 -4
- package/dist/apps/www/settings/booking-types/[uid].js +17 -12
- package/dist/apps/www/settings/encryption/index.js +514 -97
- package/dist/apps/www/settings/filters/[uid].js +22 -7
- package/dist/apps/www/settings/filters/new/index.js +8 -2
- package/dist/apps/www/settings/focused-inbox/index.js +3 -1
- package/dist/apps/www/settings/privacy/index.js +57 -22
- package/dist/apps/www/settings/read-receipts/index.js +10 -3
- package/dist/apps/www/settings/sharing/index.js +7 -11
- package/dist/apps/www/tasks/index.js +50 -10
- package/package.json +2 -2
|
@@ -1,180 +1,222 @@
|
|
|
1
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
-
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
-
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
-
import React from "react";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
"text-sm py-2 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return "
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { format } from "date-fns";
|
|
7
|
+
import { RecurrenceFrequency, RecurrenceRule, WeekdayCode } from "@rapidmx/react-shared/calendar/calendarApi.js";
|
|
8
|
+
import { WEEKDAY_CODES, WEEKDAY_LABELS, describeRecurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
9
|
+
import { recurrenceUntilDateKey, recurrenceUntilInstant } from "./allDay.js";
|
|
10
|
+
|
|
11
|
+
const SELECT_CLASS =
|
|
12
|
+
"text-sm py-2 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
13
|
+
const INPUT_CLASS =
|
|
14
|
+
"text-sm py-2 px-2 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
|
|
15
|
+
|
|
16
|
+
const FREQ_LABEL: Record<RecurrenceFrequency, { unit: string; unitPlural: string }> = {
|
|
17
|
+
daily: { unit: "day", unitPlural: "days" },
|
|
18
|
+
weekly: { unit: "week", unitPlural: "weeks" },
|
|
19
|
+
monthly: { unit: "month", unitPlural: "months" },
|
|
20
|
+
yearly: { unit: "year", unitPlural: "years" },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type EndCondition = "never" | "count" | "until";
|
|
24
|
+
|
|
25
|
+
function endConditionOf(rule: RecurrenceRule): EndCondition {
|
|
26
|
+
if (rule.count) return "count";
|
|
27
|
+
if (rule.until) return "until";
|
|
28
|
+
return "never";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RecurrenceEditorProps {
|
|
32
|
+
/** `null` means "does not repeat". */
|
|
33
|
+
value: RecurrenceRule | null;
|
|
34
|
+
onChange: (value: RecurrenceRule | null) => void;
|
|
35
|
+
/** Whether the event is all-day - its series expands in UTC, so the inclusive "Ends on" date is stored as
|
|
36
|
+
* the end of that UTC day rather than the local one (see `allDay.ts`'s `recurrenceUntilInstant()`). */
|
|
37
|
+
allDay?: boolean;
|
|
38
|
+
/** The weekday the event starts on (in the frame its series expands in - see `allDay.ts`'s
|
|
39
|
+
* `startWeekdayCode()`): the day a new weekly rule, or one switched to Weekly, repeats on. Defaults to Monday. */
|
|
40
|
+
startWeekday?: WeekdayCode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A recurring-event editor: frequency + interval, a weekday picker (weekly only), and an end
|
|
45
|
+
* condition (never / after N occurrences / on a date) — everything `RecurrenceRule` can express. The
|
|
46
|
+
* live "every ... until/for ..." summary comes from `describeRecurrence()` (built on `rrule`'s own
|
|
47
|
+
* `.toText()`), so it never drifts out of sync with what will actually be submitted.
|
|
48
|
+
*/
|
|
49
|
+
export default function RecurrenceEditor({ value, onChange, allDay = false, startWeekday = "MO" }: RecurrenceEditorProps) {
|
|
50
|
+
function handleEnable(enabled: boolean) {
|
|
51
|
+
onChange(enabled ? { freq: "weekly", interval: 1, byDay: [startWeekday], exceptions: [] } : null);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// `update`/`toggleDay` are only ever invoked from handlers rendered inside the `{value && (...)}`
|
|
55
|
+
// block below, so `value` is always non-null here — no defensive null check needed.
|
|
56
|
+
function update(patch: Partial<RecurrenceRule>) {
|
|
57
|
+
onChange({ ...(value as RecurrenceRule), ...patch });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// A weekly rule always keeps at least one weekday: with none left, rrule would repeat every day instead.
|
|
61
|
+
function toggleDay(day: WeekdayCode) {
|
|
62
|
+
const current = (value as RecurrenceRule).byDay ?? [];
|
|
63
|
+
if (current.length === 1 && current[0] === day) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const next = current.includes(day) ? current.filter((d) => d !== day) : [...current, day];
|
|
67
|
+
update({ byDay: next });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// react-shared passes `byDay` to rrule for every frequency, so a weekday list left over from Weekly would
|
|
71
|
+
// turn "every day" into "every Monday". Leaving Weekly drops it; entering Weekly starts on the event's own
|
|
72
|
+
// start weekday (a select only reports an actual change, so this is never Weekly -> Weekly).
|
|
73
|
+
function handleFrequency(freq: RecurrenceFrequency) {
|
|
74
|
+
update({ freq, byDay: freq === "weekly" ? [startWeekday] : undefined });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleEndCondition(condition: EndCondition) {
|
|
78
|
+
if (condition === "never") {
|
|
79
|
+
update({ count: undefined, until: undefined });
|
|
80
|
+
} else if (condition === "count") {
|
|
81
|
+
update({ count: 10, until: undefined });
|
|
82
|
+
} else {
|
|
83
|
+
update({ until: recurrenceUntilInstant(format(new Date(), "yyyy-MM-dd"), allDay), count: undefined });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A positive whole number from a number input, or `null` while it's empty (a user clearing the field
|
|
88
|
+
* to type a new value) — the rule keeps its previous value then, rather than becoming 0/NaN. */
|
|
89
|
+
function parsePositive(raw: string): number | null {
|
|
90
|
+
if (raw.trim() === "") return null;
|
|
91
|
+
const n = Math.floor(Number(raw));
|
|
92
|
+
return n >= 1 ? n : 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function handleInterval(raw: string) {
|
|
96
|
+
const interval = parsePositive(raw);
|
|
97
|
+
if (interval !== null) update({ interval });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function handleCount(raw: string) {
|
|
101
|
+
const count = parsePositive(raw);
|
|
102
|
+
if (count !== null) update({ count });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleUntil(raw: string) {
|
|
106
|
+
// A cleared (or partially typed) date input reports "" — keep the previous end date.
|
|
107
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) update({ until: recurrenceUntilInstant(raw, allDay) });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className="flex flex-col gap-3">
|
|
112
|
+
<label className="flex items-center gap-2 text-sm font-medium">
|
|
113
|
+
<input type="checkbox" checked={value !== null} onChange={(e) => handleEnable(e.target.checked)} />
|
|
114
|
+
Repeats
|
|
115
|
+
</label>
|
|
116
|
+
|
|
117
|
+
{value && (
|
|
118
|
+
<div className="flex flex-col gap-3 pl-6 border-l-2 border-border">
|
|
119
|
+
<div className="flex items-center gap-2 text-sm">
|
|
120
|
+
<span>Every</span>
|
|
121
|
+
<input
|
|
122
|
+
type="number"
|
|
123
|
+
min={1}
|
|
124
|
+
className={`${INPUT_CLASS} w-16`}
|
|
125
|
+
value={value.interval}
|
|
126
|
+
onChange={(e) => handleInterval(e.target.value)}
|
|
127
|
+
aria-label="Recurrence interval"
|
|
128
|
+
/>
|
|
129
|
+
<select
|
|
130
|
+
className={SELECT_CLASS}
|
|
131
|
+
value={value.freq}
|
|
132
|
+
onChange={(e) => handleFrequency(e.target.value as RecurrenceFrequency)}
|
|
133
|
+
aria-label="Recurrence frequency"
|
|
134
|
+
>
|
|
135
|
+
<option value="daily">{value.interval === 1 ? FREQ_LABEL.daily.unit : FREQ_LABEL.daily.unitPlural}</option>
|
|
136
|
+
<option value="weekly">{value.interval === 1 ? FREQ_LABEL.weekly.unit : FREQ_LABEL.weekly.unitPlural}</option>
|
|
137
|
+
<option value="monthly">{value.interval === 1 ? FREQ_LABEL.monthly.unit : FREQ_LABEL.monthly.unitPlural}</option>
|
|
138
|
+
<option value="yearly">{value.interval === 1 ? FREQ_LABEL.yearly.unit : FREQ_LABEL.yearly.unitPlural}</option>
|
|
139
|
+
</select>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{value.freq === "weekly" && (
|
|
143
|
+
<div className="flex gap-1">
|
|
144
|
+
{WEEKDAY_CODES.map((day) => (
|
|
145
|
+
<button
|
|
146
|
+
key={day}
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={() => toggleDay(day)}
|
|
149
|
+
aria-pressed={(value.byDay ?? []).includes(day)}
|
|
150
|
+
aria-label={WEEKDAY_LABELS[day]}
|
|
151
|
+
className={[
|
|
152
|
+
"w-9 h-9 rounded-full text-xs font-semibold border",
|
|
153
|
+
(value.byDay ?? []).includes(day)
|
|
154
|
+
? "bg-primary text-white border-primary"
|
|
155
|
+
: "bg-surface text-text-muted border-border hover:border-primary",
|
|
156
|
+
].join(" ")}
|
|
157
|
+
>
|
|
158
|
+
<span aria-hidden="true">{WEEKDAY_LABELS[day][0]}</span>
|
|
159
|
+
</button>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
<fieldset className="flex flex-col gap-1.5 text-sm">
|
|
165
|
+
<legend className="text-xs font-bold uppercase tracking-wide text-text-muted mb-1">Ends</legend>
|
|
166
|
+
<label className="flex items-center gap-2">
|
|
167
|
+
<input
|
|
168
|
+
type="radio"
|
|
169
|
+
name="recurrence-end"
|
|
170
|
+
checked={endConditionOf(value) === "never"}
|
|
171
|
+
onChange={() => handleEndCondition("never")}
|
|
172
|
+
/>
|
|
173
|
+
Never
|
|
174
|
+
</label>
|
|
175
|
+
<label className="flex items-center gap-2">
|
|
176
|
+
<input
|
|
177
|
+
type="radio"
|
|
178
|
+
name="recurrence-end"
|
|
179
|
+
aria-label="After"
|
|
180
|
+
checked={endConditionOf(value) === "count"}
|
|
181
|
+
onChange={() => handleEndCondition("count")}
|
|
182
|
+
/>
|
|
183
|
+
After
|
|
184
|
+
<input
|
|
185
|
+
type="number"
|
|
186
|
+
min={1}
|
|
187
|
+
className={`${INPUT_CLASS} w-16`}
|
|
188
|
+
value={value.count ?? 10}
|
|
189
|
+
disabled={endConditionOf(value) !== "count"}
|
|
190
|
+
onChange={(e) => handleCount(e.target.value)}
|
|
191
|
+
aria-label="Number of occurrences"
|
|
192
|
+
/>
|
|
193
|
+
occurrences
|
|
194
|
+
</label>
|
|
195
|
+
<label className="flex items-center gap-2">
|
|
196
|
+
<input
|
|
197
|
+
type="radio"
|
|
198
|
+
name="recurrence-end"
|
|
199
|
+
aria-label="On"
|
|
200
|
+
checked={endConditionOf(value) === "until"}
|
|
201
|
+
onChange={() => handleEndCondition("until")}
|
|
202
|
+
/>
|
|
203
|
+
On
|
|
204
|
+
<input
|
|
205
|
+
type="date"
|
|
206
|
+
className={INPUT_CLASS}
|
|
207
|
+
value={value.until ? recurrenceUntilDateKey(value.until, allDay) : ""}
|
|
208
|
+
disabled={endConditionOf(value) !== "until"}
|
|
209
|
+
onChange={(e) => handleUntil(e.target.value)}
|
|
210
|
+
aria-label="End date"
|
|
211
|
+
/>
|
|
212
|
+
</label>
|
|
213
|
+
</fieldset>
|
|
214
|
+
|
|
215
|
+
{(value.freq !== "weekly" || (value.byDay ?? []).length > 0) && (
|
|
216
|
+
<p className="text-xs text-text-muted">Repeats {describeRecurrence(value)}.</p>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|