Compare commits

...
Author SHA1 Message Date
jaredvu a123976646 notification adjustments 2023-09-06 13:53:38 -07:00
jaredvu 788fed9c26 Merge branch 'main' into abacus-tracking 2023-09-05 15:03:36 -07:00
jaredvu b8526a5568 Merge branch 'main' into abacus-tracking 2023-09-05 13:01:07 -07:00
jaredvu b0cd8e71ac add tracking protocol 2023-09-02 14:25:23 -07:00
13 changed files with 118 additions and 98 deletions
+1 -1
View File
@@ -37,7 +37,7 @@
"@cosmjs/proto-signing": "^0.31.0", "@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0", "@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0", "@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/abacus": "^0.4.7", "@dydxprotocol/abacus": "^0.4.8",
"@dydxprotocol/v4-client-js": "^0.32.0", "@dydxprotocol/v4-client-js": "^0.32.0",
"@dydxprotocol/v4-localization": "^0.0.25", "@dydxprotocol/v4-localization": "^0.0.25",
"@ethersproject/providers": "^5.7.2", "@ethersproject/providers": "^5.7.2",
+4 -4
View File
@@ -27,8 +27,8 @@ dependencies:
specifier: ^0.31.0 specifier: ^0.31.0
version: 0.31.0 version: 0.31.0
'@dydxprotocol/abacus': '@dydxprotocol/abacus':
specifier: ^0.4.7 specifier: ^0.4.8
version: 0.4.7 version: 0.4.8
'@dydxprotocol/v4-client-js': '@dydxprotocol/v4-client-js':
specifier: ^0.32.0 specifier: ^0.32.0
version: 0.32.0 version: 0.32.0
@@ -1145,8 +1145,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==} resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true dev: true
/@dydxprotocol/abacus@0.4.7: /@dydxprotocol/abacus@0.4.8:
resolution: {integrity: sha512-3c7Dx5H+KeXZYDpAOJs6fqxSNerKqNSD4tuMNn5F7RXAPWUOU8ADQHAFPnrP1PgTxPjfZBZQzLIziKpyXww4NQ==} resolution: {integrity: sha512-hX31m+a5Bwbs4dr5raPDtnflwUzEu/9BIt+LOlQgt1YOViuIse8dozgpgCHvqvA8MttqY3QGPalx5iRB8j8vtw==}
dev: false dev: false
/@dydxprotocol/dydxjs@0.3.0: /@dydxprotocol/dydxjs@0.3.0:
-4
View File
@@ -80,8 +80,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
{group.items.map((item) => ( {group.items.map((item) => (
<Fragment key={item.value}> <Fragment key={item.value}>
<Styled.Item <Styled.Item
// value={item.value} // search by both value and textContent
// value={[group.groupLabel, item.label, item.tag].filter(Boolean).join('|')} // exclude item.value from searchable terms (not guaranteed to be unique)
value={[group.groupLabel, item.value, item.description, item.label, item.tag] value={[group.groupLabel, item.value, item.description, item.label, item.tag]
.filter(Boolean) .filter(Boolean)
.join('|')} .join('|')}
@@ -116,8 +114,6 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
item.subitems?.map((subitem) => ( item.subitems?.map((subitem) => (
<Fragment key={subitem.value}> <Fragment key={subitem.value}>
<Styled.Item <Styled.Item
// value={subitem.value} // search by both value and textContent
// value={[group.groupLabel, item.label, subitem.label, subitem.tag].filter(Boolean).join('|')}
value={[ value={[
group.groupLabel, group.groupLabel,
item.value, item.value,
+4 -1
View File
@@ -40,7 +40,10 @@ export type AbacusFileSystemProtocol = Omit<
Abacus.exchange.dydx.abacus.protocols.FileSystemProtocol, Abacus.exchange.dydx.abacus.protocols.FileSystemProtocol,
'__doNotUseOrImplementIt' '__doNotUseOrImplementIt'
>; >;
export type AbacusTrackingProtocol = Omit<
Abacus.exchange.dydx.abacus.protocols.TrackingProtocol,
'__doNotUseOrImplementIt'
>;
export type FileLocation = Abacus.exchange.dydx.abacus.protocols.FileLocation; export type FileLocation = Abacus.exchange.dydx.abacus.protocols.FileLocation;
export type ThreadingType = Abacus.exchange.dydx.abacus.protocols.ThreadingType; export type ThreadingType = Abacus.exchange.dydx.abacus.protocols.ThreadingType;
export const CoroutineTimer = Abacus.exchange.dydx.abacus.utils.CoroutineTimer; export const CoroutineTimer = Abacus.exchange.dydx.abacus.utils.CoroutineTimer;
+2
View File
@@ -34,6 +34,8 @@ export const STRING_KEYS = {
...WARNINGS_STRING_KEYS, ...WARNINGS_STRING_KEYS,
}; };
export type StringKey = keyof typeof STRING_KEYS;
export type LocaleData = typeof EN_LOCALE_DATA; export type LocaleData = typeof EN_LOCALE_DATA;
export type StringGetterFunction = (a: { export type StringGetterFunction = (a: {
+17 -11
View File
@@ -1,4 +1,5 @@
import { StatusResponse } from "@0xsquid/sdk"; import type { ReactNode } from 'react';
import type { StatusResponse } from '@0xsquid/sdk';
/** implemented in useNotificationTypes */ /** implemented in useNotificationTypes */
export enum NotificationType { export enum NotificationType {
@@ -18,26 +19,31 @@ export type NotificationTypeConfig<
/** React hook to trigger notifications based on app state */ /** React hook to trigger notifications based on app state */
useTrigger: (_: { useTrigger: (_: {
trigger: ( trigger: ({
id,
displayData,
updateKey,
isNew,
}: {
/** Unique ID for the triggered notification */ /** Unique ID for the triggered notification */
id: _NotificationId, id: _NotificationId;
/** Display data for the triggered notification */ /** Display data for the triggered notification */
displayData: NotificationDisplayData, displayData: NotificationDisplayData;
/** /**
* JSON-serializable key. * JSON-serializable key.
* Re-triggers the notification if passed a different value from the last trigger() call (even from a previous browser session). * Re-triggers the notification if passed a different value from the last trigger() call (even from a previous browser session).
* Suggested usage: data dependency array * Suggested usage: data dependency array
*/ */
updateKey?: NotificationUpdateKey, updateKey?: NotificationUpdateKey;
/** /**
* @param true (default): Notification initialized with status NotificationStatus.Triggered * @param true (default): Notification initialized with status NotificationStatus.Triggered
* @param false: Notification initialized with status NotificationStatus.Cleared * @param false: Notification initialized with status NotificationStatus.Cleared
*/ */
isNew?: boolean isNew?: boolean;
) => void; }) => void;
lastUpdated: number; lastUpdated: number;
}) => void; }) => void;
@@ -79,15 +85,15 @@ export type Notifications = Record<NotificationId, Notification<any>>;
/** Notification display data derived from app state at runtime. */ /** Notification display data derived from app state at runtime. */
export type NotificationDisplayData = { export type NotificationDisplayData = {
icon?: React.ReactNode; icon?: React.ReactElement<any, 'svg'>;
title?: string; title?: string;
description?: React.ReactNode; description?: ReactNode;
customContent?: React.ReactNode; customContent?: ReactNode;
customMenuContent?: React.ReactNode; customMenuContent?: ReactNode;
actionDescription?: string; actionDescription?: string;
+38 -39
View File
@@ -1,27 +1,27 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useSelector, shallowEqual, useDispatch } from 'react-redux'; import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
import { AbacusOrderStatus, ORDER_SIDES, ORDER_STATUS_STRINGS } from '@/constants/abacus'; import { AbacusOrderStatus, ORDER_SIDES, ORDER_STATUS_STRINGS } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs'; import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS } from '@/constants/localization';
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications'; import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade'; import { ORDER_SIDE_STRINGS } from '@/constants/trade';
import { useLocalNotifications } from '@/hooks/useLocalNotifications'; import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon'; import { Icon, IconName } from '@/components/Icon';
import { Output, OutputType } from '@/components/Output'; import { Output, OutputType } from '@/components/Output';
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
import { TransferStatusToast } from '@/views/TransferStatus'; import { TransferStatusToast } from '@/views/TransferStatus';
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors'; import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs'; import { openDialog } from '@/state/dialogs';
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
import { useStringGetter } from './useStringGetter'; import { useStringGetter } from './useStringGetter';
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
export const notificationTypes = [ export const notificationTypes = [
{ {
@@ -58,34 +58,33 @@ export const notificationTypes = [
} }
: undefined); : undefined);
if (order) if (order) console.log(order);
trigger( trigger({
order.id, id: order.id,
{ displayData: {
icon: ( icon: <AssetIcon symbol={order.marketId.split('-')[0]} />, //<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />,
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} /> title: `${stringGetter({
), key: order.resources.typeStringKey ?? '',
title: `${stringGetter({ })} ${
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey, order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0
})} ${ ? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 0 : stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED }) }`,
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
}`, description: `${stringGetter({
description: `${stringGetter({ key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]], })} ${order.size} ${order.marketId} @ $${order.price}`,
})} ${order.size} ${order.marketId} @ $${order.price}`, actionDescription: 'View Order',
actionDescription: 'View Order', actionAltText: 'View this order in the Orders tab or the Notifications menu.',
actionAltText: 'View this order in the Orders tab or the Notifications menu.', toastSensitivity:
toastSensitivity: order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background', toastDuration: Infinity,
toastDuration: 5000, },
}, updateKey: [order.status.name, order.size],
[order.status.name, order.size], isNew: !order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated,
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated });
);
} }
}, [orderIds]); }, [orderIds, stringGetter]);
}, },
useNotificationAction: () => { useNotificationAction: () => {
@@ -120,9 +119,9 @@ export const notificationTypes = [
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing'; const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw'; const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw';
trigger( trigger({
txHash, id: txHash,
{ displayData: {
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />, icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
title: stringGetter({ key: getTitleStringKey(type, finished) }), title: stringGetter({ key: getTitleStringKey(type, finished) }),
// TODO: confirm with design what the description should be // TODO: confirm with design what the description should be
@@ -142,10 +141,10 @@ export const notificationTypes = [
customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />, customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />,
toastSensitivity: 'foreground', toastSensitivity: 'foreground',
}, },
[] updateKey: [],
); });
} }
}, [transferNotifications]); }, [transferNotifications, stringGetter]);
}, },
}, } as NotificationTypeConfig<string, []>,
] satisfies NotificationTypeConfig[]; ] satisfies NotificationTypeConfig[];
+17 -22
View File
@@ -92,7 +92,7 @@ const useNotificationsContext = () => {
for (const { type, useTrigger } of notificationTypes) for (const { type, useTrigger } of notificationTypes)
useTrigger({ useTrigger({
trigger: useCallback( trigger: useCallback(
(id, displayData, updateKey, isNew = true) => { ({ id, displayData, updateKey, isNew = true }) => {
const key = getKey({ type, id }); const key = getKey({ type, id });
const notification = notifications[key]; const notification = notifications[key];
@@ -182,28 +182,23 @@ const useNotificationsContext = () => {
const iconUrl = const iconUrl =
displayData.icon && (await renderSvgToDataUrl(displayData.icon).catch(() => undefined)); displayData.icon && (await renderSvgToDataUrl(displayData.icon).catch(() => undefined));
const pushNotification = new globalThis.Notification(displayData.title, { if (displayData.title) {
renotify: true, const pushNotification = new globalThis.Notification(displayData.title, {
tag: getKey(notification), renotify: true,
data: notification, tag: getKey(notification),
body: displayData.description, data: notification,
icon: iconUrl ?? '/favicon.svg', body: displayData.description ?? '',
badge: iconUrl ?? '/favicon.svg', icon: (iconUrl as string) ?? '/favicon.svg',
image: iconUrl ?? '/favicon.svg', badge: (iconUrl as string) ?? '/favicon.svg',
vibrate: displayData.toastSensitivity === 'foreground', image: (iconUrl as string) ?? '/favicon.svg',
requireInteraction: displayData.toastDuration === Infinity, requireInteraction: displayData.toastDuration === Infinity,
// actions: [ });
// {
// action: displayData.actionDescription,
// title: displayData.actionDescription,
// }
// ].slice(0, globalThis.Notification.maxActions),
});
pushNotification.addEventListener('click', () => { pushNotification.addEventListener('click', () => {
onNotificationAction(notification); onNotificationAction(notification);
markSeen(notification); markSeen(notification);
}); });
}
} }
setPushNotificationsLastUpdated(Date.now()); setPushNotificationsLastUpdated(Date.now());
+2 -1
View File
@@ -36,6 +36,7 @@ import AbacusStateNotifier from './stateNotification';
import AbacusLocalizer from './localizer'; import AbacusLocalizer from './localizer';
import AbacusFormatter from './formatter'; import AbacusFormatter from './formatter';
import AbacusThreading from './threading'; import AbacusThreading from './threading';
import AbacusTracking from './tracking';
import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem'; import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem';
import { LocaleSeparators } from '../numbers'; import { LocaleSeparators } from '../numbers';
class AbacusStateManager { class AbacusStateManager {
@@ -59,7 +60,7 @@ class AbacusStateManager {
new AbacusRest(), new AbacusRest(),
this.websocket, this.websocket,
new AbacusChainTransaction(), new AbacusChainTransaction(),
null, new AbacusTracking(),
new AbacusThreading(), new AbacusThreading(),
new CoroutineTimer(), new CoroutineTimer(),
new AbacusFileSystem() new AbacusFileSystem()
+9
View File
@@ -0,0 +1,9 @@
import type { AbacusTrackingProtocol, Nullable } from '@/constants/abacus';
class AbacusTracking implements AbacusTrackingProtocol {
log(event: string, data: Nullable<string>) {
console.log({ event, data });
}
}
export default AbacusTracking;
+4 -2
View File
@@ -21,8 +21,10 @@ const applyComputedStyles = (html: string) => {
const toDataUrl = (bytes: string, type = 'image/svg+xml') => const toDataUrl = (bytes: string, type = 'image/svg+xml') =>
new Promise<string | ArrayBuffer | null>((resolve, reject) => { new Promise<string | ArrayBuffer | null>((resolve, reject) => {
Object.assign(new FileReader(), { Object.assign(new FileReader(), {
onload: (e) => resolve(e.target.result), onload: (e: {
onerror: (e) => reject(e.target.error), target: { result: string | ArrayBuffer | PromiseLike<string | ArrayBuffer | null> | null };
}) => resolve(e.target.result),
onerror: (e: { target: { error: Error } }) => reject(e.target.error),
}).readAsDataURL(new File([bytes], '', { type })); }).readAsDataURL(new File([bytes], '', { type }));
}); });
@@ -7,7 +7,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
import { AbacusOrderStatus, AbacusOrderTypes, type Nullable } from '@/constants/abacus'; import { AbacusOrderStatus, AbacusOrderTypes, type Nullable } from '@/constants/abacus';
import { ButtonAction } from '@/constants/buttons'; import { ButtonAction } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization'; import { STRING_KEYS, StringKey } from '@/constants/localization';
import { AssetIcon } from '@/components/AssetIcon'; import { AssetIcon } from '@/components/AssetIcon';
import { Button } from '@/components/Button'; import { Button } from '@/components/Button';
@@ -38,6 +38,9 @@ type ElementProps = {
setIsOpen: (open: boolean) => void; setIsOpen: (open: boolean) => void;
}; };
const isStringKey = (key: Nullable<string>): key is StringKey =>
key != null && STRING_KEYS.hasOwnProperty(key);
export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => { export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter(); const stringGetter = useStringGetter();
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -47,7 +50,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
const { cancelOrder } = useSubaccount(); const { cancelOrder } = useSubaccount();
const { const {
asset = {}, asset,
cancelReason, cancelReason,
createdAtMilliseconds, createdAtMilliseconds,
expiresAtMilliseconds, expiresAtMilliseconds,
@@ -57,7 +60,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
price, price,
reduceOnly, reduceOnly,
totalFilled, totalFilled,
resources = {}, resources,
size, size,
status, status,
stepSizeDecimals, stepSizeDecimals,
@@ -109,9 +112,9 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
<Styled.Row> <Styled.Row>
<Styled.StatusIcon iconName={statusIcon} color={statusIconColor} /> <Styled.StatusIcon iconName={statusIcon} color={statusIconColor} />
<Styled.Status> <Styled.Status>
{statusStringKey {isStringKey(statusStringKey)
? stringGetter({ key: statusStringKey }) ? stringGetter({ key: statusStringKey })
: resources.statusStringKey : isStringKey(resources.statusStringKey)
? stringGetter({ key: resources.statusStringKey }) ? stringGetter({ key: resources.statusStringKey })
: undefined} : undefined}
</Styled.Status> </Styled.Status>
@@ -121,7 +124,9 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
{ {
key: 'cancel-reason', key: 'cancel-reason',
label: stringGetter({ key: STRING_KEYS.CANCEL_REASON }), label: stringGetter({ key: STRING_KEYS.CANCEL_REASON }),
value: cancelReason ? stringGetter({ key: STRING_KEYS[cancelReason] }) : undefined, value: isStringKey(cancelReason)
? stringGetter({ key: STRING_KEYS[cancelReason] })
: undefined,
}, },
{ {
key: 'amount', key: 'amount',
+9 -7
View File
@@ -1,10 +1,12 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import styled from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { type Notification, NotificationStatus } from '@/constants/notifications'; import { type Notification, NotificationStatus } from '@/constants/notifications';
import { useStringGetter } from '@/hooks';
import { useNotifications } from '@/hooks/useNotifications'; import { useNotifications } from '@/hooks/useNotifications';
import { CloseIcon } from '@/icons';
import { Button } from '@/components/Button'; import { Button } from '@/components/Button';
import { ButtonAction, ButtonSize } from '@/constants/buttons'; import { ButtonAction, ButtonSize } from '@/constants/buttons';
@@ -13,9 +15,7 @@ import { DialogPlacement } from '@/components/Dialog';
import { Output, OutputType } from '@/components/Output'; import { Output, OutputType } from '@/components/Output';
import { IconButton } from '@/components/IconButton'; import { IconButton } from '@/components/IconButton';
import { Toolbar } from '@/components/Toolbar'; import { Toolbar } from '@/components/Toolbar';
import { CloseIcon } from '@/icons';
import styled from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins'; import { layoutMixins } from '@/styles/layoutMixins';
type ElementProps = { type ElementProps = {
@@ -27,6 +27,8 @@ export const NotificationsMenu = ({
slotTrigger, slotTrigger,
placement = DialogPlacement.Sidebar, placement = DialogPlacement.Sidebar,
}: ElementProps) => { }: ElementProps) => {
const stringGetter = useStringGetter();
const { const {
notifications, notifications,
getDisplayData, getDisplayData,
@@ -69,7 +71,7 @@ export const NotificationsMenu = ({
.map(([status, notifications]) => ({ .map(([status, notifications]) => ({
group: status, group: status,
groupLabel: { groupLabel: {
[NotificationStatus.Triggered]: 'New', [NotificationStatus.Triggered]: stringGetter({ key: STRING_KEYS.NEW }),
// [NotificationStatus.Updated]: 'Updates', // [NotificationStatus.Updated]: 'Updates',
[NotificationStatus.Seen]: 'Seen', [NotificationStatus.Seen]: 'Seen',
[NotificationStatus.Cleared]: 'Archived', [NotificationStatus.Cleared]: 'Archived',
@@ -135,7 +137,7 @@ export const NotificationsMenu = ({
isOpen={isMenuOpen || placement === DialogPlacement.Inline} isOpen={isMenuOpen || placement === DialogPlacement.Inline}
setIsOpen={setIsMenuOpen} setIsOpen={setIsMenuOpen}
items={items} items={items}
title="Notifications" title={stringGetter({ key: STRING_KEYS.NOTIFICATIONS })}
slotTrigger={ slotTrigger={
<$TriggerContainer> <$TriggerContainer>
{slotTrigger} {slotTrigger}
@@ -168,7 +170,7 @@ export const NotificationsMenu = ({
), ),
}} }}
> >
Clear All {stringGetter({ key: STRING_KEYS.CLEAR })}
</Button> </Button>
</$FooterToolbar> </$FooterToolbar>
} }