Skip to content

Commit

Permalink
Merge pull request #156 from Sharlaan/dev
Browse files Browse the repository at this point in the history
Release 1.9.0 (selectAll/Reset)
  • Loading branch information
Sharlaan committed Mar 23, 2018
2 parents 310b78f + 45221ac commit 36f5b6b
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 46 deletions.
65 changes: 49 additions & 16 deletions README.md
Expand Up @@ -15,15 +15,17 @@
- [Properties](#properties)
- [Usage examples](#usage)
- [Building](#building)
- [Linking in another local project](#linking-in-another-local-project)
- [Tests](#tests)
- [Contributing](#contributing)
- [Known bugs](#known-bugs)
- [TodoList](#todolist)

## Preview ([Live demo](https://sharlaan.github.io/material-ui-superselectfield))

![dataSource](https://github.com/Sharlaan/material-ui-superselectfield/blob/master/src/assets/dataSource.png)
![caseInsensitive](https://github.com/Sharlaan/material-ui-superselectfield/blob/master/src/assets/caseInsensitive.png)
![chips](https://github.com/Sharlaan/material-ui-superselectfield/blob/master/src/assets/chips.png)
![dataSource](https://github.com/Sharlaan/material-ui-superselectfield/blob/master/demo/src/assets/dataSource.png)
![caseInsensitive](https://github.com/Sharlaan/material-ui-superselectfield/blob/master/demo/src/assets/caseInsensitive.png)
![chips](https://github.com/Sharlaan/material-ui-superselectfield/blob/master/demo/src/assets/chips.png)

## Installation

Expand All @@ -33,14 +35,18 @@ This component requires 3 dependencies :
- react-dom
- material-ui

... so make sure they are installed in your project, or install them as well ;)
... so make sure they are installed in your project.

`yarn add material-ui-superselectfield`

### ES5 version

`npm i material-ui-superselectfield`
`import SelectField from 'material-ui-superselectfield'`

#### ES6+ version

`import SelectField from 'material-ui-superselectfield/es'`

## Properties

| Name | Type | Default | Description |
Expand Down Expand Up @@ -69,6 +75,7 @@ This component requires 3 dependencies :
| showAutocompleteThreshold | number, 'always', 'never' | 10 | Maximum number of options from which to display the autocomplete search field.<br> For example, if autoComplete textfield needs to be disabled, just set this prop with a value higher than children length.<br> However, if you need the autocomplete to show always, you may pass `'always'`. This will open the menu even if there are no items to display. Passing `'never'` will never show the autocomplete regadless of how many children are passed. |
| useLayerForClickAway | bool | false | If true, the popover dropdown will render on top of an invisible layer, which will prevent clicks to the underlying elements, and trigger an `onRequestClose('clickAway')` call. |
| value | null, object, object[] | null | Selected value(s).<br>/!\ REQUIRED: each object must expose a 'value' property. |
| withResetSelectAllButtons | bool | false | Paired with 'multiple', shows an header containing the 'RESET' and 'SELECT ALL' buttons. |

### Note when setting value

Expand Down Expand Up @@ -97,7 +104,10 @@ PropTypes should raise warnings if implementing otherwise.
| menuCloseButton | node | | A button for an explicit closing of the menu. Useful on mobiles. |
| noMatchFoundStyle | object | {} | Allows to change the style of the noMatchFound list item. |
| popoverClassName | string | '' | Sets the `className` property of the Popover component. |
| selectedMenuItemStyle | object | {color: muiTheme.menuItem.selectedTextColor} | Styles to be applied to the selected MenuItem. |
| popoverWidth | number | 180 | Sets the width of the Menu.<br>The menu is the container for 4 main sub-components: the autocomplete textfield, the header for reset/selectAll buttons, the options container, and the footer.<br>The menu width will always set its width to the highest value between popoverWidth prop(in px) or the root component width. The default value 180px were chosen so that the header's inner buttons don't overflow. |
| resetButton | node | see below | Node used to deselect all options.<br>/!\ Requires `withResetSelectAllButtons`. |
| selectAllButton | node | see below | Node used to select all options.<br>/!\ Requires `withResetSelectAllButtons`. |
| selectedMenuItemStyle | object | | Styles to be applied to the selected MenuItem. |
| selectionsRenderer | function | see below | Provide your own renderer for selected options. Defaults to concatenating children's values text. Check CodeExample4 for a more advanced renderer example. |
| style | object | {} | Insert your own inlined styles, applied to the root component. |
| unCheckedIcon | SVGicon | see below | The SvgIcon to use for the unchecked state. This is useful to create icon toggles. |
Expand All @@ -111,9 +121,11 @@ PropTypes should raise warnings if implementing otherwise.
|:---- |:---- |
| autocompleteFilter | ```(searchText, text) => !text || text.toLowerCase().includes(searchText.toLowerCase())``` |
| checkedIcon | `<CheckedIcon style={{ top: 'calc(50% - 12px)' }} />` |
| unCheckedIcon | `<UnCheckedIcon style={{ top: 'calc(50% - 12px)' }} />` |
| dropDownIcon | `<DropDownArrow/>` |
| selectionsRenderer | |
| resetButton | `<FlatButton label='reset' hoverColor='rgba(69, 90, 100, 0.1)' fullWidth />` |
| selectAllButton | `<FlatButton label='select all' hoverColor='rgba(69, 90, 100, 0.1)' fullWidth labelStyle={{ whiteSpace: 'nowrap' }} />` |
| unCheckedIcon | `<UnCheckedIcon style={{ top: 'calc(50% - 12px)' }} />` |
| selectionsRenderer |
<pre>(values, hintText) => {
if (!values) return hintText
const { value, label } = values
Expand All @@ -136,14 +148,30 @@ You can build the project with :

```sh
git clone https://github.com/Sharlaan/material-ui-superselectfield.git
npm i && npm start
yarn && yarn start
```

It should open a new page on your default browser @ localhost:3000

## Linking in another local project

To test changes on a local build of SSF :

```sh
yarn build && yarn link
```

... then navigate into your local project directory, and type :

```sh
yarn link material-ui-superselectfield
```

/!\ Warning : if you reinstall dependencies in your project, this will break the link, you will have to re-link SSF.

## Tests

`npm test`
`yarn test`

## Contributing

Expand All @@ -152,10 +180,11 @@ In lieu of a formal style guide, take care to maintain the existing coding style
## Known bugs

- keyboard-focus handling combined with optgroups and autocompleted results
- dynamic heights calculation

## TodoList

- [x] implement select all and reset, for multiple mode

- [x] implement onClose handler for multiple mode, to allow registering selected values in oneshot instead of exposing values at each selection (ie one single server request)

- [ ] set autoWidth to false automatically if width prop has a value
Expand All @@ -168,23 +197,26 @@ In lieu of a formal style guide, take care to maintain the existing coding style
- [x] add proptypes checking for value and children

- [x] support of \<optgroup />
- [ ] implement selectable \<optgroup /> to select all inner children

- [x] check rendering performance with 200 MenuItems (integrate react-infinite)

- [ ] implement the container for errors (absolutely positioned below the focusedLine)
- [x] implement the container for errors (absolutely positioned below the focusedLine)

Expose more props :
- [x] noMatchFound message
- [ ] floatingLabelText
- [x] floatingLabelText
- [x] canAutoPosition
- [x] checkPosition
- [x] anchorOrigin
- [ ] popoverStyle
- [x] popoverStyle
- [x] hoverColor
- [x] disabled
- [ ] required
- [ ] errorMessage
- [ ] errorStyle
- [x] errorMessage
- [x] errorStyle
- [ ] classeNames for sub-components
- [ ] maxSelection

- [x] add props.disableAutoComplete (default: false), or a nbItems2showAutocomplete (default: null, meaning never show the searchTextField)
- [x] make Autocomplete appears only if current numberOfMenuItems > props.autocompleteTreshold
Expand All @@ -193,3 +225,4 @@ In lieu of a formal style guide, take care to maintain the existing coding style
- [x] make a PR reimplementing MenuItem.insetChildren replaced with checkPosition={'left'(default) or 'right'}

- [ ] add an example with GooglePlaces
- [ ] add an example with ReduxForm
7 changes: 6 additions & 1 deletion demo/src/CodeExample1.js
Expand Up @@ -79,7 +79,12 @@ class CodeExample extends Component {
<div value='C'>Option C</div>
</SuperSelectField>

<SuperSelectField disabled hintText='Disabled' style={{ minWidth: 150, margin: 10 }} />
<SuperSelectField
disabled
floatingLabel='Disabled'
hintText='Disabled'
style={{ minWidth: 150, margin: 10 }}
/>
</div>

<h3 style={{ marginTop: 200 }}>Edges cases</h3>
Expand Down
8 changes: 4 additions & 4 deletions demo/src/CodeExample2.js
Expand Up @@ -47,7 +47,7 @@ class CodeExample extends Component {
onChange={this.handleSelection}
value={state21}
style={{ minWidth: 150, marginRight: 40 }}
elementHeight={[36, 68, 36]}
elementHeight={[36, 52, 36]}
>
<div value='A'>Option A</div>
<div value='B'>Option B super longue</div>
Expand All @@ -63,8 +63,8 @@ class CodeExample extends Component {
onChange={this.handleSelection}
value={state22}
style={{ minWidth: 150, marginRight: 40 }}
elementHeight={[36, 68, 36]}
menuCloseButton={<FlatButton label='close' hoverColor={'lightSalmon'} />}
elementHeight={[36, 52, 36]}
menuCloseButton={<FlatButton label='close' hoverColor='lightSalmon' />}
>
<div value='D'>Option D</div>
<div value='E'>Option E super longue</div>
Expand All @@ -80,7 +80,7 @@ class CodeExample extends Component {
style={{ minWidth: 150 }}
elementHeight={[36, 52, 36]}
menuFooterStyle={{ width: '100%' }}
menuCloseButton={<FlatButton label='close' hoverColor={'lightSalmon'} style={{ width: '100%' }} />}
menuCloseButton={<FlatButton label='close' hoverColor='lightSalmon' style={{ width: '100%' }} />}
>
<div value='G'>Option G</div>
<div value='H'>Option H super longue</div>
Expand Down
3 changes: 2 additions & 1 deletion demo/src/CodeExample3.js
Expand Up @@ -167,6 +167,7 @@ class CodeExample extends Component {
<SuperSelectField
name='state32'
multiple
withResetSelectAllButtons
floatingLabel={CustomFloatingLabel}
floatingLabelStyle={{ color: pink200 }}
floatingLabelFocusStyle={{ color: pink500 }}
Expand All @@ -183,7 +184,7 @@ class CodeExample extends Component {
hoverColor='rgba(3, 169, 244, 0.15)'
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
style={{ width: 200, marginTop: 20 }}
menuCloseButton={<FlatButton label='close' hoverColor={'lightSalmon'} />}
menuCloseButton={<FlatButton label='close' hoverColor='lightSalmon' />}
dropDownIcon={<ArrowDown />}
>
{dataSourceNodes}
Expand Down
Binary file added demo/src/assets/selectAll.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "material-ui-superselectfield",
"version": "1.8.11",
"version": "1.9.0",
"description": "original Material-UI's SelectField component enhanced with autocompletion, multiselection, custom renderers, and infinite loading.",
"keywords": [
"react-component",
Expand Down
1 change: 0 additions & 1 deletion src/FloatingLabel.js
Expand Up @@ -2,7 +2,6 @@ import React, { Component } from 'react'
import { floatingLabelTypes } from './types'
import { floatingLabelDefaultProps } from './defaultProps'

// TODO: implement style lock when disabled = true
class FloatingLabel extends Component {
state = { flabelHeight: 0 }

Expand Down
7 changes: 4 additions & 3 deletions src/SelectionsPresenter.js
Expand Up @@ -45,9 +45,10 @@ const SelectionsPresenter = ({

// Condition for shrinking the floating Label
const isShrunk =
(Array.isArray(selectedValues) && (!!selectedValues.length || isFocused)) ||
(!Array.isArray(selectedValues) && (isValidObject(selectedValues) || (selectedValues === null && isFocused))) ||
isOpen
!disabled &&
((Array.isArray(selectedValues) && (!!selectedValues.length || isFocused)) ||
(!Array.isArray(selectedValues) && (isValidObject(selectedValues) || (selectedValues === null && isFocused))) ||
isOpen)

const baseHRstyle = {
borderBottom: '1px solid',
Expand Down
63 changes: 49 additions & 14 deletions src/SuperSelectField.js
Expand Up @@ -20,6 +20,7 @@ class SelectField extends Component {
this.state = {
isOpen: false,
isFocused: false,
initialValue: value || (multiple ? [] : null),
itemsLength,
isAutocompleteShown: this.showAutocomplete(showAutocompleteThreshold, itemsLength),
selectedItems: value || (multiple ? [] : null),
Expand Down Expand Up @@ -128,6 +129,21 @@ class SelectField extends Component {
}
}

/**
* Menu Header methods
*/
selectAll = () => {
const { children } = this.props
const fixedChildren = Array.isArray(children) ? children : [children]
const selectedItems = fixedChildren.reduce((nodes, child) => {
const { type, props: { value, label } } = child
return nodes.concat(type !== 'optgroup' ? { value, label } : this.selectAllInGroup(child))
}, [])
this.setState({ selectedItems }, () => this.getSelected())
}

reset = () => this.setState({ selectedItems: this.state.initialValue }, () => this.getSelected())

/**
* Menu methods
*/
Expand All @@ -149,6 +165,13 @@ class SelectField extends Component {

getSelected = () => this.props.onSelect && this.props.onSelect(this.state.selectedItems, this.props.name)

// group must be of type 'optgroup'
selectAllInGroup = (group) => {
const { children } = group.props
const fixedChildren = Array.isArray(children) ? children : [children]
return fixedChildren.map(({ props: { value, label } }) => ({ value, label }))
}

// TODO: add Shift+Tab
/**
* this.menuItems can contain uncontinuous React elements, because of filtering
Expand Down Expand Up @@ -226,13 +249,17 @@ class SelectField extends Component {
noMatchFound,
noMatchFoundStyle,
popoverClassName,
popoverWidth,
resetButton,
selectAllButton,
selectedMenuItemStyle,
selectionsRenderer,
style,
unCheckedIcon,
underlineErrorStyle,
underlineFocusStyle,
underlineStyle,
withResetSelectAllButtons,
} = this.props

// Default style depending on Material-UI context (muiTheme)
Expand Down Expand Up @@ -317,23 +344,21 @@ class SelectField extends Component {
return groupedItems.length ? [...nodes, menuGroup, ...groupedItems] : nodes
}, [])

/*
const menuItemsHeights = this.state.isOpen
? this.menuItems.map(item => findDOMNode(item).clientHeight) // can't resolve since items not rendered yet, need componentDiDMount
: elementHeight
*/
const autoCompleteHeight = this.state.isAutocompleteShown ? 53 : 0
const headerHeight = multiple && withResetSelectAllButtons ? 36 : 0
const footerHeight = multiple && menuCloseButton ? 36 : 0
const noMatchFoundHeight = 36
const optionsContainerHeight =
(Array.isArray(elementHeight)
? elementHeight.reduce((totalHeight, height) => totalHeight + height, 0)
: elementHeight * (nb2show < menuItems.length ? nb2show : menuItems.length)) || 0
const popoverHeight = autoCompleteHeight + (optionsContainerHeight || noMatchFoundHeight) + footerHeight + 6
const popoverHeight =
autoCompleteHeight + headerHeight + (optionsContainerHeight || noMatchFoundHeight) + footerHeight + 6

const scrollableStyle = { overflowY: nb2show >= menuItems.length ? 'hidden' : 'scroll' }

const menuWidth = this.root ? this.root.clientWidth : null
const baseWidth = this.root ? this.root.clientWidth : null
const menuWidth = Math.max(baseWidth, popoverWidth)

return (
<div
Expand Down Expand Up @@ -377,7 +402,7 @@ class SelectField extends Component {
className={popoverClassName}
onRequestClose={this.closeMenu}
open={this.state.isOpen}
style={{ height: popoverHeight }}
style={{ height: popoverHeight, width: menuWidth }}
useLayerForClickAway={false}
>
{this.state.isAutocompleteShown && (
Expand All @@ -388,17 +413,26 @@ class SelectField extends Component {
inputStyle={autocompleteStyle}
onChange={this.handleTextFieldAutocompletionFiltering}
onKeyDown={this.handleTextFieldKeyDown}
style={{ marginLeft: 16, marginBottom: 5, width: menuWidth - 16 * 2 }}
style={{ margin: '0 16px 5px', width: 'calc(100% - 32px)' }}
underlineFocusStyle={autocompleteUnderlineFocusStyle}
underlineStyle={autocompleteUnderlineStyle}
value={this.state.searchText}
/>
)}
<div
ref={(ref) => (this.menu = ref)}
onKeyDown={this.handleMenuKeyDown}
style={{ width: menuWidth, ...menuStyle }}
>

{multiple &&
withResetSelectAllButtons && (
<header style={{ display: 'flex', alignItems: 'center' }}>
<div onClick={this.selectAll} style={{ flex: '50%' }}>
{selectAllButton}
</div>
<div onClick={this.reset} style={{ flex: '50%' }}>
{resetButton}
</div>
</header>
)}

<div ref={(ref) => (this.menu = ref)} onKeyDown={this.handleMenuKeyDown} style={menuStyle}>
{menuItems.length ? (
<InfiniteScroller
containerHeight={optionsContainerHeight}
Expand All @@ -415,6 +449,7 @@ class SelectField extends Component {
/>
)}
</div>

{multiple && (
<footer style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }}>
<div onClick={this.closeMenu} style={menuFooterStyle}>
Expand Down

0 comments on commit 36f5b6b

Please sign in to comment.