Feat/324 sparkline component (#381)

* move colors in ui-toolkit/config

* fixing tests and add stories

* fixing some tests and edit story

* update sparkline story

* rename story templates for sparkline

* use tailwind colors and rename to *.spec.tsx

* use tailwind only

* remove test.tsx

* Update libs/tailwindcss-config/src/theme.js

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>

* remove gray from tailwind and use black/40 and white/40 for strokeCurrent

Co-authored-by: madalinaraicu <“madalina@raygroup.uk”>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
m.ray 2022-05-09 18:19:19 +03:00 committed by GitHub
parent 19a9e9adb0
commit 319e14164d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 362 additions and 88 deletions

View File

@ -5,7 +5,6 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Colors } from '../../config';
import {
AppStateActionType,
useAppState,
@ -26,6 +25,9 @@ import {
WalletCardRow,
} from '../wallet-card';
import { Button, Loader } from '@vegaprotocol/ui-toolkit';
import { theme } from '@vegaprotocol/tailwindcss-config';
const Colors = theme.colors;
const removeLeadingAddressSymbol = (key: string) => {
if (key && key.length > 2 && key.slice(0, 2) === '0x') {
@ -68,13 +70,13 @@ const AssociatedAmounts = ({
total={associationAmounts.total}
leftLabel={t('associated')}
rightLabel={t('notAssociated')}
leftColor={Colors.WHITE}
rightColor={Colors.BLACK}
leftColor={Colors.white.DEFAULT}
rightColor={Colors.black.DEFAULT}
light={true}
/>
{vestingAssociationByVegaKey.length ? (
<>
<hr style={{ borderStyle: 'dashed', color: Colors.TEXT }} />
<hr style={{ borderStyle: 'dashed', color: Colors.text }} />
<WalletCardRow label="Associated with Vega keys" bold={true} />
{vestingAssociationByVegaKey.map(([key, amount]) => {
return (

View File

@ -2,9 +2,11 @@ import './locked-progress.scss';
import React from 'react';
import { Colors } from '../../config';
import { formatNumber } from '../../lib/format-number';
import type { BigNumber } from '../../lib/bignumber';
import { theme } from '@vegaprotocol/tailwindcss-config';
const Colors = theme.colors;
export interface LockedProgressProps {
total: BigNumber;
@ -23,8 +25,8 @@ export const LockedProgress = ({
unlocked,
leftLabel,
rightLabel,
leftColor = Colors.PINK,
rightColor = Colors.GREEN,
leftColor = Colors.pink,
rightColor = Colors.green.DEFAULT,
light = false,
}: LockedProgressProps) => {
const lockedPercentage = React.useMemo(() => {

View File

@ -1,6 +1,5 @@
import { useTranslation } from 'react-i18next';
import { Colors, Links } from '../../config';
import { Links } from '../../config';
export const DownloadWalletPrompt = () => {
const { t } = useTranslation();
@ -9,7 +8,7 @@ export const DownloadWalletPrompt = () => {
<h3>{t('getWallet')}</h3>
<p style={{ margin: 0 }}>
<a
style={{ color: Colors.DEEMPHASISE }}
className={'text-deemphasise'}
href={Links.WALLET_GUIDE}
target="_blank"
rel="noreferrer"
@ -19,7 +18,7 @@ export const DownloadWalletPrompt = () => {
</p>
<p style={{ margin: 0 }}>
<a
style={{ color: Colors.DEEMPHASISE }}
className={'text-deemphasise'}
href={Links.WALLET_RELEASES}
target="_blank"
rel="noreferrer"

View File

@ -1,24 +0,0 @@
export const Colors = {
WHITE: '#FFF',
RED: '#ED1515',
PINK: '#ff2d5e',
GREEN: '#26ff8a',
TEXT: '#c7c7c7',
BLACK: '#000',
DEEMPHASISE: '#8a9ba8',
GRAY_DARK_1: '#292929',
GRAY_DARK_2: '#333',
GRAY: '#494949',
GRAY_LIGHT: '#ccc',
GRAY_LIGHT_1: '#6e6e6e',
GREEN_DARK: '#246340', // Same as GREEN_TRANSPARENT given a background of #1f1f1f
GREEN_TRANSPARENT: 'rgba(38, 255, 138, 0.3)',
RED_TRANSPARENT: 'rgba(255, 38, 65, 0.3)',
TRANSPARENT: 'rgba(0,0,0,0)',
VEGA_RED: '#ff261a',
VEGA_ORANGE: '#d9822b',
VEGA_GREEN: '#26ff8a',
VEGA_YELLOW: '#daff0d',
};

View File

@ -1,4 +1,3 @@
export * from './colors';
export * from './flags';
export * from './ethereum';
export * from './links';

View File

@ -1,7 +1,9 @@
import React from 'react';
import { Colors } from '../config';
import { usePrevious } from './use-previous';
import type { BigNumber } from '../lib/bignumber';
import { theme as tailwindcss } from '@vegaprotocol/tailwindcss-config';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
const Colors = tailwindcss.colors;
const FLASH_DURATION = 1200; // Duration of flash animation in milliseconds
@ -11,6 +13,7 @@ export function useAnimateValue(
) {
const shouldAnimate = React.useRef(false);
const previous = usePrevious(value);
const [theme] = useThemeSwitcher();
React.useEffect(() => {
const timeout = setTimeout(() => {
@ -28,9 +31,17 @@ export function useAnimateValue(
) {
elRef.current?.animate(
[
{ backgroundColor: Colors.VEGA_RED, color: Colors.WHITE },
{ backgroundColor: Colors.VEGA_RED, color: Colors.WHITE, offset: 0.8 },
{ backgroundColor: Colors.GRAY_LIGHT, color: Colors.WHITE },
{ backgroundColor: Colors.red.vega, color: Colors.white.DEFAULT },
{
backgroundColor: Colors.red.vega,
color: Colors.white.DEFAULT,
offset: 0.8,
},
{
backgroundColor:
theme === 'dark' ? Colors.white[60] : Colors.black[60],
color: Colors.white.DEFAULT,
},
],
FLASH_DURATION
);
@ -43,13 +54,20 @@ export function useAnimateValue(
) {
elRef.current?.animate(
[
{ backgroundColor: Colors.VEGA_GREEN, color: Colors.WHITE },
{
backgroundColor: Colors.VEGA_GREEN,
color: Colors.WHITE,
backgroundColor: Colors.green.vega,
color: Colors.white.DEFAULT,
},
{
backgroundColor: Colors.green.vega,
color: Colors.white.DEFAULT,
offset: 0.8,
},
{ backgroundColor: Colors.GRAY_LIGHT, color: Colors.WHITE },
{
backgroundColor:
theme === 'dark' ? Colors.white[60] : Colors.black[60],
color: Colors.white.DEFAULT,
},
],
FLASH_DURATION
);

View File

@ -1,7 +1,5 @@
import { Trans, useTranslation } from 'react-i18next';
import { Colors } from '../../config';
interface TargetAddressMismatchProps {
connectedAddress: string;
expectedAddress: string;
@ -27,7 +25,7 @@ export const TargetAddressMismatch = ({
}}
components={{
bold: <strong />,
red: <span style={{ color: Colors.RED }} />,
red: <span className={'text-red'} />,
}}
/>
</p>

View File

@ -3,7 +3,6 @@ import { Trans, useTranslation } from 'react-i18next';
import { Link, useParams, useOutletContext } from 'react-router-dom';
import { TransactionCallout } from '../../../components/transaction-callout';
import { Colors } from '../../../config';
import { ADDRESSES } from '../../../config';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import { useContracts } from '../../../contexts/contracts/contracts-context';
@ -88,7 +87,7 @@ export const RedeemFromTranche = () => {
{txState.txState !== TxState.Default ? (
<TransactionCallout
completeHeading={
<strong style={{ color: Colors.WHITE }}>
<strong className={'text-white'}>
{t('Tokens from this Tranche have been redeemed')}
</strong>
}

View File

@ -8,7 +8,7 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { TokenInput } from '../../components/token-input';
import { Colors, NetworkParams } from '../../config';
import { NetworkParams } from '../../config';
import { useAppState } from '../../contexts/app-state/app-state-context';
import { useNetworkParam } from '../../hooks/use-network-param';
import { useSearchParams } from '../../hooks/use-search-params';
@ -203,11 +203,9 @@ export const StakingForm = ({
availableStakeToRemove.isEqualTo(0)
) {
if (appState.lien.isGreaterThan(0)) {
return (
<span style={{ color: Colors.RED }}>{t('stakeNodeWrongVegaKey')}</span>
);
return <span className={'text-red'}>{t('stakeNodeWrongVegaKey')}</span>;
} else {
return <span style={{ color: Colors.RED }}>{t('stakeNodeNone')}</span>;
return <span className={'text-red'}>{t('stakeNodeNone')}</span>;
}
}

View File

@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { EpochCountdown } from '../../components/epoch-countdown';
import { Colors } from '../../config';
import type { VegaKeyExtended } from '@vegaprotocol/wallet';
import { BigNumber } from '../../lib/bignumber';
import type { Staking as StakingQueryResult } from './__generated__/Staking';
@ -88,9 +87,7 @@ export const StakingNode = ({ vegaKey, data }: StakingNodeProps) => {
if (!nodeInfo) {
return (
<span style={{ color: Colors.RED }}>
{t('stakingNodeNotFound', { node })}
</span>
<span className={'text-red'}>{t('stakingNodeNotFound', { node })}</span>
);
}

View File

@ -1,8 +1,9 @@
import { useTranslation } from 'react-i18next';
import { Colors } from '../../config';
import { getAbbreviatedNumber } from '../../lib/abbreviate-number';
import { ProgressBar } from './progress-bar';
import type { BigNumber } from '../../lib/bignumber';
import { theme } from '@vegaprotocol/tailwindcss-config';
const Colors = theme.colors;
interface TrancheProgressProps {
locked: BigNumber;
@ -28,7 +29,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Locked')}</span>
<ProgressBar
width={220}
color={Colors.PINK}
color={Colors.pink}
percentage={lockedPercentage}
/>
<span className="tranches__progress-numbers">
@ -39,7 +40,7 @@ export const TrancheProgress = ({
<span className="tranches__progress-title">{t('Redeemed')}</span>
<ProgressBar
width={220}
color={Colors.GREEN}
color={Colors.green.DEFAULT}
percentage={removedPercentage}
/>
<span className="tranches__progress-numbers">

View File

@ -13,10 +13,12 @@ import {
YAxis,
} from 'recharts';
import { Colors } from '../../config';
import { DATE_FORMAT_LONG } from '../../lib/date-formats';
import data from './data.json';
import { theme } from '@vegaprotocol/tailwindcss-config';
const Colors = theme.colors;
const ORDER = ['community', 'publicSale', 'earlyInvestors', 'team'];
export const VestingChart = () => {
@ -31,10 +33,10 @@ export const VestingChart = () => {
<AreaChart data={data}>
<defs>
{[
['pink', Colors.PINK],
['green', Colors.VEGA_GREEN],
['orange', Colors.VEGA_ORANGE],
['yellow', Colors.VEGA_YELLOW],
['pink', Colors.pink],
['green', Colors.green.vega],
['orange', Colors.orange],
['yellow', Colors.yellow.DEFAULT],
].map(([key, color]) => (
<linearGradient id={key} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity={0.85} />
@ -43,7 +45,7 @@ export const VestingChart = () => {
))}
</defs>
<Tooltip
contentStyle={{ backgroundColor: Colors.BLACK }}
contentStyle={{ backgroundColor: Colors.black.DEFAULT }}
separator=":"
formatter={(value: number) => {
return (
@ -68,7 +70,7 @@ export const VestingChart = () => {
value={t('VEGA').toString()}
position="left"
offset={-5}
fill={Colors.WHITE}
fill={Colors.white.DEFAULT}
/>
</YAxis>
<XAxis dataKey="date">
@ -76,24 +78,24 @@ export const VestingChart = () => {
value={t('date').toString()}
position="bottom"
offset={5}
fill={Colors.WHITE}
fill={Colors.white.DEFAULT}
/>
</XAxis>
<ReferenceLine
x={currentDate}
stroke={Colors.WHITE}
stroke={Colors.white.DEFAULT}
strokeWidth={2}
label={{
position: 'right',
value: currentDate,
fill: Colors.WHITE,
fill: Colors.white.DEFAULT,
}}
/>
<Area
dot={false}
type="linear"
dataKey="team"
stroke={Colors.PINK}
stroke={Colors.pink}
fill="url(#pink)"
yAxisId={0}
strokeWidth={2}
@ -105,7 +107,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="earlyInvestors"
stroke={Colors.VEGA_GREEN}
stroke={Colors.green.vega}
fill="url(#green)"
yAxisId={0}
strokeWidth={2}
@ -117,7 +119,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="publicSale"
stroke={Colors.VEGA_YELLOW}
stroke={Colors.yellow.DEFAULT}
fill="url(#yellow)"
yAxisId={0}
strokeWidth={2}
@ -129,7 +131,7 @@ export const VestingChart = () => {
dot={false}
type="monotone"
dataKey="community"
stroke={Colors.VEGA_ORANGE}
stroke={Colors.orange}
fill="url(#orange)"
yAxisId={0}
strokeWidth={2}

View File

@ -71,8 +71,8 @@ export const FlashCell = memo(({ children, value }: FlashCellProps) => {
if (value < previousValue) {
ref.current?.animate(
[
{ color: theme.colors.vega.pink },
{ color: theme.colors.vega.pink, offset: 0.8 },
{ color: theme.colors.pink },
{ color: theme.colors.pink, offset: 0.8 },
{ color: 'inherit' },
],
FLASH_DURATION
@ -80,8 +80,8 @@ export const FlashCell = memo(({ children, value }: FlashCellProps) => {
} else if (value > previousValue) {
ref.current?.animate(
[
{ color: theme.colors.vega.green },
{ color: theme.colors.vega.green, offset: 0.8 },
{ color: theme.colors.green.vega },
{ color: theme.colors.green.vega, offset: 0.8 },
{ color: 'inherit' },
],
FLASH_DURATION

View File

@ -10,6 +10,26 @@ module.exports = {
colors: {
transparent: 'transparent',
current: 'currentColor',
bullish: '#26FF8A',
bearish: '#ED1515',
vega: {
yellow: '#EDFF22',
pink: '#FF2D5E',
green: '#00F780',
},
red: {
DEFAULT: '#ED1515',
transparent: 'rgba(255, 38, 65, 0.3)',
vega: '#FF261A',
},
green: {
DEFAULT: '#26FF8A',
transparent: 'rgba(38, 255, 138, 0.3)',
dark: '#246340',
vega: '#00F780',
},
text: '#C7C7C7',
deemphasise: '#8A9BA8',
white: {
DEFAULT: '#FFF',
'02': 'rgba(255, 255, 255, 0.02)',
@ -36,12 +56,12 @@ module.exports = {
},
blue: '#1DA2FB',
coral: '#FF6057',
vega: {
yellow: '#EDFF22',
pink: '#FF2D5E',
green: '#00F780',
pink: '#FF2D5E',
orange: '#D9822B',
yellow: {
DEFAULT: '#EDFF22',
dark: '#474B0A', // yellow 0.3 opacity on black
},
'vega-yellow-dark': '#474B0A', // yellow 0.3 opacity on black
intent: {
danger: '#FF261A',
warning: '#FF7A1A',

View File

@ -18,7 +18,7 @@ const vegaCustomClasses = plugin(function ({ addUtilities }) {
},
'.dark .syntax-highlighter-wrapper .hljs': {
background: '#2C2C2C',
color: theme.colors.vega.green,
color: theme.colors.vega.green.DEFAULT,
},
'.syntax-highlighter-wrapper .hljs-literal': {
color: theme.colors.vega.pink,

View File

@ -9,7 +9,7 @@ const agGridLightVariables = `
--ag-header-background-color: ${theme.colors.white[100]};
--ag-odd-row-background-color: ${theme.colors.white[100]};
--ag-row-border-color: ${theme.colors.white[100]};
--ag-row-hover-color: ${theme.colors.vega.yellow};
--ag-row-hover-color: ${theme.colors.yellow.DEFAULT};
--ag-font-size: 12px;
}

View File

@ -96,7 +96,7 @@ const getClasses = (
standardButtonPaddingRight,
standardButtonBorderWidth,
buttonHeight,
'bg-vega-yellow dark:bg-vega-yellow hover:bg-vega-yellow-dark dark:hover:bg-vega-yellow/30 active:bg-white dark:active:bg-black',
'bg-vega-yellow dark:bg-vega-yellow hover:bg-yellow/dark dark:hover:bg-vega-yellow/30 active:bg-white dark:active:bg-black',
'text-ui uppercase text-black dark:text-black hover:text-white dark:hover:text-white active:text-black dark:active:text-white',
'border-transparent dark:border-transparent',
];

View File

@ -22,3 +22,4 @@ export * from './toggle';
export * from './tooltip';
export * from './vega-logo';
export * from './syntax-highlighter';
export * from './sparkline';

View File

@ -0,0 +1 @@
export * from './sparkline';

View File

@ -0,0 +1,73 @@
import { render, screen } from '@testing-library/react';
import { theme } from '@vegaprotocol/tailwindcss-config';
import { Sparkline } from './sparkline';
export const Colors = theme.colors;
const props = {
data: [
1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 6, 7, 8, 9,
10, 11, 12,
],
};
it('Renders an svg with a single path', () => {
render(<Sparkline {...props} />);
expect(screen.getByTestId('sparkline-svg')).toBeInTheDocument();
const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0];
expect(path).toBeInTheDocument();
expect(path).toHaveAttribute('d', expect.any(String));
expect(path).toHaveAttribute('stroke', expect.any(String));
expect(path).toHaveAttribute('stroke-width', '2');
expect(path).toHaveAttribute('fill', 'transparent');
});
it('Requires a data prop but width and height are optional', () => {
render(<Sparkline {...props} />);
const svg = screen.getByTestId('sparkline-svg');
expect(svg).toHaveAttribute('width', '60');
expect(svg).toHaveAttribute('height', '15');
});
it('Renders a red line if the last value is less than the first', () => {
props.data[0] = 10;
props.data[props.data.length - 1] = 5;
render(<Sparkline {...props} />);
const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0];
expect(path).toHaveClass('stroke-bearish');
});
it('Renders a green line if the last value is greater than the first', () => {
props.data[0] = 5;
props.data[props.data.length - 1] = 10;
render(<Sparkline {...props} />);
const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0];
expect(path).toHaveClass('stroke-bullish');
});
it('Renders a white line if the first and last values are equal', () => {
props.data[0] = 5;
props.data[props.data.length - 1] = 5;
render(<Sparkline {...props} />);
const paths = screen.getAllByTestId('sparkline-path');
const path = paths[0];
expect(path).toHaveClass(
'[vector-effect:non-scaling-stroke] stroke-black/40 dark:stroke-white/40'
);
});
it('Renders a gray line if there are not 24 values', () => {
props.data = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23,
];
render(<Sparkline {...props} />);
const paths = screen.queryAllByTestId('sparkline-path');
expect(paths).toHaveLength(2);
expect(paths[0]).toHaveClass(
'[vector-effect:non-scaling-stroke] stroke-black/40 dark:stroke-white/40'
);
});

View File

@ -0,0 +1,56 @@
import type { Story, Meta } from '@storybook/react';
import { Sparkline } from './sparkline';
export default {
component: Sparkline,
title: 'Sparkline',
} as Meta;
const Template: Story = (args) => <Sparkline data={args['data']} {...args} />;
export const Grey = Template.bind({});
Grey.args = {
data: [
1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 6, 7, 8,
],
width: 60,
height: 30,
points: 25,
className: 'w-[113px]',
};
export const Equal = Template.bind({});
Equal.args = {
data: [
12, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 6, 7, 8, 9,
10, 11, 12,
],
width: 60,
height: 30,
points: 25,
className: 'w-[113px]',
};
export const Increase = Template.bind({});
Increase.args = {
data: [
1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 6, 7, 8, 9,
10, 11, 12,
],
width: 60,
height: 30,
points: 25,
className: 'w-[113px]',
};
export const Decrease = Template.bind({});
Decrease.args = {
data: [
12, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 6, 7, 8, 9,
10, 11, 1,
],
width: 60,
height: 30,
points: 25,
className: 'w-[113px]',
};

View File

@ -0,0 +1,132 @@
import { extent } from 'd3-array';
import { scaleLinear } from 'd3-scale';
import { line } from 'd3-shape';
import isEqual from 'lodash/isEqual';
import React from 'react';
function colorByChange(a: number, b: number) {
return a === b
? 'stroke-black/40 dark:stroke-white/40'
: a < b
? 'stroke-bullish'
: 'stroke-bearish';
}
export interface SparklineProps {
data: number[];
width?: number;
height?: number;
points?: number;
className?: string;
}
export const SparklineView = ({
data,
width = 60,
height = 15,
points = 25,
className,
}: SparklineProps) => {
// How many points are missing. If market is 12 hours old the 25 - 12
const preMarketLength = points - data.length;
// Create two dimensional array for sparkline points [x, y]
const marketData: [number, number][] = data.map((d, i) => [
preMarketLength + i,
d,
]);
// Empty two dimensional array for gray, 'no data' line
let preMarketData: [number, number][] = [];
// Get the extent for our y value
const [min, max] = extent(marketData, (d) => d[1]);
if (typeof min !== 'number' || typeof max !== 'number') {
return null;
}
// Create a second set of data to render a gray line for any
// missing points if the market is less than 24 hours old
if (marketData.length < points) {
// Populate preMarketData with the average of our extents
// so that the line renders centered vertically
const fillValue = (min + max) / 2;
preMarketData = new Array(points - marketData.length)
.fill(fillValue)
.map((d: number, i) => [i, d] as [number, number]);
// Add the first point of or market data so that the two
// lines join up
preMarketData.push(marketData[0] as [number, number]);
}
const xScale = scaleLinear().domain([0, points]).range([0, 100]);
const yScale = scaleLinear().domain([min, max]).range([100, 0]);
const lineSeries = line()
.x((d) => xScale(d[0]))
.y((d) => yScale(d[1]));
// Get the color of the marketData line
const [firstVal, lastVal] = [data[0], data[data.length - 1]];
const strokeClassName =
data.length >= 24
? colorByChange(firstVal, lastVal)
: 'stroke-black/40 dark:stroke-white/40';
// Create paths
const preMarketCreationPath = lineSeries(preMarketData);
const mainPath = lineSeries(marketData);
return (
<svg
data-testid="sparkline-svg"
className={`pt-px pr-0 w-full overflow-visible ${className}`}
width={width}
height={height}
viewBox="0 0 100 100"
preserveAspectRatio="none"
>
{preMarketCreationPath && (
<path
data-testid="sparkline-path"
className={`[vector-effect:non-scaling-stroke] ${strokeClassName}`}
d={preMarketCreationPath}
stroke="strokeCurrent"
strokeWidth={2}
fill="transparent"
/>
)}
{mainPath && (
<path
data-testid="sparkline-path"
d={mainPath}
className={strokeClassName}
stroke="strokeCurrent"
strokeWidth={1}
fill="transparent"
/>
)}
</svg>
);
};
SparklineView.displayName = 'SparklineView';
// Use react memo to only re-render if props change
export const Sparkline = React.memo(
SparklineView,
function (prevProps, nextProps) {
// Warning! The return value here is the opposite of shouldComponentUpdate.
// Return true if you DON NOT want a re-render
if (
prevProps.width !== nextProps.width ||
prevProps.height !== nextProps.height
) {
return false;
}
return isEqual(prevProps.data, nextProps.data);
}
);
Sparkline.displayName = 'Sparkline';

View File

@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node", "@testing-libary/jest-dom"]
"types": ["jest", "node", "@testing-library/jest-dom"]
},
"include": [
"**/*.test.ts",