-
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.
- Loading branch information
Federico Zivolo
committed
Jan 7, 2019
1 parent
51ff519
commit ca924ba
Showing
6 changed files
with
370 additions
and
1 deletion.
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,22 @@ | ||
The `InputColor` component shares the same API of the HTML `input[type=color]` element. | ||
|
||
```js | ||
<InputColor onChange={e => console.log(e.target.value)} /> | ||
``` | ||
|
||
It also supports all the `InputText` and `Button` properties: | ||
|
||
```js | ||
<InputColor size="small" defaultValue="#FFF000" importance="primary" /> | ||
``` | ||
|
||
The component supports both uncontrolled and controlled API styles: | ||
|
||
```js | ||
initialState = { value: '#00FF00' }; | ||
<InputColor | ||
size="large" | ||
value={state.value} | ||
onChange={evt => setState({ value: evt.target.value })} | ||
/>; | ||
``` |
135 changes: 135 additions & 0 deletions
135
packages/react-forms/src/InputColor/__snapshots__/index.test.js.snap
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,135 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`renders the expected markup 1`] = ` | ||
.emotion-7 { | ||
all: unset; | ||
display: inline-block; | ||
border-radius: 2px; | ||
-webkit-transition: 0.2s ease-in-out border-color; | ||
transition: 0.2s ease-in-out border-color; | ||
border: 1px solid #8F9BA3; | ||
} | ||
.emotion-7:focus-within { | ||
border-color: #00c1bb; | ||
} | ||
.emotion-7[data-invalid='true'] { | ||
border-color: #E61E27; | ||
} | ||
.emotion-4 { | ||
display: -webkit-inline-box; | ||
display: -webkit-inline-flex; | ||
display: -ms-inline-flexbox; | ||
display: inline-flex; | ||
position: relative; | ||
height: 32px; | ||
} | ||
.emotion-1 { | ||
opacity: 0; | ||
position: absolute; | ||
width: 100%; | ||
height: 100%; | ||
overflow: hidden; | ||
top: 0; | ||
left: 0; | ||
bottom: 0; | ||
right: 0; | ||
cursor: pointer; | ||
} | ||
.emotion-1:focus:not(:focus-visible) ~ button { | ||
box-shadow: none; | ||
} | ||
.emotion-0 { | ||
border-radius: 1px 0 0 1px; | ||
border-left: 0; | ||
border-top: 0; | ||
border-bottom: 0; | ||
} | ||
.emotion-3 { | ||
border-radius: 0 1px 1px 0; | ||
border-right: 0; | ||
border-top: 0; | ||
border-bottom: 0; | ||
} | ||
<InputColor | ||
onChange={[Function]} | ||
> | ||
<ForwardRef | ||
className="emotion-9 emotion-6" | ||
onChange={[Function]} | ||
> | ||
<InputGroup | ||
className="emotion-9 emotion-6" | ||
> | ||
<fieldset | ||
className="emotion-6 emotion-7 emotion-8" | ||
data-invalid={false} | ||
onChange={[Function]} | ||
onInvalid={[Function]} | ||
> | ||
<input | ||
className="emotion-0" | ||
key=".0" | ||
maxLength={7} | ||
onChange={[Function]} | ||
value="#000000" | ||
/> | ||
<Wrapper | ||
key=".1" | ||
> | ||
<div | ||
className="emotion-4 emotion-5" | ||
> | ||
<ColorSelector | ||
onBlur={[Function]} | ||
onChange={[Function]} | ||
onFocus={[Function]} | ||
onMouseDown={[Function]} | ||
onMouseEnter={[Function]} | ||
onMouseLeave={[Function]} | ||
onMouseUp={[Function]} | ||
type="color" | ||
value="#000000" | ||
> | ||
<input | ||
className="emotion-1 emotion-2" | ||
onBlur={[Function]} | ||
onChange={[Function]} | ||
onFocus={[Function]} | ||
onMouseDown={[Function]} | ||
onMouseEnter={[Function]} | ||
onMouseLeave={[Function]} | ||
onMouseUp={[Function]} | ||
type="color" | ||
value="#000000" | ||
/> | ||
</ColorSelector> | ||
<button | ||
className="emotion-3" | ||
data-state="" | ||
importance="secondary" | ||
tabIndex={-1} | ||
> | ||
<x-icon | ||
name="tint" | ||
style={ | ||
Object { | ||
"color": "#000000", | ||
} | ||
} | ||
/> | ||
</button> | ||
</div> | ||
</Wrapper> | ||
</fieldset> | ||
</InputGroup> | ||
</ForwardRef> | ||
</InputColor> | ||
`; |
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,128 @@ | ||
// @flow | ||
import * as React from 'react'; | ||
import styled from '@emotion/styled/macro'; | ||
import callAll from '../utils/callAll'; | ||
import InputGroup from '../InputGroup'; | ||
import InputText, { HEIGHT } from '../InputText'; | ||
import Button from '../Button'; | ||
import { Icon } from '@quid/react-core'; | ||
|
||
type Props = { | ||
onChange?: (SyntheticInputEvent<HTMLInputElement>) => void, | ||
className?: string, | ||
style?: Object, | ||
innerRef?: React.ElementRef<any>, | ||
disabled?: boolean, | ||
defaultValue?: string, | ||
size?: 'regular' | 'small' | 'large', | ||
}; | ||
|
||
type State = { | ||
color: string, | ||
buttonState: Array<string>, | ||
}; | ||
|
||
const addState = (...states) => ({ buttonState }) => ({ | ||
buttonState: [...new Set([...buttonState, ...states])], | ||
}); | ||
const removeState = (...states) => ({ buttonState }) => ({ | ||
buttonState: buttonState.filter(s => !states.includes(s)), | ||
}); | ||
|
||
const ColorSelector = styled.input` | ||
opacity: 0; | ||
position: absolute; | ||
width: 100%; | ||
height: 100%; | ||
overflow: hidden; | ||
top: 0; | ||
left: 0; | ||
bottom: 0; | ||
right: 0; | ||
cursor: pointer; | ||
&:focus:not(:focus-visible) ~ ${Button} { | ||
box-shadow: none; | ||
} | ||
`; | ||
|
||
const Wrapper = styled.div` | ||
display: inline-flex; | ||
position: relative; | ||
height: ${props => HEIGHT[props.size || 'regular']}px; | ||
`; | ||
|
||
class InnerInput extends React.Component<Props, State> { | ||
state = { | ||
color: this.props.defaultValue || '#000000', | ||
buttonState: [], | ||
}; | ||
|
||
static getDerivedStateFromProps(props, state) { | ||
return { color: props.value || state.color }; | ||
} | ||
|
||
input = React.createRef(); | ||
|
||
updateColor = ({ target: { value } }) => | ||
this.setState({ color: String(value).toUpperCase() }); | ||
|
||
render() { | ||
const { | ||
className, | ||
style, | ||
disabled, | ||
innerRef, | ||
onChange, | ||
...props | ||
} = this.props; | ||
return ( | ||
<InputGroup style={style} className={className}> | ||
{cn => ( | ||
<InputText | ||
{...props} | ||
className={cn} | ||
onChange={callAll(this.updateColor, onChange)} | ||
value={this.state.color} | ||
maxLength={7} | ||
disabled={disabled} | ||
/> | ||
)} | ||
{cn => ( | ||
<Wrapper size={props.size}> | ||
<ColorSelector | ||
type="color" | ||
value={this.state.color} | ||
onChange={callAll(this.updateColor, onChange)} | ||
onMouseDown={() => this.setState(addState('active'))} | ||
onMouseUp={() => this.setState(removeState('active'))} | ||
onMouseEnter={() => this.setState(addState('hover'))} | ||
onMouseLeave={() => this.setState(removeState('hover', 'active'))} | ||
onFocus={() => this.setState(addState('focus'))} | ||
onBlur={() => this.setState(removeState('focus', 'active'))} | ||
ref={innerRef} | ||
disabled={disabled} | ||
/> | ||
<Button | ||
importance="secondary" | ||
{...props} | ||
className={cn} | ||
tabIndex={-1} | ||
data-state={this.state.buttonState.join(' ')} | ||
disabled={disabled} | ||
> | ||
<Icon name="tint" style={{ color: this.state.color }} /> | ||
</Button> | ||
</Wrapper> | ||
)} | ||
</InputGroup> | ||
); | ||
} | ||
} | ||
|
||
const InputColor: React.StatelessFunctionalComponent<Props> = styled( | ||
React.forwardRef((props, ref) => <InnerInput {...props} innerRef={ref} />) | ||
)(); | ||
|
||
// @component | ||
export default InputColor; |
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,80 @@ | ||
// @flow | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { mount } from 'enzyme'; | ||
import InputColor from './index'; | ||
|
||
jest.mock('../InputText', () => ({ | ||
__esModule: true, | ||
default: 'input', | ||
HEIGHT: jest.requireActual('../InputText').HEIGHT, | ||
})); | ||
jest.mock('../Button', () => 'button'); | ||
jest.mock('@quid/react-core', () => ({ | ||
Icon: 'x-icon', | ||
})); | ||
|
||
const noop = () => undefined; | ||
|
||
it('renders the expected markup', () => { | ||
const wrapper = mount(<InputColor onChange={noop} />); | ||
|
||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
|
||
it('responds to user interaction on fake button', () => { | ||
const wrapper = mount(<InputColor onChange={noop} />); | ||
|
||
wrapper.find('input[type="color"]').simulate('mouseEnter'); | ||
wrapper.find('input[type="color"]').simulate('mouseDown'); | ||
wrapper.find('input[type="color"]').simulate('focus'); | ||
|
||
expect(wrapper.find('button').prop('data-state')).toMatchInlineSnapshot( | ||
`"hover active focus"` | ||
); | ||
|
||
wrapper.find('input[type="color"]').simulate('mouseLeave'); | ||
wrapper.find('input[type="color"]').simulate('mouseUp'); | ||
wrapper.find('input[type="color"]').simulate('blur'); | ||
|
||
expect(wrapper.find('button').prop('data-state')).toMatchInlineSnapshot(`""`); | ||
}); | ||
|
||
it('renders a disabled component', () => { | ||
const wrapper = mount(<InputColor disabled onChange={noop} />); | ||
|
||
expect( | ||
wrapper | ||
.find('input') | ||
.at(0) | ||
.prop('disabled') | ||
).toBe(true); | ||
}); | ||
|
||
it('triggers onChange when color is changed using the `type=color` input', () => { | ||
const handleChange = jest.fn(); | ||
const wrapper = mount(<InputColor onChange={handleChange} />); | ||
|
||
wrapper | ||
.find('input') | ||
.at(1) | ||
.simulate('change', { target: { value: '#000000' } }); | ||
|
||
expect(handleChange.mock.calls[0][0]).toMatchObject({ | ||
target: { value: '#000000' }, | ||
}); | ||
}); | ||
|
||
it('triggers onChange when color is changed using the `type=text` input', () => { | ||
const handleChange = jest.fn(); | ||
const wrapper = mount(<InputColor onChange={handleChange} />); | ||
|
||
wrapper | ||
.find('input') | ||
.at(0) | ||
.simulate('change', { target: { value: '#000000' } }); | ||
|
||
expect(handleChange.mock.calls[0][0]).toMatchObject({ | ||
target: { value: '#000000' }, | ||
}); | ||
}); |
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
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