UNPKG

@spark-web/radio

Version:

--- title: Radio storybookPath: forms-radio--default isExperimentalPackage: true ---

439 lines (428 loc) 16.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties'); var controlLabel = require('@spark-web/control-label'); var stack = require('@spark-web/stack'); var react = require('react'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var react$1 = require('@emotion/react'); var a11y = require('@spark-web/a11y'); var box = require('@spark-web/box'); var theme = require('@spark-web/theme'); var lodash = require('lodash'); var jsxRuntime = require('@emotion/react/jsx-runtime'); var text = require('@spark-web/text'); var field = require('@spark-web/field'); var RadioGroupContext = /*#__PURE__*/react.createContext(undefined); var useRadioGroupContext = function useRadioGroupContext() { return react.useContext(RadioGroupContext); }; var _excluded$2 = ["size"], _excluded2$1 = ["checked"]; var RadioPrimitive = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { var _inputProps$value; var _ref$size = _ref.size, size = _ref$size === void 0 ? 'small' : _ref$size, inputProps = _objectWithoutProperties(_ref, _excluded$2); var theme$1 = theme.useTheme(); var responsiveStyles = theme$1.utils.responsiveStyles({ mobile: { height: theme$1.typography.text.small.mobile.capHeight }, tablet: { height: theme$1.typography.text.small.tablet.capHeight } }); var _useRadioStyles = useRadioStyles({ size: size }), _useRadioStyles2 = _slicedToArray(_useRadioStyles, 2), boxProps = _useRadioStyles2[0], radioStyles = _useRadioStyles2[1]; return jsxRuntime.jsxs(box.Box, { display: "flex", alignItems: "center", flexShrink: 0, css: react$1.css(responsiveStyles), children: [jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread(_objectSpread({}, inputProps), boxProps), {}, { "aria-checked": inputProps.checked, "aria-disabled": inputProps.disabled, as: "input", css: react$1.css(radioStyles), ref: forwardedRef, type: "radio", value: (_inputProps$value = inputProps.value) === null || _inputProps$value === void 0 ? void 0 : _inputProps$value.toString() })), jsxRuntime.jsx("span", { "aria-hidden": true, "data-radio-border": "true" })] }); }); RadioPrimitive.displayName = 'RadioPrimitive'; var sizeToScaleKey = { small: 'xxsmall', medium: 'xsmall' }; var outerToInnerSize = { xxsmall: 6, xsmall: 9 }; function useTransitionProperties() { var theme$1 = theme.useTheme(); return { transitionProperty: 'color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter', transitionTimingFunction: theme$1.animation.standard.easing, transitionDuration: "".concat(theme$1.animation.standard.duration, "ms") }; } /** * Returns a tuple where the first item is an object of props to spread onto the * underlying Box component that our inputs are created with, and the second * item is a CSS object to be passed to Emotion's `css` function */ function useRadioStyles(_ref2) { var _focusChecked$border, _focusChecked$border2; var size = _ref2.size; var theme$1 = theme.useTheme(); var focusRingStyles = a11y.useFocusRing(); var radioTheme = theme$1.components.radio; var _ref3 = radioTheme.focus || {}, focusChecked = _ref3.checked, focus = _objectWithoutProperties(_ref3, _excluded2$1); var outerSize = sizeToScaleKey[size]; var innerSize = outerToInnerSize[outerSize]; var transitionProperties = useTransitionProperties(); return [{ border: radioTheme.border["default"], borderRadius: radioTheme.border.radius.full, background: radioTheme.background, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: 1, height: outerSize, width: outerSize, position: 'relative', shadow: 'small' }, _objectSpread(_objectSpread({ appearance: 'none', verticalAlign: 'text-bottom', borderWidth: radioTheme.border.width }, transitionProperties), {}, { // Inner circle of radio '&::before': _objectSpread({ content: '""', position: 'absolute', margin: 'auto', top: 0, right: 0, bottom: 0, left: 0, height: 0, width: 0, overflow: 'hidden' }, transitionProperties), // Focus styles '&:focus': _objectSpread({}, !lodash.isEmpty(focus) ? focus : focusRingStyles), // Checked styles '&:checked': { background: radioTheme.color.background.primary, borderColor: radioTheme.border.color.primary, '&:focus': focusChecked ? { borderWidth: focusChecked.border.width, borderColor: (_focusChecked$border = focusChecked.border) === null || _focusChecked$border === void 0 ? void 0 : _focusChecked$border.color } : undefined }, '&:checked::before': { background: radioTheme.color.background.surface, borderRadius: radioTheme.border.radius.pseudo, height: innerSize, width: innerSize }, '&:hover::before': { background: radioTheme.backgroundInteractions.backgroundColorHover }, // Show a border on the radio when the label is hovered. 'label:hover &:not([disabled], &[aria-disabled=true])': { borderColor: radioTheme.border.color.primary }, // Hover styles when checked 'label:hover &:not([disabled], &[aria-disabled=true]):checked': { // TODO: radio gets lighter on hover instead of darker like in the designs, will fix once tokens are revised background: radioTheme.backgroundInteractions.primaryHover, borderColor: radioTheme.backgroundInteractions.borderColor, borderWidth: radioTheme.backgroundInteractions.borderWidthHover, '&:focus': focusChecked ? { borderWidth: focusChecked.border.width, borderColor: (_focusChecked$border2 = focusChecked.border) === null || _focusChecked$border2 === void 0 ? void 0 : _focusChecked$border2.color } : undefined }, // Disabled styles when checked '&[disabled]:checked, &[aria-disabled=true]:checked': { // TODO: using a `border` colour for background here as we don't have a token for it just yet background: radioTheme.border.color.field, border: radioTheme.border.color.accent }, '&[disabled]:checked::before, &[aria-disabled=true]:checked::before': { background: radioTheme.color.background.fieldAccent } })]; } function useRadioGroupState(props) { var _props$name; var name = a11y.useId(props.name); return { disabled: props.disabled, name: (_props$name = props.name) !== null && _props$name !== void 0 ? _props$name : name, onChange: function onChange(event) { if (props.disabled) { return; } var inputValue = event.target.value; // event.target.value always returns string, convert to boolean if value is originally boolean type or initially undefined. if (typeof props.value === 'boolean' || typeof props.value === 'undefined') { if (inputValue === 'true') { inputValue = true; } if (inputValue === 'false') { inputValue = false; } } props.onChange(inputValue); }, size: props.size, value: props.value, 'aria-describedby': props['aria-describedby'] }; } function useRadioGroupItem(_ref) { var props = _ref.props, state = _ref.state; if (typeof state === 'undefined') { return undefined; } var radioValue = props.value; if (typeof radioValue === 'undefined') { throw new Error('Each <Radio> within a <RadioGroup> requires a `value` property.'); } return { checked: state.value === radioValue, disabled: state.disabled, name: state.name, onChange: state.onChange, size: state.size, value: radioValue, 'aria-describedby': state['aria-describedby'] }; } var _excluded$1 = ["children", "data", "disabled", "id", "size"]; var Radio = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { var _ref2; var children = _ref.children, data = _ref.data, disabled = _ref.disabled, id = _ref.id, sizeProp = _ref.size, consumerProps = _objectWithoutProperties(_ref, _excluded$1); var groupState = useRadioGroupContext(); var radioGroupItemProps = useRadioGroupItem({ props: consumerProps, state: groupState }); var inputProps = typeof groupState === 'undefined' ? consumerProps : radioGroupItemProps; var isDisabled = (_ref2 = disabled !== null && disabled !== void 0 ? disabled : groupState === null || groupState === void 0 ? void 0 : groupState.disabled) !== null && _ref2 !== void 0 ? _ref2 : false; var size = sizeProp !== null && sizeProp !== void 0 ? sizeProp : groupState === null || groupState === void 0 ? void 0 : groupState.size; return jsxRuntime.jsx(stack.Stack, { gap: "small", children: jsxRuntime.jsx(controlLabel.ControlLabel, { control: jsxRuntime.jsx(RadioPrimitive, _objectSpread(_objectSpread({}, inputProps), {}, { data: data, ref: forwardedRef, disabled: isDisabled, size: size, id: id })), disabled: isDisabled, htmlFor: id, size: size !== null && size !== void 0 ? size : 'small', children: children }) }); }); Radio.displayName = 'Radio'; var _excluded = ["children", "data", "description", "padding", "disabled"], _excluded2 = ["checked"]; var RadioCard = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { var _ref2; var children = _ref.children, data = _ref.data, description = _ref.description, _ref$padding = _ref.padding, padding = _ref$padding === void 0 ? 'none' : _ref$padding, disabled = _ref.disabled, consumerProps = _objectWithoutProperties(_ref, _excluded); var groupState = useRadioGroupContext(); var radioGroupItemProps = useRadioGroupItem({ props: consumerProps, state: groupState }); var inputProps = typeof groupState === 'undefined' ? consumerProps : radioGroupItemProps; var isDisabled = (_ref2 = disabled !== null && disabled !== void 0 ? disabled : groupState === null || groupState === void 0 ? void 0 : groupState.disabled) !== null && _ref2 !== void 0 ? _ref2 : false; var size = 'small'; var _useRadioCardStyles = useRadioCardStyles(isDisabled), _useRadioCardStyles2 = _slicedToArray(_useRadioCardStyles, 2), boxProps = _useRadioCardStyles2[0], radioCardStyles = _useRadioCardStyles2[1]; var inputId = a11y.useId(); var labelId = a11y.composeId(inputId, 'label'); var descriptionId = a11y.composeId(inputId, 'description'); return jsxRuntime.jsx(stack.Stack, _objectSpread(_objectSpread({}, boxProps), {}, { as: "label", htmlFor: inputId, css: react$1.css(radioCardStyles), children: jsxRuntime.jsxs(box.Box, { alignItems: "start", display: "inline-flex", gap: size, padding: padding, children: [jsxRuntime.jsx(RadioPrimitive, _objectSpread(_objectSpread({}, inputProps), {}, { "aria-describedby": description ? descriptionId : undefined, "aria-labelledby": labelId, data: data, disabled: isDisabled, id: inputId, ref: forwardedRef, size: size })), jsxRuntime.jsxs(stack.Stack, { gap: "large", children: [jsxRuntime.jsx(text.DefaultTextPropsProvider, { tone: isDisabled ? 'disabled' : 'neutral', weight: description ? 'semibold' : 'regular', children: jsxRuntime.jsx(Content, { id: labelId, children: children }) }), description && jsxRuntime.jsx(text.Text, { id: descriptionId, tone: isDisabled ? 'disabled' : 'muted', children: description })] })] }) })); }); RadioCard.displayName = 'RadioCard'; function Content(_ref3) { var children = _ref3.children, id = _ref3.id; if (typeof children === 'string' || typeof children === 'number') { return jsxRuntime.jsx(text.Text, { id: id, children: children }); } return jsxRuntime.jsx(react.Fragment, { children: children }); } function useRadioCardStyles(isDisabled) { var _radioTheme$radioCard, _radioTheme$radioCard2; var focusRingStyles = a11y.useFocusRing(); var transitionProperties = useTransitionProperties(); var theme$1 = theme.useTheme(); var radioTheme = theme$1.components.radio; var _ref4 = radioTheme.focus || {}; _ref4.checked; var focus = _objectWithoutProperties(_ref4, _excluded2); return [ // Box props to be spread onto label { background: isDisabled ? 'inputDisabled' : 'surface', cursor: isDisabled ? 'default' : 'pointer', padding: 'large', position: 'relative', userSelect: 'none', height: 'full' }, { // Simulates a border for the radio card 'input[type=radio] + [data-radio-border=true]': _objectSpread({ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, borderColor: radioTheme.border.color.field, borderStyle: 'solid', borderWidth: radioTheme.radioCardBorder.border.width, borderRadius: radioTheme.radioCardBorder.border.radius, pointerEvents: 'none' }, transitionProperties), // Change border color of card on hover (when not disabled) ':hover input:not([disabled], [aria-disabled=true]) + [data-radio-border=true]': { borderColor: radioTheme.backgroundInteractions.borderColorHover, boxShadow: (_radioTheme$radioCard = radioTheme.radioCardBorder.hover) !== null && _radioTheme$radioCard !== void 0 && _radioTheme$radioCard.boxShadow ? (_radioTheme$radioCard2 = radioTheme.radioCardBorder) === null || _radioTheme$radioCard2 === void 0 || (_radioTheme$radioCard2 = _radioTheme$radioCard2.hover) === null || _radioTheme$radioCard2 === void 0 ? void 0 : _radioTheme$radioCard2.boxShadow : 'none' }, // Remove focus ring on radio (as it is on the whole card) 'input[type=radio]:focus': { boxShadow: 'none' }, // Border style for card when checked 'input[type=radio]:checked + [data-radio-border=true]': { borderColor: isDisabled ? radioTheme.border.color.field : radioTheme.border.color.primary, // TO DO: Fix this -> should be 2 from token borderWidth: radioTheme.backgroundInteractions.borderWidthChecked }, // Focus styles for card 'input[type=radio]:focus + [data-radio-border=true]': _objectSpread({ borderColor: radioTheme.backgroundInteractions.borderColorFocus }, !lodash.isEmpty(focus) ? focus : focusRingStyles) }]; } var RadioGroup = function RadioGroup(_ref) { var children = _ref.children, disabled = _ref.disabled, name = _ref.name, onChange = _ref.onChange, size = _ref.size, value = _ref.value, message = _ref.message, idProp = _ref.id, ariaDescribedBy = _ref['aria-describedby'], ariaLabel = _ref['aria-label'], _ref$tone = _ref.tone, tone = _ref$tone === void 0 ? 'neutral' : _ref$tone; var context = useRadioGroupState({ disabled: disabled, name: name, onChange: onChange, size: size, value: value, 'aria-describedby': ariaDescribedBy }); var _useFieldIds = field.useFieldIds(idProp), radioGroupId = _useFieldIds.inputId, messageId = _useFieldIds.messageId; return jsxRuntime.jsxs(RadioGroupContext.Provider, { value: _objectSpread(_objectSpread({}, context), {}, { 'aria-describedby': message && messageId }), children: [jsxRuntime.jsx("div", { id: radioGroupId, role: "radiogroup", style: { display: 'contents' }, "aria-label": ariaLabel, children: children }), message && jsxRuntime.jsx(field.FieldMessage, { tone: tone, id: messageId, message: message })] }); }; exports.Radio = Radio; exports.RadioCard = RadioCard; exports.RadioGroup = RadioGroup; exports.RadioPrimitive = RadioPrimitive;