Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Dispatch, SetStateAction } from 'react';
import type { NotificationSeverity } from 'stream-chat';
import {
Button,
GlobalModal,
IconArrowDown,
IconArrowLeft,
IconArrowUp,
Expand All @@ -24,6 +25,7 @@ import {
useDialogIsOpen,
useDialogOnNearestManager,
useNotificationApi,
Viewer,
} from 'stream-chat-react';
import { DraggableDialog } from './DraggableDialog';
import {
Expand Down Expand Up @@ -75,11 +77,13 @@ const isDraftActionReady = (
) => action.label.trim().length > 0 && action.feedback.trim().length > 0;

const NotificationEntrySelect = ({
disabled,
label,
onChange,
options,
value,
}: {
disabled?: boolean;
label: string;
onChange: (value: string) => void;
options: readonly string[];
Expand All @@ -89,6 +93,7 @@ const NotificationEntrySelect = ({
<span className='app__notification-dialog__field-label'>{label}</span>
<select
className='app__notification-dialog__select'
disabled={disabled}
onChange={(event) => onChange(event.target.value)}
value={value}
>
Expand All @@ -101,11 +106,46 @@ const NotificationEntrySelect = ({
</label>
);

const NotificationTargetPanelSelect = ({
onChange,
options,
value,
}: {
onChange: (value: NotificationTargetPanel[]) => void;
options: readonly NotificationTargetPanel[];
value: NotificationTargetPanel[];
}) => (
<div className='app__notification-dialog__field'>
<span className='app__notification-dialog__field-label'>Target Panels</span>
<div className='app__notification-dialog__target-panel-options'>
{options.map((option) => (
<label className='app__notification-dialog__target-panel-option' key={option}>
<input
checked={value.includes(option)}
onChange={(event) => {
if (event.target.checked) {
onChange([...value, option]);
return;
}

onChange(value.filter((panel) => panel !== option));
}}
type='checkbox'
/>
<span>{option}</span>
</label>
))}
</div>
</div>
);

const NotificationChipList = ({
notifications,
publishQueuedNotification,
removeQueuedNotification,
}: {
notifications: QueuedNotification[];
publishQueuedNotification: (notification: QueuedNotification) => void;
removeQueuedNotification: (id: string) => void;
}) => {
const [tooltipState, setTooltipState] = useState<{
Expand Down Expand Up @@ -160,14 +200,24 @@ const NotificationChipList = ({
{notification.entryDirection}
</span>
<span className='app__notification-dialog__chip-panel'>
{notification.targetPanel}
{notification.targetPanels.join(', ')}
</span>
{notification.actions.length > 0 && (
<span className='app__notification-dialog__chip-panel'>
{formatActionsLabel(notification.actions.length)}
</span>
)}
</span>
<Button
appearance='outline'
className='app__notification-dialog__chip-trigger'
onClick={() => publishQueuedNotification(notification)}
size='xs'
type='button'
variant='secondary'
>
Trigger
</Button>
<Button
appearance='ghost'
aria-label={`Remove ${notification.message}`}
Expand All @@ -194,20 +244,28 @@ const NotificationChipList = ({

const NotificationDraftForm = ({
addDraftAction,
closeOnSubmit,
draft,
openGlobalModal,
publishQueuedNotification,
queueCurrentDraft,
queuedNotifications,
registerQueuedNotifications,
setCloseOnSubmit,
toggleDraftActionInPayload,
removeQueuedNotification,
setDraft,
updateDraftAction,
}: {
addDraftAction: () => void;
closeOnSubmit: boolean;
draft: NotificationDraft;
openGlobalModal: () => void;
publishQueuedNotification: (notification: QueuedNotification) => void;
queueCurrentDraft: () => void;
queuedNotifications: QueuedNotification[];
registerQueuedNotifications: () => void;
setCloseOnSubmit: Dispatch<SetStateAction<boolean>>;
toggleDraftActionInPayload: (id: string) => void;
removeQueuedNotification: (id: string) => void;
setDraft: Dispatch<SetStateAction<NotificationDraft>>;
Expand Down Expand Up @@ -247,36 +305,58 @@ const NotificationDraftForm = ({
/>
<div className='app__notification-dialog__field'>
<span className='app__notification-dialog__field-label'>Duration (ms)</span>
<NumericInput
aria-label='Duration (ms)'
min={0}
onChange={(event) =>
setDraft((current) => ({ ...current, duration: event.target.value }))
}
value={draft.duration}
/>
<div className='app__notification-dialog__duration-controls'>
<NumericInput
aria-label='Duration (ms)'
min={0}
onChange={(event) =>
setDraft((current) => ({ ...current, duration: event.target.value }))
}
value={draft.duration}
/>
<Button
appearance='outline'
className='app__notification-dialog__permanent-duration-button'
onClick={() => setDraft((current) => ({ ...current, duration: '0' }))}
size='sm'
type='button'
variant='secondary'
>
Make permanent
</Button>

<Button
appearance='outline'
className='app__notification-dialog__open-modal-button'
onClick={openGlobalModal}
size='sm'
type='button'
variant='secondary'
>
Open preview modal
</Button>
</div>
</div>
<NotificationEntrySelect
label='Entry Direction'
<NotificationTargetPanelSelect
onChange={(value) =>
setDraft((current) => ({
...current,
entryDirection: value as NotificationListEnterFrom,
targetPanels: value,
}))
}
options={entryDirectionOptions}
value={draft.entryDirection}
options={targetPanelOptions}
value={draft.targetPanels}
/>
<NotificationEntrySelect
label='Target Panel'
label='Entry Direction'
onChange={(value) =>
setDraft((current) => ({
...current,
targetPanel: value as NotificationTargetPanel,
entryDirection: value as NotificationListEnterFrom,
}))
}
options={targetPanelOptions}
value={draft.targetPanel}
options={entryDirectionOptions}
value={draft.entryDirection}
/>
<div className='app__notification-dialog__actions'>
<div className='app__notification-dialog__actions-header'>
Expand Down Expand Up @@ -369,10 +449,19 @@ const NotificationDraftForm = ({
</div>
<NotificationChipList
notifications={queuedNotifications}
publishQueuedNotification={publishQueuedNotification}
removeQueuedNotification={removeQueuedNotification}
/>
</div>
<Prompt.FooterControls className='app__notification-dialog__footer-controls'>
<label className='app__notification-dialog__close-on-submit'>
<input
checked={closeOnSubmit}
onChange={(event) => setCloseOnSubmit(event.target.checked)}
type='checkbox'
/>
<span>Close on submit</span>
</label>
<Prompt.FooterControlsButtonPrimary
disabled={queuedNotifications.length === 0}
onClick={registerQueuedNotifications}
Expand Down Expand Up @@ -412,6 +501,8 @@ export const NotificationPromptDialog = ({
const [queuedNotifications, setQueuedNotifications] = useState<QueuedNotification[]>(
[],
);
const [closeOnSubmit, setCloseOnSubmit] = useState(false);
const [globalModalOpen, setGlobalModalOpen] = useState(false);
const chipIdRef = useRef(0);
const { addNotification } = useNotificationApi();
const { dialog, dialogManager } = useDialogOnNearestManager({
Expand All @@ -422,6 +513,7 @@ export const NotificationPromptDialog = ({
const resetState = useCallback(() => {
setDraft(createInitialDraftState());
setQueuedNotifications([]);
setCloseOnSubmit(false);
}, [createInitialDraftState]);

useEffect(() => {
Expand All @@ -439,13 +531,15 @@ export const NotificationPromptDialog = ({
actions: buildNotificationActions(notification),
context: {
entryDirection: notification.entryDirection,
panel: notification.targetPanel,
...(notification.targetPanels.length === 1
? { panel: notification.targetPanels[0] }
: { targetPanels: notification.targetPanels }),
},
duration: notification.duration,
emitter: 'vite-preview/ActionsMenu',
message: notification.message,
severity: notification.severity,
targetPanels: [notification.targetPanel],
targetPanels: notification.targetPanels,
});
},
[addNotification],
Expand All @@ -472,21 +566,33 @@ export const NotificationPromptDialog = ({
id: `queued-notification-${chipIdRef.current}`,
message: draft.message.trim(),
severity: draft.severity as NotificationSeverity,
targetPanel: draft.targetPanel as NotificationTargetPanel,
targetPanels: draft.targetPanels,
},
]);
setDraft(createInitialDraftState());
}, [createInitialDraftState, draft]);

const registerQueuedNotifications = useCallback(() => {
queuedNotifications.forEach(publishNotification);
closeDialog();
}, [closeDialog, publishNotification, queuedNotifications]);
setQueuedNotifications([]);

if (closeOnSubmit) {
closeDialog();
}
}, [closeDialog, closeOnSubmit, publishNotification, queuedNotifications]);

const removeQueuedNotification = useCallback((id: string) => {
setQueuedNotifications((current) => current.filter((item) => item.id !== id));
}, []);

const publishQueuedNotification = useCallback(
(notification: QueuedNotification) => {
publishNotification(notification);
removeQueuedNotification(notification.id);
},
[publishNotification, removeQueuedNotification],
);

const addDraftAction = useCallback(() => {
setDraft((current) => ({
...current,
Expand Down Expand Up @@ -536,29 +642,47 @@ export const NotificationPromptDialog = ({
);

return (
<DraggableDialog
dialogClassName='app__notification-dialog'
dialogId={notificationPromptDialogId}
dialogIsOpen={dialogIsOpen}
dialogManagerId={dialogManager?.id}
dragHandleClassName='app__notification-dialog__drag-handle'
onClose={closeDialog}
promptClassName='app__notification-dialog__prompt'
referenceElement={referenceElement}
shellClassName='app__notification-dialog__shell'
title='Trigger Notification'
>
<NotificationDraftForm
addDraftAction={addDraftAction}
draft={draft}
queueCurrentDraft={queueCurrentDraft}
queuedNotifications={queuedNotifications}
registerQueuedNotifications={registerQueuedNotifications}
removeQueuedNotification={removeQueuedNotification}
setDraft={setDraft}
toggleDraftActionInPayload={toggleDraftActionInPayload}
updateDraftAction={updateDraftAction}
/>
</DraggableDialog>
<>
<GlobalModal onClose={() => setGlobalModalOpen(false)} open={globalModalOpen}>
<Viewer.Root className='app__notification-dialog__global-modal-preview'>
<h2>GlobalModal notification preview</h2>
<p>
Keep the trigger dialog open and submit notifications while this modal is
visible.
</p>
<Button onClick={() => setGlobalModalOpen(false)} type='button'>
Close preview modal
</Button>
</Viewer.Root>
</GlobalModal>
<DraggableDialog
dialogClassName='app__notification-dialog'
dialogId={notificationPromptDialogId}
dialogIsOpen={dialogIsOpen}
dialogManagerId={dialogManager?.id}
dragHandleClassName='app__notification-dialog__drag-handle'
onClose={closeDialog}
promptClassName='app__notification-dialog__prompt'
referenceElement={referenceElement}
shellClassName='app__notification-dialog__shell'
title='Trigger Notification'
>
<NotificationDraftForm
addDraftAction={addDraftAction}
closeOnSubmit={closeOnSubmit}
draft={draft}
openGlobalModal={() => setGlobalModalOpen(true)}
publishQueuedNotification={publishQueuedNotification}
queueCurrentDraft={queueCurrentDraft}
queuedNotifications={queuedNotifications}
registerQueuedNotifications={registerQueuedNotifications}
removeQueuedNotification={removeQueuedNotification}
setCloseOnSubmit={setCloseOnSubmit}
setDraft={setDraft}
toggleDraftActionInPayload={toggleDraftActionInPayload}
updateDraftAction={updateDraftAction}
/>
</DraggableDialog>
</>
);
};
Loading