UNPKG

@cimpress/react-components

Version:
283 lines (271 loc) 10.8 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import React, { useEffect, useState } from 'react'; import { css, cx } from '@emotion/css'; import { useMemoizedId } from './utils'; import cvar from './theme/cvar'; import SuccessSvg from './icons/SuccessSvg'; import WarningSvg from './icons/WarningSvg'; import ErrorSvg from './icons/ErrorSvg'; import { useFeatureFlags } from './FeatureFlags'; const isTextTruthy = (val) => val !== undefined && val !== null && val.toString() !== ''; export const formGroupCss = css ` position: relative; margin-bottom: ${cvar('spacing-16')}; input, textarea { color: ${cvar('color-text-default')}; } input { box-shadow: none; padding: ${cvar('spacing-16')}; transition: all 0.2s ease-out; &:focus { outline: none; box-shadow: none; border-color: ${cvar('color-border-interactive')}; } } textarea { box-shadow: none; padding: ${cvar('spacing-16')}; height: auto; &:focus { outline: none; box-shadow: none; border-color: ${cvar('color-border-interactive')}; } } label { color: ${cvar('color-text-label')}; font-weight: normal; position: absolute; left: ${cvar('spacing-16')}; top: ${cvar('spacing-16')}; line-height: 16px; margin-bottom: 0; transition: all 0.2s ease-out; z-index: 5; overflow-x: clip; white-space: nowrap; text-overflow: ellipsis; max-width: 90%; } `; export const formGroupActiveCss = css ` input, textarea { padding: ${cvar('spacing-24')} ${cvar('spacing-16')} ${cvar('spacing-8')}; } label { top: ${cvar('spacing-4')}; font-size: 12px; } `; export const controlLabelCss = css ` margin-bottom: 0; font-size: inherit; line-height: 1.5; `; const isRequired = css ` label:after { content: ' *'; color: ${cvar('color-required-asterisk')}; } `; const isDisabled = css ` input { color: ${cvar('color-text-label')}; background: ${cvar('color-textfield-disabled')}; } `; const formControl = css ` height: ${cvar('spacing-48')}; border: 1px solid ${cvar('color-border-default')}; position: relative; flex: 1 1 auto; width: 1%; margin-bottom: 0; `; const inputGroup = css ` z-index: 0; position: relative; display: flex; width: 100%; > .crc-button { padding: ${cvar('spacing-12')} ${cvar('spacing-24')}; } /* Remove border from input when right addon is present */ &:has(> .crc-button) :is(input, textarea) { border-right: 0; } /* Remove right border radius from addon button when consecutive addon is present */ &:has(> .crc-button + .crc-button) :not(.crc-button:last-of-type) { border-top-right-radius: 0; border-bottom-right-radius: 0; } /* Remove border radius from left addon when it is the first addon */ > .crc-button:first-of-type { border-top-left-radius: 0; border-bottom-left-radius: 0; } /* Remove double borders between addons, and remove left border radius from when addons are consecutive */ > .crc-button + .crc-button { border-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; } `; const helpBlock = css ` display: block; margin-top: ${cvar('spacing-4')}; `; const textfieldColorMap = { success: 'color-border-success', warning: 'color-border-warning', error: 'color-border-error', }; const statusIconStyles = css ` position: absolute; right: ${cvar('spacing-12')}; top: ${cvar('spacing-16')}; color: var(--crc-text-input-status-icon-color); transition: all 0.2s ease-out; `; const statusIconSvg = { success: React.createElement(SuccessSvg, { className: statusIconStyles }), warning: React.createElement(WarningSvg, { className: statusIconStyles }), error: React.createElement(ErrorSvg, { className: statusIconStyles }), }; const statusCss = (status) => css ` --crc-text-input-status-icon-color: ${cvar(textfieldColorMap[status])}; input { box-shadow: none; padding-right: ${cvar('spacing-32')}; &:focus { border-color: ${cvar(textfieldColorMap[status])}; } } .crc-text-field { border-color: ${cvar(textfieldColorMap[status])}; } `; const inputWrapper = css ` display: flex; flex: 1 1 auto; position: relative; `; const noOuterMarginCss = css ` margin-bottom: 0; `; export const TextField = React.forwardRef((_a, ref) => { var { autoFocus = false, className = '', disabled = false, helpText, id, inputStyle, label, name, onBlur, onChange, onClick, onFocus, onKeyDown, placeholder, required = false, rightAddon, status, style, type = 'text', value, defaultValue = '', pattern } = _a, rest = __rest(_a, ["autoFocus", "className", "disabled", "helpText", "id", "inputStyle", "label", "name", "onBlur", "onChange", "onClick", "onFocus", "onKeyDown", "placeholder", "required", "rightAddon", "status", "style", "type", "value", "defaultValue", "pattern"]); const { v17_noOuterSpacing } = useFeatureFlags(); const [internalValue, setInternalValue] = useState(defaultValue); const isControlled = value !== undefined; const currentValue = isControlled ? value : internalValue; const [float, setFloat] = useState(isTextTruthy(currentValue)); const uniqueId = useMemoizedId({ label: `${name}-${placeholder}-${label}`, id }); useEffect(() => { setFloat(isTextTruthy(currentValue)); }, [setFloat, currentValue]); const handleBlur = (e) => { setFloat(isTextTruthy(currentValue)); onBlur && onBlur(e); }; const handleFocus = (e) => { setFloat(true); onFocus && onFocus(e); }; const handleChange = (e) => { const newValue = e.target.value; const isValid = !pattern || !!newValue.match(new RegExp(`^${pattern}$`)); if (!isControlled) { setInternalValue(newValue); } onChange && onChange(Object.assign(Object.assign({}, e), { isValid })); }; const inputId = id || uniqueId; const wrapperClassNames = cx('crc-text-input', formGroupCss, v17_noOuterSpacing && noOuterMarginCss, className, { [formGroupActiveCss]: !!label && float, [statusCss(status)]: !!status, [isRequired]: !!required, [isDisabled]: !!disabled, }); const inputProps = Object.assign(Object.assign({}, rest), { autoFocus, disabled, id: inputId, name, onBlur: handleBlur, onChange: handleChange, onClick, onFocus: handleFocus, onKeyDown, placeholder, required, style: inputStyle, value: currentValue, pattern, type }); const inputElement = (React.createElement("input", Object.assign({}, inputProps, { className: cx(formControl, 'crc-text-field'), ref: ref }))); const labelElement = label ? (React.createElement("label", { className: controlLabelCss, htmlFor: inputId, title: label }, label)) : null; return (React.createElement("div", { className: wrapperClassNames, style: style }, React.createElement("div", { className: inputGroup }, React.createElement("div", { className: inputWrapper }, inputElement, labelElement, !!status && statusIconSvg[status]), rightAddon), helpText ? React.createElement("small", { className: helpBlock }, helpText) : null)); }); TextField.displayName = 'TextField'; export const TextArea = React.forwardRef((_a, ref) => { var { autoFocus = false, className = '', disabled = false, helpText, id, inputStyle, label, name, onBlur, onChange, onClick, onFocus, onKeyDown, placeholder, required = false, rightAddon, status, style, value, defaultValue = '', pattern } = _a, rest = __rest(_a, ["autoFocus", "className", "disabled", "helpText", "id", "inputStyle", "label", "name", "onBlur", "onChange", "onClick", "onFocus", "onKeyDown", "placeholder", "required", "rightAddon", "status", "style", "value", "defaultValue", "pattern"]); const { v17_noOuterSpacing: noOuterSpacing } = useFeatureFlags(); const [internalValue, setInternalValue] = useState(defaultValue); const isControlled = value !== undefined; const currentValue = isControlled ? value : internalValue; const [float, setFloat] = useState(isTextTruthy(currentValue)); const uniqueId = useMemoizedId({ label: `${name}-${placeholder}-${label}`, id }); useEffect(() => { setFloat(isTextTruthy(currentValue)); }, [setFloat, currentValue]); const handleBlur = (e) => { setFloat(isTextTruthy(currentValue)); onBlur && onBlur(e); }; const handleFocus = (e) => { setFloat(true); onFocus && onFocus(e); }; const handleChange = (e) => { const newValue = e.target.value; const isValid = !pattern || !!newValue.match(new RegExp(`^${pattern}$`)); if (!isControlled) { setInternalValue(newValue); } onChange && onChange(Object.assign(Object.assign({}, e), { isValid })); }; const inputId = id || uniqueId; const wrapperClassNames = cx('crc-text-input', formGroupCss, noOuterSpacing && noOuterMarginCss, className, { [formGroupActiveCss]: !!label && float, [statusCss(status)]: !!status, [isRequired]: !!required, [isDisabled]: !!disabled, }); const inputProps = Object.assign(Object.assign({}, rest), { autoFocus, disabled, id: inputId, name, onBlur: handleBlur, onChange: handleChange, onClick, onFocus: handleFocus, onKeyDown, placeholder, required, style: inputStyle, value: currentValue }); const inputElement = (React.createElement("textarea", Object.assign({}, inputProps, { className: cx(formControl, 'crc-text-field'), ref: ref }))); const labelElement = label ? (React.createElement("label", { className: controlLabelCss, htmlFor: inputId, title: label }, label)) : null; return (React.createElement("div", { className: wrapperClassNames, style: style }, React.createElement("div", { className: inputGroup }, React.createElement("div", { className: inputWrapper }, inputElement, labelElement, !!status && statusIconSvg[status]), rightAddon && React.createElement("div", null, rightAddon)), helpText ? React.createElement("small", { className: helpBlock }, helpText) : null)); }); TextArea.displayName = 'TextArea'; //# sourceMappingURL=TextInput.js.map