Skip to content

Commit

Permalink
Switch component (#620)
Browse files Browse the repository at this point in the history
Add a Switch component and update theme’s control-related colors.
  • Loading branch information
adekbadek authored and bpierre committed Nov 18, 2019
1 parent 168aa44 commit 04a0480
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 2 deletions.
44 changes: 44 additions & 0 deletions devbox/apps/Switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useState } from 'react'
import styled from 'styled-components'
import { Switch } from '@aragon/ui'

const Text = styled.span`
padding-right: 20px;
min-width: 100px;
`
const OptionWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`

// eslint-disable-next-line react/prop-types
const Option = ({ name, initiallyChecked, ...passedProps }) => {
const [checked, setIsChecked] = useState(Boolean(initiallyChecked))
return (
<OptionWrapper>
<Text>{name}</Text>
<Switch onChange={setIsChecked} checked={checked} {...passedProps} />
</OptionWrapper>
)
}

export default () => {
return (
<div
css={`
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
`}
>
<div>
<Option name="On" initiallyChecked />
<Option name="Off" />
<Option name="Disabled on" initiallyChecked disabled />
<Option name="Disabled off" disabled />
</div>
</div>
)
}
1 change: 1 addition & 0 deletions devbox/apps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export { default as ProgressBar } from './ProgressBar'
export { default as Radio } from './Radio'
export { default as Scratchpad } from './Scratchpad'
export { default as SidePanel } from './SidePanel'
export { default as Switch } from './Switch'
export { default as SyncIndicator } from './SyncIndicator'
export { default as Tabs } from './Tabs'
export { default as Tag } from './Tag'
Expand Down
49 changes: 49 additions & 0 deletions gallery/src/pages/PageSwitch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useState } from 'react'
import styled from 'styled-components'
import { Switch } from '@aragon/ui'

import Page from 'comps/Page/Page'
import readme from 'ui-src/components/Switch/README.md'

const Text = styled.span`
padding-right: 20px;
min-width: 100px;
`
const OptionWrapper = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`

// eslint-disable-next-line react/prop-types
const Option = ({ name, initiallyChecked, ...passedProps }) => {
const [checked, setIsChecked] = useState(Boolean(initiallyChecked))
return (
<OptionWrapper>
<Text>{name}</Text>
<Switch onChange={setIsChecked} checked={checked} {...passedProps} />
</OptionWrapper>
)
}

// eslint-disable-next-line react/prop-types
const PageTabs = ({ title }) => {
return (
<Page title={title} readme={readme}>
<Page.Demo opaque>
<div
css={`
padding: 20px;
`}
>
<Option name="On" initiallyChecked />
<Option name="Off" />
<Option name="Disabled on" initiallyChecked disabled />
<Option name="Disabled off" disabled />
</div>
</Page.Demo>
</Page>
)
}

export default PageTabs
2 changes: 2 additions & 0 deletions gallery/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import PageField from './pages/PageField'
import PageSlider from './pages/PageSlider'
import PageTabs from './pages/PageTabs'
import PageAutoComplete from './pages/PageAutoComplete'
import PageSwitch from './pages/PageSwitch'

// Other components
import PageAddressField from './pages/PageAddressField'
Expand Down Expand Up @@ -98,6 +99,7 @@ export const PAGE_GROUPS = [
[PageSlider, 'Slider'],
[PageTextInput, 'TextInput'],
[PageAutoComplete, 'AutoComplete'],
[PageSwitch, 'Switch'],
],
},
{
Expand Down
35 changes: 35 additions & 0 deletions src/components/Switch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Switch

A simple switch component.

## Usage

```jsx
import React, { useState } from 'react'
import { Switch } from '@aragon/ui'

const App = () => {
const [checked, setChecked] = useState(false)
return <Switch checked={checked} onChange={setChecked} />
}
```

## Props

### `checked`

| Type | Default value |
| --------- | ------------- |
| `Boolean` | `false` |

### `disabled`

| Type | Default value |
| --------- | ------------- |
| `Boolean` | `false` |

### `onChange`

| Type | Default value |
| ------------------------------------- | ------------- |
| `Function`: `(checked: Boolean) -> *` | `() => {}` |
128 changes: 128 additions & 0 deletions src/components/Switch/Switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { Spring, animated } from 'react-spring'

import { useTheme } from '../../theme'
import { noop } from '../../utils'
import { springs, GU } from '../../style'
import FocusVisible from '../FocusVisible/FocusVisible'

const BORDER = 1
const WRAPPER_WIDTH = 5 * GU
const WRAPPER_HEIGHT = 2.25 * GU

function Switch({ checked, disabled, onChange }) {
const theme = useTheme()
const [isFocused, setIsFocused] = useState(false)

const colors = {
checkedBackground: disabled ? theme.controlDisabled : theme.selected,
unCheckedBackground: disabled ? theme.controlDisabled : theme.surfaceUnder,
}

const handleChange = disabled ? noop : () => onChange(!checked)

return (
<FocusVisible>
{({ focusVisible, onFocus }) => (
<span
onClick={handleChange}
css={`
position: relative;
display: inline-block;
width: ${WRAPPER_WIDTH}px;
height: ${WRAPPER_HEIGHT}px;
border: ${BORDER}px solid ${theme.border};
border-radius: ${WRAPPER_HEIGHT}px;
background-color: ${checked
? colors.checkedBackground
: colors.unCheckedBackground};
transition: border-color 50ms, background-color 50ms;
cursor: ${disabled ? 'default' : 'pointer'};
${disabled
? ''
: `&:active {
border-color: ${theme.controlBorderPressed};
}`}
${isFocused && focusVisible
? `
&:after {
content: '';
position: absolute;
left: ${-BORDER * 2}px;
top: ${-BORDER * 2}px;
width: ${WRAPPER_WIDTH + BORDER * 2}px;
height: ${WRAPPER_HEIGHT + BORDER * 2}px;
border-radius: ${WRAPPER_HEIGHT}px;
border: 2px solid ${theme.focus};
}
`
: ''};
`}
>
<input
type="checkbox"
onFocus={() => {
setIsFocused(true)
onFocus()
}}
onBlur={() => setIsFocused(false)}
checked={checked}
disabled={disabled}
onChange={handleChange}
css={`
opacity: 0;
pointer-events: none;
`}
/>
<Spring
to={{
progress: checked
? WRAPPER_WIDTH - WRAPPER_HEIGHT + BORDER
: BORDER,
}}
config={springs.smooth}
native
>
{({ progress }) => (
<animated.span
style={{
transform: progress.interpolate(
v => `translate3d(${v}px, 0, 0)`
),
}}
css={`
position: absolute;
left: 0;
z-index: 1;
top: ${BORDER}px;
width: ${WRAPPER_HEIGHT - BORDER * 4}px;
height: ${WRAPPER_HEIGHT - BORDER * 4}px;
border-radius: ${WRAPPER_HEIGHT - BORDER * 4}px;
background-color: ${theme.surface};
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.15);
`}
/>
)}
</Spring>
</span>
)}
</FocusVisible>
)
}

Switch.propTypes = {
checked: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func,
}

Switch.defaultProps = {
checked: false,
disabled: false,
onChange: noop,
}

export default Switch
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export { default as SidePanelSeparator } from './SidePanel/SidePanelSeparator'
export { default as SidePanelSplit } from './SidePanel/SidePanelSplit'
export { default as Slider } from './Slider/Slider'
export { default as Split } from './Split/Split'
export { default as Switch } from './Switch/Switch'
export { default as SyncIndicator } from './SyncIndicator/SyncIndicator'
export { default as Table } from './Table/Table'
export { default as TableCell } from './Table/TableCell'
Expand Down
4 changes: 2 additions & 2 deletions src/theme/theme-light.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ export default {
disabledContent: colors.GreyMedium,
disabledIcon: colors.ArcticBlueDarker,

control: colors.ArcticBlue,
control: colors.ArcticBlueLight,
controlBorder: colors.GreyBasic,
controlBorderPressed: colors.Grey,
controlBorderPressed: colors.ArcticBlueDark,
controlDisabled: colors.GreyBasic,

accent: colors.AragonBlue,
Expand Down

0 comments on commit 04a0480

Please sign in to comment.