Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions src/components/Empty/Network.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';

const renderStop = (color: string, offset: number, opacity?: number) => (
<stop stopColor={color} offset={`${offset}%`} stopOpacity={opacity} />
);

export const Network = (
<svg viewBox='0 0 160 160'>
<defs>
<linearGradient id='c' x1='64.022%' y1='100%' x2='64.022%' y2='0%'>
{renderStop('#FFF', 0, 0.5)}
{renderStop('#F2F3F5', 100)}
</linearGradient>
<linearGradient id='d' x1='64.022%' y1='96.956%' x2='64.022%' y2='0%'>
{renderStop('#F2F3F5', 0, 0.3)}
{renderStop('#F2F3F5', 100)}
</linearGradient>
<linearGradient id='h' x1='50%' y1='0%' x2='50%' y2='84.459%'>
{renderStop('#EBEDF0', 0)}
{renderStop('#DCDEE0', 100, 0)}
</linearGradient>
<linearGradient id='i' x1='100%' y1='0%' x2='100%' y2='100%'>
{renderStop('#EAEDF0', 0)}
{renderStop('#DCDEE0', 100)}
</linearGradient>
<linearGradient id='k' x1='100%' y1='100%' x2='100%' y2='0%'>
{renderStop('#EAEDF0', 0)}
{renderStop('#DCDEE0', 100)}
</linearGradient>
<linearGradient id='m' x1='0%' y1='43.982%' x2='100%' y2='54.703%'>
{renderStop('#EAEDF0', 0)}
{renderStop('#DCDEE0', 100)}
</linearGradient>
<linearGradient id='n' x1='94.535%' y1='43.837%' x2='5.465%' y2='54.948%'>
{renderStop('#EAEDF0', 0)}
{renderStop('#DCDEE0', 100)}
</linearGradient>
<radialGradient
id='g'
cx='50%'
cy='0%'
fx='50%'
fy='0%'
r='100%'
gradientTransform='matrix(0 1 -.54835 0 .5 -.5)'
>
{renderStop('#EBEDF0', 0)}
{renderStop('#FFF', 100, 0)}
</radialGradient>
</defs>
<g fill='none' fillRule='evenodd'>
<g opacity='.8'>
<path
d='M0 124V46h20v20h14v58H0z'
fill='url(#c)'
transform='matrix(-1 0 0 1 36 7)'
/>
<path
d='M40.5 5a8.504 8.504 0 018.13 6.009l.12-.005L49 11a8 8 0 11-1 15.938V27H34v-.174a6.5 6.5 0 11-1.985-12.808A8.5 8.5 0 0140.5 5z'
fill='url(#d)'
transform='translate(2 7)'
/>
<path
d='M96.016 0a4.108 4.108 0 013.934 2.868l.179-.004c2.138 0 3.871 1.71 3.871 3.818 0 2.109-1.733 3.818-3.871 3.818-.164 0-.325-.01-.484-.03v.03h-6.774v-.083a3.196 3.196 0 01-.726.083C90.408 10.5 89 9.111 89 7.398c0-1.636 1.284-2.976 2.911-3.094a3.555 3.555 0 01-.008-.247c0-2.24 1.842-4.057 4.113-4.057z'
fill='url(#d)'
transform='translate(2 7)'
/>
<path
d='M121 8h22.231v14H152v77.37h-31V8z'
fill='url(#c)'
transform='translate(2 7)'
/>
</g>
<path fill='url(#g)' d='M0 139h160v21H0z' />
<path
d='M37 18a7 7 0 013 13.326v26.742c0 1.23-.997 2.227-2.227 2.227h-1.546A2.227 2.227 0 0134 58.068V31.326A7 7 0 0137 18z'
fill='url(#h)'
fillRule='nonzero'
transform='translate(43 36)'
/>
<g opacity='.6' strokeLinecap='round' strokeWidth='7'>
<path
d='M20.875 11.136a18.868 18.868 0 00-5.284 13.121c0 5.094 2.012 9.718 5.284 13.12'
stroke='url(#i)'
transform='translate(43 36)'
/>
<path
d='M9.849 0C3.756 6.225 0 14.747 0 24.146c0 9.398 3.756 17.92 9.849 24.145'
stroke='url(#i)'
transform='translate(43 36)'
/>
<path
d='M57.625 11.136a18.868 18.868 0 00-5.284 13.121c0 5.094 2.012 9.718 5.284 13.12'
stroke='url(#k)'
transform='rotate(-180 76.483 42.257)'
/>
<path
d='M73.216 0c-6.093 6.225-9.849 14.747-9.849 24.146 0 9.398 3.756 17.92 9.849 24.145'
stroke='url(#k)'
transform='rotate(-180 89.791 42.146)'
/>
</g>
<g transform='translate(31 105)' fillRule='nonzero'>
<rect fill='url(#m)' width='98' height='34' rx='2' />
<rect fill='#FFF' x='9' y='8' width='80' height='18' rx='1.114' />
<rect fill='url(#n)' x='15' y='12' width='18' height='6' rx='1.114' />
</g>
</g>
</svg>
);
39 changes: 39 additions & 0 deletions src/components/Empty/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @import '../../styles/colors.scss';
// @import '../../styles/spacing.scss';
// @import '../../styles/typography.scss';
// @import '../../styles/opacity.scss';
@import '../../styles/variables.scss';

$baseClass: 'vant-button';


.#{$baseClass} {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: $empty-padding;

&__image {
width: $empty-image-size;
height: $empty-image-size;

img {
width: 100%;
height: 100%;
}
}

&__description {
margin-top: $empty-description-margin-top;
padding: $empty-description-padding;
color: $empty-description-color;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use typography variables

font-size: $empty-description-font-size;
line-height: $empty-description-line-height;
}

&__bottom {
margin-top: $empty-bottom-margin-top;
}
}
91 changes: 91 additions & 0 deletions src/components/Empty/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React, { useState } from 'react';
import Empty from '.';
import Button from '../Button';
import Image from '../Image';

export default {
title: 'Empty',
component: Empty
};

export const BasicUsage = () => (
<div className='storybook__container empty'>
<Empty />
</div>
);

export const Description = () => (
<div className='storybook__container empty'>
<Empty description='description' />
</div>
);

export const ImageType = () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make the presentation prettier

const [showType, setShowType] = useState('error');

return (
<div className='storybook__container empty'>
<Button
onClick={() => {
setShowType('error');
}}
text='Error'
type='primary'
/>
<Button
onClick={() => {
setShowType('network');
}}
text='Network'
type='info'
/>
<Button
onClick={() => {
setShowType('search');
}}
text='Search'
type='warning'
/>
<Button
onClick={() => {
setShowType('custom');
}}
text='Custom'
type='danger'
/>
{showType === 'error' && (
<Empty image='error' description='description' />
)}
{showType === 'network' && (
<Empty image='network' description='description' />
)}
{showType === 'search' && (
<Empty image='search' description='description' />
)}
{showType === 'custom' && (
<Empty image={<Image src='https://img.yzcdn.cn/vant/cat.jpeg' />} />
)}
</div>
);
};

export const CustomImage = () => (
<div className='storybook__container empty'>
<Empty
image='https://img.yzcdn.cn/vant/custom-empty-image.png'
description='description'
/>
</div>
);

export const BottomContent = () => (
<div className='storybook__container empty'>
<Empty
bottom={
<Button round type='danger'>
Button
</Button>
}
/>
</div>
);
61 changes: 61 additions & 0 deletions src/components/Empty/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';

import classnames from '../../utils/classNames';

import { Props } from './types';

import './index.scss';
import { Network } from './Network';

const baseClass = 'vant-button';
const PRESET_IMAGES = ['error', 'search', 'default'];

// TODO: custom imageSize
// TODO: bottom & image & description solts
export default function Empty({
description,
image = 'default',
bottom
}: Props) {
const containerProps = {
className: classnames(`${baseClass}`, []),
style: {}
};

const imageProps = {
className: classnames(`${baseClass}__image`, []),
style: {}
};

const bottomProps = {
className: classnames(`${baseClass}__bottom`, []),
style: {}
};

const descriptionProps = {
className: classnames(`${baseClass}__description`, []),
style: {}
};

const renderImage = () => {
if (image === 'network') {
return Network;
}
if (typeof image === 'string') {
if (PRESET_IMAGES.includes(image)) {
image = `https://img.yzcdn.cn/vant/empty-image-${image}.png`;
}
return <img src={image} />;
} else {
return image;
}
};

return (
<div {...containerProps}>
<div {...imageProps}>{renderImage()}</div>
{description && <p {...descriptionProps}>{description}</p>}
{bottom && <div {...bottomProps}>{bottom}</div>}
</div>
);
}
8 changes: 8 additions & 0 deletions src/components/Empty/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactElement } from 'react';

export interface Props {
imageSize?: number | string;
description?: string | ReactElement;
image?: string | ReactElement;
bottom?: ReactElement;
}
5 changes: 4 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Slider from './components/Slider';
import Checkbox from './components/Checkbox';
import Radio from './components/Radio';
import Stepper from './components/Stepper';
import Empty from './components/Empty';

export { default as Button } from './components/Button';
export { default as Icon } from './components/Icons';
Expand All @@ -27,6 +28,7 @@ export { default as Slider } from './components/Slider';
export { default as Checkbox } from './components/Checkbox';
export { default as Radio } from './components/Radio';
export { default as Stepper } from './components/Stepper';
export { default as Empty } from './components/Empty';

const Vant = {
Button,
Expand All @@ -42,7 +44,8 @@ const Vant = {
Slider,
Checkbox,
Radio,
Stepper
Stepper,
Empty
};

export default Vant;
19 changes: 19 additions & 0 deletions src/styles/variables.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import './spacing.scss';

// loaders
$loader-size: 20px;
$loader-animation-duration: 2s;
Expand All @@ -16,3 +18,20 @@ $icon-dot-size: 8px;
// popups
$popup-alpha: 0.5;
$popup-background-color: #000;

// Empty
$padding-base: 4px;
$padding-md: $padding-base * 4;
$padding-xl: $padding-base * 8;
$gray-6: #969799;
$font-size-md: 14px;
$line-height-md: 20px;

$empty-padding: $padding-xl 0;
$empty-image-size: 160px;
$empty-description-margin-top: $padding-md;
$empty-description-padding: 0 60px;
$empty-description-color: $gray-6;
$empty-description-font-size: $font-size-md;
$empty-description-line-height: $line-height-md;
$empty-bottom-margin-top: 24px;