UNPKG

@spark-web/field

Version:

--- title: Field isExperimentalPackage: false ---

205 lines (195 loc) 5.98 kB
import { createContext, useContext, forwardRef, useMemo, Fragment } from 'react'; import { css } from '@emotion/react'; import { mergeIds, VisuallyHidden, useId, composeId } from '@spark-web/a11y'; import { Box } from '@spark-web/box'; import { ExclamationCircleIcon, CheckCircleIcon } from '@spark-web/icon'; import { Stack } from '@spark-web/stack'; import { Text } from '@spark-web/text'; import { useTheme } from '@spark-web/theme'; import { jsxs, jsx } from '@emotion/react/jsx-runtime'; var FieldContext = /*#__PURE__*/createContext(null); var FieldContextProvider = FieldContext.Provider; var FIELD_CONTEXT_ERROR_MESSAGE = 'Input components must be inside a `Field`.'; function useFieldContext() { var ctx = useContext(FieldContext); if (!ctx) { throw new Error(FIELD_CONTEXT_ERROR_MESSAGE); } return ctx; } /** * Using a [context](https://reactjs.org/docs/context.html), the field * component connects the label, description, and message to the input element. */ var Field = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) { var children = _ref.children, idProp = _ref.id, data = _ref.data, description = _ref.description, _ref$disabled = _ref.disabled, disabled = _ref$disabled === void 0 ? false : _ref$disabled, label = _ref.label, adornment = _ref.adornment, _ref$labelVisibility = _ref.labelVisibility, labelVisibility = _ref$labelVisibility === void 0 ? 'visible' : _ref$labelVisibility, message = _ref.message, secondaryLabel = _ref.secondaryLabel, _ref$tone = _ref.tone, tone = _ref$tone === void 0 ? 'neutral' : _ref$tone, _ref$readOnly = _ref.readOnly, readOnly = _ref$readOnly === void 0 ? false : _ref$readOnly; var _useFieldIds = useFieldIds(idProp), descriptionId = _useFieldIds.descriptionId, inputId = _useFieldIds.inputId, messageId = _useFieldIds.messageId; // field context var invalid = Boolean(message && tone === 'critical'); var fieldContext = useMemo(function () { return [{ disabled: disabled, invalid: invalid, readOnly: readOnly }, { 'aria-describedby': mergeIds(message && messageId, description ? descriptionId : undefined), 'aria-invalid': invalid || undefined, id: inputId }]; }, [description, descriptionId, disabled, inputId, invalid, message, messageId, readOnly]); // label prep var hiddenLabel = jsxs(VisuallyHidden, { as: "label", htmlFor: inputId, children: [label, " ", secondaryLabel] }); var labelElement = { hidden: hiddenLabel, visible: jsx(Box, { as: "label", htmlFor: inputId, children: jsxs(Text, { tone: disabled || readOnly ? 'field' : 'neutral', weight: "semibold", children: [label, ' ', secondaryLabel && jsx(Text, { inline: true, tone: disabled || readOnly ? 'field' : 'muted', weight: "regular", children: secondaryLabel })] }) }), 'reserve-space': jsxs(Fragment, { children: [hiddenLabel, jsx(Text, { "aria-hidden": true, children: "\xA0" })] }) }; var LabelWrapper = labelVisibility === 'hidden' ? Fragment : FieldLabelWrapper; return jsx(FieldContextProvider, { value: fieldContext, children: jsxs(Stack, { ref: forwardedRef, data: data, gap: "medium", children: [jsxs(LabelWrapper, { children: [labelElement[labelVisibility], adornment] }), description && (typeof description === 'string' ? jsx(Text, { tone: "muted", size: "small", id: descriptionId, children: description }) : jsx(Box, { as: "label", htmlFor: descriptionId, children: description })), children, message && jsx(FieldMessage, { tone: tone, id: messageId, message: message })] }) }); }); Field.displayName = 'Field'; // Utils // ------------------------------ function useFieldIds(id) { var inputId = useId(id); var descriptionId = composeId(inputId, 'description'); var messageId = composeId(inputId, 'message'); return { descriptionId: descriptionId, inputId: inputId, messageId: messageId }; } // Styled components // ------------------------------ function FieldLabelWrapper(_ref2) { var children = _ref2.children; return jsx(Box, { display: "flex", alignItems: "center", justifyContent: "spaceBetween", gap: "large", children: children }); } var messageToneMap = { critical: 'critical', neutral: 'muted', positive: 'positive' }; // NOTE: use icons in addition to color for folks with visions issues var messageIconMap = { critical: ExclamationCircleIcon, neutral: null, positive: CheckCircleIcon }; var FieldMessage = function FieldMessage(_ref3) { var message = _ref3.message, id = _ref3.id, tone = _ref3.tone; var textTone = messageToneMap[tone]; var Icon = messageIconMap[tone]; return jsxs(Box, { display: "flex", gap: "xsmall", children: [Icon ? jsx(IndicatorContainer, { children: jsx(Icon, { size: "xxsmall", tone: tone }) }) : null, jsx(Text, { tone: textTone, size: "small", id: id, children: message })] }); }; function IndicatorContainer(_ref4) { var children = _ref4.children; var theme = useTheme(); var _theme$typography$tex = theme.typography.text.small, mobile = _theme$typography$tex.mobile, tablet = _theme$typography$tex.tablet; var responsiveStyles = theme.utils.responsiveStyles({ mobile: { height: mobile.capHeight }, tablet: { height: tablet.capHeight } }); return jsx(Box, { display: "flex", alignItems: "center", "aria-hidden": true, cursor: "default", flexShrink: 0, css: css(responsiveStyles), children: children }); } export { Field, FieldContextProvider, FieldMessage, useFieldContext, useFieldIds };