Skip to content

Instantly share code, notes, and snippets.

@TimCodes
Created April 23, 2018 21:12
Show Gist options
  • Select an option

  • Save TimCodes/abe11532ad73f62c72bfc662625b08ff to your computer and use it in GitHub Desktop.

Select an option

Save TimCodes/abe11532ad73f62c72bfc662625b08ff to your computer and use it in GitHub Desktop.

Revisions

  1. TimCodes created this gist Apr 23, 2018.
    1 change: 1 addition & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    <div id="root"></div>
    9 changes: 9 additions & 0 deletions react-horizontal-scrolling-menu.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,9 @@
    React Horizontal Scrolling Menu
    -------------------------------
    ES6 & React - Simple Click Counter Example

    Forked from [Brad Daily](http://codepen.io/bradleyboy/)'s Pen [ES6 playground](http://codepen.io/bradleyboy/pen/vEeENy/).

    A [Pen](https://codepen.io/TimCodes/pen/yjNEKe) by [Tim Hardy](https://codepen.io/TimCodes) on [CodePen](https://codepen.io).

    [License](https://codepen.io/TimCodes/pen/yjNEKe/license).
    228 changes: 228 additions & 0 deletions script.babel
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,228 @@
    let linkArr = [
    { label : "Tables", isSelected : true} , { label : "Chairs", isSelected : false} , { label : "Beds", isSelected : false},
    { label : "Ricks", isSelected : false} , { label : "Morty's", isSelected : false},{ label : "Sanchez", isSelected : false},
    { label :"PinkBear", isSelected : false} , { label :"Raven", isSelected : false}, { label : "Penis", isSelected : false}
    ]

    const leftButton = ({onClick}) => (
    <button onClick={onClick} className="pn-Advancer pn-Advancer_Left" type="button">
    <svg className="pn-Advancer_Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 551 1024">
    <path d="M445.44 38.183L-2.53 512l447.97 473.817 85.857-81.173-409.6-433.23v81.172l409.6-433.23L445.44 38.18z"/>
    </svg>
    </button>
    );
    class Counter extends React.Component {

    constructor(props) {
    super(props);
    this.state = {
    count: 6,
    linkArr : linkArr,
    navBarTravelling: false,
    navBarTravelDirection: "",
    navBarTravelDistance: 150,
    last_known_scroll_position: 0,
    ticking: false,
    hrNavOverFlow : "right"

    };
    this.setSelected = this.setSelected.bind(this);
    this.determineOverflow = this.determineOverflow.bind(this);
    this.handleNavScroll = this.handleNavScroll.bind(this);
    this.setNavOverFlow = this.setNavOverFlow.bind(this);
    this.handleAdvanceLeftClick = this.handleAdvanceLeftClick.bind(this);
    this.handleAdvanceRightClick = this.handleAdvanceRightClick.bind(this);
    this.handleNavTransition = this.handleNavTransition.bind(this);
    }

    componentWillMount(){
    document.documentElement.classList.remove("no-js");
    document.documentElement.classList.add("js");
    }
    componentDidMount(){
    console.log("--- container monuted ---");
    console.log(this.refs.hrzSlideNavContents)
    this.setNavOverFlow();
    let { hrzSlideNav, hrzSlideNavContents } = this.refs;
    hrzSlideNavContents.getDOMNode().addEventListener(
    "transitionend",this.handleNavTransition ,false);
    }
    setNavOverFlow(){
    let {hrzSlideNav, hrzSlideNavContents} = this.refs;
    this.setState(
    {hrNavOverflow : this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode())}
    );
    hrzSlideNav.getDOMNode()
    .setAttribute( "data-overflowing",
    this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode())
    )
    }
    handleNavScroll(){
    console.log("handlenavScroll")

    let{last_known_scroll_position, ticking} = this.state;
    this.setState({last_known_scroll_position :window.scrollY});
    if (!ticking) {
    // use animation frame to get off of main thread
    window.requestAnimationFrame(function() {
    doSomething(last_known_scroll_position);
    this.setStae({ticking : false});
    });
    }
    this.setStae({ticking : true});
    }
    handleAdvanceLeftClick(){

    let { navBarTravelDistance, navBarTravelling, navBarTravelDirection } = this.state;
    let { hrzSlideNav, hrzSlideNavContents } = this.refs;

    // If in the middle of a move return
    if (navBarTravelling === true) {
    return;
    }
    // If we have content overflowing both sides or on the left
    if (this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "left" ||
    this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "both") {
    // Find how far this panel has been scrolled
    var availableScrollLeft = hrzSlideNav.getDOMNode().scrollLeft;
    // If the space available is less than two lots of our desired distance, just move the whole amount
    // otherwise, move by the amount in the settings
    if (availableScrollLeft < navBarTravelDistance * 2) {
    hrzSlideNavContents.getDOMNode().style.transform = "translateX(" + availableScrollLeft + "px)";
    } else {
    hrzSlideNavContents.getDOMNode().style.transform = "translateX(" + navBarTravelDistance + "px)";
    }
    // We do want a transition (this is set in CSS) when moving so remove the class that would prevent that
    hrzSlideNavContents.getDOMNode().classList.remove("pn-ProductNav_Contents-no-transition");
    // Update our settings
    this.setState({
    navBarTravelDirection : "left",
    navBarTravelling: true
    })

    }
    // Now update the attribute in the DOM
    hrzSlideNav.getDOMNode().setAttribute( "data-overflowing", this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) );
    }
    handleAdvanceRightClick(){
    console.log("handle right");
    let { navBarTravelDistance, navBarTravelling, navBarTravelDirection } = this.state;
    let { hrzSlideNav, hrzSlideNavContents } = this.refs;

    // If in the middle of a move return
    if (navBarTravelling === true) {
    return;
    }
    // If we have content overflowing both sides or on the right
    if (this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "right" ||
    this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode()) === "both") {
    // Get the right edge of the container and content
    var navBarRightEdge = hrzSlideNavContents.getDOMNode().getBoundingClientRect().right;
    var navBarScrollerRightEdge = hrzSlideNav.getDOMNode().getBoundingClientRect().right;
    // Now we know how much space we have available to scroll
    var availableScrollRight = Math.floor(navBarRightEdge - navBarScrollerRightEdge);
    // If the space available is less than two lots of our desired distance, just move the whole amount
    // otherwise, move by the amount in the settings
    if (availableScrollRight < navBarTravelDistance * 2) {
    hrzSlideNavContents.getDOMNode().style.transform = "translateX(-" + availableScrollRight + "px)";
    } else {
    hrzSlideNavContents.getDOMNode().style.transform = "translateX(-" + navBarTravelDistance + "px)";
    }
    // We do want a transition (this is set in CSS) when moving so remove the class that would prevent that
    hrzSlideNavContents.getDOMNode().classList.remove("pn-ProductNav_Contents-no-transition");
    // Update our settings
    this.setState({
    navBarTravelDirection : "right",
    navBarTravelling: true
    })

    }
    // Now update the attribute in the DOM
    hrzSlideNav.getDOMNode().setAttribute("data-overflowing", this.determineOverflow(hrzSlideNavContents.getDOMNode(), hrzSlideNav.getDOMNode() ));
    }
    handleNavTransition(){
    console.log("on transtion handler");
    let { hrzSlideNav, hrzSlideNavContents } = this.refs;
    let { navBarTravelDistance, navBarTravelling, navBarTravelDirection } = this.state;
    hrzSlideNav = hrzSlideNav.getDOMNode();
    hrzSlideNavContents = hrzSlideNavContents.getDOMNode();
    // get the value of the transform, apply that to the current scroll position (so get the scroll pos first) and then remove the transform
    var styleOfTransform = window.getComputedStyle( hrzSlideNavContents, null);
    var tr = styleOfTransform.getPropertyValue("-webkit-transform") || styleOfTransform.getPropertyValue("transform");
    // If there is no transition we want to default to 0 and not null
    var amount = Math.abs(parseInt(tr.split(",")[4]) || 0);
    hrzSlideNavContents.style.transform = "none";
    hrzSlideNavContents.classList.add("pn-ProductNav_Contents-no-transition");
    // Now lets set the scroll position
    if (navBarTravelDirection === "left") {
    hrzSlideNav.scrollLeft = hrzSlideNav.scrollLeft - amount;
    } else {
    hrzSlideNav.scrollLeft = hrzSlideNav.scrollLeft + amount;
    }
    this.setState({navBarTravelling : false});

    }
    setSelected(label){
    let newLinkArr =this.state.linkArr.map( l => {
    console.log(l)
    if(l.label == label){
    l.isSelected = true;
    }else{
    l.isSelected = false;
    }
    return l;
    })
    this.setState({linkArr: newLinkArr});
    }
    determineOverflow =(content, container) => {
    var containerMetrics = container.getBoundingClientRect();
    var containerMetricsRight = Math.floor(containerMetrics.right);
    var containerMetricsLeft = Math.floor(containerMetrics.left);
    var contentMetrics = content.getBoundingClientRect();
    var contentMetricsRight = Math.floor(contentMetrics.right);
    var contentMetricsLeft = Math.floor(contentMetrics.left);
    if (containerMetricsLeft > contentMetricsLeft && containerMetricsRight < contentMetricsRight) {
    return "both";
    } else if (contentMetricsLeft < containerMetricsLeft) {
    return "left";
    } else if (contentMetricsRight > containerMetricsRight) {
    return "right";
    } else {
    return "none";
    }
    };


    render() {
    let{linkArr, hrNavOverFlow} = this.state;
    let links = linkArr.map(l =>
    <a
    className = "pn-ProductNav_Link"
    aria-selected={l.isSelected}
    onClick = {() => this.setSelected(l.label)}
    >
    {l.label}
    </a>
    );

    return (
    <div className="pn-ProductNav_Wrapper">
    <nav ref="hrzSlideNav" className="pn-ProductNav" data-overflowing="right">
    <div ref="hrzSlideNavContents" onTransitionEnd = {() => this.handleNavTransition } className="pn-ProductNav_Contents">
    {links}
    </div>
    <span classNameName="pn-ProductNav_Indicator"></span>
    </nav>

    <button onClick={this.handleAdvanceRightClick} className="pn-Advancer pn-Advancer_Right" type="button">
    <svg className="pn-Advancer_Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 551 1024"><path d="M105.56 985.817L553.53 512 105.56 38.183l-85.857 81.173 409.6 433.23v-81.172l-409.6 433.23 85.856 81.174z"/></svg>
    </button>
    </div>
    );
    }
    }

    React.render(
    <Counter/>,
    document.getElementById("root")
    );
    1 change: 1 addition & 0 deletions scripts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    <script src="//cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.min.js"></script>
    107 changes: 107 additions & 0 deletions style.scss
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    .pn-ProductNav_Wrapper {
    position: relative;
    padding: 0 11px;
    width: 200px;
    }

    .pn-ProductNav {
    /* Make this scrollable when needed */
    overflow-x: auto;
    /* We don't want vertical scrolling */
    overflow-y: hidden;
    /* For WebKit implementations, provide inertia scrolling */
    -webkit-overflow-scrolling: touch;
    /* We don't want internal inline elements to wrap */
    white-space: nowrap;
    /* If JS present, let's hide the default scrollbar */
    .js & {
    /* Make an auto-hiding scroller for the 3 people using a IE */
    -ms-overflow-style: -ms-autohiding-scrollbar;
    /* Remove the default scrollbar for WebKit implementations */
    &::-webkit-scrollbar {
    display: none;
    }
    }
    /* positioning context for advancers */
    position: relative;
    }

    .pn-ProductNav_Contents {
    float: left;
    transition: transform .2s ease-in-out;

    }

    .pn-ProductNav_Contents-no-transition {
    transition: none;
    }

    .pn-ProductNav_Link {
    text-decoration: none;
    color: #888;
    font-size: 1.2em;
    font-family: -apple-system, sans-serif;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    & + & {
    margin-left: 11px;
    padding-left: 11px;
    border-left: 1px solid #eee;
    }
    &[aria-selected="true"] {
    color: #111;
    }
    }

    .pn-Advancer {
    /* Reset the button */
    appearance: none;
    background: transparent;
    padding: 0;
    border: 0;
    &:focus {
    outline: 0;
    }
    &:hover {
    cursor: pointer;
    }
    /* Now style it as needed */
    position: absolute;
    top: 0;
    bottom: 0;
    /* Set the buttons invisible by default */
    opacity: 0;
    transition: opacity .3s;
    }

    .pn-Advancer_Left {
    left: 0;
    [data-overflowing="both"] ~ &,
    [data-overflowing="left"] ~ & {
    opacity: 1;
    }
    }

    .pn-Advancer_Right {
    right: 0;
    [data-overflowing="both"] ~ &,
    [data-overflowing="right"] ~ & {
    opacity: 1;
    }
    }

    .pn-Advancer_Icon {
    width: 20px;
    height: 44px;
    fill: #bbb;
    }

    .pn-ProductNav_Indicator {
    position: absolute;
    bottom: 0;
    left: 0;
    height: 4px;
    width: 100px;
    background-color: #f90;
    }