From 378b3e4508c8d9a32c0b7ba0b4c5f2a5ba32e193 Mon Sep 17 00:00:00 2001 From: mikasackermn Date: Wed, 17 Jan 2024 09:35:42 +0000 Subject: [PATCH] feat: for long routes, we should show a shorter version and hide the rest in a button. --- .vscode/settings.json | 2 +- .../src/components/Quote/Quote.styles.ts | 47 +++- .../embedded/src/components/Quote/Quote.tsx | 65 +----- .../src/components/Quote/Quote.types.ts | 17 ++ .../src/components/Quote/QuoteTrigger .tsx | 215 ++++++++++++++++++ widget/embedded/src/utils/quote.ts | 19 +- .../src/components/Tooltip/Tooltip.styles.ts | 7 + widget/ui/src/components/Tooltip/Tooltip.tsx | 3 +- .../src/components/Tooltip/Tooltip.types.ts | 8 +- widget/ui/src/theme.ts | 1 + 10 files changed, 317 insertions(+), 67 deletions(-) create mode 100644 widget/embedded/src/components/Quote/QuoteTrigger .tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 81e23afff3..59f51dbc0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "eslint.validate": [ "javascript", diff --git a/widget/embedded/src/components/Quote/Quote.styles.ts b/widget/embedded/src/components/Quote/Quote.styles.ts index c23d1a8699..1b124b1a54 100644 --- a/widget/embedded/src/components/Quote/Quote.styles.ts +++ b/widget/embedded/src/components/Quote/Quote.styles.ts @@ -114,6 +114,15 @@ export const summaryStyles = css({ cursor: 'default', }); +export const rowStyles = css({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + '.blockchainImage': { + marginLeft: '-$8', + }, +}); export const basicInfoStyles = css({ paddingTop: '$10', display: 'flex', @@ -127,7 +136,7 @@ export const basicInfoStyles = css({ }, }); -export const Chains = styled(Collapsible.Trigger, { +export const Trigger = styled(Collapsible.Trigger, { display: 'flex', width: '100%', justifyContent: 'space-between', @@ -171,17 +180,27 @@ export const Chains = styled(Collapsible.Trigger, { }, }, }, - '& div:nth-child(1)': { display: 'flex' }, + '.blockchains_section': { + display: 'none', + }, + '@xs': { + '.blockchains_section': { + display: 'block', + }, + }, }); -export const ChainImageContainer = styled('div', { +export const ImageContainer = styled('div', { width: '18px', height: '18px', borderRadius: '100%', - border: '1.5px transparent solid', display: 'flex', justifyContent: 'center', alignItems: 'center', + border: '1.5px transparent solid', + img: { + borderRadius: '100%', + }, variants: { state: { error: { @@ -252,3 +271,23 @@ export const ContainerInfoOutput = styled('div', { display: 'flex', flexWrap: 'wrap', }); + +export const MoreStep = styled('div', { + width: '18px', + height: '18px', + borderRadius: '100%', + backgroundColor: '$background', + cursor: 'default', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: '1.5px transparent solid', + variants: { + state: { + error: { + borderColor: '$error500', + }, + warning: { borderColor: '$warning500' }, + }, + }, +}); diff --git a/widget/embedded/src/components/Quote/Quote.tsx b/widget/embedded/src/components/Quote/Quote.tsx index 0d2f8a9895..d1cff40c17 100644 --- a/widget/embedded/src/components/Quote/Quote.tsx +++ b/widget/embedded/src/components/Quote/Quote.tsx @@ -5,10 +5,7 @@ import type { SwapResult } from 'rango-sdk'; import { i18n } from '@lingui/core'; import { Alert, - ChevronDownIcon, - ChevronRightIcon, Divider, - Image, InfoIcon, QuoteCost, StepDetails, @@ -52,20 +49,18 @@ import { getTotalFeeInUsd } from '../../utils/swap'; import { BasicInfoOutput, basicInfoStyles, - ChainImageContainer, - Chains, ContainerInfoOutput, Content, EXPANDABLE_QUOTE_TRANSITION_DURATION, FrameIcon, HorizontalSeparator, - IconContainer, QuoteContainer, stepsDetailsStyles, SummaryContainer, summaryStyles, } from './Quote.styles'; import { QuoteSummary } from './QuoteSummary'; +import { QuoteTrigger } from './QuoteTrigger '; export function Quote(props: QuoteProps) { const { @@ -374,59 +369,13 @@ export function Quote(props: QuoteProps) { recommended={recommended} open={expanded} onOpenChange={setExpanded}> - (quoteRef.current = ref)} + setExpanded((prevState) => !prevState)}> -
- {steps.map((step, index) => { - const key = `item-${index}`; - const arrow = ( - - - - ); - return ( - - - - - - - {index === numberOfSteps - 1 && ( - <> - {arrow} - - - - - - - )} - {index !== numberOfSteps - 1 && <>{arrow}} - - ); - })} -
- - - -
+ setExpanded={setExpanded} + expanded={expanded} + steps={steps} + />
diff --git a/widget/embedded/src/components/Quote/Quote.types.ts b/widget/embedded/src/components/Quote/Quote.types.ts index 3d4cc3ce58..1ff1233553 100644 --- a/widget/embedded/src/components/Quote/Quote.types.ts +++ b/widget/embedded/src/components/Quote/Quote.types.ts @@ -1,4 +1,5 @@ import type { QuoteError, QuoteWarning } from '../../types'; +import type { Step } from '@rango-dev/ui'; import type { BestRouteResponse } from 'rango-sdk'; import type { ReactNode } from 'react'; @@ -13,3 +14,19 @@ export type QuoteProps = { output: { value: string; usdValue?: string }; expanded?: boolean; }; + +export type QuoteTriggerProps = { + quoteRef: React.MutableRefObject; + recommended: boolean; + setExpanded: React.Dispatch>; + steps: Step[]; + expanded?: boolean; +}; + +export type QuoteTriggerImagesProps = { + content: ReactNode; + state?: 'error' | 'warning' | undefined; + src: string; + open?: boolean; + className?: string; +}; diff --git a/widget/embedded/src/components/Quote/QuoteTrigger .tsx b/widget/embedded/src/components/Quote/QuoteTrigger .tsx new file mode 100644 index 0000000000..c1c832457e --- /dev/null +++ b/widget/embedded/src/components/Quote/QuoteTrigger .tsx @@ -0,0 +1,215 @@ +import type { QuoteTriggerImagesProps, QuoteTriggerProps } from './Quote.types'; + +import { i18n } from '@lingui/core'; +import { + ChevronDownIcon, + ChevronRightIcon, + Divider, + Image, + Tooltip, + Typography, +} from '@rango-dev/ui'; +import React, { useEffect, useState } from 'react'; + +import { getContainer } from '../../utils/common'; +import { getUniqueBlockchains } from '../../utils/quote'; + +import { + IconContainer, + ImageContainer, + MoreStep, + rowStyles, + Trigger, +} from './Quote.styles'; + +const MAX_STEPS = 4; +const MAX_BLOCKCHAINS = 6; +const MIN_WIDTH_WINDOW = 375; + +const ImageComponent = (props: QuoteTriggerImagesProps) => { + const tooltipContainer = getContainer(); + const { content, src, className, open, state } = props; + return ( + + + + + + ); +}; + +export function QuoteTrigger(props: QuoteTriggerProps) { + const { quoteRef, recommended, setExpanded, steps, expanded } = props; + const tooltipContainer = getContainer(); + const numberOfSteps = steps.length; + const blockchains = getUniqueBlockchains(steps); + const [isMobile, setIsMobile] = useState(false); + + //choose the screen size + const handleResize = () => { + if (window.innerWidth < MIN_WIDTH_WINDOW) { + setIsMobile(true); + } else { + setIsMobile(false); + } + }; + + useEffect(() => { + window.addEventListener('resize', handleResize); + }); + + return ( + (quoteRef.current = ref)} + recommended={recommended} + onClick={() => setExpanded((prevState) => !prevState)}> +
+ + {i18n.t('Via:')} + + + {steps.map((step, index) => { + const key = `item-${index}`; + const arrow = ( + + + + ); + + return isMobile ? ( + <> + + {index !== numberOfSteps - 1 && <>{arrow}} + + ) : ( + + {numberOfSteps <= MAX_STEPS || + (numberOfSteps > MAX_STEPS && index < MAX_STEPS - 1) ? ( + <> + + {index !== numberOfSteps - 1 && <>{arrow}} + + ) : ( + index === MAX_STEPS - 1 && ( + + {arrow} + {steps.map((step, i) => { + const key = `image-${i}`; + return ( + i >= index && ( + + + {i !== numberOfSteps - 1 && <>{arrow}} + + ) + ); + })} +
+ }> + + i >= index && + (step.state === 'error' || step.state === 'warning') + )?.state + }> + + +{numberOfSteps - index} + + + + ) + )} + + ); + })} +
+
+
+
+ + {i18n.t('Chains:')} + + + {blockchains.map((blockchain, index) => ( + <> + {blockchains.length <= MAX_BLOCKCHAINS || + (blockchains.length > MAX_BLOCKCHAINS && + index < MAX_BLOCKCHAINS - 1) ? ( + + ) : ( + index === MAX_BLOCKCHAINS - 1 && ( + + {blockchains.map( + (chain, i) => + i >= index && ( + index ? 'blockchainImage' : ''} + /> + ) + )} +
+ }> + + + +{blockchains.length - index} + + + + ) + )} + + ))} + + +
+
+ + + + + + + ); +} diff --git a/widget/embedded/src/utils/quote.ts b/widget/embedded/src/utils/quote.ts index 485131abca..152b5a1ee6 100644 --- a/widget/embedded/src/utils/quote.ts +++ b/widget/embedded/src/utils/quote.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-magic-numbers */ import type { QuoteError, QuoteWarning, Wallet } from '../types'; -import type { PriceImpactWarningLevel } from '@rango-dev/ui'; +import type { PriceImpactWarningLevel, Step } from '@rango-dev/ui'; import type { SimulationAssetAndAmount, SimulationValidationStatus, @@ -336,3 +336,20 @@ export function getPriceImpact( return percentageChange && percentageChange < 0 ? percentageChange : null; } + +export const getUniqueBlockchains = (steps: Step[]) => { + const set = new Set(); + const result: { displayName: string; image: string }[] = []; + steps.forEach((step) => { + if (!set.has(step.from.chain.displayName)) { + set.add(step.from.chain.displayName); + result.push(step.from.chain); + } + if (!set.has(step.to.chain.displayName)) { + set.add(step.to.chain.displayName); + result.push(step.to.chain); + } + }); + + return result; +}; diff --git a/widget/ui/src/components/Tooltip/Tooltip.styles.ts b/widget/ui/src/components/Tooltip/Tooltip.styles.ts index 4edd7e72c8..372284d590 100644 --- a/widget/ui/src/components/Tooltip/Tooltip.styles.ts +++ b/widget/ui/src/components/Tooltip/Tooltip.styles.ts @@ -5,6 +5,13 @@ import { Typography } from '../Typography'; export const TooltipContent = styled(RadixTooltip.Content, { zIndex: '999999', + variants: { + align: { + right: { + position: 'absolute', + }, + }, + }, }); export const TooltipTypography = styled(Typography, { diff --git a/widget/ui/src/components/Tooltip/Tooltip.tsx b/widget/ui/src/components/Tooltip/Tooltip.tsx index 6805a9921b..ee4be7ef67 100644 --- a/widget/ui/src/components/Tooltip/Tooltip.tsx +++ b/widget/ui/src/components/Tooltip/Tooltip.tsx @@ -20,6 +20,7 @@ export function Tooltip(props: PropsWithChildren) { open, side = 'top', style, + align, } = props; return ( @@ -28,7 +29,7 @@ export function Tooltip(props: PropsWithChildren) { {children} - + {content} diff --git a/widget/ui/src/components/Tooltip/Tooltip.types.ts b/widget/ui/src/components/Tooltip/Tooltip.types.ts index 4ad4db4876..5bd92db7c0 100644 --- a/widget/ui/src/components/Tooltip/Tooltip.types.ts +++ b/widget/ui/src/components/Tooltip/Tooltip.types.ts @@ -1,8 +1,11 @@ +import type { TooltipContent } from './Tooltip.styles'; import type * as RadixTooltip from '@radix-ui/react-tooltip'; -import type { CSSProperties } from '@stitches/react'; +import type * as Stitches from '@stitches/react'; import type { ComponentProps, ReactNode } from 'react'; type RadixTooltipContentProps = ComponentProps; +type BaseProps = Stitches.VariantProps; +type BaseAlign = Exclude; export interface PropTypes { content: ReactNode; @@ -11,5 +14,6 @@ export interface PropTypes { sideOffset?: RadixTooltipContentProps['sideOffset']; container?: HTMLElement; open?: boolean; - style?: CSSProperties; + style?: Stitches.CSSProperties; + align?: BaseAlign; } diff --git a/widget/ui/src/theme.ts b/widget/ui/src/theme.ts index f970351b0d..ed9a3f01db 100644 --- a/widget/ui/src/theme.ts +++ b/widget/ui/src/theme.ts @@ -170,6 +170,7 @@ export const theme = { transitions: {}, }; const media = { + xs: '(min-width: 375px)', sm: '(min-width: 640px)', md: '(min-width: 768px)', lg: '(min-width: 1024px)',