import { Accessibility, radioGroupItemBehavior, RadioGroupItemBehaviorProps } from '@fluentui/accessibility'; import { Ref } from '@fluentui/react-component-ref'; import * as customPropTypes from '@fluentui/react-proptypes'; import * as React from 'react'; import * as PropTypes from 'prop-types'; import * as _ from 'lodash'; import { createShorthandFactory, UIComponentProps, ChildrenComponentProps, commonPropTypes } from '../../utils'; import { Box, BoxProps } from '../Box/Box'; import { ComponentEventHandler, ShorthandValue, FluentComponentStaticProps } from '../../types'; import { ComponentWithAs, useAutoControlled, getElementType, useAccessibility, useFluentContext, useStyles, useTelemetry, useUnhandledProps, } from '@fluentui/react-bindings'; export interface RadioGroupItemSlotClassNames { indicator: string; } export interface RadioGroupItemProps extends UIComponentProps, ChildrenComponentProps { /** Accessibility behavior if overridden by the user. */ accessibility?: Accessibility; /** Whether or not radio item is checked. */ checked?: boolean; /** * Called after radio item checked state is changed. * @param event - React's original SyntheticEvent. * @param data - All props. */ onChange?: ComponentEventHandler; /** The label of the radio item. */ label?: ShorthandValue; /** Initial checked value. */ defaultChecked?: boolean; /** A radio item can appear disabled and be unable to change states. */ disabled?: boolean; /** The radio item indicator can be customized. */ indicator?: ShorthandValue; /** The HTML input name. */ name?: string; /** * Called after radio item is clicked. * @param event - React's original SyntheticEvent. * @param data - All props. */ onClick?: ComponentEventHandler; /** Whether should focus when checked */ shouldFocus?: boolean; // TODO: RFC #306 /** The HTML input value. */ value?: string | number; /** A vertical radio group displays elements vertically. */ vertical?: boolean; } export const radioGroupItemClassName = 'ui-radiogroup__item'; export const radioGroupItemSlotClassNames: RadioGroupItemSlotClassNames = { indicator: `${radioGroupItemClassName}__indicator`, }; export type RadioGroupItemStylesProps = Required>; /** * A RadioGroupItem represents single input element within a RadioGroup. * * @accessibility * Radio items need to be grouped to correctly handle accessibility. */ export const RadioGroupItem: ComponentWithAs<'div', RadioGroupItemProps> & FluentComponentStaticProps = props => { const context = useFluentContext(); const { setStart, setEnd } = useTelemetry(RadioGroupItem.displayName, context.telemetry); setStart(); const { label, indicator, disabled, vertical, className, design, styles, variables, shouldFocus } = props; const elementRef = React.useRef(); const ElementType = getElementType(props); const unhandledProps = useUnhandledProps(RadioGroupItem.handledProps, props); const [checked, setChecked] = useAutoControlled({ defaultValue: props.defaultChecked, value: props.checked, initialValue: false, }); const handleClick = (e: React.MouseEvent | React.KeyboardEvent) => { _.invoke(props, 'onClick', e, props); setChecked(prevChecked => { _.invoke(props, 'onChange', undefined, { ...props, checked: !prevChecked }); return !prevChecked; }); }; React.useEffect(() => { if (checked && shouldFocus) elementRef.current.focus(); }, [checked, shouldFocus]); const { classes, styles: resolvedStyles } = useStyles(RadioGroupItem.displayName, { className: radioGroupItemClassName, mapPropsToStyles: () => ({ vertical, disabled, checked, }), mapPropsToInlineStyles: () => ({ className, design, styles, variables, }), rtl: context.rtl, }); const getA11yProps = useAccessibility(props.accessibility, { debugName: RadioGroupItem.displayName, actionHandlers: { performClick: e => { e.preventDefault(); handleClick(e); }, }, mapPropsToBehavior: () => ({ checked, disabled, }), rtl: context.rtl, }); const handleChange = (e: React.ChangeEvent) => { // RadioGroupItem component doesn't present any `input` component in markup, however all of our // components should handle events transparently. _.invoke(props, 'onChange', e, { ...props, checked }); }; const element = getA11yProps.unstable_wrapWithFocusZone( {Box.create(indicator, { defaultProps: () => ({ className: radioGroupItemSlotClassNames.indicator, styles: resolvedStyles.indicator, }), })} {Box.create(label, { defaultProps: () => ({ as: 'span', }), })} , ); setEnd(); return element; }; RadioGroupItem.displayName = 'RadioGroupItem'; RadioGroupItem.propTypes = { ...commonPropTypes.createCommon({ content: false, }), checked: PropTypes.bool, defaultChecked: PropTypes.bool, disabled: PropTypes.bool, indicator: customPropTypes.shorthandAllowingChildren, label: customPropTypes.itemShorthand, name: PropTypes.string, onClick: PropTypes.func, onChange: PropTypes.func, shouldFocus: PropTypes.bool, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), vertical: PropTypes.bool, }; RadioGroupItem.defaultProps = { accessibility: radioGroupItemBehavior, indicator: {}, }; RadioGroupItem.handledProps = Object.keys(RadioGroupItem.propTypes) as any; RadioGroupItem.create = createShorthandFactory({ Component: RadioGroupItem, mappedProp: 'label' });