UNPKG

@eureca/eureca-ui

Version:

UI component library of Eureca's user and admin apps

345 lines (316 loc) 9.28 kB
import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import styled, { css } from 'styled-components'; import _ from 'lodash'; import { Box, Menu, MenuItem, Typography } from '@material-ui/core'; import { motion } from 'framer-motion'; import { FiChevronDown } from 'react-icons/fi'; import { colors } from '../../theme/colors'; import { Checkbox } from '../Checkbox'; import { Flex } from '../Flex'; const variants = { closed: { height: 0, overflow: 'hidden' }, open: { height: 'auto' }, transition: { duration: 500, ease: 'easeInOut' }, }; const SelectFlex = styled(Flex)` ${({ error }) => css` border: 1px solid rgba(0, 0, 0, 0.23); &:hover { border: 1px solid rgba(0, 0, 0, 0.87); } ${error && css` border: 1px solid ${colors.error1}; &:hover { border: 1px solid ${colors.error1}; } `} `} `; const ItemList = React.forwardRef(({ item, values, onClickItem, onClickAll, subItem }, ref) => { const [isOpen, setIsOpen] = useState(false); const indexValues = values.findIndex(element => element.id === item.id); // 'haveItemSet' checks if the item has subitems as options or is a normal checkbox. const haveItemSet = item[subItem]?.length > 0; // 'set' is the subitems array, if exists const set = haveItemSet && item[subItem]; const hasSome = haveItemSet && _.intersection([...set.flatMap(i => i.id)], values[indexValues]?.subItems).length; const allChecked = haveItemSet && hasSome === Object.keys(set).length; function openOrToggleCheckbox(isMulti, e) { e.stopPropagation(); if (isMulti) { setIsOpen(!isOpen); } else { onClickItem(item.id, ''); } } function checkAllOrOne(isMulti, e) { e.stopPropagation(); if (isMulti) { onClickAll( item.id, set.map(i => i.id) ); } else { onClickItem(item.id, ''); } } return ( <> <MenuItem onClick={e => openOrToggleCheckbox(haveItemSet, e)} data-testid={`menu-item-${item.id}`} > <Flex directionRow alignCenter justifySpaceBetween width={1}> <Flex directionRow alignCenter> <Checkbox name={item.name} value={item.id} checked={haveItemSet ? hasSome > 0 : values[indexValues]?.id === item.id} indeterminate={haveItemSet ? !allChecked && hasSome > 0 : false} data-testid={`select-checkbox-${item.id}`} onClick={e => checkAllOrOne(haveItemSet, e)} /> <Box> <Typography>{item.name}</Typography> </Box> </Flex> {haveItemSet && ( <Box ml={2} p={1}> <FiChevronDown size={16} style={{ transition: 'all 0.3s ease-in-out', transform: isOpen ? 'rotate(-180deg)' : 'rotate(0deg)', }} /> </Box> )} </Flex> </MenuItem> {haveItemSet && ( <motion.div variants={variants} initial="closed" animate={isOpen ? 'open' : 'closed'} aria-expanded={isOpen.toString()} > {isOpen && set.map(subItem => ( <MenuItem key={subItem.id} onClick={e => { e.preventDefault(); onClickItem(item.id, subItem); }} > <Box pl={3}> <Checkbox label={subItem.name} name={subItem.name} value={subItem.id} checked={values[indexValues]?.subItems.includes(subItem.id)} data-testid={`select-checkbox-${subItem.id}`} /> </Box> </MenuItem> ))} </motion.div> )} </> ); }); function SelectMulti({ items = [], data = [], onChange = () => {}, label = 'Select Multi', subItem = 'subItems', helperText, error, }) { const [values, setValues] = useState(data ? [...data] : []); const [content, setContent] = useState([]); const [anchorEl, setAnchorEl] = useState(null); const handleClick = event => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; function onClickItem(item, subItem) { const indexValues = values.findIndex(element => element.id === item); // Does the item already exists on the selected array? if (indexValues === -1) { // If it doesn't exist yet, add it to the array const updated = values; values.push({ id: item, subItems: [subItem.id] }); setValues([...updated]); return; } // If the item does exist, check if the subitem exists in it's array if (!values[indexValues].subItems.includes(subItem.id) && subItem !== ['']) { const updated = values; updated[indexValues] = { id: item, subItems: [...updated[indexValues].subItems, subItem.id], }; setValues([...updated]); return; } // If it does exists it means we must remove it from the array of subitems, it must be 'unchecked' const updated = values; updated[indexValues] = { id: item, subItems: updated[indexValues].subItems.filter(i => i !== subItem.id), }; if (values[indexValues].subItems.length > 0) { // If there's any other item on the array we're good setValues([...updated]); return; } else { // If it was the last one of it's kind we must remove the whole item from the values array const newArray = [...values]; newArray.splice(indexValues, 1); setValues(newArray); return; } } function handleClickAll(item, subItems) { const indexValues = values.findIndex(element => element.id === item); const areAllChecked = _.intersection(values[indexValues]?.subItems, subItems).length === subItems.length; // Does the item already exists on the selected array? if (indexValues === -1) { // If it doesn't exist yet, add it to the array const updated = values; values.push({ id: item, subItems: [...subItems] }); setValues([...updated]); return; } // Add all items if they are not all at the values array if (!areAllChecked) { const updated = values; updated[indexValues] = { id: item, subItems: [...subItems], }; setValues([...updated]); } else { // Remove item from the array if they were all already present const updated = [...values]; updated.splice(indexValues, 1); setValues(updated); } } useEffect(() => { let sum = 0; values.forEach(item => { sum = sum + item.subItems.length; }); if (sum === 0) { setContent(label); return; } if (sum === 1) { setContent(`(${sum}) selecionada`); return; } if (sum > 1 && sum < 10) { setContent(`(0${sum}) selecionadas`); return; } setContent(`(${sum}) selecionadas`); }, [values, subItem, label]); useEffect(() => { onChange(values); }, [onChange, values]); return ( <Box position="relative"> <SelectFlex px={1.5} height={56} borderRadius={4} cursorPointer directionRow alignCenter justifySpaceBetween onClick={handleClick} error={error} data-testid="select-multi-button" > <Typography style={{ color: error ? colors.error1 : colors.gray3 }}> {content.length > 0 ? content : label} </Typography> <Box width={0} height={0} borderLeft="5px solid transparent" borderRight="5px solid transparent" borderTop={`5px solid ${error ? colors.error1 : 'rgba(0, 0, 0, 0.54)'}`} /> </SelectFlex> <Box mt="3px" ml="14px"> <Typography variant="body2" style={{ color: error ? colors.error1 : colors.gray3, lineHeight: 1.66, }} > {helperText} </Typography> </Box> <Menu id="select-permissions" anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose} > {items.map(item => ( <ItemList key={item.id} item={item} values={values} onClickItem={onClickItem} onClickAll={handleClickAll} subItem={subItem} /> ))} </Menu> </Box> ); } SelectMulti.propTypes = { items: PropTypes.array.isRequired, data: PropTypes.array.isRequired, onChange: PropTypes.func.isRequired, label: PropTypes.string, subItem: PropTypes.string, helperText: PropTypes.string, error: PropTypes.bool, }; SelectMulti.defaultProps = { items: [ { id: 0, name: 'Item', subItem: [ { id: 10, name: 'SubItem', }, ], }, ], data: [], onChange: () => {}, label: '', subItem: 'subItems', helperText: '', error: false, }; export { SelectMulti };