Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch component #620

Merged
merged 6 commits into from
Oct 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
sohkai marked this conversation as resolved.
Show resolved Hide resolved

### `checked`
sohkai marked this conversation as resolved.
Show resolved Hide resolved

| 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}
bpierre marked this conversation as resolved.
Show resolved Hide resolved
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
sohkai marked this conversation as resolved.
Show resolved Hide resolved
type="checkbox"
onFocus={() => {
setIsFocused(true)
onFocus()
}}
onBlur={() => setIsFocused(false)}
checked={checked}
disabled={disabled}
onChange={handleChange}
css={`
bpierre marked this conversation as resolved.
Show resolved Hide resolved
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);
sohkai marked this conversation as resolved.
Show resolved Hide resolved
`}
/>
)}
</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,
sohkai marked this conversation as resolved.
Show resolved Hide resolved
controlDisabled: colors.GreyBasic,

accent: colors.AragonBlue,
Expand Down