UNPKG

@eureca/eureca-ui

Version:

UI component library of Eureca's user and admin apps

244 lines (220 loc) 6.2 kB
import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { motion } from 'framer-motion'; import { useTheme, Grid, Typography, Box } from '@material-ui/core'; import { IconContext } from 'react-icons'; import { FiChevronDown } from 'react-icons/fi'; import { FaRegCheckCircle } from 'react-icons/fa'; import { Flex } from '../Flex'; import { colors } from '../../theme/colors'; const LeftIcon = ({ big, icon, color }) => { const theme = useTheme(); const iconStyle = big ? { height: theme.spacing(5), width: theme.spacing(5), borderRadius: theme.spacing(5), border: `2px solid ${color}`, } : null; return ( <Box mr={2} style={iconStyle}> <Flex alignCenter justifyCenter style={{ height: '100%' }}> <IconContext.Provider value={{ color: color, size: '1.25rem', attr: { 'data-testid': 'acc-icon' }, }} > {icon ? icon : <FaRegCheckCircle />} </IconContext.Provider> </Flex> </Box> ); }; LeftIcon.propTypes = { big: PropTypes.bool, icon: PropTypes.object, color: PropTypes.string, }; const Title = ({ big, title, color = colors.gray2 }) => { const fontVariant = big ? 'h3' : 'body1'; return ( <Typography variant={fontVariant} style={{ color }}> {title} </Typography> ); }; Title.propTypes = { big: PropTypes.bool, title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), color: PropTypes.string, }; const Description = ({ description }) => ( <> <Box mx={1}> <Typography variant="body1" style={{ color: colors.gray2 }}> | </Typography> </Box> <Typography variant="body1" style={{ color: colors.gray4, fontStyle: 'italic' }}> {description} </Typography> </> ); Description.propTypes = { description: PropTypes.string, }; const RightSideText = ({ text, color = colors.gray2 }) => ( <Typography style={{ color, fontSize: 14, lineHeight: '12px', letterSpacing: 1 }}> {text} </Typography> ); RightSideText.propTypes = { text: PropTypes.string, color: PropTypes.string, }; /** * We have two sets of headers: a default and a `big` header. The big variant has a different * height, different icon style and fonts and, most importantly, it can hold a 'valid' state. * When the header is valid, it turns green and all texts turn to white. */ const Header = ({ big, isSelected, isOpen, onClick, icon, iconColor, title, description, rightSideText, isDisabled, isList, selected, type, }) => { const turnWhite = big && isSelected ? colors.white : null; const backgroundColor = isSelected ? colors.green1 : isDisabled && colors['color-e5e5e5']; return ( <Box py={2} px={3} onClick={!isDisabled && onClick} style={{ backgroundColor, cursor: isDisabled ? 'default' : 'pointer' }} > <Grid container spacing={3} alignItems="center"> {isList ? ( <> <Grid item xs={4}> <Title big={big} color={turnWhite} title={title} /> </Grid> <Grid item xs={4}> <Title big={big} color={turnWhite} title={selected} /> </Grid> <Grid item xs={2}> {isList && <Title big={big} color={turnWhite} title={type} />} </Grid> </> ) : ( <Grid item xs={10}> <Flex directionRow> {iconColor && <LeftIcon color={turnWhite || iconColor} icon={icon} big={big} />} <Flex directionRow alignCenter justifySpaceBetween flexGrow={1}> <Title big={big} color={turnWhite} title={title} /> {!big && description && <Description description={description} />} </Flex> </Flex> </Grid> )} <Grid item xs={2}> <Flex directionRowReverse justifySpaceBetween alignCenter> <FiChevronDown color={turnWhite} style={{ fontSize: '1.25rem', transition: 'all 0.3s ease-in-out', transform: isOpen ? 'rotate(-180deg)' : 'rotate(0deg)', }} data-testid="accordion-knob" /> {big && rightSideText && <RightSideText color={turnWhite} text={rightSideText} />} </Flex> </Grid> </Grid> </Box> ); }; Header.defaultProps = { big: false, isSelected: false, isOpen: false, title: '', description: '', rightSideText: '', icon: null, iconColor: '', onClick: () => {}, isList: false, selected: {}, type: '', }; Header.propTypes = { big: PropTypes.bool, isSelected: PropTypes.bool, isOpen: PropTypes.bool, title: PropTypes.string, description: PropTypes.string, rightSideText: PropTypes.string, icon: PropTypes.object, iconColor: PropTypes.string, onClick: PropTypes.func, isList: PropTypes.bool, selected: PropTypes.object, type: PropTypes.string, }; const variants = { closed: { height: 0 }, open: { height: 'auto' }, transition: { duration: 500, ease: 'easeInOut' }, }; function Accordion({ children, open, ...props }) { const [isOpen, setIsOpen] = useState(open); useEffect(() => { setIsOpen(open); }, [open]); return ( <div style={{ backgroundColor: colors.white, boxShadow: '0px 6px 16px rgba(0, 0, 0, 0.05)', overflow: 'hidden', width: '100%', }} > <Header {...props} onClick={() => setIsOpen(!isOpen)} isOpen={isOpen} /> <motion.div variants={variants} animate={isOpen ? 'open' : 'closed'} aria-expanded={isOpen ? 'true' : 'false'} data-testid="accordion-expanded" > {children} </motion.div> </div> ); } Accordion.defaultProps = { title: '', description: '', iconColor: null, children: null, }; Accordion.propTypes = { title: PropTypes.string.isRequired, description: PropTypes.string, iconColor: PropTypes.string, children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]), }; export { Accordion };