Created
October 8, 2022 21:31
-
-
Save AlexMachin1997/ae50f9b5ef1a918fad0a1eb0e398d9f0 to your computer and use it in GitHub Desktop.
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
import * as React from 'react'; | |
import PropTypes from 'prop-types'; | |
import classNames from 'classnames'; | |
import { useVirtualizer } from '@tanstack/react-virtual'; | |
import { RadioGroup } from '@headlessui/react'; | |
import Icon from '../Icon/Icon'; | |
import CountryFlag from '../../CountryFlag/CountryFlag'; | |
const CustomRadioGroup = React.memo( | |
({ | |
options, | |
value, | |
onChange, | |
displayName, | |
noOptionsAvailableMessage, | |
disabled, | |
showRadioButtonOnTheLeft, | |
addSpaceBetweenLabelAndRadioButton, | |
name, | |
defaultValue, | |
getRadioLabelClassName, | |
getRadioOptionClassName, | |
iconComponent: IconComponent, | |
getIconComponentClassName | |
}) => { | |
const optionsRef = React.useRef(); | |
const rowVirtualizer = useVirtualizer({ | |
count: options?.length ?? 0, | |
getScrollElement: () => optionsRef.current, | |
estimateSize: React.useCallback(() => 35, []), | |
overscan: 5 | |
}); | |
return ( | |
<div className='w-full'> | |
<div className='mx-auto w-full' ref={optionsRef}> | |
{rowVirtualizer.getVirtualItems().length === 0 && ( | |
<p className='relative cursor-default select-none py-2 text-gray-700'> | |
{noOptionsAvailableMessage} | |
</p> | |
)} | |
{rowVirtualizer.getVirtualItems().length > 0 && ( | |
<div | |
style={{ | |
height: `${rowVirtualizer.getTotalSize()}px`, | |
width: '100%', | |
position: 'relative' | |
}} | |
> | |
<RadioGroup | |
// Used when using the "uncontrolled" component ie using your own form management solution with state () | |
value={value} | |
onChange={(newValue) => { | |
if (onChange) { | |
onChange({ value: newValue }); | |
} | |
}} | |
// Used when using the "controlled" component ie using the native html form formData object | |
name={name} | |
defaultValue={defaultValue} | |
// Other general properties made available to the component | |
disabled={disabled} | |
> | |
{rowVirtualizer.getVirtualItems().map((virtualRow) => { | |
const option = options[virtualRow.index]; | |
return ( | |
<RadioGroup.Option | |
key={option.id} | |
ref={virtualRow.measureElement} | |
value={option.value} | |
className={({ checked, active }) => { | |
// If the getRadioOptionClassName function is passed used the value returned from that otherwise use the default | |
if (getRadioOptionClassName) { | |
return getRadioOptionClassName({ | |
isChecked: checked, | |
isActive: active, | |
isDisabled: disabled | |
}); | |
} | |
// If the getRadioOptionClassName wasn't passed just use the default classNames for the option | |
return classNames( | |
'relative flex cursor-pointer rounded-lg border border-solid border-gray-300 px-5 py-4 shadow-md focus:outline-none', | |
{ | |
'ring-2 ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-300': | |
active === true, | |
'bg-sky-900/75 text-white': checked === true, | |
'bg-white': checked === false, | |
'cursor-not-allowed': disabled === true | |
} | |
); | |
}} | |
disabled={option.disabled} | |
style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
width: '100%', | |
transform: `translateY(${virtualRow.start}px)` | |
}} | |
> | |
{({ checked }) => { | |
let generatedIconClassName = classNames('fa-regular', { | |
'text-white': checked === true, | |
'text-gray-300': checked === false, | |
'fa-circle-dot': checked === true, | |
'fa-circle': checked === false | |
}); | |
if (getIconComponentClassName) { | |
generatedIconClassName = getIconComponentClassName({ | |
isChecked: checked, | |
option, | |
isRadioGroupDisabled: disabled | |
}); | |
} | |
return ( | |
<div | |
className={classNames('flex w-full items-center', { | |
'justify-between': addSpaceBetweenLabelAndRadioButton === true | |
})} | |
> | |
{showRadioButtonOnTheLeft === true && ( | |
<CountryFlag countryCode={option?.value ?? ''} /> | |
)} | |
<div className='flex items-center'> | |
<div className='text-sm'> | |
<RadioGroup.Label | |
as='p' | |
className={() => { | |
// If the getRadioLabelClassName function is passed used the value returned from that otherwise use the default | |
if (getRadioLabelClassName) { | |
return getRadioLabelClassName({ isChecked: checked }); | |
} | |
// If the getRadioLabelClassName wasn't passed just use the default classNames for the option label | |
return classNames('font-medium', { | |
'text-white': checked === true, | |
'text-gray-900': checked === false | |
}); | |
}} | |
> | |
{option[displayName]} | |
</RadioGroup.Label> | |
</div> | |
</div> | |
{showRadioButtonOnTheLeft === false && ( | |
<span className='ml-3'> | |
<IconComponent | |
isChecked={checked} | |
option={option} | |
className={generatedIconClassName} | |
/> | |
</span> | |
)} | |
</div> | |
); | |
}} | |
</RadioGroup.Option> | |
); | |
})} | |
</RadioGroup> | |
</div> | |
)} | |
</div> | |
</div> | |
); | |
} | |
); | |
CustomRadioGroup.propTypes = { | |
// These are our controlled properties, these values are used to control the value manually via some state | |
value: PropTypes.string, | |
onChange: PropTypes.func, | |
// These are our uncontrolled properties, these values are used when there is no state or onChange, instead the component controls it's own state | |
defaultValue: PropTypes.string, | |
// General properties to provide additional functionality options, displayName etc | |
name: PropTypes.string, | |
options: PropTypes.array, | |
displayName: PropTypes.string, | |
noOptionsAvailableMessage: PropTypes.string, | |
disabled: PropTypes.bool, | |
showRadioButtonOnTheLeft: PropTypes.bool, | |
addSpaceBetweenLabelAndRadioButton: PropTypes.bool, | |
getRadioLabelClassName: PropTypes.func, | |
getRadioOptionClassName: PropTypes.func, | |
iconComponent: PropTypes.node, | |
getIconComponentClassName: PropTypes.func | |
}; | |
CustomRadioGroup.defaultProps = { | |
// These are our controlled properties, these values are used to control the value manually via some state | |
value: undefined, | |
onChange: null, | |
// These are our uncontrolled properties, these values are used when there is no state or onChange, instead the component controls it's own state | |
defaultValue: undefined, | |
// General properties to provide additional functionality options, displayName etc | |
name: 'name', | |
options: [], | |
displayName: 'name', | |
noOptionsAvailableMessage: 'No options currently available.', | |
disabled: false, | |
showRadioButtonOnTheLeft: true, | |
addSpaceBetweenLabelAndRadioButton: false, | |
getRadioLabelClassName: null, | |
getRadioOptionClassName: null, | |
iconComponent: Icon, | |
getIconComponentClassName: null | |
}; | |
export default CustomRadioGroup; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment