UNPKG

@cimpress/react-components

Version:
209 lines 11.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InlineEdit = void 0; const react_1 = __importStar(require("react")); const css_1 = require("@emotion/css"); const utils_1 = require("../utils"); const cvar_1 = __importDefault(require("../theme/cvar")); const CloseSvg_1 = __importDefault(require("../icons/CloseSvg")); const SuccessSvg_1 = __importDefault(require("../icons/SuccessSvg")); const PencilSvg_1 = __importDefault(require("../icons/PencilSvg")); const styles_1 = require("./styles"); const Check_1 = __importDefault(require("../icons/Check")); const internal_1 = require("../internal"); const noop = () => { }; const enterKeyCode = 13; const escKeyCode = 27; const isTextTruthy = (val) => val !== undefined && val !== null && val.toString() !== ''; exports.InlineEdit = react_1.default.forwardRef((_a, ref) => { var { disabled, forwardedRef, id, label = '', minWidth = 130, name = '', placeholder, required = false, requiredWarningMessage = 'This Field is Required', size = 'default', type = 'text', validateInput = noop, value = '', onBlur = noop, onCancel = noop, onChange, onEditStateChange = noop, onFocus = noop, onSave, className, style } = _a, rest = __rest(_a, ["disabled", "forwardedRef", "id", "label", "minWidth", "name", "placeholder", "required", "requiredWarningMessage", "size", "type", "validateInput", "value", "onBlur", "onCancel", "onChange", "onEditStateChange", "onFocus", "onSave", "className", "style"]); // States for Inline Edit const [localValue, setLocalValue] = (0, react_1.useState)(value); const [showInput, setShowInput] = (0, react_1.useState)(false); const [inputWidth, setInputWidth] = (0, react_1.useState)(); const [savedTimer, setSavedTimer] = (0, react_1.useState)(); const [contentJustSaved, setContentJustSaved] = (0, react_1.useState)(false); const [validationError, setValidationError] = (0, react_1.useState)(); const inputId = (0, utils_1.useMemoizedId)({ id, label }); // ensure we clear any timers in case component unmounts. (0, react_1.useEffect)(() => () => clearTimeout(savedTimer), [savedTimer]); const previousShowInput = (0, react_1.useRef)(showInput); (0, react_1.useEffect)(() => { if (previousShowInput.current !== showInput) { onEditStateChange(showInput); } previousShowInput.current = showInput; }, [showInput, onEditStateChange]); (0, react_1.useEffect)(() => { setLocalValue(value); }, [value]); (0, react_1.useEffect)(() => { setValidationError(validateInput(localValue)); }, [localValue, validateInput]); const hasOnChange = Boolean(onChange); const displayValue = hasOnChange ? value : localValue; (0, react_1.useEffect)(() => { if (!isTextTruthy(displayValue) && required) { setValidationError(requiredWarningMessage); } }, [displayValue, required, requiredWarningMessage]); const inputRef = (0, react_1.useRef)(); /** * This hook is used to customize the instance value exposed to the parent for the ref. * It must always be used with React.forwardRef. This gives us control of where to pass the ref prop. * Whatever ref is passed to Inline Edit will receive the inputRef instance. * This pattern supports all methods of creating refs. */ (0, react_1.useImperativeHandle)(forwardedRef, () => inputRef.current); // used to calculate sizing for input to grow with text const valueSizeRef = (0, react_1.useRef)() || ref; const placeholderSizeRef = (0, react_1.useRef)(); const errorMessageRef = (0, react_1.useRef)(); const isTextArea = type === 'textarea'; (0, react_1.useEffect)(() => { // If there's an onChange handler, the save and cancel buttons are not shown, so the additional // padding is not needed. However, there's still an additional 5px that need to be added. const extraPadding = hasOnChange ? 5 : 70; const pencilPadding = 25; let newWidth = isTextTruthy(displayValue) ? valueSizeRef.current.scrollWidth : placeholderSizeRef.current ? placeholderSizeRef.current.scrollWidth : 0; const errorMessageWidth = errorMessageRef.current ? errorMessageRef.current.scrollWidth : 0; newWidth = Math.max(newWidth, errorMessageWidth); newWidth += showInput ? extraPadding : pencilPadding; if (newWidth < minWidth) { newWidth = minWidth; } setInputWidth(newWidth); }, [displayValue, hasOnChange, valueSizeRef, placeholderSizeRef, minWidth, showInput, validationError]); (0, react_1.useEffect)(() => { if (showInput && isTextArea) { // Set the cursor to the end of the text and scroll to the end inputRef.current.setSelectionRange(inputRef.current.textContent.length, inputRef.current.textContent.length); inputRef.current.scrollTop = inputRef.current.scrollHeight; } }, [isTextArea, showInput]); const showSuccess = () => { setContentJustSaved(true); setShowInput(false); setSavedTimer(setTimeout(() => { setContentJustSaved(false); }, 1000)); }; const handleBlur = (e) => { if (hasOnChange) { setShowInput(false); showSuccess(); } onBlur(e); }; const handleFocus = (e) => { onFocus(e); }; const handleChange = (e) => { const inputValue = e.target.value; if (hasOnChange) { onChange(e); } else { setLocalValue(inputValue); } }; const handleSaveClick = (e) => { if (!hasOnChange) { if (validationError) { return; } onSave({ value: localValue, name }, e); } showSuccess(); }; // Optional to handle how we cancel our input const handleCancelClick = (e) => { onCancel({ value: localValue, name }, e); setLocalValue(value); setShowInput(false); }; const handleKeyDown = (e) => { e.stopPropagation(); if (e.keyCode === enterKeyCode) { handleSaveClick(e); } else if (e.keyCode === escKeyCode) { handleCancelClick(e); } }; const handleActiveClick = () => { setShowInput(true); }; const inputProps = Object.assign(Object.assign({}, rest), { name, type, value: displayValue, label, placeholder, id: inputId, onKeyDown: handleKeyDown, ref: inputRef, onBlur: handleBlur, onFocus: handleFocus, onChange: handleChange, autoFocus: true }); const inputStyle = Object.assign({ marginBottom: '20px', width: showInput ? `${inputWidth}px` : null }, style); const parentClassName = (0, css_1.cx)(styles_1.inlineEdit, { required }, className); const inlineInput = (react_1.default.createElement(react_1.default.Fragment, null, isTextArea ? (react_1.default.createElement("textarea", Object.assign({ className: (0, css_1.cx)(size, styles_1.formControl) }, inputProps, { style: { marginTop: '17px', padding: '16px', minWidth: '100px' } }))) : (react_1.default.createElement("input", Object.assign({ className: (0, css_1.cx)(['crc-inline-edit__input', (0, styles_1.inputBaseStyle)(size), size]) }, inputProps))), label ? (react_1.default.createElement("label", { className: (0, css_1.cx)('crc-inline-edit__label', styles_1.labelStyle), htmlFor: inputId }, label)) : null, !hasOnChange ? (react_1.default.createElement("div", { className: (0, css_1.cx)('crc-inline-edit__right-button', styles_1.rightAddOn) }, react_1.default.createElement("button", { onClick: handleSaveClick, "aria-label": "save", disabled: Boolean(validationError) }, react_1.default.createElement(Check_1.default, { color: (0, cvar_1.default)('color-inline-edit-border') })), react_1.default.createElement("button", { onClick: handleCancelClick, "aria-label": "cancel" }, react_1.default.createElement(CloseSvg_1.default, { color: (0, cvar_1.default)('color-inline-edit-border') })))) : null)); // For disabled state we add the disabled class to the parent // In addition we pass a <p> without the onClick handler const inlineDisplay = (react_1.default.createElement(internal_1.InlineButton, { onClick: disabled ? () => { } : handleActiveClick, className: (0, css_1.cx)('crc-inline-edit--inactive', { disabled }) }, react_1.default.createElement("p", { className: size }, displayValue || placeholder, contentJustSaved && (react_1.default.createElement("div", { className: (0, css_1.cx)('crc-inline-edit__success-icon', styles_1.successIconWrapper) }, react_1.default.createElement(SuccessSvg_1.default, { color: (0, cvar_1.default)('color-background-success') }))), !disabled && (react_1.default.createElement("div", { className: "crc-inline-edit__edit-icon" }, react_1.default.createElement(PencilSvg_1.default, { size: '1em', color: (0, cvar_1.default)('color-inline-edit-border') })))), label ? react_1.default.createElement("div", { className: styles_1.labelStyle }, label) : null)); return (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement("div", { className: parentClassName, style: inputStyle }, showInput ? inlineInput : inlineDisplay, validationError ? (react_1.default.createElement("div", { "data-testid": "error-message", className: (0, css_1.cx)('crc-inline-edit__error', styles_1.errorMessage), ref: errorMessageRef }, validationError)) : null), react_1.default.createElement("div", { className: size, ref: valueSizeRef, style: styles_1.sizeStyle }, displayValue), react_1.default.createElement("div", { className: size, ref: placeholderSizeRef, style: styles_1.sizeStyle }, placeholder || ''))); }); exports.InlineEdit.displayName = 'InlineEdit'; //# sourceMappingURL=InlineEdit.js.map