Skip to content

Commit 101104f

Browse files
committed
feat(js): separate notification action concern
1 parent 5acda8d commit 101104f

File tree

3 files changed

+330
-279
lines changed

3 files changed

+330
-279
lines changed

packages/js/src/ui/components/Notification/DefaultNotification.tsx

+5-279
Original file line numberDiff line numberDiff line change
@@ -4,81 +4,18 @@ import type { Notification } from '../../../notifications';
44
import { ActionTypeEnum } from '../../../types';
55
import { useInboxContext, useLocalization } from '../../context';
66
import { cn, formatToRelativeTime, useStyle } from '../../helpers';
7-
import { MarkAsUnarchived } from '../../icons';
87
import { Clock } from '../../icons/Clock';
9-
import { MarkAsArchived } from '../../icons/MarkAsArchived';
10-
import { MarkAsRead } from '../../icons/MarkAsRead';
11-
import { MarkAsUnread } from '../../icons/MarkAsUnread';
12-
import { Snooze } from '../../icons/Snooze';
13-
import { Unsnooze } from '../../icons/Unsnooze';
148
import {
15-
LocalizationKey,
16-
NotificationStatus,
179
type BodyRenderer,
1810
type NotificationActionClickHandler,
1911
type NotificationClickHandler,
2012
type SubjectRenderer,
2113
} from '../../types';
2214
import Markdown from '../elements/Markdown';
2315
import { ExternalElementRenderer } from '../ExternalElementRenderer';
24-
import { Button, Dropdown, dropdownItemVariants, Popover } from '../primitives';
16+
import { Button } from '../primitives';
2517
import { Badge } from '../primitives/Badge';
26-
import { Tooltip } from '../primitives/Tooltip';
27-
import { SnoozeDateTimePicker } from './SnoozeDateTimePicker';
28-
29-
const SNOOZE_PRESETS = [
30-
{
31-
key: 'snooze.options.anHourFromNow',
32-
hours: 1,
33-
getDate: () => new Date(Date.now() + 1 * 60 * 60 * 1000),
34-
},
35-
{
36-
key: 'snooze.options.inTwelveHours',
37-
hours: 12,
38-
getDate: () => new Date(Date.now() + 12 * 60 * 60 * 1000),
39-
},
40-
{
41-
key: 'snooze.options.inOneDay',
42-
hours: 24,
43-
getDate: () => new Date(Date.now() + 1 * 24 * 60 * 60 * 1000),
44-
},
45-
{
46-
key: 'snooze.options.inOneWeek',
47-
hours: 168,
48-
getDate: () => new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
49-
},
50-
] satisfies {
51-
key: LocalizationKey;
52-
hours: number;
53-
getDate: () => Date;
54-
}[];
55-
56-
const formatSnoozeOption = (
57-
preset: (typeof SNOOZE_PRESETS)[number],
58-
t: (key: LocalizationKey) => string,
59-
locale: string
60-
): string => {
61-
const date = preset.getDate();
62-
63-
// For hour-based presets (1 hour, 12 hours), just show the translation without time
64-
if (preset.hours <= 12) {
65-
return t(preset.key);
66-
}
67-
68-
// Format time (e.g., "9:00 AM")
69-
const timeString = new Intl.DateTimeFormat(locale, { hour: 'numeric', minute: 'numeric' }).format(date);
70-
71-
// For weekly option, show "Next Monday" etc.
72-
if (preset.key === 'snooze.options.inOneWeek') {
73-
// Get the day name (e.g., "Monday")
74-
const dayName = new Intl.DateTimeFormat(locale, { weekday: 'long' }).format(date);
75-
76-
return `${t(preset.key)} ${dayName}, ${timeString}`;
77-
}
78-
79-
// Fallback to original translation
80-
return `${t(preset.key)}, ${timeString}`;
81-
};
18+
import { renderNotificationActions } from './NotificationActions';
8219

8320
type DefaultNotificationProps = {
8421
notification: Notification;
@@ -92,8 +29,7 @@ type DefaultNotificationProps = {
9229
export const DefaultNotification = (props: DefaultNotificationProps) => {
9330
const style = useStyle();
9431
const { t, locale } = useLocalization();
95-
const { navigate, status, maxSnoozeDurationHours } = useInboxContext();
96-
const [isSnoozeDateTimePickerOpen, setIsSnoozeDateTimePickerOpen] = createSignal(false);
32+
const { navigate, status } = useInboxContext();
9733
const [minutesPassed, setMinutesPassed] = createSignal(0);
9834
const createdAt = createMemo(() => {
9935
minutesPassed(); // register as dep
@@ -120,12 +56,6 @@ export const DefaultNotification = (props: DefaultNotificationProps) => {
12056
);
12157
});
12258

123-
const availableSnoozePresets = createMemo(() => {
124-
if (!maxSnoozeDurationHours()) return SNOOZE_PRESETS;
125-
126-
return SNOOZE_PRESETS.filter((preset) => preset.hours <= maxSnoozeDurationHours());
127-
});
128-
12959
createEffect(() => {
13060
const interval = setInterval(() => {
13161
setMinutesPassed((prev) => prev + 1);
@@ -228,214 +158,10 @@ export const DefaultNotification = (props: DefaultNotificationProps) => {
228158
<div
229159
class={style(
230160
'notificationDefaultActions',
231-
'nt-absolute nt-transition nt-duration-100 nt-ease-out nt-gap-0.5 nt-flex nt-shrink-0 nt-opacity-0 group-hover:nt-opacity-100 group-focus-within:nt-opacity-100 nt-justify-center nt-items-center nt-bg-background/90 nt-right-3 nt-top-3 nt-border nt-border-neutral-alpha-100 nt-rounded-lg nt-backdrop-blur-lg nt-p-0.5'
161+
`nt-absolute nt-transition nt-duration-100 nt-ease-out nt-gap-0.5 nt-flex nt-shrink-0 nt-opacity-0 group-hover:nt-opacity-100 group-focus-within:nt-opacity-100 nt-justify-center nt-items-center nt-bg-background/90 nt-right-3 nt-top-3 nt-border nt-border-neutral-alpha-100 nt-rounded-lg nt-backdrop-blur-lg nt-p-0.5`
232162
)}
233163
>
234-
<Show when={status() !== NotificationStatus.ARCHIVED}>
235-
<Show
236-
when={props.notification.isRead}
237-
fallback={
238-
<Tooltip.Root>
239-
<Tooltip.Trigger
240-
asChild={(childProps) => (
241-
<Button
242-
appearanceKey="notificationRead__button"
243-
size="iconSm"
244-
variant="ghost"
245-
{...childProps}
246-
onClick={async (e) => {
247-
e.stopPropagation();
248-
await props.notification.read();
249-
}}
250-
>
251-
<MarkAsRead class={style('notificationRead__icon', 'nt-size-3')} />
252-
</Button>
253-
)}
254-
/>
255-
<Tooltip.Content data-localization="notification.actions.read.tooltip">
256-
{t('notification.actions.read.tooltip')}
257-
</Tooltip.Content>
258-
</Tooltip.Root>
259-
}
260-
>
261-
<Tooltip.Root>
262-
<Tooltip.Trigger
263-
asChild={(childProps) => (
264-
<Button
265-
appearanceKey="notificationUnread__button"
266-
size="iconSm"
267-
variant="ghost"
268-
{...childProps}
269-
onClick={async (e) => {
270-
e.stopPropagation();
271-
await props.notification.unread();
272-
}}
273-
>
274-
<MarkAsUnread class={style('notificationUnread__icon', 'nt-size-3')} />
275-
</Button>
276-
)}
277-
/>
278-
<Tooltip.Content data-localization="notification.actions.unread.tooltip">
279-
{t('notification.actions.unread.tooltip')}
280-
</Tooltip.Content>
281-
</Tooltip.Root>
282-
</Show>
283-
</Show>
284-
285-
<Show
286-
when={props.notification.isSnoozed}
287-
fallback={
288-
<Tooltip.Root>
289-
<Tooltip.Trigger
290-
asChild={(tooltipProps) => (
291-
<Dropdown.Root>
292-
<Dropdown.Trigger
293-
{...tooltipProps}
294-
asChild={(popoverProps) => (
295-
<Button
296-
appearanceKey="notificationSnooze__button"
297-
size="iconSm"
298-
variant="ghost"
299-
{...popoverProps}
300-
>
301-
<Snooze class={style('notificationSnooze__icon', 'nt-size-3')} />
302-
</Button>
303-
)}
304-
/>
305-
<Dropdown.Content appearanceKey="notificationSnooze__dropdownContent">
306-
<For each={availableSnoozePresets()}>
307-
{(preset) => (
308-
<Dropdown.Item
309-
appearanceKey="notificationSnooze__dropdownItem"
310-
onClick={async (e) => {
311-
e.stopPropagation();
312-
await props.notification.snooze(preset.getDate().toISOString());
313-
}}
314-
>
315-
<Clock
316-
class={style(
317-
'notificationSnooze__dropdownItem__icon',
318-
'nt-size-3 nt-text-foreground-alpha-400'
319-
)}
320-
/>
321-
{formatSnoozeOption(preset, t, locale())}
322-
</Dropdown.Item>
323-
)}
324-
</For>
325-
326-
<Popover.Root open={isSnoozeDateTimePickerOpen()} onOpenChange={setIsSnoozeDateTimePickerOpen}>
327-
<Dropdown.Item
328-
asChild={(props) => (
329-
<Popover.Trigger
330-
class={style('notificationSnooze__dropdownItem', dropdownItemVariants())}
331-
{...props}
332-
>
333-
<Clock
334-
class={style(
335-
'notificationSnooze__dropdownItem__icon',
336-
'nt-size-3 nt-text-foreground-alpha-400'
337-
)}
338-
/>
339-
{t('snooze.options.customTime')}
340-
</Popover.Trigger>
341-
)}
342-
/>
343-
<Popover.Content
344-
portal
345-
class={style('notificationSnoozeCustomTime_popoverContent', 'nt-size-fit')}
346-
>
347-
<SnoozeDateTimePicker
348-
maxDurationHours={maxSnoozeDurationHours()}
349-
onSelect={async (date) => {
350-
await props.notification.snooze(date.toISOString());
351-
}}
352-
onCancel={() => {
353-
setIsSnoozeDateTimePickerOpen(false);
354-
}}
355-
/>
356-
</Popover.Content>
357-
</Popover.Root>
358-
</Dropdown.Content>
359-
</Dropdown.Root>
360-
)}
361-
/>
362-
<Tooltip.Content data-localization="notification.actions.read.tooltip">
363-
{t('notification.actions.snooze.tooltip')}
364-
</Tooltip.Content>
365-
</Tooltip.Root>
366-
}
367-
>
368-
<Tooltip.Root>
369-
<Tooltip.Trigger
370-
asChild={(childProps) => (
371-
<Button
372-
appearanceKey="notificationUnsnooze__button"
373-
size="iconSm"
374-
variant="ghost"
375-
{...childProps}
376-
onClick={async (e) => {
377-
e.stopPropagation();
378-
await props.notification.unsnooze();
379-
}}
380-
>
381-
<Unsnooze class={style('notificationUnsnooze__icon', 'nt-size-3')} />
382-
</Button>
383-
)}
384-
/>
385-
<Tooltip.Content data-localization="notification.actions.unsnooze.tooltip">
386-
{t('notification.actions.unsnooze.tooltip')}
387-
</Tooltip.Content>
388-
</Tooltip.Root>
389-
</Show>
390-
391-
<Show
392-
when={props.notification.isArchived}
393-
fallback={
394-
<Tooltip.Root>
395-
<Tooltip.Trigger
396-
asChild={(childProps) => (
397-
<Button
398-
appearanceKey="notificationArchive__button"
399-
size="iconSm"
400-
variant="ghost"
401-
{...childProps}
402-
onClick={async (e) => {
403-
e.stopPropagation();
404-
await props.notification.archive();
405-
}}
406-
>
407-
<MarkAsArchived class={style('notificationArchive__icon', 'nt-size-3')} />
408-
</Button>
409-
)}
410-
/>
411-
<Tooltip.Content data-localization="notification.actions.archive.tooltip">
412-
{t('notification.actions.archive.tooltip')}
413-
</Tooltip.Content>
414-
</Tooltip.Root>
415-
}
416-
>
417-
<Tooltip.Root>
418-
<Tooltip.Trigger
419-
asChild={(childProps) => (
420-
<Button
421-
appearanceKey="notificationUnarchive__button"
422-
size="iconSm"
423-
variant="ghost"
424-
{...childProps}
425-
onClick={async (e) => {
426-
e.stopPropagation();
427-
await props.notification.unarchive();
428-
}}
429-
>
430-
<MarkAsUnarchived class={style('notificationArchive__icon', 'nt-size-3')} />
431-
</Button>
432-
)}
433-
/>
434-
<Tooltip.Content data-localization="notification.actions.unarchive.tooltip">
435-
{t('notification.actions.unarchive.tooltip')}
436-
</Tooltip.Content>
437-
</Tooltip.Root>
438-
</Show>
164+
{renderNotificationActions(props.notification, status)}
439165
</div>
440166
<Show when={props.notification.primaryAction || props.notification.secondaryAction}>
441167
<div class={style('notificationCustomActions', 'nt-flex nt-flex-wrap nt-gap-2')}>

0 commit comments

Comments
 (0)