Skip to content

Commit 276e7e0

Browse files
authored
Merge pull request #57 from easeq/master
Dropdown improvements
2 parents 1371e7f + 40f20e5 commit 276e7e0

File tree

8 files changed

+75
-66
lines changed

8 files changed

+75
-66
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ import { reducer, epics } from '@flipbyte/redux-datatable';
158158
btnClassName: 'your custom class names',
159159
menuClassName: 'your custom class names',
160160
menuItemClassName: 'your custom class names',
161+
activeClassName: 'your custom class names',
161162
styles: {
162163
button: { ... },
163164
dropdownMenu: { ... },
@@ -215,6 +216,7 @@ import { reducer, epics } from '@flipbyte/redux-datatable';
215216
btnClassName: 'your custom class names',
216217
menuClassName: 'your custom class names',
217218
menuItemClassName: 'your custom class names',
219+
activeClassName: 'your custom class names',
218220
styles: {
219221
button: { ... },
220222
dropdownMenu: { ... },
@@ -492,6 +494,7 @@ Toggles the table between editable and non-editable and shows a save button when
492494
| btnClassName | string | false | rdt-toolbar-button | html class names |
493495
| menuClassName | string | false | rdt-toolbar-menu | html class names |
494496
| menuItemClassName | string | false | rdt-toolbar-item | html class names |
497+
| activeClassName | string | false | show | html class names |
495498

496499
**_Actions object properties_**
497500

@@ -500,6 +503,7 @@ Toggles the table between editable and non-editable and shows a save button when
500503
| type | string | true | - | action |
501504
| name | string | true | - | Unique name |
502505
| label | string | true | - | Label for the action item |
506+
| role | string | false | - | Add role 'menu-item' to hide dropdown on click |
503507
| thunk | function | true | - | ( config ) => ( dispatch, getState ) => { ... } |
504508

505509
**_Styles object properties_**
@@ -548,6 +552,7 @@ Shows the columns toggling dropdown.
548552
| btnClassName | string | false | rdt-toolbar-button | html class names |
549553
| menuClassName | string | false | rdt-toolbar-menu | html class names |
550554
| menuItemClassName | string | false | rdt-toolbar-item | html class names |
555+
| activeClassName | string | false | show | html class names |
551556

552557
**_Styles object properties_**
553558

demo/src/schema/normalized.js

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export default {
148148
styles: {
149149
backgroundColor: 'red',
150150
},
151+
role: 'menu-item',
151152
thunk: ( config ) => ( dispatch, getState ) => {
152153
// Get current table state.
153154
const tableState = getState()[config.reducerName][config.name];
@@ -165,6 +166,7 @@ export default {
165166
type: 'action',
166167
name: 'edit',
167168
label: 'Edit this field',
169+
role: 'menu-item',
168170
}]
169171
},
170172
SimpleButton: {

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@flipbyte/redux-datatable",
3-
"version": "0.7.13",
3+
"version": "0.7.14",
44
"description": "React-Redux data table",
55
"main": "lib/index.js",
66
"module": "es/index.js",

src/components/Columns.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import _ from 'lodash';
21
import React, { useContext } from 'react';
32
import { useSelector } from 'react-redux';
43
import { withDropdown } from '../hoc';
54
import { Button, Dropdown } from '../styled-components';
65
import ConfigContext from '../context';
76
import { SET_VISIBLE_COLUMN_IDS } from '../actions';
87

9-
const Columns = ({
8+
const Columns = React.forwardRef(({
109
open,
1110
toggle,
1211
config: {
@@ -15,20 +14,21 @@ const Columns = ({
1514
btnClassName = 'rdt-toolbar-button',
1615
menuClassName = 'rdt-toolbar-menu',
1716
menuItemClassName = 'rdt-toolbar-item',
17+
activeClassName = 'show'
1818
}
19-
}) => {
20-
const {
21-
columns,
22-
action,
23-
getData
24-
} = useContext(ConfigContext);
19+
}, ref) => {
20+
const { columns, action, getData } = useContext(ConfigContext);
2521
const visibleColumnIds = useSelector(getData(({ visibleColumnIds }) => visibleColumnIds)) || [];
2622
return (
27-
<Dropdown.Container className={ className }>
23+
<Dropdown.Container ref={ ref } className={ className }>
2824
<Button className={ btnClassName } dropdownToggle onClick={ toggle } styles={ styles.button }>
2925
Columns
3026
</Button>
31-
<Dropdown.Menu className={ menuClassName } hidden={ !open } styles={ styles.dropdownMenu }>
27+
<Dropdown.Menu
28+
className={ `${menuClassName} ${open ? activeClassName : ''}` }
29+
hidden={ !open }
30+
styles={ styles.dropdownMenu }
31+
>
3232
{ columns.map(({ name, label, width }, index) => (
3333
<Dropdown.Item
3434
key={ index }
@@ -55,6 +55,6 @@ const Columns = ({
5555
</Dropdown.Menu>
5656
</Dropdown.Container>
5757
);
58-
};
58+
});
5959

6060
export default withDropdown(Columns);

src/components/MassActions.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { withDropdown } from '../hoc';
33
import { Button, Dropdown } from '../styled-components';
44
import ConfigContext from '../context';
55

6-
const MassActions = ({
6+
const MassActions = React.forwardRef(({
77
toggle,
88
open,
99
config: {
@@ -15,28 +15,34 @@ const MassActions = ({
1515
btnClassName = 'rdt-toolbar-button',
1616
menuClassName = 'rdt-toolbar-menu',
1717
menuItemClassName = 'rdt-toolbar-item',
18+
activeClassName = 'show'
1819
},
19-
}) => {
20+
}, ref) => {
2021
const { thunk } = useContext(ConfigContext);
2122
return (
22-
<Dropdown.Container className={ className }>
23+
<Dropdown.Container ref={ ref } className={ className }>
2324
<Button className={ btnClassName } dropdownToggle onClick={ toggle } styles={ styles.button }>
2425
{ label }
2526
</Button>
26-
<Dropdown.Menu className={ menuClassName } hidden={ !open } styles={ styles.dropdownMenu }>
27-
{ options.map(({ thunk: cb, ...option, styles: itemStyles }, index) =>
27+
<Dropdown.Menu
28+
className={ `${menuClassName} ${open ? activeClassName : ''}` }
29+
hidden={ !open }
30+
styles={ styles.dropdownMenu }
31+
>
32+
{ options.map(({ role = '', thunk: cb, ...option, styles: itemStyles }, index) =>
2833
<Dropdown.Item
2934
className={ menuItemClassName }
3035
key={ index }
3136
onClick={ cb && thunk.bind(this, cb, { option }) }
3237
styles={ itemStyles || styles.dropdownItem }
38+
role={ role }
3339
>
3440
{ option.label }
3541
</Dropdown.Item>
3642
)}
3743
</Dropdown.Menu>
3844
</Dropdown.Container>
3945
);
40-
};
46+
});
4147

4248
export default withDropdown(MassActions);

src/constants.js

+2
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export const SET_IS_PRINTING = 'SET_IS_PRINTING';
1212
export const TOGGLE_EDITABLE = 'TOGGLE_EDITABLE';
1313

1414
export const SELECT_ALL = 'all';
15+
16+
export const ROLE_MENU_ITEM = 'menu-item';

src/hoc/withDropdown.js

+41-47
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,54 @@
1-
import React, { Component } from 'react';
2-
import ReactDOM from 'react-dom';
3-
4-
const withDropdown = WrappedComponent => (
5-
class extends Component {
6-
constructor(props) {
7-
super(props);
8-
this.state = { open: false };
9-
this.toggle = this.toggle.bind(this);
10-
this.manageEvents = this.manageEvents.bind(this);
11-
this.handleDocumentClick = this.handleDocumentClick.bind(this);
12-
}
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import { ROLE_MENU_ITEM } from '../constants';
133

14-
componentWillUnmount() {
15-
this.manageEvents(true);
16-
}
4+
const events = ['click', 'touchstart', 'keyup'];
5+
const getMenuItems = (element) => [].slice.call(element.querySelectorAll(`[role="${ROLE_MENU_ITEM}"]`));
176

18-
componentDidMount() {
19-
this.manageEvents();
20-
}
7+
const withDropdown = WrappedComponent => (props) => {
8+
const ref = useRef(null);
9+
const [ open, toggle ] = useState(false);
2110

22-
toggle( e ) {
23-
const { open } = this.state;
24-
this.setState({ open: !open });
11+
const handleDocumentClick = (e) => {
12+
if (e && (e.which === 3 || (e.type === 'keyup' && e.which !== 9))) {
13+
return;
2514
}
2615

27-
manageEvents(remove = false) {
28-
var eventUpdater = remove ? document.removeEventListener : document.addEventListener;
16+
const container = ref.current;
17+
if (container.contains(e.target)) {
18+
if (e.target.getAttribute('role') === ROLE_MENU_ITEM) {
19+
toggle(open => open ? !open : open);
20+
} else {
21+
const menuItems = getMenuItems(container);
22+
menuItems.map((menuItem) => {
23+
if (menuItem.contains(e.target)) {
24+
toggle(open => open ? !open : open);
25+
}
26+
});
27+
}
28+
}
2929

30-
['click', 'touchstart', 'keyup'].forEach( event =>
31-
eventUpdater(event, this.handleDocumentClick, true)
32-
);
30+
if (container.contains(e.target) && container !== e.target
31+
&& (e.type !== 'keyup' || e.which === 9)
32+
) {
33+
return;
3334
}
3435

35-
handleDocumentClick(e) {
36-
if (e && (e.which === 3 || (e.type === 'keyup' && e.which !== 9))) {
37-
return;
38-
}
36+
toggle(open => open ? !open : open);
37+
};
3938

40-
const container = ReactDOM.findDOMNode(this);
41-
if (container.contains(e.target) && container !== e.target
42-
&& (e.type !== 'keyup' || e.which === 9)
43-
) {
44-
return;
45-
}
39+
const manageEvents = (remove = false) => {
40+
var eventUpdater = remove ? document.removeEventListener : document.addEventListener;
41+
events.forEach((event) => eventUpdater(event, handleDocumentClick, true));
42+
};
4643

47-
if (this.state.open) {
48-
this.toggle(e);
49-
}
50-
}
44+
useEffect(() => {
45+
manageEvents();
46+
return () => manageEvents(true);
47+
}, []);
5148

52-
render() {
53-
return (
54-
<WrappedComponent ref={ this.ref } toggle={ this.toggle } open={ this.state.open } { ...this.props } />
55-
);
56-
}
57-
}
58-
);
49+
return (
50+
<WrappedComponent ref={ ref } toggle={ toggle } open={ open } { ...props } />
51+
);
52+
};
5953

6054
export default withDropdown;

0 commit comments

Comments
 (0)