@eureca/eureca-ui
Version:
UI component library of Eureca's user and admin apps
244 lines (220 loc) • 6.2 kB
JavaScript
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 };