feat(toggle): add components

This commit is contained in:
unix
2020-04-02 05:20:08 +08:00
parent e08e6cad2a
commit f5ae55c772
3 changed files with 262 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
import Toggle from './toggle'
export default Toggle

View File

@@ -0,0 +1,169 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../styles/use-theme'
import { NormalSizes } from '../utils/prop-types'
interface ToggleEventTarget {
checked: boolean
}
export interface ToggleEvent {
target: ToggleEventTarget
stopPropagation: () => void
preventDefault: () => void
nativeEvent: React.ChangeEvent
}
interface Props {
checked?: boolean
initialChecked?: boolean
onChange?: (ev: ToggleEvent) => void
disabled?: boolean
size?: NormalSizes
className?: string
}
const defaultProps = {
size: 'medium' as NormalSizes,
disabled: false,
initialChecked: false,
className: '',
}
type NativeAttrs = Omit<React.LabelHTMLAttributes<any>, keyof Props>
export type ToggleProps = Props & typeof defaultProps & NativeAttrs
export type ToggleSize = {
width: string
height: string
}
const getSizes = (size: NormalSizes) => {
const sizes: { [key in NormalSizes]: ToggleSize } = {
mini: {
width: '1.67rem', height: '.835rem',
},
small: {
width: '1.67rem', height: '.835rem',
},
medium: {
width: '1.75rem', height: '.875rem',
},
large: {
width: '2rem', height: '1rem',
},
}
return sizes[size]
}
const Toggle: React.FC<ToggleProps> = React.memo(({
initialChecked, checked, disabled, onChange, size, className, ...props
}) => {
const theme = useTheme()
const [selfChecked, setSelfChecked] = useState<boolean>(initialChecked)
const { width, height } = useMemo(() => getSizes(size), [size])
const changeHandle = useCallback((ev: React.ChangeEvent) => {
if (disabled) return
const selfEvent: ToggleEvent = {
target: {
checked: !selfChecked,
},
stopPropagation: ev.stopPropagation,
preventDefault: ev.preventDefault,
nativeEvent: ev,
}
setSelfChecked(!selfChecked)
onChange && onChange(selfEvent)
}, [disabled, selfChecked, onChange])
useEffect(() => {
if (checked === undefined) return
setSelfChecked(checked)
}, [checked])
return (
<label className={className} {...props}>
<input type="checkbox" disabled={disabled} checked={selfChecked} onChange={changeHandle} />
<div className={`toggle ${selfChecked ? 'checked' : ''} ${disabled ? 'disabled' : ''}`}>
<span className="inner" />
</div>
<style jsx>{`
label {
-webkit-tap-highlight-color: transparent;
display: inline-block;
vertical-align: center;
white-space: nowrap;
user-select: none;
padding: 3px 0;
position: relative;
cursor: ${disabled ? 'not-allowed' : 'pointer'};
}
input {
overflow: hidden;
visibility: hidden;
height: 0;
opacity: 0;
width: 0;
position: absolute;
background-color: transparent;
z-index: -1;
}
.toggle {
height: ${height};
width: ${width};
border-radius: ${height};
transition-delay: 0.12s;
transition-duration: 0.2s;
transition-property: background, border;
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
position: relative;
border: 1px solid transparent;
background-color: ${theme.palette.accents_2};
padding: 0;
}
.inner {
width: calc(${height} - 2px);
height: calc(${height} - 2px);
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 1px;
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0, rgba(0, 0, 0, 0.1) 0 1px 3px 0;
transition: left 280ms cubic-bezier(0, 0, 0.2, 1);
border-radius: 50%;
background-color: ${theme.palette.background};
}
.disabled {
border-color: ${theme.palette.accents_2};
background-color: ${theme.palette.accents_1};
}
.disabled > .inner {
background-color: ${theme.palette.accents_2};
}
.disabled.checked {
border-color: ${theme.palette.accents_4};
background-color: ${theme.palette.accents_4};
}
.checked {
background-color: ${theme.palette.success};
}
.checked > .inner {
left: calc(100% - (${height} - 2px));
box-shadow: none;
}
`}</style>
</label>
)
})
export default withDefaults(Toggle, defaultProps)

View File

@@ -0,0 +1,90 @@
import { Layout, Playground, Attributes } from 'lib/components'
import { Toggle, Spacer } from 'components'
export const meta = {
title: 'Toggle',
description: 'Toggle',
}
## Toggle
Displays a boolean value.
<Playground
scope={{ Toggle, Spacer }}
code={`
<>
<Toggle />
<Spacer y={.5} />
<Toggle initialChecked />
</>
`} />
<Playground
title="disabled"
scope={{ Toggle, Spacer }}
code={`
<>
<Toggle disabled />
<Spacer y={.5} />
<Toggle initialChecked disabled />
</>
`} />
<Playground
title="size"
scope={{ Toggle, Spacer }}
code={`
<>
<Toggle size="small" />
<Spacer y={.5} />
<Toggle initialChecked size="medium" />
<Spacer y={.5} />
<Toggle initialChecked size="large" />
</>
`} />
<Playground
title="get change"
scope={{ Toggle, Spacer }}
code={`
() => {
const handler = event => {
console.log(event.target.checked)
}
return <Toggle onChange={handler} />
}
`} />
<Attributes edit="/pages/docs/components/toggle.mdx">
<Attributes.Title>Toggle.Props</Attributes.Title>
| Attribute | Description | Type | Accepted values | Default
| ---------- | ---------- | ---- | -------------- | ------ |
| **checked** | checked or not | `boolean` | - | - |
| **initialChecked** | checked or not on initial | `boolean` | - | `false` |
| **onChange** | change event handler | `ToggleEvent` | - | - |
| **size** | input size | `NormalSizes` | `'mini', 'small', 'medium', 'large'` | `medium` |
| **disabled** | disable toggle | `boolean` | - | `false` |
| ... | native props | `LabelHTMLAttributes` | `'from', 'name', 'className', ...` | - |
<Attributes.Title>ToggleEvent</Attributes.Title>
```ts
interface ToggleEventTarget {
checked: boolean
}
export interface ToggleEvent {
target: ToggleEventTarget
stopPropagation: () => void
preventDefault: () => void
nativeEvent: React.ChangeEvent
}
```
</Attributes>
export default ({ children }) => <Layout meta={meta}>{children}</Layout>