Skip to content

Commit

Permalink
Merge pull request #187 from dabapps/nav-bar
Browse files Browse the repository at this point in the history
Nav bar
  • Loading branch information
JakeSidSmith authored Jan 26, 2018
2 parents 0c352ba + 90b4df9 commit a6bbe25
Show file tree
Hide file tree
Showing 12 changed files with 497 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "roe",
"version": "0.8.29",
"version": "0.8.30",
"description": "A Collection of React Components for Project Development",
"main": "dist/js/index.js",
"types": "dist/js/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/less/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import 'variables.less';
@import 'overrides.less';
@import 'grid.less';
@import 'nav-bar.less';
@import 'buttons.less';
@import 'inputs.less';
@import 'layout.less';
Expand Down
30 changes: 30 additions & 0 deletions src/less/nav-bar.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
body.with-fixed-nav-bar {
padding-top: @nav-bar-height;
}

.nav-bar {
.clearfix();
padding: @padding-base 0;
background-color: @nav-bar-background;
border-bottom: @nav-bar-border;
box-shadow: @shadow-hard;
height: @nav-bar-height;
transition: ease-in-out 0.2s transform, ease-in-out 0.2s box-shadow;

&.no-shaddow {
box-shadow: @shadow-none;
}

&.fixed {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 500;
}

&.shy.hidden {
transform: translate(0, -100%);
box-shadow: @shadow-none;
}
}
4 changes: 4 additions & 0 deletions src/less/variables.less
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@

@container-background: @white;

@nav-bar-background: @body-background;
@nav-bar-border: @border-base;
@nav-bar-height: @input-height + @padding-base * 2;

@button-text-color-dark: @grey-dark;
@button-text-color-light: @grey-lightest;
@button-background-default: @grey-lighter;
Expand Down
71 changes: 71 additions & 0 deletions src/ts/components/navigation/nav-bar.examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#### Example

```js
class NavBarExample extends React.Component {
constructor (props) {
super(props);

this.state = {};

this.onChange = this.onChange.bind(this);
}

render () {
const {
type
} = this.state;

return (
<div>
<NavBar fixed={type === 'fixed'} shy={type === 'shy'}>
<Column>
<SpacedGroup>
<Button>
Home
</Button>
<Button>
About
</Button>
<Button>
Contact
</Button>
</SpacedGroup>
</Column>
</NavBar>
<FormGroup block>
<label>
NavBar type
</label>
<select onChange={this.onChange}>
<option value="static">
Static
</option>
<option value="fixed">
Fixed
</option>
<option value="shy">
Shy
</option>
</select>
</FormGroup>
</div>
);
}

onChange (event) {
this.setState({
type: event.target.value
});
}
}

<NavBarExample />
```

#### Less variables

```less
@nav-bar-background: @body-background; // @white;
@nav-bar-border: @border-base;
@nav-bar-height: @input-height + @padding-base * 2;
```
137 changes: 137 additions & 0 deletions src/ts/components/navigation/nav-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as classNames from 'classnames';
import * as React from 'react';
import { HTMLProps, PureComponent } from 'react';
import * as ReactDOM from 'react-dom';
import { ComponentProps } from '../../types';
import { addClassName, getScrollOffset, removeClassName } from '../../utils';

const WITH_FIXED_NAV_BAR = 'with-fixed-nav-bar';

export interface NavBarProps extends ComponentProps, HTMLProps<HTMLElement> {
/**
* Fix the navbar to the top of the screen
*/
fixed?: boolean;
/**
* Hide the navbar when scrolling down, but display when scrolling up
*/
shy?: boolean;
/**
* Remove NavBar shadow
*/
noShadow?: boolean;
}

export interface NavBarState {
hidden: boolean;
}

export class NavBar extends PureComponent<NavBarProps, NavBarState> {
private previousScrollY: number;

public constructor (props: NavBarProps) {
super(props);

this.previousScrollY = getScrollOffset().y;

this.state = {
hidden: false,
};
}

public componentWillMount () {
this.updateBodyClass(this.props);
this.toggleShyListeners(this.props);
}

public componentWillUpdate (nextProps: NavBarProps) {
if (this.props.shy !== nextProps.shy) {
this.toggleShyListeners(nextProps);
}

if (this.props.fixed !== nextProps.fixed || this.props.shy !== nextProps.shy) {
this.updateBodyClass(nextProps);
}
}

public componentWillUnmount () {
window.removeEventListener('scroll', this.hideOrShowNavBar);
window.removeEventListener('resize', this.hideOrShowNavBar);
}

public render () {
const {
children,
className,
fixed,
shy,
noShadow,
component: Component = 'div',
...remainingProps,
} = this.props;

const {
hidden,
} = this.state;

const myClassNames = [
'nav-bar',
fixed || shy ? 'fixed' : null,
shy ? 'shy' : null,
hidden ? 'hidden' : null,
noShadow ? 'no-shadow' : null,
className
];

return (
<Component {...remainingProps} className={classNames(myClassNames)}>
{children}
</Component>
);
}

private updateBodyClass (props: NavBarProps) {
const { fixed, shy } = props;

if (fixed || shy) {
addClassName(document.body, WITH_FIXED_NAV_BAR);
} else {
removeClassName(document.body, WITH_FIXED_NAV_BAR)
}
}

private toggleShyListeners (props: NavBarProps) {
const { shy } = props;

if (shy) {
window.addEventListener('scroll', this.hideOrShowNavBar);
window.addEventListener('resize', this.hideOrShowNavBar);
} else {
window.removeEventListener('scroll', this.hideOrShowNavBar);
window.removeEventListener('resize', this.hideOrShowNavBar);
}
}

private hideOrShowNavBar = () => {
const { y } = getScrollOffset();
const element = ReactDOM.findDOMNode(this);

if (element) {
const { height } = element.getBoundingClientRect();

if (y > this.previousScrollY && y > height) {
this.setState({
hidden: true,
});
} else if (y < this.previousScrollY) {
this.setState({
hidden: false,
});
}
}

this.previousScrollY = y;
}
}

export default NavBar;
5 changes: 3 additions & 2 deletions src/ts/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export const NBSP = '\u00a0';

export const MATCHES_WHITESPACE = /\s+/g;
export const MATCHES_INITIAL_INDENTATION = /^([^\S\n]*)\S/;
export const MATCHES_BLANK_FIRST_LINE = /^\s*\n/;
export const MATCHES_BLANK_LAST_LINE = /\n\s*$/;

export const MATCHES_AMPERSAND = /&/gi;
export const MATCHES_AMPERSAND = /&/g;
export const MATCHES_NON_WORD_CHARACTERS = /[\W_]+/gi;
export const MATCHES_LEADING_AND_TRAILING_HYPHENS = /(^-+|-+$)/gi;
export const MATCHES_LEADING_AND_TRAILING_HYPHENS = /(^-+|-+$)/g;
1 change: 1 addition & 0 deletions src/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { default as ModalCloseIcon } from './components/modals/modal-close-icon'
export { default as ModalFooter } from './components/modals/modal-footer';
export { default as ModalHeader } from './components/modals/modal-header';
export { default as ModalRenderer } from './components/modals/modal-renderer';
export { default as NavBar } from './components/navigation/nav-bar';
export { default as Row } from './components/grid/row';
export { default as Section } from './components/content/section';
export { default as SpacedGroup } from './components/spaced-group';
Expand Down
40 changes: 40 additions & 0 deletions src/ts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MATCHES_INITIAL_INDENTATION,
MATCHES_LEADING_AND_TRAILING_HYPHENS,
MATCHES_NON_WORD_CHARACTERS,
MATCHES_WHITESPACE,
} from './constants';

export const formatCode = (code: string) => {
Expand Down Expand Up @@ -56,3 +57,42 @@ export const shouldNotBeRendered = (children: any) => {
}

export const isValidColumnNumber = (value?: number) => typeof value === 'number' && value === +value;

export const addClassName = (element: HTMLElement, className: string) => {
const myClassNames = element.className
.trim()
.split(MATCHES_WHITESPACE);

if (myClassNames.indexOf(className) >= 0) {
return;
}

element.className = [...myClassNames, className].join(' ');
};

export const removeClassName = (element: HTMLElement, className: string) => {
const myClassNames = element.className
.trim()
.split(MATCHES_WHITESPACE);

const index = myClassNames.indexOf(className);

if (index < 0) {
return;
}

myClassNames.splice(index, 1);

element.className = myClassNames.join(' ');
};

export const getScrollOffset = () => {
const doc = document.documentElement;
const left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
const top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);

return {
x: left,
y: top,
};
}
4 changes: 4 additions & 0 deletions styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ var components = [
name: 'Modals',
components: 'src/ts/components/modals/**/*.tsx'
},
{
name:'Navigation',
components: 'src/ts/components/navigation/**/*.tsx'
},
{
name: 'Forms',
components: 'src/ts/components/forms/**/*.tsx'
Expand Down
41 changes: 41 additions & 0 deletions tests/__snapshots__/nav-bar.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`NavBar should apply fixed class 1`] = `
<div
className="nav-bar fixed"
/>
`;

exports[`NavBar should apply no shadow class 1`] = `
<div
className="nav-bar no-shadow"
/>
`;

exports[`NavBar should apply shy class 1`] = `
<div
className="nav-bar fixed shy"
/>
`;

exports[`NavBar should apply the hidden class 1`] = `
<NavBar
shy={true}
>
<div
className="nav-bar fixed shy hidden"
/>
</NavBar>
`;

exports[`NavBar should match snapshot 1`] = `
<div
className="nav-bar"
/>
`;

exports[`NavBar should take regular element attributes 1`] = `
<div
className="nav-bar my-class"
/>
`;
Loading

0 comments on commit a6bbe25

Please sign in to comment.