-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: refactor radio button and implement radioGroup
- Loading branch information
Showing
7 changed files
with
649 additions
and
351 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -1,19 +1,57 @@ | ||
import { ChangeEvent, FC } from 'react'; | ||
import { CSSProperties, ChangeEvent, FC, useEffect, useState } from 'react'; | ||
|
||
import { Input, Label, Wrapper } from './styles'; | ||
|
||
export type Props = { | ||
id: string; | ||
value: string; | ||
label: string; | ||
group: string; | ||
onChange: (event: ChangeEvent<HTMLInputElement>) => void; | ||
disabled?: boolean; | ||
style?: CSSProperties; | ||
checked?: boolean; | ||
}; | ||
|
||
const Radio: FC<Props> = ({ onChange, id, label, group }) => ( | ||
<Wrapper> | ||
<Input onChange={onChange} id={id} name={group} type="radio" /> | ||
<Label htmlFor={id}>{label}</Label> | ||
</Wrapper> | ||
); | ||
const Radio: FC<Props> = ({ | ||
onChange, | ||
id, | ||
value, | ||
label, | ||
group, | ||
style, | ||
disabled = false, | ||
checked = false, | ||
}) => { | ||
const [internalChecked, setInternalChecked] = useState(checked); | ||
|
||
const handleOnChange = (event: ChangeEvent<HTMLInputElement>) => { | ||
if (disabled) return; | ||
onChange(event); | ||
}; | ||
|
||
useEffect(() => { | ||
setInternalChecked(checked); | ||
}, [checked]); | ||
|
||
useEffect(() => { | ||
if (disabled && checked) setInternalChecked(false); | ||
}, [disabled, checked]); | ||
|
||
return ( | ||
<Wrapper disabled={!!disabled} style={style}> | ||
<Input | ||
onChange={handleOnChange} | ||
id={id} | ||
value={value} | ||
name={group} | ||
type="radio" | ||
disabled={disabled} | ||
checked={internalChecked} | ||
/> | ||
<Label htmlFor={id}>{label}</Label> | ||
</Wrapper> | ||
); | ||
}; | ||
|
||
export default Radio; |
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 |
---|---|---|
@@ -1,64 +1,86 @@ | ||
import styled from 'styled-components'; | ||
import { getTheme, ifStyle } from '@platformbuilders/theme-toolkit'; | ||
|
||
export const Wrapper = styled.div` | ||
margin: 16px 0; | ||
const brandPrimaryMain = getTheme('brand.primary.main'); | ||
const brandPrimaryContrast = getTheme('brand.primary.contrast'); | ||
const themeRadiusInput = getTheme('themeRadius.input'); | ||
const spacingXs = getTheme('spacing.xs'); | ||
const spacingSm = getTheme('spacing.sm'); | ||
const spacingMd = getTheme('spacing.md'); | ||
const textMain = getTheme('text.main'); | ||
const isDisabled = ifStyle('disabled'); | ||
|
||
type WrapperProps = { | ||
disabled?: boolean; | ||
}; | ||
|
||
export const Wrapper = styled.div<WrapperProps>` | ||
display: flex; | ||
position: relative; | ||
flex-direction: column; | ||
width: 100%; | ||
padding: ${spacingXs}px ${spacingSm}px ${spacingXs}px ${spacingMd}px; | ||
opacity: ${isDisabled('0.3', '1')}; | ||
&:hover { | ||
background-color: ${(props) => | ||
isDisabled('transparent', `${brandPrimaryMain(props)}10`)}; | ||
border-radius: ${themeRadiusInput}px; | ||
} | ||
&:hover > label { | ||
color: ${(props) => isDisabled(`${textMain(props)}70`, textMain)}; | ||
} | ||
`; | ||
|
||
export const Input = styled.input` | ||
display: none; | ||
position: absolute; | ||
opacity: 0; | ||
&:checked + label:before { | ||
border-color: rgb(51, 122, 183); | ||
animation: ripple 0.2s linear forwards; | ||
background: ${brandPrimaryMain}; | ||
} | ||
&:checked + label:after { | ||
transform: scale(1); | ||
border-color: ${brandPrimaryContrast}; | ||
transition-property: transform, border-color, background-color; | ||
transition-duration: 0.3s; | ||
transition-timing-function: ease; | ||
} | ||
&:checked + label { | ||
color: ${textMain}; | ||
} | ||
`; | ||
|
||
export const Label = styled.label` | ||
display: inline-block; | ||
min-height: 20px; | ||
position: relative; | ||
padding: 0 30px; | ||
margin-bottom: 0; | ||
cursor: pointer; | ||
vertical-align: bottom; | ||
@keyframes ripple { | ||
0% { | ||
box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0); | ||
} | ||
50% { | ||
box-shadow: 0px 0px 0px 15px rgba(0, 0, 0, 0.1); | ||
} | ||
100% { | ||
box-shadow: 0px 0px 0px 15px rgba(0, 0, 0, 0); | ||
} | ||
} | ||
color: ${textMain}70; | ||
&:before, | ||
&:after { | ||
position: absolute; | ||
content: ''; | ||
border-radius: 50%; | ||
transition: all 0.3s ease; | ||
transition-property: transform, border-color; | ||
position: relative; | ||
display: inline-block; | ||
vertical-align: bottom; | ||
} | ||
&:before { | ||
left: 0; | ||
top: 0; | ||
width: 16px; | ||
height: 16px; | ||
border: 2px solid rgba(0, 0, 0, 0.54); | ||
width: 1rem; | ||
height: 1rem; | ||
border-radius: 100%; | ||
border: 2px solid ${brandPrimaryMain}; | ||
margin-right: ${spacingSm}px; | ||
} | ||
&:after { | ||
top: 5px; | ||
left: 5px; | ||
width: 10px; | ||
height: 10px; | ||
transform: scale(0); | ||
background: rgb(51, 122, 183); | ||
position: absolute; | ||
top: 0.5rem; | ||
left: 21px; | ||
width: 0.375rem; | ||
height: 0.1875rem; | ||
border: solid 2px transparent; | ||
border-right: none; | ||
border-top: none; | ||
transform: translate(0.0625rem, 0.125rem) rotate(-45deg); | ||
} | ||
`; |
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,56 @@ | ||
import { CSSProperties, ChangeEvent, FC, useState } from 'react'; | ||
|
||
import Radio from '../Radio'; | ||
import { Wrapper } from './styles'; | ||
|
||
type OptionProps = { | ||
id: string; | ||
value: string; | ||
label: string; | ||
style?: CSSProperties; | ||
}; | ||
|
||
export type Props = { | ||
id: string; | ||
name: string; | ||
options: OptionProps[]; | ||
defaultValue: string; | ||
onChange: (event: ChangeEvent<HTMLInputElement>) => void; | ||
disabled: boolean; | ||
}; | ||
|
||
const RadioGroup: FC<Props> = ({ | ||
id, | ||
name, | ||
options, | ||
defaultValue, | ||
onChange, | ||
disabled, | ||
}) => { | ||
const [internalValue, setInternalValue] = useState(defaultValue); | ||
|
||
const handleOnChange = (event: ChangeEvent<HTMLInputElement>) => { | ||
setInternalValue(event.target.value); | ||
onChange(event); | ||
}; | ||
|
||
return ( | ||
<Wrapper id={id}> | ||
{options.map(({ id, value, label, style }) => ( | ||
<Radio | ||
key={id} | ||
id={id} | ||
value={value} | ||
label={label} | ||
style={style} | ||
group={name} | ||
disabled={disabled} | ||
checked={value === internalValue} | ||
onChange={handleOnChange} | ||
/> | ||
))} | ||
</Wrapper> | ||
); | ||
}; | ||
|
||
export default RadioGroup; |
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,63 @@ | ||
import { expect, jest } from '@storybook/jest'; | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { configure, userEvent, within } from '@storybook/testing-library'; | ||
import RadioGroup from './index'; | ||
|
||
configure({ testIdAttribute: 'id' }); | ||
|
||
// Mocks | ||
const mockTextId = 'radio-group-test-id'; | ||
const mockOnChange = jest.fn(); | ||
|
||
const meta: Meta<typeof RadioGroup> = { | ||
title: 'Components/RadioGroup', | ||
component: RadioGroup, | ||
parameters: { | ||
layout: 'centered', | ||
}, | ||
tags: ['autodocs'], | ||
args: { | ||
id: 'mockTextId', | ||
options: [ | ||
{ | ||
id: 'option1', | ||
value: 'option1', | ||
label: 'Opção 1', | ||
}, | ||
{ | ||
id: 'option2', | ||
value: 'option2', | ||
label: 'Opção 2', | ||
}, | ||
{ | ||
id: 'option3', | ||
value: 'option3', | ||
label: 'Opção 3', | ||
}, | ||
], | ||
defaultValue: 'option1', | ||
name: 'radio-group-test', | ||
onChange: mockOnChange, | ||
disabled: false, | ||
}, | ||
play: async ({ canvasElement, step }) => { | ||
const canvas = within(canvasElement); | ||
|
||
await step('RadioGroup | Test Render', async () => { | ||
expect(canvas.getByTestId(mockTextId)).toBeInTheDocument(); | ||
}); | ||
|
||
await step('RadioGroup | Event Click', async () => { | ||
await userEvent.click(canvas.getByTestId('option1')); | ||
expect(mockOnChange).toHaveBeenCalled(); | ||
}); | ||
}, | ||
}; | ||
|
||
type Story = StoryObj<typeof RadioGroup>; | ||
|
||
export const Default: Story = { | ||
args: {}, | ||
}; | ||
|
||
export default meta; |
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,13 @@ | ||
import styled from 'styled-components'; | ||
import { getTheme } from '@platformbuilders/theme-toolkit'; | ||
|
||
const spacingSm = getTheme('spacing.sm'); | ||
const spacingMd = getTheme('spacing.md'); | ||
|
||
export const Wrapper = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
width: 100%; | ||
gap:${spacingMd}px | ||
padding: ${spacingSm}px; | ||
`; |
Oops, something went wrong.