Skip to content

Commit

Permalink
feat(community-input-group): add input group component
Browse files Browse the repository at this point in the history
  • Loading branch information
zyl-edison-telus authored and theetrain committed Mar 20, 2020
1 parent 979b763 commit 3560e25
Show file tree
Hide file tree
Showing 8 changed files with 369 additions and 0 deletions.
141 changes: 141 additions & 0 deletions packages/InputGroup/InputGroup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useState, useEffect, useMemo, createRef } from 'react'
import PropTypes from 'prop-types'
import DecorativeIcon from '@tds/core-decorative-icon'
import { Close } from '@tds/core-interactive-icon'
import A11yContent from '@tds/core-a11y-content'
import Box from '@tds/core-box'
import Text from '@tds/core-text'
import Strong from '@tds/core-strong'
import { uniqueId } from '@tds/util-helpers'
import { InputGroupStyle, LabelStyle } from './style'

/**
* @version ./package.json
*/
const InputGroup = ({
id,
className,
placeholder,
defaultValue,
buttonText,
label,
hint,
onChange,
onKeyDown,
onButtonClick,
onClearButtonClick,
}) => {
const inputRef = createRef()
const [inputValue, setInputValue] = useState('')
const inputId = useMemo(() => (label ? uniqueId(label) : id), [label, id])

useEffect(() => {
if (defaultValue) {
setInputValue(defaultValue)
}
}, [defaultValue])

return (
<Box className={className} between={1}>
{label && (
<LabelStyle htmlFor={inputId}>
<Text>
<Strong>{label}</Strong>
</Text>
<Text>{hint}</Text>
</LabelStyle>
)}
<InputGroupStyle hasValue={!!inputValue}>
<input
id={inputId}
type="text"
value={inputValue}
placeholder={placeholder}
onChange={e => {
setInputValue(e.target.value)
if (onChange) onChange(e)
}}
onKeyDown={onKeyDown}
ref={inputRef}
/>
{!!inputValue && (
<Close
a11yText="Close"
onClick={e => {
setInputValue('')
inputRef.current.focus()
if (onClearButtonClick) onClearButtonClick(e)
}}
/>
)}
<button type="button" onClick={onButtonClick}>
<A11yContent>{buttonText}</A11yContent>
<DecorativeIcon symbol="spyglass" variant="secondary" />
</button>
</InputGroupStyle>
</Box>
)
}

InputGroup.propTypes = {
/**
* The unique identifier
*/
id: PropTypes.string,
/**
* The class name
*/
className: PropTypes.string,
/**
* The textbox placeholder
*/
placeholder: PropTypes.string,
/**
* The textbox default value
*/
defaultValue: PropTypes.string,
/**
* The button text for accessibility
*/
buttonText: PropTypes.string,
/**
* The label displayed above the textbox
*/
label: PropTypes.string,
/**
* The hint displayed below the label
*/
hint: PropTypes.string,
/**
* The event triggered every time when making changes to the textbox
*/
onChange: PropTypes.func,
/**
* The event triggered every time when pressing any keyboard key
*/
onKeyDown: PropTypes.func,
/**
* The event triggered every time a user click the button
*/
onButtonClick: PropTypes.func,
/**
* The event triggered every time a user click the clear/cross button
*/
onClearButtonClick: PropTypes.func,
}

InputGroup.defaultProps = {
id: '',
className: '',
placeholder: '',
defaultValue: '',
buttonText: '',
label: '',
hint: '',
onChange: undefined,
onKeyDown: undefined,
onButtonClick: undefined,
onClearButtonClick: undefined,
}

export default InputGroup
12 changes: 12 additions & 0 deletions packages/InputGroup/InputGroup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
```jsx
<InputGroup placeholder="Placeholder text" label="Label" hint="Helper text (optional)" />
```

```jsx
<InputGroup
placeholder="Placeholder text"
defaultValue="AMC"
label="Label"
hint="Helper text (optional)"
/>
```
96 changes: 96 additions & 0 deletions packages/InputGroup/__tests__/InputGroup.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react'
import { shallow, mount } from 'enzyme'
import InputGroup from '../InputGroup'

describe('InputGroup', () => {
let onChangeMockFn
let onKeyDownMockFn
let onButtonClickMockFn
let onClearButtonClickMockFn

let props

const doShallow = (properties = {}) => shallow(<InputGroup {...properties} />)

const doMount = (properties = {}) => mount(<InputGroup {...properties} />)
beforeEach(() => {
onChangeMockFn = jest.fn()
onKeyDownMockFn = jest.fn()
onButtonClickMockFn = jest.fn()
onClearButtonClickMockFn = jest.fn()

props = {
id: 'someId',
className: 'someClassName',
placeholder: 'some placeholder',
defaultValue: 'default value',
buttonText: 'button text',
onChange: onChangeMockFn,
onKeyDown: onKeyDownMockFn,
onButtonClick: onButtonClickMockFn,
onClearButtonClick: onClearButtonClickMockFn,
}
})

describe('Render', () => {
it('renders InputGroup with label and hint', () => {
const inputGroup = doMount(props)

expect(inputGroup.find('label').exists()).toEqual(false)
})
it('renders InputGroup with label and hint', () => {
props.label = 'some label'
props.hint = 'some hint'
const inputGroup = doMount(props)
const texts = inputGroup.find('Text')

expect(texts.at(0).text()).toEqual('some label')
expect(texts.at(1).text()).toEqual('some hint')
})
})

describe('Events', () => {
describe('Input#onChange', () => {
it('shows clear button', () => {
props.defaultValue = ''
props.onChange = undefined
const inputGroup = doShallow(props)
const inputWrapper = inputGroup.find('input')
expect(inputGroup.find('Close').exists()).toEqual(false)
inputWrapper.simulate('change', { target: { value: 'input text' } })
expect(inputGroup.find('Close').exists()).toEqual(true)
})

it('calls onChange prop function', () => {
props.defaultValue = ''
const inputGroup = doShallow(props)
const inputWrapper = inputGroup.find('input')
inputWrapper.simulate('change', { target: { value: 'input text' } })
expect(onChangeMockFn.mock.calls.length).toEqual(1)
})
})

describe('Close#onClick', () => {
it('hides clear button', () => {
props.onClearButtonClick = undefined
const inputGroup = doMount(props)
const inputWrapper = inputGroup.find('input')
inputWrapper.simulate('change', { target: { value: 'input text' } })
const closeButtonWrapper = inputGroup.find('Close')
expect(closeButtonWrapper.exists()).toEqual(true)
closeButtonWrapper.simulate('click')
expect(inputGroup.find('Close').exists()).toEqual(false)
})

it('calls onClearButtonClick prop function', () => {
const inputGroup = doMount(props)
const inputWrapper = inputGroup.find('input')
inputWrapper.simulate('change', { target: { value: 'input text' } })
const closeButtonWrapper = inputGroup.find('Close')
expect(closeButtonWrapper.exists()).toEqual(true)
closeButtonWrapper.simulate('click')
expect(onClearButtonClickMockFn.mock.calls.length).toEqual(1)
})
})
})
})
3 changes: 3 additions & 0 deletions packages/InputGroup/index.cjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const InputGroup = require('./dist/index.cjs')

module.exports = InputGroup
3 changes: 3 additions & 0 deletions packages/InputGroup/index.es.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import InputGroup from './dist/index.es'

export default InputGroup
38 changes: 38 additions & 0 deletions packages/InputGroup/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@tds/community-input-group",
"version": "1.0.0-0",
"description": "",
"main": "index.cjs.js",
"module": "index.es.js",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/telus/tds-community.git"
},
"publishConfig": {
"access": "public"
},
"author": "TELUS digital",
"engines": {
"node": ">=8"
},
"bugs": {
"url": "https://github.com/telus/tds-community/issues"
},
"homepage": "http://tds.telus.com",
"peerDependencies": {
"react": "^16.8.2",
"react-dom": "^16.8.2",
"styled-components": "^4.1.3"
},
"dependencies": {
"@tds/core-a11y-content": "^2.1.5",
"@tds/core-box": "^2.1.2",
"@tds/core-decorative-icon": "^2.6.4",
"@tds/core-interactive-icon": "^1.4.3",
"@tds/core-strong": "^2.1.10",
"@tds/core-text": "^3.0.4",
"@tds/util-helpers": "^1.2.0",
"prop-types": "^15.6.2"
}
}
7 changes: 7 additions & 0 deletions packages/InputGroup/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import configure from '../../config/rollup.config'
import { dependencies } from './package.json'

export default configure({
input: './InputGroup.jsx',
dependencies,
})
69 changes: 69 additions & 0 deletions packages/InputGroup/style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import styled from 'styled-components'

export const LabelStyle = styled.label(() => ({
span: {
fontSize: '14px',
color: '#2A2C2E',
display: 'block',
},
}))

export const InputGroupStyle = styled.div(({ hasValue }) => ({
display: 'inline-flex',
flexWrap: 'nowrap',
height: '52px',
width: '100%',
borderRadius: '5px',
'&:hover': {
boxShadow: '0 0 0 1px #4B286D',
input: {
borderColor: '#4B286D',
},
},
input: {
height: '100%',
borderLeft: '1px solid #D8D8D8',
borderTop: '1px solid #D8D8D8',
borderBottom: '1px solid #D8D8D8',
borderRight: 'none',
borderRadius: '4px 0 0 4px',
color: '#2A2C2E',
flexGrow: 1,
marginRight: hasValue ? '-52px' : '0',
textIndent: '15px',
'&::-ms-clear': {
display: 'none',
},
'&::placeholder': {
color: '#71757B',
},
'&:active': {
borderColor: '#4B286D',
},
'&:focus': {
borderColor: '#4B286D',
},
},
button: {
height: '100%',
width: '52px',
cursor: 'pointer',
'&:last-child': {
backgroundColor: '#462C6A',
borderLeft: 'none',
borderTop: '1px solid #462C6A',
borderRight: '1px solid #462C6A',
borderBottom: '1px solid #462C6A',
borderRadius: '0 4px 4px 0',
i: {
color: '#FFFFFF',
},
},
},
'input:focus, button:last-child:focus': {
outlineWidth: '4px',
outlineStyle: 'solid',
outlineColor: 'rgba(75, 40, 109, 0.5)',
outlineOffset: '1px',
},
}))

0 comments on commit 3560e25

Please sign in to comment.