UNPKG

@spark-web/text-input

Version:

--- title: Text Input storybookPath: forms-textinput--default isExperimentalPackage: false ---

274 lines (266 loc) 8.86 kB
import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2'; import { Box } from '@spark-web/box'; import { useFieldContext, FieldContextProvider } from '@spark-web/field'; import { useTheme } from '@spark-web/theme'; import { useMemo, createContext, Children, isValidElement, forwardRef } from 'react'; import { jsx, jsxs } from '@emotion/react/jsx-runtime'; import _slicedToArray from '@babel/runtime/helpers/esm/slicedToArray'; import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties'; import { css } from '@emotion/react'; import { useFocusRing } from '@spark-web/a11y'; import { useOverflowStrategy, useText } from '@spark-web/text'; /** * Components like the `SelectInput` may subscribe to the adornment context and * change their appearance or behaviour. */ var InputAdornmentContext = /*#__PURE__*/createContext(null); var placementToPadding = { start: { paddingLeft: 'medium', paddingRight: 'xsmall' }, end: { paddingLeft: 'xsmall', paddingRight: 'medium' } }; /** * Places an element at the "start" or "end" of the input, only one adornment * may be provided for each placement. By default, the adornment element will be * wrapped to provide alignment and spacing, use the "raw" property to opt-out * of this behaviour. * * @example * <TextInput> * <InputAdornment placement="start"> * <Text tone="placeholder">$</Text> * </InputAdornment> * </TextInput> */ var InputAdornment = function InputAdornment(_ref) { var children = _ref.children, fieldLabel = _ref.fieldLabel, placement = _ref.placement, raw = _ref.raw; var theme = useTheme(); var adornmentContext = useMemo(function () { return { placement: placement }; }, [placement]); var _placementToPadding$p = placementToPadding[placement], paddingLeft = _placementToPadding$p.paddingLeft, paddingRight = _placementToPadding$p.paddingRight; var content = children; if (!raw) { content = jsx(Box, { paddingLeft: paddingLeft, paddingRight: paddingRight, children: jsx(Box, { display: "flex", alignItems: "center", justifyContent: "center", style: { minWidth: theme.sizing.xxsmall }, children: children }) }); } var wrappedContent = jsx(InputAdornmentContext.Provider, { value: adornmentContext, children: content }); if (fieldLabel) { return jsx(FieldAdornment, { fieldLabel: fieldLabel, children: wrappedContent }); } return wrappedContent; }; /** * Wrap the element with a field provider to override the parent field label. * Only split-out from `InputAdornment` to avoid the conditional hook rule. */ var FieldAdornment = function FieldAdornment(_ref2) { var children = _ref2.children, fieldLabel = _ref2.fieldLabel; var parentFieldContext = useFieldContext(); var fieldContext = useMemo(function () { return _objectSpread(_objectSpread({}, parentFieldContext), {}, { accessibilityLabel: fieldLabel }); }, [fieldLabel, parentFieldContext]); return jsx(FieldContextProvider, { value: fieldContext, children: children }); }; var _excluded$1 = ["children", "startAdornment", "endAdornment"]; var InputContainer = function InputContainer(_ref) { var children = _ref.children, startAdornment = _ref.startAdornment, endAdornment = _ref.endAdornment, boxProps = _objectWithoutProperties(_ref, _excluded$1); var _useFieldContext = useFieldContext(), _useFieldContext2 = _slicedToArray(_useFieldContext, 1), _useFieldContext2$ = _useFieldContext2[0], disabled = _useFieldContext2$.disabled, invalid = _useFieldContext2$.invalid, readOnly = _useFieldContext2$.readOnly; return jsxs(Box, _objectSpread(_objectSpread({ borderRadius: "small", position: "relative", background: disabled || readOnly ? 'inputDisabled' : 'input' }, boxProps), {}, { children: [startAdornment, children, jsx(FocusIndicator, { invalid: invalid }), endAdornment] })); }; var FocusIndicator = function FocusIndicator(_ref2) { var invalid = _ref2.invalid; var theme = useTheme(); return jsx(Box, { "aria-hidden": "true", as: "span", data: { 'focus-indicator': 'true' } // Styles , border: invalid ? 'critical' : theme.components.textInput.borderColor, borderRadius: "small", position: "absolute", bottom: 0, left: 0, right: 0, top: 0, shadow: "small", css: css({ pointerEvents: 'none' }) }); }; // NOTE: `null | undefined` allow consumers to conditionally render adornments /** * Map children for placement within the `TextInput` flex container. Ensures that * placeholders are provided for unused placements. */ var childrenToAdornments = function childrenToAdornments(children) { var startAdornment = null; var endAdornment = null; if (!children) { return { startAdornment: startAdornment, endAdornment: endAdornment }; } Children.forEach(children, function (child) { if ( /*#__PURE__*/isValidElement(child)) { if (child.props.placement === 'end') { endAdornment = child; } if (child.props.placement === 'start') { startAdornment = child; } } }); return { startAdornment: startAdornment, endAdornment: endAdornment }; }; var _excluded = ["children", "data", "overflowStrategy"]; /** Organize and emphasize information quickly and effectively in a list of text elements. */ var TextInput = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) { var children = _ref.children, data = _ref.data, overflowStrategy = _ref.overflowStrategy, consumerProps = _objectWithoutProperties(_ref, _excluded); var _useFieldContext = useFieldContext(), _useFieldContext2 = _slicedToArray(_useFieldContext, 2), _useFieldContext2$ = _useFieldContext2[0], disabled = _useFieldContext2$.disabled, invalid = _useFieldContext2$.invalid, readOnly = _useFieldContext2$.readOnly, a11yProps = _useFieldContext2[1]; var _childrenToAdornments = childrenToAdornments(children), startAdornment = _childrenToAdornments.startAdornment, endAdornment = _childrenToAdornments.endAdornment; var _useInputStyles = useInputStyles({ disabled: disabled, invalid: invalid, startAdornment: Boolean(startAdornment), endAdornment: Boolean(endAdornment), readOnly: readOnly, overflowStrategy: overflowStrategy }), _useInputStyles2 = _slicedToArray(_useInputStyles, 2), boxProps = _useInputStyles2[0], inputStyles = _useInputStyles2[1]; return jsx(InputContainer, { display: "inline-flex", alignItems: "center", startAdornment: startAdornment, endAdornment: endAdornment, children: jsx(Box, _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, boxProps), consumerProps), a11yProps), {}, { as: "input", css: css(inputStyles), data: data, disabled: disabled, readOnly: readOnly, ref: forwardedRef })) }); }); TextInput.displayName = 'TextInput'; /** * 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 **/ var useInputStyles = function useInputStyles(_ref2) { var disabled = _ref2.disabled, startAdornment = _ref2.startAdornment, endAdornment = _ref2.endAdornment, readOnly = _ref2.readOnly, _ref2$overflowStrateg = _ref2.overflowStrategy, overflowStrategy = _ref2$overflowStrateg === void 0 ? 'truncate' : _ref2$overflowStrateg; var theme = useTheme(); var overflowStyles = useOverflowStrategy(overflowStrategy); var focusRingStyles = useFocusRing({ always: true }); var textStyles = useText({ baseline: false, tone: disabled || readOnly ? 'field' : 'neutral', size: 'standard', weight: 'regular' }); var _textStyles = _slicedToArray(textStyles, 2), typographyStyles = _textStyles[0], responsiveStyles = _textStyles[1]; return [{ flex: 1, position: 'relative', height: 'medium', paddingLeft: startAdornment ? 'none' : 'medium', paddingRight: endAdornment ? 'none' : 'medium', width: 'full' }, _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, typographyStyles), responsiveStyles), overflowStyles), {}, { ':enabled': { ':focus + [data-focus-indicator]': _objectSpread({ borderColor: theme.border.color.fieldAccent }, focusRingStyles) }, ':focus': { outline: 'none' }, '&[aria-invalid=true]': { color: theme.color.foreground.muted } })]; }; export { InputAdornment, InputContainer, TextInput, useInputStyles };