UNPKG

@retailmenot/anchor

Version:

A React UI Library by RetailMeNot

191 lines (183 loc) 6.97 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; }; // REACT import * as React from 'react'; // VENDOR import classNames from 'classnames'; import styled, { css } from '@xstyled/styled-components'; import { th, variant, space as spaceStyles } from '@xstyled/system'; // COMPONENTS import { Typography } from '../../Typography'; import { INPUT_THEME, INPUT_KEY } from './utils'; // UTILS import { get } from '../../utils/get/get'; const { useState, forwardRef, useImperativeHandle, useEffect, createRef, } = React; const StyledInputWrapper = styled('div') ` // Input Display Size display: block; position: relative; border: solid thin ${th.color('borders.base')}; border-radius: base; cursor: text; box-sizing: border-box; min-width: 5rem; width: 100%; margin: 0; // TODO: delete overflow: hidden; ::placeholder { font-family: base; color: text.placeholder; } &.focus { border-color: borders.dark; } label { transform-origin: left bottom; height: 1.4rem; transform: translate(0, 0.6rem) scale(1); &.lift { transform: translate(0, 0) scale(1); } } ${variant({ key: `${INPUT_KEY}.sizes`, prop: 'size', default: 'md', variants: INPUT_THEME.sizes, })} ${spaceStyles} `; const StyledReversedInputContainer = styled('div') ` flex: 1 1 auto; display: flex; flex-flow: column-reverse; padding: 0 0.25rem; justify-content: center; `; const LabelPresent = css ` &:focus::-webkit-input-placeholder { opacity: 1; } ::-webkit-input-placeholder { opacity: 0; transition: inherit; } `; const StyledInput = styled('input') ` box-sizing: border-box; border: none; padding: 0; outline: none !important; touch-action: manipulation; -webkit-appearance: none; background-color: transparent; z-index: 1; color: text.base; // TODO: bring this back when the 'bug' in styled components gets sorted out (MVP) //transition: all 250ms; font-family: base; // Disable Number Spinners &[type='number']::-webkit-inner-spin-button, &[type='number']::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } &:placeholder-shown + label { cursor: text; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } ${({ hasLabel = false }) => (hasLabel ? LabelPresent : null)}; `; const StyledInputContainer = styled('div') ` display: flex; align-items: center; height: 100%; `; const eventTypeResolver = (handler, event, type) => { const inputValue = get(event, 'target.value'); switch (type) { case 'number': // Although counter-intuitive, to properly clear an input field with type number it must // be fed an empty string. handler(inputValue ? parseFloat(inputValue) : '', event); break; case 'text': // An empty inputValue would be null which React doesn't handle well. handler(inputValue || '', event); break; default: handler(inputValue || '', event); break; } }; export const Input = forwardRef((_a, ref) => { var { className, inputProps, onBlur = () => null, onKeyDown = () => null, onKeyUp = () => null, onChange = () => null, onFocus = () => null, type = 'text', filter = val => val, placeholder, name = 'input', label, prefix, suffix, value, size, id, ariaLabel, autoComplete = 'on', autoFocus = false } = _a, props = __rest(_a, ["className", "inputProps", "onBlur", "onKeyDown", "onKeyUp", "onChange", "onFocus", "type", "filter", "placeholder", "name", "label", "prefix", "suffix", "value", "size", "id", "ariaLabel", "autoComplete", "autoFocus"]); const inputRef = createRef(); const [inputValue, setInputValue] = useState(filter(value)); const [focus, setFocus] = useState(false); useEffect(() => { setInputValue(filter(value)); }, [value]); useImperativeHandle(ref, () => ({ update: (newValue) => { setInputValue(filter(newValue)); }, blur: () => (inputRef.current ? inputRef.current.blur() : null), })); return (React.createElement(StyledInputWrapper, Object.assign({ size: size, onClick: () => { const { current } = inputRef; if (current) { current.focus(); } }, className: classNames('anchor-input', className, { focus, }) }, props), React.createElement(StyledInputContainer, null, prefix ? React.cloneElement(prefix, { className: 'input-prefix', }) : null, React.createElement(StyledReversedInputContainer, null, React.createElement(StyledInput, Object.assign({ "aria-label": ariaLabel, ref: inputRef, id: id, hasLabel: !!label, name: name, autoComplete: autoComplete, autoFocus: autoFocus, onBlur: (event) => { eventTypeResolver(onBlur, event, type); setFocus(false); }, onChange: (event) => { const { target: { value: currentValue }, } = event; setInputValue(filter(currentValue)); eventTypeResolver(onChange, event, type); }, onKeyDown: (event) => { onKeyDown(event); }, onKeyUp: (event) => { onKeyUp(event); }, onFocus: (event) => { eventTypeResolver(onFocus, event, type); setFocus(true); }, value: inputValue, type: type, placeholder: placeholder }, inputProps, props)), label && (React.createElement(Typography, { color: "text.label", scale: focus || (inputValue && `${inputValue}`.length) ? 12 : 14, htmlFor: name, as: "label", className: classNames({ lift: focus || (inputValue && `${inputValue}`.length), }) }, label))), suffix ? React.cloneElement(suffix, { className: 'input-suffix', }) : null))); }); //# sourceMappingURL=Input.component.js.map