Commit be5b304d authored by tom's avatar tom

fix gas info tooltip

parent ff73835e
import type { SystemStyleObject } from '@chakra-ui/react';
import {
AbsoluteCenter,
ProgressCircle as ChakraProgressCircle,
} from '@chakra-ui/react';
import * as React from 'react';
export interface ProgressCircleRingProps extends ChakraProgressCircle.CircleProps {
trackColor?: SystemStyleObject['stroke'];
cap?: SystemStyleObject['strokeLinecap'];
}
export const ProgressCircleRing = React.forwardRef<
SVGSVGElement,
ProgressCircleRingProps
>(function ProgressCircleRing(props, ref) {
const { trackColor, cap, color, ...rest } = props;
return (
<ChakraProgressCircle.Circle { ...rest } ref={ ref }>
<ChakraProgressCircle.Track stroke={ trackColor }/>
<ChakraProgressCircle.Range stroke={ color } strokeLinecap={ cap }/>
</ChakraProgressCircle.Circle>
);
});
export const ProgressCircleValueText = React.forwardRef<
HTMLDivElement,
ChakraProgressCircle.ValueTextProps
>(function ProgressCircleValueText(props, ref) {
return (
<AbsoluteCenter>
<ChakraProgressCircle.ValueText { ...props } ref={ ref }/>
</AbsoluteCenter>
);
});
export const ProgressCircleRoot = ChakraProgressCircle.Root;
......@@ -46,7 +46,7 @@ export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(
export const Skeleton = React.forwardRef<HTMLDivElement, ChakraSkeletonProps>(
function Skeleton(props, ref) {
const { loading = false, variant = 'shine', ...rest } = props;
return <ChakraSkeleton loading={ loading } variant={ variant } { ...rest } ref={ ref }/>;
const { loading = false, ...rest } = props;
return <ChakraSkeleton loading={ loading } { ...rest } ref={ ref }/>;
},
);
export const keyframes = {
fromLeftToRight: {
from: {
left: '100%',
},
to: {
left: '0%',
},
},
};
......@@ -83,6 +83,17 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
shadow: { value: { base: '{colors.blackAlpha.200}', _dark: '{colors.whiteAlpha.300}' } },
},
},
progressCircle: {
trackColor: {
DEFAULT: { value: { base: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } },
},
},
skeleton: {
bg: {
start: { value: { base: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.50}' } },
end: { value: { base: '{colors.blackAlpha.100}', _dark: '{colors.whiteAlpha.100}' } },
},
},
text: {
primary: { value: { base: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
secondary: { value: { base: '{colors.gray.500}', _dark: '{colors.gray.400}' } },
......
import { recipe as button } from './button.recipe';
import { recipe as link } from './link.recipe';
import { recipe as popover } from './popover.recipe';
import { recipe as progressCircle } from './progress-circle.recipe';
import { recipe as skeleton } from './skeleton.recipe';
import { recipe as tooltip } from './tooltip.recipe';
export const recipes = {
button,
link,
skeleton,
};
export const slotRecipes = {
tooltip,
popover,
progressCircle,
};
import { defineSlotRecipe } from '@chakra-ui/react';
export const recipe = defineSlotRecipe({
className: 'chakra-popover',
slots: [ 'content', 'header', 'body', 'footer', 'arrow', 'arrowTip' ],
base: {
content: {
......
import { defineSlotRecipe } from '@chakra-ui/react';
export const recipe = defineSlotRecipe({
slots: [ 'root', 'circle', 'circleTrack', 'circleRange', 'label', 'valueText' ],
base: {
root: {
display: 'inline-flex',
textStyle: 'sm',
position: 'relative',
},
circle: {
_indeterminate: {
animation: 'spin 2s linear infinite',
},
},
circleTrack: {
'--track-color': 'colors.progressCircle.trackColor',
stroke: 'var(--track-color)',
},
circleRange: {
stroke: 'blue.500',
transitionProperty: 'stroke-dasharray',
transitionDuration: '0.6s',
_indeterminate: {
animation: 'circular-progress 1.5s linear infinite',
},
},
label: {
display: 'inline-flex',
},
valueText: {
lineHeight: '1',
fontWeight: 'medium',
letterSpacing: 'tight',
fontVariantNumeric: 'tabular-nums',
},
},
variants: {
size: {
sm: {
circle: {
'--size': '16px',
'--thickness': '2px',
},
valueText: {
textStyle: '2xs',
},
},
md: {
circle: {
'--size': '20px',
'--thickness': '2px',
},
valueText: {
textStyle: 'xs',
},
},
},
},
defaultVariants: {
size: 'md',
},
});
import { defineRecipe } from '@chakra-ui/react';
export const recipe = defineRecipe({
base: {},
variants: {
loading: {
'true': {
borderRadius: 'l2',
boxShadow: 'none',
backgroundClip: 'padding-box',
cursor: 'default',
color: 'transparent',
pointerEvents: 'none',
userSelect: 'none',
flexShrink: '0',
'&::before, &::after, *': {
visibility: 'hidden',
},
},
'false': {
background: 'unset',
animation: 'fade-in var(--fade-duration, 0.1s) ease-out !important',
},
},
variant: {
pulse: {
background: 'bg.emphasized',
animation: 'pulse',
animationDuration: 'var(--duration, 1.2s)',
},
shine: {
'--animate-from': '100%',
'--animate-to': '-100%',
'--start-color': 'colors.skeleton.bg.start',
'--end-color': 'colors.skeleton.bg.end',
backgroundImage:
'linear-gradient(90deg,var(--start-color) 8%,var(--end-color) 18%,var(--start-color) 33%)',
backgroundSize: '200% 100%',
animation: 'bg-position var(--duration, 2s) linear infinite',
},
none: {
animation: 'none',
},
},
},
defaultVariants: {
variant: 'shine',
loading: true,
},
});
......@@ -3,6 +3,7 @@ import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react';
// TODO @tom2drum migrate this to the new recipe system
// import components from './components/index';
// import config from './config';
import { keyframes } from './foundations/animations';
import * as borders from './foundations/borders';
import breakpoints from './foundations/breakpoints';
import colors from './foundations/colors';
......@@ -18,6 +19,7 @@ const customConfig = defineConfig({
globalCss,
theme: {
breakpoints,
keyframes,
recipes,
slotRecipes,
semanticTokens,
......
......@@ -3,8 +3,11 @@ import React from 'react';
import { Button } from 'toolkit/chakra/button';
import { useColorMode } from 'toolkit/chakra/color-mode';
import { ProgressCircleRing, ProgressCircleRoot } from 'toolkit/chakra/progress-circle';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Switch } from 'toolkit/chakra/switch';
import { Tooltip } from 'toolkit/chakra/tooltip';
import ContentLoader from 'ui/shared/ContentLoader';
import PageTitle from 'ui/shared/Page/PageTitle';
const ChakraShowcases = () => {
......@@ -51,6 +54,29 @@ const ChakraShowcases = () => {
</Tooltip>
</HStack>
</section>
<section>
<Heading textStyle="heading.md" mb={ 2 }>Progress Circle</Heading>
<HStack gap={ 4 }>
<ProgressCircleRoot
value={ 45 }
colorPalette="blue"
>
<ProgressCircleRing/>
</ProgressCircleRoot>
</HStack>
</section>
{ /* TODO @tom2drum check skeleton styles */ }
<section>
<Heading textStyle="heading.md" mb={ 2 }>Skeleton & Loaders</Heading>
<HStack gap={ 4 }>
<Skeleton loading>
<span>Skeleton</span>
</Skeleton>
<ContentLoader/>
</HStack>
</section>
</VStack>
</>
);
......
......@@ -18,8 +18,7 @@ const ContentLoader = ({ className, text }: Props) => {
position: 'absolute',
width: '60px',
height: '6px',
// TODO @tom2drum check this animation
animation: `slide-from-left-full 700ms ease-in-out infinite alternate`,
animation: `fromLeftToRight 700ms ease-in-out infinite alternate`,
left: '100%',
top: 0,
backgroundColor: 'blue.300',
......
import type {
PlacementWithLogical } from '@chakra-ui/react';
import {
Box,
Flex,
Grid,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
} from '@chakra-ui/react';
import React from 'react';
import type { HomeStats } from 'types/api/stats';
import type { ExcludeUndefined } from 'types/utils';
import { route } from 'nextjs-routes';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import config from 'configs/app';
import dayjs from 'lib/date/dayjs';
import Popover from 'ui/shared/chakra/Popover';
import type { TooltipProps } from 'toolkit/chakra/tooltip';
import { Tooltip } from 'toolkit/chakra/tooltip';
import LinkInternal from 'ui/shared/links/LinkInternal';
import GasInfoTooltipRow from './GasInfoTooltipRow';
......@@ -29,14 +24,12 @@ interface Props {
data: HomeStats;
dataUpdatedAt: number;
isOpen?: boolean; // for testing purposes only; the tests were flaky, i couldn't find a better way
placement?: PlacementWithLogical;
placement?: ExcludeUndefined<TooltipProps['positioning']>['placement'];
}
const feature = config.features.gasTracker;
const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Props) => {
const tooltipBg = useColorModeValue('gray.700', 'gray.900');
if (!data.gas_prices) {
return null;
}
......@@ -47,39 +40,42 @@ const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Pr
feature.isEnabled && feature.units.length === 2 ?
3 : 2;
const content = (
<Flex flexDir="column" textStyle="xs" rowGap={ 3 }>
{ data.gas_price_updated_at && (
<Flex justifyContent="space-between">
<Box color="text_secondary">Last update</Box>
<Flex color="text_secondary" justifyContent="flex-end" columnGap={ 2 } ml={ 3 }>
{ dayjs(data.gas_price_updated_at).format('MMM DD, HH:mm:ss') }
{ data.gas_prices_update_in !== 0 &&
<GasInfoUpdateTimer key={ dataUpdatedAt } startTime={ dataUpdatedAt } duration={ data.gas_prices_update_in }/> }
</Flex>
</Flex>
) }
<Grid rowGap={ 2 } columnGap="10px" gridTemplateColumns={ `repeat(${ columnNum }, minmax(min-content, auto))` }>
<GasInfoTooltipRow name="Fast" info={ data.gas_prices.fast }/>
<GasInfoTooltipRow name="Normal" info={ data.gas_prices.average }/>
<GasInfoTooltipRow name="Slow" info={ data.gas_prices.slow }/>
</Grid>
<LinkInternal href={ route({ pathname: '/gas-tracker' }) }>
Gas tracker overview
</LinkInternal>
</Flex>
);
return (
<Popover trigger="hover" isLazy isOpen={ isOpen } placement={ placement }>
<PopoverTrigger>
{ children }
</PopoverTrigger>
<Portal>
<PopoverContent bgColor={ tooltipBg } w="auto">
<PopoverBody color="white">
{ /* TODO @tom2drum add dark mode */ }
<Flex flexDir="column" fontSize="xs" lineHeight={ 4 } rowGap={ 3 }>
{ data.gas_price_updated_at && (
<Flex justifyContent="space-between">
<Box color="text_secondary">Last update</Box>
<Flex color="text_secondary" justifyContent="flex-end" columnGap={ 2 } ml={ 3 }>
{ dayjs(data.gas_price_updated_at).format('MMM DD, HH:mm:ss') }
{ data.gas_prices_update_in !== 0 &&
<GasInfoUpdateTimer key={ dataUpdatedAt } startTime={ dataUpdatedAt } duration={ data.gas_prices_update_in }/> }
</Flex>
</Flex>
) }
<Grid rowGap={ 2 } columnGap="10px" gridTemplateColumns={ `repeat(${ columnNum }, minmax(min-content, auto))` }>
<GasInfoTooltipRow name="Fast" info={ data.gas_prices.fast }/>
<GasInfoTooltipRow name="Normal" info={ data.gas_prices.average }/>
<GasInfoTooltipRow name="Slow" info={ data.gas_prices.slow }/>
</Grid>
<LinkInternal href={ route({ pathname: '/gas-tracker' }) }>
Gas tracker overview
</LinkInternal>
</Flex>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
<Tooltip
content={ content }
positioning={{ placement }}
open={ isOpen }
lazyMount
interactive
showArrow={ false }
// TODO @tom2drum forced light mode doesn't work for now
contentProps={{ p: 4, borderRadius: 'md', className: 'light' }}
>
{ children }
</Tooltip>
);
};
......
import { CircularProgress, chakra, useColorModeValue } from '@chakra-ui/react';
import type { ProgressCircle } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
import dayjs from 'lib/date/dayjs';
import { ProgressCircleRing, ProgressCircleRoot } from 'toolkit/chakra/progress-circle';
interface Props {
startTime: number;
duration: number;
className?: string;
size?: number;
size?: ProgressCircle.RootProps['size'];
}
const getValue = (startDate: dayjs.Dayjs, duration: number) => {
......@@ -22,9 +24,8 @@ const getValue = (startDate: dayjs.Dayjs, duration: number) => {
return value;
};
const GasInfoUpdateTimer = ({ startTime, duration, className, size = 4 }: Props) => {
const GasInfoUpdateTimer = ({ startTime, duration, className, size = 'sm' }: Props) => {
const [ value, setValue ] = React.useState(getValue(dayjs(startTime), duration));
const trackColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.100');
React.useEffect(() => {
const startDate = dayjs(startTime);
......@@ -42,7 +43,15 @@ const GasInfoUpdateTimer = ({ startTime, duration, className, size = 4 }: Props)
};
}, [ startTime, duration ]);
return <CircularProgress className={ className } value={ value } trackColor={ trackColor } size={ size }/>;
return (
<ProgressCircleRoot
className={ className }
value={ value }
size={ size }
>
<ProgressCircleRing/>
</ProgressCircleRoot>
);
};
export default React.memo(chakra(GasInfoUpdateTimer));
......@@ -13,7 +13,7 @@ import * as Layout from './components';
const LayoutDefault = ({ children }: Props) => {
return (
<Layout.Container>
{ /* <Layout.TopRow/> */ }
<Layout.TopRow/>
<Layout.NavBar/>
{ /* <HeaderMobile/> */ }
<Layout.MainArea>
......
import { Alert, AlertIcon, AlertTitle } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
import { Alert } from 'toolkit/chakra/alert';
// TODO @tom2drum fix this alert
const MaintenanceAlert = () => {
if (!config.UI.maintenanceAlert.message) {
return null;
......@@ -10,10 +12,10 @@ const MaintenanceAlert = () => {
return (
<Alert status="info" colorScheme="gray" py={ 3 } borderRadius="md">
<AlertIcon display={{ base: 'none', lg: 'flex' }}/>
<AlertTitle
{ /* <AlertIcon display={{ base: 'none', lg: 'flex' }}/> */ }
<Box
dangerouslySetInnerHTML={{ __html: config.UI.maintenanceAlert.message }}
sx={{
css={{
'& a': {
color: 'link',
_hover: {
......@@ -21,6 +23,7 @@ const MaintenanceAlert = () => {
},
},
}}
/>
</Alert>
);
......
......@@ -39,8 +39,7 @@ const GetGasButton = () => {
href={ href }
display="flex"
alignItems="center"
fontSize="xs"
lineHeight={ 5 }
textStyle="xs"
onClick={ onGetGasClick }
>
{ getGasFeature.logoUrl && (
......
import { Flex, Divider, Box } from '@chakra-ui/react';
import { Flex, Separator, Box } from '@chakra-ui/react';
import React from 'react';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import config from 'configs/app';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils';
import DeFiDropdown from './DeFiDropdown';
......@@ -25,19 +25,19 @@ const TopBar = () => {
>
<TopBarStats/>
<Flex alignItems="center">
{ config.features.deFiDropdown.isEnabled && (
{ /* { config.features.deFiDropdown.isEnabled && (
<>
<DeFiDropdown/>
<Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/>
<Separator mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/>
</>
) }
<Settings/>
{ config.UI.navigation.layout === 'horizontal' && Boolean(config.UI.navigation.featuredNetworks) && (
) } */ }
{ /* <Settings/> */ }
{ /* { config.UI.navigation.layout === 'horizontal' && Boolean(config.UI.navigation.featuredNetworks) && (
<Box display={{ base: 'none', lg: 'flex' }}>
<Divider mx={ 3 } height={ 4 } orientation="vertical"/>
<Separator mx={ 3 } height={ 4 } orientation="vertical"/>
<NetworkMenu/>
</Box>
) }
) } */ }
</Flex>
</Flex>
</Box>
......
import { Flex, Link, Skeleton, chakra } from '@chakra-ui/react';
import { Flex, Link, chakra } from '@chakra-ui/react';
import React from 'react';
import config from 'configs/app';
......@@ -6,6 +6,7 @@ import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile';
import { HOMEPAGE_STATS } from 'stubs/stats';
import { Skeleton } from 'toolkit/chakra/skeleton';
import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip';
import GasPrice from 'ui/shared/gas/GasPrice';
import TextSeparator from 'ui/shared/TextSeparator';
......@@ -55,12 +56,12 @@ const TopBarStats = () => {
>
{ data?.coin_price && (
<Flex columnGap={ 1 }>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
<chakra.span color="text_secondary">{ config.chain.currency.symbol } </chakra.span>
<span>${ Number(data.coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }) }</span>
</Skeleton>
{ data.coin_price_change_percentage && (
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
<chakra.span color={ Number(data.coin_price_change_percentage) >= 0 ? 'green.500' : 'red.500' }>
{ Number(data.coin_price_change_percentage).toFixed(2) }%
</chakra.span>
......@@ -70,7 +71,7 @@ const TopBarStats = () => {
) }
{ !isMobile && data?.secondary_coin_price && config.chain.secondaryCoin.symbol && (
<Flex columnGap={ 1 } ml={ data?.coin_price ? 3 : 0 }>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData }>
<chakra.span color="text_secondary">{ config.chain.secondaryCoin.symbol } </chakra.span>
<span>${ Number(data.secondary_coin_price).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 6 }) }</span>
</Skeleton>
......@@ -79,7 +80,7 @@ const TopBarStats = () => {
{ data?.coin_price && config.features.gasTracker.isEnabled && <TextSeparator color="border.divider"/> }
{ data?.gas_prices && data.gas_prices.average !== null && config.features.gasTracker.isEnabled && (
<>
<Skeleton isLoaded={ !isPlaceholderData }>
<Skeleton loading={ isPlaceholderData } display="inline-flex" whiteSpace="pre-wrap">
<chakra.span color="text_secondary">Gas </chakra.span>
<GasInfoTooltip data={ data } dataUpdatedAt={ dataUpdatedAt } placement={ !data?.coin_price ? 'bottom-start' : undefined }>
<Link>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment