Merge pull request #32 from unix/loading

feat(loading): add component
This commit is contained in:
witt
2020-03-29 05:11:41 +08:00
committed by GitHub
10 changed files with 250 additions and 77 deletions

View File

@@ -6,7 +6,7 @@ import AutoCompleteSearching from './auto-complete-searching'
import AutoCompleteEmpty from './auto-complete-empty'
import { AutoCompleteContext, AutoCompleteConfig } from './auto-complete-context'
import { NormalSizes, NormalTypes } from '../utils/prop-types'
import ButtonLoading from '../button/button.loading'
import Loading from '../loading'
import { pickChild } from 'components/utils/collections'
export type AutoCompleteOption = {
@@ -62,7 +62,7 @@ const childrenToOptionsNode = (options: AutoCompleteOptions) => {
// When the search is seted, at least one element should exist to avoid re-render.
const getSearchIcon = (searching?: boolean) => {
if (searching === undefined) return null
return searching ? <ButtonLoading bgColor="transparent" /> : <span />
return searching ? <Loading size="medium" /> : <span />
}
const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
@@ -146,6 +146,12 @@ const AutoComplete: React.FC<React.PropsWithChildren<AutoCompleteProps>> = ({
.auto-complete {
width: ${width || 'max-content'};
}
.auto-complete :global(.loading) {
left: -3px;
right: -3px;
width: max-content;
}
`}</style>
</div>
</AutoCompleteContext.Provider>

View File

@@ -4,7 +4,7 @@ import withDefaults from '../utils/with-defaults'
import { getColor } from './styles'
import { useButtonDropdown } from './button-dropdown-context'
import { getButtonSize } from '../button/styles'
import ButtonLoading from '../button/button.loading'
import Loading from '../loading'
import { NormalTypes } from '../utils/prop-types'
interface Props {
@@ -43,7 +43,7 @@ const ButtonDropdownItem: React.FC<React.PropsWithChildren<ButtonDropdownItemPro
return (
<button className={className} onClick={clickHandler} {...props}>
{loading ? <ButtonLoading /> : children}
{loading ? <Loading /> : children}
<style jsx>{`
button {
position: relative;

View File

@@ -1,69 +0,0 @@
import React from 'react'
import useTheme from '../styles/use-theme'
interface Props {
bgColor?: string
}
const ButtonLoading: React.FC<Props> = React.memo(({
bgColor,
}) => {
const theme = useTheme()
return (
<span className="loading">
<i />
<i />
<i />
<style jsx>{`
.loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: ${bgColor || theme.palette.accents_1};
}
i {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: ${theme.palette.accents_6};
margin: 0 1px;
display: inline-block;
animation: loading-blink 1.4s infinite both;
}
i:nth-child(2) {
animation-delay: 0.2s;
}
i:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes loading-blink {
0% {
opacity: 0.2;
}
20% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
`}</style>
</span>
)
})
export default ButtonLoading

View File

@@ -3,7 +3,7 @@ import withDefaults from '../utils/with-defaults'
import useTheme from '../styles/use-theme'
import { ButtonTypes, NormalSizes } from '../utils/prop-types'
import ButtonDrip from './button.drip'
import ButtonLoading from './button.loading'
import ButtonLoading from '../loading'
import { getButtonColors, getButtonCursor, getButtonHoverColors, getButtonSize } from './styles'
interface Props {

View File

@@ -39,3 +39,4 @@ export { default as Tree } from './file-tree'
export { default as Badge } from './badge'
export { default as AutoComplete } from './auto-complete'
export { default as Collapse } from './collapse'
export { default as Loading } from './loading'

View File

@@ -18,10 +18,10 @@ const InputIcon: React.FC<InputIconProps> = React.memo(({
}, [theme.layout.gap, ratio])
return (
<span>
<span className="input-icon">
{icon}
<style jsx>{`
span {
.input-icon {
box-sizing: content-box;
display: flex;
width: ${width};

View File

@@ -0,0 +1,3 @@
import Loading from './loading'
export default Loading

View File

@@ -0,0 +1,138 @@
import React, { useMemo } from 'react'
import useTheme from '../styles/use-theme'
import withDefaults from '../utils/with-defaults'
import { NormalSizes, NormalTypes } from 'components/utils/prop-types'
import { ZeitUIThemesPalette } from 'components/styles/themes'
interface Props {
size?: NormalSizes
type?: NormalTypes
color?: string
width?: string
height?: string
}
const defaultProps = {
size: 'medium' as NormalSizes,
type: 'default' as NormalTypes,
}
export type LoadingProps = Props & typeof defaultProps & React.HTMLAttributes<any>
const getIconSize = (size: NormalSizes) => {
const sizes: { [key in NormalSizes]: string } = {
mini: '2px',
small: '3px',
medium: '4px',
large: '5px',
}
return sizes[size]
}
const getIconBgColor = (
type: NormalTypes,
palette: ZeitUIThemesPalette,
color?: string,
) => {
const colors: { [key in NormalTypes]: string } = {
default: palette.accents_6,
secondary: palette.secondary,
success: palette.success,
warning: palette.warning,
error: palette.error,
}
return color ? color : colors[type]
}
const Loading: React.FC<React.PropsWithChildren<LoadingProps>> = React.memo(({
children, size, type, color,
}) => {
const theme = useTheme()
const width = useMemo(() => getIconSize(size), [size])
const bgColor = useMemo(
() => getIconBgColor(type, theme.palette, color),
[type, theme.palette, color],
)
return (
<div className="loading-container">
<span className="loading">
{children && (
<label>{children}</label>
)}
<i />
<i />
<i />
</span>
<style jsx>{`
.loading-container {
display: inline-flex;
align-items: center;
position: relative;
width: 100%;
height: 100%;
}
label {
margin-right: ${theme.layout.gapHalf};
color: ${theme.palette.accents_5};
}
label :global(*) {
margin: 0;
}
.loading {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: transparent;
user-select: none;
}
i {
width: ${width};
height: ${width};
border-radius: 50%;
background-color: ${bgColor};
margin: 0 1px;
display: inline-block;
animation: loading-blink 1.4s infinite both;
}
i:nth-child(2) {
animation-delay: 0.2s;
}
i:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes loading-blink {
0% {
opacity: 0.2;
}
20% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
`}</style>
</div>
)
})
export default withDefaults(Loading, defaultProps)

View File

@@ -0,0 +1,91 @@
import { Layout, Playground, Attributes } from 'lib/components'
import { Loading, Spacer, Row } from 'components'
export const meta = {
title: 'Loading',
description: 'Loading',
}
## Loading
Indicate an action running in the background.
<Playground
scope={{ Loading, Row, Spacer }}
code={`
<Row style={{ padding: '10px 0'}}>
<Loading />
</Row>
`} />
<Playground
title="with text"
scope={{ Loading, Row }}
code={`
<Row style={{ padding: '10px 0'}}>
<Loading>Loading</Loading>
</Row>
`} />
<Playground
title="size"
scope={{ Loading, Row, Spacer }}
code={`
<>
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading size="mini" />
</Row>
<Spacer y={.5} />
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading size="small" />
</Row>
<Spacer y={.5} />
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading size="medium" />
</Row>
<Spacer y={.5} />
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading size="large" />
</Row>
</>
`} />
<Playground
title="type"
scope={{ Loading, Row, Spacer }}
code={`
<>
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading type="success" />
</Row>
<Spacer y={.5} />
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading type="secondary" />
</Row>
<Spacer y={.5} />
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading type="warning" />
</Row>
<Spacer y={.5} />
<Row style={{ padding: '10px 0', width: '50px' }}>
<Loading type="error" />
</Row>
</>
`} />
<Attributes edit="/pages/docs/components/loading.mdx">
<Attributes.Title>Loading.Props</Attributes.Title>
| Attribute | Description | Type | Accepted values | Default
| ---------- | ---------- | ---- | -------------- | ------ |
| **type** | bg-color type | `NormalTypes` | `'default', 'secondary', 'success', 'warning', 'error'` | `default` |
| **size** | loading size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` |
| **color** | custom bg color | `string` | - | - |
| **width** | custom width | `string` | - | `100%` |
| **height** | custom height | `string` | - | `100%` |
| ... | native props | `HTMLAttributes` | `'id', 'title', 'className', ...` | - |
</Attributes>
export default ({ children }) => <Layout meta={meta}>{children}</Layout>

View File

@@ -1,5 +1,6 @@
import { Layout, Playground, Attributes } from 'lib/components'
import { Spinner, Spacer } from 'components'
import { Spinner, Spacer, Note, Code, Link } from 'components'
import NextLink from 'next/link'
export const meta = {
title: 'Spinner',
@@ -10,6 +11,8 @@ export const meta = {
Indicate an action running in the background.
<Note>Looking for <Code>Loading</Code>? Use the <NextLink href="/docs/components/loading"><a>Loading Component</a></NextLink>.</Note>
<Playground
scope={{ Spinner }}
code={`