Skip to content

Commit c8e0910

Browse files
authored
Merge pull request #100 from arturbien/feature/window-enhancements-and-tests
Feature/window enhancements and tests
2 parents 8fd2b8b + 9243875 commit c8e0910

File tree

9 files changed

+187
-50
lines changed

9 files changed

+187
-50
lines changed

src/components/Button/Button.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const commonButtonStyles = css`
3030
padding-top: ${({ active, isDisabled }) => active && !isDisabled && '2px'};
3131
`;
3232

33-
const StyledButton = styled.button`
33+
export const StyledButton = styled.button`
3434
${({ variant, theme, active, isDisabled, primary }) =>
3535
variant === 'flat'
3636
? css`

src/components/Window/Window.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,62 @@
11
import React from 'react';
22
import propTypes from 'prop-types';
33

4-
import styled from 'styled-components';
4+
import styled, { css } from 'styled-components';
55
import { createBorderStyles, createBoxStyles } from '../common';
66

77
const StyledWindow = styled.div`
88
position: relative;
9-
padding: 2px;
9+
padding: 4px;
1010
${createBorderStyles({ windowBorders: true })}
1111
${createBoxStyles()}
1212
`;
13+
const ResizeHandle = styled.span`
14+
${({ theme }) => css`
15+
display: inline-block;
16+
position: absolute;
17+
bottom: 4px;
18+
right: 4px;
19+
width: 25px;
20+
height: 25px;
21+
background-image: linear-gradient(
22+
135deg,
23+
${theme.borderLightest} 16.67%,
24+
${theme.material} 16.67%,
25+
${theme.material} 33.33%,
26+
${theme.borderDark} 33.33%,
27+
${theme.borderDark} 50%,
28+
${theme.borderLightest} 50%,
29+
${theme.borderLightest} 66.67%,
30+
${theme.material} 66.67%,
31+
${theme.material} 83.33%,
32+
${theme.borderDark} 83.33%,
33+
${theme.borderDark} 100%
34+
);
35+
background-size: 8.49px 8.49px;
36+
clip-path: polygon(100% 0px, 0px 100%, 100% 100%);
37+
border-width: 2px;
38+
border-style: solid;
39+
border-color: ${theme.material};
40+
cursor: nwse-resize;
41+
`}
42+
`;
1343

14-
const Window = ({ shadow, className, children, ...otherProps }) => (
15-
<StyledWindow shadow={shadow} className={className} {...otherProps} swag>
44+
const Window = ({ resizable, shadow, children, ...otherProps }) => (
45+
<StyledWindow shadow={shadow} {...otherProps}>
1646
{children}
47+
{resizable && <ResizeHandle data-testid='resizeHandle' />}
1748
</StyledWindow>
1849
);
1950

2051
Window.defaultProps = {
52+
resizable: false,
2153
shadow: true,
22-
className: '',
2354
children: null
2455
};
2556

2657
Window.propTypes = {
2758
shadow: propTypes.bool,
28-
className: propTypes.string,
59+
resizable: propTypes.bool,
2960
children: propTypes.node
3061
};
3162

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
3+
import { renderWithTheme } from '../../../test/utils';
4+
5+
import Window from './Window';
6+
7+
describe('<Window />', () => {
8+
it('renders Window', () => {
9+
const { container } = renderWithTheme(<Window />);
10+
const window = container.firstChild;
11+
12+
expect(window).toBeInTheDocument();
13+
});
14+
15+
it('renders children', () => {
16+
const textContent = 'Hi there!';
17+
const { getByText } = renderWithTheme(
18+
<Window>
19+
<span>{textContent}</span>
20+
</Window>
21+
);
22+
expect(getByText(textContent)).toBeInTheDocument();
23+
});
24+
25+
describe('prop: resizable', () => {
26+
it('does not render resize handle by default', () => {
27+
const { queryByTestId } = renderWithTheme(<Window />);
28+
29+
expect(queryByTestId('resizeHandle')).not.toBeInTheDocument();
30+
});
31+
it('renders resize handle when set to true', () => {
32+
const { queryByTestId } = renderWithTheme(<Window resizable />);
33+
34+
expect(queryByTestId('resizeHandle')).toBeInTheDocument();
35+
});
36+
});
37+
});

src/components/Window/Window.stories.js

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,9 @@ storiesOf('Window', module)
2828
}}
2929
>
3030
<span>react95.exe</span>
31-
<Button
32-
style={{ marginRight: '-6px', marginTop: '1px' }}
33-
size='sm'
34-
square
35-
>
31+
<Button>
3632
<span style={{ fontWeight: 'bold', transform: 'translateY(-1px)' }}>
37-
x
33+
X
3834
</span>
3935
</Button>
4036
</WindowHeader>
@@ -59,16 +55,17 @@ storiesOf('Window', module)
5955
</WindowContent>
6056
</Window>
6157
))
62-
.add('no shadow', () => (
63-
<Window shadow={false}>
58+
.add('resizable', () => (
59+
<Window resizable>
6460
<WindowHeader>react95.exe</WindowHeader>
6561
<WindowContent>
66-
<ul>
67-
<li>something here</li>
68-
<li>something here</li>
69-
<li>something here</li>
70-
<li>something here</li>
71-
</ul>
62+
Resizable Window displays resize handle in bottom right corner
7263
</WindowContent>
7364
</Window>
65+
))
66+
.add('not Active', () => (
67+
<Window>
68+
<WindowHeader isActive={false}>react95.exe</WindowHeader>
69+
<WindowContent>I am not active</WindowContent>
70+
</Window>
7471
));

src/components/WindowContent/WindowContent.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,15 @@ const StyledWindowContent = styled.div`
99
margin-right: 2px;
1010
`;
1111

12-
const WindowContent = ({ className, children, style, ...otherProps }) => (
13-
<StyledWindowContent className={className} style={style} {...otherProps}>
14-
{children}
15-
</StyledWindowContent>
12+
const WindowContent = ({ children, ...otherProps }) => (
13+
<StyledWindowContent {...otherProps}>{children}</StyledWindowContent>
1614
);
1715

1816
WindowContent.defaultProps = {
19-
className: '',
20-
style: {},
2117
children: null
2218
};
2319

2420
WindowContent.propTypes = {
25-
className: propTypes.string,
26-
style: propTypes.shape([propTypes.string, propTypes.number]),
2721
children: propTypes.node
2822
};
2923

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
3+
import { renderWithTheme } from '../../../test/utils';
4+
5+
import WindowContent from './WindowContent';
6+
7+
describe('<WindowContent />', () => {
8+
it('renders WindowContent', () => {
9+
const { container } = renderWithTheme(<WindowContent />);
10+
const windowContent = container.firstChild;
11+
12+
expect(windowContent).toBeInTheDocument();
13+
});
14+
15+
it('renders children', () => {
16+
const textContent = 'Hi there!';
17+
const { getByText } = renderWithTheme(
18+
<WindowContent>
19+
<span>{textContent}</span>
20+
</WindowContent>
21+
);
22+
expect(getByText(textContent)).toBeInTheDocument();
23+
});
24+
});

src/components/WindowHeader/WindowHeader.js

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,49 @@ import React from 'react';
22
import propTypes from 'prop-types';
33

44
import styled from 'styled-components';
5-
import { padding } from '../common/system';
5+
import { StyledButton } from '../Button/Button';
66

77
const SlyledWindowHeader = styled.div`
88
height: 33px;
99
line-height: 33px;
10-
padding: 0 ${padding.sm};
11-
margin-right: 2px;
12-
margin-bottom: 4px;
13-
10+
padding-left: 0.25rem;
11+
padding-right: 3px;
1412
font-weight: bold;
15-
color: ${({ theme }) => theme.textInvert};
1613
17-
background: linear-gradient(
18-
to right,
19-
${({ theme }) => theme.headerMaterialDark},
20-
${({ theme }) => theme.headerMaterialLight}
21-
);
14+
&[data-active='false'] {
15+
background: ${({ theme }) => theme.headerNotActive};
16+
color: ${({ theme }) => theme.material};
17+
}
18+
&[data-active='true'] {
19+
background: linear-gradient(
20+
to right,
21+
${({ theme }) => theme.headerMaterialDark},
22+
${({ theme }) => theme.headerMaterialLight}
23+
);
24+
color: ${({ theme }) => theme.textInvert};
25+
}
26+
${StyledButton} {
27+
padding-left: 0;
28+
padding-right: 0;
29+
height: 27px;
30+
width: 31px;
31+
}
2232
`;
23-
24-
const WindowHeader = ({ className, style, children, ...otherProps }) => (
25-
<SlyledWindowHeader className={className} style={style} {...otherProps}>
33+
// TODO - should we add some aria label indicating if window is currently active?
34+
const WindowHeader = ({ isActive, children, ...otherProps }) => (
35+
<SlyledWindowHeader data-active={isActive.toString()} {...otherProps}>
2636
{children}
2737
</SlyledWindowHeader>
2838
);
2939

3040
WindowHeader.defaultProps = {
31-
className: '',
32-
style: {},
33-
children: null
41+
children: null,
42+
isActive: true
3443
};
3544

3645
WindowHeader.propTypes = {
37-
className: propTypes.string,
38-
style: propTypes.shape([propTypes.string, propTypes.number]),
39-
children: propTypes.node
46+
children: propTypes.node,
47+
isActive: propTypes.bool
4048
};
4149

4250
export default WindowHeader;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
3+
import { renderWithTheme } from '../../../test/utils';
4+
5+
import WindowHeader from './WindowHeader';
6+
7+
describe('<WindowHeader />', () => {
8+
it('renders WindowHeader', () => {
9+
const { container } = renderWithTheme(<WindowHeader />);
10+
const windowHeader = container.firstChild;
11+
12+
expect(windowHeader).toBeInTheDocument();
13+
});
14+
15+
it('renders children', () => {
16+
const textContent = 'Hi there!';
17+
const { getByText } = renderWithTheme(
18+
<WindowHeader>
19+
<span>{textContent}</span>
20+
</WindowHeader>
21+
);
22+
expect(getByText(textContent)).toBeInTheDocument();
23+
});
24+
25+
describe('prop: isActive', () => {
26+
it('displays active header by default', () => {
27+
const { container } = renderWithTheme(<WindowHeader />);
28+
const windowHeader = container.firstChild;
29+
30+
expect(windowHeader).toHaveAttribute('data-active', 'true');
31+
});
32+
33+
it('renders non-active header when set to false', () => {
34+
const { container } = renderWithTheme(<WindowHeader isActive={false} />);
35+
const windowHeader = container.firstChild;
36+
37+
expect(windowHeader).toHaveAttribute('data-active', 'false');
38+
});
39+
});
40+
});

src/components/common/themes.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ themes.default = {
1212
borderLight: '#dfe0e3',
1313

1414
headerMaterialDark: '#000080',
15-
headerMaterialLight: '#1034a6',
15+
headerMaterialLight: '#000080',
1616
headerText: '#ffffff',
17+
headerNotActive: '#7f7f7f',
1718

1819
text: '#050608',
1920
textInvert: '#ffffff',
@@ -53,6 +54,7 @@ themes.water = {
5354
headerMaterialDark: '#72b3b4',
5455
headerMaterialLight: '#72b3b4',
5556
headerText: '#ffffff',
57+
headerNotActive: '#9a9e9c',
5658

5759
text: '#050608',
5860
textInvert: '#ffffff',
@@ -95,6 +97,7 @@ themes.coldGray = {
9597
headerMaterialDark: '#3B3D64',
9698
headerMaterialLight: '#8d88c2',
9799
headerText: '#010601',
100+
headerNotActive: '#6063a5',
98101

99102
text: '#010601',
100103
textInvert: '#c7c7df',
@@ -135,6 +138,7 @@ themes.lilacRoseDark = {
135138
headerMaterialDark: '#4C0030',
136139
headerMaterialLight: '#8d88c2',
137140
headerText: '#010601',
141+
headerNotActive: '#763a60',
138142

139143
text: '#000000',
140144
textInvert: '#ecbfe3',
@@ -163,6 +167,7 @@ themes.violetDark = {
163167
canvas: '#c47bcc',
164168
material: '#652a6d',
165169
materialDark: '#210e23',
170+
166171
borderDarkest: '#18051a',
167172
borderLightest: '#c47bcc',
168173
borderDark: '#3c1f3e',
@@ -171,6 +176,7 @@ themes.violetDark = {
171176

172177
headerMaterialDark: '#1034a6',
173178
headerMaterialLight: '#512155',
179+
headerNotActive: '#210e23',
174180

175181
text: '#c57ece',
176182
textInvert: '#c47bcc',

0 commit comments

Comments
 (0)