diff --git a/README.md b/README.md
index f6dda5924..c4090afd7 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,7 @@ const App = () => {
- [ ] Collapse
- [ ] CountDown
- [ ] Divider
-- [ ] Empty
+- [x] Empty
- [ ] ImagePreview
- [ ] Lazyload
- [ ] List
diff --git a/package.json b/package.json
index b4e277f31..c03c92a7d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vant-react",
- "version": "0.3.0",
+ "version": "1.0.0",
"description": "Lightweight Mobile UI Components built in React & Typescript, inspired by Vant: https://youzan.github.io/vant",
"author": "mxdi9i7",
"license": "MIT",
diff --git a/src/components/Empty/Network.tsx b/src/components/Empty/Network.tsx
new file mode 100644
index 000000000..aa9e225f4
--- /dev/null
+++ b/src/components/Empty/Network.tsx
@@ -0,0 +1,110 @@
+import React from 'react';
+
+const renderStop = (color: string, offset: number, opacity?: number) => (
+
+);
+
+export const Network = (
+
+);
diff --git a/src/components/Empty/index.scss b/src/components/Empty/index.scss
new file mode 100644
index 000000000..5c9f12ef4
--- /dev/null
+++ b/src/components/Empty/index.scss
@@ -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;
+ font-size: $empty-description-font-size;
+ line-height: $empty-description-line-height;
+ }
+
+ &__bottom {
+ margin-top: $empty-bottom-margin-top;
+ }
+}
diff --git a/src/components/Empty/index.stories.tsx b/src/components/Empty/index.stories.tsx
new file mode 100644
index 000000000..f07e2c611
--- /dev/null
+++ b/src/components/Empty/index.stories.tsx
@@ -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 = () => (
+
+
+
+);
+
+export const Description = () => (
+
+
+
+);
+
+export const ImageType = () => {
+ const [showType, setShowType] = useState('error');
+
+ return (
+
+
+
+ {showType === 'error' && (
+
+ )}
+ {showType === 'network' && (
+
+ )}
+ {showType === 'search' && (
+
+ )}
+ {showType === 'custom' && (
+
} />
+ )}
+
+ );
+};
+
+export const CustomImage = () => (
+
+
+
+);
+
+export const CustomBottomContent = () => (
+
+
+ Button
+
+ }
+ />
+
+);
diff --git a/src/components/Empty/index.tsx b/src/components/Empty/index.tsx
new file mode 100644
index 000000000..f0ce3086d
--- /dev/null
+++ b/src/components/Empty/index.tsx
@@ -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
;
+ } else {
+ return image;
+ }
+ };
+
+ return (
+
+
{renderImage()}
+ {description &&
{description}
}
+ {bottom &&
{bottom}
}
+
+ );
+}
diff --git a/src/components/Empty/types.ts b/src/components/Empty/types.ts
new file mode 100644
index 000000000..34e605cdd
--- /dev/null
+++ b/src/components/Empty/types.ts
@@ -0,0 +1,8 @@
+import { ReactElement } from 'react';
+
+export interface Props {
+ imageSize?: number | string;
+ description?: string | ReactElement;
+ image?: string | ReactElement;
+ bottom?: ReactElement;
+}
diff --git a/src/index.tsx b/src/index.tsx
index 62b093c1a..0da5f3270 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -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';
@@ -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,
@@ -42,7 +44,8 @@ const Vant = {
Slider,
Checkbox,
Radio,
- Stepper
+ Stepper,
+ Empty
};
export default Vant;
diff --git a/src/styles/stories.scss b/src/styles/stories.scss
index 31378b7a4..30d7316ea 100644
--- a/src/styles/stories.scss
+++ b/src/styles/stories.scss
@@ -62,6 +62,10 @@ body {
height: 200px;
}
}
+
+ &.empty {
+ flex-direction: column;
+ }
}
.slider-container {
display: flex;
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
index 89bf0f928..e7e201263 100644
--- a/src/styles/variables.scss
+++ b/src/styles/variables.scss
@@ -1,3 +1,5 @@
+@import './spacing.scss';
+
// loaders
$loader-size: 20px;
$loader-animation-duration: 2s;
@@ -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;