@spark-web/field
Version:
--- title: Field isExperimentalPackage: false ---
205 lines (195 loc) • 5.98 kB
JavaScript
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 };