From b8a89a5d739d2f626a9c262112874d0d33b68703 Mon Sep 17 00:00:00 2001 From: Eddie Dugan Date: Thu, 23 Feb 2023 17:31:39 -0800 Subject: [PATCH 1/5] feat: new error view --- src/components/ActionButton.tsx | 36 +++++-- src/components/BottomSheetModal.tsx | 6 +- src/components/Column.tsx | 3 +- src/components/Error/ErrorBoundary.tsx | 40 +++---- src/components/Error/ErrorDialog.tsx | 101 ----------------- src/components/Error/ErrorView.tsx | 91 ++++++++++++++++ src/components/Expando.tsx | 102 ++++++++++-------- .../Swap/Settings/MaxSlippageSelect.tsx | 2 - .../Swap/Settings/TransactionTtlInput.tsx | 2 - src/components/Swap/Status/StatusDialog.tsx | 2 +- src/components/Swap/Toolbar/index.tsx | 3 +- src/css/hover.ts | 8 ++ src/errors.ts | 6 +- 13 files changed, 216 insertions(+), 186 deletions(-) delete mode 100644 src/components/Error/ErrorDialog.tsx create mode 100644 src/components/Error/ErrorView.tsx create mode 100644 src/css/hover.ts diff --git a/src/components/ActionButton.tsx b/src/components/ActionButton.tsx index fc3fc35a1..6a98acf8d 100644 --- a/src/components/ActionButton.tsx +++ b/src/components/ActionButton.tsx @@ -7,10 +7,10 @@ import Button from './Button' import Row, { RowProps } from './Row' import Tooltip from './Tooltip' -const StyledButton = styled(Button)<{ shouldUseDisabledColor?: boolean }>` - border-radius: ${({ theme }) => theme.borderRadius.medium}em; +const StyledButton = styled(Button)<{ shouldUseDisabledColor?: boolean; narrow?: boolean }>` + border-radius: ${({ theme, narrow }) => (narrow ? theme.borderRadius.small : theme.borderRadius.medium)}em; flex-grow: 1; - max-height: 56px; + max-height: ${({ narrow }) => (narrow ? '2.5em' : '3.5em')}; transition: background-color ${AnimationSpeed.Medium} ease-out, border-radius ${AnimationSpeed.Medium} ease-out, flex-grow ${AnimationSpeed.Medium} ease-out; ${({ theme, disabled, shouldUseDisabledColor }) => @@ -57,11 +57,11 @@ const actionCss = css` } ` -export const Overlay = styled(Row)<{ hasAction: boolean }>` - border-radius: ${({ theme }) => theme.borderRadius.medium}em; +export const Overlay = styled(Row)<{ hasAction: boolean; narrow?: boolean }>` + border-radius: ${({ theme, narrow }) => (narrow ? theme.borderRadius.small : theme.borderRadius.medium)}em; flex-flow: row-reverse nowrap; margin-top: 0.25em; - min-height: 3.5em; + min-height: ${({ narrow }) => (narrow ? '2.5em' : '3.5em')}; transition: padding ${AnimationSpeed.Medium} ease-out; ${({ hasAction }) => hasAction && actionCss} ` @@ -84,6 +84,7 @@ interface BaseProps { action?: Action wrapperProps?: Omit, keyof RowProps> shouldUseDisabledColor?: boolean + narrow?: boolean } export type ActionButtonProps = BaseProps & Omit, keyof BaseProps> @@ -96,6 +97,7 @@ export default function ActionButton({ onClick, children, wrapperProps, + narrow, ...rest }: ActionButtonProps) { const textColor = useMemo(() => { @@ -115,17 +117,35 @@ export default function ActionButton({ } }, [color, disabled]) + const buttonSize = useMemo(() => { + if (narrow) { + return 'small' + } + if (action) { + return 'medium' + } + return 'large' + }, [narrow, action]) + return ( - + {!action?.hideButton && ( - + {action?.children || children} diff --git a/src/components/BottomSheetModal.tsx b/src/components/BottomSheetModal.tsx index 59d670219..c8eda53fd 100644 --- a/src/components/BottomSheetModal.tsx +++ b/src/components/BottomSheetModal.tsx @@ -1,4 +1,5 @@ import { Trans } from '@lingui/macro' +import { iconHoverCss } from 'css/hover' import { X } from 'icons' import { forwardRef, PropsWithChildren, useState } from 'react' import { createPortal } from 'react-dom' @@ -75,10 +76,7 @@ const Wrapper = styled.div<{ open: boolean }>` ` const StyledXButton = styled(X)` - :hover { - cursor: pointer; - opacity: 0.6; - } + ${iconHoverCss} ` type BottomSheetModalProps = PropsWithChildren<{ diff --git a/src/components/Column.tsx b/src/components/Column.tsx index 47cb75f41..8d29fcb7b 100644 --- a/src/components/Column.tsx +++ b/src/components/Column.tsx @@ -7,6 +7,7 @@ export interface ColumnProps { justify?: string gap?: number padded?: true + padding?: string flex?: true grow?: true css?: ReturnType @@ -22,7 +23,7 @@ const Column = styled.div` grid-auto-flow: row; grid-template-columns: 1fr; justify-content: ${({ justify }) => justify ?? 'space-between'}; - padding: ${({ padded }) => padded && '0.75em'}; + padding: ${({ padded, padding }) => padding ?? (padded && '0.75em')}; ${({ css }) => css} ` diff --git a/src/components/Error/ErrorBoundary.tsx b/src/components/Error/ErrorBoundary.tsx index 5fba5ff07..3d0272db1 100644 --- a/src/components/Error/ErrorBoundary.tsx +++ b/src/components/Error/ErrorBoundary.tsx @@ -1,8 +1,8 @@ -import { DEFAULT_ERROR_ACTION, DEFAULT_ERROR_HEADER, WidgetError } from 'errors' +import { t } from '@lingui/macro' +import { DEFAULT_ERROR_HEADER, WidgetError } from 'errors' import { Component, ErrorInfo, PropsWithChildren, useCallback, useState } from 'react' -import Dialog from '../Dialog' -import ErrorDialog from './ErrorDialog' +import ErrorView from './ErrorView' export type OnError = (error: Error, info?: ErrorInfo) => void @@ -53,30 +53,30 @@ export default class ErrorBoundary extends Component - { - this.setState({ error: undefined }) - } - : () => window.location.reload() - } - /> - + { + this.setState({ error: undefined }) + } + : () => window.location.reload() + } + onClick={() => { + window.open('https://support.uniswap.org/', '_blank', 'noopener,noreferrer') + }} + /> ) } render() { if (this.state.error) { - return this.renderErrorDialog(this.state.error) + return this.renderErrorView(this.state.error) } return this.props.children } diff --git a/src/components/Error/ErrorDialog.tsx b/src/components/Error/ErrorDialog.tsx deleted file mode 100644 index c0362fb76..000000000 --- a/src/components/Error/ErrorDialog.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Trans } from '@lingui/macro' -import ActionButton from 'components/ActionButton' -import Column from 'components/Column' -import Expando from 'components/Expando' -import { AlertTriangle, Icon, LargeIcon } from 'icons' -import { Info as InfoIcon } from 'icons' -import { ReactNode, useCallback, useState } from 'react' -import styled from 'styled-components/macro' -import { AnimationSpeed, Color, ThemedText } from 'theme' - -const HeaderIcon = styled(LargeIcon)` - flex-grow: 1; - transition: height ${AnimationSpeed.Medium}, width ${AnimationSpeed.Medium}; - - svg { - transition: height ${AnimationSpeed.Medium}, width ${AnimationSpeed.Medium}; - } -` - -interface StatusHeaderProps { - icon: Icon - iconColor?: Color - iconSize?: number - children: ReactNode -} - -export function StatusHeader({ icon: Icon, iconColor, iconSize = 2.5, children }: StatusHeaderProps) { - return ( - <> - - - - {children} - - - - ) -} - -const ErrorHeader = styled(Column)<{ open: boolean }>` - transition: gap ${AnimationSpeed.Medium}; - - div:last-child { - max-height: ${({ open }) => (open ? 0 : 60 / 14)}em; // 3 * line-height - overflow-y: hidden; - transition: max-height ${AnimationSpeed.Medium}; - } -` - -const ExpandoContent = styled(ThemedText.Code)` - margin: 0.5em 0; -` - -interface ErrorDialogProps { - header?: ReactNode - message: ReactNode - error?: Error - action: ReactNode - onClick: () => void -} - -export default function ErrorDialog({ header, message, error, action, onClick }: ErrorDialogProps) { - const [open, setOpen] = useState(false) - const onExpand = useCallback(() => setOpen((open) => !open), []) - - return ( - - - - {header || Something went wrong.} - {!open && {message}} - - - - {error ? ( - - - Error details - - } - open={open} - onExpand={onExpand} - height={7.5} - > - - {error.name} - {error.message ? `: ${error.message}` : ''} - - - ) : ( - - )} - - {action} - - - - ) -} diff --git a/src/components/Error/ErrorView.tsx b/src/components/Error/ErrorView.tsx new file mode 100644 index 000000000..d693449a6 --- /dev/null +++ b/src/components/Error/ErrorView.tsx @@ -0,0 +1,91 @@ +import { Trans } from '@lingui/macro' +import ActionButton from 'components/ActionButton' +import Column from 'components/Column' +import Expando from 'components/Expando' +import Row from 'components/Row' +import { iconHoverCss } from 'css/hover' +import { AlertTriangle, Icon, LargeIcon, X } from 'icons' +import { ReactNode, useState } from 'react' +import styled from 'styled-components/macro' +import { Color, ThemedText } from 'theme' + +const HeaderIcon = styled(LargeIcon)` + flex-grow: 1; + margin: 2em 0; +` + +interface StatusHeaderProps { + icon: Icon + iconColor?: Color + iconSize?: number + children: ReactNode +} + +export function StatusHeader({ icon: Icon, iconColor, iconSize = 2.5, children }: StatusHeaderProps) { + return ( + <> + + + + {children} + + + + ) +} + +const ExpandoContent = styled(ThemedText.Code)` + margin: 0.5em; +` + +const StyledXButton = styled(X)` + ${iconHoverCss} +` + +const ErrorDialogWrapper = styled(Column)` + background-color: ${({ theme }) => theme.container}; +` + +interface ErrorDialogProps { + header?: ReactNode + message: ReactNode + error?: Error + action: ReactNode + onClick: () => void + onDismiss: () => void +} + +export default function ErrorDialog({ header, message, error, action, onClick, onDismiss }: ErrorDialogProps) { + const [open, setOpen] = useState(false) + + return ( + + + + + + + {header || Something went wrong} + {message} + + + {error ? ( + Show less : Show more} + open={open} + onExpand={() => setOpen((open) => !open)} + maxHeight={11.5 /* em */} + > + + {error.toString()} + + + ) : ( + + )} + + {action} + + + ) +} diff --git a/src/components/Expando.tsx b/src/components/Expando.tsx index cf9bd86a1..07f1696af 100644 --- a/src/components/Expando.tsx +++ b/src/components/Expando.tsx @@ -5,12 +5,22 @@ import Rule from 'components/Rule' import useScrollbar from 'hooks/useScrollbar' import { Expando as ExpandoIcon } from 'icons' import { PropsWithChildren, ReactNode, useState } from 'react' -import styled, { css } from 'styled-components/macro' +import styled from 'styled-components/macro' import { AnimationSpeed, ThemedText } from 'theme' -const HeaderColumn = styled(Column)` +const HeaderRow = styled(Column)` cursor: pointer; - transition: gap ${AnimationSpeed.Medium}; + padding: 1.25em 1.5em; +` + +const StyledWrapper = styled(Column)<{ expanded: boolean }>` + background-color: ${({ theme }) => theme.module}; + border-radius: ${({ theme }) => theme.borderRadius.medium}rem; + overflow: hidden; + + @supports (overflow: clip) { + overflow: clip; + } ` const TitleRow = styled(Row)` @@ -23,19 +33,7 @@ const TitleHeader = styled.div` justify-content: center; ` -const bottomCss = css` - :after { - background: linear-gradient(transparent, ${({ theme }) => theme.dialog}); - bottom: 0; - content: ''; - height: 0.75em; - pointer-events: none; - position: absolute; - width: calc(100% - 1em); - } -` - -const MAX_HEIGHT = 20 +const MAX_HEIGHT = 20 // em function getExpandoContentHeight(height: number | undefined, maxHeight: number | undefined): number { return Math.min(height ?? MAX_HEIGHT, maxHeight ?? MAX_HEIGHT) @@ -45,13 +43,11 @@ const ExpandoColumn = styled(Column)<{ height?: number maxHeight?: number open: boolean - showBottomGradient: boolean }>` max-height: ${({ open, height, maxHeight }) => (open ? getExpandoContentHeight(height, maxHeight) : 0)}em; overflow: hidden; position: relative; transition: max-height ${AnimationSpeed.Medium}, padding ${AnimationSpeed.Medium}; - ${({ showBottomGradient }) => showBottomGradient && bottomCss} ` const InnerColumn = styled(Column)<{ height?: number; maxHeight?: number }>` @@ -74,9 +70,7 @@ interface ExpandoProps extends ColumnProps { // If relying on auto-sizing, this should be something close to (but still larger than) // the content's height. Otherwise, the animation will feel fast. maxHeight?: number - hideRulers?: boolean - styledTitleWrapper?: boolean - showBottomGradient?: boolean + styledWrapper?: boolean } /** A scrollable Expando with an absolute height. */ @@ -88,37 +82,57 @@ export default function Expando({ height, maxHeight, children, - hideRulers, - styledTitleWrapper = true, - showBottomGradient = true, + styledWrapper = true, ...rest }: PropsWithChildren) { const [scrollingEl, setScrollingEl] = useState(null) const scrollbar = useScrollbar(scrollingEl, { hideScrollbar: true }) return ( - {styledTitleWrapper ? ( - - {!hideRulers && } - - - {title} - - {iconPrefix && {iconPrefix}} - - - - - {!hideRulers && open && } - + {styledWrapper ? ( + + + + + {title} + + {iconPrefix && {iconPrefix}} + + + + + + {open && } + + + {children} + + + ) : ( - title + <> + {title} + + + {children} + + + )} - - - {children} - - ) } diff --git a/src/components/Swap/Settings/MaxSlippageSelect.tsx b/src/components/Swap/Settings/MaxSlippageSelect.tsx index 5e2f767bc..724e77a6c 100644 --- a/src/components/Swap/Settings/MaxSlippageSelect.tsx +++ b/src/components/Swap/Settings/MaxSlippageSelect.tsx @@ -142,8 +142,6 @@ export default function MaxSlippageSelect() { return (