Skip to content

Commit

Permalink
Merge pull request #1427 from storybooks/151-story-hierarchy
Browse files Browse the repository at this point in the history
Story Hierarchy - keyboard accessibility
  • Loading branch information
igor-dv committed Jul 14, 2017
2 parents d6efd70 + a22cdda commit 0121a7c
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 36 deletions.
2 changes: 1 addition & 1 deletion lib/ui/src/modules/ui/components/layout/index.js
Expand Up @@ -204,7 +204,7 @@ class Layout extends React.Component {
showLeftPanel,
() =>
<div style={leftPanelStyle(leftPanelOnTop)}>
<div style={{ flexGrow: 1, height: '100%' }}>{leftPanel()}</div>
<div style={{ flexGrow: 1, height: '100%', width: '100%' }}>{leftPanel()}</div>
<USplit shift={5} split={storiesSplit} />
</div>,
() => <span />
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/src/modules/ui/components/left_panel/index.js
Expand Up @@ -8,7 +8,7 @@ import TextFilter from './text_filter';
const scrollStyle = {
height: 'calc(100vh - 105px)',
marginTop: 10,
overflowY: 'auto',
overflow: 'auto',
};

const mainStyle = {
Expand Down
13 changes: 11 additions & 2 deletions lib/ui/src/modules/ui/components/left_panel/stories_tree/index.js
Expand Up @@ -3,10 +3,11 @@ import PropTypes from 'prop-types';
import React from 'react';
import deepEqual from 'deep-equal';
import treeNodeTypes from './tree_node_type';
import treeDecorators from './tree_decorators';
import createTreeDecorators from './tree_decorators';
import treeStyle from './tree_style';

const namespaceSeparator = '@';
const keyCodeEnter = 13;

function createNodeKey({ namespaces, type }) {
return [...namespaces, [type]].join(namespaceSeparator);
Expand Down Expand Up @@ -39,12 +40,14 @@ class Stories extends React.Component {
constructor(...args) {
super(...args);
this.onToggle = this.onToggle.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);

const { selectedHierarchy } = this.props;

this.state = {
nodes: getSelectedNodes(selectedHierarchy),
};
this.treeDecorators = createTreeDecorators(this);
}

componentWillReceiveProps(nextProps) {
Expand Down Expand Up @@ -82,6 +85,12 @@ class Stories extends React.Component {
}));
}

onKeyDown(event, node) {
if (event.keyCode === keyCodeEnter) {
this.onToggle(node, !node.toggled);
}
}

fireOnKind(kind) {
const { onSelectStory } = this.props;
if (onSelectStory) onSelectStory(kind, null);
Expand Down Expand Up @@ -144,7 +153,7 @@ class Stories extends React.Component {
style={treeStyle}
data={data}
onToggle={this.onToggle}
decorators={treeDecorators}
decorators={this.treeDecorators}
/>
);
}
Expand Down
Expand Up @@ -313,5 +313,46 @@ describe('manager.ui.components.left_panel.stories', () => {

expect(onSelectStory).toHaveBeenCalledWith('another.space.20', 'b2');
});

test('should call the onSelectStory prop when a story is selected with enter key', () => {
const data = createHierarchy(
[
{ kind: 'some.name.item1', stories: ['a1', 'a2'] },
{ kind: 'another.space.20', stories: ['b1', 'b2'] },
],
'\\.'
);

const onSelectStory = jest.fn();
const wrap = mount(
<Stories
storiesHierarchy={data}
selectedKind="some.name.item1"
selectedStory="a2"
selectedHierarchy={['some', 'name', 'item1']}
onSelectStory={onSelectStory}
/>
);

wrap
.find('a')
.filterWhere(el => el.text() === 'another')
.last()
.simulate('keyDown', { keyCode: 13 });

wrap
.find('a')
.filterWhere(el => el.text() === 'space')
.last()
.simulate('keyDown', { keyCode: 13 });

wrap
.find('a')
.filterWhere(el => el.text() === '20')
.last()
.simulate('keyDown', { keyCode: 13 });

expect(onSelectStory).toHaveBeenCalledWith('another.space.20', null);
});
});
});
Expand Up @@ -34,41 +34,61 @@ ContainerDecorator.propTypes = {
}).isRequired,
};

function HeaderDecorator(props) {
const { style, node } = props;
function createHeaderDecoratorScope(parent) {
class HeaderDecorator extends React.Component {
constructor(...args) {
super(...args);
this.onKeyDown = this.onKeyDown.bind(this);
}

const newStyleTitle = {
...style.title,
};
onKeyDown(event) {
const { onKeyDown } = parent;
const { node } = this.props;

onKeyDown(event, node);
}

render() {
const { style, node } = this.props;

const newStyleTitle = {
...style.title,
};

const Icon = iconsMap[node.type];
const Icon = iconsMap[node.type];

if (!node.children || !node.children.length) {
newStyleTitle.fontSize = '13px';
if (!node.children || !node.children.length) {
newStyleTitle.fontSize = '13px';
}

return (
<div style={style.base} role="menuitem" tabIndex="0" onKeyDown={this.onKeyDown}>
{Icon && <Icon color={iconsColor} />}
<a style={newStyleTitle}>
{node.name}
</a>
</div>
);
}
}

return (
<div style={style.base}>
{Icon && <Icon color={iconsColor} />}
<a style={newStyleTitle}>
{node.name}
</a>
</div>
);
}
HeaderDecorator.propTypes = {
style: PropTypes.shape({
title: PropTypes.object.isRequired,
base: PropTypes.object.isRequired,
}).isRequired,
node: PropTypes.shape({
name: PropTypes.string.isRequired,
}).isRequired,
};

HeaderDecorator.propTypes = {
style: PropTypes.shape({
title: PropTypes.object,
base: PropTypes.object,
}).isRequired,
node: PropTypes.shape({
name: PropTypes.string,
}).isRequired,
};
return HeaderDecorator;
}

export default {
...decorators,
Header: HeaderDecorator,
Container: ContainerDecorator,
};
export default function(parent) {
return {
...decorators,
Header: createHeaderDecoratorScope(parent),
Container: ContainerDecorator,
};
}
@@ -1,5 +1,7 @@
import { baseFonts } from '../../theme';

const toggleWidth = '24px';

export default {
tree: {
base: {
Expand All @@ -8,6 +10,7 @@ export default {
padding: 0,
fontFamily: baseFonts.fontFamily,
fontSize: '15px',
minWidth: '200px',
},
node: {
base: {
Expand All @@ -30,7 +33,7 @@ export default {
verticalAlign: 'top',
marginLeft: '-5px',
height: '24px',
width: '24px',
width: toggleWidth,
},
wrapper: {
position: 'absolute',
Expand All @@ -49,6 +52,7 @@ export default {
base: {
display: 'inline-block',
verticalAlign: 'top',
maxWidth: `calc(100% - ${toggleWidth})`,
},
connector: {
width: '2px',
Expand Down

0 comments on commit 0121a7c

Please sign in to comment.