feat: add new theme berry (#860)

* feat: add theme berry

* docs: add development notes

* fix: fix blank page

* chore: update implementation

* fix: fix package.json

* chore: update ui copy

---------

Co-authored-by: JustSong <songquanpeng@foxmail.com>
This commit is contained in:
Buer
2024-01-07 14:20:07 +08:00
committed by GitHub
parent 6227eee5bc
commit 48989d4a0b
157 changed files with 13979 additions and 5 deletions

View File

@@ -0,0 +1,11 @@
import { styled } from '@mui/material/styles';
import { Container } from '@mui/material';
const AdminContainer = styled(Container)(({ theme }) => ({
[theme.breakpoints.down('md')]: {
paddingLeft: '0px',
paddingRight: '0px'
}
}));
export default AdminContainer;

View File

@@ -0,0 +1,37 @@
// material-ui
import { Link, Container, Box } from '@mui/material';
import React from 'react';
import { useSelector } from 'react-redux';
// ==============================|| FOOTER - AUTHENTICATION 2 & 3 ||============================== //
const Footer = () => {
const siteInfo = useSelector((state) => state.siteInfo);
return (
<Container sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '64px' }}>
<Box sx={{ textAlign: 'center' }}>
{siteInfo.footer_html ? (
<div className="custom-footer" dangerouslySetInnerHTML={{ __html: siteInfo.footer_html }}></div>
) : (
<>
<Link href="https://github.com/songquanpeng/one-api" target="_blank">
{siteInfo.system_name} {process.env.REACT_APP_VERSION}{' '}
</Link>
{' '}
<Link href="https://github.com/songquanpeng" target="_blank">
JustSong
</Link>{' '}
构建主题 berry 来自{' '}
<Link href="https://github.com/MartialBE" target="_blank">
MartialBE
</Link>{' '}
<Link href="https://opensource.org/licenses/mit-license.php"> MIT 协议</Link>
</>
)}
</Box>
</Container>
);
};
export default Footer;

View File

@@ -0,0 +1,158 @@
/*
* Label.js
*
* This file uses code from the Minimal UI project, available at
* https://github.com/minimal-ui-kit/material-kit-react/blob/main/src/components/label/label.jsx
*
* Minimal UI is licensed under the MIT License. A copy of the license is included below:
*
* MIT License
*
* Copyright (c) 2021 Minimal UI (https://minimals.cc/)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
import Box from '@mui/material/Box';
import { useTheme } from '@mui/material/styles';
import { alpha, styled } from '@mui/material/styles';
// ----------------------------------------------------------------------
const Label = forwardRef(({ children, color = 'default', variant = 'soft', startIcon, endIcon, sx, ...other }, ref) => {
const theme = useTheme();
const iconStyles = {
width: 16,
height: 16,
'& svg, img': { width: 1, height: 1, objectFit: 'cover' }
};
return (
<StyledLabel
ref={ref}
component="span"
ownerState={{ color, variant }}
sx={{
...(startIcon && { pl: 0.75 }),
...(endIcon && { pr: 0.75 }),
...sx
}}
theme={theme}
{...other}
>
{startIcon && <Box sx={{ mr: 0.75, ...iconStyles }}> {startIcon} </Box>}
{children}
{endIcon && <Box sx={{ ml: 0.75, ...iconStyles }}> {endIcon} </Box>}
</StyledLabel>
);
});
Label.propTypes = {
children: PropTypes.node,
endIcon: PropTypes.object,
startIcon: PropTypes.object,
sx: PropTypes.object,
variant: PropTypes.oneOf(['filled', 'outlined', 'ghost', 'soft']),
color: PropTypes.oneOf(['default', 'primary', 'secondary', 'info', 'success', 'warning', 'orange', 'error'])
};
export default Label;
const StyledLabel = styled(Box)(({ theme, ownerState }) => {
// const lightMode = theme.palette.mode === 'light';
const filledVariant = ownerState.variant === 'filled';
const outlinedVariant = ownerState.variant === 'outlined';
const softVariant = ownerState.variant === 'soft';
const ghostVariant = ownerState.variant === 'ghost';
const defaultStyle = {
...(ownerState.color === 'default' && {
// FILLED
...(filledVariant && {
color: theme.palette.grey[300],
backgroundColor: theme.palette.text.primary
}),
// OUTLINED
...(outlinedVariant && {
color: theme.palette.grey[500],
border: `2px solid ${theme.palette.grey[500]}`
}),
// SOFT
...(softVariant && {
color: theme.palette.text.secondary,
backgroundColor: alpha(theme.palette.grey[500], 0.16)
})
})
};
const colorStyle = {
...(ownerState.color !== 'default' && {
// FILLED
...(filledVariant && {
color: theme.palette.background.paper,
backgroundColor: theme.palette[ownerState.color]?.main
}),
// OUTLINED
...(outlinedVariant && {
backgroundColor: 'transparent',
color: theme.palette[ownerState.color]?.main,
border: `2px solid ${theme.palette[ownerState.color]?.main}`
}),
// SOFT
...(softVariant && {
color: theme.palette[ownerState.color]['dark'],
backgroundColor: alpha(theme.palette[ownerState.color]?.main, 0.16)
}),
// GHOST
...(ghostVariant && {
color: theme.palette[ownerState.color]?.main
})
})
};
return {
height: 24,
minWidth: 24,
lineHeight: 0,
borderRadius: 6,
cursor: 'default',
alignItems: 'center',
whiteSpace: 'nowrap',
display: 'inline-flex',
justifyContent: 'center',
// textTransform: 'capitalize',
padding: theme.spacing(0, 0.75),
fontSize: theme.typography.pxToRem(12),
fontWeight: theme.typography.fontWeightBold,
transition: theme.transitions.create('all', {
duration: theme.transitions.duration.shorter
}),
...defaultStyle,
...colorStyle
};
});

View File

@@ -0,0 +1,15 @@
import { Suspense } from 'react';
// project imports
import Loader from './Loader';
// ==============================|| LOADABLE - LAZY LOADING ||============================== //
const Loadable = (Component) => (props) =>
(
<Suspense fallback={<Loader />}>
<Component {...props} />
</Suspense>
);
export default Loadable;

View File

@@ -0,0 +1,21 @@
// material-ui
import LinearProgress from '@mui/material/LinearProgress';
import { styled } from '@mui/material/styles';
// styles
const LoaderWrapper = styled('div')({
position: 'fixed',
top: 0,
left: 0,
zIndex: 1301,
width: '100%'
});
// ==============================|| LOADER ||============================== //
const Loader = () => (
<LoaderWrapper>
<LinearProgress color="primary" />
</LoaderWrapper>
);
export default Loader;

View File

@@ -0,0 +1,21 @@
// material-ui
import logo from 'assets/images/logo.svg';
import { useSelector } from 'react-redux';
/**
* if you want to use image instead of <svg> uncomment following.
*
* import logoDark from 'assets/images/logo-dark.svg';
* import logo from 'assets/images/logo.svg';
*
*/
// ==============================|| LOGO SVG ||============================== //
const Logo = () => {
const siteInfo = useSelector((state) => state.siteInfo);
return <img src={siteInfo.logo || logo} alt={siteInfo.system_name} width="80" />;
};
export default Logo;

View File

@@ -0,0 +1,31 @@
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
import Box from '@mui/material/Box';
// ----------------------------------------------------------------------
const SvgColor = forwardRef(({ src, sx, ...other }, ref) => (
<Box
component="span"
className="svg-color"
ref={ref}
sx={{
width: 24,
height: 24,
display: 'inline-block',
bgcolor: 'currentColor',
mask: `url(${src}) no-repeat center / contain`,
WebkitMask: `url(${src}) no-repeat center / contain`,
...sx
}}
{...other}
/>
));
SvgColor.propTypes = {
src: PropTypes.string,
sx: PropTypes.object
};
export default SvgColor;

View File

@@ -0,0 +1,37 @@
import { styled } from '@mui/material/styles';
import Switch from '@mui/material/Switch';
const TableSwitch = styled(Switch)(({ theme }) => ({
padding: 8,
'& .MuiSwitch-track': {
borderRadius: 22 / 2,
'&:before, &:after': {
content: '""',
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
width: 16,
height: 16
},
'&:before': {
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
theme.palette.getContrastText(theme.palette.primary.main)
)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`,
left: 12
},
'&:after': {
backgroundImage: `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 24 24"><path fill="${encodeURIComponent(
theme.palette.getContrastText(theme.palette.primary.main)
)}" d="M19,13H5V11H19V13Z" /></svg>')`,
right: 12
}
},
'& .MuiSwitch-thumb': {
boxShadow: 'none',
width: 16,
height: 16,
margin: 2
}
}));
export default TableSwitch;

View File

@@ -0,0 +1,47 @@
import PropTypes from 'prop-types';
import Toolbar from '@mui/material/Toolbar';
import OutlinedInput from '@mui/material/OutlinedInput';
import InputAdornment from '@mui/material/InputAdornment';
import { useTheme } from '@mui/material/styles';
import { IconSearch } from '@tabler/icons-react';
// ----------------------------------------------------------------------
export default function TableToolBar({ filterName, handleFilterName, placeholder }) {
const theme = useTheme();
const grey500 = theme.palette.grey[500];
return (
<Toolbar
sx={{
height: 80,
display: 'flex',
justifyContent: 'space-between',
p: (theme) => theme.spacing(0, 1, 0, 3)
}}
>
<OutlinedInput
id="keyword"
sx={{
minWidth: '100%'
}}
value={filterName}
onChange={handleFilterName}
placeholder={placeholder}
startAdornment={
<InputAdornment position="start">
<IconSearch stroke={1.5} size="20px" color={grey500} />
</InputAdornment>
}
/>
</Toolbar>
);
}
TableToolBar.propTypes = {
filterName: PropTypes.string,
handleFilterName: PropTypes.func,
placeholder: PropTypes.string
};

View File

@@ -0,0 +1,55 @@
import PropTypes from 'prop-types';
import { useTheme } from '@mui/material/styles';
import { ButtonBase, Link, Tooltip } from '@mui/material';
// project imports
import Avatar from '../extended/Avatar';
// ==============================|| CARD SECONDARY ACTION ||============================== //
const CardSecondaryAction = ({ title, link, icon }) => {
const theme = useTheme();
return (
<Tooltip title={title || 'Reference'} placement="left">
<ButtonBase disableRipple>
{!icon && (
<Avatar component={Link} href={link} target="_blank" alt="MUI Logo" size="badge" color="primary" outline>
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0)">
<path d="M100 260.9V131L212.5 195.95V239.25L137.5 195.95V282.55L100 260.9Z" fill={theme.palette.primary[800]} />
<path
d="M212.5 195.95L325 131V260.9L250 304.2L212.5 282.55L287.5 239.25V195.95L212.5 239.25V195.95Z"
fill={theme.palette.primary.main}
/>
<path d="M212.5 282.55V325.85L287.5 369.15V325.85L212.5 282.55Z" fill={theme.palette.primary[800]} />
<path
d="M287.5 369.15L400 304.2V217.6L362.5 239.25V282.55L287.5 325.85V369.15ZM362.5 195.95V152.65L400 131V174.3L362.5 195.95Z"
fill={theme.palette.primary.main}
/>
</g>
<defs>
<clipPath id="clip0">
<rect width="300" height="238.3" fill="white" transform="translate(100 131)" />
</clipPath>
</defs>
</svg>
</Avatar>
)}
{icon && (
<Avatar component={Link} href={link} target="_blank" size="badge" color="primary" outline>
{icon}
</Avatar>
)}
</ButtonBase>
</Tooltip>
);
};
CardSecondaryAction.propTypes = {
icon: PropTypes.node,
link: PropTypes.string,
title: PropTypes.string
};
export default CardSecondaryAction;

View File

@@ -0,0 +1,80 @@
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
// material-ui
import { useTheme } from '@mui/material/styles';
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material';
// constant
const headerSX = {
'& .MuiCardHeader-action': { mr: 0 }
};
// ==============================|| CUSTOM MAIN CARD ||============================== //
const MainCard = forwardRef(
(
{
border = true,
boxShadow,
children,
content = true,
contentClass = '',
contentSX = {},
darkTitle,
secondary,
shadow,
sx = {},
title,
...others
},
ref
) => {
const theme = useTheme();
return (
<Card
ref={ref}
{...others}
sx={{
border: border ? '1px solid' : 'none',
borderColor: theme.palette.primary[200] + 25,
':hover': {
boxShadow: boxShadow ? shadow || '0 2px 14px 0 rgb(32 40 45 / 8%)' : 'inherit'
},
...sx
}}
>
{/* card header and action */}
{title && <CardHeader sx={headerSX} title={darkTitle ? <Typography variant="h3">{title}</Typography> : title} action={secondary} />}
{/* content & header divider */}
{title && <Divider />}
{/* card content */}
{content && (
<CardContent sx={contentSX} className={contentClass}>
{children}
</CardContent>
)}
{!content && children}
</Card>
);
}
);
MainCard.propTypes = {
border: PropTypes.bool,
boxShadow: PropTypes.bool,
children: PropTypes.node,
content: PropTypes.bool,
contentClass: PropTypes.string,
contentSX: PropTypes.object,
darkTitle: PropTypes.bool,
secondary: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]),
shadow: PropTypes.string,
sx: PropTypes.object,
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object])
};
export default MainCard;

View File

@@ -0,0 +1,32 @@
// material-ui
import { Card, CardContent, Grid } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
// ==============================|| SKELETON - EARNING CARD ||============================== //
const EarningCard = () => (
<Card>
<CardContent>
<Grid container direction="column">
<Grid item>
<Grid container justifyContent="space-between">
<Grid item>
<Skeleton variant="rectangular" width={44} height={44} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" width={34} height={34} />
</Grid>
</Grid>
</Grid>
<Grid item>
<Skeleton variant="rectangular" sx={{ my: 2 }} height={40} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={30} />
</Grid>
</Grid>
</CardContent>
</Card>
);
export default EarningCard;

View File

@@ -0,0 +1,8 @@
// material-ui
import Skeleton from '@mui/material/Skeleton';
// ==============================|| SKELETON IMAGE CARD ||============================== //
const ImagePlaceholder = ({ ...others }) => <Skeleton variant="rectangular" {...others} animation="wave" />;
export default ImagePlaceholder;

View File

@@ -0,0 +1,155 @@
// material-ui
import { Card, CardContent, Grid } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
// project imports
import { gridSpacing } from 'store/constant';
// ==============================|| SKELETON - POPULAR CARD ||============================== //
const PopularCard = () => (
<Card>
<CardContent>
<Grid container spacing={gridSpacing}>
<Grid item xs={12}>
<Grid container alignItems="center" justifyContent="space-between" spacing={gridSpacing}>
<Grid item xs zeroMinWidth>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={20} width={20} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Skeleton variant="rectangular" height={150} />
</Grid>
<Grid item xs={12}>
<Grid container spacing={1}>
<Grid item xs={12}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item xs={6}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs zeroMinWidth>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={16} width={16} />
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Grid container spacing={1}>
<Grid item xs={12}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item xs={6}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs zeroMinWidth>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={16} width={16} />
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Grid container spacing={1}>
<Grid item xs={12}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item xs={6}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs zeroMinWidth>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={16} width={16} />
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Grid container spacing={1}>
<Grid item xs={12}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item xs={6}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs zeroMinWidth>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={16} width={16} />
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Grid container spacing={1}>
<Grid item xs={12}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item xs={6}>
<Grid container alignItems="center" spacing={gridSpacing} justifyContent="space-between">
<Grid item xs zeroMinWidth>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={16} width={16} />
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
<Grid item xs={6}>
<Skeleton variant="rectangular" height={20} />
</Grid>
</Grid>
</Grid>
</Grid>
</CardContent>
<CardContent sx={{ p: 1.25, display: 'flex', pt: 0, justifyContent: 'center' }}>
<Skeleton variant="rectangular" height={25} width={75} />
</CardContent>
</Card>
);
export default PopularCard;

View File

@@ -0,0 +1,44 @@
// material-ui
import { CardContent, Grid, Skeleton, Stack } from '@mui/material';
// project import
import MainCard from '../MainCard';
// ===========================|| SKELETON TOTAL GROWTH BAR CHART ||=========================== //
const ProductPlaceholder = () => (
<MainCard content={false} boxShadow>
<Skeleton variant="rectangular" height={220} />
<CardContent sx={{ p: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Skeleton variant="rectangular" height={20} />
</Grid>
<Grid item xs={12}>
<Skeleton variant="rectangular" height={45} />
</Grid>
<Grid item xs={12} sx={{ pt: '8px !important' }}>
<Stack direction="row" alignItems="center" spacing={1}>
<Skeleton variant="rectangular" height={20} width={90} />
<Skeleton variant="rectangular" height={20} width={38} />
</Stack>
</Grid>
<Grid item xs={12}>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Grid container spacing={1}>
<Grid item>
<Skeleton variant="rectangular" height={20} width={40} />
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={17} width={20} />
</Grid>
</Grid>
<Skeleton variant="rectangular" height={32} width={47} />
</Stack>
</Grid>
</Grid>
</CardContent>
</MainCard>
);
export default ProductPlaceholder;

View File

@@ -0,0 +1,39 @@
// material-ui
import { Card, CardContent, Grid } from '@mui/material';
import Skeleton from '@mui/material/Skeleton';
// project imports
import { gridSpacing } from 'store/constant';
// ==============================|| SKELETON TOTAL GROWTH BAR CHART ||============================== //
const TotalGrowthBarChart = () => (
<Card>
<CardContent>
<Grid container spacing={gridSpacing}>
<Grid item xs={12}>
<Grid container alignItems="center" justifyContent="space-between" spacing={gridSpacing}>
<Grid item xs zeroMinWidth>
<Grid container spacing={1}>
<Grid item xs={12}>
<Skeleton variant="text" />
</Grid>
<Grid item xs={12}>
<Skeleton variant="rectangular" height={20} />
</Grid>
</Grid>
</Grid>
<Grid item>
<Skeleton variant="rectangular" height={50} width={80} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Skeleton variant="rectangular" height={530} />
</Grid>
</Grid>
</CardContent>
</Card>
);
export default TotalGrowthBarChart;

View File

@@ -0,0 +1,19 @@
// material-ui
import { Card, List, ListItem, ListItemAvatar, ListItemText, Skeleton } from '@mui/material';
// ==============================|| SKELETON - TOTAL INCOME DARK/LIGHT CARD ||============================== //
const TotalIncomeCard = () => (
<Card sx={{ p: 2 }}>
<List sx={{ py: 0 }}>
<ListItem alignItems="center" disableGutters sx={{ py: 0 }}>
<ListItemAvatar>
<Skeleton variant="rectangular" width={44} height={44} />
</ListItemAvatar>
<ListItemText sx={{ py: 0 }} primary={<Skeleton variant="rectangular" height={20} />} secondary={<Skeleton variant="text" />} />
</ListItem>
</List>
</Card>
);
export default TotalIncomeCard;

View File

@@ -0,0 +1,72 @@
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
// material-ui
import { useTheme } from '@mui/material/styles';
import { Card, CardContent, CardHeader, Divider, Typography } from '@mui/material';
// ==============================|| CUSTOM SUB CARD ||============================== //
const SubCard = forwardRef(
({ children, content, contentClass, darkTitle, secondary, sx = {}, contentSX = {}, title, subTitle, ...others }, ref) => {
const theme = useTheme();
return (
<Card
ref={ref}
sx={{
border: '1px solid',
borderColor: theme.palette.primary.light,
':hover': {
boxShadow: '0 2px 14px 0 rgb(32 40 45 / 8%)'
},
...sx
}}
{...others}
>
{/* card header and action */}
{!darkTitle && title && (
<CardHeader sx={{ p: 2.5 }} title={<Typography variant="h5">{title}</Typography>} action={secondary} subheader={subTitle} />
)}
{darkTitle && title && (
<CardHeader sx={{ p: 2.5 }} title={<Typography variant="h4">{title}</Typography>} action={secondary} subheader={subTitle} />
)}
{/* content & header divider */}
{title && (
<Divider
sx={{
opacity: 1,
borderColor: theme.palette.primary.light
}}
/>
)}
{/* card content */}
{content && (
<CardContent sx={{ p: 2.5, ...contentSX }} className={contentClass || ''}>
{children}
</CardContent>
)}
{!content && children}
</Card>
);
}
);
SubCard.propTypes = {
children: PropTypes.node,
content: PropTypes.bool,
contentClass: PropTypes.string,
darkTitle: PropTypes.bool,
secondary: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]),
sx: PropTypes.object,
contentSX: PropTypes.object,
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object])
};
SubCard.defaultProps = {
content: true
};
export default SubCard;

View File

@@ -0,0 +1,121 @@
/*
* UserCard.js
*
* This file uses code from the Minimal UI project, available at
* https://github.com/minimal-ui-kit/material-kit-react/blob/main/src/sections/blog/post-card.jsx
*
* Minimal UI is licensed under the MIT License. A copy of the license is included below:
*
* MIT License
*
* Copyright (c) 2021 Minimal UI (https://minimals.cc/)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { Box, Avatar } from '@mui/material';
import { alpha } from '@mui/material/styles';
import Card from '@mui/material/Card';
import shapeAvatar from 'assets/images/icons/shape-avatar.svg';
import coverAvatar from 'assets/images/invite/cover.jpg';
import userAvatar from 'assets/images/users/user-round.svg';
import SvgColor from 'ui-component/SvgColor';
import React from 'react';
export default function UserCard({ children }) {
const renderShape = (
<SvgColor
color="paper"
src={shapeAvatar}
sx={{
width: '100%',
height: 62,
zIndex: 10,
bottom: -26,
position: 'absolute',
color: 'background.paper'
}}
/>
);
const renderAvatar = (
<Avatar
src={userAvatar}
sx={{
zIndex: 11,
width: 64,
height: 64,
position: 'absolute',
alignItems: 'center',
marginLeft: 'auto',
marginRight: 'auto',
left: 0,
right: 0,
bottom: (theme) => theme.spacing(-4)
}}
/>
);
const renderCover = (
<Box
component="img"
src={coverAvatar}
sx={{
top: 0,
width: 1,
height: 1,
objectFit: 'cover',
position: 'absolute'
}}
/>
);
return (
<Card>
<Box
sx={{
position: 'relative',
'&:after': {
top: 0,
content: "''",
width: '100%',
height: '100%',
position: 'absolute',
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.42)
},
pt: {
xs: 'calc(100% / 3)',
sm: 'calc(100% / 4.66)'
}
}}
>
{renderShape}
{renderAvatar}
{renderCover}
</Box>
<Box
sx={{
p: (theme) => theme.spacing(4, 3, 3, 3)
}}
>
{children}
</Box>
</Card>
);
}

View File

@@ -0,0 +1,92 @@
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
// third-party
import { motion, useCycle } from 'framer-motion';
// ==============================|| ANIMATION BUTTON ||============================== //
const AnimateButton = forwardRef(({ children, type, direction, offset, scale }, ref) => {
let offset1;
let offset2;
switch (direction) {
case 'up':
case 'left':
offset1 = offset;
offset2 = 0;
break;
case 'right':
case 'down':
default:
offset1 = 0;
offset2 = offset;
break;
}
const [x, cycleX] = useCycle(offset1, offset2);
const [y, cycleY] = useCycle(offset1, offset2);
switch (type) {
case 'rotate':
return (
<motion.div
ref={ref}
animate={{ rotate: 360 }}
transition={{
repeat: Infinity,
repeatType: 'loop',
duration: 2,
repeatDelay: 0
}}
>
{children}
</motion.div>
);
case 'slide':
if (direction === 'up' || direction === 'down') {
return (
<motion.div ref={ref} animate={{ y: y !== undefined ? y : '' }} onHoverEnd={() => cycleY()} onHoverStart={() => cycleY()}>
{children}
</motion.div>
);
}
return (
<motion.div ref={ref} animate={{ x: x !== undefined ? x : '' }} onHoverEnd={() => cycleX()} onHoverStart={() => cycleX()}>
{children}
</motion.div>
);
case 'scale':
default:
if (typeof scale === 'number') {
scale = {
hover: scale,
tap: scale
};
}
return (
<motion.div ref={ref} whileHover={{ scale: scale?.hover }} whileTap={{ scale: scale?.tap }}>
{children}
</motion.div>
);
}
});
AnimateButton.propTypes = {
children: PropTypes.node,
offset: PropTypes.number,
type: PropTypes.oneOf(['slide', 'scale', 'rotate']),
direction: PropTypes.oneOf(['up', 'down', 'left', 'right']),
scale: PropTypes.oneOfType([PropTypes.number, PropTypes.object])
};
AnimateButton.defaultProps = {
type: 'scale',
offset: 10,
direction: 'right',
scale: {
hover: 1,
tap: 0.9
}
};
export default AnimateButton;

View File

@@ -0,0 +1,72 @@
import PropTypes from 'prop-types';
// material-ui
import { useTheme } from '@mui/material/styles';
import MuiAvatar from '@mui/material/Avatar';
// ==============================|| AVATAR ||============================== //
const Avatar = ({ color, outline, size, sx, ...others }) => {
const theme = useTheme();
const colorSX = color && !outline && { color: theme.palette.background.paper, bgcolor: `${color}.main` };
const outlineSX = outline && {
color: color ? `${color}.main` : `primary.main`,
bgcolor: theme.palette.background.paper,
border: '2px solid',
borderColor: color ? `${color}.main` : `primary.main`
};
let sizeSX = {};
switch (size) {
case 'badge':
sizeSX = {
width: theme.spacing(3.5),
height: theme.spacing(3.5)
};
break;
case 'xs':
sizeSX = {
width: theme.spacing(4.25),
height: theme.spacing(4.25)
};
break;
case 'sm':
sizeSX = {
width: theme.spacing(5),
height: theme.spacing(5)
};
break;
case 'lg':
sizeSX = {
width: theme.spacing(9),
height: theme.spacing(9)
};
break;
case 'xl':
sizeSX = {
width: theme.spacing(10.25),
height: theme.spacing(10.25)
};
break;
case 'md':
sizeSX = {
width: theme.spacing(7.5),
height: theme.spacing(7.5)
};
break;
default:
sizeSX = {};
}
return <MuiAvatar sx={{ ...colorSX, ...outlineSX, ...sizeSX, ...sx }} {...others} />;
};
Avatar.propTypes = {
className: PropTypes.string,
color: PropTypes.string,
outline: PropTypes.bool,
size: PropTypes.string,
sx: PropTypes.object
};
export default Avatar;

View File

@@ -0,0 +1,187 @@
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
// material-ui
import { useTheme } from '@mui/material/styles';
import { Box, Card, Divider, Grid, Typography } from '@mui/material';
import MuiBreadcrumbs from '@mui/material/Breadcrumbs';
// project imports
import config from 'config';
import { gridSpacing } from 'store/constant';
// assets
import { IconTallymark1 } from '@tabler/icons-react';
import AccountTreeTwoToneIcon from '@mui/icons-material/AccountTreeTwoTone';
import HomeIcon from '@mui/icons-material/Home';
import HomeTwoToneIcon from '@mui/icons-material/HomeTwoTone';
const linkSX = {
display: 'flex',
color: 'grey.900',
textDecoration: 'none',
alignContent: 'center',
alignItems: 'center'
};
// ==============================|| BREADCRUMBS ||============================== //
const Breadcrumbs = ({ card, divider, icon, icons, maxItems, navigation, rightAlign, separator, title, titleBottom, ...others }) => {
const theme = useTheme();
const iconStyle = {
marginRight: theme.spacing(0.75),
marginTop: `-${theme.spacing(0.25)}`,
width: '1rem',
height: '1rem',
color: theme.palette.secondary.main
};
const [main, setMain] = useState();
const [item, setItem] = useState();
// set active item state
const getCollapse = (menu) => {
if (menu.children) {
menu.children.filter((collapse) => {
if (collapse.type && collapse.type === 'collapse') {
getCollapse(collapse);
} else if (collapse.type && collapse.type === 'item') {
if (document.location.pathname === config.basename + collapse.url) {
setMain(menu);
setItem(collapse);
}
}
return false;
});
}
};
useEffect(() => {
navigation?.items?.map((menu) => {
if (menu.type && menu.type === 'group') {
getCollapse(menu);
}
return false;
});
});
// item separator
const SeparatorIcon = separator;
const separatorIcon = separator ? <SeparatorIcon stroke={1.5} size="1rem" /> : <IconTallymark1 stroke={1.5} size="1rem" />;
let mainContent;
let itemContent;
let breadcrumbContent = <Typography />;
let itemTitle = '';
let CollapseIcon;
let ItemIcon;
// collapse item
if (main && main.type === 'collapse') {
CollapseIcon = main.icon ? main.icon : AccountTreeTwoToneIcon;
mainContent = (
<Typography component={Link} to="#" variant="subtitle1" sx={linkSX}>
{icons && <CollapseIcon style={iconStyle} />}
{main.title}
</Typography>
);
}
// items
if (item && item.type === 'item') {
itemTitle = item.title;
ItemIcon = item.icon ? item.icon : AccountTreeTwoToneIcon;
itemContent = (
<Typography
variant="subtitle1"
sx={{
display: 'flex',
textDecoration: 'none',
alignContent: 'center',
alignItems: 'center',
color: 'grey.500'
}}
>
{icons && <ItemIcon style={iconStyle} />}
{itemTitle}
</Typography>
);
// main
if (item.breadcrumbs !== false) {
breadcrumbContent = (
<Card
sx={{
marginBottom: card === false ? 0 : theme.spacing(gridSpacing),
border: card === false ? 'none' : '1px solid',
borderColor: theme.palette.primary[200] + 75,
background: card === false ? 'transparent' : theme.palette.background.default
}}
{...others}
>
<Box sx={{ p: 2, pl: card === false ? 0 : 2 }}>
<Grid
container
direction={rightAlign ? 'row' : 'column'}
justifyContent={rightAlign ? 'space-between' : 'flex-start'}
alignItems={rightAlign ? 'center' : 'flex-start'}
spacing={1}
>
{title && !titleBottom && (
<Grid item>
<Typography variant="h3" sx={{ fontWeight: 500 }}>
{item.title}
</Typography>
</Grid>
)}
<Grid item>
<MuiBreadcrumbs
sx={{ '& .MuiBreadcrumbs-separator': { width: 16, ml: 1.25, mr: 1.25 } }}
aria-label="breadcrumb"
maxItems={maxItems || 8}
separator={separatorIcon}
>
<Typography component={Link} to="/" color="inherit" variant="subtitle1" sx={linkSX}>
{icons && <HomeTwoToneIcon sx={iconStyle} />}
{icon && <HomeIcon sx={{ ...iconStyle, mr: 0 }} />}
{!icon && 'Dashboard'}
</Typography>
{mainContent}
{itemContent}
</MuiBreadcrumbs>
</Grid>
{title && titleBottom && (
<Grid item>
<Typography variant="h3" sx={{ fontWeight: 500 }}>
{item.title}
</Typography>
</Grid>
)}
</Grid>
</Box>
{card === false && divider !== false && <Divider sx={{ borderColor: theme.palette.primary.main, mb: gridSpacing }} />}
</Card>
);
}
}
return breadcrumbContent;
};
Breadcrumbs.propTypes = {
card: PropTypes.bool,
divider: PropTypes.bool,
icon: PropTypes.bool,
icons: PropTypes.bool,
maxItems: PropTypes.number,
navigation: PropTypes.object,
rightAlign: PropTypes.bool,
separator: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
title: PropTypes.bool,
titleBottom: PropTypes.bool
};
export default Breadcrumbs;

View File

@@ -0,0 +1,107 @@
import PropTypes from 'prop-types';
import { forwardRef } from 'react';
// material-ui
import { Collapse, Fade, Box, Grow, Slide, Zoom } from '@mui/material';
// ==============================|| TRANSITIONS ||============================== //
const Transitions = forwardRef(({ children, position, type, direction, ...others }, ref) => {
let positionSX = {
transformOrigin: '0 0 0'
};
switch (position) {
case 'top-right':
positionSX = {
transformOrigin: 'top right'
};
break;
case 'top':
positionSX = {
transformOrigin: 'top'
};
break;
case 'bottom-left':
positionSX = {
transformOrigin: 'bottom left'
};
break;
case 'bottom-right':
positionSX = {
transformOrigin: 'bottom right'
};
break;
case 'bottom':
positionSX = {
transformOrigin: 'bottom'
};
break;
case 'top-left':
default:
positionSX = {
transformOrigin: '0 0 0'
};
break;
}
return (
<Box ref={ref}>
{type === 'grow' && (
<Grow {...others}>
<Box sx={positionSX}>{children}</Box>
</Grow>
)}
{type === 'collapse' && (
<Collapse {...others} sx={positionSX}>
{children}
</Collapse>
)}
{type === 'fade' && (
<Fade
{...others}
timeout={{
appear: 500,
enter: 600,
exit: 400
}}
>
<Box sx={positionSX}>{children}</Box>
</Fade>
)}
{type === 'slide' && (
<Slide
{...others}
timeout={{
appear: 0,
enter: 400,
exit: 200
}}
direction={direction}
>
<Box sx={positionSX}>{children}</Box>
</Slide>
)}
{type === 'zoom' && (
<Zoom {...others}>
<Box sx={positionSX}>{children}</Box>
</Zoom>
)}
</Box>
);
});
Transitions.propTypes = {
children: PropTypes.node,
type: PropTypes.oneOf(['grow', 'fade', 'collapse', 'slide', 'zoom']),
position: PropTypes.oneOf(['top-left', 'top-right', 'top', 'bottom-left', 'bottom-right', 'bottom']),
direction: PropTypes.oneOf(['up', 'down', 'left', 'right'])
};
Transitions.defaultProps = {
type: 'grow',
position: 'top-left',
direction: 'up'
};
export default Transitions;