From 8b4f69e1d26350312b0173551968ef88856fc468 Mon Sep 17 00:00:00 2001 From: Estevan Maito Date: Wed, 24 Jun 2020 22:52:49 -0300 Subject: [PATCH] feat(input): add form inputs --- __tests__/Input.test.js | 68 +++++++++++++++++++++++++++++++++++++++++ src/Input.js | 65 +++++++++++++++++++++++++++++++++++++++ src/index.js | 1 + src/themes/default.js | 15 +++++++++ 4 files changed, 149 insertions(+) create mode 100644 __tests__/Input.test.js create mode 100644 src/Input.js diff --git a/__tests__/Input.test.js b/__tests__/Input.test.js new file mode 100644 index 0000000..2eda163 --- /dev/null +++ b/__tests__/Input.test.js @@ -0,0 +1,68 @@ +import React from 'react' +import { mount } from 'enzyme' +import Input from '../src/Input' + +describe('Input', () => { + it('should render without crashing', () => { + mount() + }) + + it('should render with base styles', () => { + const expected = + 'block w-full text-sm dark:border-gray-600 focus:outline-none dark:text-gray-300 form-input' + const wrapper = mount() + + expect(wrapper.find('input').getDOMNode().getAttribute('class')).toContain(expected) + }) + + it('should render with active styles', () => { + const expected = + 'focus:border-purple-400 focus:shadow-outline-purple dark:focus:border-gray-600 dark:focus:shadow-outline-gray dark:bg-gray-700' + const wrapper = mount() + + expect(wrapper.find('input').getDOMNode().getAttribute('class')).toContain(expected) + }) + + it('should render with disabled styles', () => { + const expected = 'cursor-not-allowed opacity-50 bg-gray-300 dark:bg-gray-800' + const wrapper = mount() + + expect(wrapper.find('input').getDOMNode().getAttribute('class')).toContain(expected) + }) + + it('should render with valid styles', () => { + const expected = + 'border-green-600 dark:bg-gray-700 focus:border-green-400 focus:shadow-outline-green' + const wrapper = mount() + + expect(wrapper.find('input').getDOMNode().getAttribute('class')).toContain(expected) + }) + + it('should render with invalid styles', () => { + const expected = + 'border-red-600 dark:text-gray-300 dark:bg-gray-700 focus:border-red-400 focus:shadow-outline-red' + const wrapper = mount() + + expect(wrapper.find('input').getDOMNode().getAttribute('class')).toContain(expected) + }) + + it('should render with radio styles', () => { + const expected = + 'text-purple-600 form-radio focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray' + const wrapper = mount() + + expect(wrapper.find('input[type="radio"]').getDOMNode().getAttribute('class')).toContain( + expected + ) + }) + + it('should render with checkbox styles', () => { + const expected = + 'text-purple-600 form-checkbox focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray' + const wrapper = mount() + + expect(wrapper.find('input[type="checkbox"]').getDOMNode().getAttribute('class')).toContain( + expected + ) + }) +}) diff --git a/src/Input.js b/src/Input.js new file mode 100644 index 0000000..bd4a766 --- /dev/null +++ b/src/Input.js @@ -0,0 +1,65 @@ +import React, { useContext } from 'react' +import classNames from 'classnames' +import PropTypes from 'prop-types' +import { ThemeContext } from './context/ThemeContext' +import defaultTheme from './themes/default' + +const Input = React.forwardRef(function Input(props, ref) { + const { valid, disabled, className, type, ...other } = props + + const { input } = useContext(ThemeContext) || defaultTheme + + const baseStyle = input.base + const activeStyle = input.active + const disabledStyle = input.disabled + const validStyle = input.valid + const invalidStyle = input.invalid + const radioStyle = input.radio + const checkStyle = input.checkbox + + function hasValidation(valid) { + return valid !== undefined + } + + function validationStyle(valid) { + if (hasValidation(valid)) { + return valid ? validStyle : invalidStyle + } + } + + function typeStyle(type) { + switch (type) { + case 'radio': + return radioStyle + case 'checkbox': + return checkStyle + default: + return baseStyle + } + } + + const cls = classNames( + typeStyle(type), + // don't apply activeStyle if has valid or disabled + !hasValidation(valid) && !disabled && activeStyle, + // don't apply disabledStyle if has valid + !hasValidation(valid) && disabled && disabledStyle, + validationStyle(valid), + className + ) + + return +}) + +Input.propTypes = { + type: PropTypes.string, + disabled: PropTypes.bool, + valid: PropTypes.bool, + className: PropTypes.string, +} + +Input.defaultProps = { + type: 'text', +} + +export default Input diff --git a/src/index.js b/src/index.js index c6373a5..2e867f5 100644 --- a/src/index.js +++ b/src/index.js @@ -2,3 +2,4 @@ export { default as Button } from './Button' export { default as Card } from './Card' export { default as CardBody } from './CardBody' export { default as HelperText } from './HelperText' +export { default as Input } from './Input' diff --git a/src/themes/default.js b/src/themes/default.js index 9571dcd..f981569 100644 --- a/src/themes/default.js +++ b/src/themes/default.js @@ -1,4 +1,19 @@ export default { + // Input + input: { + base: + 'block w-full text-sm dark:border-gray-600 focus:outline-none dark:text-gray-300 form-input', + active: + 'focus:border-purple-400 focus:shadow-outline-purple dark:focus:border-gray-600 dark:focus:shadow-outline-gray dark:bg-gray-700', + disabled: 'cursor-not-allowed opacity-50 bg-gray-300 dark:bg-gray-800', + valid: 'border-green-600 dark:bg-gray-700 focus:border-green-400 focus:shadow-outline-green', + invalid: + 'border-red-600 dark:text-gray-300 dark:bg-gray-700 focus:border-red-400 focus:shadow-outline-red', + radio: + 'text-purple-600 form-radio focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray', + checkbox: + 'text-purple-600 form-checkbox focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray', + }, // HelperText helperText: { base: 'text-xs',