First Commit

This commit is contained in:
cesnimda
2026-03-21 11:55:27 +01:00
commit 2e8a29b4d0
1757 changed files with 166084 additions and 0 deletions
@@ -0,0 +1,268 @@
'use client';
import PropTypes from 'prop-types';
import { useState } from 'react';
// @mui
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Tab from '@mui/material/Tab';
import TabContext from '@mui/lab/TabContext';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import { GraphicsCard } from '@/components/cards';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import GraphicsImage from '@/components/GraphicsImage';
/*************************** FEATURE - 18 ***************************/
/**
*
* Demos:
* - [Feature18](https://www.saasable.io/blocks/feature/feature18)
*
* API
* - [Feature18 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/feature/feature18#props-details)
*/
export default function Feature18({ heading, caption, topics }) {
const boxPadding = { xs: 3, md: 5 };
const imagePadding = { xs: 3, sm: 4, md: 5 };
const [value, setValue] = useState('1');
// Handle tab change
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
<Typeset
{...{
heading,
caption,
stackProps: { sx: { alignItems: 'center', textAlign: 'center', maxWidth: { sm: 470, md: 615 }, mx: 'auto' } }
}}
/>
<Stack sx={{ gap: 1.5, alignItems: 'center' }}>
<TabContext value={value}>
<GraphicsCard sx={{ width: { xs: 1, sm: 'unset' } }}>
<Box sx={{ p: 0.25 }}>
<TabList
onChange={handleChange}
sx={{ minHeight: 'unset', p: 0.25 }}
slotProps={{ indicator: { sx: { display: 'none' } } }}
variant="scrollable"
>
{topics.map((item, index) => (
<Tab
label={item.title}
disableFocusRipple
{...(item.icon && {
icon: (
<SvgIcon
{...(typeof item.icon === 'string' ? { name: item.icon } : { ...item.icon })}
size={16}
stroke={2}
color="text.secondary"
/>
)
})}
value={String(index + 1)}
key={index}
iconPosition="start"
tabIndex={0}
sx={{
minHeight: 44,
minWidth: { xs: 112, md: 160, sm: 156 },
borderRadius: 10,
borderWidth: 1,
borderStyle: 'solid',
borderColor: 'transparent',
'& svg ': { mr: 1 },
'&.Mui-selected': {
bgcolor: 'grey.200',
borderColor: 'grey.400',
minWidth: { xs: 112, md: 160, sm: 156 },
color: 'text.primary',
'& svg': { stroke: 'text.primary' }
},
'&.Mui-focusVisible': { bgcolor: 'grey.300' },
'&:hover': { bgcolor: 'grey.200' }
}}
/>
))}
</TabList>
</Box>
</GraphicsCard>
{topics.map((item, index) => (
<TabPanel value={String(index + 1)} key={index} sx={{ p: 0, width: 1 }}>
<Grid container spacing={1.5}>
<Grid size={{ xs: 12, sm: 5 }}>
<GraphicsCard>
<motion.div
initial={{ opacity: 0, x: 100, y: 100 }}
whileInView={{ opacity: 1, x: 0, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<Box
sx={{
pl: item.isCoverImage ? 0 : imagePadding,
pt: item.isCoverImage ? 0 : imagePadding,
height: { xs: 260, sm: 396, md: 434 }
}}
>
<GraphicsImage
sx={{
height: 1,
backgroundPositionX: 'left',
backgroundPositionY: 'top',
...(item.isImageBorder && { borderTop: '5px solid', borderLeft: '5px solid', borderColor: 'grey.200' }),
...(item.isCoverImage && { backgroundSize: 'cover', border: 'none' }),
borderTopLeftRadius: { xs: 12 },
borderBottomRightRadius: { xs: 20, sm: 32, md: 40 }
}}
image={item.image}
/>
</Box>
</motion.div>
</GraphicsCard>
</Grid>
<Grid size={{ xs: 12, sm: 7 }} sx={{ display: 'flex' }}>
<GraphicsCard>
<Stack
sx={{
justifyContent: 'space-between',
gap: 5,
height: item.actionBtn || item.actionBtn2 ? { sm: 'calc(100% - 98px)', md: 'calc(100% - 114px)' } : 1,
pt: boxPadding,
px: boxPadding
}}
>
<motion.div
key={index}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -30 }}
transition={{ duration: 0.2, ease: 'linear', delay: index * 0.1 }}
>
<Stack direction="row" sx={{ gap: 1 }}>
{item.icon && (
<SvgIcon
{...(typeof item.icon === 'string' ? { name: item.icon } : { ...item.icon })}
size={16}
stroke={2}
color="text.primary"
/>
)}
<Typography variant="subtitle2" sx={{ color: 'text.secondary' }}>
{item.title}
</Typography>
</Stack>
</motion.div>
<Stack sx={{ gap: { xs: 2, md: 3 }, pb: boxPadding }}>
<Stack sx={{ gap: 0.5 }}>
<Typography variant="h4">{item.title2}</Typography>
{item.description && <Typography sx={{ color: 'text.secondary' }}>{item.description}</Typography>}
</Stack>
{item.list && (
<Grid container spacing={{ xs: 0.75, md: 1 }}>
{item.list.map((list, index) => (
<Grid key={index} size={{ xs: 12, md: 6 }}>
<motion.div
key={index}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -30 }}
transition={{ duration: 0.2, ease: 'linear', delay: index * 0.1 }}
>
<Stack
direction="row"
sx={{
gap: 0.5,
alignItems: 'center',
'& svg.tabler-rosette-discount-check': { width: { xs: 16, md: 24 }, height: { xs: 16, md: 24 } }
}}
>
<SvgIcon name="tabler-rosette-discount-check" stroke={1} color="text.secondary" />
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{list.primary}
</Typography>
</Stack>
</motion.div>
</Grid>
))}
</Grid>
)}
</Stack>
</Stack>
{(item.actionBtn || item.actionBtn2) && (
<GraphicsCard sx={{ bgcolor: 'grey.200' }}>
<Stack direction="row" sx={{ alignItems: 'flex-start', gap: 1.5, p: { xs: 2, sm: 3, md: 4 } }}>
{item.actionBtn2 && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="outlined"
color="primary"
startIcon={<SvgIcon name="tabler-help" size={16} stroke={3} />}
{...item.actionBtn2}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
{item.actionBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
color="primary"
startIcon={<SvgIcon name="tabler-link" size={16} stroke={3} color="background.default" />}
{...item.actionBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</GraphicsCard>
)}
</GraphicsCard>
</Grid>
</Grid>
</TabPanel>
))}
</TabContext>
</Stack>
</Stack>
</ContainerWrapper>
);
}
Feature18.propTypes = { heading: PropTypes.string, caption: PropTypes.string, topics: PropTypes.array };
@@ -0,0 +1,248 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import { GraphicsCard } from '@/components/cards';
import GraphicsImage from '@/components/GraphicsImage';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import { withAlpha } from '@/utils/colorUtils';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import Star from '@/images/graphics/Star';
/*************************** FEATURE - 20 ***************************/
/**
*
* Demos:
* - [Feature20](https://www.saasable.io/blocks/feature/feature20)
*
* API
* - [Feature20 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/feature/feature20#props-details)
*/
export default function Feature20({ heading, caption, image, features, actionBtn, secondaryBtn }) {
const theme = useTheme();
const downSM = useMediaQuery(theme.breakpoints.down('sm'));
const downMD = useMediaQuery(theme.breakpoints.down('md'));
const partitionInExtraSmall = 1;
const partitionInSmall = 2;
const partitionInLarge = 3;
const columns = downSM ? partitionInExtraSmall : downMD ? partitionInSmall : partitionInLarge;
const calculateElementsInLastRow = (dataArray, columns) => {
const totalItems = dataArray.length;
const elementsInLastRow = totalItems % columns || columns;
return elementsInLastRow;
};
const calculateIndexOfFirstElementInLastRow = (dataArray, elementsInLastRow) => {
const totalItems = dataArray.length;
const indexOfFirstElementInLastRow = totalItems - elementsInLastRow;
return indexOfFirstElementInLastRow;
};
const elementsInLastRow = calculateElementsInLastRow(features, columns);
const indexOfFirstElementInLastRow = calculateIndexOfFirstElementInLastRow(features, elementsInLastRow);
const calculateIndexOfLastElementOfEachRow = (dataArray, columns) => {
const indices = [];
const totalItems = dataArray.length;
const rows = Math.ceil(totalItems / columns);
for (let i = 1; i <= rows; i++) {
const lastIndexInRow = i * columns - 1;
indices.push(lastIndexInRow < totalItems ? lastIndexInRow : totalItems - 1);
}
return indices;
};
const indicesOfLastElements = calculateIndexOfLastElementOfEachRow(features, columns);
const gc = theme.vars.palette.background.default;
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4, md: 5 } }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.3
}}
>
<Typeset {...{ heading, stackProps: { sx: { maxWidth: { md: 500 }, ...(!image && { maxWidth: 1, textAlign: 'center' }) } } }} />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<GraphicsCard sx={{ position: 'relative', overflow: 'visible' }}>
{image && (
<GraphicsCard
sx={{
height: { md: 267 },
width: { md: 456 },
bgcolor: 'transparent',
position: 'absolute',
top: -190,
right: 45,
zIndex: -1,
display: { xs: 'none', md: 'block' }
}}
>
<GraphicsImage sx={{ height: 1, backgroundPositionX: 'right', backgroundPositionY: 'top' }} image={image}>
<Box
sx={{ width: 1, height: 1, background: `linear-gradient(180deg, ${withAlpha(gc, 0)} 0%, ${withAlpha(gc, 0.6)} 100%)` }}
/>
</GraphicsImage>
</GraphicsCard>
)}
<Box sx={{ p: 3 }}>
<Grid container>
{features.map((item, index) => (
<Grid
key={index}
size={{ xs: 12 / partitionInExtraSmall, sm: 12 / partitionInSmall, md: 12 / partitionInLarge }}
sx={{
position: 'relative',
...(index < indexOfFirstElementInLastRow && { borderBottom: `1px solid ${theme.vars.palette.grey[300]}` }),
...(!indicesOfLastElements.includes(index) && { borderRight: `1px solid ${theme.vars.palette.grey[300]}` })
}}
>
<Stack sx={{ gap: { xs: 3, sm: 4 }, height: 1, py: { xs: 1.5, sm: 3, md: 4 }, px: { xs: 0, sm: 3, md: 4 } }}>
<Avatar sx={{ width: 60, height: 60, bgcolor: 'grey.300' }}>
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 2, delay: index * 0.1 }}
>
<SvgIcon {...(typeof item.icon === 'string' ? { name: item.icon } : { ...item.icon })} />
</motion.div>
</Avatar>
<Stack sx={{ gap: { xs: 0.5, md: 1 } }}>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.2 }}
>
{item.title && <Typography variant="h4">{item.title}</Typography>}
</motion.div>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.3 }}
>
{item.content && <Typography sx={{ color: 'text.secondary' }}>{item.content}</Typography>}
</motion.div>
</Stack>
</Stack>
{index < indexOfFirstElementInLastRow && !indicesOfLastElements.includes(index) && (
<Stack sx={{ position: 'absolute', bottom: -9, right: -9 }}>
<Star />
</Stack>
)}
</Grid>
))}
</Grid>
</Box>
</GraphicsCard>
</motion.div>
<Stack sx={{ alignItems: 'center', gap: 3 }}>
<Typography variant="h6" sx={{ color: 'text.secondary', maxWidth: { xs: '75%', sm: '45%' }, textAlign: 'center' }}>
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
{caption}
</motion.div>
</Typography>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.5
}}
>
<Stack direction="row" sx={{ alignItems: 'center', justifyContent: 'center', gap: 1.5 }}>
{secondaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="outlined" {...secondaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
)}
{actionBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
color="primary"
startIcon={<SvgIcon name="tabler-sparkles" size={16} stroke={3} color="background.default" />}
{...actionBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</motion.div>
</Stack>
</Stack>
</ContainerWrapper>
);
}
Feature20.propTypes = {
heading: PropTypes.string,
caption: PropTypes.string,
image: PropTypes.any,
features: PropTypes.array,
actionBtn: PropTypes.any,
secondaryBtn: PropTypes.any
};
@@ -0,0 +1,153 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { GraphicsCard, IconCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import GraphicsImage from '@/components/GraphicsImage';
import Typeset from '@/components/Typeset';
import SvgIcon from '@/components/SvgIcon';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** FEATURE - 21 ***************************/
/**
*
* Demos:
* - [Feature21](https://www.saasable.io/blocks/feature/feature21)
*
* API
* - [Feature21 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/feature/feature21#props-details)
*/
export default function Feature21({ heading, caption, image, features, primaryBtn, secondaryBtn }) {
const imagePadding = { xs: 3, sm: 4, md: 5 };
const iconProps = { color: 'text.primary', stroke: 1 };
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
{(heading || caption) && (
<motion.div
initial={{ opacity: 0, y: 5 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<Typeset {...{ heading, caption, stackProps: { sx: { textAlign: 'center' } } }} />
</motion.div>
)}
<Stack sx={{ gap: 1.5 }}>
{image && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.3,
delay: 0.3
}}
>
<GraphicsCard>
<Box sx={{ pl: imagePadding, pt: imagePadding, height: { xs: 204, sm: 300, md: 360 } }}>
<GraphicsImage
image={image}
sx={{
height: 1,
backgroundPositionX: 'left',
backgroundPositionY: 'top',
borderTopLeftRadius: { xs: 12 },
borderBottomRightRadius: { xs: 20, sm: 32, md: 40 },
border: '5px solid',
borderColor: 'grey.200',
borderBottom: 'none',
borderRight: 'none'
}}
/>
</Box>
</GraphicsCard>
</motion.div>
)}
<Grid container spacing={1.5}>
{features.map((item, index) => (
<Grid key={index} size={{ xs: 12, sm: 6, md: 3 }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: index * 0.2 }}
style={{ height: '100%' }}
>
<IconCard
icon={{ ...(typeof item.icon === 'string' ? { name: item.icon, ...iconProps } : { ...iconProps, ...item.icon }) }}
title={item.title}
stackProps={{ sx: { gap: 2, height: 1 } }}
cardPadding={{ xs: 2, sm: 3 }}
/>
</motion.div>
</Grid>
))}
</Grid>
</Stack>
{(primaryBtn || secondaryBtn) && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<Stack direction="row" sx={{ alignItems: 'center', justifyContent: 'center', gap: 1.5 }}>
{secondaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="outlined" startIcon={<SvgIcon name="tabler-eye" size={16} stroke={3} />} {...secondaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
)}
{primaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
startIcon={<SvgIcon name="tabler-download" size={16} stroke={3} color="background.default" />}
{...primaryBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</motion.div>
)}
</Stack>
</ContainerWrapper>
);
}
Feature21.propTypes = {
heading: PropTypes.string,
caption: PropTypes.string,
image: PropTypes.any,
features: PropTypes.array,
primaryBtn: PropTypes.any,
secondaryBtn: PropTypes.any
};
@@ -0,0 +1,3 @@
export { default as Feature18 } from './Feature18';
export { default as Feature20 } from './Feature20';
export { default as Feature21 } from './Feature21';