diff --git a/packages/staking/src/features/delegation-card/DelegationCard.tsx b/packages/staking/src/features/delegation-card/DelegationCard.tsx index 1b0865d2d..a5e4b9ce8 100644 --- a/packages/staking/src/features/delegation-card/DelegationCard.tsx +++ b/packages/staking/src/features/delegation-card/DelegationCard.tsx @@ -3,8 +3,9 @@ import cn from 'classnames'; import { Fragment, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { TranslationKey } from '../i18n'; -// import { PERCENTAGE_SCALE_MAX } from '../store'; import * as styles from './DelegationCard.css'; +import { DelegationTooltip } from './DelegationTooltip'; +import { DistributionItem } from './types'; // TODO const PERCENTAGE_SCALE_MAX = 100; @@ -15,17 +16,11 @@ export type DelegationStatus = | 'under-allocated' | 'no-selection'; -type Distribution = Array<{ - name: string; - percentage: number; - color: PieChartColor; -}>; - type DelegationCardProps = { arrangement?: 'vertical' | 'horizontal'; balance: string; cardanoCoinSymbol: string; - distribution: Distribution; + distribution: DistributionItem[]; status: DelegationStatus; showDistribution?: boolean; }; @@ -72,7 +67,7 @@ export const DelegationCard = ({ const { data, colorSet = PIE_CHART_DEFAULT_COLOR_SET } = useMemo((): { colorSet?: PieChartColor[]; - data: Distribution; + data: DistributionItem[]; } => { const GREY_COLOR: PieChartColor = '#C0C0C0'; const RED_COLOR: PieChartColor = '#FF5470'; @@ -118,7 +113,7 @@ export const DelegationCard = ({ data-testid="delegation-info-card" >
- + {showDistribution && {totalPercentage}%}
>): ReactElement | null => { + const { t } = useTranslation(); + if (active && payload) { + const { apy, saturation, fill } = payload; + + return ( + + + + {t('browsePools.stakePoolTableBrowser.tableHeader.ros.title')} + {apy ? `${apy}%` : '-'} + + + {t('browsePools.stakePoolTableBrowser.tableHeader.saturation.title')} + {saturation ? `${saturation}%` : '-'} + + + } + /> + + ); + } + + // eslint-disable-next-line unicorn/no-null + return null; +}; diff --git a/packages/staking/src/features/delegation-card/types.ts b/packages/staking/src/features/delegation-card/types.ts new file mode 100644 index 000000000..5a987de47 --- /dev/null +++ b/packages/staking/src/features/delegation-card/types.ts @@ -0,0 +1,9 @@ +import { PieChartColor } from '@lace/ui'; + +export type DistributionItem = { + name: string; + percentage: number; + color: PieChartColor; + apy?: string; + saturation?: string; +}; diff --git a/packages/staking/src/features/drawer/preferences/StepPreferencesContent.tsx b/packages/staking/src/features/drawer/preferences/StepPreferencesContent.tsx index 5eed8c21f..390b839ce 100644 --- a/packages/staking/src/features/drawer/preferences/StepPreferencesContent.tsx +++ b/packages/staking/src/features/drawer/preferences/StepPreferencesContent.tsx @@ -48,18 +48,20 @@ export const StepPreferencesContent = () => { const displayData = draftPortfolio.map((draftPool, i) => { const { - displayData: { name }, + displayData: { name, apy, saturation }, id, sliderIntegerPercentage, } = draftPool; return { + apy: apy ? String(apy) : undefined, cardanoCoinSymbol, color: PIE_CHART_DEFAULT_COLOR_SET[i] as PieChartColor, id, name: name || '-', onChainPercentage: draftPool.basedOnCurrentPortfolio ? draftPool.onChainPercentage : undefined, percentage: sliderIntegerPercentage, + saturation: saturation ? String(saturation) : undefined, savedIntegerPercentage: draftPool.basedOnCurrentPortfolio ? draftPool.savedIntegerPercentage : undefined, // TODO sliderIntegerPercentage, diff --git a/packages/staking/src/features/overview/Overview.tsx b/packages/staking/src/features/overview/Overview.tsx index 9abe204d2..4163bb5a0 100644 --- a/packages/staking/src/features/overview/Overview.tsx +++ b/packages/staking/src/features/overview/Overview.tsx @@ -95,10 +95,12 @@ export const Overview = () => { ({ + distribution={displayData.map(({ color, name = '-', onChainPercentage, apy, saturation }) => ({ + apy: apy ? String(apy) : undefined, color, name, percentage: onChainPercentage, + saturation: saturation ? String(saturation) : undefined, }))} status={currentPortfolio.length === 1 ? 'simple-delegation' : 'multi-delegation'} /> diff --git a/packages/staking/src/features/overview/OverviewPopup.tsx b/packages/staking/src/features/overview/OverviewPopup.tsx index 4bf20e17e..24cd9c472 100644 --- a/packages/staking/src/features/overview/OverviewPopup.tsx +++ b/packages/staking/src/features/overview/OverviewPopup.tsx @@ -98,10 +98,12 @@ export const OverviewPopup = () => { balance={compactNumber(balancesBalance.available.coinBalance)} cardanoCoinSymbol={walletStoreWalletUICardanoCoin.symbol} arrangement="vertical" - distribution={displayData.map(({ color, name = '-', onChainPercentage }) => ({ + distribution={displayData.map(({ color, name = '-', onChainPercentage, apy, saturation }) => ({ + apy: apy ? String(apy) : undefined, color, name, percentage: onChainPercentage, + saturation: saturation ? String(saturation) : undefined, }))} status={currentPortfolio.length === 1 ? 'simple-delegation' : 'multi-delegation'} /> diff --git a/packages/staking/src/features/theme/colors.ts b/packages/staking/src/features/theme/colors.ts index f07e7605d..08336b600 100644 --- a/packages/staking/src/features/theme/colors.ts +++ b/packages/staking/src/features/theme/colors.ts @@ -23,6 +23,7 @@ export const colorsContract = { $sliderFillSecondary: '', $sliderKnobFill: '', $sliderRailFill: '', + $tooltipBgColor: '', }; export const lightThemeColors: typeof colorsContract = { @@ -48,6 +49,7 @@ export const lightThemeColors: typeof colorsContract = { $sliderFillSecondary: lightColorScheme.$primary_dark_grey, $sliderKnobFill: lightColorScheme.$primary_white, $sliderRailFill: lightColorScheme.$primary_light_grey_plus, + $tooltipBgColor: lightColorScheme.$primary_white, }; export const darkThemeColors: typeof colorsContract = { @@ -78,4 +80,5 @@ export const darkThemeColors: typeof colorsContract = { $sliderFillSecondary: darkColorScheme.$primary_light_grey, $sliderKnobFill: lightColorScheme.$primary_black, $sliderRailFill: darkColorScheme.$primary_dark_grey_plus, + $tooltipBgColor: darkColorScheme.$primary_mid_grey, }; diff --git a/packages/ui/src/design-system/index.ts b/packages/ui/src/design-system/index.ts index 8ce8e96a4..69b4af6b6 100644 --- a/packages/ui/src/design-system/index.ts +++ b/packages/ui/src/design-system/index.ts @@ -21,7 +21,7 @@ export * as FlowCard from './flow-card'; export * as IconButton from './icon-buttons'; export * as TransactionSummary from './transaction-summary'; export { ToastBar } from './toast-bar'; -export { Tooltip } from './tooltip'; +export * from './tooltip'; export { Message } from './message'; export { PasswordBox } from './password-box'; export { Metadata } from './metadata'; diff --git a/packages/ui/src/design-system/pie-chart/index.ts b/packages/ui/src/design-system/pie-chart/index.ts index 1ce2905df..11f173fba 100644 --- a/packages/ui/src/design-system/pie-chart/index.ts +++ b/packages/ui/src/design-system/pie-chart/index.ts @@ -1,4 +1,8 @@ -export type { PieChartColor, PieChartProps } from './pie-chart.component'; +export type { + PieChartColor, + PieChartProps, + TooltipContentRendererProps, +} from './pie-chart.component'; export { PieChart } from './pie-chart.component'; export { PIE_CHART_DEFAULT_COLOR_SET, diff --git a/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx b/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx index 76e98218f..d0520448f 100644 --- a/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx +++ b/packages/ui/src/design-system/pie-chart/pie-chart.component.tsx @@ -1,6 +1,8 @@ /* eslint-disable functional/prefer-immutable-types */ -import React from 'react'; +import type { ReactElement, ReactNode } from 'react'; +import React, { isValidElement, useMemo, useState } from 'react'; +import isFunction from 'lodash/isFunction'; import { Cell, Pie, @@ -15,20 +17,58 @@ import { } from './pie-chart.data'; import type { ColorValueHex } from '../../types'; -import type { CellProps, TooltipProps } from 'recharts'; +import type { CellProps } from 'recharts'; import type { PickByValue } from 'utility-types'; type PieChartDataProps = Partial<{ overrides: CellProps; }>; export type PieChartColor = ColorValueHex | PieChartGradientColor; + +export interface TooltipContentRendererProps { + active?: boolean; + name?: string; + payload?: T; +} +export type TooltipContentRenderer = ( + props: TooltipContentRendererProps, +) => ReactNode; +type TooltipContent = ReactElement | TooltipContentRenderer; + +interface RechartTooltipContentRendererProps { + name?: string; + active?: boolean; + payload?: { name?: string; payload?: T }[]; +} + +type RechartTooltipContentRenderer = ( + props: RechartTooltipContentRendererProps, +) => ReactNode; + +// Recharts passes to the renderer for some reason the payload as +// a list which is a bit cumbersome because in practice we care just about the +// first element and the adapter below removes this inconvenience +const transformTooltipContentRenderer = + ( + tooltipContentRenderer: TooltipContentRenderer, + ): RechartTooltipContentRenderer => + ({ + active, + payload, + }: { + active?: boolean; + payload?: { name?: string; payload?: T }[]; + }) => + tooltipContentRenderer({ active, ...payload?.[0] }); + interface PieChartBaseProps { animate?: boolean; colors?: PieChartColor[]; data: (PieChartDataProps & T)[]; direction?: 'clockwise' | 'counterclockwise'; - tooltip?: TooltipProps['content']; + tooltip?: TooltipContent; } + interface PieChartCustomKeyProps extends PieChartBaseProps { nameKey: keyof PickByValue; @@ -63,6 +103,7 @@ const formatPieColor = (color: PieChartColor): string => * @param tooltip component accepted by Recharts Tooltip `content` prop * @param valueKey object key of a `data` item that will be used as value (displayed in the tooltip) */ + export const PieChart = ({ animate = true, colors = PIE_CHART_DEFAULT_COLOR_SET, @@ -73,17 +114,46 @@ export const PieChart = ({ valueKey = 'value', }: PieChartProps): JSX.Element => { const data = inputData.slice(0, colors.length); + const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); + + const tooltipContent = useMemo(() => { + if (!tooltip || isValidElement(tooltip)) { + return tooltip; + } + + if (isFunction(tooltip)) { + return transformTooltipContentRenderer(tooltip); + } + }, [tooltip]); + + const handleMouseMove = (event: React.MouseEvent): void => { + const clientWidth = window.innerWidth; + if (event.target instanceof SVGSVGElement && clientWidth > 360) { + const { x, y } = event.target.getBoundingClientRect(); + setTooltipPosition({ x: event.clientX - x, y: event.clientY - y }); + } + }; return ( - + { + handleMouseMove(event as React.MouseEvent); + }} + > - {Boolean(tooltip) && } + {tooltipContent && ( + + )} = { export default meta; +const CustomTooltip = (): ReactElement => ( + + + +); + export const Overview = (): JSX.Element => ( @@ -231,12 +239,13 @@ export const Overview = (): JSX.Element => ( type ConfigurableStoryProps = Pick< PieChartProps<{ name: string; value: number }>, - 'colors' | 'data' | 'direction' | 'tooltip' ->; + 'colors' | 'data' | 'direction' +> & { tooltip: boolean }; export const Controls = ({ colors, data, + tooltip, ...props }: Readonly): JSX.Element => ( @@ -245,6 +254,7 @@ export const Controls = ({ animate={isNotInChromatic} colors={colors} data={data} + tooltip={tooltip ? CustomTooltip : undefined} {...props} /> diff --git a/packages/ui/src/design-system/tooltip/rich-tooltip-content-inner.component.tsx b/packages/ui/src/design-system/tooltip/rich-tooltip-content-inner.component.tsx index d94b8845d..6d72a8d4a 100644 --- a/packages/ui/src/design-system/tooltip/rich-tooltip-content-inner.component.tsx +++ b/packages/ui/src/design-system/tooltip/rich-tooltip-content-inner.component.tsx @@ -10,16 +10,21 @@ import * as cx from './rich-tooltip-content-inner.css'; export interface RichContentInnerProps { title: string; description: ReactNode; + dotColor?: string; } export const RichContentInner = ({ title, description, + dotColor, }: Readonly): JSX.Element => { + const customDotStyle = + dotColor == undefined ? {} : { backgroundColor: dotColor }; + return ( - + diff --git a/packages/ui/src/design-tokens/elevation.data.ts b/packages/ui/src/design-tokens/elevation.data.ts index 3e716303b..ba5f2371d 100644 --- a/packages/ui/src/design-tokens/elevation.data.ts +++ b/packages/ui/src/design-tokens/elevation.data.ts @@ -1,5 +1,6 @@ export const elevation = { - $tooltip: '', + $tooltip: + '0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05)', $dialog: '', $primaryButton: '', $assets: '',