@appearhere/bloom
Version:
Appear Here's pattern library and styleguide
143 lines (123 loc) • 4 kB
JavaScript
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import cx from 'classnames';
import throttle from 'lodash/fp/throttle';
import { canUseDOM } from 'exenv';
import m from '../../../globals/modifiers.css';
import css from './HorizontalOverflowBar.css';
export default class HorizontalOverflowBar extends Component {
static propTypes = {
children: PropTypes.func.isRequired,
threshold: PropTypes.number,
className: PropTypes.string,
/* eslint-disable react/no-unused-prop-types */
containerQuery: PropTypes.shape({
[css.centered]: PropTypes.bool,
}),
/* eslint-enable react/no-unused-prop-types */
};
static defaultProps = {
threshold: 1,
};
constructor(props) {
super(props);
this.state = {
edgeNearlyReached: 'left',
};
this.actualListWidth = 0;
this.handleEdgeNearlyReached = throttle(100, this.handleEdgeNearlyReached);
}
componentDidMount() {
this.scrollable.addEventListener('scroll', this.handleEdgeNearlyReached);
this.getActualListWidth();
this.handleEdgeNearlyReached();
}
componentWillUnmount() {
this.scrollable.removeEventListener('scroll', this.handleEdgeNearlyReached);
}
getActualListWidth = () => {
if (!canUseDOM) return;
const { scrollable } = this;
const listItems = Array.prototype.slice.call(
scrollable.querySelectorAll(`.${css.item}`)
);
const unpaddedWidth = listItems.reduce((prev, component) =>
prev + component.offsetWidth
, 0);
const leftPadding = parseInt(
/* eslint-disable no-undef */
window.getComputedStyle(scrollable, null).getPropertyValue('padding-left').slice('px')[0],
10
/* eslint-enable no-undef */
);
const rightPadding = parseInt(
/* eslint-disable no-undef */
window.getComputedStyle(scrollable, null).getPropertyValue('padding-right').slice('px')[0],
10
/* eslint-enable no-undef */
);
this.actualListWidth = unpaddedWidth + leftPadding + rightPadding;
};
handleEdgeNearlyReached = () => {
if (!canUseDOM) return;
const { navigation, scrollable, actualListWidth } = this;
const { threshold } = this.props;
const { left: leftPosition } = scrollable.getBoundingClientRect();
const { scrollLeft: scrollPosition } = scrollable;
const outerWidth = navigation.getBoundingClientRect().width;
if (scrollPosition <= leftPosition + threshold) {
this.setState({
edgeNearlyReached: 'left',
});
} else if (actualListWidth - outerWidth - scrollPosition < threshold) {
this.setState({
edgeNearlyReached: 'right',
});
} else {
this.setState({
edgeNearlyReached: null,
});
}
};
render() {
const { children, className, containerQuery } = this.props;
const { edgeNearlyReached } = this.state;
const classes = cx(
css.root,
{
[css.left]: edgeNearlyReached === 'left',
[css.right]: edgeNearlyReached === 'right',
},
className,
containerQuery,
m.fontRegular
);
return (
<nav className={ classes } ref={ (c) => { this.navigation = c; } }>
<div className={ css.listWrapper } ref={ (c) => { this.scrollable = c; } }>
<ul className={ css.list }>
{ children && children(({
className: childClassName,
href,
label,
active,
...rest,
}) => {
const itemClasses = cx(css.item, childClassName, {
[css.active]: active,
});
const linkClasses = cx(css.link, {
[css.activeLink]: active,
});
return (
<li className={ itemClasses } { ...rest }>
<a className={ linkClasses } href={ href }>{ label }</a>
</li>
);
}) }
</ul>
</div>
</nav>
);
}
}