-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(community-input-group): add input group component
- Loading branch information
1 parent
979b763
commit 3560e25
Showing
8 changed files
with
369 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)" | ||
/> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const InputGroup = require('./dist/index.cjs') | ||
|
||
module.exports = InputGroup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import InputGroup from './dist/index.es' | ||
|
||
export default InputGroup |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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', | ||
}, | ||
})) |