Last active
September 9, 2024 01:15
-
-
Save dimitur2204/b9ea018fa71bcb6f0948483f70ef0cb1 to your computer and use it in GitHub Desktop.
MUI Radio Group using MUI Card as buttons (also accessible <3)
This file contains hidden or 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
import React from 'react' | |
import { | |
Card, | |
CardProps, | |
FormControl, | |
FormControlLabel, | |
Radio, | |
RadioGroup, | |
RadioGroupProps, | |
Stack, | |
Unstable_Grid2 as Grid2, | |
} from '@mui/material' | |
import { styled, lighten } from '@mui/material/styles' | |
import { CardGiftcard, Check, Cyclone } from '@mui/icons-material' | |
// Theme is a MUI theme object with an added property of borders | |
// { borders: { semiRound: '4px' } } | |
import theme from 'common/theme' | |
export const StyledRadioCardItem = styled(Card)(() => ({ | |
padding: theme.spacing(2), | |
margin: 0, | |
cursor: 'pointer', | |
border: `1px solid ${theme.palette.primary.main}`, | |
width: '100%', | |
'&:focus-within': { | |
outline: `2px solid ${theme.palette.common.black}`, | |
}, | |
})) | |
interface StyledRadioCardItemProps extends CardProps { | |
control: React.ReactNode | |
icon: React.ReactNode | |
selected?: boolean | |
disabled?: boolean | |
} | |
// Temporarily here for testing until the components starts being used | |
export const testRadioOptions: Option[] = [ | |
{ | |
value: 'card', | |
label: 'Card', | |
icon: <Check sx={{ width: 80, height: 80 }} />, | |
}, | |
{ | |
value: 'bank', | |
label: 'Bank', | |
icon: <Cyclone sx={{ width: 80, height: 80 }} />, | |
}, | |
{ | |
value: 'paypal', | |
label: 'PayPal', | |
icon: <CardGiftcard sx={{ width: 80, height: 80 }} />, | |
}, | |
] | |
function RadioCardItem({ control, icon, selected, disabled, ...rest }: StyledRadioCardItemProps) { | |
const selectedStyles = { | |
backgroundColor: selected ? lighten(theme.palette.primary.light, 0.7) : 'inherit', | |
} | |
const disabledStyles = { | |
opacity: 0.7, | |
backgroundColor: `${theme.palette.grey[300]} !important`, | |
pointerEvents: 'none', | |
} | |
let styles = {} | |
if (disabled) { | |
styles = disabledStyles | |
} else if (selected) { | |
styles = selectedStyles | |
} | |
return ( | |
<StyledRadioCardItem | |
sx={styles} | |
{...rest}> | |
<Stack justifyContent="center" alignItems="center"> | |
{icon} | |
{control} | |
</Stack> | |
</StyledRadioCardItem> | |
) | |
} | |
type Option = { | |
value: string | |
label: string | |
icon: React.ReactNode | |
} | |
export interface RadioCardGroupProps extends RadioGroupProps { | |
options: Option[] | |
defaultValue?: string | |
} | |
function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { | |
const [value, setValue] = React.useState(defaultValue) | |
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |
setValue(event.target.value) | |
} | |
return ( | |
<FormControl> | |
<RadioGroup | |
aria-labelledby="TODO: Label by the title" | |
name="controlled-radio-buttons-group" | |
value={value} | |
onChange={handleChange}> | |
<Grid2 spacing={2} container> | |
{options.map((option) => ( | |
<Grid2 xs={4} key={option.value}> | |
<RadioCardItem | |
onClick={() => setValue(option.value)} | |
control={ | |
<FormControlLabel | |
value={option.value} | |
disableTypography | |
sx={{ margin: 0, ...theme.typography.h6 }} | |
control={ | |
<Radio | |
sx={{ opacity: 0, position: 'absolute', width: 0, height: 0 }} | |
inputProps={{ | |
style: { | |
width: 0, | |
height: 0, | |
}, | |
}} | |
/> | |
} | |
label={option.label} | |
/> | |
} | |
icon={option.icon} | |
selected={value === option.value} | |
/> | |
</Grid2> | |
))} | |
</Grid2> | |
</RadioGroup> | |
</FormControl> | |
) | |
} | |
export default RadioCardGroup |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment