-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(radio-button): standalone radio button component (#903)
* feat(radio-button): standalone radio button component
- Loading branch information
1 parent
9cc7e22
commit 6ed27f3
Showing
9 changed files
with
468 additions
and
84 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
packages/react/src/components/radio-button/radio-button.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { mountWithTheme } from '../../test-utils/renderer'; | ||
import { RadioButton } from './radio-button'; | ||
import { getByTestId } from '../../test-utils/enzyme-selectors'; | ||
import { doNothing } from '../../test-utils/callbacks'; | ||
|
||
describe('Radio button', () => { | ||
test('should have controllable data-testid', () => { | ||
const wrapper = mountWithTheme(<RadioButton checked data-testid="radiobutton-testid" />); | ||
|
||
expect(getByTestId(wrapper, 'radiobutton-testid').exists()).toBe(true); | ||
}); | ||
|
||
test('should be checked by default is defaultChecked prop is present', () => { | ||
const wrapper = mountWithTheme(<RadioButton defaultChecked />); | ||
|
||
const input = wrapper.find('input[type="radio"]'); | ||
expect(input.prop('defaultChecked')).toBe(true); | ||
}); | ||
|
||
test('onChange callback should be called when radio button is checked', () => { | ||
const callback = jest.fn(); | ||
const wrapper = mountWithTheme(<RadioButton onChange={callback} />); | ||
|
||
wrapper.find('input[type="radio"]').simulate('change'); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('should be checked when checked prop is set to true', () => { | ||
const wrapper = mountWithTheme(<RadioButton checked onChange={doNothing} />); | ||
|
||
const input = wrapper.find('input[type="radio"]'); | ||
expect(input.prop('checked')).toBe(true); | ||
}); | ||
|
||
test('should be disabled when disabled prop is set to true', () => { | ||
const wrapper = mountWithTheme(<RadioButton disabled />); | ||
|
||
const input = wrapper.find('input[type="radio"]'); | ||
expect(input.prop('disabled')).toBe(true); | ||
}); | ||
|
||
test('matches snapshot', () => { | ||
const tree = mountWithTheme(<RadioButton label="This is a label" />); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
118 changes: 118 additions & 0 deletions
118
packages/react/src/components/radio-button/radio-button.test.tsx.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Radio button matches snapshot 1`] = ` | ||
.c1 { | ||
-webkit-appearance: none; | ||
-moz-appearance: none; | ||
appearance: none; | ||
background-color: #FFFFFF; | ||
border: 1px solid #60666E; | ||
border-radius: 50%; | ||
color: #FFFFFF; | ||
display: inline-block; | ||
-webkit-flex-shrink: 0; | ||
-ms-flex-negative: 0; | ||
flex-shrink: 0; | ||
height: var(--size-1x); | ||
margin: 0; | ||
margin-top: var(--spacing-half); | ||
position: relative; | ||
width: var(--size-1x); | ||
} | ||
.c1 + { | ||
outline: 2px solid transparent; | ||
outline-offset: -2px; | ||
} | ||
.c1:focus + { | ||
box-shadow: 0 0 0 2px #006296; | ||
outline: 2px solid #84C6EA; | ||
outline-offset: -2px; | ||
} | ||
.c1:checked { | ||
border: 2px solid #006296; | ||
} | ||
.c1:checked::after { | ||
background-color: #006296; | ||
border-radius: 50%; | ||
content: ''; | ||
display: block; | ||
height: var(--size-half); | ||
left: 50%; | ||
position: absolute; | ||
top: 50%; | ||
-webkit-transform: translate(-50%,-50%); | ||
-ms-transform: translate(-50%,-50%); | ||
transform: translate(-50%,-50%); | ||
width: var(--size-half); | ||
} | ||
.c1:disabled + label { | ||
color: #B7BBC2; | ||
} | ||
.c1:hover:checked:not(:disabled) { | ||
border: 1px solid #000000; | ||
} | ||
.c1:hover:not(:checked) { | ||
border: 1px solid #000000; | ||
} | ||
.c2 { | ||
font-size: 0.875rem; | ||
line-height: 1.5rem; | ||
margin-left: var(--spacing-1x); | ||
} | ||
.c0 { | ||
-webkit-align-items: flex-start; | ||
-webkit-box-align: flex-start; | ||
-ms-flex-align: flex-start; | ||
align-items: flex-start; | ||
display: -webkit-inline-box; | ||
display: -webkit-inline-flex; | ||
display: -ms-inline-flexbox; | ||
display: inline-flex; | ||
margin-top: var(--spacing-1x); | ||
position: relative; | ||
} | ||
<RadioButton | ||
label="This is a label" | ||
> | ||
<styled.div> | ||
<div | ||
className="c0" | ||
> | ||
<styled.input | ||
data-testid="radiobutton-uuid1" | ||
id="uuid1" | ||
type="radio" | ||
> | ||
<input | ||
className="c1" | ||
data-testid="radiobutton-uuid1" | ||
id="uuid1" | ||
type="radio" | ||
/> | ||
</styled.input> | ||
<styled.label | ||
data-testid="radiobutton-uuid1_label" | ||
htmlFor="uuid1" | ||
> | ||
<label | ||
className="c2" | ||
data-testid="radiobutton-uuid1_label" | ||
htmlFor="uuid1" | ||
> | ||
This is a label | ||
</label> | ||
</styled.label> | ||
</div> | ||
</styled.div> | ||
</RadioButton> | ||
`; |
128 changes: 128 additions & 0 deletions
128
packages/react/src/components/radio-button/radio-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { ChangeEvent, FunctionComponent } from 'react'; | ||
import styled from 'styled-components'; | ||
import { focus } from '../../utils/css-state'; | ||
import { useId } from '../../hooks/use-id'; | ||
|
||
const StyledInput = styled.input<{ disabled?: boolean }>` | ||
appearance: none; | ||
background-color: ${({ theme, disabled }) => (disabled ? theme.component['radio-button-disabled-background-color'] : theme.component['radio-button-background-color'])}; | ||
border: 1px solid ${({ theme, disabled }) => (disabled ? theme.component['radio-button-disabled-border-color'] : theme.component['radio-button-border-color'])}; | ||
border-radius: 50%; | ||
color: ${({ theme, disabled }) => (disabled ? theme.component['radio-button-disabled-background-color'] : theme.component['radio-button-background-color'])}; | ||
display: inline-block; | ||
flex-shrink: 0; | ||
height: var(--size-1x); | ||
margin: 0; | ||
margin-top: var(--spacing-half); | ||
position: relative; | ||
width: var(--size-1x); | ||
${(theme) => focus(theme, { selector: '+' })} | ||
&:checked { | ||
border: 2px solid ${({ theme, disabled }) => (disabled ? theme.component['radio-button-disabled-border-color'] : theme.component['radio-button-checked-border-color'])}; | ||
&::after { | ||
background-color: ${({ theme, disabled }) => (disabled ? theme.component['radio-button-disabled-border-color'] : theme.component['radio-button-checked-background-color'])}; | ||
border-radius: 50%; | ||
content: ''; | ||
display: block; | ||
height: var(--size-half); | ||
left: 50%; | ||
position: absolute; | ||
top: 50%; | ||
transform: translate(-50%, -50%); | ||
width: var(--size-half); | ||
} | ||
} | ||
&:disabled { | ||
& + label { | ||
color: ${({ theme }) => theme.component['radio-button-disabled-label-color']}; | ||
} | ||
} | ||
&:hover { | ||
&:checked:not(:disabled) { | ||
border: 1px solid ${({ theme }) => theme.component['radio-button-hover-border-color']}; | ||
} | ||
&:not(:checked) { | ||
border: 1px solid ${({ theme, disabled }) => (disabled ? theme.component['radio-button-disabled-hover-border-color'] : theme.component['radio-button-hover-border-color'])}; | ||
} | ||
} | ||
`; | ||
|
||
const StyledLabel = styled.label` | ||
font-size: 0.875rem; | ||
line-height: 1.5rem; | ||
margin-left: var(--spacing-1x); | ||
`; | ||
|
||
const StyledContainer = styled.div` | ||
align-items: flex-start; | ||
display: inline-flex; | ||
margin-top: var(--spacing-1x); | ||
position: relative; | ||
`; | ||
|
||
interface RadioButtonProps { | ||
ariaLabel?: string; | ||
ariaLabelledBy?: string[]; | ||
checked?: boolean; | ||
className?: string; | ||
defaultChecked?: boolean; | ||
disabled?: boolean; | ||
id?: string; | ||
label?: string; | ||
name?: string; | ||
value?: string; | ||
|
||
onChange?(event: ChangeEvent<HTMLInputElement>): void; | ||
} | ||
|
||
export const RadioButton: FunctionComponent<RadioButtonProps> = ({ | ||
ariaLabel, | ||
ariaLabelledBy, | ||
checked, | ||
className, | ||
defaultChecked, | ||
disabled, | ||
id, | ||
label, | ||
name, | ||
value, | ||
onChange, | ||
}) => { | ||
const inputId = useId(id); | ||
|
||
return ( | ||
<StyledContainer> | ||
<StyledInput | ||
data-testid={`radiobutton-${inputId}`} | ||
id={inputId} | ||
type="radio" | ||
name={name} | ||
className={className} | ||
value={value} | ||
defaultChecked={defaultChecked} | ||
checked={checked} | ||
disabled={disabled} | ||
onChange={onChange} | ||
aria-label={ariaLabel} | ||
aria-labelledby={ariaLabelledBy?.join(' ')} | ||
/> | ||
{ | ||
label && ( | ||
<StyledLabel | ||
data-testid={`radiobutton-${inputId}_label`} | ||
htmlFor={inputId} | ||
> | ||
{label} | ||
</StyledLabel> | ||
) | ||
} | ||
</StyledContainer> | ||
); | ||
}; | ||
|
||
RadioButton.displayName = 'RadioButton'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { RadioButtonGroup } from '@equisoft/design-elements-react'; | ||
import { ArgTypes, Canvas, Meta } from '@storybook/blocks'; | ||
import * as RadioButtonGroupStories from './radio-button-group.stories'; | ||
|
||
<Meta of={RadioButtonGroupStories} /> | ||
|
||
# Radio Button | ||
1. [Definition](#definition) | ||
2. [Usage](#usage) | ||
3. [Variants](#variants) | ||
4. [Properties](#properties) | ||
|
||
## Definition | ||
Radio buttons display mutually exclusive options from which a user selects exactly one [1]. | ||
<Canvas of={RadioButtonGroupStories.Default} /> | ||
|
||
## Usage | ||
|
||
### When to use | ||
- When you only want the user to select one item [3]. | ||
- Use when the user has to select only one option from a list between 2 to 6 choices [3]. | ||
- Use it when validation is needed, such as a submit button [3]. | ||
|
||
### When not to use | ||
- Radio buttons should not be used to perform actions [2]. | ||
- Don't use it when there are more than 6 options, consider the dropdown-list instead [3]. | ||
|
||
## Variants | ||
|
||
### Default | ||
<Canvas of={RadioButtonGroupStories.Default} /> | ||
|
||
### With conditional content | ||
<Canvas of={RadioButtonGroupStories.WithConditionalContent} /> | ||
|
||
## Properties | ||
<ArgTypes of={RadioButtonGroup} /> | ||
|
||
## References | ||
1. [https://www.nngroup.com/articles/radio-buttons-default-selection/](https://www.nngroup.com/articles/radio-buttons-default-selection/) | ||
2. [https://uxplanet.org/radio-buttons-ux-design-588e5c0a50dc](https://uxplanet.org/radio-buttons-ux-design-588e5c0a50dc) | ||
3. [https://uxdesign.cc/ui-cheat-sheet-radio-buttons-checkboxes-and-other-selectors-bf56777ad59e](https://uxdesign.cc/ui-cheat-sheet-radio-buttons-checkboxes-and-other-selectors-bf56777ad59e) |
Oops, something went wrong.