Skip to content

Commit 285d5a0

Browse files
authored
Merge pull request #98 from arturbien/feature/color-input
Feature/color input
2 parents f2b1510 + fe2796a commit 285d5a0

File tree

5 files changed

+218
-1
lines changed

5 files changed

+218
-1
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import React from 'react';
2+
import propTypes from 'prop-types';
3+
4+
import styled, { css } from 'styled-components';
5+
import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled';
6+
import Button from '../Button/Button';
7+
import Bar from '../Bar/Bar';
8+
9+
const StyledBar = styled(Bar)`
10+
height: 23px;
11+
position: relative;
12+
top: -1px;
13+
`;
14+
15+
export const StyledColorInput = styled.input`
16+
box-sizing: border-box;
17+
width: 100%;
18+
height: 100%;
19+
position: absolute;
20+
left: 0;
21+
top: 0;
22+
opacity: 0;
23+
`;
24+
25+
// TODO replace with SVG icon
26+
const ColorPreview = styled.div`
27+
box-sizing: border-box;
28+
height: 21px;
29+
display: inline-block;
30+
width: 35px;
31+
margin-right: 0.5rem;
32+
33+
background: ${({ color }) => color};
34+
35+
${({ isDisabled }) =>
36+
isDisabled
37+
? css`
38+
border: 2px solid ${({ theme }) => theme.textDisabled};
39+
filter: drop-shadow(
40+
1px 1px 0px ${({ theme }) => theme.textDisabledShadow}
41+
);
42+
`
43+
: css`
44+
border: 2px solid ${({ theme }) => theme.text};
45+
`}
46+
`;
47+
48+
const ChevronIcon = styled.span`
49+
position: relative;
50+
width: 0px;
51+
height: 0px;
52+
border-left: 6px solid transparent;
53+
border-right: 6px solid transparent;
54+
display: inline-block;
55+
margin-left: 0.5rem;
56+
57+
${({ isDisabled }) =>
58+
isDisabled
59+
? css`
60+
border-top: 6px solid ${({ theme }) => theme.textDisabled};
61+
filter: drop-shadow(
62+
1px 1px 0px ${({ theme }) => theme.textDisabledShadow}
63+
);
64+
`
65+
: css`
66+
border-top: 6px solid ${({ theme }) => theme.text};
67+
`}
68+
`;
69+
70+
// TODO make sure all aria and role attributes are in place
71+
const ColorInput = React.forwardRef(function ColorInput(props, ref) {
72+
const {
73+
value,
74+
defaultValue,
75+
onChange,
76+
disabled,
77+
variant,
78+
...otherProps
79+
} = props;
80+
81+
const [valueDerived, setValueState] = useControlledOrUncontrolled({
82+
value,
83+
defaultValue
84+
});
85+
86+
const handleChange = e => {
87+
const color = e.target.value;
88+
setValueState(color);
89+
if (onChange) {
90+
onChange(e);
91+
}
92+
};
93+
94+
return (
95+
// we only need button styles, so we display it as a div and reset type attribute
96+
<Button isDisabled={disabled} as='div' type={null} variant={variant}>
97+
<ColorPreview
98+
color={valueDerived}
99+
isDisabled={disabled}
100+
role='presentation'
101+
/>
102+
{variant === 'default' && <StyledBar />}
103+
<StyledColorInput
104+
onChange={handleChange}
105+
readOnly={disabled}
106+
disabled={disabled}
107+
value={valueDerived || '#008080'}
108+
type='color'
109+
ref={ref}
110+
{...otherProps}
111+
/>
112+
<ChevronIcon isDisabled={disabled} />
113+
</Button>
114+
);
115+
});
116+
117+
ColorInput.defaultProps = {
118+
value: undefined,
119+
defaultValue: undefined,
120+
disabled: false,
121+
variant: 'default',
122+
onChange: () => {}
123+
};
124+
125+
ColorInput.propTypes = {
126+
value: propTypes.string,
127+
defaultValue: propTypes.string,
128+
onChange: propTypes.func,
129+
disabled: propTypes.bool,
130+
variant: propTypes.oneOf(['default', 'flat'])
131+
};
132+
export default ColorInput;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
3+
import { renderWithTheme } from '../../../test/utils';
4+
import ColorInput from './ColorInput';
5+
6+
function rgb2hex(str) {
7+
const rgb = str.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
8+
function hex(x) {
9+
return `0${parseInt(x, 10).toString(16)}`.slice(-2);
10+
}
11+
return `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}`;
12+
}
13+
14+
describe('<ColorInput />', () => {
15+
// TODO
16+
it('should call handlers', () => {});
17+
18+
it('should properly pass value to input element', () => {
19+
const color = '#f0f0dd';
20+
const { container } = renderWithTheme(<ColorInput value={color} />);
21+
const input = container.querySelector(`[type="color"]`);
22+
23+
expect(input.value).toBe(color);
24+
});
25+
it('should display current color', () => {
26+
const color = '#f0f0dd';
27+
const { getByRole } = renderWithTheme(<ColorInput value={color} />);
28+
const colorPreview = getByRole('presentation');
29+
const displayedColor = window.getComputedStyle(colorPreview, null)
30+
.background;
31+
32+
const displayedColorHex = rgb2hex(displayedColor);
33+
expect(displayedColorHex).toBe(color);
34+
});
35+
36+
describe('prop: disabled', () => {
37+
it('should render disabled input', () => {
38+
const { container } = renderWithTheme(<ColorInput disabled />);
39+
const input = container.querySelector(`[type="color"]`);
40+
41+
expect(input).toHaveAttribute('disabled');
42+
});
43+
44+
it('should be overridden by props', () => {
45+
const { container, rerender } = renderWithTheme(<ColorInput disabled />);
46+
rerender(<ColorInput disabled={false} />);
47+
const input = container.querySelector(`[type="color"]`);
48+
49+
expect(input).not.toHaveAttribute('disabled');
50+
});
51+
});
52+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import { storiesOf } from '@storybook/react';
3+
4+
import styled from 'styled-components';
5+
6+
import { ColorInput, Cutout } from '..';
7+
8+
const Wrapper = styled.div`
9+
background: ${({ theme }) => theme.material};
10+
padding: 5rem;
11+
`;
12+
const StyledCutout = styled(Cutout)`
13+
background: ${({ theme }) => theme.canvas};
14+
display: inline-block;
15+
padding: 4rem;
16+
`;
17+
storiesOf('ColorInput', module)
18+
.addDecorator(story => <Wrapper>{story()}</Wrapper>)
19+
.add('default', () =>
20+
React.createElement(() => <ColorInput defaultValue='#00f' />)
21+
)
22+
23+
.add('disabled', () =>
24+
React.createElement(() => <ColorInput disabled defaultValue='#00f' />)
25+
)
26+
.add('flat', () =>
27+
React.createElement(() => (
28+
<StyledCutout>
29+
<ColorInput variant='flat' defaultValue='#00f' />
30+
</StyledCutout>
31+
))
32+
);

src/components/Slider/Slider.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('<Slider />', () => {
114114
});
115115

116116
describe('prop: disabled', () => {
117-
it("shouldn't render disabled slider", () => {
117+
it('should render disabled slider', () => {
118118
const { getByRole, container } = renderWithTheme(
119119
<Slider
120120
step={null}

src/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export { default as Tooltip } from './Tooltip/Tooltip';
3838
export { default as Window } from './Window/Window';
3939
export { default as WindowContent } from './WindowContent/WindowContent';
4040
export { default as WindowHeader } from './WindowHeader/WindowHeader';
41+
export { default as ColorInput } from './ColorInput/ColorInput';
4142

4243
export {
4344
default as LoadingIndicator

0 commit comments

Comments
 (0)