Merge pull request #4073 from influxdata/reusable/inputs

Reusable Input
pull/4075/merge
Alex Paxton 2018-08-01 14:20:47 -07:00 committed by GitHub
commit fefa0ab2e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 432 additions and 2 deletions

View File

@ -0,0 +1,253 @@
/*
Input Styles
------------------------------------------------------------------------------
*/
@import 'src/style/modules/influx-colors';
@import 'src/style/modules/variables';
@import 'src/style/modules/mixins';
$input-bg: $g2-kevlar;
$input-disabled-bg: $g3-castle;
$input-text: $g15-platinum;
$input-border: $g5-pepper;
$input-hover-text: $g17-whisper;
$input-hover-border: $g7-graphite;
$input-focus-text: $g20-white;
$input-focus-border: $c-pool;
$input-disabled-text: $g9-mountain;
$input-field-z: 1;
$input-shadow-z: $input-field-z + 1;
$input-status-z: $input-field-z + 2;
.input {
position: relative;
}
.input-field {
position: relative;
z-index: $input-field-z;
width: 100%;
font-weight: 600;
font-family: $ix-text-font;
transition:
background-color 0.25s ease,
border-color 0.25s ease,
box-shadow 0.25s ease,
color 0.25s ease;
outline: none;
appearance: none;
border-radius: $radius;
background-color: $input-bg;
color: $input-text;
border: $ix-border solid $input-border;
&:hover {
border-color: $input-hover-border;
color: $input-hover-text;
}
&:focus {
color: $input-focus-text;
border-color: $input-focus-border;
box-shadow: 0 0 6px 0 $input-focus-border;
}
&[disabled],
&[disabled]:hover {
cursor: default;
border-color: $input-border;
background-color: $input-disabled-bg;
color: $input-disabled-text;
font-style: italic;
}
&::-webkit-input-placeholder {
color: $input-disabled-text;
font-weight: 600 !important;
font-style: italic;
}
&::-moz-placeholder {
color: $input-disabled-text;
font-weight: 600 !important;
font-style: italic;
}
&:-ms-input-placeholder {
color: $input-disabled-text;
font-weight: 600 !important;
font-style: italic;
}
&:-moz-placeholder {
color: $input-disabled-text;
font-weight: 600 !important;
font-style: italic;
}
}
/*
Input Icons (Including Status)
------------------------------------------------------------------------------
*/
.input-icon,
.input-status {
pointer-events: none;
top: 50%;
position: absolute;
z-index: $input-status-z;
transition: color 0.25s ease;
font-size: 1.1em;
}
.input-status {
transform: translate(50%, -50%);
}
.input-icon {
transform: translate(-50%, -50%);
color: $input-text;
}
.input-field:hover + .input-icon {
color: $input-hover-text
}
.input-field:focus + .input-icon {
color: $input-focus-text;
}
.input--disabled .input-icon,
.input--disabled .input-field:hover + .input-icon {
color: $input-disabled-text;
}
.input-shadow {
pointer-events: none;
position: absolute;
z-index: $input-shadow-z;
height: calc(100% - #{$ix-border * 2});
top: $ix-border;
border-radius: 0 3px 3px 0;
@include gradient-h(rgba($input-bg, 0), $input-bg);
border-style: solid;
border-color: $input-bg;
border-width: 0;
transition: opacity 0.25s ease;
opacity: 0;
}
.input-status + .input-shadow {
opacity: 1;
}
/*
Size Modifiers
------------------------------------------------------------------------------
*/
@mixin inputSizeModifier($fontSize, $padding, $height) {
height: $height;
font-size: $fontSize;
.input-field {
font-size: $fontSize;
padding: 0 $padding;
height: $height;
}
&.input--has-icon .input-field {
padding-left: $height;
}
.input-icon {
left: ($height / 2) + $ix-border;
}
.input-status {
right: $height / 2;
}
.input-shadow {
width: $height;
right: $padding;
border-right-width: $padding;
}
.input-spinner {
width: $height / 2;
height: $height / 2;
}
}
.input-xs {
@include inputSizeModifier($form-xs-font, $form-xs-padding, $form-xs-height);
}
.input-sm {
@include inputSizeModifier($form-sm-font, $form-sm-padding, $form-sm-height);
}
.input-md {
@include inputSizeModifier($form-md-font, $form-md-padding, $form-md-height);
}
.input-lg {
@include inputSizeModifier($form-lg-font, $form-lg-padding, $form-lg-height);
}
/*
Valid State
------------------------------------------------------------------------------
*/
.input--valid {
.input-status {
color: $c-rainforest;
}
}
/*
Error State
------------------------------------------------------------------------------
*/
.input--error {
.input-status {
color: $c-dreamsicle;
}
.input-field {
border-color: $c-curacao
}
.input-field:hover {
border-color: $c-dreamsicle;
}
.input-field:focus {
border-color: $c-dreamsicle;
box-shadow: 0 0 6px 0 $c-fire;
}
}
/*
Loading State
------------------------------------------------------------------------------
*/
.input--loading {
.input-status {
color: $c-pool;
}
}
@keyframes LoadingSpinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.input-spinner {
border-style: solid;
border-radius: 50%;
animation-duration: 0.85s;
animation-name: LoadingSpinner;
animation-timing-function: linear;
animation-iteration-count: infinite;
border: $ix-border solid $g5-pepper;
border-top-color: $c-pool;
}

View File

@ -0,0 +1,177 @@
// Libraries
import React, {
Component,
CSSProperties,
ChangeEvent,
KeyboardEvent,
} from 'react'
import classnames from 'classnames'
// Types
import {ComponentStatus, ComponentSize, IconFont} from 'src/reusable_ui/types'
// Styles
import './Input.scss'
export enum InputType {
Text = 'text',
Number = 'number',
Password = 'password',
Email = 'email',
}
interface Props {
value?: string
placeholder?: string
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
onBlur?: () => void
onFocus?: () => void
onKeyPress?: (e: KeyboardEvent<HTMLInputElement>) => void
onKeyUp?: (e: KeyboardEvent<HTMLInputElement>) => void
onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void
size?: ComponentSize
icon?: IconFont
status?: ComponentStatus
autoFocus?: boolean
spellCheck?: boolean
type?: InputType
widthPixels?: number
titleText?: string
disabledTitleText?: string
customClass?: string
}
class Input extends Component<Props> {
public static defaultProps: Partial<Props> = {
value: '',
placeholder: '',
titleText: '',
disabledTitleText: 'This input is disabled',
size: ComponentSize.Small,
status: ComponentStatus.Default,
autoFocus: false,
spellCheck: false,
type: InputType.Text,
}
public render() {
const {
status,
type,
value,
placeholder,
autoFocus,
spellCheck,
onChange,
onBlur,
onFocus,
onKeyPress,
onKeyUp,
onKeyDown,
} = this.props
return (
<div className={this.className} style={this.containerStyle}>
<input
title={this.title}
type={type}
value={value}
placeholder={placeholder}
autoFocus={autoFocus}
spellCheck={spellCheck}
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
onKeyPress={onKeyPress}
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
className="input-field"
disabled={status === ComponentStatus.Disabled}
/>
{this.icon}
{this.statusIndicator}
</div>
)
}
private get icon(): JSX.Element {
const {icon} = this.props
if (icon) {
return <span className={`input-icon icon ${icon}`} />
}
return null
}
private get title(): string {
const {titleText, disabledTitleText, status} = this.props
if (status === ComponentStatus.Disabled) {
return disabledTitleText
}
return titleText
}
private get statusIndicator(): JSX.Element {
const {status} = this.props
if (status === ComponentStatus.Loading) {
return (
<>
<div className="input-status">
<div className="input-spinner" />
</div>
<div className="input-shadow" />
</>
)
}
if (status === ComponentStatus.Error) {
return (
<>
<span className={`input-status icon ${IconFont.AlertTriangle}`} />
<div className="input-shadow" />
</>
)
}
if (status === ComponentStatus.Valid) {
return (
<>
<span className={`input-status icon ${IconFont.Checkmark}`} />
<div className="input-shadow" />
</>
)
}
return <div className="input-shadow" />
}
private get className(): string {
const {size, status, icon, customClass} = this.props
return classnames('input', {
[`input-${size}`]: size,
'input--has-icon': icon,
'input--valid': status === ComponentStatus.Valid,
'input--error': status === ComponentStatus.Error,
'input--loading': status === ComponentStatus.Loading,
'input--disabled': status === ComponentStatus.Disabled,
[`${customClass}`]: customClass,
})
}
private get containerStyle(): CSSProperties {
const {widthPixels} = this.props
if (widthPixels) {
return {width: `${widthPixels}px`}
}
return {width: '100%'}
}
}
export default Input

View File

@ -16,10 +16,10 @@ export enum ComponentSize {
} }
export enum ComponentStatus { export enum ComponentStatus {
Default = '', Default = 'default',
Loading = 'loading', Loading = 'loading',
Error = 'error', Error = 'error',
Valiid = 'valid', Valid = 'valid',
Disabled = 'disabled', Disabled = 'disabled',
} }