L-Sun
2025-04-23 05:41:14 +00:00
parent 9b2cf5cafa
commit 27ff9ab9f4
21 changed files with 304 additions and 339 deletions

View File

@@ -4,7 +4,8 @@ import clsx from 'clsx';
import { useCallback } from 'react';
import { Button, IconButton } from '../../button';
import type { NotificationCardProps } from '../types';
import { FlexWrapper } from '../../layout/wrapper';
import type { NotificationActionProps, NotificationCardProps } from '../types';
import { getCardVars } from '../utils';
import * as styles from './styles.css';
@@ -18,10 +19,9 @@ export const DesktopNotificationCard = ({
icon = <InformationFillDuotoneIcon />,
iconColor,
thumb,
action,
actions,
error,
title,
footer,
alignMessage = 'title',
onDismiss,
rootAttrs,
@@ -33,13 +33,6 @@ export const DesktopNotificationCard = ({
? t[errorI18nKey](error?.data)
: undefined;
const onActionClicked = useCallback(() => {
action?.onClick()?.catch(console.error);
if (action?.autoClose !== false) {
onDismiss?.();
}
}, [action, onDismiss]);
return (
<div
style={getCardVars(style, theme, iconColor)}
@@ -56,18 +49,6 @@ export const DesktopNotificationCard = ({
</div>
) : null}
<div className={styles.title}>{title || errorTitle}</div>
{action ? (
<div className={clsx(styles.headAlignWrapper, styles.action)}>
<Button
className={styles.actionButton}
onClick={onActionClicked}
{...action.buttonProps}
>
{action.label}
</Button>
</div>
) : null}
<div
data-float={!!thumb}
className={clsx(styles.headAlignWrapper, styles.closeButton)}
@@ -83,8 +64,42 @@ export const DesktopNotificationCard = ({
<main data-align={alignMessage} className={styles.main}>
{notification.message}
</main>
<footer>{footer}</footer>
<footer>
<FlexWrapper marginTop={8} justifyContent="flex-end" gap="12px">
{actions?.map(action => (
<NotificationCardAction
key={action.key}
action={action}
onDismiss={onDismiss}
/>
))}
</FlexWrapper>
</footer>
</div>
</div>
);
};
const NotificationCardAction = ({
action,
onDismiss,
}: NotificationActionProps) => {
const onActionClicked = useCallback(() => {
action.onClick()?.catch(console.error);
if (action.autoClose !== false) {
onDismiss?.();
}
}, [action, onDismiss]);
return (
<Button
variant="plain"
data-testid={action.key}
className={styles.actionButton}
onClick={onActionClicked}
{...action.buttonProps}
>
{action.label}
</Button>
);
};

View File

@@ -55,14 +55,14 @@ export const title = style({
fontSize: 15,
marginRight: 10,
});
export const action = style({
marginRight: 16,
});
export const actionButton = style({
color: actionTextColor,
position: 'relative',
background: 'transparent',
border: 'none',
fontSize: cssVar('fontSm'),
lineHeight: '22px',
});
export const closeButton = style({
selectors: {

View File

@@ -4,7 +4,7 @@ import { useCallback, useState } from 'react';
import { Button, IconButton } from '../../button';
import { Modal } from '../../modal';
import type { NotificationCardProps } from '../types';
import type { NotificationActionProps, NotificationCardProps } from '../types';
import { getCardVars } from '../utils';
import * as styles from './styles.css';
@@ -70,8 +70,7 @@ const MobileNotifyDetail = ({
iconColor,
title,
message,
footer,
action,
actions,
error,
} = notification;
const t = useI18n();
@@ -87,12 +86,6 @@ const MobileNotifyDetail = ({
},
[onClose]
);
const onActionClicked = useCallback(() => {
action?.onClick()?.catch(console.error);
if (action?.autoClose !== false) {
onClose?.();
}
}, [action, onClose]);
return (
<Modal
@@ -114,14 +107,33 @@ const MobileNotifyDetail = ({
<main className={styles.detailContent}>{message}</main>
{/* actions */}
<div className={styles.detailActions}>
{action ? (
<Button onClick={onActionClicked} {...action.buttonProps}>
{action.label}
</Button>
) : null}
{footer}
{actions?.map(action => (
<NotificationCardAction
key={action.key}
action={action}
onDismiss={onClose}
/>
))}
</div>
</div>
</Modal>
);
};
const NotificationCardAction = ({
action,
onDismiss,
}: NotificationActionProps) => {
const onActionClicked = useCallback(() => {
action.onClick()?.catch(console.error);
if (action.autoClose !== false) {
onDismiss?.();
}
}, [action, onDismiss]);
return (
<Button onClick={onActionClicked} {...action.buttonProps}>
{action.label}
</Button>
);
};

View File

@@ -181,10 +181,13 @@ export const WithAction: StoryFn = () => {
),
style,
theme,
action: {
label: 'UNDO',
onClick: () => console.log('undo'),
},
actions: [
{
key: 'undo',
label: 'UNDO',
onClick: () => console.log('undo'),
},
],
})
}
>
@@ -204,11 +207,14 @@ export const WithAction: StoryFn = () => {
{
title: 'Disable auto close',
message: 'Test with disable auto close',
action: {
label: 'UNDO',
onClick: () => console.log('undo'),
autoClose: false,
},
actions: [
{
key: 'undo',
label: 'UNDO',
onClick: () => console.log('undo'),
autoClose: false,
},
],
},
{ duration: 22222222 }
);
@@ -296,25 +302,12 @@ export const DifferentSize: StoryFn = () => {
{ duration: 60000 }
);
};
const openWithFooter = () => {
notify(
{
title: 'With footer',
message: 'With basic title and one line message',
footer: (
<Button onClick={() => console.log('clicked')}>Click me</Button>
),
},
{ duration: 60000 }
);
};
return (
<Root style={{ display: 'flex', gap: 8 }}>
<Button onClick={openTiny}>Open Tiny</Button>
<Button onClick={openNormal}>Open Normal</Button>
<Button onClick={openLarge}>Open Large</Button>
<Button onClick={openWithThumb}>Open with thumb</Button>
<Button onClick={openWithFooter}>Open with footer</Button>
</Root>
);
};

View File

@@ -14,7 +14,8 @@ export interface Notification {
background?: string;
foreground?: string;
alignMessage?: 'title' | 'icon';
action?: {
actions?: {
key: string;
label: ReactNode;
onClick: (() => void) | (() => Promise<void>);
buttonProps?: ButtonProps;
@@ -22,7 +23,7 @@ export interface Notification {
* @default true
*/
autoClose?: boolean;
};
}[];
rootAttrs?: HTMLAttributes<HTMLDivElement>;
@@ -33,7 +34,6 @@ export interface Notification {
error?: UserFriendlyError;
icon?: ReactNode;
iconColor?: string;
footer?: ReactNode;
// events
onDismiss?: () => void;
@@ -50,3 +50,8 @@ export interface NotificationCustomRendererProps {
export interface NotificationCardProps extends HTMLAttributes<HTMLDivElement> {
notification: Notification;
}
export interface NotificationActionProps {
action: NonNullable<Notification['actions']>[number];
onDismiss: Notification['onDismiss'];
}