/* * The MIT License (MIT) * * Copyright (c) 2015 - present Instructure, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { ComponentElement, Children, Component } from 'react' import { FormFieldGroup } from '@instructure/ui-form-field/latest' import { matchComponentTypes, safeCloneElement, omitProps, pickProps } from '@instructure/ui-react-utils' import { RadioInput } from '../../RadioInput/v2/index.js' import type { RadioInputProps } from '../../RadioInput/v2/props' import type { RadioInputGroupProps, RadioInputGroupState } from './props' import { allowedProps } from './props.js' type RadioInputChild = ComponentElement /** --- category: components --- **/ class RadioInputGroup extends Component< RadioInputGroupProps, RadioInputGroupState > { static displayName = 'RadioInputGroup' static readonly componentId = 'RadioInputGroup' static allowedProps = allowedProps static defaultProps = { disabled: false, variant: 'simple', size: 'medium', layout: 'stacked', readOnly: false } ref: Element | null = null handleRef = (el: Element | null) => { this.ref = el } constructor(props: RadioInputGroupProps) { super(props) if (typeof props.value === 'undefined') { this.state = { value: props.defaultValue } } } get invalid() { return !!this.props.messages?.find( (m) => m.type === 'newError' || m.type === 'error' ) } handleChange: RadioInputProps['onChange'] = (e) => { const value = e.target.value if (this.props.disabled || this.props.readOnly) { e.preventDefault() return } if (typeof this.props.value === 'undefined') { this.setState({ value }) } if (typeof this.props.onChange === 'function') { this.props.onChange(e, value) } } get value() { return typeof this.props.value === 'undefined' ? this.state.value : this.props.value } renderChildren(describedBy?: string) { const { children, name, variant, size, disabled, readOnly } = this.props // This adds the passed in name property to each RadioInput component // and checks the input whose value matches the value property return Children.map(children, (child, index) => { if (matchComponentTypes(child, [RadioInput])) { const isChecked = this.value === child.props.value const defaultFocus = !this.value && index === 0 return safeCloneElement(child, { name, disabled: disabled || child.props.disabled, variant, size, checked: isChecked, onChange: this.handleChange, readOnly: readOnly || child.props.readOnly, width: child.props.width || 'auto', 'aria-describedby': describedBy, // only one radio in a group should be considered tabbable // if a radio is checked, it should be the input to receive focus when tabbed to // if none of the inputs are checked, the first should receive the focus tabIndex: isChecked || defaultFocus ? 0 : -1, label: child.props.label }) } else { return child // ignore (but preserve) children that aren't RadioInput } }) } render() { const { variant, layout, description, isRequired } = this.props return ( {({ describedBy }) => this.renderChildren(describedBy)} ) } } export default RadioInputGroup export { RadioInputGroup }