Skip to content

Commit

Permalink
refactor: migrate radio to tailwind (#1923)
Browse files Browse the repository at this point in the history
* refactor: migrate radio to tailwind

* refactor: extract icon

* fix: improve semantic by using label instead of div
  • Loading branch information
keellyp authored Dec 16, 2024
1 parent dfe3d3c commit a9fab15
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 106 deletions.
112 changes: 16 additions & 96 deletions src/components/form/Radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
/* eslint-disable tailwindcss/no-custom-classname */
import { cx } from 'class-variance-authority'
import { isBoolean } from 'lodash'
import { forwardRef, ReactNode, useId, useRef, useState } from 'react'
import styled from 'styled-components'

import { Typography, TypographyProps } from '~/components/designSystem'
import RadioCheckedIcon from '~/public/icons/forms/radio-checked.svg'
import RadioIcon from '~/public/icons/forms/radio.svg'
import { theme } from '~/styles'
import { tw } from '~/styles/utils'

import { RadioIcon } from './RadioIcon'

export interface RadioProps {
name?: string
Expand All @@ -20,7 +17,7 @@ export interface RadioProps {
onChange?: (value: string | number | boolean) => void
}

export const Radio = forwardRef<HTMLDivElement, RadioProps>(
export const Radio = forwardRef<HTMLLabelElement, RadioProps>(
(
{ name, checked, label, labelVariant, sublabel, disabled, value, onChange }: RadioProps,
ref,
Expand All @@ -31,39 +28,33 @@ export const Radio = forwardRef<HTMLDivElement, RadioProps>(
const [focused, setFocused] = useState(false)

return (
<Container
<label
ref={ref}
onClick={() => inputRef.current?.click()}
className={cx({
'radio--disabled': disabled,
'radio--focused': focused,
'radio--checked': checked,
'radio--unchecked': !checked,
})}
htmlFor={componentId}
className={tw('flex w-full items-start', !disabled && 'group/radio-icon cursor-pointer')}
>
<RadioContainer>
<div className="mr-3 flex items-start pt-1">
<input
readOnly
id={componentId}
ref={inputRef}
disabled={disabled}
aria-label={name}
name={name}
{...(isBoolean(value) ? { checked: value } : { value: value })}
type="radio"
onClick={() => onChange && onChange(value)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
className="absolute m-0 size-0 p-0 opacity-0"
/>
{checked ? (
<RadioCheckedIcon className="radio-icon" />
) : (
<RadioIcon className="radio-icon" />
)}
</RadioContainer>
<RadioLabelWrapper>
<RadioIcon checked={checked} disabled={disabled} focused={focused} />
</div>
<div className="w-full">
<Typography
variant={labelVariant || 'bodyHl'}
color={disabled ? 'disabled' : 'textSecondary'}
className={tw(!disabled && 'cursor-pointer')}
component={(labelProps) => <label htmlFor={componentId} {...labelProps} />}
>
{label}
Expand All @@ -76,81 +67,10 @@ export const Radio = forwardRef<HTMLDivElement, RadioProps>(
) : (
sublabel
))}
</RadioLabelWrapper>
</Container>
</div>
</label>
)
},
)

Radio.displayName = 'Radio'

const Container = styled.div`
width: 100%;
display: flex;
align-items: flex-start;
cursor: pointer;
> * {
cursor: pointer;
}
&.radio--checked {
.radio-coloured,
.radio-checked-coloured {
fill: ${theme.palette.primary[700]};
}
}
&.radio--disabled {
> * {
cursor: default;
}
.radio-coloured,
.radio-checked-coloured {
fill: ${theme.palette.grey[400]};
}
}
&.radio--focused .radio-icon {
box-shadow: 0px 0px 0px 4px ${theme.palette.primary[200]};
border-radius: 50%;
}
&:hover:not(.radio--disabled) {
&.radio--checked .radio-inner {
fill: ${theme.palette.primary[100]};
}
&.radio--unchecked .radio-inner {
fill: ${theme.palette.grey[200]};
}
}
&:active:not(.radio--disabled) {
&.radio--checked .radio-inner {
fill: ${theme.palette.primary[200]};
}
&.radio--unchecked .radio-inner {
fill: ${theme.palette.grey[300]};
}
}
`

const RadioContainer = styled.div`
margin-right: ${theme.spacing(3)};
display: flex;
align-items: flex-start;
padding-top: 4px;
input {
opacity: 0;
position: absolute;
width: 0;
height: 0;
margin: 0;
padding: 0;
}
`

const RadioLabelWrapper = styled.div`
width: 100%;
`
2 changes: 1 addition & 1 deletion src/components/form/Radio/RadioField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface RadioFieldProps extends Omit<RadioProps, 'checked' | 'name'> {
}

export const RadioField = memo(
forwardRef<HTMLInputElement, RadioFieldProps>(
forwardRef<HTMLLabelElement, RadioFieldProps>(
({ name, value, formikProps, ...props }: RadioFieldProps, ref) => {
const { values, setFieldValue } = formikProps

Expand Down
76 changes: 76 additions & 0 deletions src/components/form/Radio/RadioIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { FC } from 'react'

import { tw } from '~/styles/utils'

interface RadioIconProps {
focused?: boolean
disabled?: boolean
}

const RadioCheckedIcon: FC<RadioIconProps> = ({ focused, disabled }) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={tw(focused && 'rounded-full ring')}
>
<circle
className={tw('fill-blue-700', disabled && 'fill-grey-400')}
cx="8"
cy="8"
r="8"
fill="currentColor"
/>
<circle
className="group-hover/radio-icon:fill-blue-100 group-active/radio-icon:fill-blue-200"
cx="8"
cy="8"
r="7"
fill="white"
/>
<circle
className={tw('fill-blue-700', disabled && 'fill-grey-400')}
cx="8"
cy="8"
r="4"
fill="currentColor"
/>
</svg>
)
}

const RadioUncheckedIcon: FC<RadioIconProps> = ({ focused, disabled }) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={tw(focused && 'rounded-full ring')}
>
<circle className={tw(disabled && 'fill-grey-400')} cx="8" cy="8" r="8" fill="currentColor" />
<circle
className="group-hover/radio-icon:fill-grey-200 group-active/radio-icon:fill-grey-300"
cx="8"
cy="8"
r="7"
fill="white"
/>
</svg>
)
}

export const RadioIcon: FC<{ checked: boolean } & RadioIconProps> = ({
checked,
...radioIconProps
}) => {
return checked ? (
<RadioCheckedIcon {...radioIconProps} />
) : (
<RadioUncheckedIcon {...radioIconProps} />
)
}
5 changes: 0 additions & 5 deletions src/public/icons/forms/radio-checked.svg

This file was deleted.

4 changes: 0 additions & 4 deletions src/public/icons/forms/radio.svg

This file was deleted.

0 comments on commit a9fab15

Please sign in to comment.