UNPKG

@spark-web/field

Version:

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

199 lines (183 loc) 5.93 kB
import { createContext, useContext, forwardRef, useMemo, Fragment } from 'react'; import { css } from '@emotion/css'; import { mergeIds, useId, composeId, VisuallyHidden } 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 { jsx, jsxs } from '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; 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 }, { 'aria-describedby': mergeIds(message && messageId, description && descriptionId), 'aria-invalid': invalid || undefined, id: inputId }]; }, [description, descriptionId, disabled, inputId, invalid, message, messageId]); // label prep var hiddenLabel = /*#__PURE__*/jsxs(VisuallyHidden, { as: "label", htmlFor: inputId, children: [label, " ", secondaryLabel] }); var labelElement = { hidden: hiddenLabel, visible: /*#__PURE__*/jsx(Box, { as: "label", htmlFor: inputId, children: /*#__PURE__*/jsxs(Text, { tone: disabled ? 'disabled' : 'neutral', weight: "semibold", children: [label, ' ', secondaryLabel && /*#__PURE__*/jsx(Text, { inline: true, tone: disabled ? 'disabled' : 'muted', weight: "regular", children: secondaryLabel })] }) }), 'reserve-space': /*#__PURE__*/jsxs(Fragment, { children: [hiddenLabel, /*#__PURE__*/jsx(Text, { "aria-hidden": true, children: "\xA0" })] }) }; var LabelWrapper = labelVisibility === 'hidden' ? Fragment : FieldLabelWrapper; return /*#__PURE__*/jsx(FieldContextProvider, { value: fieldContext, children: /*#__PURE__*/jsxs(Stack, { ref: forwardedRef, data: data, gap: "medium", children: [/*#__PURE__*/jsxs(LabelWrapper, { children: [labelElement[labelVisibility], adornment] }), description && /*#__PURE__*/jsx(Text, { tone: "muted", size: "small", id: descriptionId, children: description }), children, message && /*#__PURE__*/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 /*#__PURE__*/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 /*#__PURE__*/jsxs(Box, { display: "flex", gap: "xsmall", children: [Icon ? /*#__PURE__*/jsx(IndicatorContainer, { children: /*#__PURE__*/jsx(Icon, { size: "xxsmall", tone: tone }) }) : null, /*#__PURE__*/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 /*#__PURE__*/jsx(Box, { display: "flex", alignItems: "center", "aria-hidden": true, cursor: "default", flexShrink: 0, className: css(responsiveStyles), children: children }); } export { Field, FieldContextProvider, FieldMessage, useFieldContext, useFieldIds };