npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-navtree

v0.0.6

Published

Library for making page navigation (using keyboard or TV Remote Control in STB/ Smart TV apps) React way

Downloads

46

Readme

react-navtree

Library for making page navigation (using keyboard or TV Remote Control in STB/ Smart TV apps) React way

  • Lightweight (8 kb minified, 3 kb gzipped)
  • No dependencies (except for React itself)

This project is under development and is still experimental

In STB/ Smart TV apps navigation is performed using TV remote control. In traditional approach, navigation logic is implemented outside 'View' layer and is tightly coupled with UI layout, which doesn't fit with React principles of reusable and independent components.

The idea is to make components' navigation logic encapsulated within themselves and independent of the layout the component is placed into. react-navtree was designed to help make navigation React way - declarative and reusable.

Demo

Live examples. See examples directory for source code.

Installation

npm install --save react-navtree

Usage example

  1. Initialize NavTree instance which will serve as an entry point for keypress events
// At the bootstrap phase (before rendering the app):

const navTree = new NavTree()

// this code depends on the platform you're developing for
window.document.addEventListener('keydown', (e) => {
    let key

    switch (e.keyCode) {
      case 40:
        key = 'down'; break
      case 38:
        key = 'up'; break
      case 37:
        key = 'left'; break
      case 39:
        key = 'right'; break
      case 27:
        key = 'enter'; break
      default:
    }
    if (key) {
      e.preventDefault()
      navTree.resolve(key)
    }
  }, false)
  1. Make use of <Nav> component
class Header extends React.PureComponent {
  constructor (props) {
    super(props)
    this.state = { tab: 'tab-foo' }
  }

  render () {
    return <Nav className='header'>
      <Nav
        onNav={path => { if (path) this.setState({ tab: path[0] }) }}
        func={/* focus open tab first */ (key, navTree, focusedNode) => !navTree.focusedNode ? this.state.tab : navDynamic(key, navTree, focusedNode)}
        className='tabs'
      >
        <Nav navId='tab-foo' className={this.state.tab === 'tab-foo' ? 'selected' : ''}>Foo</Nav>
        <Nav navId='tab-qux' className={this.state.tab === 'tab-qux' ? 'selected' : ''}>Qux</Nav>
      </Nav>

      { this.state.tab === 'tab-foo' && <Nav className='tab-content' >
        <Nav className='nav-block inline'>Foo</Nav>
        <Nav className='nav-block inline'>Bar</Nav>
        <Nav className='nav-block inline'>Baz</Nav>
      </Nav> }

      { this.state.tab === 'tab-qux' && <Nav className='tab-content' >
        <Nav className='nav-block'>Qux</Nav>
        <Nav className='nav-block'>Quux</Nav>
      </Nav> }
    </Nav>
  }
}

class Table extends React.PureComponent {
  constructor (props) {
    super(props)
    this.state = { focusedPath: false }
  }

  render () {
    let {children, navId, cols} = this.props

    return <Nav onNav={path => { this.setState({ focusedPath: path }) }} navId={navId} className='nav-block'>
      <div className='caption'>{ this.state.focusedPath && 'Focused: ' + this.state.focusedPath.join(' -> ') }</div>
      <div className={'tbl col' + cols}>
        { children.map((child, i) => <div key={i}><Nav navId={`row ${Math.ceil((i + 1) / cols)} cell ${(i % cols) + 1}`} className='nav-block'>{child}</Nav></div>) }
      </div>
    </Nav>
  }
}

class Body extends React.PureComponent {
  render () {
    return (<div>

      <Table cols={3}>
        <div>A</div>
        <div>B</div>
        <div>C</div>
        <div>D</div>
        <div>E</div>
        <div>F</div>
        <div>G</div>
        <div>H</div>
        <div>J</div>
      </Table>

      <Table cols={2}>
        <Table navId='table 1' cols={2}>
          <div>1</div>
          <div>2</div>
          <div>3</div>
          <div>4</div>
        </Table>
        <Table navId='table 2' cols={2}>
          <div>5</div>
          <div>6</div>
          <div>7</div>
          <div>8</div>
        </Table>
      </Table>

    </div>)
  }
}

class Footer extends React.PureComponent {
  render () {
    return (
      <Nav className='footer'>
        <Nav className='nav-block inline'>Foo</Nav>
        <Nav className='nav-block inline'>Bar</Nav>
        <Nav className='nav-block inline'>Baz</Nav>
      </Nav>
    )
  }
}

export default function Layout () {
  return <Nav>

    <Header />
    <Body />
    <Footer />

  </Nav>
}

ReactDOM.render(<Layout tree={navTree} />)

Open the example above

Description

The project consists of 2 main classes:

  1. NavTree - Navigation tree
  2. <Nav> - React component
Tree hierarchy

NavTree represents a tree structure reflecting React's node hierarchy of <Nav> components. It is accomplished by binding <Nav> components to the navigation tree at the mounting phase. When <Nav> component is mounted it creates a new branch in the tree. This branch in its turn will serve as a parent tree to children Nav components through Reacts' context mechanism. Likewise, when <Nav> component is unmounted, the branch gets removed from the tree thus keeping the tree in sync with React.

Navigation

Navigation is performed by using NavTree.focus(path) or NavTree.resolve(event) functions.

NavTree.focus(path) is used for imperative navigation.

NavTree.resolve(event) is used for resolving an event (keypress) to a node that should be focused next.

Navigation resolving

Navigation resolving is a process of finding a node that should be focused next when an event occurred.

Each node in the navigation tree must have so-called "resolve function" that is responsible for finding a node that should receive focus according to an event and previously focused node.

* By default, navDynamic is used if the resolve function is not provided.

Resolving process phases:
  1. Resolving Up: Starting at the deepest focused node, the event is propagated up the tree until it is resolved.
  2. Resolving Down: Starting at the resolved node in phase #1, the event is propagated down the tree as long as it is resolved.
  3. Focus: The destination node (the last one in phase #2) gets focused.

* event is resolved if the node's "resolve function" returns a child node ID or NULL (itself) * if a node gets focused, all of his ascendants get focused as well

From component perspective:

When an event (keypress) occurs, first, the currently focused <Nav> component gets control of the process. The resolve function (passed as "func" property) gets called to determine if the component can resolve the event.

If so, the component stays focused and the event is propagated down to a child component returned by the resolve function. (If the resolve function returns NULL, the component stays focused, but the focused child (if any) will lose focus)

If not, the event is propagated up to the parent component. The component will lose focus if the event is eventually resolved to a different component.

API

<Nav> component

| Property | Type | Default | Description | | --- | --- | --- | --- | | func | function (event: any, node: NavTree, previouslyFocusedNode: NavTree) => string | null | false | navDynamic | Navigation resolve function. | | tree | NavTree | obtained from context | Parent navigation tree. Should be set only on root component. | | navId | string | | ID used for the bound NavTree node. Must be unique within parent tree's direct nodes. If omitted, a numerical ID will be generated. | | defaultFocused | boolean | false | If set to true, the component will get focused after it's been mounted | | onNav | function (path: Array<string> | false ) => void | | Navigation event callback | | component | string | 'div' | Wrapper element | | focusedClass | string | 'nav-focused' | CSS-class applied to focused component | ... | | | All other properties are passed to the component node. |

NavTree

| Method | Arguments | Return value | Description | | --- | --- | --- | --- | | focus(path) | path: string | Array<string> | undefined | | Focuses a node specified by path argument. If omitted, the tree instance itself gets focused. | | resolve(event) | event: any | | Resolves the event so that the appropriate node gets focused. | | addNode(id) | id: string | undefined | NavTree | Creates a new branch in the tree. If id is omitted, a numerical ID will be generated.id must be unique within the direct nodes of the tree. | | removeNode(id) | id: string | | Removes the node from the tree | | getNode(path) | string | Array<string> | NavTree | null | Returns descendant node instance | | getFocusedPath() | | Array<string> | Returns descendant focused node's path |

| Property | Type | Description | | --- | --- | --- | | nodes | Object{ id: Navtree } | List of child nodes | | nodesId | Array<string> | Array of child nodes ID arranged in the order the nodes have been added | | parent | Navtree | null | Parent node | | focusedNode | string | null | ID of focused child node |

License

MIT