Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empty component #91

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const App = () => {
- [ ] Collapse
- [ ] CountDown
- [ ] Divider
- [ ] Empty
- [x] Empty
- [ ] ImagePreview
- [ ] Lazyload
- [ ] List
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vant-react",
"version": "0.3.0",
"version": "1.0.0",
Copy link
Owner

Choose a reason for hiding this comment

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

should be 0.3.1

"description": "Lightweight Mobile UI Components built in React & Typescript, inspired by Vant: https://youzan.github.io/vant",
"author": "mxdi9i7",
"license": "MIT",
Expand Down
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>
);
35 changes: 35 additions & 0 deletions src/components/Empty/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@import '../../styles/variables.scss';
@import '../../styles/typography.scss';

$baseClass: 'vant-empty';

.#{$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;
}
}
93 changes: 93 additions & 0 deletions src/components/Empty/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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'>
<div>
Copy link
Owner

Choose a reason for hiding this comment

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

we don't need this div, we can use <></> fragments to enclose the buttons

<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'
/>
</div>
{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 CustomBottomContent = () => (
<div className='storybook__container empty'>
<Empty
bottom={
<Button round type='danger'>
Button
</Button>
}
/>
</div>
);
58 changes: 58 additions & 0 deletions src/components/Empty/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import classnames from '../../utils/classNames';
import { Props } from './types';
import './index.scss';
import { Network } from './Network';

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

// TODO: custom imageSize
// TODO: bottom & image & description slots
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;
4 changes: 4 additions & 0 deletions src/styles/stories.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ body {
height: 200px;
}
}

&.empty {
flex-direction: column;
}
}
.slider-container {
display: flex;
Expand Down
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;