UNPKG

@spark-web/radio

Version:

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

414 lines (382 loc) 13.7 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 css = require('@emotion/css'); var a11y = require('@spark-web/a11y'); var box = require('@spark-web/box'); var theme = require('@spark-web/theme'); var jsxRuntime = require('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"]; var RadioPrimitive = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { 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 /*#__PURE__*/jsxRuntime.jsxs(box.Box, { display: "flex", alignItems: "center", flexShrink: 0, className: css.css(responsiveStyles), children: [/*#__PURE__*/jsxRuntime.jsx(box.Box, _objectSpread(_objectSpread(_objectSpread({}, inputProps), boxProps), {}, { "aria-checked": inputProps.checked, "aria-disabled": inputProps.disabled, as: "input", className: css.css(radioStyles), ref: forwardedRef, type: "radio" })), /*#__PURE__*/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 size = _ref2.size; var theme$1 = theme.useTheme(); var focusRingStyles = a11y.useFocusRing(); var outerSize = sizeToScaleKey[size]; var innerSize = outerToInnerSize[outerSize]; var transitionProperties = useTransitionProperties(); return [{ border: 'field', borderRadius: 'full', background: 'surface', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flex: 1, height: outerSize, width: outerSize, position: 'relative', shadow: 'small' }, _objectSpread(_objectSpread({ appearance: 'none', verticalAlign: 'text-bottom' }, 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), // Show a border on the radio when the label is hovered. 'label:hover &:not([disabled], &[aria-disabled=true])': { borderColor: theme$1.border.color.primaryHover }, // Focus styles '&:focus': focusRingStyles, // Checked styles '&:checked': { background: theme$1.color.background.primary, borderColor: theme$1.border.color.primaryHover }, '&:checked::before': { background: theme$1.color.background.surface, borderRadius: theme$1.border.radius.full, height: innerSize, width: innerSize }, // 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: theme$1.backgroundInteractions.primaryHover, border: theme$1.border.color.fieldAccent }, // 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: theme$1.border.color.field, border: theme$1.border.color.accent }, '&[disabled]:checked::before, &[aria-disabled=true]:checked::before': { background: theme$1.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; 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 /*#__PURE__*/jsxRuntime.jsx(stack.Stack, { gap: "small", children: /*#__PURE__*/jsxRuntime.jsx(controlLabel.ControlLabel, { control: /*#__PURE__*/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", "disabled"]; var RadioCard = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) { var _ref2; var children = _ref.children, data = _ref.data, description = _ref.description, 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 /*#__PURE__*/jsxRuntime.jsx(stack.Stack, _objectSpread(_objectSpread({}, boxProps), {}, { as: "label", htmlFor: inputId, className: css.css(radioCardStyles), children: /*#__PURE__*/jsxRuntime.jsxs(box.Box, { alignItems: "start", display: "inline-flex", gap: size, children: [/*#__PURE__*/jsxRuntime.jsx(RadioPrimitive, _objectSpread(_objectSpread({}, inputProps), {}, { "aria-describedby": description ? descriptionId : undefined, "aria-labelledby": labelId, data: data, disabled: isDisabled, id: inputId, ref: forwardedRef, size: size })), /*#__PURE__*/jsxRuntime.jsxs(stack.Stack, { gap: "large", children: [/*#__PURE__*/jsxRuntime.jsx(text.DefaultTextPropsProvider, { tone: isDisabled ? 'disabled' : 'neutral', weight: description ? 'semibold' : 'regular', children: /*#__PURE__*/jsxRuntime.jsx(Content, { id: labelId, children: children }) }), description && /*#__PURE__*/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 /*#__PURE__*/jsxRuntime.jsx(text.Text, { id: id, children: children }); } return /*#__PURE__*/jsxRuntime.jsx(react.Fragment, { children: children }); } function useRadioCardStyles(isDisabled) { var theme$1 = theme.useTheme(); var focusRingStyles = a11y.useFocusRing(); var transitionProperties = useTransitionProperties(); return [// Box props to be spread onto label { background: isDisabled ? 'inputDisabled' : 'surface', cursor: isDisabled ? 'default' : 'pointer', padding: 'large', position: 'relative', userSelect: 'none' }, { // 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: theme$1.border.color.field, borderStyle: 'solid', borderWidth: theme$1.border.width.standard, borderRadius: theme$1.border.radius.small, 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: theme$1.border.color.primaryHover }, // Focus styles for card 'input[type=radio]:focus + [data-radio-border=true]': _objectSpread({ borderColor: theme$1.border.color.primary }, focusRingStyles), // 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 ? theme$1.border.color.field : theme$1.border.color.primary, borderWidth: theme$1.border.width.large } }]; } 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'], _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 /*#__PURE__*/jsxRuntime.jsxs(RadioGroupContext.Provider, { value: _objectSpread(_objectSpread({}, context), {}, { 'aria-describedby': message && messageId }), children: [/*#__PURE__*/jsxRuntime.jsx("div", { id: radioGroupId, role: "radiogroup", style: { display: 'contents' }, children: children }), message && /*#__PURE__*/jsxRuntime.jsx(field.FieldMessage, { tone: tone, id: messageId, message: message })] }); }; exports.Radio = Radio; exports.RadioCard = RadioCard; exports.RadioGroup = RadioGroup; exports.RadioPrimitive = RadioPrimitive;