mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
New component Statistic / Countdown (#14154)
* init
* number format
* add Countdown
* ⚡ Try something ts stuff
* move out Countdown
* add format
* support countdown
* add prefix & suffix
* Move Number to Statistic
* adjust ts
* clean up
* roll back of lodash
* update doc & style
* variable of the less style
* add demo test
* full coverage
* hide title if not need
* update snapshot
* update accessiblity
* update color
* stop countdown when time is out
* formatTimeStr adjust
* use reset class
* add miss tab index
* update doc
* update title prop & snapshot
* rm additional aria. It's over design
* use card sample on unit demo
* sfc
* adjust demo
This commit is contained in:
parent
54463c908b
commit
ff13ac0f5e
@ -35,6 +35,7 @@ Array [
|
|||||||
"message",
|
"message",
|
||||||
"Menu",
|
"Menu",
|
||||||
"Modal",
|
"Modal",
|
||||||
|
"Statistic",
|
||||||
"notification",
|
"notification",
|
||||||
"Pagination",
|
"Pagination",
|
||||||
"Popconfirm",
|
"Popconfirm",
|
||||||
|
@ -11043,6 +11043,66 @@ exports[`ConfigProvider components Spin prefixCls 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`ConfigProvider components Statistic configProvider 1`] = `
|
||||||
|
<div
|
||||||
|
class="config-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="config-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="config-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="config-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ConfigProvider components Statistic normal 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`ConfigProvider components Statistic prefixCls 1`] = `
|
||||||
|
<div
|
||||||
|
class="prefix-Statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="prefix-Statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="prefix-Statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="prefix-Statistic-content-value-int"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`ConfigProvider components Steps configProvider 1`] = `
|
exports[`ConfigProvider components Steps configProvider 1`] = `
|
||||||
<div
|
<div
|
||||||
class="config-steps config-steps-horizontal config-steps-label-horizontal"
|
class="config-steps config-steps-horizontal config-steps-label-horizontal"
|
||||||
|
@ -40,6 +40,7 @@ import Select from '../../select';
|
|||||||
import Skeleton from '../../skeleton';
|
import Skeleton from '../../skeleton';
|
||||||
import Slider from '../../slider';
|
import Slider from '../../slider';
|
||||||
import Spin from '../../spin';
|
import Spin from '../../spin';
|
||||||
|
import Statistic from '../../statistic';
|
||||||
import Steps from '../../steps';
|
import Steps from '../../steps';
|
||||||
import Switch from '../../switch';
|
import Switch from '../../switch';
|
||||||
import Table from '../../table';
|
import Table from '../../table';
|
||||||
@ -427,6 +428,9 @@ describe('ConfigProvider', () => {
|
|||||||
// Spin
|
// Spin
|
||||||
testPair('Spin', props => <Spin {...props} />);
|
testPair('Spin', props => <Spin {...props} />);
|
||||||
|
|
||||||
|
// Statistic
|
||||||
|
testPair('Statistic', props => <Statistic {...props} value={0} />);
|
||||||
|
|
||||||
// Steps
|
// Steps
|
||||||
testPair('Steps', props => {
|
testPair('Steps', props => {
|
||||||
const myProps = { ...props };
|
const myProps = { ...props };
|
||||||
|
@ -87,9 +87,9 @@ interface ConsumerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function withConfigConsumer<ExportProps extends BasicExportProps>(config: ConsumerConfig) {
|
export function withConfigConsumer<ExportProps extends BasicExportProps>(config: ConsumerConfig) {
|
||||||
return function(Component: IReactComponent): React.SFC<ExportProps> {
|
return function <ComponentDef>(Component: IReactComponent): React.SFC<ExportProps> & ComponentDef {
|
||||||
// Wrap with ConfigConsumer. Since we need compatible with react 15, be care when using ref methods
|
// Wrap with ConfigConsumer. Since we need compatible with react 15, be care when using ref methods
|
||||||
return (props: ExportProps) => (
|
return ((props: ExportProps) => (
|
||||||
<ConfigConsumer>
|
<ConfigConsumer>
|
||||||
{(configProps: ConfigConsumerProps) => {
|
{(configProps: ConfigConsumerProps) => {
|
||||||
const { prefixCls: basicPrefixCls } = config;
|
const { prefixCls: basicPrefixCls } = config;
|
||||||
@ -99,7 +99,7 @@ export function withConfigConsumer<ExportProps extends BasicExportProps>(config:
|
|||||||
return <Component {...configProps} {...props} prefixCls={prefixCls} />;
|
return <Component {...configProps} {...props} prefixCls={prefixCls} />;
|
||||||
}}
|
}}
|
||||||
</ConfigConsumer>
|
</ConfigConsumer>
|
||||||
);
|
)) as React.SFC<ExportProps> & ComponentDef;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,8 @@ export { default as Menu } from './menu';
|
|||||||
|
|
||||||
export { default as Modal } from './modal';
|
export { default as Modal } from './modal';
|
||||||
|
|
||||||
|
export { default as Statistic } from './statistic';
|
||||||
|
|
||||||
export { default as notification } from './notification';
|
export { default as notification } from './notification';
|
||||||
|
|
||||||
export { default as Pagination } from './pagination';
|
export { default as Pagination } from './pagination';
|
||||||
|
78
components/statistic/Countdown.tsx
Normal file
78
components/statistic/Countdown.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { polyfill } from 'react-lifecycles-compat';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import interopDefault from '../_util/interopDefault';
|
||||||
|
import Statistic, { StatisticProps } from './Statistic';
|
||||||
|
import { formatCountdown, countdownValueType, FormatConfig } from './utils';
|
||||||
|
|
||||||
|
const REFRESH_INTERVAL = 1000 / 30;
|
||||||
|
|
||||||
|
interface CountdownProps extends StatisticProps {
|
||||||
|
value?: countdownValueType;
|
||||||
|
format?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Countdown extends React.Component<CountdownProps, {}> {
|
||||||
|
static defaultProps: Partial<CountdownProps> = {
|
||||||
|
format: 'HH:mm:ss',
|
||||||
|
};
|
||||||
|
|
||||||
|
countdownId?: number = undefined;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.syncTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.syncTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.stopTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
syncTimer = () => {
|
||||||
|
const { value } = this.props;
|
||||||
|
|
||||||
|
const timestamp = interopDefault(moment)(value).valueOf();
|
||||||
|
if (timestamp >= Date.now()) {
|
||||||
|
this.startTimer();
|
||||||
|
} else {
|
||||||
|
this.stopTimer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
startTimer = () => {
|
||||||
|
if (this.countdownId !== undefined) return;
|
||||||
|
|
||||||
|
this.countdownId = window.setInterval(() => {
|
||||||
|
this.forceUpdate();
|
||||||
|
}, REFRESH_INTERVAL);
|
||||||
|
};
|
||||||
|
|
||||||
|
stopTimer = () => {
|
||||||
|
clearInterval(this.countdownId);
|
||||||
|
this.countdownId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
formatCountdown = (value: countdownValueType, config: FormatConfig) => {
|
||||||
|
const { format } = this.props;
|
||||||
|
return formatCountdown(value, { ...config, format });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Countdown do not need display the timestamp
|
||||||
|
valueRender = (node: React.ReactElement<HTMLDivElement>) =>
|
||||||
|
React.cloneElement(node, {
|
||||||
|
title: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Statistic valueRender={this.valueRender} {...this.props} formatter={this.formatCountdown} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
polyfill(Countdown);
|
||||||
|
|
||||||
|
export default Countdown;
|
55
components/statistic/Number.tsx
Normal file
55
components/statistic/Number.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import padEnd from 'lodash/padEnd';
|
||||||
|
import { valueType, FormatConfig } from './utils';
|
||||||
|
|
||||||
|
interface NumberProps extends FormatConfig {
|
||||||
|
value: valueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatisticNumber: React.SFC<NumberProps> = props => {
|
||||||
|
const { value, formatter, precision, decimalSeparator, prefixCls } = props;
|
||||||
|
|
||||||
|
let valueNode: React.ReactNode;
|
||||||
|
|
||||||
|
if (typeof formatter === 'function') {
|
||||||
|
// Customize formatter
|
||||||
|
valueNode = formatter(value);
|
||||||
|
} else {
|
||||||
|
// Internal formatter
|
||||||
|
const val: string = String(value);
|
||||||
|
const cells = val.match(/^(\d*)(\.(\d+))?$/);
|
||||||
|
|
||||||
|
// Process if illegal number
|
||||||
|
if (!cells) {
|
||||||
|
valueNode = val;
|
||||||
|
} else {
|
||||||
|
let int = cells[1] || '0';
|
||||||
|
let decimal = cells[3] || '';
|
||||||
|
|
||||||
|
int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||||
|
|
||||||
|
if (typeof precision === 'number') {
|
||||||
|
decimal = padEnd(decimal, precision, '0').slice(0, precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decimal) {
|
||||||
|
decimal = `${decimalSeparator}${decimal}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
valueNode = [
|
||||||
|
<span key="int" className={`${prefixCls}-content-value-int`}>
|
||||||
|
{int}
|
||||||
|
</span>,
|
||||||
|
decimal && (
|
||||||
|
<span key="decimal" className={`${prefixCls}-content-value-decimal`}>
|
||||||
|
{decimal}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className={`${prefixCls}-content-value`}>{valueNode}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatisticNumber;
|
64
components/statistic/Statistic.tsx
Normal file
64
components/statistic/Statistic.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { withConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||||
|
import StatisticNumber from './Number';
|
||||||
|
import Countdown from './Countdown';
|
||||||
|
import { valueType, FormatConfig } from './utils';
|
||||||
|
|
||||||
|
interface StatisticComponent {
|
||||||
|
Countdown: typeof Countdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatisticProps extends FormatConfig {
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
value?: valueType;
|
||||||
|
valueStyle?: React.CSSProperties;
|
||||||
|
valueRender?: (node: React.ReactNode) => React.ReactNode;
|
||||||
|
title?: React.ReactNode;
|
||||||
|
prefix?: React.ReactNode;
|
||||||
|
suffix?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Statistic: React.SFC<StatisticProps & ConfigConsumerProps> = props => {
|
||||||
|
const {
|
||||||
|
prefixCls,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
valueStyle,
|
||||||
|
value = 0,
|
||||||
|
title,
|
||||||
|
valueRender,
|
||||||
|
prefix,
|
||||||
|
suffix,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let valueNode: React.ReactNode = <StatisticNumber {...props} value={value} />;
|
||||||
|
|
||||||
|
if (valueRender) {
|
||||||
|
valueNode = valueRender(valueNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(prefixCls, className)} style={style}>
|
||||||
|
{title && <div className={`${prefixCls}-title`}>{title}</div>}
|
||||||
|
<div style={valueStyle} className={`${prefixCls}-content`}>
|
||||||
|
{prefix && <span className={`${prefixCls}-content-prefix`}>{prefix}</span>}
|
||||||
|
{valueNode}
|
||||||
|
{suffix && <span className={`${prefixCls}-content-suffix`}>{suffix}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Statistic.defaultProps = {
|
||||||
|
decimalSeparator: '.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const WrapperStatistic = withConfigConsumer<StatisticProps>({
|
||||||
|
prefixCls: 'statistic',
|
||||||
|
})<StatisticComponent>(Statistic);
|
||||||
|
|
||||||
|
export default WrapperStatistic;
|
388
components/statistic/__tests__/__snapshots__/demo.test.js.snap
Normal file
388
components/statistic/__tests__/__snapshots__/demo.test.js.snap
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`renders ./components/statistic/demo/basic.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-row"
|
||||||
|
style="margin-left:-8px;margin-right:-8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Active Users
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
112,893
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Account Balance (CNY)
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
112,893
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-decimal"
|
||||||
|
>
|
||||||
|
.00
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="ant-btn ant-btn-primary"
|
||||||
|
style="margin-top:16px"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Recharge
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/statistic/demo/card.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
style="background:#ECECEC;padding:30px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-row"
|
||||||
|
style="margin-left:-8px;margin-right:-8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-card ant-card-bordered"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-card-body"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
style="color:#3f8600"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-prefix"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="anticon anticon-arrow-up"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
data-icon="arrow-up"
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M868 545.5L536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
11
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-decimal"
|
||||||
|
>
|
||||||
|
.28
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-suffix"
|
||||||
|
>
|
||||||
|
%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-card ant-card-bordered"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-card-body"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Idle
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
style="color:#cf1322"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-prefix"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="anticon anticon-arrow-down"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
data-icon="arrow-down"
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0 0 48.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
9
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-decimal"
|
||||||
|
>
|
||||||
|
.30
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-suffix"
|
||||||
|
>
|
||||||
|
%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/statistic/demo/countdown.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-row"
|
||||||
|
style="margin-left:-8px;margin-right:-8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Countdown
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
48:00:30
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Million Seconds
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
48:00:30:000
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-col-24"
|
||||||
|
style="padding-left:8px;padding-right:8px;margin-top:32px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Day Level
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
2 天 0 时 0 分 30 秒
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`renders ./components/statistic/demo/unit.md correctly 1`] = `
|
||||||
|
<div
|
||||||
|
class="ant-row"
|
||||||
|
style="margin-left:-8px;margin-right:-8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Feedback
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-prefix"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="anticon anticon-like"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class=""
|
||||||
|
data-icon="like"
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="64 64 896 896"
|
||||||
|
width="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M885.9 533.7c16.8-22.2 26.1-49.4 26.1-77.7 0-44.9-25.1-87.4-65.5-111.1a67.67 67.67 0 0 0-34.3-9.3H572.4l6-122.9c1.4-29.7-9.1-57.9-29.5-79.4A106.62 106.62 0 0 0 471 99.9c-52 0-98 35-111.8 85.1l-85.9 311H144c-17.7 0-32 14.3-32 32v364c0 17.7 14.3 32 32 32h601.3c9.2 0 18.2-1.8 26.5-5.4 47.6-20.3 78.3-66.8 78.3-118.4 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7 0-12.6-1.8-25-5.4-37 16.8-22.2 26.1-49.4 26.1-77.7-.2-12.6-2-25.1-5.6-37.1zM184 852V568h81v284h-81zm636.4-353l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 16.5-7.2 32.2-19.6 43l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 16.5-7.2 32.2-19.6 43l-21.9 19 13.9 25.4a56.2 56.2 0 0 1 6.9 27.3c0 22.4-13.2 42.6-33.6 51.8H329V564.8l99.5-360.5a44.1 44.1 0 0 1 42.2-32.3c7.6 0 15.1 2.2 21.1 6.7 9.9 7.4 15.2 18.6 14.6 30.5l-9.6 198.4h314.4C829 418.5 840 436.9 840 456c0 16.5-7.2 32.1-19.6 43z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
1,128
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-col-12"
|
||||||
|
style="padding-left:8px;padding-right:8px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-title"
|
||||||
|
>
|
||||||
|
Unmerged
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ant-statistic-content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-value-int"
|
||||||
|
>
|
||||||
|
93
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="ant-statistic-content-suffix"
|
||||||
|
>
|
||||||
|
/ 100
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
3
components/statistic/__tests__/demo.test.js
Normal file
3
components/statistic/__tests__/demo.test.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import demoTest from '../../../tests/shared/demoTest';
|
||||||
|
|
||||||
|
demoTest('statistic');
|
65
components/statistic/__tests__/index.test.js
Normal file
65
components/statistic/__tests__/index.test.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MockDate from 'mockdate';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import Statistic from '..';
|
||||||
|
|
||||||
|
const delay = timeout => new Promise(resolve => setTimeout(resolve, timeout));
|
||||||
|
|
||||||
|
describe('Statistic', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
MockDate.set(moment('2018-11-28 00:00:00'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
MockDate.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('customize formatter', () => {
|
||||||
|
const formatter = jest.fn(() => 93);
|
||||||
|
const wrapper = mount(<Statistic value={1128} formatter={formatter} />);
|
||||||
|
expect(formatter).toBeCalledWith(1128);
|
||||||
|
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual('93');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('not a number', () => {
|
||||||
|
const wrapper = mount(<Statistic value="bamboo" />);
|
||||||
|
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual('bamboo');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Countdown', () => {
|
||||||
|
it('render correctly', () => {
|
||||||
|
const now = moment()
|
||||||
|
.add(2, 'd')
|
||||||
|
.add(11, 'h')
|
||||||
|
.add(28, 'm')
|
||||||
|
.add(9, 's')
|
||||||
|
.add(3, 'ms');
|
||||||
|
|
||||||
|
[
|
||||||
|
['H:m:s', '59:28:9'],
|
||||||
|
['HH:mm:ss', '59:28:09'],
|
||||||
|
['HH:mm:ss:SSS', '59:28:09:003'],
|
||||||
|
['DD-HH:mm:ss', '02-11:28:09'],
|
||||||
|
].forEach(([format, value]) => {
|
||||||
|
const wrapper = mount(<Statistic.Countdown format={format} value={now} />);
|
||||||
|
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('time going', async () => {
|
||||||
|
const now = Date.now() + 1000;
|
||||||
|
const wrapper = mount(<Statistic.Countdown value={now} />);
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
// setInterval should work
|
||||||
|
const instance = wrapper.instance();
|
||||||
|
expect(instance.countdownId).not.toBe(undefined);
|
||||||
|
|
||||||
|
await delay(50);
|
||||||
|
|
||||||
|
wrapper.unmount();
|
||||||
|
expect(instance.countdownId).toBe(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
31
components/statistic/demo/basic.md
Normal file
31
components/statistic/demo/basic.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
order: 0
|
||||||
|
title:
|
||||||
|
zh-CN: 基本
|
||||||
|
en-US: Basic
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
简单的展示。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Simplest Usage.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Statistic, Row, Col, Button } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Statistic title="Active Users" value={112893} />
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Statistic title="Account Balance (CNY)" value={112893} precision={2} />
|
||||||
|
<Button style={{ marginTop: 16 }} type="primary">Recharge</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>,
|
||||||
|
mountNode
|
||||||
|
);
|
||||||
|
```
|
50
components/statistic/demo/card.md
Normal file
50
components/statistic/demo/card.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
order: 2
|
||||||
|
title:
|
||||||
|
zh-CN: 在卡片中使用
|
||||||
|
en-US: In Card
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
在卡片中展示统计数值。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Display statistic data in Card.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Statistic, Card, Row, Col, Icon } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<div style={{ background: '#ECECEC', padding: '30px' }}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="Active"
|
||||||
|
value={11.28}
|
||||||
|
precision={2}
|
||||||
|
valueStyle={{ color: '#3f8600' }}
|
||||||
|
prefix={<Icon type="arrow-up" />}
|
||||||
|
suffix="%"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="Idle"
|
||||||
|
value={9.3}
|
||||||
|
precision={2}
|
||||||
|
valueStyle={{ color: '#cf1322' }}
|
||||||
|
prefix={<Icon type="arrow-down" />}
|
||||||
|
suffix="%"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>,
|
||||||
|
mountNode
|
||||||
|
);
|
||||||
|
```
|
36
components/statistic/demo/countdown.md
Normal file
36
components/statistic/demo/countdown.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
order: 3
|
||||||
|
title:
|
||||||
|
zh-CN: 倒计时
|
||||||
|
en-US: Countdown
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
倒计时组件。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Countdown component.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Statistic, Row, Col } from 'antd';
|
||||||
|
|
||||||
|
const Countdown = Statistic.Countdown;
|
||||||
|
const deadline = Date.now() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; // Moment is also OK
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Countdown title="Countdown" value={deadline} />
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Countdown title="Million Seconds" value={deadline} format="HH:mm:ss:SSS" />
|
||||||
|
</Col>
|
||||||
|
<Col span={24} style={{ marginTop: 32 }}>
|
||||||
|
<Countdown title="Day Level" value={deadline} format="D 天 H 时 m 分 s 秒" />
|
||||||
|
</Col>
|
||||||
|
</Row>,
|
||||||
|
mountNode
|
||||||
|
);
|
||||||
|
```
|
30
components/statistic/demo/unit.md
Normal file
30
components/statistic/demo/unit.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
order: 1
|
||||||
|
title:
|
||||||
|
zh-CN: 单位
|
||||||
|
en-US: Unit
|
||||||
|
---
|
||||||
|
|
||||||
|
## zh-CN
|
||||||
|
|
||||||
|
通过前缀和后缀添加单位。
|
||||||
|
|
||||||
|
## en-US
|
||||||
|
|
||||||
|
Add unit through `prefix` and `suffix`.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Statistic, Row, Col, Icon } from 'antd';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Statistic title="Feedback" value={1128} prefix={<Icon type="like" />} />
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Statistic title="Unmerged" value={93} suffix="/ 100" />
|
||||||
|
</Col>
|
||||||
|
</Row>,
|
||||||
|
mountNode
|
||||||
|
);
|
||||||
|
```
|
38
components/statistic/index.en-US.md
Normal file
38
components/statistic/index.en-US.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
type: Data Display
|
||||||
|
title: Statistic
|
||||||
|
---
|
||||||
|
|
||||||
|
Display statistic number.
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
* When want to highlight some data.
|
||||||
|
* When want to display statistic data with description.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
#### Statistic
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| decimalSeparator | decimal separator | string | - |
|
||||||
|
| formatter | customize value display logic | (value) => ReactNode | - |
|
||||||
|
| precision | precision of input value | number | - |
|
||||||
|
| prefix | prefix node of value | string \| ReactNode | - |
|
||||||
|
| suffix | suffix node of value | string \| ReactNode | - |
|
||||||
|
| title | Display title | string \| ReactNode | - |
|
||||||
|
| value | Display value | string \| number | - |
|
||||||
|
| valueStyle | Set value css style | style | - |
|
||||||
|
|
||||||
|
#### Statistic.Countdown
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| format | Format as [moment](http://momentjs.com/) | string | 'HH:mm:ss' |
|
||||||
|
| prefix | prefix node of value | string \| ReactNode | - |
|
||||||
|
| suffix | suffix node of value | string \| ReactNode | - |
|
||||||
|
| title | Display title | string \| ReactNode | - |
|
||||||
|
| value | Set target countdown time | number \| moment | - |
|
||||||
|
| valueStyle | Set value css style | style | - |
|
6
components/statistic/index.tsx
Normal file
6
components/statistic/index.tsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Statistic from './Statistic';
|
||||||
|
import Countdown from './Countdown';
|
||||||
|
|
||||||
|
Statistic.Countdown = Countdown;
|
||||||
|
|
||||||
|
export default Statistic;
|
39
components/statistic/index.zh-CN.md
Normal file
39
components/statistic/index.zh-CN.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
category: Components
|
||||||
|
subtitle: 统计数值
|
||||||
|
type: 数据展示
|
||||||
|
title: Statistic
|
||||||
|
---
|
||||||
|
|
||||||
|
展示统计数值。
|
||||||
|
|
||||||
|
## 何时使用
|
||||||
|
|
||||||
|
* 当需要突出某个或某组数字时。
|
||||||
|
* 当需要展示带描述的统计类数据时使用。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
#### Statistic
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| decimalSeparator | 设置小数点 | string | - |
|
||||||
|
| formatter | 自定义数值展示 | (value) => ReactNode | - |
|
||||||
|
| precision | 数值精度 | number | - |
|
||||||
|
| prefix | 设置数值的前缀 | string \| ReactNode | - |
|
||||||
|
| suffix | 设置数值的后缀 | string \| ReactNode | - |
|
||||||
|
| title | 数值的标题 | string \| ReactNode | - |
|
||||||
|
| value | 数值内容 | string \| number | - |
|
||||||
|
| valueStyle | 设置数值的样式 | style | - |
|
||||||
|
|
||||||
|
#### Statistic.Countdown
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| format | 格式化倒计时展示,参考 [moment](http://momentjs.com/) | string | 'HH:mm:ss' |
|
||||||
|
| prefix | 设置数值的前缀 | string \| ReactNode | - |
|
||||||
|
| suffix | 设置数值的后缀 | string \| ReactNode | - |
|
||||||
|
| title | 数值的标题 | string \| ReactNode | - |
|
||||||
|
| value | 数值内容 | number \| moment | - |
|
||||||
|
| valueStyle | 设置数值的样式 | style | - |
|
38
components/statistic/style/index.less
Normal file
38
components/statistic/style/index.less
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
@import '../../style/themes/default';
|
||||||
|
@import '../../style/mixins/index';
|
||||||
|
|
||||||
|
@statistic-prefix-cls: ~'@{ant-prefix}-statistic';
|
||||||
|
|
||||||
|
.@{statistic-prefix-cls} {
|
||||||
|
.reset-component;
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: @statistic-title-font-size;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
font-size: @statistic-content-font-size;
|
||||||
|
font-family: @statistic-font-family;
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
&-decimal {
|
||||||
|
font-size: @statistic-unit-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-prefix,
|
||||||
|
&-suffix {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-prefix {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-suffix {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: @statistic-unit-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
components/statistic/style/index.tsx
Normal file
2
components/statistic/style/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import '../../style/index.less';
|
||||||
|
import './index.less';
|
60
components/statistic/utils.tsx
Normal file
60
components/statistic/utils.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import padStart from 'lodash/padStart';
|
||||||
|
import interopDefault from '../_util/interopDefault';
|
||||||
|
|
||||||
|
export type valueType = number | string;
|
||||||
|
export type countdownValueType = valueType | string;
|
||||||
|
|
||||||
|
export type Formatter =
|
||||||
|
| false
|
||||||
|
| 'number'
|
||||||
|
| 'countdown'
|
||||||
|
| ((value: valueType, config?: FormatConfig) => React.ReactNode);
|
||||||
|
|
||||||
|
export interface FormatConfig {
|
||||||
|
formatter?: Formatter;
|
||||||
|
decimalSeparator?: string;
|
||||||
|
precision?: number;
|
||||||
|
prefixCls?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CountdownFormatConfig extends FormatConfig {
|
||||||
|
format?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Countdown
|
||||||
|
const timeUnits: [string, number][] = [
|
||||||
|
['Y', 1000 * 60 * 60 * 24 * 365], // years
|
||||||
|
['M', 1000 * 60 * 60 * 24 * 30], // months
|
||||||
|
['D', 1000 * 60 * 60 * 24], // days
|
||||||
|
['H', 1000 * 60 * 60], // hours
|
||||||
|
['m', 1000 * 60], // minutes
|
||||||
|
['s', 1000], // seconds
|
||||||
|
['S', 1], // million seconds
|
||||||
|
];
|
||||||
|
|
||||||
|
function formatTimeStr(duration: number, format: string) {
|
||||||
|
let leftDuration: number = duration;
|
||||||
|
|
||||||
|
return timeUnits.reduce((current, [name, unit]) => {
|
||||||
|
if (current.indexOf(name) !== -1) {
|
||||||
|
const value = Math.floor(leftDuration / unit);
|
||||||
|
leftDuration -= value * unit;
|
||||||
|
return current.replace(new RegExp(`${name}+`, 'g'), (match: string) => {
|
||||||
|
const len = match.length;
|
||||||
|
return padStart(value.toString(), len, '0');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatCountdown(value: countdownValueType, config: CountdownFormatConfig) {
|
||||||
|
const { format = '' } = config;
|
||||||
|
const target = interopDefault(moment)(value).valueOf();
|
||||||
|
const current = interopDefault(moment)().valueOf();
|
||||||
|
const diff = Math.max(target - current, 0);
|
||||||
|
|
||||||
|
return formatTimeStr(diff, format);
|
||||||
|
}
|
@ -562,3 +562,10 @@
|
|||||||
@list-item-meta-margin-bottom: @padding-md;
|
@list-item-meta-margin-bottom: @padding-md;
|
||||||
@list-item-meta-avatar-margin-right: @padding-md;
|
@list-item-meta-avatar-margin-right: @padding-md;
|
||||||
@list-item-meta-title-margin-bottom: @padding-sm;
|
@list-item-meta-title-margin-bottom: @padding-sm;
|
||||||
|
|
||||||
|
// Statistic
|
||||||
|
// ---
|
||||||
|
@statistic-title-font-size: @font-size-base;
|
||||||
|
@statistic-content-font-size: 24px;
|
||||||
|
@statistic-unit-font-size: 16px;
|
||||||
|
@statistic-font-family: Tahoma, 'Helvetica Neue', @font-family;
|
||||||
|
@ -35,6 +35,7 @@ Array [
|
|||||||
"message",
|
"message",
|
||||||
"Menu",
|
"Menu",
|
||||||
"Modal",
|
"Modal",
|
||||||
|
"Statistic",
|
||||||
"notification",
|
"notification",
|
||||||
"Pagination",
|
"Pagination",
|
||||||
"Popconfirm",
|
"Popconfirm",
|
||||||
|
4
typings/custom-typings.d.ts
vendored
4
typings/custom-typings.d.ts
vendored
@ -93,6 +93,10 @@ declare module '*.json' {
|
|||||||
|
|
||||||
declare module 'lodash/debounce';
|
declare module 'lodash/debounce';
|
||||||
|
|
||||||
|
declare module 'lodash/padStart';
|
||||||
|
|
||||||
|
declare module 'lodash/padEnd';
|
||||||
|
|
||||||
declare module 'lodash/uniqBy';
|
declare module 'lodash/uniqBy';
|
||||||
|
|
||||||
declare module "raf";
|
declare module "raf";
|
||||||
|
Loading…
Reference in New Issue
Block a user