├── .gitignore
├── .jshintrc
├── LICENSE
├── README.md
├── components
├── CellWrapper.js
├── SectionHeader.js
├── SectionList.js
└── SelectableSectionsListView.js
├── compositor.json
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | workbench
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "-W093": true,
3 | "asi": false,
4 | "bitwise": true,
5 | "boss": false,
6 | "browser": false,
7 | "camelcase": true,
8 | "couch": false,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "dojo": false,
13 | "eqeqeq": true,
14 | "eqnull": false,
15 | "esnext": true,
16 | "evil": false,
17 | "expr": true,
18 | "forin": false,
19 | "freeze": true,
20 | "funcscope": true,
21 | "gcl": false,
22 | "globalstrict": true,
23 | "immed": false,
24 | "indent": 2,
25 | "iterator": false,
26 | "jquery": false,
27 | "lastsemic": false,
28 | "latedef": false,
29 | "laxbreak": true,
30 | "laxcomma": false,
31 | "loopfunc": false,
32 | "maxcomplexity": false,
33 | "maxdepth": false,
34 | "maxerr": 50,
35 | "maxlen": 80,
36 | "maxparams": false,
37 | "maxstatements": false,
38 | "mootools": false,
39 | "moz": false,
40 | "multistr": false,
41 | "newcap": true,
42 | "noarg": true,
43 | "node": true,
44 | "noempty": true,
45 | "nonbsp": true,
46 | "nonew": true,
47 | "nonstandard": false,
48 | "notypeof": false,
49 | "noyield": false,
50 | "phantom": false,
51 | "plusplus": false,
52 | "predef": [
53 | "jasmine",
54 | "describe",
55 | "beforeEach",
56 | "it",
57 | "jest",
58 | "pit",
59 | "expect",
60 | "rootRequire"
61 | ],
62 | "proto": false,
63 | "prototypejs": false,
64 | "quotmark": true,
65 | "rhino": false,
66 | "scripturl": false,
67 | "shadow": false,
68 | "smarttabs": false,
69 | "strict": true,
70 | "sub": false,
71 | "supernew": false,
72 | "trailing": true,
73 | "undef": true,
74 | "unused": true,
75 | "validthis": false,
76 | "worker": false,
77 | "wsh": false,
78 | "yui": false
79 | }
80 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Johannes Lumpe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-selectablesectionlistview
2 |
3 | A Listview with a sidebar to directly jump to sections.
4 |
5 | Please file issues for missing features or bugs.
6 |
7 | I apologize for the bad name.
8 |
9 | 
10 |
11 | ## Usage
12 |
13 | The most basic way to use this component is as follows:
14 |
15 | ```javascript
16 | var SelectableSectionsListView = require('react-native-selectablesectionlistview');
17 |
18 | // inside your render function
19 |
25 | ```
26 |
27 | You can find a more complete example below
28 |
29 | ## Props
30 |
31 | ### SelectableSectionsListView
32 |
33 | All props are passed through to the underlying `ListView`, so you can specify all the available props for `ListView` normally - except the following, which are defined internally and will be overwritten:
34 |
35 | * `onScroll`
36 | * `onScrollAnimationEnd`
37 | * `dataSource`
38 | * `renderRow`
39 | * `renderSectionHeader`
40 |
41 | #### data
42 | `array|object`, **required**
43 | The data to render in the listview
44 |
45 | #### hideSectionList
46 | `boolean`
47 | Whether to show the section listing or not. *Note: If the data your are providing to
48 | the component is an array, the section list will automatically be hidden.*
49 |
50 | #### getSectionTitle
51 | `function`
52 | Function to provide titles for the section headers
53 |
54 | #### getSectionListTitle
55 | `function`
56 | Function to provide titles for the section list items
57 |
58 | #### onCellSelect
59 | `function`
60 | Callback which should be called when a cell has been selected
61 |
62 | #### onScrollToSection
63 | `function`
64 | Callback which should be called when the user scrolls to a section
65 |
66 | #### cell
67 | `function` **required**
68 | The cell component to render for each row
69 |
70 | #### sectionListItem
71 | `function`
72 | A custom component to render for each section list item
73 |
74 | #### sectionHeader
75 | `function`
76 | A custom component to render for each section header
77 |
78 | #### footer
79 | `function`
80 | A custom component to render as footer
81 | **This props takes precedence over `renderFooter`**
82 |
83 | #### renderFooter
84 | `function`
85 | A custom function which has to return a valid React element, which will be
86 | used as footer.
87 |
88 | #### header
89 | `function`
90 | A custom component to render as header
91 | **This props takes precedence over `renderHeader`**
92 |
93 | #### renderHeader
94 | `function`
95 | A custom function which has to return a valid React element, which will be used as header.
96 |
97 | #### headerHeight
98 | `number`
99 | The height of the rendered header element.
100 | **Is required if a header element is used, so the positions can be calculated correctly**
101 |
102 | #### cellProps
103 | `object`
104 | An object containing additional props, which will be passed to each cell component
105 |
106 | #### sectionHeaderHeight
107 | `number` **required**
108 | The height of the section header component
109 |
110 | #### cellHeight
111 | `number` **required**
112 | The height of the cell component
113 |
114 | #### useDynamicHeights
115 | `boolean`
116 | Whether to determine the y position to scroll to by calculating header and cell heights or by using the UIManager to measure the position of the destination element. Defaults to `false`
117 | **This is an experimental feature. For it to work properly you will most likely have to experiment with different values for `scrollRenderAheadDistance`, depending on the size of your data set.**
118 |
119 | #### updateScrollState
120 | `boolean`
121 | Whether to set the current y offset as state and pass it to each cell during re-rendering
122 |
123 | #### style
124 | `object|number`
125 | Styles to pass to the container
126 |
127 | #### sectionListStyle
128 | `object|number`
129 | Styles to pass to the section list container
130 |
131 | ---
132 | ### Cell component
133 |
134 | These props are automatically passed to your component. In addition to these, your cell will receive all props which you specified in the object you passed as `cellProps` prop to the listview.
135 |
136 | #### index
137 | `number`
138 | The index of the cell inside the current section
139 |
140 | #### sectionId
141 | `string`
142 | The id of the parent section
143 |
144 | #### isFirst
145 | `boolean`
146 | Whether the cell is the first in the section
147 |
148 | #### isLast
149 | `boolean`
150 | Whether the cell is the last in the section
151 |
152 | #### item
153 | `mixed`
154 | The item to render
155 |
156 | #### offsetY
157 | `number`
158 | The current y offset of the list view
159 | **If you do not specify `updateScrollState={true}` for the list component, this props will always be 0**
160 |
161 | #### onSelect
162 | `function`
163 | The function which should be called when a cell is being selected
164 |
165 | ---
166 | ### Section list item component
167 |
168 | These props are automatically passed to your component
169 |
170 | #### sectionId
171 | `string`
172 | The id of the parent section
173 |
174 | #### title
175 | `string`
176 | The title for this section. Either the return value of `getSectionListTitle` or the same value as `sectionId`
177 |
178 | ## Example
179 |
180 | ```javascript
181 | class SectionHeader extends Component {
182 | render() {
183 | // inline styles used for brevity, use a stylesheet when possible
184 | var textStyle = {
185 | textAlign:'center',
186 | color:'#fff',
187 | fontWeight:'700',
188 | fontSize:16
189 | };
190 |
191 | var viewStyle = {
192 | backgroundColor: '#ccc'
193 | };
194 | return (
195 |
196 | {this.props.title}
197 |
198 | );
199 | }
200 | }
201 |
202 | class SectionItem extends Component {
203 | render() {
204 | return (
205 | {this.props.title}
206 | );
207 | }
208 | }
209 |
210 | class Cell extends Component {
211 | render() {
212 | return (
213 |
214 | {this.props.item}
215 |
216 | );
217 | }
218 | }
219 |
220 | class MyComponent extends Component {
221 |
222 | constructor(props, context) {
223 | super(props, context);
224 |
225 | this.state = {
226 | data: {
227 | A: ['some','entries','are here'],
228 | B: ['some','entries','are here'],
229 | C: ['some','entries','are here'],
230 | D: ['some','entries','are here'],
231 | E: ['some','entries','are here'],
232 | F: ['some','entries','are here'],
233 | G: ['some','entries','are here'],
234 | H: ['some','entries','are here'],
235 | I: ['some','entries','are here'],
236 | J: ['some','entries','are here'],
237 | K: ['some','entries','are here'],
238 | L: ['some','entries','are here'],
239 | M: ['some','entries','are here'],
240 | N: ['some','entries','are here'],
241 | O: ['some','entries','are here'],
242 | P: ['some','entries','are here'],
243 | Q: ['some','entries','are here'],
244 | R: ['some','entries','are here'],
245 | S: ['some','entries','are here'],
246 | T: ['some','entries','are here'],
247 | U: ['some','entries','are here'],
248 | V: ['some','entries','are here'],
249 | X: ['some','entries','are here'],
250 | Y: ['some','entries','are here'],
251 | Z: ['some','entries','are here'],
252 | }
253 | };
254 | }
255 |
256 | render() {
257 | return (
258 |
266 | );
267 | }
268 | }
269 |
270 | ```
271 |
--------------------------------------------------------------------------------
/components/CellWrapper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react-native');
4 | var {Component, PropTypes, View} = React;
5 |
6 | class CellWrapper extends Component {
7 |
8 | componentDidMount() {
9 | this.props.updateTag && this.props.updateTag(this.refs.view.getNodeHandle(), this.props.sectionId);
10 | }
11 |
12 | render() {
13 | var Cell = this.props.component;
14 | return (
15 |
16 | |
17 |
18 | );
19 | }
20 | }
21 |
22 | CellWrapper.propTypes = {
23 |
24 | /**
25 | * The id of the section
26 | */
27 | sectionId: PropTypes.oneOfType([
28 | PropTypes.number,
29 | PropTypes.string
30 | ]),
31 |
32 | /**
33 | * A component to render for each cell
34 | */
35 | component: PropTypes.func.isRequired,
36 |
37 | /**
38 | * A function used to propagate the root nodes handle back to the parent
39 | */
40 | updateTag: PropTypes.func
41 |
42 | };
43 |
44 |
45 | module.exports = CellWrapper;
46 |
--------------------------------------------------------------------------------
/components/SectionHeader.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react-native');
4 | var {Component, PropTypes, StyleSheet, View, Text} = React;
5 | var UIManager = require('NativeModules').UIManager;
6 | class SectionHeader extends Component {
7 |
8 | componentDidMount() {
9 | this.props.updateTag && this.props.updateTag(this.refs.view.getNodeHandle(), this.props.sectionId);
10 | }
11 |
12 | render() {
13 | var SectionComponent = this.props.component;
14 | var content = SectionComponent ?
15 | :
16 | {this.props.title};
17 |
18 | return (
19 |
20 | {content}
21 |
22 | );
23 | }
24 | }
25 |
26 | var styles = StyleSheet.create({
27 | container: {
28 | backgroundColor:'#f8f8f8',
29 | borderTopWidth: 1,
30 | borderTopColor: '#ececec'
31 | },
32 | text: {
33 | fontWeight: '700',
34 | paddingTop:2,
35 | paddingBottom:2,
36 | paddingLeft: 2
37 | }
38 | });
39 |
40 | SectionHeader.propTypes = {
41 |
42 | /**
43 | * The id of the section
44 | */
45 | sectionId: PropTypes.oneOfType([
46 | PropTypes.number,
47 | PropTypes.string
48 | ]),
49 |
50 | /**
51 | * A component to render for each section item
52 | */
53 | component: PropTypes.func,
54 |
55 | /**
56 | * A function used to propagate the root nodes handle back to the parent
57 | */
58 | updateTag: PropTypes.func
59 |
60 | };
61 |
62 | module.exports = SectionHeader;
63 |
--------------------------------------------------------------------------------
/components/SectionList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react-native');
4 | var {Component, PropTypes, StyleSheet, View, Text} = React;
5 | var UIManager = require('NativeModules').UIManager;
6 |
7 | var noop = () => {};
8 | var returnTrue = () => true;
9 |
10 | class SectionList extends Component {
11 |
12 | constructor(props, context) {
13 | super(props, context);
14 |
15 | this.onSectionSelect = this.onSectionSelect.bind(this);
16 | this.resetSection = this.resetSection.bind(this);
17 | this.detectAndScrollToSection = this.detectAndScrollToSection.bind(this);
18 | this.lastSelectedIndex = null;
19 | }
20 |
21 | onSectionSelect(sectionId, fromTouch) {
22 | this.props.onSectionSelect && this.props.onSectionSelect(sectionId);
23 |
24 | if (!fromTouch) {
25 | this.lastSelectedIndex = null;
26 | }
27 | }
28 |
29 | resetSection() {
30 | this.lastSelectedIndex = null;
31 | }
32 |
33 | detectAndScrollToSection(e) {
34 | var ev = e.nativeEvent;
35 | var rect = {width:1, height:1, x: ev.locationX, y: ev.locationY};
36 |
37 | UIManager.measureViewsInRect(rect, e.target, noop, (frames) => {
38 | if (frames.length) {
39 | var index = frames[0].index;
40 | if (this.lastSelectedIndex !== index) {
41 | this.lastSelectedIndex = index;
42 | this.onSectionSelect(this.props.sections[index], true);
43 | }
44 | }
45 | });
46 | }
47 |
48 | render() {
49 | var SectionComponent = this.props.component;
50 | var sections = this.props.sections.map((section, index) => {
51 | var title = this.props.getSectionListTitle ?
52 | this.props.getSectionListTitle(section) :
53 | section;
54 |
55 | var child = SectionComponent ?
56 | :
60 |
62 | {title}
63 | ;
64 |
65 | return (
66 |
67 | {child}
68 |
69 | );
70 | });
71 |
72 | return (
73 |
80 | {sections}
81 |
82 | );
83 | }
84 | }
85 |
86 | SectionList.propTypes = {
87 |
88 | /**
89 | * A component to render for each section item
90 | */
91 | component: PropTypes.func,
92 |
93 | /**
94 | * Function to provide a title the section list items.
95 | */
96 | getSectionListTitle: PropTypes.func,
97 |
98 | /**
99 | * Function to be called upon selecting a section list item
100 | */
101 | onSectionSelect: PropTypes.func,
102 |
103 | /**
104 | * The sections to render
105 | */
106 | sections: PropTypes.array.isRequired,
107 |
108 | /**
109 | * A style to apply to the section list container
110 | */
111 | style: PropTypes.oneOfType([
112 | PropTypes.number,
113 | PropTypes.object,
114 | ])
115 | };
116 |
117 | var styles = StyleSheet.create({
118 | container: {
119 | position: 'absolute',
120 | backgroundColor: 'transparent',
121 | alignItems:'center',
122 | justifyContent:'center',
123 | right: 0,
124 | top: 0,
125 | bottom: 0,
126 | width: 15
127 | },
128 |
129 | item: {
130 | padding: 0
131 | },
132 |
133 | text: {
134 | fontWeight: '700',
135 | color: '#008fff'
136 | }
137 | });
138 |
139 | module.exports = SectionList;
140 |
--------------------------------------------------------------------------------
/components/SelectableSectionsListView.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /* jshint esnext: true */
3 |
4 | var React = require('react-native');
5 | var {Component, ListView, StyleSheet, View, PropTypes} = React;
6 | var UIManager = require('NativeModules').UIManager;
7 | var merge = require('merge');
8 |
9 | var SectionHeader = require('./SectionHeader');
10 | var SectionList = require('./SectionList');
11 | var CellWrapper = require('./CellWrapper');
12 |
13 | class SelectableSectionsListView extends Component {
14 |
15 | constructor(props, context) {
16 | super(props, context);
17 |
18 | this.state = {
19 | dataSource: new ListView.DataSource({
20 | rowHasChanged: (row1, row2) => row1 !== row2,
21 | sectionHeaderHasChanged: (prev, next) => prev !== next
22 | }),
23 | offsetY: 0
24 | };
25 |
26 | this.renderFooter = this.renderFooter.bind(this);
27 | this.renderHeader = this.renderHeader.bind(this);
28 | this.renderRow = this.renderRow.bind(this);
29 | this.renderSectionHeader = this.renderSectionHeader.bind(this);
30 |
31 | this.onScroll = this.onScroll.bind(this);
32 | this.onScrollAnimationEnd = this.onScrollAnimationEnd.bind(this);
33 | this.scrollToSection = this.scrollToSection.bind(this);
34 |
35 | // used for dynamic scrolling
36 | // always the first cell of a section keyed by section id
37 | this.cellTagMap = {};
38 | this.sectionTagMap = {};
39 | this.updateTagInCellMap = this.updateTagInCellMap.bind(this);
40 | this.updateTagInSectionMap = this.updateTagInSectionMap.bind(this);
41 | }
42 |
43 | componentWillMount() {
44 | this.calculateTotalHeight();
45 | }
46 |
47 | componentDidMount() {
48 | // push measuring into the next tick
49 | setTimeout(() => {
50 | UIManager.measure(this.refs.view.getNodeHandle(), (x,y,w,h) => {
51 | this.containerHeight = h;
52 | });
53 | }, 0);
54 | }
55 |
56 | componentWillReceiveProps(nextProps) {
57 | if (nextProps.data && nextProps.data !== this.props.data) {
58 | this.calculateTotalHeight(nextProps.data);
59 | }
60 | }
61 |
62 | calculateTotalHeight(data) {
63 | data = data || this.props.data;
64 |
65 | if (Array.isArray(data)) {
66 | return;
67 | }
68 |
69 | this.sectionItemCount = {};
70 | this.totalHeight = Object.keys(data)
71 | .reduce((carry, key) => {
72 | var itemCount = data[key].length;
73 | carry += itemCount * this.props.cellHeight;
74 | carry += this.props.sectionHeaderHeight;
75 |
76 | this.sectionItemCount[key] = itemCount;
77 |
78 | return carry;
79 | }, 0);
80 | }
81 |
82 | updateTagInSectionMap(tag, section) {
83 | this.sectionTagMap[section] = tag;
84 | }
85 |
86 | updateTagInCellMap(tag, section) {
87 | this.cellTagMap[section] = tag;
88 | }
89 |
90 | scrollToSection(section) {
91 | var y = 0;
92 | var headerHeight = this.props.headerHeight || 0;
93 | y += headerHeight;
94 |
95 | if (!this.props.useDynamicHeights) {
96 | var cellHeight = this.props.cellHeight;
97 | var sectionHeaderHeight = this.props.sectionHeaderHeight;
98 | var keys = Object.keys(this.props.data);
99 | var index = keys.indexOf(section);
100 |
101 | var numcells = 0;
102 | for (var i = 0; i < index; i++) {
103 | numcells += this.props.data[keys[i]].length;
104 | }
105 |
106 | sectionHeaderHeight = index * sectionHeaderHeight;
107 | y += numcells * cellHeight + sectionHeaderHeight;
108 | var maxY = this.totalHeight - this.containerHeight + headerHeight;
109 | y = y > maxY ? maxY : y;
110 |
111 | this.refs.listview.refs.listviewscroll.scrollTo(y, 0);
112 | } else {
113 | // this breaks, if not all of the listview is pre-rendered!
114 | UIManager.measure(this.cellTagMap[section], (x, y, w, h) => {
115 | y = y - this.props.sectionHeaderHeight;
116 | this.refs.listview.refs.listviewscroll.scrollTo(y, 0);
117 | });
118 | }
119 |
120 | this.props.onScrollToSection && this.props.onScrollToSection(section);
121 | }
122 |
123 | renderSectionHeader(sectionData, sectionId) {
124 | var updateTag = this.props.useDynamicHeights ?
125 | this.updateTagInSectionMap :
126 | null;
127 |
128 | var title = this.props.getSectionTitle ?
129 | this.props.getSectionTitle(sectionId) :
130 | sectionId;
131 |
132 | return (
133 |
140 | );
141 | }
142 |
143 | renderFooter() {
144 | var Footer = this.props.footer;
145 | return ;
146 | }
147 |
148 | renderHeader() {
149 | var Header = this.props.header;
150 | return ;
151 | }
152 |
153 | renderRow(item, sectionId, index) {
154 | var CellComponent = this.props.cell;
155 | index = parseInt(index, 10);
156 |
157 | var isFirst = index === 0;
158 | var isLast = this.sectionItemCount[sectionId]-1 === index;
159 |
160 | var props = {
161 | isFirst,
162 | isLast,
163 | sectionId,
164 | index,
165 | item,
166 | offsetY: this.state.offsetY,
167 | onSelect: this.props.onCellSelect
168 | };
169 |
170 | return index === 0 && this.props.useDynamicHeights ?
171 | :
174 | ;
175 | }
176 |
177 | onScroll(e) {
178 | var offsetY = e.nativeEvent.contentOffset.y;
179 | if (this.props.updateScrollState) {
180 | this.setState({
181 | offsetY
182 | });
183 | }
184 |
185 | this.props.onScroll && this.props.onScroll(e);
186 | }
187 |
188 | onScrollAnimationEnd(e) {
189 | if (this.props.updateScrollState) {
190 | this.setState({
191 | offsetY: e.nativeEvent.contentOffset.y
192 | });
193 | }
194 | }
195 |
196 | render() {
197 | var data = this.props.data;
198 | var dataIsArray = Array.isArray(data);
199 | var sectionList;
200 | var renderSectionHeader;
201 | var dataSource;
202 |
203 | if (dataIsArray) {
204 | dataSource = this.state.dataSource.cloneWithRows(data);
205 | } else {
206 | sectionList = !this.props.hideSectionList ?
207 | :
214 | null;
215 |
216 | renderSectionHeader = this.renderSectionHeader;
217 | dataSource = this.state.dataSource.cloneWithRowsAndSections(data);
218 | }
219 |
220 | var renderFooter = this.props.footer ?
221 | this.renderFooter :
222 | this.props.renderFooter;
223 |
224 | var renderHeader = this.props.header ?
225 | this.renderHeader :
226 | this.props.renderHeader;
227 |
228 | var props = merge(this.props, {
229 | onScroll: this.onScroll,
230 | onScrollAnimationEnd: this.onScrollAnimationEnd,
231 | dataSource,
232 | renderFooter,
233 | renderHeader,
234 | renderRow: this.renderRow,
235 | renderSectionHeader
236 | });
237 |
238 | props.style = void 0;
239 |
240 | return (
241 |
242 |
246 | {sectionList}
247 |
248 | );
249 | }
250 | }
251 |
252 | var styles = StyleSheet.create({
253 | container: {
254 | flex: 1
255 | }
256 | });
257 |
258 | var stylesheetProp = PropTypes.oneOfType([
259 | PropTypes.number,
260 | PropTypes.object,
261 | ]);
262 |
263 | SelectableSectionsListView.propTypes = {
264 | /**
265 | * The data to render in the listview
266 | */
267 | data: PropTypes.oneOfType([
268 | PropTypes.array,
269 | PropTypes.object,
270 | ]).isRequired,
271 |
272 | /**
273 | * Whether to show the section listing or not
274 | */
275 | hideSectionList: PropTypes.bool,
276 |
277 | /**
278 | * Functions to provide a title for the section header and the section list
279 | * items. If not provided, the section ids will be used (the keys from the data object)
280 | */
281 | getSectionTitle: PropTypes.func,
282 | getSectionListTitle: PropTypes.func,
283 |
284 | /**
285 | * Callback which should be called when a cell has been selected
286 | */
287 | onCellSelect: PropTypes.func,
288 |
289 | /**
290 | * Callback which should be called when the user scrolls to a section
291 | */
292 | onScrollToSection: PropTypes.func,
293 |
294 | /**
295 | * The cell element to render for each row
296 | */
297 | cell: PropTypes.func.isRequired,
298 |
299 | /**
300 | * A custom element to render for each section list item
301 | */
302 | sectionListItem: PropTypes.func,
303 |
304 | /**
305 | * A custom element to render for each section header
306 | */
307 | sectionHeader: PropTypes.func,
308 |
309 | /**
310 | * A custom element to render as footer
311 | */
312 | footer: PropTypes.func,
313 |
314 | /**
315 | * A custom element to render as header
316 | */
317 | header: PropTypes.func,
318 |
319 | /**
320 | * The height of the header element to render. Is required if a
321 | * header element is used, so the positions can be calculated correctly
322 | */
323 | headerHeight: PropTypes.number,
324 |
325 | /**
326 | * A custom function to render as footer
327 | */
328 | renderHeader: PropTypes.func,
329 |
330 | /**
331 | * A custom function to render as header
332 | */
333 | renderFooter: PropTypes.func,
334 |
335 | /**
336 | * An object containing additional props, which will be passed
337 | * to each cell component
338 | */
339 | cellProps: PropTypes.object,
340 |
341 | /**
342 | * The height of the section header component
343 | */
344 | sectionHeaderHeight: PropTypes.number.isRequired,
345 |
346 | /**
347 | * The height of the cell component
348 | */
349 | cellHeight: PropTypes.number.isRequired,
350 |
351 | /**
352 | * Whether to determine the y postion to scroll to by calculating header and
353 | * cell heights or by using the UIManager to measure the position of the
354 | * destination element. This is an exterimental feature
355 | */
356 | useDynamicHeights: PropTypes.bool,
357 |
358 | /**
359 | * Whether to set the current y offset as state and pass it to each
360 | * cell during re-rendering
361 | */
362 | updateScrollState: PropTypes.bool,
363 |
364 | /**
365 | * Styles to pass to the container
366 | */
367 | style: stylesheetProp,
368 |
369 | /**
370 | * Styles to pass to the section list container
371 | */
372 | sectionListStyle: stylesheetProp
373 |
374 | };
375 |
376 | module.exports = SelectableSectionsListView;
377 |
--------------------------------------------------------------------------------
/compositor.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "johanneslumpe/react-native-selectablesectionlistview",
3 | "version": "0.1.4",
4 | "libraries": {
5 | "xv": "^1.1.25"
6 | },
7 | "title": "",
8 | "branch": "",
9 | "style": {
10 | "name": "Material",
11 | "componentSet": {
12 | "nav": "nav/DarkAbsoluteNav",
13 | "header": "header/GradientHeader",
14 | "article": "article/BasicArticle",
15 | "footer": "footer/BasicFooter"
16 | },
17 | "fontFamily": "Roboto, sans-serif",
18 | "heading": {
19 | "fontWeight": 500,
20 | "letterSpacing": "-0.01em"
21 | },
22 | "colors": {
23 | "text": "#212121",
24 | "background": "#fff",
25 | "primary": "#2196f3",
26 | "secondary": "#1565c0",
27 | "highlight": "#ff4081",
28 | "border": "#e0e0e0",
29 | "muted": "#f5f5f5"
30 | },
31 | "layout": {
32 | "centered": true,
33 | "bannerHeight": "80vh",
34 | "maxWidth": 896
35 | }
36 | },
37 | "content": [
38 | {
39 | "component": "nav",
40 | "links": [
41 | {
42 | "href": "https://github.com/johanneslumpe/react-native-selectablesectionlistview",
43 | "text": "GitHub"
44 | },
45 | {
46 | "href": "https://npmjs.com/package/react-native-selectablesectionlistview",
47 | "text": "npm"
48 | }
49 | ]
50 | },
51 | {
52 | "component": "header",
53 | "heading": "react-native-selectablesectionlistview",
54 | "subhead": "A Listview with a sidebar to jump to sections directly",
55 | "children": [
56 | {
57 | "component": "ui/TweetButton",
58 | "text": "react-native-selectablesectionlistview: A Listview with a sidebar to jump to sections directly",
59 | "url": null
60 | },
61 | {
62 | "component": "ui/GithubButton",
63 | "user": "johanneslumpe",
64 | "repo": "react-native-selectablesectionlistview"
65 | }
66 | ],
67 | "text": "v0.4.0"
68 | },
69 | {
70 | "component": "article",
71 | "metadata": {
72 | "source": "github.readme"
73 | },
74 | "html": "\n
A Listview with a sidebar to directly jump to sections.
\nPlease file issues for missing features or bugs.
\nI apologize for the bad name.
\n
\nUsage
\nThe most basic way to use this component is as follows:
\nvar SelectableSectionsListView = require('react-native-selectablesectionlistview');\n\n\n<SelectableSectionsListView\n data={yourData}\n cell={YourCellComponent}\n cellHeight={100}\n sectionHeaderHeight={22.5}\n/>
You can find a more complete example below
\nProps
\nSelectableSectionsListView
\nAll props are passed through to the underlying ListView
, so you can specify all the available props for ListView
normally - except the following, which are defined internally and will be overwritten:
\n\nonScroll
\nonScrollAnimationEnd
\ndataSource
\nrenderRow
\nrenderSectionHeader
\n
\ndata
\narray|object
, required
The data to render in the listview
\nhideSectionList
\nboolean
Whether to show the section listing or not. Note: If the data your are providing to\nthe component is an array, the section list will automatically be hidden.
\ngetSectionTitle
\nfunction
Function to provide titles for the section headers
\ngetSectionListTitle
\nfunction
Function to provide titles for the section list items
\nonCellSelect
\nfunction
Callback which should be called when a cell has been selected
\nonScrollToSection
\nfunction
Callback which should be called when the user scrolls to a section
\ncell
\nfunction
required
The cell component to render for each row
\nsectionListItem
\nfunction
A custom component to render for each section list item
\nsectionHeader
\nfunction
A custom component to render for each section header
\nfooter
\nfunction
A custom component to render as footer
This props takes precedence over renderFooter
\nrenderFooter
\nfunction
A custom function which has to return a valid React element, which will be\nused as footer.
\nheader
\nfunction
A custom component to render as header
This props takes precedence over renderHeader
\nrenderHeader
\nfunction
A custom function which has to return a valid React element, which will be used as header.
\nheaderHeight
\nnumber
The height of the rendered header element.
Is required if a header element is used, so the positions can be calculated correctly
\ncellProps
\nobject
An object containing additional props, which will be passed to each cell component
\nsectionHeaderHeight
\nnumber
required
The height of the section header component
\ncellHeight
\nnumber
required
The height of the cell component
\nuseDynamicHeights
\nboolean
Whether to determine the y position to scroll to by calculating header and cell heights or by using the UIManager to measure the position of the destination element. Defaults to false
This is an experimental feature. For it to work properly you will most likely have to experiment with different values for scrollRenderAheadDistance
, depending on the size of your data set.
\nupdateScrollState
\nboolean
Whether to set the current y offset as state and pass it to each cell during re-rendering
\nstyle
\nobject|number
Styles to pass to the container
\nsectionListStyle
\nobject|number
Styles to pass to the section list container
\n
\nCell component
\nThese props are automatically passed to your component. In addition to these, your cell will receive all props which you specified in the object you passed as cellProps
prop to the listview.
\nindex
\nnumber
The index of the cell inside the current section
\nsectionId
\nstring
The id of the parent section
\nisFirst
\nboolean
Whether the cell is the first in the section
\nisLast
\nboolean
Whether the cell is the last in the section
\nitem
\nmixed
The item to render
\noffsetY
\nnumber
The current y offset of the list view
If you do not specify updateScrollState={true}
for the list component, this props will always be 0
\nonSelect
\nfunction
The function which should be called when a cell is being selected
\n
\nSection list item component
\nThese props are automatically passed to your component
\nsectionId
\nstring
The id of the parent section
\ntitle
\nstring
The title for this section. Either the return value of getSectionListTitle
or the same value as sectionId
\nExample
\nclass SectionHeader extends Component {\n render() {\n \n var textStyle = {\n textAlign:'center',\n color:'#fff',\n fontWeight:'700',\n fontSize:16\n };\n\n var viewStyle = {\n backgroundColor: '#ccc'\n };\n return (\n <View style={viewStyle}>\n <Text style={textStyle}>{this.props.title}</Text>\n </View>\n );\n }\n}\n\nclass SectionItem extends Component {\n render() {\n return (\n <Text style={{color:'#f00'}}>{this.props.title}</Text>\n );\n }\n}\n\nclass Cell extends Component {\n render() {\n return (\n <View style={{height:30}}>\n <Text>{this.props.item}</Text>\n </View>\n );\n }\n}\n\nclass MyComponent extends Component {\n\n constructor(props, context) {\n super(props, context);\n\n this.state = {\n data: {\n A: ['some','entries','are here'],\n B: ['some','entries','are here'],\n C: ['some','entries','are here'],\n D: ['some','entries','are here'],\n E: ['some','entries','are here'],\n F: ['some','entries','are here'],\n G: ['some','entries','are here'],\n H: ['some','entries','are here'],\n I: ['some','entries','are here'],\n J: ['some','entries','are here'],\n K: ['some','entries','are here'],\n L: ['some','entries','are here'],\n M: ['some','entries','are here'],\n N: ['some','entries','are here'],\n O: ['some','entries','are here'],\n P: ['some','entries','are here'],\n Q: ['some','entries','are here'],\n R: ['some','entries','are here'],\n S: ['some','entries','are here'],\n T: ['some','entries','are here'],\n U: ['some','entries','are here'],\n V: ['some','entries','are here'],\n X: ['some','entries','are here'],\n Y: ['some','entries','are here'],\n Z: ['some','entries','are here'],\n }\n };\n }\n\n render() {\n return (\n <SelectableSectionsListView\n data={this.state.data}\n cell={Cell}\n cellHeight={30}\n sectionListItem={SectionItem}\n sectionHeader={SectionHeader}\n sectionHeaderHeight={22.5}\n />\n );\n }\n}
"
75 | },
76 | {
77 | "component": "footer",
78 | "links": [
79 | {
80 | "href": "https://github.com/johanneslumpe/react-native-selectablesectionlistview",
81 | "text": "GitHub"
82 | },
83 | {
84 | "href": "https://github.com/johanneslumpe",
85 | "text": "johanneslumpe"
86 | }
87 | ]
88 | }
89 | ]
90 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var SelectableSectionsListView = require('./components/SelectableSectionsListView');
4 |
5 | module.exports = SelectableSectionsListView;
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-selectablesectionlistview",
3 | "version": "0.4.0",
4 | "description": "A Listview with a sidebar to jump to sections directly",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git@github.com:johanneslumpe/react-native-selectablesectionlistview.git"
12 | },
13 | "author": "Johannes Lumpe (https://github.com/johanneslumpe)",
14 | "license": "MIT",
15 | "keywords": [
16 | "react-component",
17 | "react-native",
18 | "ios"
19 | ],
20 | "devDependencies": {
21 | "react-native": ">=0.3.4 <0.5.0"
22 | },
23 | "peerDependencies": {
24 | "react-native": ">=0.3.4 <0.5.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------