Skip to content

Commit

Permalink
feat(ui-shell): add option for large variation and disabling mouse li…
Browse files Browse the repository at this point in the history
…steners (#3802)

* feat(ui-shell): add alternative behaviours

add ability to remove mouse listeners on sidenav

* feat(ui-shell): add alternative behaviours

add large side nav items
  • Loading branch information
matthew-chirgwin authored and tw15egan committed Aug 23, 2019
1 parent 3b7e1c0 commit 16fa92b
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 14 deletions.
16 changes: 16 additions & 0 deletions packages/components/src/components/ui-shell/_side-nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@
color: $ibm-color__gray-100;
}

.#{$prefix}--side-nav__item--large {
height: mini-units(6);
}

//----------------------------------------------------------------------------
// Side-nav > Navigation > {Menu,Submenu}
//----------------------------------------------------------------------------
Expand Down Expand Up @@ -397,6 +401,12 @@
transform: rotate(180deg);
}

.#{$prefix}--side-nav__item--large {
.#{$prefix}--side-nav__submenu {
height: mini-units(6);
}
}

.#{$prefix}--side-nav__item--active .#{$prefix}--side-nav__submenu:hover {
background-color: $shell-side-nav-bg-04;
color: $ibm-color__gray-100;
Expand Down Expand Up @@ -484,6 +494,12 @@
outline $duration--fast-02;
}

.#{$prefix}--side-nav__item--large {
a.#{$prefix}--side-nav__link {
height: mini-units(6);
}
}

a.#{$prefix}--side-nav__link > .#{$prefix}--side-nav__link-text,
.#{$prefix}--side-nav
a.#{$prefix}--header__menu-item[role='menuitem']
Expand Down
22 changes: 18 additions & 4 deletions packages/react/src/components/UIShell/SideNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const SideNav = React.forwardRef(function SideNav(props, ref) {
isFixedNav,
isRail,
isPersistent,
addMouseListeners,
} = props;

const { current: controlled } = useRef(expandedProp !== undefined);
Expand Down Expand Up @@ -90,17 +91,24 @@ const SideNav = React.forwardRef(function SideNav(props, ref) {
});
}

const eventHanders = {
onFocus: event => handleToggle(event, true),
onBlur: event => handleToggle(event, false),
};

if (addMouseListeners) {
eventHanders.onMouseEnter = () => handleToggle(true, true);
eventHanders.onMouseLeave = () => handleToggle(false, false);
}

return (
<>
{isFixedNav ? null : <div className={overlayClassName} />}
<nav
ref={ref}
className={`${prefix}--side-nav__navigation ${className}`}
{...accessibilityLabel}
onFocus={event => handleToggle(event, true)}
onBlur={event => handleToggle(event, false)}
onMouseEnter={() => handleToggle(true, true)}
onMouseLeave={() => handleToggle(false, false)}>
{...eventHanders}>
{childrenToRender}
</nav>
</>
Expand All @@ -119,6 +127,7 @@ SideNav.defaultProps = {
isChildOfHeader: true,
isFixedNav: false,
isPersistent: true,
addMouseListeners: true,
};

SideNav.propTypes = {
Expand Down Expand Up @@ -179,6 +188,11 @@ SideNav.propTypes = {
* Specify if the sideNav will be persistent above the lg breakpoint
*/
isPersistent: PropTypes.bool,

/**
* Specify whether mouse entry/exit listeners are added. They are by default.
*/
addMouseListeners: PropTypes.bool,
};

export default SideNav;
17 changes: 15 additions & 2 deletions packages/react/src/components/UIShell/SideNavItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ import React from 'react';

const { prefix } = settings;

const SideNavItem = ({ className: customClassName, children }) => {
const className = cx(`${prefix}--side-nav__item`, customClassName);
const SideNavItem = ({
className: customClassName,
children,
large = false,
}) => {
const className = cx({
[`${prefix}--side-nav__item`]: true,
[`${prefix}--side-nav__item--large`]: large,
[customClassName]: !!customClassName,
});
return <li className={className}>{children}</li>;
};

Expand All @@ -28,6 +36,11 @@ SideNavItem.propTypes = {
* container
*/
children: PropTypes.node.isRequired,

/**
* Specify if this is a large variation of the SideNavItem
*/
large: PropTypes.bool,
};

export default SideNavItem;
9 changes: 8 additions & 1 deletion packages/react/src/components/UIShell/SideNavLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const SideNavLink = ({
children,
renderIcon: IconElement,
isActive,
large,
...rest
}) => {
const className = cx({
Expand All @@ -30,7 +31,7 @@ const SideNavLink = ({
});

return (
<SideNavItem>
<SideNavItem large={large}>
<Link {...rest} className={className}>
{IconElement && (
<SideNavIcon small>
Expand Down Expand Up @@ -66,10 +67,16 @@ SideNavLink.propTypes = {
* keep local state and styling in step with the SideNav expansion state.
*/
isSideNavExpanded: PropTypes.bool,

/**
* Specify if this is a large variation of the SideNavLink
*/
large: PropTypes.bool,
};

SideNavLink.defaultProps = {
element: 'a',
large: false,
};

export const createCustomSideNavLink = element => props => {
Expand Down
8 changes: 8 additions & 0 deletions packages/react/src/components/UIShell/SideNavMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,17 @@ export class SideNavMenu extends React.Component {
* keep local state and styling in step with the SideNav expansion state.
*/
isSideNavExpanded: PropTypes.bool,

/**
* Specify if this is a large variation of the SideNavMenu
*/
large: PropTypes.bool,
};

static defaultProps = {
defaultExpanded: false,
isActive: false,
large: false,
};

static getDerivedStateFromProps = (props, state) => {
Expand Down Expand Up @@ -102,6 +108,7 @@ export class SideNavMenu extends React.Component {
renderIcon: IconElement,
isActive,
title,
large,
} = this.props;
const { isExpanded } = this.state;

Expand All @@ -127,6 +134,7 @@ export class SideNavMenu extends React.Component {
[`${prefix}--side-nav__item--active`]:
isActive || (hasActiveChild && !isExpanded),
[`${prefix}--side-nav__item--icon`]: IconElement,
[`${prefix}--side-nav__item--large`]: large,
[customClassName]: !!customClassName,
});
return (
Expand Down
43 changes: 43 additions & 0 deletions packages/react/src/components/UIShell/UIShell-story.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,4 +775,47 @@ storiesOf('UI Shell', module)
)}
/>
))
)
.add(
'SideNav w/ large side nav items',
withReadme(readme, () => (
<>
<SideNav
expanded={true}
isChildOfHeader={false}
aria-label="Side navigation">
<SideNavItems>
<SideNavMenu title="Large menu" large>
<SideNavMenuItem href="javascript:void(0)">
Menu 1
</SideNavMenuItem>
<SideNavMenuItem href="javascript:void(0)">
Menu 2
</SideNavMenuItem>
<SideNavMenuItem href="javascript:void(0)">
Menu 3
</SideNavMenuItem>
</SideNavMenu>
<SideNavLink href="javascript:void(0)" large>
Large link
</SideNavLink>
<SideNavMenu renderIcon={Fade16} title="Large menu w/icon" large>
<SideNavMenuItem href="javascript:void(0)">
Menu 1
</SideNavMenuItem>
<SideNavMenuItem href="javascript:void(0)">
Menu 2
</SideNavMenuItem>
<SideNavMenuItem href="javascript:void(0)">
Menu 3
</SideNavMenuItem>
</SideNavMenu>
<SideNavLink renderIcon={Fade16} href="javascript:void(0)" large>
Large link w/icon
</SideNavLink>
</SideNavItems>
</SideNav>
<StoryContent />
</>
))
);
25 changes: 23 additions & 2 deletions packages/react/src/components/UIShell/__tests__/SideNav-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { mount } from 'enzyme';
import SideNav from '../SideNav';

describe('SideNav', () => {
let mockProps;
let mockProps, wrapper;

beforeEach(() => {
mockProps = {
Expand All @@ -19,8 +19,29 @@ describe('SideNav', () => {
};
});

afterEach(() => {
wrapper && wrapper.unmount();
});

it('should render', () => {
const wrapper = mount(<SideNav {...mockProps} />);
wrapper = mount(<SideNav {...mockProps} />);
expect(wrapper).toMatchSnapshot();
});

it('by default, all event listeners are added', () => {
wrapper = mount(<SideNav {...mockProps} />);
expect(wrapper.find('nav').props().onFocus).toBeDefined();
expect(wrapper.find('nav').props().onBlur).toBeDefined();
expect(wrapper.find('nav').props().onMouseEnter).toBeDefined();
expect(wrapper.find('nav').props().onMouseLeave).toBeDefined();
});

it('if addMouseListeners is specified as false, no mouse listener props are added', () => {
wrapper = mount(<SideNav {...mockProps} />);
wrapper.setProps({ addMouseListeners: false });
expect(wrapper.find('nav').props().onFocus).toBeDefined();
expect(wrapper.find('nav').props().onBlur).toBeDefined();
expect(wrapper.find('nav').props().onMouseEnter).not.toBeDefined();
expect(wrapper.find('nav').props().onMouseLeave).not.toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import React from 'react';
import { mount } from 'enzyme';
import SideNavItem from '../SideNavItem';
import { settings } from 'carbon-components';
const { prefix } = settings;

describe('SideNavItem', () => {
let mockProps;
let mockProps, wrapper;

beforeEach(() => {
mockProps = {
Expand All @@ -19,8 +21,23 @@ describe('SideNavItem', () => {
};
});

afterEach(() => {
wrapper && wrapper.unmount();
});

it('should render', () => {
const wrapper = mount(<SideNavItem {...mockProps} />);
wrapper = mount(<SideNavItem {...mockProps} />);
expect(wrapper).toMatchSnapshot();
});

it('should include a css class to render the large varient is large prop is set', () => {
wrapper = mount(<SideNavItem {...mockProps} />);
expect(
wrapper.find('li').hasClass(`${prefix}--side-nav__item--large`)
).toBe(false);
wrapper.setProps({ large: true });
expect(
wrapper.find('li').hasClass(`${prefix}--side-nav__item--large`)
).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import React from 'react';
import { mount } from 'enzyme';
import SideNavLink from '../SideNavLink';
import { settings } from 'carbon-components';
const { prefix } = settings;

describe('SideNavLink', () => {
let mockProps;
let mockProps, wrapper;

beforeEach(() => {
mockProps = {
Expand All @@ -21,10 +23,25 @@ describe('SideNavLink', () => {
};
});

afterEach(() => {
wrapper && wrapper.unmount();
});

it('should render', () => {
const inactive = mount(<SideNavLink {...mockProps} />);
expect(inactive).toMatchSnapshot();
const active = mount(<SideNavLink {...mockProps} isActive />);
expect(active).toMatchSnapshot();
});

it('should include a css class to render the large varient is large prop is set', () => {
wrapper = mount(<SideNavLink {...mockProps} />);
expect(
wrapper.find('li').hasClass(`${prefix}--side-nav__item--large`)
).toBe(false);
wrapper.setProps({ large: true });
expect(
wrapper.find('li').hasClass(`${prefix}--side-nav__item--large`)
).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,15 @@ describe('SideNavMenu', () => {
wrapper.find('li').hasClass(`${prefix}--side-nav__item--active`)
).toBe(true);
});

it('should include a css class to render the large varient is large prop is set', () => {
wrapper = mount(<SideNavMenu {...mockProps} />);
expect(
wrapper.find('li').hasClass(`${prefix}--side-nav__item--large`)
).toBe(false);
wrapper.setProps({ large: true });
expect(
wrapper.find('li').hasClass(`${prefix}--side-nav__item--large`)
).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`SideNav should render 1`] = `
<ForwardRef(SideNav)
addMouseListeners={true}
aria-label="Navigation"
defaultExpanded={false}
isChildOfHeader={true}
Expand Down
Loading

0 comments on commit 16fa92b

Please sign in to comment.