From ff66580b3d739f0d62556c531873bee3e521c816 Mon Sep 17 00:00:00 2001 From: unclebill Date: Mon, 18 Apr 2022 10:29:24 +0800 Subject: [PATCH] feat: add Confirm as common ui --- .../src/components/shared/WalletStatusBox.tsx | 9 +- .../src/contexts/SharedContextProvider.tsx | 5 +- .../shared/src/contexts/UITaskManager.tsx | 82 +++++++++++++++++++ .../src/contexts/common/Confirm/Dialog.tsx | 39 +++++++++ .../src/contexts/common/Confirm/index.tsx | 16 ++++ .../src/contexts/common/Confirm/types.ts | 6 ++ packages/shared/src/contexts/common/index.tsx | 8 ++ .../contexts/components/DialogDismissIcon.tsx | 4 - .../evm/TokenPicker/SelectTokenDialog.tsx | 12 +-- .../src/contexts/evm/TokenPicker/index.tsx | 72 +++------------- packages/shared/src/contexts/index.ts | 3 +- packages/shared/src/locales/en-US.json | 4 +- 12 files changed, 182 insertions(+), 78 deletions(-) create mode 100644 packages/shared/src/contexts/UITaskManager.tsx create mode 100644 packages/shared/src/contexts/common/Confirm/Dialog.tsx create mode 100644 packages/shared/src/contexts/common/Confirm/index.tsx create mode 100644 packages/shared/src/contexts/common/Confirm/types.ts create mode 100644 packages/shared/src/contexts/common/index.tsx diff --git a/packages/mask/src/components/shared/WalletStatusBox.tsx b/packages/mask/src/components/shared/WalletStatusBox.tsx index f75f919884b7..7adeb276572a 100644 --- a/packages/mask/src/components/shared/WalletStatusBox.tsx +++ b/packages/mask/src/components/shared/WalletStatusBox.tsx @@ -17,7 +17,7 @@ import { useReverseAddress, useWallet, } from '@masknet/plugin-infra/web3' -import { FormattedAddress, useSnackbarCallback, WalletIcon } from '@masknet/shared' +import { FormattedAddress, useShowConfirm, useSnackbarCallback, WalletIcon } from '@masknet/shared' import { WalletMessages } from '../../plugins/Wallet/messages' import { useI18N } from '../../utils' import Services from '../../extension/service' @@ -136,6 +136,7 @@ export function WalletStatusBox(props: WalletStatusBox) { ) // #endregion + const showConfirm = useShowConfirm() const onDisconnect = useCallback(async () => { switch (providerType) { case ProviderType.WalletConnect: @@ -199,6 +200,12 @@ export function WalletStatusBox(props: WalletStatusBox) { + {!props.disableChange && (
{providerType === ProviderType.WalletConnect || providerType === ProviderType.Fortmatic ? ( diff --git a/packages/shared/src/contexts/SharedContextProvider.tsx b/packages/shared/src/contexts/SharedContextProvider.tsx index 67838690496c..8ea25d438baa 100644 --- a/packages/shared/src/contexts/SharedContextProvider.tsx +++ b/packages/shared/src/contexts/SharedContextProvider.tsx @@ -1,11 +1,14 @@ import type { FC, PropsWithChildren } from 'react' import { BaseSharedUIProvider } from './base' +import { CommonUIProvider } from './common' import { EvmSharedUIProvider } from './evm' export const SharedContextProvider: FC> = ({ children }) => { return ( - {children} + + {children} + ) } diff --git a/packages/shared/src/contexts/UITaskManager.tsx b/packages/shared/src/contexts/UITaskManager.tsx new file mode 100644 index 000000000000..97c4a01fef9d --- /dev/null +++ b/packages/shared/src/contexts/UITaskManager.tsx @@ -0,0 +1,82 @@ +import { defer, DeferTuple } from '@dimensiondev/kit' +import { EMPTY_LIST } from '@masknet/shared-base' +import { createContext, createElement, FC, FunctionComponent, PropsWithChildren, useMemo, useState } from 'react' +import type { InjectedDialogProps } from './components' + +interface ContextOptionsGeneric { + show(options: T): Promise +} + +type BaseDialogProps = Pick + +export const createUITaskManager = < + TaskOptions, + Result, + Props extends BaseDialogProps, + ResolveProp extends keyof Props, +>( + Component: FunctionComponent, + taskResolveFieldKey: ResolveProp, +) => { + type ContextOptions = ContextOptionsGeneric + const TaskManagerContext = createContext(null!) + let id = 0 + + type TaskDeferTuple = DeferTuple + interface TaskBase { + id: number + promise: TaskDeferTuple[0] + resolve: TaskDeferTuple[1] + reject: TaskDeferTuple[2] + } + interface Task extends TaskBase { + taskOptions: TaskOptions + } + + const TaskManagerProvider: FC> = ({ children }) => { + const [tasks, setTasks] = useState(EMPTY_LIST) + + const contextValue = useMemo(() => { + const removeTask = (task: Task) => { + setTasks((list) => list.filter((t) => t !== task)) + } + + return { + show: (options: TaskOptions) => { + const [promise, resolve, reject] = defer() + id += 1 + const newTask: Task = { id, promise, resolve, reject, taskOptions: options } + setTasks((list) => [...list, newTask]) + promise.then(() => { + removeTask(newTask) + }) + return promise + }, + } + }, []) + + return ( + + {children} + {tasks.map((task) => { + return createElement( + Component, + { + key: task.id, + open: true, + ...task.taskOptions, + [taskResolveFieldKey]: (result: Result | null) => { + task.resolve(result) + }, + onClose: () => { + task.resolve(null) + }, + } as unknown as Props, + children, + ) + })} + + ) + } + return { TaskManagerContext, TaskManagerProvider } +} diff --git a/packages/shared/src/contexts/common/Confirm/Dialog.tsx b/packages/shared/src/contexts/common/Confirm/Dialog.tsx new file mode 100644 index 000000000000..fdcff6a28776 --- /dev/null +++ b/packages/shared/src/contexts/common/Confirm/Dialog.tsx @@ -0,0 +1,39 @@ +import { makeStyles } from '@masknet/theme' +import { Button, DialogActions, DialogContent } from '@mui/material' +import type { FC } from 'react' +import { useSharedI18N } from '../../../locales' +import { InjectedDialog, InjectedDialogProps } from '../../components' +import type { ConfirmOptions } from './types' + +const useStyles = makeStyles()((theme) => ({ + content: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + minHeight: 80, + margin: theme.spacing(0, 1), + fontSize: 18, + fontWeight: 'bold', + }, + button: { + margin: theme.spacing(1), + }, +})) + +export interface ConfirmDialogProps extends ConfirmOptions, Omit {} + +export const ConfirmDialog: FC = ({ title, confirmLabel, content, onConfirm, ...rest }) => { + const t = useSharedI18N() + const { classes } = useStyles() + + return ( + + {content} + + + + + ) +} diff --git a/packages/shared/src/contexts/common/Confirm/index.tsx b/packages/shared/src/contexts/common/Confirm/index.tsx new file mode 100644 index 000000000000..796c7c4a0bd4 --- /dev/null +++ b/packages/shared/src/contexts/common/Confirm/index.tsx @@ -0,0 +1,16 @@ +import { useContext } from 'react' +import { createUITaskManager } from '../../UITaskManager' +import { ConfirmDialog, ConfirmDialogProps } from './Dialog' +import type { ConfirmOptions } from './types' + +const { TaskManagerContext, TaskManagerProvider: ConfirmProvider } = createUITaskManager< + ConfirmOptions, + void, + ConfirmDialogProps, + 'onConfirm' +>(ConfirmDialog, 'onConfirm') + +export const useShowConfirm = () => { + return useContext(TaskManagerContext).show +} +export { ConfirmProvider } diff --git a/packages/shared/src/contexts/common/Confirm/types.ts b/packages/shared/src/contexts/common/Confirm/types.ts new file mode 100644 index 000000000000..00b45f4825cc --- /dev/null +++ b/packages/shared/src/contexts/common/Confirm/types.ts @@ -0,0 +1,6 @@ +export interface ConfirmOptions { + title?: string + content: string + confirmLabel?: string + onConfirm?(): void +} diff --git a/packages/shared/src/contexts/common/index.tsx b/packages/shared/src/contexts/common/index.tsx new file mode 100644 index 000000000000..b02969f64623 --- /dev/null +++ b/packages/shared/src/contexts/common/index.tsx @@ -0,0 +1,8 @@ +import type { FC, PropsWithChildren } from 'react' +import { ConfirmProvider } from './Confirm' + +export * from './Confirm' + +export const CommonUIProvider: FC> = ({ children }) => { + return {children} +} diff --git a/packages/shared/src/contexts/components/DialogDismissIcon.tsx b/packages/shared/src/contexts/components/DialogDismissIcon.tsx index b6b0af2ddd4c..4636c45cb52a 100644 --- a/packages/shared/src/contexts/components/DialogDismissIcon.tsx +++ b/packages/shared/src/contexts/components/DialogDismissIcon.tsx @@ -1,5 +1,3 @@ -// see https://github.com/import-js/eslint-plugin-import/issues/2288 -// eslint-disable-next-line import/no-deprecated import { useTheme, useMediaQuery } from '@mui/material' import CloseIcon from '@mui/icons-material/Close' import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded' @@ -11,8 +9,6 @@ export interface DialogDismissIconUIProps { export function DialogDismissIcon(props: DialogDismissIconUIProps) { const close = const back = - // see https://github.com/import-js/eslint-plugin-import/issues/2288 - // eslint-disable-next-line import/no-deprecated const auto = useMediaQuery(`(min-width: ${useTheme().breakpoints.values.sm}px)`) if (!props.style || props.style === 'auto') return auto ? close : back if (props.style === 'back') return back diff --git a/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx b/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx index 37c841bc85e3..ebe0ce1adc7d 100644 --- a/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx +++ b/packages/shared/src/contexts/evm/TokenPicker/SelectTokenDialog.tsx @@ -2,13 +2,11 @@ import { EMPTY_LIST } from '@masknet/shared-base' import { ERC20TokenList, useSharedI18N } from '@masknet/shared' import { makeStyles, MaskColorVar } from '@masknet/theme' import { ChainId, FungibleTokenDetailed, useTokenConstants } from '@masknet/web3-shared-evm' -// see https://github.com/import-js/eslint-plugin-import/issues/2288 -// eslint-disable-next-line import/no-deprecated import { DialogContent, Theme, useMediaQuery } from '@mui/material' import type { FC } from 'react' import { useBaseUIRuntime } from '../../base' import { MINDS_ID } from '../../../constants' -import { InjectedDialog } from '../../components' +import { InjectedDialog, InjectedDialogProps } from '../../components' import { useRowSize } from './useRowSize' interface StyleProps { @@ -50,14 +48,11 @@ export interface PickTokenOptions { blacklist?: string[] tokens?: FungibleTokenDetailed[] selectedTokens?: string[] -} - -export interface SelectTokenDialogProps extends PickTokenOptions { - open: boolean onSelect?(token: FungibleTokenDetailed): void - onClose?(): void } +export interface SelectTokenDialogProps extends PickTokenOptions, Omit {} + export const SelectTokenDialog: FC = ({ open, chainId, @@ -76,7 +71,6 @@ export const SelectTokenDialog: FC = ({ const compact = networkIdentifier === MINDS_ID const { classes } = useStyles({ compact, disablePaddingTop: isDashboard }) const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(chainId) - // eslint-disable-next-line import/no-deprecated const isMdScreen = useMediaQuery((theme) => theme.breakpoints.down('md')) const rowSize = useRowSize() diff --git a/packages/shared/src/contexts/evm/TokenPicker/index.tsx b/packages/shared/src/contexts/evm/TokenPicker/index.tsx index b5abae98c24f..0832ace429f6 100644 --- a/packages/shared/src/contexts/evm/TokenPicker/index.tsx +++ b/packages/shared/src/contexts/evm/TokenPicker/index.tsx @@ -1,66 +1,16 @@ -import { defer, DeferTuple } from '@dimensiondev/kit' -import { EMPTY_LIST } from '@masknet/shared-base' import type { FungibleTokenDetailed } from '@masknet/web3-shared-evm' -import { createContext, FC, PropsWithChildren, useCallback, useContext, useMemo, useState } from 'react' -import { PickTokenOptions, SelectTokenDialog } from './SelectTokenDialog' +import { useContext } from 'react' +import { createUITaskManager } from '../../UITaskManager' +import { PickTokenOptions, SelectTokenDialog, SelectTokenDialogProps } from './SelectTokenDialog' -interface ContextOptions { - pickToken: (options: PickTokenOptions) => Promise -} -const TokenPickerContext = createContext(null!) - -type PickerDeferTuple = DeferTuple - -interface Task { - id: number - promise: PickerDeferTuple[0] - resolve: PickerDeferTuple[1] - reject: PickerDeferTuple[2] - pickerOptions: PickTokenOptions -} - -let id = 0 -export const TokenPickerProvider: FC> = ({ children }) => { - const [tasks, setTasks] = useState(EMPTY_LIST) - - const removeTask = useCallback((task: Task) => { - setTasks((list) => list.filter((t) => t !== task)) - }, []) - - const contextValue = useMemo(() => { - return { - pickToken: (options: PickTokenOptions) => { - const [promise, resolve, reject] = defer() - id += 1 - const newTask: Task = { id, promise, resolve, reject, pickerOptions: options } - setTasks((list) => [...list, newTask]) - return promise - }, - } - }, []) - - return ( - - {children} - {tasks.map((task) => ( - { - task.resolve(token) - removeTask(task) - }} - onClose={() => { - task.resolve(null) - removeTask(task) - }} - /> - ))} - - ) -} +const { TaskManagerContext, TaskManagerProvider: TokenPickerProvider } = createUITaskManager< + PickTokenOptions, + FungibleTokenDetailed, + SelectTokenDialogProps, + 'onSelect' +>(SelectTokenDialog, 'onSelect') export const usePickToken = () => { - return useContext(TokenPickerContext).pickToken + return useContext(TaskManagerContext).show } +export { TokenPickerProvider } diff --git a/packages/shared/src/contexts/index.ts b/packages/shared/src/contexts/index.ts index f56942ef31c8..036e11d535e1 100644 --- a/packages/shared/src/contexts/index.ts +++ b/packages/shared/src/contexts/index.ts @@ -1,4 +1,5 @@ -export * from './evm' export * from './base' +export * from './common' export * from './components' +export * from './evm' export * from './SharedContextProvider' diff --git a/packages/shared/src/locales/en-US.json b/packages/shared/src/locales/en-US.json index c0aca43099c6..c61a23329f2b 100644 --- a/packages/shared/src/locales/en-US.json +++ b/packages/shared/src/locales/en-US.json @@ -20,5 +20,7 @@ "address_viewer_address_name_rns": "RNS", "address_viewer_address_name_address": "address", "address_viewer_address_name_twitter": "twitter blue", - "dialog_dismiss": "Dismiss" + "dialog_dismiss": "Dismiss", + "dialog_confirm": "Confirm", + "dialog_confirm_label": "Confirm" }