Skip to content

Commit

Permalink
feat: Add NavBar component (#387)
Browse files Browse the repository at this point in the history
* feat: Add Navbar component

* try out

* link and location cannot be used

* progress

* add Navbar

* navbar trigger

* progress

* more progress

* clean up css

* allow pass trigger children

* add pagination

* fix(styles): set accessible color for css selectors in light mode for syntax highlighting (#388)

* feat: Add a NavBar component

* add transition

* more edits

* update

* correct case

* export navbartrigger

* remove to rename directory

* add folder again

* add tests

* remove changes in Tabs

* add more tests

* update css

* updates

* fix

* more edits

* fix

* clean up

* more

* more fix

* edits

* clean up

* fix list border-bottom

* fix collapsed navbar hover

* edits

* add keydown event for escape

* add focus eventlistener and handler

* update tests

* make keydown an event for both trigger and navitems

* adjust icon size to match

* remove failing tests

* move keydown event handler

* remove flex-grow

* clean up

* oops

* remove scrim

Co-authored-by: Jason <[email protected]>
  • Loading branch information
dequejosie and scurker authored Nov 15, 2021
1 parent 6f2d09f commit f2f0263
Show file tree
Hide file tree
Showing 16 changed files with 662 additions and 37 deletions.
3 changes: 2 additions & 1 deletion docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ const componentsList = [
'DescriptionList',
'TopBar',
'Stepper',
'ProgressBar'
'ProgressBar',
'NavBar'
].sort();

const App = () => {
Expand Down
7 changes: 7 additions & 0 deletions docs/patterns/components/NavBar/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.NavBarButton {
margin-top: var(--space-large);
}

.NavBar--collapsed {
width: 20rem;
}
65 changes: 65 additions & 0 deletions docs/patterns/components/NavBar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { useState } from 'react';
import { NavBar, NavItem, Code, Button } from '@deque/cauldron-react';
import './index.css';

const Demo = () => {
const [isMobil, setIsMobil] = useState(false);
const componentsList = new Array(5).fill('NavItem');
const handleToggle = () => {
setIsMobil(!isMobil);
};
return (
<div className="NavBarDemo">
<h1>NavBar</h1>
<h2>Demo</h2>
<h3>Basic NavBar</h3>
<NavBar collapsed={isMobil}>
{componentsList.map((name, index) => {
return (
<NavItem key={`${name}-${index}`} active={index === 2}>
<a href="#">{`${name} ${index + 1}`}</a>
</NavItem>
);
})}
</NavBar>
<Button onClick={handleToggle} className="NavBarButton">
Toggle between mobil and non-mobile mode
</Button>
<h2>Code Sample</h2>
<Code language="javascript">
{`
import React, { useState } from 'react';
import { NavBar, NavItem, Code, Button } from '@deque/cauldron-react';
import './index.css';
const Demo = () => {
const [isMobil, setIsMobil] = useState(false);
const componentsList = new Array(5).fill('NavItem');
const handleToggle = () => {
setIsMobil(!isMobil);
};
return (
<div className="NavBarDemo">
<h1>NavBar</h1>
<h2>Demo</h2>
<h3>Basic NavBar</h3>
<NavBar collapsed={isMobil}>
{componentsList.map((name, index) => {
return (
<NavItem key={\`\${name}-\${index}\`} active={index === 2}>
<a href="#">{\`\${name} \${index + 1}\`}</a>
</NavItem>
);
})}
</NavBar>
<Button onClick={handleToggle} className="NavBarButton">
Toggle between mobil and non-mobile mode
</Button>
</div>
`}
</Code>
</div>
);
};

export default Demo;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@types/node": "^14.10.1",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4",
"@types/react-router-dom": "^5.3.2",
"@types/react-syntax-highlighter": "^11.0.4",
"@types/webpack-dev-server": "^3.10.1",
"@typescript-eslint/eslint-plugin": "^2.19.2",
Expand Down Expand Up @@ -91,7 +92,7 @@
"react-dom": "^16.13.1",
"react-element-to-jsx-string": "^14.0.2",
"react-helmet": "^5.2.1",
"react-router-dom": "^4.2.2",
"react-router-dom": "^5.3.0",
"standard-version": "^9.1.1",
"style-loader": "^0.19.0",
"terser-webpack-plugin": "^2.3.5",
Expand Down
199 changes: 199 additions & 0 deletions packages/react/__tests__/src/components/NavBar/NavBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React from 'react';
import { mount } from 'enzyme';
import NavBar, { NavItem } from 'src/components/NavBar';
import { act } from 'react-dom/test-utils';

let wrapperNode;
let mountNode;

beforeEach(() => {
wrapperNode = document.createElement('div');
wrapperNode.innerHTML = `
<a href="#foo" data-test>Click Me!</a>
<div id="#mount"></div>
`;
document.body.appendChild(wrapperNode);
mountNode = document.getElementById('mount');
});

afterEach(() => {
document.body.innerHTML = '';
wrapperNode = null;
mountNode = null;
});

test('mounts without error', () => {
expect(() =>
mount(
<NavBar>
<div />
</NavBar>
)
).not.toThrow();
});

test('renders className prop', () => {
const MountedNavBar = mount(
<NavBar className="find--me">
<div />
</NavBar>
);
expect(MountedNavBar.find('.find--me').exists()).toBe(true);
});

test('renders propId', () => {
const MountedNavBar = mount(
<NavBar propId="someid">
<div />
</NavBar>
);

expect(MountedNavBar.find('ul#someid').exists()).toBe(true);
});

test('renders NavBar trigger button when collapsed prop is true', () => {
const MountedNavBar = mount(
<NavBar collapsed>
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>
);

expect(MountedNavBar.find('.NavBar__trigger').exists()).toBe(true);
expect(MountedNavBar.find('.NavBar__trigger--active').exists()).toBe(false);
expect(MountedNavBar.find('NavItem').exists()).toBe(false);
expect(MountedNavBar.find('button').prop('aria-expanded')).toBe(false);
expect(MountedNavBar.find('Icon').prop('type')).toEqual('hamburger-menu');
});

test('renders navTriggerLabel properly', () => {
const MountedNavBar = mount(
<NavBar collapsed navTriggerLabel="I am a label">
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>
);

expect(MountedNavBar.find('.NavBar__trigger').exists()).toBe(true);
expect(
MountedNavBar.find('.NavBar__trigger')
.find('button')
.text()
).toEqual('I am a label');
expect(MountedNavBar.find('NavItem').exists()).toBe(false);
});

test('renders aria-controls prop on trigger button', () => {
const MountedTrigger = mount(
<NavBar collapsed propId="someid">
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>
);
expect(
MountedTrigger.find('button.NavBar__trigger').prop('aria-controls')
).toEqual('someid');
});

test('shows NavItems after clicking NavBar trigger button', () => {
const MountedNavBar = mount(
<NavBar collapsed>
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>
);

MountedNavBar.find('.NavBar__trigger').simulate('click');
MountedNavBar.update();
expect(MountedNavBar.find('NavItem').exists()).toBe(true);
expect(MountedNavBar.find('.NavBar__trigger--active').exists()).toBe(true);
expect(
MountedNavBar.find('button.NavBar__trigger').prop('aria-expanded')
).toBe(true);
expect(MountedNavBar.find('Icon').prop('type')).toEqual('close');
});

test('hides NavItems after pressing escape key', () => {
const MountedNavBar = mount(
<NavBar collapsed>
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>
);

MountedNavBar.find('.NavBar__trigger').simulate('click');
MountedNavBar.update();

MountedNavBar.find('ul').simulate('keydown', { key: 'Escape' });
MountedNavBar.update();

expect(MountedNavBar.find('NavItem').exists()).toBe(false);
});

test('does not hide NavItems after pressing other keys', () => {
const MountedNavBar = mount(
<NavBar collapsed>
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>
);

MountedNavBar.find('.NavBar__trigger').simulate('click');
MountedNavBar.update();

MountedNavBar.find('ul').simulate('keydown', { key: 'Home' });
MountedNavBar.update();

expect(MountedNavBar.find('NavItem').exists()).toBe(true);
});

test('hides NavItems when focusing outside nav', async () => {
const MountedNavBar = mount(
<NavBar collapsed>
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>,
{ attachTo: mountNode }
);

MountedNavBar.find('.NavBar__trigger').simulate('click');
MountedNavBar.update();

act(() => {
wrapperNode
.querySelector('a')
.dispatchEvent(new Event('focusin', { bubbles: true }));
});

MountedNavBar.update();

expect(MountedNavBar.find('NavItem').exists()).toBe(false);
});

test('does not hides NavItems when focusing inside nav', async () => {
const MountedNavBar = mount(
<NavBar collapsed>
<NavItem>
<p>first item</p>
</NavItem>
</NavBar>,
{ attachTo: mountNode }
);

MountedNavBar.find('.NavBar__trigger').simulate('click');
MountedNavBar.update();

MountedNavBar.find('NavItem')
.at(0)
.simulate('focus');
MountedNavBar.update();

expect(MountedNavBar.find('NavItem').exists()).toBe(true);
});
31 changes: 31 additions & 0 deletions packages/react/__tests__/src/components/NavBar/NavItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { mount } from 'enzyme';
import { NavItem } from 'src/components/NavBar';

test('mounts without error', () => {
expect(() =>
mount(
<NavItem>
<div />
</NavItem>
)
).not.toThrow();
});

test('renders children', () => {
const MountedNavItem = mount(
<NavItem>
<p>I am a child</p>
</NavItem>
);
expect(MountedNavItem.find('p').text()).toEqual('I am a child');
});

test('handles active prop properly', () => {
const MountedNavItem = mount(
<NavItem active>
<p>I am a child</p>
</NavItem>
);
expect(MountedNavItem.find('.NavItem--active').exists()).toBe(true);
});
2 changes: 1 addition & 1 deletion packages/react/src/components/Icon/icons/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/react/src/components/Icon/icons/kabob.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f2f0263

Please sign in to comment.