diff --git a/src/custom/CustomCatalog/custom-card.tsx b/src/custom/CustomCatalog/custom-card.tsx
new file mode 100644
index 000000000..c90d78f83
--- /dev/null
+++ b/src/custom/CustomCatalog/custom-card.tsx
@@ -0,0 +1,358 @@
+import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
+import { Avatar, styled } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { Grid } from '../../base';
+import { CloneIcon, CommunityClassIcon, OfficialClassIcon, OpenIcon, ShareIcon } from '../../icons';
+import VerificationClassIcon from '../../icons/ContentClassIcons/VerificationClassIcon';
+import DeploymentsIcon from '../../icons/Deployments/DeploymentsIcon';
+import { DownloadIcon } from '../../icons/Download';
+import { CustomTooltip } from '../CustomTooltip';
+import {
+ CardBack,
+ CardFront,
+ DateText,
+ DateType,
+ DesignAuthorName,
+ DesignCard,
+ DesignDetailsDiv,
+ DesignInnerCard,
+ DesignName,
+ DesignType,
+ MetricsContainerFront,
+ MetricsCount,
+ MetricsDiv,
+ NoTechnologyText,
+ ProfileSection,
+ StyledClassWrapper,
+ StyledInnerClassWrapper,
+ TechnologiesSection,
+ TechnologyText,
+ VersionDiv,
+ VersionText
+} from './style';
+
+export const DesignCardUrl = styled('a')(() => ({
+ textDecoration: 'none'
+}));
+
+interface Pattern {
+ name: string;
+ download_count: number;
+ clone_count: number;
+ view_count: number;
+ deployment_count: number;
+ share_count: number;
+ userData?: {
+ version?: string;
+ avatarUrl?: string;
+ userName?: string;
+ technologies?: string[];
+ updatedAt?: string;
+ };
+ catalog_data?: {
+ content_class?: string;
+ imageURL?: string;
+ compatibility?: string[];
+ };
+ visibility: string;
+ updated_at: Date;
+}
+
+type CatalogCardProps = {
+ pattern: Pattern;
+ patternType: string;
+ cardLink: string;
+ cardHeight: string;
+ cardWidth: string;
+ cardStyles: React.CSSProperties;
+ type: string;
+ version?: string;
+ avatarUrl: string;
+ compatibility: string[];
+ userName: string;
+ technologies: string[];
+ updatedAt: string;
+ shouldFlip?: boolean;
+ cardTechnologies?: boolean;
+ isDetailed?: boolean;
+ cardAvatarUrl?: boolean;
+ date?: boolean;
+ cardVersion?: boolean;
+ UserName?: string;
+ children?: React.ReactNode; // catalogImage
+ TechnologyComponent?: React.ReactNode;
+ basePath?: string; // path of meshmodel img stored
+ getHostUrl?: () => string;
+};
+
+export const ClassToIconMap = {
+ community: ,
+ official: ,
+ verified:
+};
+
+const ClassWrap = ({ catalogClassName }: { catalogClassName: string }) => {
+ if (!catalogClassName) return <>>;
+
+ return (
+
+
+ {catalogClassName}
+
+
+ );
+};
+
+const CustomCatalogCard: React.FC = ({
+ pattern,
+ patternType,
+ cardHeight,
+ cardWidth,
+ cardStyles,
+ cardLink,
+ shouldFlip,
+ isDetailed,
+ cardTechnologies,
+ cardVersion,
+ avatarUrl,
+ UserName,
+ children,
+ basePath,
+ getHostUrl
+}) => {
+ const outerStyles = {
+ height: cardHeight,
+ width: cardWidth,
+ ...cardStyles
+ };
+
+ const technologies = pattern.catalog_data?.compatibility || []; // an array
+ const techlimit = 5;
+ const [availableTechnologies, setAvailableTechnologies] = useState([]);
+ const checkImageUrlValidity = async (url: string, appendHostUrl = true) => {
+ return new Promise((resolve) => {
+ const img = new Image();
+ // Only append host if the URL does not start with "http" or "https"
+ if (appendHostUrl && !url.startsWith('http')) {
+ img.src = (getHostUrl ? getHostUrl() : '') + url;
+ } else {
+ img.src = url;
+ }
+ img.onload = () => {
+ // Check if the image loaded successfully
+ resolve(true);
+ };
+
+ img.onerror = () => {
+ // Handle the case where the image could not be loaded
+ resolve(false);
+ };
+ });
+ };
+
+ const handleImage = async () => {
+ const validSvgPaths = [];
+ for (const technology of technologies) {
+ const svgIconPath = `${basePath}/${technology.toLowerCase()}/icons/color/${technology.toLowerCase()}-color.svg`;
+ const isSvgPathValid = await checkImageUrlValidity(svgIconPath as string);
+ if (isSvgPathValid) {
+ validSvgPaths.push(technology);
+ }
+ }
+
+ setAvailableTechnologies(validSvgPaths);
+ };
+ useEffect(() => {
+ handleImage();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ if (!shouldFlip) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {isDetailed && (
+ <>
+
+ {patternType}
+
+
+ {pattern.name}
+
+ >
+ )}
+
+
+ {children}
+
+
+ {isDetailed && (
+
+
+
+ {pattern.download_count}
+
+
+
+ {pattern.clone_count}
+
+
+
+ {pattern.view_count}
+
+
+
+ {pattern.deployment_count}
+
+
+
+ {pattern.share_count}
+
+
+ )}
+
+ {shouldFlip && (
+
+
+
+ {UserName}
+
+
+
+ {cardTechnologies && (
+
+ Technologies
+
+ {technologies.length < 1 || availableTechnologies.length < 1 ? (
+ No technologies
+ ) : (
+ <>
+ {availableTechnologies.slice(0, techlimit).map((technology, index) => {
+ const svgPath =
+ (getHostUrl ? getHostUrl() : '') +
+ `${basePath}/${technology.toLowerCase()}/icons/color/${technology.toLowerCase()}-color.svg`;
+ return (
+
+
+
+
+
+ );
+ })}
+ {availableTechnologies.length > techlimit && (
+
+ +{availableTechnologies.length - techlimit}
+
+ )}
+ >
+ )}
+
+
+ )}
+
+
+ {isDetailed && (
+
+
+
+
+
+ Updated At
+
+
+ {' '}
+ {new Date(pattern.updated_at.toString().slice(0, 10)).toLocaleDateString(
+ 'en-US',
+ {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ }
+ )}
+
+
+
+
+ )}
+ {cardVersion && (
+
+ v{cardVersion}
+
+ )}
+
+ )}
+
+
+
+ );
+};
+
+export default CustomCatalogCard;
diff --git a/src/custom/CustomCatalog/index.tsx b/src/custom/CustomCatalog/index.tsx
new file mode 100644
index 000000000..09adbc5cc
--- /dev/null
+++ b/src/custom/CustomCatalog/index.tsx
@@ -0,0 +1,3 @@
+import CustomCatalogCard from './custom-card';
+
+export { CustomCatalogCard };
diff --git a/src/custom/CustomCatalog/style.tsx b/src/custom/CustomCatalog/style.tsx
new file mode 100644
index 000000000..863caa418
--- /dev/null
+++ b/src/custom/CustomCatalog/style.tsx
@@ -0,0 +1,397 @@
+import { styled, Typography } from '@mui/material';
+
+type DesignCardProps = {
+ outerStyles: React.CSSProperties;
+ shouldFlip?: boolean;
+ isDetailed?: boolean;
+};
+type DesignCardDivProps = {
+ shouldFlip?: boolean;
+ isDetailed?: boolean;
+};
+type MetricsProps = {
+ isDetailed?: boolean;
+};
+type CatalogProps = {
+ isCatalog?: boolean;
+};
+type StyledInnerClassWrapperProps = {
+ catalogClassName: string;
+};
+export const StyledClassWrapper = styled('div')(() => ({
+ width: '85px',
+ height: '88px',
+ overflow: 'hidden',
+ position: 'absolute',
+ top: '-3px',
+ left: '-3px'
+}));
+
+export const TechnologyText = styled('div')(() => ({
+ color: '#eee',
+ fontSize: '0.875rem',
+ lineHeight: '1.5',
+ fontWeight: '600',
+ borderBottom: '1px solid rgba(231, 239, 243, 0.40)'
+}));
+
+export const NoTechnologyText = styled('div')(() => ({
+ color: '#eee',
+ overflow: 'hidden',
+ fontSize: '14px',
+ lineHeight: '24px',
+ fontWeight: '400',
+ marginTop: '.8rem'
+}));
+
+export const StyledInnerClassWrapper = styled('div')(({
+ catalogClassName
+}) => {
+ const mapToColor: Record = {
+ community: 'rgba(122,132,142,.8)',
+ official: '#EBC017',
+ verified: '#00B39F'
+ };
+ return {
+ font: 'bold 10px sans-serif',
+ WebkitTransform: 'rotate(-45deg)',
+ textAlign: 'center',
+ transform: 'rotate(-45deg)',
+ position: 'relative',
+ padding: '4px 0',
+ top: '15px',
+ left: '-30px',
+ width: '120px',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: mapToColor[catalogClassName],
+ color: '#fff'
+ };
+});
+
+export const DesignCard = styled('div')(
+ ({ shouldFlip, isDetailed, outerStyles, theme }) => ({
+ position: 'relative',
+ borderRadius: '1rem',
+ textAlign: 'center',
+ transformStyle: 'preserve-3d',
+ display: 'block',
+ perspective: '1000px',
+ transition: 'all .9s ease-out',
+ ...(shouldFlip && {
+ '&:hover': {
+ cursor: 'pointer',
+ '& .innerCard': {
+ transform: 'rotateY(180deg)'
+ }
+ }
+ }),
+ ...(isDetailed && {
+ [theme.breakpoints.down('lg')]: {
+ height: '18.75rem'
+ }
+ }),
+ ...outerStyles
+ })
+);
+
+export const DesignInnerCard = styled('div')(({ shouldFlip, isDetailed }) => ({
+ position: 'relative',
+ width: '100%',
+ height: '100%',
+ textAlign: 'center',
+ transition: 'transform 0.6s',
+ ...(shouldFlip && {
+ transformOrigin: '50% 50%',
+ transformStyle: 'preserve-3d'
+ }),
+ ...(isDetailed && {
+ boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)',
+ borderRadius: '0.9375rem'
+ })
+}));
+
+export const DesignType = styled('span')(({ theme }) => ({
+ position: 'absolute',
+ top: '0',
+ right: '0',
+ minWidth: '3rem',
+ padding: '0 0.75rem',
+ fontSize: '0.875rem',
+ textTransform: 'capitalize',
+ background: theme.palette.background.brand?.default,
+ color: theme.palette.text.inverse,
+ borderRadius: '0 1rem 0 2rem'
+}));
+export const MetricsCount = styled('p')(({ theme }) => ({
+ fontSize: '1rem',
+ textTransform: 'capitalize',
+ margin: '0rem',
+ lineHeight: '1.5',
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+ fontWeight: '600'
+}));
+export const DesignName = styled(Typography)(({ theme }) => ({
+ fontWeight: 'bold',
+ textTransform: 'capitalize',
+ color: theme.palette.text.default,
+ fontSize: '1.125rem',
+ marginTop: '2rem',
+ padding: '0rem 1rem', // "0rem 1.5rem"
+ position: 'relative',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ textOverflow: 'ellipsis',
+ textAlign: 'center',
+ width: '100%'
+}));
+export const MetricsContainerFront = styled('div')(({ isDetailed }) => ({
+ display: 'flex',
+ justifyContent: 'space-around',
+ // borderTop: "0.851px solid #C9DBE3",
+ fontSize: '0.2rem',
+ color: 'rgba(26, 26, 26, .8)',
+ // margin: "-0.8rem 0.7rem 0",
+ padding: '0.9rem 0.1rem',
+ background: '#E7EFF3',
+ ...(isDetailed && {
+ position: 'absolute',
+ bottom: '0px'
+ }),
+ ...(!isDetailed && {
+ marginTop: '1.2rem'
+ }),
+ borderRadius: '0 0 0.9375rem 0.9375rem',
+ width: '100%'
+}));
+
+export const MetricsDiv = styled('div')(() => ({
+ display: 'flex',
+ alignItems: 'center',
+ gap: '4px',
+ fontSize: '0.2rem',
+ color: 'rgba(26, 26, 26, .8)',
+ margin: '0rem',
+ padding: '0.1rem'
+}));
+export const DesignDetailsDiv = styled('div')(() => ({
+ height: 'max-content',
+ display: 'flex',
+ marginTop: '-1rem',
+ flexDirection: 'column',
+ padding: '0rem 1rem',
+ justifyContent: 'start',
+ alignItems: 'start',
+ ['@media (max-width:1200px)']: {
+ height: 'max-content'
+ }
+}));
+
+export const ImageWrapper = styled('div')(({ theme }) => ({
+ background: theme.palette.mode === 'light' ? 'rgba(231, 239, 243, 0.40)' : '#212121',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: '0.5rem',
+ width: '100%',
+ borderRadius: '0.5rem'
+}));
+
+export const VersionTag = styled('div')(({ theme }) => ({
+ display: 'inline-block',
+ backgroundColor: theme.palette.background.supplementary,
+ color: theme.palette.text.constant?.white,
+ borderRadius: '4px',
+ fontSize: '0.75rem',
+ fontWeight: 'bold',
+ margin: '5px 0',
+ padding: '2px 5px',
+ maxWidth: 'fit-content'
+}));
+
+export const VersionDiv = styled('div')(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ gap: '4px',
+ fontSize: '0.75',
+ color: theme.palette.text.constant?.white,
+ position: 'absolute',
+ bottom: '16px',
+ left: '16px',
+ borderRadius: '4px',
+ background: theme.palette.background.supplementary,
+ justifyContent: 'center'
+}));
+
+export const VersionText = styled('p')(({ theme }) => ({
+ fontSize: '0.75rem',
+ margin: '0',
+ padding: '0.25rem .5rem',
+ lineHeight: '1.5',
+ textTransform: 'lowercase',
+ fontWeight: '600',
+ borderRadius: '4.05px',
+ color: theme.palette.text.constant?.white
+}));
+
+export const FlipCard = styled('div')(() => ({
+ perspective: '1000px',
+ '&:hover .flipper': {
+ transform: 'rotateY(-180deg)'
+ }
+}));
+
+export const Flipper = styled('div')(() => ({
+ transition: '0.6s',
+ transformStyle: 'preserve-3d',
+ position: 'relative'
+}));
+
+export const Face = styled('div')(() => ({
+ backfaceVisibility: 'hidden',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%'
+}));
+
+export const FrontFace = styled(Face)(() => ({
+ zIndex: 2,
+ transform: 'rotateY(0deg)'
+}));
+
+export const BackFace = styled('div')(() => ({
+ transform: 'rotateY(-180deg)',
+ color: '#fff',
+ display: 'inline-flex',
+ flexDirection: 'column',
+ padding: '16px',
+ height: '100%',
+ width: '100%',
+ position: 'relative',
+ bottom: 0,
+ left: 0,
+ backfaceVisibility: 'hidden'
+}));
+
+export const BackFaceContent = styled('div')(({ theme }) => ({
+ position: 'absolute',
+ background: `linear-gradient(to bottom right, black 40%, ${theme.palette.background.brand?.default})`,
+ width: '100%',
+ top: 0,
+ left: 0,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'left',
+ padding: '16px',
+ boxShadow: `2px 2px 3px 0px black`,
+ borderRadius: '1rem'
+}));
+
+export const ProfileSection = styled('div')({
+ height: 'max-content',
+ display: 'flex',
+ marginTop: '1.2rem',
+ flexDirection: 'row',
+ padding: '0rem 1rem',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ ['@media (max-width:1200px)']: {
+ height: 'max-content'
+ }
+});
+
+export const TechnologiesSection = styled('div')(() => ({
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ width: '100%',
+ gap: '1rem',
+ alignItems: 'flex-start',
+ background: 'rgba(231, 239, 243, 0.40)',
+ borderRadius: '0.25rem',
+ padding: '0.5rem 1rem',
+ alignSelf: 'stretch'
+}));
+
+export const UpdatedSection = styled('div')({
+ display: 'flex',
+ alignItems: 'center',
+ color: '#fff',
+ margin: '20px 0'
+});
+
+export const CardBack = styled('div')(({ isCatalog }) => ({
+ boxShadow: `2px 2px 3px 0px rgba(26, 26, 26, 1)`,
+ position: 'absolute',
+ width: '100%',
+ height: '100%',
+ WebkitBackfaceVisibility: 'hidden',
+ borderRadius: '0.9375rem',
+ backfaceVisibility: 'hidden',
+ color: 'white',
+ transform: 'rotateY(180deg)',
+ ...(isCatalog && {
+ background:
+ 'linear-gradient(335deg, rgba(0, 179, 159, 0.80) -13.6%, rgba(0, 0, 0, 0.68) 66.8%), radial-gradient(3970.04% 147.22% at 47.5% 100%, #000 0%, #395357 100%)'
+ }),
+ ...(!isCatalog && {
+ background: 'linear-gradient(250deg, #477e96 0%, #00b39f 35%, rgb(60, 73, 79) 100%)'
+ })
+}));
+
+export const CardFront = styled('div')(({ shouldFlip, isDetailed, theme }) => ({
+ ...(shouldFlip && {
+ position: 'absolute',
+ boxShadow: `2px 2px 3px 0px ${theme.palette.background.brand?.default}`,
+ background: `linear-gradient(to left bottom, #EBEFF1, #f4f5f7, #f7f7f9, white, white, white, white, white, white, #f7f7f9, #f4f5f7, #EBEFF1);`
+ }),
+ ...(isDetailed && {
+ boxShadow: `2px 2px 3px 0px ${theme.palette.background.brand?.default}`,
+ background: `linear-gradient(to left bottom, #EBEFF1, #f4f5f7, #f7f7f9, white, white, white, white, white, white, #f7f7f9, #f4f5f7, #EBEFF1);`
+ }),
+ width: '100%',
+ height: '100%',
+ WebkitBackfaceVisibility: 'hidden',
+ borderRadius: '0.9375rem',
+ backfaceVisibility: 'hidden'
+}));
+
+export const DateText = styled('div')(() => ({
+ fontSize: '0.875rem',
+ textTransform: 'capitalize',
+ color: '#eee',
+ margin: '0rem',
+ padding: '0.1rem',
+ fontWeight: '400',
+ lineHeight: '1.5'
+}));
+
+export const DateType = styled('p')(() => ({
+ fontSize: '0.876rem',
+ margin: '0rem',
+ lineHeight: '1.5',
+ fontWeight: '400',
+ color: '#eee'
+}));
+
+export const DesignAuthorName = styled('div')(() => ({
+ height: 'max-content',
+ display: 'flex',
+ margin: '0',
+ flexDirection: 'column',
+ padding: '0rem 1rem',
+ justifyContent: 'start',
+ alignItems: 'start',
+ fontWeight: '400',
+ textAlign: 'right',
+ color: '#E7EFF3',
+ textTransform: 'capitalize',
+ ['@media (max-width:1200px)']: {
+ height: 'max-content'
+ }
+}));
diff --git a/src/custom/StyledSearchBar/StyledSearchBar.tsx b/src/custom/StyledSearchBar/StyledSearchBar.tsx
index d543bf520..2e2799bdb 100644
--- a/src/custom/StyledSearchBar/StyledSearchBar.tsx
+++ b/src/custom/StyledSearchBar/StyledSearchBar.tsx
@@ -1,51 +1,58 @@
+import { SxProps, Theme, useTheme } from '@mui/material';
import React from 'react';
-import { Box } from '../../base/Box';
-import { InputAdornment } from '../../base/Input';
-import { TextField } from '../../base/TextField';
+import { InputAdornment } from '../../base';
+import { SearchIcon } from '../../icons';
+import { InputAdornmentEnd, StyledSearchInput } from './style';
interface SearchBarProps {
onChange?: (event: React.ChangeEvent) => void;
value?: string;
width?: string;
- label: string;
+ label?: string;
+ placeholder?: string;
+ sx?: SxProps;
endAdornment?: React.ReactNode;
}
+/**
+ * StyledSearchBar component renders a search input field with customizable properties.
+ *
+ * @param {Object} props - The component props.
+ * @param {function} [props.onChange] - Function to handle the change event when the search input value changes.
+ * @param {string} [props.value] - The current value of the search input.
+ * @param {string} [props.label] - The label for the search input.
+ * @param {string} [props.placeholder] - The placeholder text for the search input.
+ * @param {Object} [props.sx] - The style object for the search input.
+ * @param {React.ReactNode} [props.endAdornment] - The element to display at the end of the search input.
+ *
+ * @returns {JSX.Element} The rendered StyledSearchBar component.
+ */
function StyledSearchBar({
onChange,
value,
- width,
label,
- endAdornment,
- ...props
+ sx,
+ placeholder,
+ endAdornment
}: SearchBarProps): JSX.Element {
+ const theme = useTheme();
+
return (
-
- :not(style)': { width }
- }}
- {...props}
- >
- {endAdornment}
- }}
- />
-
-
+
+
+
+ }
+ endAdornment={{endAdornment}}
+ />
);
}
diff --git a/src/custom/StyledSearchBar/style.tsx b/src/custom/StyledSearchBar/style.tsx
new file mode 100644
index 000000000..92ce4187b
--- /dev/null
+++ b/src/custom/StyledSearchBar/style.tsx
@@ -0,0 +1,21 @@
+import { styled } from '@mui/material';
+import { InputAdornment, OutlinedInput } from '../../base';
+
+export const StyledSearchInput = styled(OutlinedInput)(({ style }) => ({
+ width: '100%',
+ '@media (max-width: 590px)': {
+ marginLeft: '0.25rem',
+ paddingLeft: '0.25rem'
+ },
+ display: 'flex',
+ ...style
+}));
+
+export const InputAdornmentEnd = styled(InputAdornment)(({ theme }) => ({
+ borderLeft: `1px solid ${theme.palette.border.normal}`,
+ height: '30px',
+ paddingLeft: '10px',
+ '@media (max-width: 590px)': {
+ paddingLeft: '0px'
+ }
+}));
diff --git a/src/custom/index.tsx b/src/custom/index.tsx
index 94231e4fe..9a9ddd655 100644
--- a/src/custom/index.tsx
+++ b/src/custom/index.tsx
@@ -3,6 +3,7 @@ import { BookmarkNotification } from './BookmarkNotification';
import CatalogFilter, { CatalogFilterProps } from './CatalogFilter/CatalogFilter';
import { ChapterCard } from './ChapterCard';
import { ConnectionChip } from './ConnectionChip';
+import { CustomCatalogCard } from './CustomCatalog';
import {
CustomColumn,
CustomColumnVisibilityControl,
@@ -60,6 +61,7 @@ export {
CatalogFilter,
ChapterCard,
ConnectionChip,
+ CustomCatalogCard,
CustomColumnVisibilityControl,
CustomDialog,
CustomImage,