From fd0b376e88d298073e32cd69c1bc7201183ee1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B0=D0=B5=D0=B2=20=D0=95=D0=B2=D0=B3=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9?= Date: Fri, 22 Jul 2022 18:48:05 +0300 Subject: [PATCH] add indicator plugin --- src/constants/common.ts | 2 + src/constants/index.ts | 1 + src/i18n/keysets/en.json | 1 + src/i18n/keysets/ru.json | 1 + src/plugins/index.ts | 2 + .../__stories__/Indicator.stories.tsx | 63 ++++++++++++++++++ src/plugins/indicator/index.ts | 7 ++ .../indicator/renderer/IndicatorItem.tsx | 35 ++++++++++ .../indicator/renderer/IndicatorWidget.scss | 66 +++++++++++++++++++ .../indicator/renderer/IndicatorWidget.tsx | 52 +++++++++++++++ src/plugins/indicator/types.ts | 24 +++++++ src/types/index.ts | 3 + src/types/widget.ts | 5 ++ 13 files changed, 262 insertions(+) create mode 100644 src/constants/common.ts create mode 100644 src/constants/index.ts create mode 100644 src/plugins/indicator/__stories__/Indicator.stories.tsx create mode 100644 src/plugins/indicator/index.ts create mode 100644 src/plugins/indicator/renderer/IndicatorItem.tsx create mode 100644 src/plugins/indicator/renderer/IndicatorWidget.scss create mode 100644 src/plugins/indicator/renderer/IndicatorWidget.tsx create mode 100644 src/plugins/indicator/types.ts diff --git a/src/constants/common.ts b/src/constants/common.ts new file mode 100644 index 00000000..b19578da --- /dev/null +++ b/src/constants/common.ts @@ -0,0 +1,2 @@ +// This css class should be added for DOM element for correct calculation of scrollHeight +export const CHARTKIT_SCROLLABLE_NODE_CLASSNAME = 'chartkit-scrollable-node'; diff --git a/src/constants/index.ts b/src/constants/index.ts new file mode 100644 index 00000000..fe0e0e5e --- /dev/null +++ b/src/constants/index.ts @@ -0,0 +1 @@ +export {CHARTKIT_SCROLLABLE_NODE_CLASSNAME} from './common'; diff --git a/src/i18n/keysets/en.json b/src/i18n/keysets/en.json index 1675a4c4..60a607f2 100644 --- a/src/i18n/keysets/en.json +++ b/src/i18n/keysets/en.json @@ -4,6 +4,7 @@ "tooltip-rest": "Rest" }, "error": { + "label_no-data": "No data", "label_unknown-plugin": "Unknown plugin type \"{{type}}\"", "label_unknown-error": "Unknown error" } diff --git a/src/i18n/keysets/ru.json b/src/i18n/keysets/ru.json index a3f0f8cd..2426b641 100644 --- a/src/i18n/keysets/ru.json +++ b/src/i18n/keysets/ru.json @@ -4,6 +4,7 @@ "tooltip-rest": "Остальные" }, "error": { + "label_no-data": "Нет данных", "label_unknown-plugin": "Неизвестный тип плагина \"{{type}}\"", "label_unknown-error": "Неизвестная ошибка" } diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 45a65f29..d4ba5198 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,2 +1,4 @@ export {YagrPlugin} from './yagr'; export type {YagrWidgetData, YagrWidgetProps} from './yagr/types'; +export {IndicatorPlugin} from './indicator'; +export type {IndicatorWidgetData, IndicatorWidgetProps} from './indicator/types'; diff --git a/src/plugins/indicator/__stories__/Indicator.stories.tsx b/src/plugins/indicator/__stories__/Indicator.stories.tsx new file mode 100644 index 00000000..8d0a578e --- /dev/null +++ b/src/plugins/indicator/__stories__/Indicator.stories.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import {Meta, Story} from '@storybook/react'; +import {withKnobs, boolean, color as colorKnob, radios, text} from '@storybook/addon-knobs'; +import {cloneDeep} from 'lodash'; +import {Button} from '@yandex-cloud/uikit'; +import {settings} from '../../../libs'; +import {ChartKit} from '../../../components/ChartKit'; +import type {ChartKitRef} from '../../../types'; +import {IndicatorPlugin} from '../'; +import type {IndicatorWidgetData, IndicatorWidgetDataItem} from '../types'; + +const data: IndicatorWidgetData = { + data: [ + { + content: { + current: { + value: 1539577973, + }, + }, + }, + ], +}; + +const Template: Story = () => { + const [shown, setShown] = React.useState(false); + const chartkitRef = React.useRef(); + const color = colorKnob('color', '#4da2f1'); + const size = radios( + 'size', + {s: 's', m: 'm', l: 'l', xl: 'xl'}, + 'm', + ); + const title = text('title', 'Value title'); + const nowrap = boolean('nowrap', false); + const resultData = cloneDeep(data); + + if (resultData.data) { + resultData.data[0].size = size; + resultData.data[0].color = color; + resultData.data[0].title = title; + resultData.data[0].nowrap = nowrap; + } + + if (!shown) { + settings.set({plugins: [IndicatorPlugin]}); + return ; + } + + return ( +
+ +
+ ); +}; + +export const Showcase = Template.bind({}); + +const meta: Meta = { + title: 'Plugins/Indicator', + decorators: [withKnobs], +}; + +export default meta; diff --git a/src/plugins/indicator/index.ts b/src/plugins/indicator/index.ts new file mode 100644 index 00000000..e826e9ff --- /dev/null +++ b/src/plugins/indicator/index.ts @@ -0,0 +1,7 @@ +import React from 'react'; +import {ChartKitPlugin} from '../../types'; + +export const IndicatorPlugin: ChartKitPlugin = { + type: 'indicator', + renderer: React.lazy(() => import('./renderer/IndicatorWidget')), +}; diff --git a/src/plugins/indicator/renderer/IndicatorItem.tsx b/src/plugins/indicator/renderer/IndicatorItem.tsx new file mode 100644 index 00000000..f5f296f6 --- /dev/null +++ b/src/plugins/indicator/renderer/IndicatorItem.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import block from 'bem-cn-lite'; +import type {IndicatorWidgetProps, IndicatorWidgetDataItem} from '../types'; + +const b = block('chartkit-indicator'); + +export const IndicatorItem = ( + props: IndicatorWidgetDataItem & { + defaultColor?: string; + formatNumber?: IndicatorWidgetProps['formatNumber']; + }, +) => { + const {formatNumber, content, color, defaultColor, size, title, nowrap} = props; + const mods = {size, nowrap}; + const style: React.CSSProperties = {color: color || defaultColor}; + + let value = content.current.value; + + if (formatNumber && typeof value === 'number') { + value = formatNumber(value, content.current); + } + + return ( +
+ {title && ( +
+ {title} +
+ )} +
+ {value} +
+
+ ); +}; diff --git a/src/plugins/indicator/renderer/IndicatorWidget.scss b/src/plugins/indicator/renderer/IndicatorWidget.scss new file mode 100644 index 00000000..e0df3635 --- /dev/null +++ b/src/plugins/indicator/renderer/IndicatorWidget.scss @@ -0,0 +1,66 @@ +.chartkit-indicator { + $class: &; + + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + + &__content { + width: 100%; + overflow: auto; + } + + &__item { + padding: 15px; + font-size: inherit; + box-sizing: border-box; + + &_nowrap { + #{$class}__item-title { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + + &_size_s #{$class}__item-title { + font-size: 13px; + } + + &_size_s #{$class}__item-value { + font-size: 24px; + } + + &_size_l #{$class}__item-title { + font-size: 20px; + } + + &_size_l #{$class}__item-value { + font-size: 64px; + } + + &_size_xl #{$class}__item-title { + font-size: 24px; + } + + &_size_xl #{$class}__item-value { + font-size: 80px; + } + } + + &__item-title { + font-weight: 500; + line-height: 1.2; + color: var(--yc-color-text-primary); + font-size: 16px; + padding-bottom: 0.125em; + } + + &__item-value { + font-weight: 500; + line-height: 1.2; + font-size: 48px; + white-space: nowrap; + } +} diff --git a/src/plugins/indicator/renderer/IndicatorWidget.tsx b/src/plugins/indicator/renderer/IndicatorWidget.tsx new file mode 100644 index 00000000..81ecd47a --- /dev/null +++ b/src/plugins/indicator/renderer/IndicatorWidget.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import block from 'bem-cn-lite'; +import {i18n} from '../../../i18n'; +import {CHARTKIT_ERROR_CODE, ChartKitError} from '../../../libs'; +import {CHARTKIT_SCROLLABLE_NODE_CLASSNAME} from '../../../constants'; +import type {ChartKitWidgetRef} from '../../../types'; +import type {IndicatorWidgetProps} from '../types'; +import {IndicatorItem} from './IndicatorItem'; + +import './IndicatorWidget.scss'; + +const b = block('chartkit-indicator'); + +const IndicatorWidget = React.forwardRef( + // _ref needs to avoid this warning: + // "forwardRef render functions accept exactly two parameters: props and ref" + (props, _ref) => { + const { + onLoad, + formatNumber, + data: {data, defaultColor}, + } = props; + + React.useEffect(() => { + onLoad?.(); + }, []); + + if (!data) { + throw new ChartKitError({ + code: CHARTKIT_ERROR_CODE.NO_DATA, + message: i18n('error', 'label_no-data'), + }); + } + + return ( +
+
+ {data.map((item, index) => ( + + ))} +
+
+ ); + }, +); + +export default IndicatorWidget; diff --git a/src/plugins/indicator/types.ts b/src/plugins/indicator/types.ts new file mode 100644 index 00000000..e58308d1 --- /dev/null +++ b/src/plugins/indicator/types.ts @@ -0,0 +1,24 @@ +import type {ChartKitFormatNumber} from '../../types'; + +export type IndicatorWidgetDataItem = { + content: { + current: { + value: string | number; + } & Record; + }; + color?: string; + size?: 's' | 'm' | 'l' | 'xl'; + title?: string; + nowrap?: boolean; +}; + +export type IndicatorWidgetData = { + data?: IndicatorWidgetDataItem[]; + defaultColor?: string; +}; + +export type IndicatorWidgetProps = { + data: IndicatorWidgetData; + onLoad?: () => void; + formatNumber?: ChartKitFormatNumber; +}; diff --git a/src/types/index.ts b/src/types/index.ts index 5bc6261e..2e854e7d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -19,12 +19,15 @@ export type ChartKitOnLoadData = { export type ChartKitOnError = (data: {error: any}) => void; +export type ChartKitFormatNumber = (value: number, options?: unknown) => string; + export type ChartKitProps = { type: T; data: ChartkitWidget[T]['data']; id?: string; onLoad?: (data?: ChartKitOnLoadData) => void; onError?: ChartKitOnError; + formatNumber?: ChartKitFormatNumber; } & {[key in keyof Omit]: ChartkitWidget[T][key]}; export type ChartKitPlugin = { diff --git a/src/types/widget.ts b/src/types/widget.ts index 8bc41e82..5b5dcbe9 100644 --- a/src/types/widget.ts +++ b/src/types/widget.ts @@ -1,9 +1,14 @@ import type Yagr from 'yagr'; import type {YagrWidgetData} from '../plugins/yagr/types'; +import type {IndicatorWidgetData} from '../plugins/indicator/types'; export interface ChartkitWidget { yagr: { data: YagrWidgetData; widget: Yagr; }; + indicator: { + data: IndicatorWidgetData; + widget: never; + }; }