@render-props/choices
Version:
A state container which provides an interface for making selections from a group of choices. The `Choices` component itself is a context provider which can be used with the `Choice` and `ChoicesConsumer` components for deep-tree selections. It does not ha
257 lines (232 loc) • 6.97 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/esm/objectSpread'
import React from 'react'
import PropTypes from 'prop-types'
import {callIfExists} from '@render-props/utils'
import Items from '@render-props/items'
import ChoicesContext from './ChoicesContext'
import {includesInvariant} from './invariants'
import {boundChoices, boundSelections} from './utils'
/**
import Choices, {Choice} from '@render-props/choices'
const FavoritePets = props => (
<Choices
initialChoices={new Set(['cat', 'dog', 'turtle'])}
initialSelections={new Set(['cat'])}
minChoices={1}
minSelections={1}
maxSelections={2}
onBoundMaxSelections={
function ({selections, select, deselect}) {
selections = Array.from(selections)
deselect(selections.shift())
select(selections.pop())
}
}
>
{PetsControl}
</Choices>
)
const PetsControl = ({
addChoice,
deleteChoice,
setChoices,
clearChoices,
isChoice,
select,
deselect,
setSelections,
clearSelections,
isSelected,
selections,
choices
}) => (
<div style={{borderWidth: 1}}>
<span>
Number of favorites: {selections.size}
</span>
{
Array.from(choices).map(pet => (
<Choice key={pet} value={pet}>
{
function ({select, deselect, toggle, isSelected}) {
return (
<button
onClick={toggle}
style={
isSelected
? {backgroundColor: 'green'}
: {backgroundColor: 'grey'}
}
>
{pet}
</button>
)
}
}
</Choice>
))
}
</div>
)
*/
export default function Choices(props) {
const nextProps = {
children: props.children,
}
function _ref(selectionsContext) {
nextProps.select = selectionsContext.addItem
nextProps.deselect = selectionsContext.deleteItem
nextProps.setSelections = selectionsContext.setItems
nextProps.isSelected = selectionsContext.includes
nextProps.clearSelections = selectionsContext.clearItems
nextProps.selections = selectionsContext.items
return React.createElement(Choices_, nextProps)
}
return React.createElement(
Items,
{
initialItems: props.initialChoices,
minItems: props.minChoices,
maxItems: props.maxChoices,
onAdd: props.onAddChoice,
onDelete: props.onDeleteChoice,
onChange: props.onChoicesChange,
onBoundMax: boundChoices(props.onBoundMinChoices),
onBoundMin: boundChoices(props.onBoundMaxChoices),
},
function(choicesContext) {
nextProps.addChoice = choicesContext.addItem
nextProps.deleteChoice = choicesContext.deleteItem
nextProps.clearChoices = choicesContext.clearItems
nextProps.setChoices = choicesContext.setItems
nextProps.isChoice = choicesContext.includes
nextProps.choices = choicesContext.items
return React.createElement(
Items,
{
initialItems: props.initialSelections,
minItems: props.minSelections,
maxItems: props.maxSelections,
onAdd: props.onSelect,
onDelete: props.onDeselect,
onChange: props.onChange,
onBoundMin: boundSelections(props.onBoundMinSelections),
onBoundMax: boundSelections(props.onBoundMaxSelections),
},
_ref
)
}
)
}
Choices.propTypes = {
children: PropTypes.func.isRequired,
// choices
initialChoices: PropTypes.oneOfType([
PropTypes.array,
PropTypes.instanceOf(Set),
]),
minChoices: PropTypes.number,
maxChoices: PropTypes.number,
onAddChoice: PropTypes.func,
onDeleteChoice: PropTypes.func,
onChoicesChange: PropTypes.func,
onBoundMinChoices: PropTypes.func,
onBoundMaxChoices: PropTypes.func,
// selections
initialSelections: PropTypes.oneOfType([
PropTypes.array,
PropTypes.instanceOf(Set),
]),
minSelections: PropTypes.number,
maxSelections: PropTypes.number,
onSelect: PropTypes.func,
onDeselect: PropTypes.func,
onChange: PropTypes.func,
onBoundMinSelections: PropTypes.func,
onBoundMaxSelections: PropTypes.func,
}
class Choices_ extends React.Component {
constructor(props) {
super(props)
this.select = (...items) => {
this.ifChoicesInclude(items)
return this.props.select(...items)
}
this.deselect = (...items) => {
this.ifChoicesInclude(items)
return this.props.deselect(...items)
}
this.toggle = item =>
this.props.isSelected(item) ? this.deselect(item) : this.select(item)
this.setSelections = items => {
this.ifChoicesInclude(items)
return this.props.setSelections(items)
}
this.deleteChoice = (...items) => {
const {isSelected, minSelections, deleteChoice} = this.props
const whichSize =
this.props.selections.size === void 0 ? 'length' : 'size' // removes all selections referencing this choice
for (let x = 0; x < items.length; x++) {
const item = items[x]
if (isSelected(item)) {
this.deselect(item)
if (minSelections > this.props.selections[whichSize] - 1) {
return
}
}
} // deletes the choice
return deleteChoice(...items)
}
this.setChoices = items => {
const diff = Array.from(items).filter(item => this.props.isChoice(item))
if (diff.length) {
this.props.deleteChoice(...diff)
}
return this.props.setChoices(items)
}
this.clearChoices = () => {
this.props.clearSelections()
this.props.clearChoices()
}
this.choicesContext = {
select: this.select,
deselect: this.deselect,
toggle: this.toggle,
setSelections: this.setSelections,
clearSelections: this.props.clearSelections,
isSelected: this.props.isSelected,
addChoice: this.props.addChoice,
deleteChoice: this.deleteChoice,
setChoices: this.setChoices,
clearChoices: this.clearChoices,
isChoice: this.props.isChoice,
selections: null,
choices: null,
}
}
ifChoicesInclude(items) {
if (typeof process !== void 0 && process.env.NODE_ENV !== 'production') {
for (let x = 0; x < items.length; x++) {
includesInvariant(this.props.choices, items[x])
}
}
}
render() {
if (
this.choicesContext.selections !== this.props.selections ||
this.choicesContext.choices !== this.props.choices
) {
this.choicesContext = _objectSpread({}, this.choicesContext)
}
this.choicesContext.selections = this.props.selections
this.choicesContext.choices = this.props.choices
return React.createElement(
ChoicesContext.Provider,
{
value: this.choicesContext,
},
this.props.children(this.choicesContext)
)
}
}
Choices_.displayName = 'Choices'