Skip to content

Commit

Permalink
Merge pull request #230 from unix/sizes
Browse files Browse the repository at this point in the history
feat(radio): add support of different sizes
  • Loading branch information
unix committed May 22, 2020
2 parents e16e8aa + 7cac2d6 commit 4857adc
Show file tree
Hide file tree
Showing 10 changed files with 819 additions and 135 deletions.
368 changes: 315 additions & 53 deletions components/radio/__tests__/__snapshots__/group.test.tsx.snap

Large diffs are not rendered by default.

394 changes: 340 additions & 54 deletions components/radio/__tests__/__snapshots__/index.test.tsx.snap

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions components/radio/__tests__/group.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ describe('Radio Group', () => {
expect(() => wrapper.unmount()).not.toThrow()
})

it('should work correctly with different sizes', () => {
const wrapper = mount(
<div>
<Radio.Group value="1" size="mini">
<Radio value="1">1</Radio>
</Radio.Group>
<Radio.Group value="1" size="small">
<Radio value="1">1</Radio>
</Radio.Group>
<Radio.Group value="1" size="large">
<Radio value="1">1</Radio>
</Radio.Group>
</div>,
)
expect(wrapper.html()).toMatchSnapshot()
expect(() => wrapper.unmount()).not.toThrow()
})

it('should trigger events in group', () => {
let value = ''
const changeHandler = jest.fn().mockImplementation(val => (value = val))
Expand Down
13 changes: 13 additions & 0 deletions components/radio/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ describe('Radio', () => {
expect(() => wrapper.unmount()).not.toThrow()
})

it('should work correctly with different sizes', () => {
const wrapper = mount(
<div>
<Radio size="mini">mini</Radio>
<Radio size="small">small</Radio>
<Radio size="medium">medium</Radio>
<Radio size="large">large</Radio>
</div>,
)
expect(wrapper.html()).toMatchSnapshot()
expect(() => wrapper.unmount()).not.toThrow()
})

it('should render correctly with checked prop', () => {
const wrapper = mount(<Radio>Option</Radio>)
wrapper.setProps({ checked: false })
Expand Down
2 changes: 2 additions & 0 deletions components/radio/radio-context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react'
import { NormalSizes } from 'components/utils/prop-types'

export interface RadioConfig {
updateState: (value: string) => void
disabledAll: boolean
value?: string
size?: NormalSizes
inGroup: boolean
}

Expand Down
3 changes: 2 additions & 1 deletion components/radio/radio-description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const RadioDescription: React.FC<React.PropsWithChildren<RadioDescriptionProps>>
<style jsx>{`
span {
color: ${theme.palette.accents_3};
font-size: 0.875rem;
font-size: calc(var(--radio-size) * 0.85);
padding-left: calc(var(--radio-size) + var(--radio-size) * 0.375);
}
`}</style>
</span>
Expand Down
28 changes: 22 additions & 6 deletions components/radio/radio-group.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
import React, { useEffect, useMemo, useState } from 'react'
import withDefaults from '../utils/with-defaults'
import useTheme from '../styles/use-theme'
import { RadioContext } from './radio-context'
import { NormalSizes } from 'components/utils/prop-types'

interface Props {
value?: string
initialValue?: string
disabled?: boolean
size?: NormalSizes
onChange?: (value: string) => void
className?: string
useRow?: boolean
}

const defaultProps = {
disabled: false,
size: 'medium' as NormalSizes,
className: '',
useRow: false,
}

type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type RadioGroupProps = Props & typeof defaultProps & NativeAttrs

export const getRadioSize = (selfSize: NormalSizes, groupSize?: NormalSizes): string => {
const size = groupSize || selfSize
const sizes: { [key in NormalSizes]: string } = {
mini: '.75rem',
small: '.875rem',
medium: '1rem',
large: '1.125rem',
}
return sizes[size]
}

const RadioGroup: React.FC<React.PropsWithChildren<RadioGroupProps>> = ({
disabled,
onChange,
value,
size,
children,
className,
initialValue,
useRow,
...props
}) => {
const theme = useTheme()
const [selfVal, setSelfVal] = useState<string | undefined>(initialValue)

const updateState = (nextValue: string) => {
setSelfVal(nextValue)
onChange && onChange(nextValue)
Expand All @@ -44,9 +56,13 @@ const RadioGroup: React.FC<React.PropsWithChildren<RadioGroupProps>> = ({
updateState,
disabledAll: disabled,
inGroup: true,
size,
value: selfVal,
}
}, [disabled, selfVal])
}, [disabled, selfVal, size])

const fontSize = useMemo(() => getRadioSize(size), [size])
const groupGap = `calc(${fontSize} * 1)`

useEffect(() => {
if (value === undefined) return
Expand All @@ -65,8 +81,8 @@ const RadioGroup: React.FC<React.PropsWithChildren<RadioGroupProps>> = ({
}
.radio-group :global(.radio) {
margin-top: ${useRow ? 0 : theme.layout.gap};
margin-left: ${useRow ? theme.layout.gap : 0};
margin-top: ${useRow ? 0 : groupGap};
margin-left: ${useRow ? groupGap : 0};
}
.radio-group :global(.radio:first-of-type) {
Expand Down
53 changes: 36 additions & 17 deletions components/radio/radio.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useEffect, useMemo, useState } from 'react'
import useTheme from '../styles/use-theme'
import { useRadioContext } from './radio-context'
import RadioGroup from './radio-group'
import RadioGroup, { getRadioSize } from './radio-group'
import RadioDescription from './radio-description'
import { pickChild } from '../utils/collections'
import useWarning from '../utils/use-warning'
import { NormalSizes } from '../utils/prop-types'

interface RadioEventTarget {
checked: boolean
Expand All @@ -20,12 +21,14 @@ export interface RadioEvent {
interface Props {
checked?: boolean
value?: string
size?: NormalSizes
className?: string
disabled?: boolean
onChange?: (e: RadioEvent) => void
}

const defaultProps = {
size: 'medium' as NormalSizes,
disabled: false,
className: '',
}
Expand All @@ -38,13 +41,20 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = ({
checked,
onChange,
disabled,
size,
value: radioValue,
children,
...props
}) => {
const theme = useTheme()
const [selfChecked, setSelfChecked] = useState<boolean>(!!checked)
const { value: groupValue, disabledAll, inGroup, updateState } = useRadioContext()
const {
value: groupValue,
disabledAll,
inGroup,
updateState,
size: groupSize,
} = useRadioContext()
const [withoutDescChildren, DescChildren] = pickChild(children, RadioDescription)

if (inGroup) {
Expand All @@ -59,6 +69,7 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = ({
}, [groupValue, radioValue])
}

const fontSize = useMemo(() => getRadioSize(size, groupSize), [size, groupSize])
const isDisabled = useMemo(() => disabled || disabledAll, [disabled, disabledAll])
const changeHandler = (event: React.ChangeEvent) => {
if (isDisabled) return
Expand All @@ -79,7 +90,7 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = ({

useEffect(() => {
if (checked === undefined) return
setSelfChecked(!!checked)
setSelfChecked(Boolean(checked))
}, [checked])

return (
Expand All @@ -92,9 +103,11 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = ({
onChange={changeHandler}
{...props}
/>
<span className="name">{withoutDescChildren}</span>
<span className="name">
<span className={`point ${selfChecked ? 'active' : ''}`} />
{withoutDescChildren}
</span>
{DescChildren && DescChildren}
<span className="point" />
</label>

<style jsx>{`
Expand All @@ -113,48 +126,54 @@ const Radio: React.FC<React.PropsWithChildren<RadioProps>> = ({
display: flex;
width: initial;
align-items: flex-start;
line-height: 1.5rem;
position: relative;
--radio-size: ${fontSize};
}
label {
display: flex;
flex-direction: column;
justify-content: flex-start;
margin-left: 1.375rem;
color: ${isDisabled ? theme.palette.accents_4 : theme.palette.foreground};
cursor: ${isDisabled ? 'not-allowed' : 'pointer'};
}
.name {
font-size: 1rem;
font-size: var(--radio-size);
font-weight: bold;
user-select: none;
display: inline-flex;
align-items: center;
}
.point {
position: absolute;
left: 0;
top: 6px;
height: 0.875rem;
width: 0.875rem;
height: var(--radio-size);
width: var(--radio-size);
border-radius: 50%;
border: 1px solid ${theme.palette.border};
transition: all 0.2s ease 0s;
position: relative;
display: inline-block;
transform: scale(0.875);
margin-right: calc(var(--radio-size) * 0.375);
}
.point:before {
content: '';
position: absolute;
left: -1px;
top: -1px;
height: 0.875rem;
width: 0.875rem;
transform: scale(0);
height: var(--radio-size);
width: var(--radio-size);
border-radius: 50%;
transform: scale(${selfChecked ? 1 : 0});
transition: all 0.2s ease;
background-color: ${isDisabled ? theme.palette.accents_4 : theme.palette.foreground};
}
.point.active:before {
transform: scale(0.875);
transition: all 0.2s ease 0s;
}
`}</style>
</div>
)
Expand Down
41 changes: 37 additions & 4 deletions pages/en-us/components/radio.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Layout, Playground, Attributes } from 'lib/components'
import { Radio, Spacer, Code } from 'components'
import { Radio, Spacer, Code, Text } from 'components'
import { useState } from 'react'

export const meta = {
Expand Down Expand Up @@ -39,6 +39,31 @@ Provides single user input from a selection of options.
}
`} />

<Playground
title="Sizes"
desc="Radio of different sizes."
scope={{ Radio, Spacer, Text }}
code={`
<>
<Text span size="12px" type="secondary">SINGLE</Text>
<div style={{ paddingLeft: '.25rem', marginTop: '.25rem' }}>
<Radio checked={false} size="mini">mini</Radio>
<Radio checked={false} size="small">small</Radio>
<Radio checked={false} size="medium">medium</Radio>
<Radio checked={false} size="large">large</Radio>
</div>
<Spacer y={.5} />
<Text span size="12px" type="secondary">GROUP</Text>
<div style={{ paddingLeft: '.25rem', marginTop: '.25rem' }}>
<Radio.Group size="mini">
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
<Radio value="3">Option 3</Radio>
</Radio.Group>
</div>
</>
`} />

<Playground
title="Description"
desc="`Description` can be combined with other components."
Expand Down Expand Up @@ -95,8 +120,9 @@ Provides single user input from a selection of options.
| **value** | unique ident value (in group) | `string` | - | - |
| **id** | native attr | `string` | - | - |
| **disabled** | disable current radio | `boolean` | - | `false` |
| **size** | radio size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
| **onChange** | change event | `(e: RadioEvent) => void` | - | - |
| ... | native props | `InputHTMLAttributes` | `'name', 'alt', 'className', ...` | - |
| ... | native props | `InputHTMLAttributes` | `'id', 'className', ...` | - |

<Attributes.Title>Radio.Group.Props</Attributes.Title>

Expand All @@ -106,14 +132,21 @@ Provides single user input from a selection of options.
| **value** | selected child radio | `string` | - | - |
| **useRow** | horizontal layout | `boolean` | - | `false` |
| **disabled** | disable all radios | `boolean` | - | `false` |
| **size** | size of all radios in the group | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
| **onChange** | change event | `(value: string) => void` | - | - |
| ... | native props | `HTMLAttributes` | `'name', 'id', 'className', ...` | - |
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |

<Attributes.Title alias="Radio.Desc">Radio.Description.Props</Attributes.Title>

| Attribute | Description | Type | Accepted values | Default
| ---------- | ---------- | ---- | -------------- | ------ |
| ... | native props | `HTMLAttributes` | `'name', 'id', 'className', ...` | - |
| ... | native props | `HTMLAttributes` | `'id', 'className', ...` | - |

<Attributes.Title>NormalSizes</Attributes.Title>

```ts
type NormalSizes = 'mini' | 'small' | 'medium' | 'large'
```
</Attributes>
Expand Down

0 comments on commit 4857adc

Please sign in to comment.