0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-03 21:48:55 -05:00

feat(console): dashboard blocks and curve (#1076)

This commit is contained in:
Wang Sijie 2022-06-09 14:41:30 +08:00 committed by GitHub
parent 02f98ed259
commit c38fab89e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 372 additions and 69 deletions

View file

@ -20,7 +20,6 @@
"@fontsource/roboto-mono": "^4.5.7",
"@logto/phrases": "^0.1.0",
"@logto/react": "^0.1.14",
"@logto/shared": "^0.1.0",
"@logto/schemas": "^0.1.0",
"@logto/shared": "^0.1.0",
"@mdx-js/react": "^1.6.22",
@ -69,6 +68,7 @@
"react-paginate": "^8.1.2",
"react-router-dom": "^6.2.2",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.1.10",
"remark-gfm": "^3.0.1",
"stylelint": "^14.8.2",
"swr": "^1.2.2",

View file

@ -0,0 +1,63 @@
@use '@/scss/underscore' as _;
.number {
font: var(--font-headline-small);
}
.delta {
font: var(--font-title-medium);
color: var(--color-success-50);
display: flex;
align-items: center;
&.down {
color: var(--color-error-50);
}
}
.block {
flex: 1;
&:not(:last-child) {
margin-right: _.unit(4);
}
&.bordered {
border: 1px solid var(--color-divider);
width: 360px;
flex: unset;
}
.title {
font: var(--font-title-medium);
margin-bottom: 24px;
}
.content {
display: flex;
align-items: baseline;
.number {
flex: 1;
}
}
&.plain {
padding: 0;
.title {
font: var(--font-title-medium);
margin-bottom: _.unit(6);
}
.content {
display: flex;
align-items: center;
.number {
margin-right: _.unit(2);
flex: 0;
}
}
}
}

View file

@ -0,0 +1,42 @@
import { AdminConsoleKey } from '@logto/phrases';
import { conditionalString } from '@silverhand/essentials';
import classNames from 'classnames';
import React from 'react';
import { useTranslation } from 'react-i18next';
import Card from '@/components/Card';
import { ArrowDown, ArrowUp } from '@/icons/Arrow';
import { formatNumberWithComma } from '@/utilities/number';
import * as styles from './Block.module.scss';
type Props = {
count: number;
delta?: number;
title: AdminConsoleKey;
varient?: 'bordered' | 'default' | 'plain';
};
const Block = ({ varient = 'default', count, delta, title }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const deltaLable = delta !== undefined && `${conditionalString(delta >= 0 && '+')}${delta}`;
return (
<Card className={classNames(styles.block, styles[varient])}>
<div className={styles.title}>{t(title)}</div>
<div className={styles.content}>
<div className={styles.number}>{formatNumberWithComma(count)}</div>
{delta !== undefined && (
<div className={classNames(styles.delta, delta < 0 && styles.down)}>
<span>(${deltaLable})</span>
{delta > 0 && <ArrowUp />}
{delta < 0 && <ArrowDown />}
</div>
)}
</div>
</Card>
);
};
export default Block;

View file

@ -22,41 +22,14 @@
}
}
.topBlocks {
.blocks {
display: flex;
align-items: center;
.block {
flex: 1;
&:not(:last-child) {
margin-right: _.unit(4);
}
.title {
font: var(--font-title-medium);
margin-bottom: 24px;
}
.content {
display: flex;
align-items: baseline;
.number {
flex: 1;
font: var(--font-headline-small);
}
.delta {
font: var(--font-title-medium);
color: var(--color-success-50);
display: flex;
align-items: center;
&.down {
color: var(--color-error-50);
}
}
}
}
margin-bottom: _.unit(4);
}
.curve {
margin: _.unit(10) 0 _.unit(6);
width: 100%;
height: 168px;
}

View file

@ -1,20 +1,29 @@
import classNames from 'classnames';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import useSWR from 'swr';
import Card from '@/components/Card';
import { ArrowDown, ArrowUp } from '@/icons/Arrow';
import Block from './components/Block';
import * as styles from './index.module.scss';
import { NewUsersResponse, TotalUsersResponse } from './types';
import { ActiveUsersResponse, NewUsersResponse, TotalUsersResponse } from './types';
const Dashboard = () => {
const { data: totalData } = useSWR<TotalUsersResponse>('/api/dashboard/users/total');
const { data: newData } = useSWR<NewUsersResponse>('/api/dashboard/users/new');
const { data: activeData } = useSWR<ActiveUsersResponse>('/api/dashboard/users/active');
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const isLoading = !totalData || !newData;
const isLoading = !totalData || !newData || !activeData;
return (
<div className={styles.container}>
@ -23,35 +32,68 @@ const Dashboard = () => {
<div className={styles.subtitle}>{t('dashboard.description')}</div>
</div>
{!isLoading && (
<div className={styles.topBlocks}>
<Card className={styles.block}>
<div className={styles.title}>{t('dashboard.total_users')}</div>
<div className={styles.content}>
<div className={styles.number}>{totalData.totalUserCount}</div>
<>
<div className={styles.blocks}>
<Block title="dashboard.total_users" count={totalData.totalUserCount} />
<Block
title="dashboard.new_users_today"
count={newData.today.count}
delta={newData.today.delta}
/>
<Block
title="dashboard.new_users_7_days"
count={newData.last7Days.count}
delta={newData.last7Days.delta}
/>
</div>
<Card>
<Block
title="dashboard.daily_active_users"
count={activeData.dau.count}
delta={activeData.dau.delta}
varient="plain"
/>
<div className={styles.curve}>
<ResponsiveContainer>
<AreaChart
data={activeData.dauCurve.map((item) => ({
...item,
// Remove "year" for a compact label.
date: item.date.replace(/\d{4}-/, ''),
}))}
width={1100}
height={168}
>
<CartesianGrid vertical={false} />
<Area
type="monotone"
dataKey="count"
stroke="#5D34F2"
strokeWidth={2}
fill="#F2EFFD"
/>
<XAxis dataKey="date" />
<YAxis orientation="right" axisLine={false} tickLine={false} />
<Tooltip />
</AreaChart>
</ResponsiveContainer>
</div>
<div className={styles.blocks}>
<Block
title="dashboard.weekly_active_users"
count={activeData.wau.count}
delta={activeData.wau.delta}
varient="bordered"
/>
<Block
title="dashboard.monthly_active_users"
count={activeData.mau.count}
delta={activeData.mau.delta}
varient="bordered"
/>
</div>
</Card>
<Card className={styles.block}>
<div className={styles.title}>{t('dashboard.new_users_today')}</div>
<div className={styles.content}>
<div className={styles.number}>{newData.today.count}</div>
<div className={classNames(styles.delta, newData.today.delta < 0 && styles.down)}>
({newData.today.delta > 0 && '+'}
{newData.today.delta}){newData.today.delta > 0 ? <ArrowUp /> : <ArrowDown />}
</div>
</div>
</Card>
<Card className={styles.block}>
<div className={styles.title}>{t('dashboard.new_users_7_days')}</div>
<div className={styles.content}>
<div className={styles.number}>{newData.last7Days.count}</div>
<div className={classNames(styles.delta, newData.last7Days.delta < 0 && styles.down)}>
({newData.last7Days.delta > 0 && '+'}
{newData.last7Days.delta})
{newData.last7Days.delta > 0 ? <ArrowUp /> : <ArrowDown />}
</div>
</div>
</Card>
</div>
</>
)}
</div>
);

View file

@ -11,3 +11,10 @@ export type NewUsersResponse = {
today: CountAndDelta;
last7Days: CountAndDelta;
};
export type ActiveUsersResponse = {
dau: CountAndDelta;
wau: CountAndDelta;
mau: CountAndDelta;
dauCurve: Array<{ date: string; count: number }>;
};

View file

@ -0,0 +1,2 @@
export const formatNumberWithComma = (value: number): string =>
value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');

178
pnpm-lock.yaml generated
View file

@ -670,6 +670,7 @@ importers:
react-paginate: ^8.1.2
react-router-dom: ^6.2.2
react-syntax-highlighter: ^15.5.0
recharts: ^2.1.10
remark-gfm: ^3.0.1
stylelint: ^14.8.2
swr: ^1.2.2
@ -727,6 +728,7 @@ importers:
react-paginate: 8.1.2_react@17.0.2
react-router-dom: 6.2.2_sfoxds7t5ydpegc3knd667wn6m
react-syntax-highlighter: 15.5.0_react@17.0.2
recharts: 2.1.10_sfoxds7t5ydpegc3knd667wn6m
remark-gfm: 3.0.1
stylelint: 14.8.2
swr: 1.2.2_react@17.0.2
@ -7779,6 +7781,10 @@ packages:
csstype: 3.0.11
dev: true
/@types/resize-observer-browser/0.1.7:
resolution: {integrity: sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg==}
dev: true
/@types/responselike/1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies:
@ -10526,6 +10532,10 @@ packages:
source-map: 0.6.1
dev: true
/css-unit-converter/1.1.2:
resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==}
dev: true
/css-what/2.1.3:
resolution: {integrity: sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==}
dev: true
@ -10646,6 +10656,58 @@ packages:
resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==}
dev: true
/d3-array/2.12.1:
resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
dependencies:
internmap: 1.0.1
dev: true
/d3-color/2.0.0:
resolution: {integrity: sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==}
dev: true
/d3-format/2.0.0:
resolution: {integrity: sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==}
dev: true
/d3-interpolate/2.0.1:
resolution: {integrity: sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==}
dependencies:
d3-color: 2.0.0
dev: true
/d3-path/2.0.0:
resolution: {integrity: sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==}
dev: true
/d3-scale/3.3.0:
resolution: {integrity: sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ==}
dependencies:
d3-array: 2.12.1
d3-format: 2.0.0
d3-interpolate: 2.0.1
d3-time: 2.1.1
d3-time-format: 3.0.0
dev: true
/d3-shape/2.1.0:
resolution: {integrity: sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==}
dependencies:
d3-path: 2.0.0
dev: true
/d3-time-format/3.0.0:
resolution: {integrity: sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==}
dependencies:
d3-time: 2.1.1
dev: true
/d3-time/2.1.1:
resolution: {integrity: sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==}
dependencies:
d3-array: 2.12.1
dev: true
/dargs/7.0.0:
resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==}
engines: {node: '>=8'}
@ -10737,6 +10799,10 @@ packages:
engines: {node: '>=10'}
dev: false
/decimal.js-light/2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
dev: true
/decimal.js/10.3.1:
resolution: {integrity: sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==}
dev: true
@ -11005,6 +11071,12 @@ packages:
utila: 0.4.0
dev: true
/dom-helpers/3.4.0:
resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
dependencies:
'@babel/runtime': 7.18.3
dev: true
/dom-serializer/0.1.1:
resolution: {integrity: sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==}
dependencies:
@ -11986,6 +12058,10 @@ packages:
resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
dev: true
/fast-equals/2.0.4:
resolution: {integrity: sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==}
dev: true
/fast-glob/3.2.11:
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
engines: {node: '>=8.6.0'}
@ -13591,6 +13667,10 @@ packages:
side-channel: 1.0.4
dev: true
/internmap/1.0.1:
resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
dev: true
/interpret/1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
@ -16017,7 +16097,6 @@ packages:
/mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: true
/mime-types/2.1.18:
resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==}
@ -16037,7 +16116,6 @@ packages:
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: true
/mime/1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
@ -17501,6 +17579,10 @@ packages:
resolution: {integrity: sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==}
dev: true
/performance-now/2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
dev: true
/pg-connection-string/2.5.0:
resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==}
dev: false
@ -18186,6 +18268,10 @@ packages:
postcss-selector-parser: 6.0.10
dev: true
/postcss-value-parser/3.3.1:
resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==}
dev: true
/postcss-value-parser/4.2.0:
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
dev: true
@ -18626,6 +18712,12 @@ packages:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
/raf/3.4.1:
resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
dependencies:
performance-now: 2.1.0
dev: true
/randombytes/2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
@ -18962,6 +19054,19 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/react-resize-detector/6.7.8_sfoxds7t5ydpegc3knd667wn6m:
resolution: {integrity: sha512-0FaEcUBAbn+pq3PT5a9hHRebUfuS1SRLGLpIw8LydU7zX429I6XJgKerKAMPsJH0qWAl6o5bVKNqFJqr6tGPYw==}
peerDependencies:
react: ^16.0.0 || ^17.0.0
react-dom: ^16.0.0 || ^17.0.0
dependencies:
'@types/resize-observer-browser': 0.1.7
lodash: 4.17.21
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
resize-observer-polyfill: 1.5.1
dev: true
/react-router-config/5.1.1_oyuskl3t7voyrff2xstzuy4hqu:
resolution: {integrity: sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==}
peerDependencies:
@ -19060,6 +19165,20 @@ packages:
react: 17.0.2
dev: true
/react-smooth/2.0.0_sfoxds7t5ydpegc3knd667wn6m:
resolution: {integrity: sha512-wK4dBBR6P21otowgMT9toZk+GngMplGS1O5gk+2WSiHEXIrQgDvhR5IIlT74Vtu//qpTcipkgo21dD7a7AUNxw==}
peerDependencies:
prop-types: ^15.6.0
react: ^15.0.0 || ^16.0.0 || ^17.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0
dependencies:
fast-equals: 2.0.4
raf: 3.4.1
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react-transition-group: 2.9.0_sfoxds7t5ydpegc3knd667wn6m
dev: true
/react-string-replace/1.0.0:
resolution: {integrity: sha512-+iLsyE4AeSmnfctgswXOf1PmKRgns6wJ4LVb+8ADMU6mDK3jvBE11QzfMQf7CYtPUUiBCDjZ9ZppzXOIYrzCRg==}
engines: {node: '>=0.12.0'}
@ -19112,6 +19231,20 @@ packages:
react-dom: 17.0.2_react@17.0.2
dev: true
/react-transition-group/2.9.0_sfoxds7t5ydpegc3knd667wn6m:
resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==}
peerDependencies:
react: '>=15.0.0'
react-dom: '>=15.0.0'
dependencies:
dom-helpers: 3.4.0
loose-envify: 1.4.0
prop-types: 15.8.1
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react-lifecycles-compat: 3.0.4
dev: true
/react/17.0.2:
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
engines: {node: '>=0.10.0'}
@ -19259,6 +19392,36 @@ packages:
resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==}
dev: true
/recharts-scale/0.4.5:
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
dependencies:
decimal.js-light: 2.5.1
dev: true
/recharts/2.1.10_sfoxds7t5ydpegc3knd667wn6m:
resolution: {integrity: sha512-me6c8m2Gs88X/nuM2gDSTDIhpSLNMbiTrlE4Cu53hjZNegT3g3xLlTrbYSAQuBCFWuWJAZXCmEuMr6AwizLyaA==}
engines: {node: '>=12'}
peerDependencies:
react: ^16.0.0 || ^17.0.0
react-dom: ^16.0.0 || ^17.0.0
dependencies:
classnames: 2.3.1
d3-interpolate: 2.0.1
d3-scale: 3.3.0
d3-shape: 2.1.0
eventemitter3: 4.0.7
lodash: 4.17.21
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react-is: 16.13.1
react-resize-detector: 6.7.8_sfoxds7t5ydpegc3knd667wn6m
react-smooth: 2.0.0_sfoxds7t5ydpegc3knd667wn6m
recharts-scale: 0.4.5
reduce-css-calc: 2.1.8
transitivePeerDependencies:
- prop-types
dev: true
/rechoir/0.6.2:
resolution: {integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=}
engines: {node: '>= 0.10'}
@ -19348,6 +19511,13 @@ packages:
- webpack
dev: true
/reduce-css-calc/2.1.8:
resolution: {integrity: sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==}
dependencies:
css-unit-converter: 1.1.2
postcss-value-parser: 3.3.1
dev: true
/redux/4.1.2:
resolution: {integrity: sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==}
dependencies:
@ -19575,6 +19745,10 @@ packages:
/requires-port/1.0.0:
resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=}
/resize-observer-polyfill/1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
dev: true
/resolve-alpn/1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}