Skip to content

Commit f605b8c

Browse files
committed
added dynamic resolve function, updated examples and readme
1 parent 760cc90 commit f605b8c

File tree

10 files changed

+142
-93
lines changed

10 files changed

+142
-93
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/dist
22
node_modules
33
/.idea
4+
npm-debug.log

README.md

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# react-navtree
22

3-
Utility for making page navigation (using keyboard or TV Remote Control on STB/ Smart TV apps) React way
3+
Library for making page navigation (using keyboard or TV Remote Control in STB/ Smart TV apps) React way
4+
5+
- Lightweight ( 7kb minified, 3 kb gzipped)
6+
- No dependencies (except for React itself)
47

58
_This project is under development and is still experimental_
69

710
> In STB/ Smart TV apps navigation is performed using TV remote control.
8-
> With traditional approach the navigation logic is implemented globally at the app level and is tightly coupled with UI layout, which doesn't fit with React principles of reusable and independent components.
11+
> 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.
912
>
1013
> The idea is to make components' navigation logic encapsulated within themselves and independent of the layout the component is placed into.
1114
> `react-navtree` was designed to help make navigaton React way - declarative and reusable.
@@ -18,7 +21,7 @@ _This project is under development and is still experimental_
1821
### Installation
1922

2023
```
21-
npm install react-navtree
24+
npm install --save react-navtree
2225
```
2326

2427
### Usage example
@@ -64,23 +67,23 @@ class Header extends React.PureComponent {
6467
}
6568

6669
render () {
67-
return <Nav func={navVertical} className='header'>
70+
return <Nav className='header'>
6871
<Nav
6972
onNav={path => { if (path) this.setState({ tab: path[0] }) }}
70-
func={(key, navTree) => (!navTree.focusedNode && this.state.tab) ? this.state.tab : navHorizontal(key, navTree)}
73+
func={/* focus open tab first */ (key, navTree, focusedNode) => !navTree.focusedNode ? this.state.tab : navDynamic(key, navTree, focusedNode)}
7174
className='tabs'
7275
>
7376
<Nav navId='tab-foo' className={this.state.tab === 'tab-foo' ? 'selected' : ''}>Foo</Nav>
7477
<Nav navId='tab-qux' className={this.state.tab === 'tab-qux' ? 'selected' : ''}>Qux</Nav>
7578
</Nav>
7679

77-
{ this.state.tab === 'tab-foo' && <Nav func={navHorizontal} className='tab-content' >
80+
{ this.state.tab === 'tab-foo' && <Nav className='tab-content' >
7881
<Nav className='nav-block inline'>Foo</Nav>
7982
<Nav className='nav-block inline'>Bar</Nav>
8083
<Nav className='nav-block inline'>Baz</Nav>
8184
</Nav> }
8285

83-
{ this.state.tab === 'tab-qux' && <Nav func={navVertical} className='tab-content' >
86+
{ this.state.tab === 'tab-qux' && <Nav className='tab-content' >
8487
<Nav className='nav-block'>Qux</Nav>
8588
<Nav className='nav-block'>Quux</Nav>
8689
</Nav> }
@@ -91,42 +94,25 @@ class Header extends React.PureComponent {
9194
class Table extends React.PureComponent {
9295
constructor (props) {
9396
super(props)
94-
this.state = { navPath: false }
97+
this.state = { focusedPath: false }
9598
}
9699

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

100-
// convert flat array to multidimensional (table-like format): [1, 2, 3, 4, 5, 6] => [[1, 2, 3], [4, 5, 6]]
101-
let rows = children.reduce((rows, cell) => {
102-
let row = rows.length > 0 && rows[ rows.length - 1 ]
103-
if (row.length >= cols) row = false
104-
if (!row) {
105-
row = []
106-
rows.push(row)
107-
}
108-
row.push(cell)
109-
return rows
110-
}, [])
111-
112-
return <Nav onNav={path => { this.setState({ navPath: path }) }} navId={navId} func={navTable({cols})} className='nav-block table'>
113-
<div>{ this.state.navPath && 'Focused: ' + this.state.navPath.join(' -> ') }</div>
114-
<table>
115-
<tbody>
116-
{
117-
rows.map((row, i) => <tr key={i}>
118-
{row.map((cell, j) => <td key={j}><Nav navId={`row ${i + 1} cell ${j + 1}`} className='nav-block'>{cell}</Nav></td>)}
119-
</tr>)
120-
}
121-
</tbody>
122-
</table>
103+
return <Nav onNav={path => { this.setState({ focusedPath: path }) }} navId={navId} className='nav-block'>
104+
<div className='caption'>{ this.state.focusedPath && 'Focused: ' + this.state.focusedPath.join(' -> ') }</div>
105+
<div className={'tbl col' + cols}>
106+
{ 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>) }
107+
</div>
123108
</Nav>
124109
}
125110
}
126111

127112
class Body extends React.PureComponent {
128113
render () {
129114
return (<div>
115+
130116
<Table cols={3}>
131117
<div>A</div>
132118
<div>B</div>
@@ -153,20 +139,18 @@ class Body extends React.PureComponent {
153139
<div>8</div>
154140
</Table>
155141
</Table>
142+
156143
</div>)
157144
}
158145
}
159146

160147
class Footer extends React.PureComponent {
161148
render () {
162149
return (
163-
<Nav
164-
className='footer'
165-
func={(key, navTree) => (!navTree.focusedNode) ? 'bar' : navHorizontal(key, navTree)}
166-
>
167-
<Nav navId='foo' className='nav-block inline'>Foo</Nav>
168-
<Nav navId='bar' className='nav-block inline'>Bar</Nav>
169-
<Nav navId='baz' className='nav-block inline'>Baz</Nav>
150+
<Nav className='footer'>
151+
<Nav className='nav-block inline'>Foo</Nav>
152+
<Nav className='nav-block inline'>Bar</Nav>
153+
<Nav className='nav-block inline'>Baz</Nav>
170154
</Nav>
171155
)
172156
}

examples/basic/example.css

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ body {width: 600px;}
1414
.tab-content { border-top: 1px solid #999; padding: 10px 5px 5px 5px; min-height: 100px; }
1515

1616
/* Body */
17-
.nav-block.table { text-align: center }
18-
.nav-block.table > div { font-size: 0.9em; height: 20px; }
19-
.nav-block.table table { width: 100%; table-layout: fixed }
17+
.caption { font-size: 0.9em; height: 20px; text-align: center }
18+
.tbl { text-align: center }
19+
.tbl > * { display: inline-block; }
20+
.tbl.col3 > * { width: 33% }
21+
.tbl.col2 > * { width: 50% }
2022

2123
/* Footer */
2224
.footer { background-color: #EEEEEE; padding: 10px; text-align: center }

examples/basic/example.js

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import PropTypes from 'prop-types'
3-
import Nav, {navTable, navHorizontal, navVertical} from 'react-navtree'
3+
import Nav, {navDynamic} from 'react-navtree'
44
import './example.css'
55

66
class Header extends React.PureComponent {
@@ -10,23 +10,23 @@ class Header extends React.PureComponent {
1010
}
1111

1212
render () {
13-
return <Nav func={navVertical} className='header'>
13+
return <Nav className='header'>
1414
<Nav
1515
onNav={path => { if (path) this.setState({ tab: path[0] }) }}
16-
func={(key, navTree) => (!navTree.focusedNode && this.state.tab) ? this.state.tab : navHorizontal(key, navTree)}
16+
func={/* focus open tab first */ (key, navTree, focusedNode) => !navTree.focusedNode ? this.state.tab : navDynamic(key, navTree, focusedNode)}
1717
className='tabs'
1818
>
1919
<Nav navId='tab-foo' className={this.state.tab === 'tab-foo' ? 'selected' : ''}>Foo</Nav>
2020
<Nav navId='tab-qux' className={this.state.tab === 'tab-qux' ? 'selected' : ''}>Qux</Nav>
2121
</Nav>
2222

23-
{ this.state.tab === 'tab-foo' && <Nav func={navHorizontal} className='tab-content' >
23+
{ this.state.tab === 'tab-foo' && <Nav className='tab-content' >
2424
<Nav className='nav-block inline'>Foo</Nav>
2525
<Nav className='nav-block inline'>Bar</Nav>
2626
<Nav className='nav-block inline'>Baz</Nav>
2727
</Nav> }
2828

29-
{ this.state.tab === 'tab-qux' && <Nav func={navVertical} className='tab-content' >
29+
{ this.state.tab === 'tab-qux' && <Nav className='tab-content' >
3030
<Nav className='nav-block'>Qux</Nav>
3131
<Nav className='nav-block'>Quux</Nav>
3232
</Nav> }
@@ -37,35 +37,17 @@ class Header extends React.PureComponent {
3737
class Table extends React.PureComponent {
3838
constructor (props) {
3939
super(props)
40-
this.state = { navPath: false }
40+
this.state = { focusedPath: false }
4141
}
4242

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

46-
// convert flat array to multidimensional (table-like format): [1, 2, 3, 4, 5, 6] => [[1, 2, 3], [4, 5, 6]]
47-
let rows = children.reduce((rows, cell) => {
48-
let row = rows.length > 0 && rows[ rows.length - 1 ]
49-
if (row.length >= cols) row = false
50-
if (!row) {
51-
row = []
52-
rows.push(row)
53-
}
54-
row.push(cell)
55-
return rows
56-
}, [])
57-
58-
return <Nav onNav={path => { this.setState({ navPath: path }) }} navId={navId} func={navTable({cols})} className='nav-block table'>
59-
<div>{ this.state.navPath && 'Focused: ' + this.state.navPath.join(' -> ') }</div>
60-
<table>
61-
<tbody>
62-
{
63-
rows.map((row, i) => <tr key={i}>
64-
{row.map((cell, j) => <td key={j}><Nav navId={`row ${i + 1} cell ${j + 1}`} className='nav-block'>{cell}</Nav></td>)}
65-
</tr>)
66-
}
67-
</tbody>
68-
</table>
46+
return <Nav onNav={path => { this.setState({ focusedPath: path }) }} navId={navId} className='nav-block'>
47+
<div className='caption'>{ this.state.focusedPath && 'Focused: ' + this.state.focusedPath.join(' -> ') }</div>
48+
<div className={'tbl col' + cols}>
49+
{ 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>) }
50+
</div>
6951
</Nav>
7052
}
7153
}
@@ -79,6 +61,7 @@ Table.propTypes = {
7961
class Body extends React.PureComponent {
8062
render () {
8163
return (<div>
64+
8265
<Table cols={3}>
8366
<div>A</div>
8467
<div>B</div>
@@ -105,27 +88,25 @@ class Body extends React.PureComponent {
10588
<div>8</div>
10689
</Table>
10790
</Table>
91+
10892
</div>)
10993
}
11094
}
11195

11296
class Footer extends React.PureComponent {
11397
render () {
11498
return (
115-
<Nav
116-
className='footer'
117-
func={(key, navTree) => (!navTree.focusedNode) ? 'bar' : navHorizontal(key, navTree)}
118-
>
119-
<Nav navId='foo' className='nav-block inline'>Foo</Nav>
120-
<Nav navId='bar' className='nav-block inline'>Bar</Nav>
121-
<Nav navId='baz' className='nav-block inline'>Baz</Nav>
99+
<Nav className='footer'>
100+
<Nav className='nav-block inline'>Foo</Nav>
101+
<Nav className='nav-block inline'>Bar</Nav>
102+
<Nav className='nav-block inline'>Baz</Nav>
122103
</Nav>
123104
)
124105
}
125106
}
126107

127108
export default function Example () {
128-
return <Nav func={navVertical}>
109+
return <Nav>
129110

130111
<Header />
131112
<Body />

examples/modal/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {Component} from 'react'
2-
import Nav, {navHorizontal} from 'react-navtree'
2+
import Nav from 'react-navtree'
33
import Modal from './modal'
44
import renderExample from '../utils/renderExample'
55

@@ -17,7 +17,7 @@ class Example extends Component {
1717
render () {
1818
return (
1919
<div>
20-
<Nav func={navHorizontal} navId='body'>
20+
<Nav>
2121
<Nav func={this._navFuncToggleModal} className='button'>Press to show modal window #1</Nav>
2222
<Nav func={this._navFuncToggleModal} className='button'>Press to show modal window #2</Nav>
2323
</Nav>
@@ -30,7 +30,7 @@ class Example extends Component {
3030
<li>when the modal is closed, the previously focused component gets focused back</li>
3131
</ul>
3232

33-
<Nav func={navHorizontal}>
33+
<Nav>
3434
<Nav func={this._navFuncToggleModal} defaultFocused className='button'>Agree</Nav>
3535
<Nav func={this._navFuncToggleModal} className='button'>OK</Nav>
3636
<Nav func={this._navFuncToggleModal} className='button'>Confirm</Nav>

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-navtree",
3-
"version": "0.0.1",
4-
"description": "Utility for making page navigation (using keyboard or TV Remote Control on STB/ Smart TV apps) React way",
3+
"version": "0.0.2",
4+
"description": "Library for making page navigation (using keyboard or TV Remote Control in STB/ Smart TV apps) React way",
55
"main": "dist/index.js",
66
"dependencies": {
77
"prop-types": "^15.5.7"

src/Nav.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react'
22
import PropTypes from 'prop-types'
33
import NavTree from './NavTree'
4+
import { navDynamic } from './NavFunctions'
45

56
export default class Nav extends React.PureComponent {
67
constructor (props) {
@@ -44,11 +45,12 @@ export default class Nav extends React.PureComponent {
4445
}
4546
}
4647

47-
resolveFunc (event, navTree) {
48+
resolveFunc (event, navTree, focusedNode) {
4849
if (!this.props.func) {
49-
return this.state.focused ? false : (navTree.nodesId.length > 0 ? navTree.nodesId[0] : null)
50+
if (navTree.nodesId.length > 1) return navDynamic(event, navTree, focusedNode)
51+
else return this.state.focused ? false : (navTree.nodesId.length > 0 ? navTree.nodesId[0] : null)
5052
} else {
51-
return this.props.func(event, navTree)
53+
return this.props.func(event, navTree, focusedNode)
5254
}
5355
}
5456

@@ -72,7 +74,7 @@ export default class Nav extends React.PureComponent {
7274
}
7375

7476
return (
75-
<Component className={className} {...restProps}>{children}</Component>
77+
<Component ref={ref => { if (this.tree) this.tree.el = ref }} className={className} {...restProps}>{children}</Component>
7678
)
7779
}
7880
}

0 commit comments

Comments
 (0)