@retailmenot/anchor
Version:
A React UI Library by RetailMeNot
191 lines (183 loc) • 6.97 kB
JavaScript
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