Skip to content

Commit 3dce7a6

Browse files
authored
List[View] component (#313)
* Add sourcemaps to storybook * ListView component * Add second parameter to shadowBorder for borderSize * Refactor List to not the new shadowBorder and simplify it a bit * Add global export * Improve accessibility of List (#314) * Make ListItems focusable * Add util for common key codes * Add HOC for keyboard events * Make List keyboard accessible * Don't add onKeyDown if onClick is undefined * Remove tabIndex from default props * PR Comments * Fix snapshots
1 parent 5740c6c commit 3dce7a6

21 files changed

+769
-2
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ node_modules
88
jest-test-results.json
99
__coverage__
1010
dist
11+
lib
1112
.out
1213
.docz
1314
circuit-ui-global.css

.storybook/webpack.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = function(storybookBaseConfig, configType) {
77
const isProduction = configType === 'PRODUCTION';
88

99
const ourConfig = {
10+
devtool: 'eval-source-map',
1011
externals: {
1112
jsdom: 'window',
1213
cheerio: 'window',
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
3+
import ListView from '.';
4+
5+
const BaseList = () => (
6+
<ListView>
7+
<ListView.Item>List item 1</ListView.Item>
8+
<ListView.Item selected>List item 2</ListView.Item>
9+
<ListView.Item>List item 3</ListView.Item>
10+
<ListView.Item>List item 4</ListView.Item>
11+
</ListView>
12+
);
13+
14+
describe('ListView', () => {
15+
/**
16+
* Style tests.
17+
*/
18+
it('should render with default styles', () => {
19+
const actual = create(<BaseList />);
20+
expect(actual).toMatchSnapshot();
21+
});
22+
23+
/**
24+
* Accessibility tests.
25+
*/
26+
it('should meet accessibility guidelines', async () => {
27+
const wrapper = renderToHtml(<BaseList />);
28+
const actual = await axe(wrapper);
29+
expect(actual).toHaveNoViolations();
30+
});
31+
});
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { Component } from 'react';
2+
import { range } from 'lodash/fp';
3+
import { storiesOf } from '@storybook/react';
4+
import { withInfo } from '@storybook/addon-info';
5+
import * as knobs from '@storybook/addon-knobs/react';
6+
7+
import { GROUPS } from '../../../.storybook/hierarchySeparators';
8+
import withTests from '../../util/withTests';
9+
import List from '.';
10+
11+
class ListContainer extends Component {
12+
state = { selected: 0 };
13+
14+
handleClick = selected => () => this.setState({ selected });
15+
16+
render() {
17+
const { selected } = this.state;
18+
const padding = knobs.select(
19+
'padding',
20+
[List.Item.KILO, List.Item.MEGA, List.Item.GIGA],
21+
List.Item.GIGA
22+
);
23+
24+
return (
25+
<div style={{ width: '320px' }}>
26+
<List>
27+
{range(1, 6).map(i => (
28+
<List.Item
29+
key={i}
30+
selected={selected === i}
31+
onClick={this.handleClick(i)}
32+
padding={padding}
33+
>
34+
Item #{i}
35+
</List.Item>
36+
))}
37+
</List>
38+
</div>
39+
);
40+
}
41+
}
42+
43+
storiesOf(`${GROUPS.COMPONENTS}|ListView`, module)
44+
.addDecorator(withTests('ListView'))
45+
.add('ListView', withInfo()(() => <ListContainer />));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`ListView should render with default styles 1`] = `
4+
.circuit-8 {
5+
background-color: #FFFFFF;
6+
border-radius: 4px;
7+
display: -webkit-box;
8+
display: -webkit-flex;
9+
display: -ms-flexbox;
10+
display: flex;
11+
-webkit-flex-direction: column;
12+
-ms-flex-direction: column;
13+
flex-direction: column;
14+
-webkit-box-pack: justify;
15+
-webkit-justify-content: space-between;
16+
-ms-flex-pack: justify;
17+
justify-content: space-between;
18+
box-shadow: 0 0 0 1px rgba(12,15,20,0.02), 0 0 1px 0 rgba(12,15,20,0.06), 0 2px 2px 0 rgba(12,15,20,0.06);
19+
padding: 16px 24px;
20+
padding: 0;
21+
}
22+
23+
.circuit-0 {
24+
-webkit-align-items: center;
25+
-webkit-box-align: center;
26+
-ms-flex-align: center;
27+
align-items: center;
28+
position: relative;
29+
cursor: pointer;
30+
border-bottom: 1px solid #D8DDE1;
31+
padding: 24px;
32+
}
33+
34+
.circuit-0:hover,
35+
.circuit-0:focus {
36+
outline: none;
37+
}
38+
39+
.circuit-0:hover::after,
40+
.circuit-0:focus::after {
41+
content: ' ';
42+
position: absolute;
43+
width: 100%;
44+
height: 100%;
45+
left: 0;
46+
top: 0;
47+
box-shadow: 0px 0px 0px 2px #3388FF;
48+
border-radius: 4px;
49+
}
50+
51+
.circuit-0:first-child {
52+
border-top-left-radius: 4px;
53+
border-top-right-radius: 4px;
54+
}
55+
56+
.circuit-0:last-child {
57+
border-bottom-left-radius: 4px;
58+
border-bottom-right-radius: 4px;
59+
}
60+
61+
.circuit-2 {
62+
-webkit-align-items: center;
63+
-webkit-box-align: center;
64+
-ms-flex-align: center;
65+
align-items: center;
66+
position: relative;
67+
cursor: pointer;
68+
border-bottom: 1px solid #D8DDE1;
69+
padding: 24px;
70+
background: #DAEAFF;
71+
}
72+
73+
.circuit-2:hover,
74+
.circuit-2:focus {
75+
outline: none;
76+
}
77+
78+
.circuit-2:hover::after,
79+
.circuit-2:focus::after {
80+
content: ' ';
81+
position: absolute;
82+
width: 100%;
83+
height: 100%;
84+
left: 0;
85+
top: 0;
86+
box-shadow: 0px 0px 0px 2px #3388FF;
87+
border-radius: 4px;
88+
}
89+
90+
.circuit-2:first-child {
91+
border-top-left-radius: 4px;
92+
border-top-right-radius: 4px;
93+
}
94+
95+
.circuit-2:last-child {
96+
border-bottom-left-radius: 4px;
97+
border-bottom-right-radius: 4px;
98+
}
99+
100+
<div
101+
className="circuit-8 circuit-9"
102+
spacing="giga"
103+
>
104+
<div
105+
aria-selected={undefined}
106+
className="circuit-0 circuit-1"
107+
onClick={undefined}
108+
onKeyDown={null}
109+
selected={false}
110+
tabIndex={0}
111+
>
112+
List item 1
113+
</div>
114+
<div
115+
aria-selected={true}
116+
className="circuit-2 circuit-1"
117+
onClick={undefined}
118+
onKeyDown={null}
119+
selected={true}
120+
tabIndex={0}
121+
>
122+
List item 2
123+
</div>
124+
<div
125+
aria-selected={undefined}
126+
className="circuit-0 circuit-1"
127+
onClick={undefined}
128+
onKeyDown={null}
129+
selected={false}
130+
tabIndex={0}
131+
>
132+
List item 3
133+
</div>
134+
<div
135+
aria-selected={undefined}
136+
className="circuit-0 circuit-1"
137+
onClick={undefined}
138+
onKeyDown={null}
139+
selected={false}
140+
tabIndex={0}
141+
>
142+
List item 4
143+
</div>
144+
</div>
145+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { flow } from 'lodash/fp';
4+
5+
import withKeyboardEvents from '../../../../util/withKeyboardEvents';
6+
import { sizes } from '../../../../styles/constants';
7+
import { Wrapper } from './components';
8+
import withAriaSelected from '../../../../util/withAriaSelected';
9+
10+
const { KILO, MEGA, GIGA } = sizes;
11+
12+
const Item = ({ children, ...props }) => (
13+
<Wrapper tabIndex={0} {...props}>
14+
{children}
15+
</Wrapper>
16+
);
17+
18+
Item.KILO = KILO;
19+
Item.MEGA = MEGA;
20+
Item.GIGA = GIGA;
21+
22+
Item.propTypes = {
23+
/**
24+
* When true, shows the item with selected styles.
25+
*/
26+
selected: PropTypes.bool,
27+
/**
28+
* A Circuit UI spacings size.
29+
*/
30+
padding: PropTypes.oneOf([KILO, MEGA, GIGA]),
31+
/**
32+
* Content of the list item.
33+
*/
34+
children: PropTypes.node.isRequired
35+
};
36+
37+
Item.defaultProps = {
38+
padding: Item.GIGA,
39+
selected: false
40+
};
41+
42+
/**
43+
* @component
44+
*/
45+
export default flow(withKeyboardEvents, withAriaSelected)(Item);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
3+
import Item from './Item';
4+
5+
describe('Item', () => {
6+
/**
7+
* Style tests.
8+
*/
9+
it('should render with default styles', () => {
10+
const actual = create(<Item>List item</Item>);
11+
expect(actual).toMatchSnapshot();
12+
});
13+
14+
it('should render with all paddings', () => {
15+
[Item.KILO, Item.MEGA, Item.GIGA].forEach(padding => {
16+
expect(
17+
create(<Item padding={padding}>List item</Item>)
18+
).toMatchSnapshot();
19+
});
20+
});
21+
22+
it('should render children', () => {
23+
const wrapper = shallow(
24+
<Item>
25+
<div data-selector="child">text node</div>
26+
</Item>
27+
);
28+
const actual = wrapper.find('[data-selector="child"]');
29+
30+
expect(actual).toHaveLength(1);
31+
expect(actual.text()).toEqual('text node');
32+
});
33+
34+
it('should render with selected styles', () => {
35+
const actual = render(<Item isSelected>List item</Item>);
36+
expect(actual).toMatchSnapshot();
37+
});
38+
});

0 commit comments

Comments
 (0)