extracted InputBase, styled NumberField

This commit is contained in:
Artur Bien
2019-02-23 21:51:16 +01:00
parent e7cab36730
commit 732d71eead
7 changed files with 194 additions and 87 deletions

View File

@@ -117,8 +117,9 @@ class DatePicker extends Component {
/>
<NumberField
value={year}
disableKeyboardInput
onChange={this.handleYearSelect}
width={90}
width={100}
className={`${baseClass}-toolbar__input`}
/>
</div>

View File

@@ -0,0 +1,51 @@
import React from "react";
import propTypes from "prop-types";
import styled from "styled-components";
import { StyledTextInput, StyledCutout } from "../common";
import { colors, blockSizes } from "../common/theme.variables";
const StyledInputWrapper = styled(StyledCutout)`
height: ${blockSizes.md};
padding: 2px;
background: ${props => (props.isDisabled ? colors.bg : colors.light)};
`;
const InputBase = ({
onChange,
value,
disabled,
name,
type,
style,
shadow,
...otherProps
}) => (
<StyledInputWrapper shadow={shadow} isDisabled={disabled} style={style}>
<StyledTextInput
onChange={disabled ? undefined : onChange}
readOnly={disabled}
disabled={disabled}
value={value}
name={name}
type={type}
{...otherProps}
/>
</StyledInputWrapper>
);
InputBase.defaultProps = {
value: "",
disabled: false,
shadow: true,
onChange: undefined
};
InputBase.propTypes = {
name: propTypes.string,
onChange: propTypes.func,
value: propTypes.oneOfType([propTypes.string, propTypes.number]).isRequired,
disabled: propTypes.bool,
shadow: propTypes.bool,
type: propTypes.oneOf(["text", "number", "tel"])
};
export default InputBase;

View File

@@ -1,16 +1,54 @@
import React, { Component } from "react";
import React from "react";
import propTypes from "prop-types";
import cx from "classnames";
import "./NumberField.css";
import Button from "../Button/Button";
import TextField from "../TextField/TextField";
import styled from "styled-components";
import { colors, blockSizes } from "../common/theme.variables";
import InputBase from "../InputBase/InputBase";
class NumberField extends Component {
// ⭕⭕⭕⭕⭕ fix functionality and use hooks
const StyledNumberFieldWrapper = styled.div`
display: inline-flex;
align-items: center;
`;
const StyledButtonWrapper = styled.div`
height: ${blockSizes.md};
display: flex;
flex-direction: column;
flex-wrap: nowrap;
margin-left: 2px;
margin-top: -2px;
`;
const StyledButton = styled(Button)`
height: 50%;
width: 30px;
padding: 0;
flex-shrink: 0;
border-left-color: ${colors.lightGray};
border-top-color: ${colors.lightGray};
box-shadow: inset 1px 1px 0px 1px ${colors.light},
inset -1px -1px 0 1px ${colors.darkGray};
`;
const StyledButtonIcon = styled.span`
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) ${props => props.invert && "rotateZ(180deg)"};
width: 0px;
height: 0px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
display: inline-block;
border-top: 4px solid ${colors.dark};
`;
class NumberField extends React.Component {
static defaultProps = {
value: 0,
disabled: false,
width: null
disabled: false
};
static propTypes = {
onChange: propTypes.func.isRequired,
@@ -19,6 +57,9 @@ class NumberField extends Component {
max: propTypes.number,
width: propTypes.oneOfType([propTypes.string, propTypes.number]),
disabled: propTypes.bool,
disableKeyboardInput: propTypes.bool,
fullWidth: propTypes.bool,
shadow: propTypes.bool,
className: propTypes.string
};
state = {
@@ -32,51 +73,56 @@ class NumberField extends Component {
};
handleChange = e => {
const newValue = this.normalize(parseInt(e.target.value));
this.props.onChange(newValue);
this.setState({ value: newValue });
let newValue =
e.target.value === "-" ? "-" : this.normalize(e.target.value);
console.log(newValue);
newValue = newValue ? newValue : newValue === 0 ? 0 : "";
if (e.target.validity.valid) {
this.setState({ value: newValue });
this.props.onChange(newValue);
}
};
normalize = value => {
const { min, max } = this.props;
if (min !== undefined && value < min) return min;
if (max !== undefined && value > max) return max;
return value;
return parseInt(value);
};
render() {
const { disabled, className, width, style } = this.props;
const {
disabled,
disableKeyboardInput,
className,
width,
style,
shadow
} = this.props;
const { value } = this.state;
const baseClass = "NumberField";
const rootClass = cx(baseClass, className, {
[`${baseClass}__disabled`]: disabled
});
return (
<div
className={rootClass}
<StyledNumberFieldWrapper
className={className}
style={{ ...style, width: width ? width : "auto" }}
>
<TextField
width={"100%"}
<InputBase
value={value}
onChange={disabled ? undefined : this.handleChange}
readOnly={disabled}
type="number"
onChange={
disabled || disableKeyboardInput ? undefined : this.handleChange
}
readOnly={disabled || disableKeyboardInput}
disabled={disabled}
shadow={shadow}
type="tel"
pattern="^-?[0-9]\d*\.?\d*$"
/>
<div className={`${baseClass}-buttons`}>
<button onClick={() => this.add(1)} className={`${baseClass}-button`}>
<span
className={`${baseClass}-button__icon ${baseClass}-button__icon--up`}
/>
</button>
<button
onClick={() => this.add(-1)}
className={`${baseClass}-button`}
>
<span className={`${baseClass}-button__icon`} />
</button>
</div>
</div>
<StyledButtonWrapper>
<StyledButton disabled={disabled} onClick={() => this.add(1)}>
<StyledButtonIcon invert />
</StyledButton>
<StyledButton disabled={disabled} onClick={() => this.add(-1)}>
<StyledButtonIcon />
</StyledButton>
</StyledButtonWrapper>
</StyledNumberFieldWrapper>
);
}
}

View File

@@ -15,9 +15,36 @@ storiesOf("NumberField", module)
</div>
))
.add("default", () => (
<NumberField value={1991} onChange={value => console.log(value)} />
))
.add("fixed width", () => (
<NumberField
width={200}
value={1991}
onChange={value => console.log(value)}
/>
))
.add("disabled", () => (
<NumberField
disabled
width={200}
value={1991}
onChange={value => console.log(value)}
/>
))
.add("disabled keyboard input", () => (
<NumberField
disableKeyboardInput
width={200}
value={1991}
onChange={value => console.log(value)}
/>
))
.add("no shadow", () => (
<NumberField
shadow={false}
width={200}
value={1991}
onChange={value => console.log(value)}
/>
));

View File

@@ -47,7 +47,7 @@ const StyledDropdownIcon = styled.span`
border-left: 6px solid transparent;
border-right: 6px solid transparent;
display: inline-block;
border-top: 6px solid #050608;
border-top: 6px solid ${colors.dark};
`;
const StyledDropdownList = styled.ul`

View File

@@ -1,35 +1,7 @@
import React, { useState } from "react";
import propTypes from "prop-types";
import styled from "styled-components";
import { StyledCutout } from "../common";
import {
blockSizes,
fontSizes,
padding,
colors
} from "../common/theme.variables";
const StyledInputWrapper = styled(StyledCutout)`
height: ${blockSizes.md};
padding: 2px;
background: ${props => (props.isDisabled ? colors.bg : colors.light)};
`;
const StyledInput = styled.input`
width: 100%;
height: 100%;
padding: 0 ${padding.sm};
outline: none;
border: none;
background: none;
font-size: ${fontSizes.md};
color: ${props => (props.disabled ? colors.darkGray : colors.dark)};
text-shadow: ${props =>
props.disabled ? "1px 1px " + colors.light : "none"};
filter: ${props => (props.disabled ? "grayscale(100%)" : "none")};
/* negative margin to compensate for wrapper borders */
`;
import InputBase from "../InputBase/InputBase";
const TextField = ({
onChange,
@@ -45,28 +17,24 @@ const TextField = ({
}) => {
const [inputValue, setInputValue] = useState(value);
console.log(value, inputValue);
const onValueChange = e => {
const newValue = e.target.value;
setInputValue(newValue);
onChange && onChange(e);
};
return (
<StyledInputWrapper
<InputBase
shadow={shadow}
isDisabled={disabled}
style={{ ...style, width: width ? width : "auto" }}
>
<StyledInput
onChange={disabled ? undefined : onValueChange}
readOnly={disabled}
disabled={disabled}
value={inputValue}
name={name}
className={className}
type={type}
{...otherProps}
/>
</StyledInputWrapper>
onChange={disabled ? undefined : onValueChange}
disabled={disabled}
value={inputValue}
name={name}
className={className}
type="text"
/>
);
};
@@ -83,8 +51,6 @@ TextField.propTypes = {
value: propTypes.oneOfType([propTypes.string, propTypes.number]).isRequired,
disabled: propTypes.bool,
shadow: propTypes.bool,
rows: propTypes.number,
width: propTypes.oneOfType([propTypes.string, propTypes.number]),
type: propTypes.oneOf(["text", "number"])
width: propTypes.oneOfType([propTypes.string, propTypes.number])
};
export default TextField;

View File

@@ -1,5 +1,5 @@
import styled, { css } from "styled-components";
import { colors, fontSizes } from "./theme.variables";
import { colors, fontSizes, padding } from "./theme.variables";
const { bg, light, dark, lightGray, darkGray } = colors;
@@ -80,3 +80,19 @@ export const StyledCutout = styled.div`
props.shadow && "box-shadow: inset 3px 3px 10px rgba(0, 0, 0, 0.3);"}
}
`;
export const StyledTextInput = styled.input`
width: 100%;
height: 100%;
padding: 0 ${padding.sm};
outline: none;
border: none;
background: none;
font-size: ${fontSizes.md};
color: ${props => (props.disabled ? colors.darkGray : colors.dark)};
text-shadow: ${props =>
props.disabled ? "1px 1px " + colors.light : "none"};
filter: ${props => (props.disabled ? "grayscale(100%)" : "none")};
/* negative margin to compensate for wrapper borders */
`;