@rapidmx/web-client 0.14.1 → 0.15.1
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/domains/[uid].tsx +3 -2
- package/apps/shared/appearance/AppearanceForm.tsx +9 -1
- package/apps/shared/calendar/bookingPlugin.ts +100 -0
- package/apps/shared/calendar/calendarLiveUpdates.ts +27 -0
- package/apps/shared/components/admin/layout/AdminShell.tsx +37 -5
- package/apps/shared/components/admin/settings/MailboxCreateForm.tsx +3 -1
- package/apps/shared/components/admin/settings/PluginsManager.tsx +1545 -1491
- package/apps/shared/components/calendar/AppointmentQuickForm.tsx +327 -0
- package/apps/shared/components/calendar/DescriptionEditor.tsx +274 -0
- package/apps/shared/components/calendar/EventDescriptionView.tsx +70 -0
- package/apps/shared/components/calendar/EventDetails.tsx +401 -0
- package/apps/shared/components/calendar/EventEditor.tsx +640 -0
- package/apps/shared/components/calendar/EventExpandedForm.tsx +210 -0
- package/apps/shared/components/calendar/EventFormParts.tsx +622 -0
- package/apps/shared/components/calendar/EventModal.tsx +126 -866
- package/apps/shared/components/calendar/EventQuickForm.tsx +116 -0
- package/apps/shared/components/calendar/EventShell.tsx +202 -0
- package/apps/shared/components/calendar/FindATime.tsx +379 -0
- package/apps/shared/components/calendar/LazyDescriptionEditor.tsx +70 -0
- package/apps/shared/components/calendar/MonthView.tsx +15 -3
- package/apps/shared/components/calendar/QuickCreateFaces.tsx +87 -0
- package/apps/shared/components/calendar/QuickCreateTabs.tsx +81 -0
- package/apps/shared/components/calendar/QuickFaceFrame.tsx +81 -0
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +10 -5
- package/apps/shared/components/calendar/RequestChangeForm.tsx +271 -0
- package/apps/shared/components/calendar/SplitDayView.tsx +6 -5
- package/apps/shared/components/calendar/TaskQuickForm.tsx +289 -0
- package/apps/shared/components/calendar/TimeGridView.tsx +8 -6
- package/apps/shared/components/calendar/eventDialogFields.ts +86 -0
- package/apps/shared/components/calendar/eventForm.ts +108 -0
- package/apps/shared/components/calendar/eventFormat.ts +183 -0
- package/apps/shared/components/calendar/findTimeGrid.ts +90 -0
- package/apps/shared/components/calendar/swipeNavigation.ts +14 -0
- package/apps/shared/components/layout/UserMenu.tsx +80 -28
- package/apps/shared/components/mail/ConversationList.tsx +100 -48
- package/apps/shared/components/mail/ConversationThreadPane.tsx +490 -486
- package/apps/shared/components/mail/InviteCard.tsx +387 -0
- package/apps/shared/components/mail/MessageDetailPane.tsx +24 -2
- package/apps/shared/components/mail/SwipeRow.tsx +120 -0
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +7 -3
- package/apps/shared/components/mail/invite/InviteRowChip.tsx +104 -0
- package/apps/shared/components/mail/invite/InviteRsvpPopover.tsx +271 -0
- package/apps/shared/components/mail/invite/InviteTimeline.tsx +200 -0
- package/apps/shared/components/mail/invite/ProposeTimeForm.tsx +109 -0
- package/apps/shared/components/mail/invite/inviteFormat.ts +110 -0
- package/apps/shared/components/mail/invite/inviteStore.ts +114 -0
- package/apps/shared/components/mail/layout/MailShell.tsx +522 -490
- package/apps/shared/components/settings/layout/SettingsShell.tsx +1 -0
- package/apps/shared/gestures/useEnterSlide.ts +54 -0
- package/apps/shared/gestures/useSwipe.ts +114 -0
- package/apps/shared/gestures/useSwipeSlide.ts +113 -0
- package/apps/shared/gestures/useWheelPaging.ts +107 -0
- package/apps/shared/mail/newMailNotifications.ts +171 -183
- package/apps/shared/mail/useNewMailNotifications.ts +178 -178
- package/apps/shared/notifications/preferences.ts +36 -0
- package/apps/shared/notifications/store.ts +560 -551
- package/apps/www/_routes.ts +1 -0
- package/apps/www/calendar/index.tsx +139 -37
- package/apps/www/index.tsx +273 -126
- package/apps/www/messages/[uid].tsx +31 -4
- package/apps/www/settings/profile/index.tsx +248 -0
- package/dist/apps/admin/domains/[uid].js +4 -2
- package/dist/apps/shared/appearance/AppearanceForm.js +9 -1
- package/dist/apps/shared/calendar/bookingPlugin.d.ts +50 -0
- package/dist/apps/shared/calendar/bookingPlugin.js +57 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.d.ts +11 -0
- package/dist/apps/shared/calendar/calendarLiveUpdates.js +23 -0
- package/dist/apps/shared/components/admin/layout/AdminShell.d.ts +1 -1
- package/dist/apps/shared/components/admin/layout/AdminShell.js +7 -3
- package/dist/apps/shared/components/admin/settings/MailboxCreateForm.js +3 -1
- package/dist/apps/shared/components/admin/settings/PluginsManager.js +40 -6
- package/dist/apps/shared/components/calendar/AppointmentQuickForm.d.ts +47 -0
- package/dist/apps/shared/components/calendar/AppointmentQuickForm.js +135 -0
- package/dist/apps/shared/components/calendar/DescriptionEditor.d.ts +26 -0
- package/dist/apps/shared/components/calendar/DescriptionEditor.js +192 -0
- package/dist/apps/shared/components/calendar/EventDescriptionView.d.ts +14 -0
- package/dist/apps/shared/components/calendar/EventDescriptionView.js +39 -0
- package/dist/apps/shared/components/calendar/EventDetails.d.ts +33 -0
- package/dist/apps/shared/components/calendar/EventDetails.js +137 -0
- package/dist/apps/shared/components/calendar/EventEditor.d.ts +59 -0
- package/dist/apps/shared/components/calendar/EventEditor.js +549 -0
- package/dist/apps/shared/components/calendar/EventExpandedForm.d.ts +14 -0
- package/dist/apps/shared/components/calendar/EventExpandedForm.js +44 -0
- package/dist/apps/shared/components/calendar/EventFormParts.d.ts +69 -0
- package/dist/apps/shared/components/calendar/EventFormParts.js +202 -0
- package/dist/apps/shared/components/calendar/EventModal.d.ts +26 -13
- package/dist/apps/shared/components/calendar/EventModal.js +57 -459
- package/dist/apps/shared/components/calendar/EventQuickForm.d.ts +18 -0
- package/dist/apps/shared/components/calendar/EventQuickForm.js +25 -0
- package/dist/apps/shared/components/calendar/EventShell.d.ts +64 -0
- package/dist/apps/shared/components/calendar/EventShell.js +134 -0
- package/dist/apps/shared/components/calendar/FindATime.d.ts +18 -0
- package/dist/apps/shared/components/calendar/FindATime.js +190 -0
- package/dist/apps/shared/components/calendar/LazyDescriptionEditor.d.ts +8 -0
- package/dist/apps/shared/components/calendar/LazyDescriptionEditor.js +44 -0
- package/dist/apps/shared/components/calendar/MonthView.d.ts +5 -1
- package/dist/apps/shared/components/calendar/MonthView.js +11 -5
- package/dist/apps/shared/components/calendar/QuickCreateFaces.d.ts +30 -0
- package/dist/apps/shared/components/calendar/QuickCreateFaces.js +44 -0
- package/dist/apps/shared/components/calendar/QuickCreateTabs.d.ts +24 -0
- package/dist/apps/shared/components/calendar/QuickCreateTabs.js +46 -0
- package/dist/apps/shared/components/calendar/QuickFaceFrame.d.ts +25 -0
- package/dist/apps/shared/components/calendar/QuickFaceFrame.js +19 -0
- package/dist/apps/shared/components/calendar/RecurrenceEditor.d.ts +4 -1
- package/dist/apps/shared/components/calendar/RecurrenceEditor.js +2 -2
- package/dist/apps/shared/components/calendar/RequestChangeForm.d.ts +36 -0
- package/dist/apps/shared/components/calendar/RequestChangeForm.js +138 -0
- package/dist/apps/shared/components/calendar/SplitDayView.d.ts +3 -2
- package/dist/apps/shared/components/calendar/SplitDayView.js +3 -2
- package/dist/apps/shared/components/calendar/TaskQuickForm.d.ts +40 -0
- package/dist/apps/shared/components/calendar/TaskQuickForm.js +101 -0
- package/dist/apps/shared/components/calendar/TimeGridView.d.ts +4 -2
- package/dist/apps/shared/components/calendar/TimeGridView.js +3 -2
- package/dist/apps/shared/components/calendar/eventDialogFields.d.ts +28 -0
- package/dist/apps/shared/components/calendar/eventDialogFields.js +59 -0
- package/dist/apps/shared/components/calendar/eventForm.d.ts +98 -0
- package/dist/apps/shared/components/calendar/eventForm.js +1 -0
- package/dist/apps/shared/components/calendar/eventFormat.d.ts +67 -0
- package/dist/apps/shared/components/calendar/eventFormat.js +163 -0
- package/dist/apps/shared/components/calendar/findTimeGrid.d.ts +30 -0
- package/dist/apps/shared/components/calendar/findTimeGrid.js +83 -0
- package/dist/apps/shared/components/calendar/swipeNavigation.d.ts +9 -0
- package/dist/apps/shared/components/calendar/swipeNavigation.js +8 -0
- package/dist/apps/shared/components/layout/UserMenu.d.ts +10 -8
- package/dist/apps/shared/components/layout/UserMenu.js +70 -23
- package/dist/apps/shared/components/mail/ConversationList.d.ts +12 -1
- package/dist/apps/shared/components/mail/ConversationList.js +21 -4
- package/dist/apps/shared/components/mail/ConversationThreadPane.d.ts +4 -1
- package/dist/apps/shared/components/mail/InviteCard.d.ts +20 -0
- package/dist/apps/shared/components/mail/InviteCard.js +150 -0
- package/dist/apps/shared/components/mail/MessageDetailPane.js +13 -2
- package/dist/apps/shared/components/mail/SwipeRow.d.ts +22 -0
- package/dist/apps/shared/components/mail/SwipeRow.js +71 -0
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +8 -3
- package/dist/apps/shared/components/mail/invite/InviteRowChip.d.ts +23 -0
- package/dist/apps/shared/components/mail/invite/InviteRowChip.js +46 -0
- package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.d.ts +20 -0
- package/dist/apps/shared/components/mail/invite/InviteRsvpPopover.js +141 -0
- package/dist/apps/shared/components/mail/invite/InviteTimeline.d.ts +27 -0
- package/dist/apps/shared/components/mail/invite/InviteTimeline.js +120 -0
- package/dist/apps/shared/components/mail/invite/ProposeTimeForm.d.ts +17 -0
- package/dist/apps/shared/components/mail/invite/ProposeTimeForm.js +45 -0
- package/dist/apps/shared/components/mail/invite/inviteFormat.d.ts +28 -0
- package/dist/apps/shared/components/mail/invite/inviteFormat.js +94 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.d.ts +28 -0
- package/dist/apps/shared/components/mail/invite/inviteStore.js +95 -0
- package/dist/apps/shared/components/mail/layout/MailShell.d.ts +5 -0
- package/dist/apps/shared/components/mail/layout/MailShell.js +14 -3
- package/dist/apps/shared/components/settings/layout/SettingsShell.js +1 -0
- package/dist/apps/shared/gestures/useEnterSlide.d.ts +14 -0
- package/dist/apps/shared/gestures/useEnterSlide.js +47 -0
- package/dist/apps/shared/gestures/useSwipe.d.ts +45 -0
- package/dist/apps/shared/gestures/useSwipe.js +90 -0
- package/dist/apps/shared/gestures/useSwipeSlide.d.ts +40 -0
- package/dist/apps/shared/gestures/useSwipeSlide.js +95 -0
- package/dist/apps/shared/gestures/useWheelPaging.d.ts +32 -0
- package/dist/apps/shared/gestures/useWheelPaging.js +85 -0
- package/dist/apps/shared/mail/newMailNotifications.d.ts +3 -8
- package/dist/apps/shared/mail/newMailNotifications.js +3 -12
- package/dist/apps/shared/mail/useNewMailNotifications.js +3 -2
- package/dist/apps/shared/notifications/preferences.d.ts +13 -0
- package/dist/apps/shared/notifications/preferences.js +36 -0
- package/dist/apps/shared/notifications/store.d.ts +1 -0
- package/dist/apps/shared/notifications/store.js +7 -23
- package/dist/apps/www/_routes.js +1 -0
- package/dist/apps/www/calendar/index.js +76 -6
- package/dist/apps/www/index.js +140 -39
- package/dist/apps/www/messages/[uid].js +13 -5
- package/dist/apps/www/settings/profile/index.d.ts +4 -0
- package/dist/apps/www/settings/profile/index.js +124 -0
- package/package.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import { RecurrenceRule, WeekdayCode } from "@rapidmx/react-shared/calendar/calendarApi.js";
|
|
3
|
+
import { EventFormController } from "./eventForm.js";
|
|
4
|
+
export declare const INPUT_CLASS = "w-full text-sm py-2 px-3 border border-border rounded-md bg-surface text-text focus:outline-none focus:border-primary";
|
|
5
|
+
export declare const SELECT_CLASS = "text-sm py-2 px-2 border border-border rounded-md bg-surface text-text focus:outline-none focus:border-primary";
|
|
6
|
+
/** A form row: an icon in the left gutter and its content. */
|
|
7
|
+
export declare function IconRow({ icon, children }: {
|
|
8
|
+
icon: ReactNode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
/** The location row: a text box that is plain text until it is used. */
|
|
12
|
+
export declare function LocationRow({ c }: {
|
|
13
|
+
c: EventFormController;
|
|
14
|
+
}): React.JSX.Element;
|
|
15
|
+
/** "Add video conferencing" and, once the event has a meeting, the button that joins it. */
|
|
16
|
+
export declare function VideoConferencingRow({ c }: {
|
|
17
|
+
c: EventFormController;
|
|
18
|
+
}): React.JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* The guests field: an address box (Enter, a comma or a semicolon adds what is typed) and what has been added - chips in the quick popover,
|
|
21
|
+
* rows with a role, the answer so far and a remove button in the full form, which also offers rooms and equipment.
|
|
22
|
+
*/
|
|
23
|
+
export declare function GuestsField({ c, compact }: {
|
|
24
|
+
c: EventFormController;
|
|
25
|
+
compact: boolean;
|
|
26
|
+
}): React.JSX.Element;
|
|
27
|
+
/** The guests row of the quick popover. */
|
|
28
|
+
export declare function GuestsRow({ c }: {
|
|
29
|
+
c: EventFormController;
|
|
30
|
+
}): React.JSX.Element;
|
|
31
|
+
/** The reminder row: the one notification an event has - an amount, its unit and a way to remove it - or "Add notification". */
|
|
32
|
+
export declare function ReminderRow({ value, onChange }: {
|
|
33
|
+
value: string;
|
|
34
|
+
onChange: (minutes: string) => void;
|
|
35
|
+
}): React.JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* "Does not repeat" and the plain repeats as a menu, with "Custom…" opening `RecurrenceEditor`'s full set (an interval, weekdays, an end).
|
|
38
|
+
* Choosing one of the plain repeats keeps the rule's skipped dates; the menu shows "Custom…" for any rule it has no plain entry for.
|
|
39
|
+
*/
|
|
40
|
+
export declare function RecurrenceSelect({ value, onChange, allDay, startWeekday, }: {
|
|
41
|
+
value: RecurrenceRule | null;
|
|
42
|
+
onChange: (rule: RecurrenceRule | null) => void;
|
|
43
|
+
allDay: boolean;
|
|
44
|
+
startWeekday: WeekdayCode | undefined;
|
|
45
|
+
}): React.JSX.Element;
|
|
46
|
+
/** [start date] [start time] to [end time] [end date], then "All day", the "Time zone" link (and its menu) and how the event repeats. */
|
|
47
|
+
export declare function DateTimeControls({ c }: {
|
|
48
|
+
c: EventFormController;
|
|
49
|
+
}): React.JSX.Element;
|
|
50
|
+
/** The calendar this event is in: its colour and name, a choice between calendars (and mailboxes) when an event is being created. */
|
|
51
|
+
export declare function CalendarField({ c }: {
|
|
52
|
+
c: EventFormController;
|
|
53
|
+
}): React.JSX.Element;
|
|
54
|
+
/**
|
|
55
|
+
* The description row: the rich-text box (`DescriptionEditor`). In the full card it is always there; in the quick-create popover (`collapsible`) it is an
|
|
56
|
+
* "Add description" row that opens the same box in place, as Google Calendar does - and stays open once there is text in it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function DescriptionRow({ c, collapsible }: {
|
|
59
|
+
c: EventFormController;
|
|
60
|
+
collapsible: boolean;
|
|
61
|
+
}): React.JSX.Element;
|
|
62
|
+
/** Who may see the event's details, with what each choice means underneath. */
|
|
63
|
+
export declare function VisibilitySelect({ c }: {
|
|
64
|
+
c: EventFormController;
|
|
65
|
+
}): React.JSX.Element;
|
|
66
|
+
/** What guests may do with the event: ask for it to change, ask for guests to be added, see who else is invited. Only the organizer sets these. */
|
|
67
|
+
export declare function GuestPermissions({ c }: {
|
|
68
|
+
c: EventFormController;
|
|
69
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
3
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz
|
|
4
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
6
|
+
import { useRef, useState } from "react";
|
|
7
|
+
import { HiOutlineBars3BottomLeft, HiOutlineBell, HiOutlineMapPin, HiOutlineUserGroup, HiOutlineVideoCamera, HiOutlineXMark } from "react-icons/hi2";
|
|
8
|
+
import Button from "@rapidmx/react-shared/components/buttons/Button.js";
|
|
9
|
+
import LazyDescriptionEditor from "./LazyDescriptionEditor.js";
|
|
10
|
+
import RecurrenceEditor from "./RecurrenceEditor.js";
|
|
11
|
+
import ResourcePicker from "./ResourcePicker.js";
|
|
12
|
+
import { hasDescriptionText } from "./eventDialogFields.js";
|
|
13
|
+
import { REMINDER_UNITS, RESPONSE_STATUS_LABEL, VISIBILITIES, VISIBILITY_HELP, VISIBILITY_LABEL, bestReminderUnit, } from "./eventFormat.js";
|
|
14
|
+
export const INPUT_CLASS = "w-full text-sm py-2 px-3 border border-border rounded-md bg-surface text-text focus:outline-none focus:border-primary";
|
|
15
|
+
export const SELECT_CLASS = "text-sm py-2 px-2 border border-border rounded-md bg-surface text-text focus:outline-none focus:border-primary";
|
|
16
|
+
/** A text field that reads as plain text until it is hovered or focused, as a row of the quick-create popover does. */
|
|
17
|
+
const QUIET_INPUT_CLASS = "w-full min-w-0 text-sm py-1.5 bg-transparent text-text border-0 border-b border-transparent hover:border-border focus:border-primary focus:outline-none placeholder:text-text-muted";
|
|
18
|
+
const ROW_ICON_CLASS = "shrink-0 text-text-muted mt-1.5";
|
|
19
|
+
const ATTENDEE_ROLES = ["required", "optional", "resource"];
|
|
20
|
+
/** A form row: an icon in the left gutter and its content. */
|
|
21
|
+
export function IconRow({ icon, children }) {
|
|
22
|
+
return (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("span", { "aria-hidden": "true", className: ROW_ICON_CLASS, children: icon }), _jsx("div", { className: "flex-1 min-w-0", children: children })] }));
|
|
23
|
+
}
|
|
24
|
+
/** The location row: a text box that is plain text until it is used. */
|
|
25
|
+
export function LocationRow({ c }) {
|
|
26
|
+
return (_jsx(IconRow, { icon: _jsx(HiOutlineMapPin, { size: 20 }), children: _jsx("input", { type: "text", "aria-label": "Location", placeholder: "Add location", className: QUIET_INPUT_CLASS, value: c.values.location, onChange: (e) => c.update({ location: e.target.value }) }) }));
|
|
27
|
+
}
|
|
28
|
+
/** "Add video conferencing" and, once the event has a meeting, the button that joins it. */
|
|
29
|
+
export function VideoConferencingRow({ c }) {
|
|
30
|
+
const { values } = c;
|
|
31
|
+
return (_jsxs(IconRow, { icon: _jsx(HiOutlineVideoCamera, { size: 20 }), children: [_jsxs("label", { className: "flex items-center gap-2 text-sm py-1.5", children: [_jsx("input", { type: "checkbox", checked: values.videoEnabled, onChange: (e) => c.update({ videoEnabled: e.target.checked }) }), "Add video conferencing"] }), values.videoEnabled && (_jsx("p", { className: "text-xs text-text-muted mb-1", children: "Changing attendees after enabling video conferencing won\u2019t update meeting invitees \u2014 turn this off and back on to reissue links to the current attendee list." })), c.videoError && (_jsx("p", { role: "alert", className: "text-xs text-danger mb-1", children: c.videoError })), c.videoMeetingUid && (_jsxs("div", { className: "flex items-center gap-3 mb-1", children: [_jsx(Button, { type: "button", variant: "secondary", className: "!w-auto", disabled: !c.joinUrl, onClick: () => window.open(c.joinUrl, "_blank", "noopener,noreferrer"), children: "Join video call" }), !c.joinUrl && (_jsx("span", { className: "text-xs text-text-muted", children: c.organizerJoinUrl === undefined ? "Loading the join link…" : "This meeting’s join link isn’t available." }))] }))] }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The guests field: an address box (Enter, a comma or a semicolon adds what is typed) and what has been added - chips in the quick popover,
|
|
35
|
+
* rows with a role, the answer so far and a remove button in the full form, which also offers rooms and equipment.
|
|
36
|
+
*/
|
|
37
|
+
export function GuestsField({ c, compact }) {
|
|
38
|
+
const { values } = c;
|
|
39
|
+
const [hint, setHint] = useState(null);
|
|
40
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
41
|
+
const pickerAnchor = useRef(null);
|
|
42
|
+
function commit() {
|
|
43
|
+
const invalid = c.addGuests(values.guestDraft);
|
|
44
|
+
setHint(invalid.length > 0 ? `“${invalid[0]}” isn’t a valid email address.` : null);
|
|
45
|
+
}
|
|
46
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("input", { type: "text", "aria-label": "Add guests", placeholder: "Add guests", className: compact ? QUIET_INPUT_CLASS : INPUT_CLASS, value: values.guestDraft, onChange: (e) => {
|
|
47
|
+
setHint(null);
|
|
48
|
+
c.update({ guestDraft: e.target.value });
|
|
49
|
+
}, onKeyDown: (e) => {
|
|
50
|
+
if (e.key === "Enter" || e.key === "," || e.key === ";") {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
commit();
|
|
53
|
+
}
|
|
54
|
+
} }), hint && (_jsx("p", { role: "alert", className: "text-xs text-danger", children: hint })), compact ? (values.attendees.length > 0 && (_jsx("ul", { className: "flex flex-wrap gap-1.5", children: values.attendees.map((attendee, i) => (_jsxs("li", { className: "flex items-center gap-1 text-xs rounded-full bg-surface-alt pl-2.5 pr-1 py-0.5", children: [_jsx("span", { className: "truncate max-w-[16rem]", children: attendee.address }), _jsx("button", { type: "button", onClick: () => c.removeAttendee(i), className: "w-4 h-4 flex items-center justify-center rounded-full text-text-muted hover:text-text", "aria-label": `Remove attendee ${i + 1}`, children: _jsx(HiOutlineXMark, { size: 12, "aria-hidden": "true" }) })] }, i))) }))) : (_jsxs(_Fragment, { children: [values.attendees.length > 0 && (_jsx("ul", { className: "flex flex-col gap-2", children: values.attendees.map((attendee, i) => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": "true", className: "w-7 h-7 shrink-0 rounded-full bg-primary/15 text-primary-dark text-xs font-semibold flex items-center justify-center uppercase", children: (attendee.displayName || attendee.address).charAt(0) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm truncate", children: attendee.displayName || attendee.address }), _jsxs("div", { className: "text-xs text-text-muted truncate", children: [attendee.displayName ? `${attendee.address} · ` : "", RESPONSE_STATUS_LABEL[attendee.responseStatus]] })] }), _jsx("select", { className: SELECT_CLASS, value: attendee.role, onChange: (e) => c.updateAttendee(i, { role: e.target.value }), "aria-label": `Attendee role ${i + 1}`, children: ATTENDEE_ROLES.map((role) => (_jsx("option", { value: role, children: role }, role))) }), _jsx("button", { type: "button", onClick: () => c.removeAttendee(i), className: "w-7 h-7 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", "aria-label": `Remove attendee ${i + 1}`, children: _jsx(HiOutlineXMark, { size: 16, "aria-hidden": "true" }) })] }, i))) })), _jsx("button", { ref: pickerAnchor, type: "button", onClick: () => setPickerOpen(true), className: "self-start text-xs font-medium text-primary-dark hover:underline", children: "+ Add room/equipment" }), pickerOpen && (_jsx(ResourcePicker, { anchorRef: pickerAnchor, onClose: () => setPickerOpen(false), onSelect: (mailbox) => {
|
|
55
|
+
c.addResource(mailbox);
|
|
56
|
+
setPickerOpen(false);
|
|
57
|
+
}, excludeAddresses: c.guestAddresses }))] }))] }));
|
|
58
|
+
}
|
|
59
|
+
/** The guests row of the quick popover. */
|
|
60
|
+
export function GuestsRow({ c }) {
|
|
61
|
+
return (_jsx(IconRow, { icon: _jsx(HiOutlineUserGroup, { size: 20 }), children: _jsx(GuestsField, { c: c, compact: true }) }));
|
|
62
|
+
}
|
|
63
|
+
/** The reminder row: the one notification an event has - an amount, its unit and a way to remove it - or "Add notification". */
|
|
64
|
+
export function ReminderRow({ value, onChange }) {
|
|
65
|
+
const [active, setActive] = useState(value.trim() !== "");
|
|
66
|
+
const [unit, setUnit] = useState(() => (value.trim() === "" ? "minutes" : bestReminderUnit(Number(value)).unit));
|
|
67
|
+
const factor = REMINDER_UNITS.find((u) => u.unit === unit).factor;
|
|
68
|
+
// What the amount box shows: the stored minutes in the chosen unit. A blank stays blank while the row is open.
|
|
69
|
+
const amount = value.trim() === "" ? "" : String(Math.round((Number(value) / factor) * 100) / 100);
|
|
70
|
+
function change(nextAmount, nextUnit) {
|
|
71
|
+
const nextFactor = REMINDER_UNITS.find((u) => u.unit === nextUnit).factor;
|
|
72
|
+
onChange(nextAmount.trim() === "" ? "" : String(Math.round(Number(nextAmount) * nextFactor)));
|
|
73
|
+
}
|
|
74
|
+
if (!active) {
|
|
75
|
+
return (_jsx(IconRow, { icon: _jsx(HiOutlineBell, { size: 20 }), children: _jsx("button", { type: "button", className: "text-sm text-text-muted hover:text-text py-1.5", onClick: () => {
|
|
76
|
+
setActive(true);
|
|
77
|
+
setUnit("minutes");
|
|
78
|
+
onChange("30");
|
|
79
|
+
}, children: "Add notification" }) }));
|
|
80
|
+
}
|
|
81
|
+
return (_jsx(IconRow, { icon: _jsx(HiOutlineBell, { size: 20 }), children: _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { children: "Notification" }), _jsx("input", { type: "number", min: 0, className: `${INPUT_CLASS} !w-20`, "aria-label": "Reminder (minutes before)", value: amount, onChange: (e) => change(e.target.value, unit) }), _jsx("select", { className: SELECT_CLASS, "aria-label": "Reminder unit", value: unit, onChange: (e) => {
|
|
82
|
+
const nextUnit = e.target.value;
|
|
83
|
+
setUnit(nextUnit);
|
|
84
|
+
change(amount, nextUnit);
|
|
85
|
+
}, children: REMINDER_UNITS.map((u) => (_jsx("option", { value: u.unit, children: u.unit }, u.unit))) }), _jsx("span", { className: "text-text-muted", children: "before" }), _jsx("button", { type: "button", "aria-label": "Remove notification", className: "ml-auto w-7 h-7 flex items-center justify-center rounded-full text-text-muted hover:bg-surface-alt hover:text-text", onClick: () => {
|
|
86
|
+
setActive(false);
|
|
87
|
+
onChange("");
|
|
88
|
+
}, children: _jsx(HiOutlineXMark, { size: 16, "aria-hidden": "true" }) })] }) }));
|
|
89
|
+
}
|
|
90
|
+
const WEEKDAYS_ONLY = ["MO", "TU", "WE", "TH", "FR"];
|
|
91
|
+
const WEEKDAY_NAME = {
|
|
92
|
+
MO: "Monday",
|
|
93
|
+
TU: "Tuesday",
|
|
94
|
+
WE: "Wednesday",
|
|
95
|
+
TH: "Thursday",
|
|
96
|
+
FR: "Friday",
|
|
97
|
+
SA: "Saturday",
|
|
98
|
+
SU: "Sunday",
|
|
99
|
+
};
|
|
100
|
+
function presetRule(key, startWeekday) {
|
|
101
|
+
switch (key) {
|
|
102
|
+
case "daily":
|
|
103
|
+
return { freq: "daily", interval: 1, exceptions: [] };
|
|
104
|
+
case "weekly":
|
|
105
|
+
return { freq: "weekly", interval: 1, byDay: [startWeekday ?? "MO"], exceptions: [] };
|
|
106
|
+
case "monthly":
|
|
107
|
+
return { freq: "monthly", interval: 1, exceptions: [] };
|
|
108
|
+
case "yearly":
|
|
109
|
+
return { freq: "yearly", interval: 1, exceptions: [] };
|
|
110
|
+
case "weekdays":
|
|
111
|
+
return { freq: "weekly", interval: 1, byDay: [...WEEKDAYS_ONLY], exceptions: [] };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Which menu entry a rule is: one of the plain repeats, or "custom" for anything with an interval, an end or other weekdays. */
|
|
115
|
+
function presetOf(rule, startWeekday) {
|
|
116
|
+
if (!rule) {
|
|
117
|
+
return "none";
|
|
118
|
+
}
|
|
119
|
+
if (rule.interval !== 1 || rule.count || rule.until || rule.byMonthDay?.length || rule.byMonth?.length) {
|
|
120
|
+
return "custom";
|
|
121
|
+
}
|
|
122
|
+
if (rule.freq === "weekly") {
|
|
123
|
+
const days = [...(rule.byDay ?? [])].sort().join(",");
|
|
124
|
+
if (days === [...WEEKDAYS_ONLY].sort().join(",")) {
|
|
125
|
+
return "weekdays";
|
|
126
|
+
}
|
|
127
|
+
return days === (startWeekday ?? "MO") ? "weekly" : "custom";
|
|
128
|
+
}
|
|
129
|
+
return rule.byDay?.length ? "custom" : rule.freq;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* "Does not repeat" and the plain repeats as a menu, with "Custom…" opening `RecurrenceEditor`'s full set (an interval, weekdays, an end).
|
|
133
|
+
* Choosing one of the plain repeats keeps the rule's skipped dates; the menu shows "Custom…" for any rule it has no plain entry for.
|
|
134
|
+
*/
|
|
135
|
+
export function RecurrenceSelect({ value, onChange, allDay, startWeekday, }) {
|
|
136
|
+
// Picking "Custom…" for a rule that is also a plain repeat still opens the editor.
|
|
137
|
+
const [customRequested, setCustomRequested] = useState(false);
|
|
138
|
+
const selected = customRequested && value ? "custom" : presetOf(value, startWeekday);
|
|
139
|
+
function choose(key) {
|
|
140
|
+
setCustomRequested(key === "custom");
|
|
141
|
+
if (key === "none") {
|
|
142
|
+
onChange(null);
|
|
143
|
+
}
|
|
144
|
+
else if (key === "custom") {
|
|
145
|
+
onChange(value ?? presetRule("weekly", startWeekday));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
onChange({ ...presetRule(key, startWeekday), exceptions: value?.exceptions ?? [] });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("select", { id: "event-recurrence", "aria-label": "Recurrence", className: SELECT_CLASS, value: selected, onChange: (e) => choose(e.target.value), children: [_jsx("option", { value: "none", children: "Does not repeat" }), _jsx("option", { value: "daily", children: "Daily" }), _jsx("option", { value: "weekly", children: startWeekday ? `Weekly on ${WEEKDAY_NAME[startWeekday]}` : "Weekly" }), _jsx("option", { value: "monthly", children: "Monthly" }), _jsx("option", { value: "yearly", children: "Annually" }), _jsx("option", { value: "weekdays", children: "Every weekday (Monday to Friday)" }), _jsx("option", { value: "custom", children: "Custom\u2026" })] }), selected === "custom" && _jsx(RecurrenceEditor, { value: value, onChange: onChange, allDay: allDay, startWeekday: startWeekday, hideToggle: true })] }));
|
|
152
|
+
}
|
|
153
|
+
/** [start date] [start time] to [end time] [end date], then "All day", the "Time zone" link (and its menu) and how the event repeats. */
|
|
154
|
+
export function DateTimeControls({ c }) {
|
|
155
|
+
const { values } = c;
|
|
156
|
+
const [zoneOpen, setZoneOpen] = useState(false);
|
|
157
|
+
return (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-sm", children: [_jsx("input", { type: "date", "aria-label": "Event start date", className: `${SELECT_CLASS} min-w-0`, value: values.start.slice(0, 10), onChange: (e) => c.setStart(`${e.target.value}${values.start.slice(10)}`) }), !values.allDay && (_jsx("input", { type: "time", "aria-label": "Event start time", className: `${SELECT_CLASS} min-w-0`, value: values.start.slice(11, 16), onChange: (e) => c.setStart(`${values.start.slice(0, 11)}${e.target.value}`) })), _jsx("span", { className: "text-text-muted", children: "to" }), !values.allDay && (_jsx("input", { type: "time", "aria-label": "Event end time", className: `${SELECT_CLASS} min-w-0`, value: values.end.slice(11, 16), onChange: (e) => c.setEnd(`${values.end.slice(0, 11)}${e.target.value}`) })), _jsx("input", { type: "date", "aria-label": "Event end date", className: `${SELECT_CLASS} min-w-0`, value: values.end.slice(0, 10), onChange: (e) => c.setEnd(`${e.target.value}${values.end.slice(10)}`) })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-2 text-sm", children: [_jsxs("label", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: values.allDay, onChange: (e) => c.onAllDayChange(e.target.checked) }), "All day"] }), _jsx("button", { type: "button", "aria-expanded": zoneOpen, className: "text-primary-dark hover:underline", onClick: () => setZoneOpen((open) => !open), children: "Time zone" }), !c.editingSingleOccurrence && (_jsx(RecurrenceSelect, { value: values.recurrenceRule, onChange: (rule) => c.update({ recurrenceRule: rule }), allDay: values.allDay, startWeekday: c.startWeekday }))] }), zoneOpen && (_jsx("select", { "aria-label": "Time zone", className: `${SELECT_CLASS} self-start max-w-full`, value: values.formZone, onChange: (e) => c.onTimeZoneChange(e.target.value), children: c.timeZones.map((zone) => (_jsx("option", { value: zone, children: zone }, zone))) }))] }));
|
|
158
|
+
}
|
|
159
|
+
/** The calendar this event is in: its colour and name, a choice between calendars (and mailboxes) when an event is being created. */
|
|
160
|
+
export function CalendarField({ c }) {
|
|
161
|
+
const { values } = c;
|
|
162
|
+
const mailboxChoices = !c.occurrence && c.mailboxOptions && c.mailboxOptions.length > 1 ? c.mailboxOptions : undefined;
|
|
163
|
+
const calendarChoices = !c.occurrence && c.calendarChoices && c.calendarChoices.length > 1 ? c.calendarChoices : undefined;
|
|
164
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [mailboxChoices && (_jsx("select", { "aria-label": "Mailbox", className: `${SELECT_CLASS} self-start max-w-full`, value: values.targetMailboxUid, onChange: (e) => c.onMailboxChange(e.target.value), children: mailboxChoices.map(({ mailbox }) => (_jsxs("option", { value: mailbox.uid, children: [mailbox.displayName, mailbox.ownerUserUid ? "" : " (shared)"] }, mailbox.uid))) })), _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsx("span", { "aria-hidden": "true", className: "w-3 h-3 rounded-full shrink-0", style: { backgroundColor: c.calendarColor } }), calendarChoices ? (_jsx("select", { "aria-label": "Calendar", className: `${SELECT_CLASS} min-w-0 max-w-full`, value: values.targetFolderUid, onChange: (e) => c.update({ targetFolderUid: e.target.value }), children: calendarChoices.map((cal) => (_jsx("option", { value: cal.uid, children: cal.name }, cal.uid))) })) : (_jsx("span", { className: "truncate", children: c.calendarName }))] })] }));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The description row: the rich-text box (`DescriptionEditor`). In the full card it is always there; in the quick-create popover (`collapsible`) it is an
|
|
168
|
+
* "Add description" row that opens the same box in place, as Google Calendar does - and stays open once there is text in it.
|
|
169
|
+
*/
|
|
170
|
+
export function DescriptionRow({ c, collapsible }) {
|
|
171
|
+
const [opened, setOpened] = useState(false);
|
|
172
|
+
if (collapsible && !opened && !hasDescriptionText(c.values.descriptionHtml)) {
|
|
173
|
+
return (_jsx(IconRow, { icon: _jsx(HiOutlineBars3BottomLeft, { size: 20 }), children: _jsx("button", { type: "button", className: "text-sm text-text-muted hover:text-text py-1.5", onClick: () => setOpened(true), children: "Add description" }) }));
|
|
174
|
+
}
|
|
175
|
+
return (_jsx(IconRow, { icon: _jsx(HiOutlineBars3BottomLeft, { size: 20 }), children: _jsx(LazyDescriptionEditor, { value: c.values.descriptionHtml, onChange: (html) => c.update({ descriptionHtml: html }), autoFocus: collapsible && opened }) }));
|
|
176
|
+
}
|
|
177
|
+
/** Who may see the event's details, with what each choice means underneath. */
|
|
178
|
+
export function VisibilitySelect({ c }) {
|
|
179
|
+
const { visibility } = c.values;
|
|
180
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("select", { "aria-label": "Visibility", "aria-describedby": "event-visibility-help", className: `${SELECT_CLASS} !w-auto self-start`, value: visibility, onChange: (e) => c.update({ visibility: e.target.value }), children: VISIBILITIES.map((option) => (_jsx("option", { value: option, children: VISIBILITY_LABEL[option] }, option))) }), _jsx("p", { id: "event-visibility-help", className: "text-xs text-text-muted", children: VISIBILITY_HELP[visibility] })] }));
|
|
181
|
+
}
|
|
182
|
+
const GUEST_PERMISSIONS = [
|
|
183
|
+
{
|
|
184
|
+
key: "guestsCanModify",
|
|
185
|
+
label: "Modify event",
|
|
186
|
+
help: "Guests can ask you to change the title, location, description or time. What they ask for is sent to you and applied automatically when this is on.",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
key: "guestsCanInviteOthers",
|
|
190
|
+
label: "Invite others",
|
|
191
|
+
help: "Guests can ask you to add more guests; those are added automatically when this is on.",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
key: "guestsCanSeeGuestList",
|
|
195
|
+
label: "See guest list",
|
|
196
|
+
help: "When this is off, each guest is sent an invitation that names only themselves and you.",
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
/** What guests may do with the event: ask for it to change, ask for guests to be added, see who else is invited. Only the organizer sets these. */
|
|
200
|
+
export function GuestPermissions({ c }) {
|
|
201
|
+
return (_jsxs("fieldset", { className: "flex flex-col gap-2 min-w-0", children: [_jsx("legend", { className: "text-sm font-semibold mb-1", children: "Guest permissions" }), GUEST_PERMISSIONS.map(({ key, label, help }) => (_jsxs("div", { children: [_jsxs("label", { className: "flex items-center gap-2 text-sm", children: [_jsx("input", { type: "checkbox", "aria-describedby": `event-${key}-help`, checked: c.values[key], onChange: (e) => c.update({ [key]: e.target.checked }) }), label] }), _jsx("p", { id: `event-${key}-help`, className: "text-xs text-text-muted pl-6", children: help })] }, key)))] }));
|
|
202
|
+
}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Mailbox } from "@rapidmx/react-shared/mail/mailApi.js";
|
|
3
3
|
import { CalendarOccurrence } from "@rapidmx/react-shared/calendar/recurrence.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* Deliberately generic and identical for everyone: each attendee's own personal join link is substituted into
|
|
9
|
-
* their own copy of the invitation server-side (`@rapidmx/restapi`'s `MeetingSchedulingJob`), so no link -
|
|
10
|
-
* personal or otherwise - may be stored on the shared event itself, where every attendee would read the same
|
|
11
|
-
* one.
|
|
12
|
-
*/
|
|
13
|
-
export declare const VIDEO_LOCATION_PLACEHOLDER = "Video call \u2014 link in this invitation";
|
|
4
|
+
import { EventAnchor } from "./EventShell.js";
|
|
5
|
+
export { VIDEO_LOCATION_PLACEHOLDER } from "./EventEditor.js";
|
|
6
|
+
export type { EventAnchor } from "./EventShell.js";
|
|
14
7
|
export interface EventModalProps {
|
|
15
8
|
open: boolean;
|
|
16
9
|
onClose: () => void;
|
|
@@ -36,6 +29,8 @@ export interface EventModalProps {
|
|
|
36
29
|
name: string;
|
|
37
30
|
}[];
|
|
38
31
|
}[];
|
|
32
|
+
/** Each calendar's colour, keyed by folder uid: the dot next to the calendar's name. */
|
|
33
|
+
folderColors?: Record<string, string>;
|
|
39
34
|
organizerAddress: string;
|
|
40
35
|
/** The viewing mailbox's alias addresses - an event it organizes (or an invitation to it) may name any of
|
|
41
36
|
* them instead of `organizerAddress`. Defaults to that mailbox's `aliasAddresses` from `mailboxOptions`. */
|
|
@@ -45,12 +40,30 @@ export interface EventModalProps {
|
|
|
45
40
|
/** Prefilled start/end for create mode (e.g. the day/slot the user clicked). */
|
|
46
41
|
initialStart?: Date;
|
|
47
42
|
initialEnd?: Date;
|
|
43
|
+
/** Create mode: start as an all-day event on `initialStart`'s day (a day of the month view was clicked). */
|
|
44
|
+
initialAllDay?: boolean;
|
|
45
|
+
/** Create mode: what was clicked (a slot, a day, the New event button), which the quick-create popover opens beside. Without one it
|
|
46
|
+
* opens centered near the top of the window. */
|
|
47
|
+
anchor?: EventAnchor;
|
|
48
|
+
/** Where the booking plugin's Settings pages are (`/settings/booking-types`) when it is running: a new event then has an Appointment
|
|
49
|
+
* schedule tab, and its More options opens the plugin's own new-link page. Without it there are only the Event and Task tabs. */
|
|
50
|
+
bookingHref?: string;
|
|
48
51
|
onSaved: () => void;
|
|
49
52
|
onDeleted: () => void;
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
55
|
+
* The calendar's event dialog, in three faces drawn through one frame (`EventShell`):
|
|
56
|
+
*
|
|
57
|
+
* - **A new event** opens as a quick-create popover (`EventQuickForm`, beside what was clicked; a bottom sheet on a phone). "More options"
|
|
58
|
+
* grows it into the full card (`EventExpandedForm`) with everything typed so far still in it.
|
|
59
|
+
* The popover has tabs (`QuickCreateTabs`): Event, Task, and - when the booking plugin is running (`bookingHref`) - Appointment schedule.
|
|
60
|
+
* The Task and Appointment faces are drawn by `QuickCreateFaces`, share the Event tab's title, mailbox and calendar, and only ever
|
|
61
|
+
* create a task or a booking type; the Event tab is the form it always was.
|
|
62
|
+
* - **An existing event** opens read-only (`EventDetails`): what the event says, the invited reader's Accept / Tentative / Decline,
|
|
63
|
+
* and Delete. The reader who organized it also gets Modify, which turns the same card into the full form; closing that form returns
|
|
64
|
+
* to the details, discarding the edits (a save closes everything, as it always did).
|
|
65
|
+
*
|
|
66
|
+
* Editing or deleting a recurring event's occurrence offers a choice between "this event" and "the entire series" (see
|
|
54
67
|
* `calendarMutations.ts`) — Outlook's own convention for the same ambiguity.
|
|
55
68
|
*/
|
|
56
|
-
export default function EventModal({ open, onClose, mailboxUid, folderUid, calendars, mailboxOptions, organizerAddress, organizerAliases, occurrence, initialStart, initialEnd, onSaved, onDeleted, }: EventModalProps): React.JSX.Element;
|
|
69
|
+
export default function EventModal({ open, onClose, mailboxUid, folderUid, calendars, mailboxOptions, folderColors, organizerAddress, organizerAliases, occurrence, initialStart, initialEnd, initialAllDay, anchor, bookingHref, onSaved, onDeleted, }: EventModalProps): React.JSX.Element | null;
|