* Remove X button from tablet/mobile Notification menu * Toast styling * missed in merge * .env.example * Update abacus@1.0.9 * Add notif strings, and abaucs notif state * Update Notification hooks, add Notification Preference controls * Consolidate Notification into single component * withLabel -> withTrigger prop * nits * OrderStatusChanged -> AbacusGenerated * Notification OnClick * Move Notification -> Components * withAnimation -> $withAnimation * Notification styling and click handle * load state, onClick, localStorage * add error count * Rez localStorage Notifications * Update TransferStatus UI * Use Notification component for other abacusNotifs * fix uselocalnotif query * Default to Notification from NotificationMenu --------- Co-authored-by: Bill He <bill@dydx.exchange>
202 lines
4.6 KiB
TypeScript
202 lines
4.6 KiB
TypeScript
import React, { type MouseEvent } from 'react';
|
|
import styled, { css } from 'styled-components';
|
|
|
|
import { ButtonShape, ButtonSize } from '@/constants/buttons';
|
|
|
|
import {
|
|
NotificationStatus,
|
|
type Notification as NotificationDataType,
|
|
} from '@/constants/notifications';
|
|
|
|
import { useNotifications } from '@/hooks/useNotifications';
|
|
|
|
import { popoverMixins } from '@/styles/popoverMixins';
|
|
import { layoutMixins } from '@/styles/layoutMixins';
|
|
|
|
import { IconButton } from '@/components/IconButton';
|
|
import { IconName } from '@/components/Icon';
|
|
import { Output, OutputType } from '@/components/Output';
|
|
|
|
type ElementProps = {
|
|
notification: NotificationDataType;
|
|
onClick?: () => void;
|
|
slotIcon?: React.ReactNode;
|
|
slotTitle?: React.ReactNode;
|
|
slotTitleLeft?: React.ReactNode;
|
|
slotTitleRight?: React.ReactNode;
|
|
slotDescription?: React.ReactNode;
|
|
slotCustomContent?: React.ReactNode;
|
|
slotAction?: React.ReactNode;
|
|
withClose?: boolean;
|
|
};
|
|
|
|
type StyleProps = {
|
|
className?: string;
|
|
isToast?: boolean;
|
|
};
|
|
|
|
export type NotificationProps = ElementProps & StyleProps;
|
|
|
|
export const Notification = ({
|
|
className,
|
|
isToast,
|
|
notification,
|
|
onClick,
|
|
slotIcon,
|
|
slotTitle,
|
|
slotTitleLeft,
|
|
slotTitleRight,
|
|
slotDescription,
|
|
slotCustomContent,
|
|
slotAction,
|
|
withClose = !isToast,
|
|
}: NotificationProps) => {
|
|
const { markCleared, markSeen } = useNotifications();
|
|
|
|
return (
|
|
<$Container className={className} isToast={isToast} onClick={onClick}>
|
|
<$Header>
|
|
{slotTitleLeft ?? (slotIcon && <$Icon>{slotIcon}</$Icon>)}
|
|
<$Title>{slotTitle}</$Title>
|
|
{slotTitleRight}
|
|
{withClose && !isToast && (
|
|
<$ActionItems>
|
|
<$Output
|
|
type={OutputType.RelativeTime}
|
|
value={notification.timestamps[NotificationStatus.Triggered]}
|
|
/>
|
|
|
|
{notification.status < NotificationStatus.Seen ? <$UnreadIndicator /> : null}
|
|
|
|
{notification.status < NotificationStatus.Cleared ? (
|
|
<$IconButton
|
|
iconName={IconName.Close}
|
|
shape={ButtonShape.Square}
|
|
size={ButtonSize.XSmall}
|
|
onClick={(e: MouseEvent) => {
|
|
e.stopPropagation();
|
|
|
|
if (notification.status < NotificationStatus.Seen) {
|
|
markSeen(notification);
|
|
} else if (notification.status < NotificationStatus.Cleared) {
|
|
markCleared(notification);
|
|
}
|
|
}}
|
|
/>
|
|
) : null}
|
|
</$ActionItems>
|
|
)}
|
|
</$Header>
|
|
<$Description>{slotCustomContent ?? slotDescription}</$Description>
|
|
{slotAction && <$Action>{slotAction}</$Action>}
|
|
</$Container>
|
|
);
|
|
};
|
|
|
|
const $Container = styled.div<{ isToast?: boolean }>`
|
|
// Params
|
|
--toast-icon-size: 1.75em;
|
|
|
|
// Rules
|
|
${popoverMixins.popover}
|
|
overflow: visible;
|
|
padding: 1rem 1.25rem;
|
|
|
|
${({ isToast }) =>
|
|
isToast
|
|
? css`
|
|
padding: 1rem;
|
|
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
|
// border
|
|
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
|
`
|
|
: css`
|
|
flex: 1;
|
|
background-color: transparent;
|
|
border-radius: 0;
|
|
backdrop-filter: none;
|
|
`}
|
|
`;
|
|
|
|
const $Header = styled.header`
|
|
${layoutMixins.row}
|
|
position: relative;
|
|
`;
|
|
|
|
const $Icon = styled.div`
|
|
${layoutMixins.row}
|
|
float: left;
|
|
|
|
margin-right: 0.5rem;
|
|
|
|
line-height: 1;
|
|
|
|
> svg,
|
|
img {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
}
|
|
`;
|
|
|
|
const $Title = styled.div`
|
|
flex: 1;
|
|
|
|
font: var(--font-base-medium);
|
|
color: var(--color-text-2);
|
|
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
`;
|
|
|
|
const $Description = styled.div`
|
|
margin-top: 0.5rem;
|
|
color: var(--color-text-0);
|
|
font: var(--font-small-book);
|
|
`;
|
|
|
|
const $Action = styled.div`
|
|
margin-top: 0.5rem;
|
|
font: var(--font-small-book);
|
|
`;
|
|
|
|
const $ActionItems = styled.div`
|
|
min-width: fit-content;
|
|
padding-left: 0.5rem;
|
|
height: 100%;
|
|
${layoutMixins.row};
|
|
justify-content: end;
|
|
gap: 0.5rem;
|
|
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
|
|
background-color: var(--color-layer-2);
|
|
font: var(--font-mini-book);
|
|
|
|
display: none;
|
|
z-index: 1;
|
|
|
|
${$Container}:hover & {
|
|
display: flex;
|
|
}
|
|
`;
|
|
|
|
const $UnreadIndicator = styled.div`
|
|
width: 0.5rem;
|
|
height: 0.5rem;
|
|
border-radius: 50%;
|
|
background-color: var(--color-accent);
|
|
border: 1px solid var(--color-layer-2);
|
|
`;
|
|
|
|
const $Output = styled(Output)`
|
|
color: var(--color-text-0);
|
|
`;
|
|
|
|
const $IconButton = styled(IconButton)`
|
|
--button-border: none;
|
|
--button-textColor: var(--color-text-0);
|
|
--button-hover-textColor: var(--color-text-1);
|
|
`;
|