From a7d505db4136d94ffd93c93c3f1a553c22f9081e Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 11 Feb 2022 17:43:09 +0100 Subject: [PATCH] feat: Implement EmptyState components (#18676) * feat: Implement EmptyState components * Fix title in storybook * Add licenses * Add max widths --- superset-frontend/src/assets/images/chart.svg | 22 ++ .../src/assets/images/document.svg | 22 ++ .../src/assets/images/filter.svg | 32 +++ .../EmptyState/EmptyState.stories.tsx | 73 +++++++ .../src/components/EmptyState/index.tsx | 205 ++++++++++++++++++ superset-frontend/webpack.config.js | 13 +- 6 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 superset-frontend/src/assets/images/chart.svg create mode 100644 superset-frontend/src/assets/images/document.svg create mode 100644 superset-frontend/src/assets/images/filter.svg create mode 100644 superset-frontend/src/components/EmptyState/EmptyState.stories.tsx create mode 100644 superset-frontend/src/components/EmptyState/index.tsx diff --git a/superset-frontend/src/assets/images/chart.svg b/superset-frontend/src/assets/images/chart.svg new file mode 100644 index 0000000000000..2267342bccd1f --- /dev/null +++ b/superset-frontend/src/assets/images/chart.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/superset-frontend/src/assets/images/document.svg b/superset-frontend/src/assets/images/document.svg new file mode 100644 index 0000000000000..e3d1bfe1beb28 --- /dev/null +++ b/superset-frontend/src/assets/images/document.svg @@ -0,0 +1,22 @@ + + + + + diff --git a/superset-frontend/src/assets/images/filter.svg b/superset-frontend/src/assets/images/filter.svg new file mode 100644 index 0000000000000..0e1f6b41efc3d --- /dev/null +++ b/superset-frontend/src/assets/images/filter.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/superset-frontend/src/components/EmptyState/EmptyState.stories.tsx b/superset-frontend/src/components/EmptyState/EmptyState.stories.tsx new file mode 100644 index 0000000000000..7872c4ec9266a --- /dev/null +++ b/superset-frontend/src/components/EmptyState/EmptyState.stories.tsx @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import EmptyImage from 'src/assets/images/empty.svg'; +import ChartImage from 'src/assets/images/chart.svg'; +import FilterImage from 'src/assets/images/filter.svg'; +import { EmptyStateBig, EmptyStateMedium, EmptyStateSmall } from '.'; + +export default { + title: 'Empty state', + component: EmptyStateMedium, +}; + +export const SmallEmptyState = () => ( + } + title="Small empty state" + description="This is an example of a small empty state" + /> +); + +export const MediumEmptyState = () => ( + } + title="Medium empty state" + description="This is an example of a medium empty state" + /> +); + +export const MediumEmptyStateWithButton = () => ( + } + title="Medium empty state" + description="This is an example of a medium empty state with a button" + buttonAction={() => {}} + buttonText="Click!" + /> +); + +export const BigEmptyState = () => ( + } + title="Big empty state" + description="This is an example of a big empty state" + /> +); + +export const BigEmptyStateWithButton = () => ( + } + title="Big empty state" + description="This is an example of a big empty state with a button" + buttonText="Click!" + buttonAction={() => {}} + /> +); diff --git a/superset-frontend/src/components/EmptyState/index.tsx b/superset-frontend/src/components/EmptyState/index.tsx new file mode 100644 index 0000000000000..99a6ce7bc7160 --- /dev/null +++ b/superset-frontend/src/components/EmptyState/index.tsx @@ -0,0 +1,205 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { ReactNode } from 'react'; +import { styled, css, SupersetTheme } from '@superset-ui/core'; +import { Empty } from 'src/common/components'; +import Button from 'src/components/Button'; + +export enum EmptyStateSize { + Small, + Medium, + Big, +} + +export interface EmptyStateSmallProps { + title: string | ReactNode; + description?: string | ReactNode; + image: string | ReactNode; +} + +export interface EmptyStateProps extends EmptyStateSmallProps { + buttonText?: string; + buttonAction?: React.MouseEventHandler; +} + +export interface ImageContainerProps { + image: string | ReactNode; + size: EmptyStateSize; +} + +const EmptyStateContainer = styled.div` + ${({ theme }) => css` + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + padding: ${theme.gridUnit * 4}px; + text-align: center; + + & .ant-empty-image svg { + width: auto; + } + `} +`; + +const TextContainer = styled.div``; + +const Title = styled.p` + ${({ theme }) => css` + font-size: ${theme.typography.sizes.m}px; + color: ${theme.colors.grayscale.light1}; + margin: ${theme.gridUnit * 2}px 0 0 0; + font-weight: ${theme.typography.weights.bold}; + `} +`; + +const BigTitle = styled(Title)` + ${({ theme }) => css` + font-size: ${theme.typography.sizes.l}px; + color: ${theme.colors.grayscale.light1}; + margin-top: ${theme.gridUnit * 4}px; + `} +`; + +const Description = styled.p` + ${({ theme }) => css` + font-size: ${theme.typography.sizes.s}px; + color: ${theme.colors.grayscale.light1}; + margin: ${theme.gridUnit * 2}px 0 0 0; + `} +`; + +const BigDescription = styled(Description)` + ${({ theme }) => css` + font-size: ${theme.typography.sizes.m}px; + `} +`; + +const SmallDescription = styled(Description)` + ${({ theme }) => css` + margin-top: ${theme.gridUnit}px; + `} +`; + +const ActionButton = styled(Button)` + ${({ theme }) => css` + margin-top: ${theme.gridUnit * 4}px; + `} +`; + +const getImage = (image: string | ReactNode) => + typeof image === 'string' ? `/static/assets/images/${image}` : image; + +const getImageHeight = (size: EmptyStateSize) => { + switch (size) { + case EmptyStateSize.Small: + return { height: '50px' }; + case EmptyStateSize.Medium: + return { height: '80px' }; + case EmptyStateSize.Big: + return { height: '150px' }; + default: + return { height: '50px' }; + } +}; + +const ImageContainer = ({ image, size }: ImageContainerProps) => ( + +); + +export const EmptyStateBig = ({ + title, + image, + description, + buttonAction, + buttonText, +}: EmptyStateProps) => ( + + + + css` + max-width: ${theme.gridUnit * 150}px; + ` + } + > + {title} + {description && {description}} + {buttonAction && buttonText && ( + + {buttonText} + + )} + + +); + +export const EmptyStateMedium = ({ + title, + image, + description, + buttonAction, + buttonText, +}: EmptyStateProps) => ( + + + + css` + max-width: ${theme.gridUnit * 100}px; + ` + } + > + {title} + {description && {description}} + {buttonText && buttonAction && ( + + {buttonText} + + )} + + +); + +export const EmptyStateSmall = ({ + title, + image, + description, +}: EmptyStateSmallProps) => ( + + + + css` + max-width: ${theme.gridUnit * 75}px; + ` + } + > + {title} + {description && {description}} + + +); diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index 06f6825fa9fd8..4c1aeff535578 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -404,7 +404,18 @@ const config = { { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, issuer: /\.([jt])sx?$/, - use: ['@svgr/webpack'], + use: [ + { + loader: '@svgr/webpack', + options: { + svgoConfig: { + plugins: { + removeViewBox: false, + }, + }, + }, + }, + ], }, { test: /\.(jpg|gif)$/,