Skip to content

Commit 394a7be

Browse files
committed
feat(Input): added input component
1 parent df5c160 commit 394a7be

File tree

9 files changed

+217
-0
lines changed

9 files changed

+217
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { boolean, select, text } from '@storybook/addon-knobs';
2+
import React from 'react';
3+
import { Input } from '../Input';
4+
import readme from './README.md';
5+
6+
export default { title: 'Components/Input', component: Input, parameters: { readme } };
7+
8+
export const Basic = () => (
9+
<Input
10+
message={select('message', ['default', 'warning', 'error'], 'default')}
11+
messageText={text('messageText', '')}
12+
fillWidth={boolean('fillWidth', false)}
13+
padMessageText={boolean('padMessageText', false)}
14+
disabled={boolean('disabled', false)}
15+
/>
16+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { shallow } from 'enzyme';
2+
import React from 'react';
3+
import { Input } from '../Input';
4+
5+
describe('Input component', () => {
6+
it('renders its contents', () => {
7+
const input = shallow(<Input />);
8+
expect(input.find('.ui__input')).toHaveLength(1);
9+
});
10+
11+
it('snapshot renders default input', () => {
12+
const input = shallow(<Input />);
13+
expect(input).toMatchSnapshot();
14+
});
15+
});

src/components/Input/Input.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import classnames from 'classnames';
2+
import React, { forwardRef, InputHTMLAttributes, useMemo } from 'react';
3+
import { useFocus } from '../../hooks';
4+
import { Icon } from '../Icon';
5+
6+
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
7+
/**
8+
* Messaging feedback for the input. This can be used for displaying feedback
9+
* inside of forms.
10+
* @default "default"
11+
*/
12+
message?: 'default' | 'warning' | 'error';
13+
/**
14+
* Text to display under the input describing the message.
15+
*/
16+
messageText?: string;
17+
/**
18+
* Should this input fill the width of the parent container.
19+
*/
20+
fillWidth?: boolean;
21+
/**
22+
* A class for the wrapper div which encompases the input and icon and warning
23+
* texrt. Use this to do things like add margin-bottom to the entire input for
24+
* alignment in forms.
25+
*/
26+
wrapperClassName?: string;
27+
/**
28+
* If true, will add padding below the input to compensate for message text
29+
* being absent. This prevents UI jumping when message text appears.
30+
*/
31+
padMessageText?: boolean;
32+
}
33+
34+
export const Input = forwardRef<HTMLInputElement, InputProps>(
35+
(
36+
{
37+
message = 'default',
38+
messageText,
39+
fillWidth,
40+
wrapperClassName,
41+
padMessageText,
42+
className,
43+
onFocus,
44+
onBlur,
45+
...props
46+
},
47+
ref,
48+
) => {
49+
const { focused, handleOnFocus, handleOnBlur } = useFocus(onFocus, onBlur);
50+
51+
const icon = useMemo(() => {
52+
switch (message) {
53+
case 'default':
54+
return undefined;
55+
case 'warning':
56+
return 'Warning';
57+
case 'error':
58+
return 'Error';
59+
}
60+
}, [message]);
61+
62+
const wrapperClass = classnames(
63+
'ui__base ui__input__wrapper',
64+
{
65+
'ui__input__wrapper--fill': fillWidth,
66+
'ui__input__wrapper--pad': padMessageText && !messageText,
67+
},
68+
wrapperClassName,
69+
);
70+
const mainClass = classnames('ui__input', `ui__input--message-${message}`, { 'ui__input--focused': focused });
71+
const inputClass = classnames('ui__input__input', { 'ui__input__input--disabled': props.disabled }, className);
72+
73+
return (
74+
<div className={wrapperClass}>
75+
<div className={mainClass}>
76+
<input {...props} onFocus={handleOnFocus} onBlur={handleOnBlur} className={inputClass} ref={ref} />
77+
{icon ? <Icon className="ui__input__icon" icon={icon} /> : undefined}
78+
</div>
79+
{messageText ? <div className="ui__input__messageText">{messageText}</div> : undefined}
80+
</div>
81+
);
82+
},
83+
);

src/components/Input/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A simple styled input with the ability to show error or warning feedback.

src/components/Input/_Input.scss

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
.ui__input__wrapper {
2+
width: 200px;
3+
4+
&--fill {
5+
width: 100%;
6+
}
7+
8+
&--pad {
9+
padding-bottom: 20px;
10+
}
11+
}
12+
13+
.ui__input {
14+
border-radius: $border-radius;
15+
transition: $focus-transition;
16+
background-color: $color-gray-1;
17+
border: 1px solid $color-gray-3;
18+
display: inline-flex;
19+
width: 100%;
20+
21+
&--message {
22+
&-default {
23+
&.ui__input--focused {
24+
border: 1px solid $color-theme-primary;
25+
}
26+
27+
&.ui__input--focused {
28+
@include focus;
29+
}
30+
}
31+
32+
&-warning {
33+
border-color: $color-message-warning;
34+
35+
.ui__input__icon {
36+
fill: $color-message-warning;
37+
}
38+
39+
&.ui__input--focused {
40+
box-shadow: 0 0 0 2px var(--color-message-warning-focus-shadow);
41+
}
42+
}
43+
44+
&-error {
45+
border-color: $color-message-error;
46+
47+
.ui__input__icon {
48+
fill: $color-message-error;
49+
}
50+
51+
&.ui__input--focused {
52+
box-shadow: 0 0 0 2px var(--color-message-error-focus-shadow);
53+
}
54+
}
55+
}
56+
57+
.ui__input__input {
58+
flex: 1 1 auto;
59+
padding: $padding-tiny;
60+
height: 28px;
61+
outline: none;
62+
border: none;
63+
background-color: transparent;
64+
color: $color-font-primary;
65+
min-height: 24px;
66+
67+
&--disabled {
68+
opacity: 0.7;
69+
}
70+
}
71+
72+
&__icon {
73+
flex: 0 0;
74+
padding: 0 2px;
75+
}
76+
77+
&__messageText {
78+
width: 100%;
79+
font-size: $font-size-small;
80+
margin-top: $padding-tiny;
81+
}
82+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Input component snapshot renders default input 1`] = `
4+
<div
5+
className="ui__base ui__input__wrapper"
6+
>
7+
<div
8+
className="ui__input ui__input--message-default"
9+
>
10+
<input
11+
className="ui__input__input"
12+
onBlur={[Function]}
13+
onFocus={[Function]}
14+
/>
15+
</div>
16+
</div>
17+
`;

src/components/Input/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Input';

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from './FocusTrap';
1616
export * from './Icon';
1717
export * from './IconButton';
1818
export * from './Image';
19+
export * from './Input';
1920
export * from './Modal';
2021
export * from './Overlay';
2122
export * from './Spinner';

src/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
@import './components/Icon/Icon';
3434
@import './components/IconButton/IconButton';
3535
@import './components/Image/Image';
36+
@import './components/Input/Input';
3637
@import './components/Modal/Modal';
3738
@import './components/Overlay/Overlay';
3839
@import './components/Spinner/Spinner';

0 commit comments

Comments
 (0)