@spark-web/radio
Version:
--- title: Radio storybookPath: forms-radio--default isExperimentalPackage: true ---
439 lines (428 loc) • 16.1 kB
JavaScript
'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;