diff --git a/package-lock.json b/package-lock.json index bee091161..0034a3902 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.14.11", "dependencies": { "js-yaml": "^4.1.0", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "react-share": "^5.1.0" }, "devDependencies": { "@commitlint/cli": "^17.7.2", @@ -4411,6 +4412,12 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -8893,6 +8900,29 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", + "integrity": "sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==", + "dependencies": { + "debug": "^2.1.3" + } + }, + "node_modules/jsonp/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/jsonp/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -11623,6 +11653,19 @@ } } }, + "node_modules/react-share": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-share/-/react-share-5.1.0.tgz", + "integrity": "sha512-OvyfMtj/0UzH1wi90OdHhZVJ6WUC/+IeWvBwppeZozwIGyAjQgyR0QXlHOrxVHVECqnGvcpBaFTXVrqouTieaw==", + "license": "MIT", + "dependencies": { + "classnames": "^2.3.2", + "jsonp": "^0.2.1" + }, + "peerDependencies": { + "react": "^17 || ^18" + } + }, "node_modules/react-sortable-tree-patch-react-17": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-sortable-tree-patch-react-17/-/react-sortable-tree-patch-react-17-2.9.0.tgz", @@ -17258,6 +17301,11 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -20386,6 +20434,29 @@ "universalify": "^2.0.0" } }, + "jsonp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", + "integrity": "sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==", + "requires": { + "debug": "^2.1.3" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -22192,6 +22263,15 @@ "use-sync-external-store": "^1.0.0" } }, + "react-share": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-share/-/react-share-5.1.0.tgz", + "integrity": "sha512-OvyfMtj/0UzH1wi90OdHhZVJ6WUC/+IeWvBwppeZozwIGyAjQgyR0QXlHOrxVHVECqnGvcpBaFTXVrqouTieaw==", + "requires": { + "classnames": "^2.3.2", + "jsonp": "^0.2.1" + } + }, "react-sortable-tree-patch-react-17": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/react-sortable-tree-patch-react-17/-/react-sortable-tree-patch-react-17-2.9.0.tgz", diff --git a/package.json b/package.json index 55ac382ab..04cee394a 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ }, "dependencies": { "js-yaml": "^4.1.0", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "react-share": "^5.1.0" } } diff --git a/src/custom/CatalogDetail/ActionButton.tsx b/src/custom/CatalogDetail/ActionButton.tsx new file mode 100644 index 000000000..e7a9806d7 --- /dev/null +++ b/src/custom/CatalogDetail/ActionButton.tsx @@ -0,0 +1,115 @@ +import _ from 'lodash'; +import React from 'react'; +import { CircularProgress } from '../../base'; +import { CopyIcon, KanvasIcon } from '../../icons'; +import Download from '../../icons/Download/Download'; +import { charcoal } from '../../theme'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import { downloadFilter, downloadYaml, slugify } from './helper'; +import { ActionButton, LinkUrl, StyledActionWrapper } from './style'; +import { RESOURCE_TYPES } from './types'; + +interface ActionButtonsProps { + actionItems: boolean; + details: Pattern; + type: string; + cardId: string; + isCloneLoading: boolean; + handleClone: (name: string, id: string) => void; + mode: string; + isCloneDisabled: boolean; +} + +const ActionButtons: React.FC = ({ + actionItems, + details, + type, + cardId, + isCloneLoading, + handleClone, + mode, + isCloneDisabled +}) => { + const cleanedType = type.replace('my-', '').replace(/s$/, ''); + const resourcePlaygroundType = Object.values({ + ..._.omit(RESOURCE_TYPES, ['FILTERS']), + CATALOG: 'catalog' + }).includes(cleanedType) + ? cleanedType + : 'design'; + return ( + + {actionItems && ( +
+ + cleanedType === RESOURCE_TYPES.FILTERS + ? downloadFilter(details.id, details.name) + : downloadYaml(details.pattern_file, details.name) + } + > + + Download + + + {cleanedType !== RESOURCE_TYPES.FILTERS && ( + handleClone(details?.name, details?.id)} + disabled={isCloneDisabled} + > + {isCloneLoading ? ( + + ) : ( + <> + + Clone + + )} + + )} +
+ )} + + + + Open in Playground + + +
+ ); +}; + +export default ActionButtons; diff --git a/src/custom/CatalogDetail/CaveatsSection.tsx b/src/custom/CatalogDetail/CaveatsSection.tsx new file mode 100644 index 000000000..489be4133 --- /dev/null +++ b/src/custom/CatalogDetail/CaveatsSection.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import { RenderMarkdown } from '../Markdown'; +import { ContentDetailsText } from '../Typography'; +import { CaveatsContainer, ContentHeading } from './style'; + +interface CaveatsSectionProps { + details: Pattern; +} + +const CaveatsSection: React.FC = ({ details }) => { + return ( + + +

CAVEATS AND CONSIDERATIONS

+
+ {details?.catalog_data?.pattern_caveats ? ( + + + + ) : ( + No caveats registered + )} +
+ ); +}; + +export default CaveatsSection; diff --git a/src/custom/CatalogDetail/ChallengesSection.tsx b/src/custom/CatalogDetail/ChallengesSection.tsx new file mode 100644 index 000000000..77028a2f0 --- /dev/null +++ b/src/custom/CatalogDetail/ChallengesSection.tsx @@ -0,0 +1,74 @@ +import { useEffect, useState } from 'react'; +import { Link, ListItemIcon } from '../../base'; +import { ChallengesIcon } from '../../icons'; +import { useTheme } from '../../theme'; +import CollapsibleSection from './CollapsibleSection'; +import { slugify } from './helper'; +import { LabelDiv } from './style'; +import { FilteredAcademyData } from './types'; + +interface ChallengesSectionProps { + filteredAcademyData: FilteredAcademyData; +} + +const ChallengesSection: React.FC = ({ filteredAcademyData }) => { + const theme = useTheme(); + const [openChallenges, setOpenChallenges] = useState(false); + const [autoUpdate, setAutoUpdate] = useState(true); + + useEffect(() => { + if (autoUpdate) { + setOpenChallenges((filteredAcademyData?.['challenges'] ?? []).length > 0); + } + }, [filteredAcademyData, autoUpdate]); + + const toggleOpenChallenges = () => { + setOpenChallenges((prev) => !prev); + setAutoUpdate(false); + }; + + const renderChallengeItem = (item: string, index: number) => ( + + + + + + {item} + + + ); + + return ( + <> +
+ + + ); +}; + +export default ChallengesSection; diff --git a/src/custom/CatalogDetail/CollapsibleSection.tsx b/src/custom/CatalogDetail/CollapsibleSection.tsx new file mode 100644 index 000000000..49c417947 --- /dev/null +++ b/src/custom/CatalogDetail/CollapsibleSection.tsx @@ -0,0 +1,61 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { Collapse, List, ListItemText } from '../../base'; +import { InfoTooltip } from '../CustomTooltip'; +import { SideContainer, SideTitleButton } from './style'; + +interface CollapsibleSectionProps { + title: string; + isOpen: boolean; + onToggle: () => void; + items: any[]; + renderItem: (item: any, index: number) => React.ReactNode; + emptyState: string; + tooltip: string; +} + +const CollapsibleSection: React.FC = ({ + title, + isOpen, + onToggle, + items, + renderItem, + emptyState, + tooltip +}) => { + return ( + + + + + {isOpen ? : } + + + {items && items.length > 0 ? ( + + {items?.map(renderItem)} + + ) : ( + + )} + + + ); +}; + +export default CollapsibleSection; diff --git a/src/custom/CatalogDetail/ContentClassInfo.tsx b/src/custom/CatalogDetail/ContentClassInfo.tsx new file mode 100644 index 000000000..effd24378 --- /dev/null +++ b/src/custom/CatalogDetail/ContentClassInfo.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { Box } from '../../base'; +import { CommunityClassIcon, OfficialClassIcon, VerificationClassIcon } from '../../icons'; +import { KEPPEL, useTheme } from '../../theme'; +import { InfoTooltip } from '../CustomTooltip'; +import { ContentDetailsPoints, ContentDetailsText } from '../Typography'; +import { formatToTitleCase } from './helper'; +import { Class, ContentClassType } from './types'; + +interface ContentClassInfoProps { + contentClass: string; + classes: Class[]; +} + +const ContentClassInfo: React.FC = ({ contentClass, classes }) => { + const _classDescription = (className: string): string | undefined => { + const classObj = classes && classes.find((classObj) => classObj.class === className); + return classObj?.description; + }; + + const theme = useTheme(); + + const CONTENT_CLASS: ContentClassType = { + community: { + icon: CommunityClassIcon, + color: theme.palette.icon.secondary + }, + official: { + icon: OfficialClassIcon, + color: '#EBC017' + }, + verified: { + icon: VerificationClassIcon, + color: theme.palette.primary.brand?.default || KEPPEL + } + } as const; + + const ClassIcon: React.FC<{ className: string }> = ({ className }) => { + const Icon = CONTENT_CLASS[className]?.icon; + const fill = CONTENT_CLASS[className]?.color; + return Icon ? : null; + }; + + return ( +
+ + + CLASS + + + + + + {formatToTitleCase(contentClass)} + +
+ ); +}; + +export default ContentClassInfo; diff --git a/src/custom/CatalogDetail/LearningSection.tsx b/src/custom/CatalogDetail/LearningSection.tsx new file mode 100644 index 000000000..d6e308075 --- /dev/null +++ b/src/custom/CatalogDetail/LearningSection.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react'; +import { Link, ListItemIcon } from '../../base'; +import { LearningIcon } from '../../icons'; +import { useTheme } from '../../theme'; +import CollapsibleSection from './CollapsibleSection'; +import { slugify } from './helper'; +import { LabelDiv } from './style'; +import { FilteredAcademyData } from './types'; + +interface LearningSectionProps { + filteredAcademyData: FilteredAcademyData; +} + +const LearningSection: React.FC = ({ filteredAcademyData }) => { + const theme = useTheme(); + const [openLearning, setOpenLearning] = useState(false); + const [autoUpdate, setAutoUpdate] = useState(true); + + useEffect(() => { + if (autoUpdate) { + setOpenLearning(Boolean((filteredAcademyData?.['learning-path'] ?? []).length > 0)); + } + }, [filteredAcademyData, autoUpdate]); + + const toggleOpenLearning = (): void => { + setOpenLearning((prev) => !prev); + setAutoUpdate(false); + }; + + const renderLearningItem = (item: string, index: number) => ( + + + + + + {item} + + + ); + + return ( + <> +
+ + + ); +}; + +export default LearningSection; diff --git a/src/custom/CatalogDetail/LeftPanel.tsx b/src/custom/CatalogDetail/LeftPanel.tsx new file mode 100644 index 000000000..2bb7fbb37 --- /dev/null +++ b/src/custom/CatalogDetail/LeftPanel.tsx @@ -0,0 +1,94 @@ +import { useTheme } from '../../theme'; +import { CatalogCardDesignLogo, CustomCatalogCard } from '../CustomCatalog'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import ActionButtons from './ActionButton'; +import ChallengesSection from './ChallengesSection'; +import LearningSection from './LearningSection'; +import TechnologySection from './TechnologySection'; +import { FilteredAcademyData } from './types'; + +interface LeftPanelProps { + details: Pattern; + type: string; + cardId?: string; + actionItems?: boolean; + isCloneLoading: boolean; + handleClone: (name: string, id: string) => void; + showTechnologies?: boolean; + mode: string; + filteredAcademyData: FilteredAcademyData; + isCloneDisabled: boolean; + technologySVGPath: string; + technologySVGSubpath: string; + fontFamily?: string; +} + +const LeftPanel: React.FC = ({ + details, + type, + cardId = details.id, + actionItems = true, + isCloneLoading, + handleClone, + showTechnologies = true, + mode, + filteredAcademyData, + isCloneDisabled, + technologySVGPath, + technologySVGSubpath, + fontFamily +}) => { + const theme = useTheme(); + + return ( +
+ + + + + {showTechnologies && ( + + )} + + +
+ ); +}; + +export default LeftPanel; diff --git a/src/custom/CatalogDetail/MetricsDisplay.tsx b/src/custom/CatalogDetail/MetricsDisplay.tsx new file mode 100644 index 000000000..85cf7ec42 --- /dev/null +++ b/src/custom/CatalogDetail/MetricsDisplay.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { Grid } from '../../base'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import { MetricsContainer, MetricsData, MetricsSection, MetricsType } from './style'; + +interface MetricItem { + label: string; + value: number; +} + +interface MetricsDisplayProps { + details: Pattern; +} + +const MetricsDisplay: React.FC = ({ details }) => { + const metrics: MetricItem[] = [ + { label: 'Opens', value: details.view_count }, + { label: 'Downloads', value: details.download_count }, + { label: 'Deploys', value: details.deployment_count }, + { label: 'Clones', value: details.clone_count }, + { label: 'Shares', value: details.share_count } + ]; + + return ( + + + {metrics.map((metric) => ( + + {metric.value} + {metric.label} + + ))} + + + ); +}; + +export default MetricsDisplay; diff --git a/src/custom/CatalogDetail/OverviewSection.tsx b/src/custom/CatalogDetail/OverviewSection.tsx new file mode 100644 index 000000000..4d842bde7 --- /dev/null +++ b/src/custom/CatalogDetail/OverviewSection.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { Grid } from '../../base'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import ContentClassInfo from './ContentClassInfo'; +import MetricsDisplay from './MetricsDisplay'; +import PatternInfo from './PatternInfo'; +import SocialSharePopper from './SocialSharePopper'; +import UserInfo from './UserInfo'; +import { ContentRow, DesignHeading, OverviewContainer } from './style'; +import { Class } from './types'; + +interface OverviewSectionProps { + details: Pattern; + type: string; + cardId: string; + title: string; + getUrl: (type: string, id: string) => string; + showContentDetails: boolean; + ViewsComponent?: React.ReactNode; + showVersion: boolean; + classes: Class[]; + handleCopyUrl: (type: string, name: string, id: string) => void; + fontFamily?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + userProfile?: any; +} + +const OverviewSection: React.FC = ({ + details, + type, + cardId, + title, + getUrl, + showContentDetails, + ViewsComponent, + showVersion, + classes, + handleCopyUrl, + fontFamily, + userProfile +}) => { + return ( + +
+ {details?.name} + +
+ + + + + {details?.catalog_data?.content_class && ( + + + + )} + + + + + + {showContentDetails ? ( + +

WHAT DOES THIS DESIGN DO?

+ {details?.catalog_data?.pattern_info ? ( + + ) : ( +
No description available
+ )} +
+ ) : ( + ViewsComponent + )} +
+ {!(type === 'view' || type === 'filter') && } +
+
+ ); +}; + +export default OverviewSection; diff --git a/src/custom/CatalogDetail/PatternInfo.tsx b/src/custom/CatalogDetail/PatternInfo.tsx new file mode 100644 index 000000000..6371ddcd7 --- /dev/null +++ b/src/custom/CatalogDetail/PatternInfo.tsx @@ -0,0 +1,45 @@ +import React, { useState } from 'react'; +import { RenderMarkdown } from '../Markdown'; +import { ShowToggleBtn } from './style'; + +interface PatternInfoProps { + text: string; + redirect?: boolean; + id?: string; +} + +const PatternInfo: React.FC = ({ text, redirect, id }) => { + const [isExpanded, setIsExpanded] = useState(false); + + const toggleExpand = () => { + setIsExpanded(!isExpanded); + }; + + const handleRedirect = () => { + if (id) { + window.location.href = `/catalog/content/design/${id}`; + } + }; + + return ( +
+ {isExpanded ? ( +
+ + show less +
+ ) : ( +
+ + {text.length > (redirect ? 400 : 500) && ( + + ...show more + + )} +
+ )} +
+ ); +}; + +export default PatternInfo; diff --git a/src/custom/CatalogDetail/RelatedDesigns.tsx b/src/custom/CatalogDetail/RelatedDesigns.tsx new file mode 100644 index 000000000..fdf8cedf3 --- /dev/null +++ b/src/custom/CatalogDetail/RelatedDesigns.tsx @@ -0,0 +1,66 @@ +import { CatalogCardDesignLogo } from '../CustomCatalog'; +import CustomCatalogCard, { Pattern } from '../CustomCatalog/CustomCard'; +import { formatToTitleCase } from './helper'; +import { AdditionalContainer, ContentHeading, DesignCardContainer } from './style'; +import { UserProfile } from './types'; + +export interface PatternsPerUser { + patterns: Pattern[]; +} + +interface RelatedDesignsProps { + details: Pattern; + type: string; + patternsPerUser: PatternsPerUser; + onSuggestedPatternClick: (pattern: Pattern) => void; + userProfile?: UserProfile; +} + +const RelatedDesigns: React.FC = ({ + details, + type, + patternsPerUser, + onSuggestedPatternClick, + userProfile +}) => { + const filteredPatternsPerUser = patternsPerUser?.patterns?.filter( + (pattern) => pattern.id !== details.id + ); + + if (!filteredPatternsPerUser?.length) return null; + + return ( + + +

+ Other published design by {formatToTitleCase(userProfile?.first_name ?? '')} +

+
+ + {filteredPatternsPerUser.map((pattern, index) => ( + onSuggestedPatternClick(pattern)} + UserName={`${userProfile?.first_name ?? ''} ${userProfile?.last_name ?? ''}`} + avatarUrl={userProfile?.avatar_url} + basePath="/static/img/meshmodels" + subBasePath="color" + cardTechnologies={true} + > + + + ))} + +
+ ); +}; + +export default RelatedDesigns; diff --git a/src/custom/CatalogDetail/RightPanel.tsx b/src/custom/CatalogDetail/RightPanel.tsx new file mode 100644 index 000000000..0c3c8a4d5 --- /dev/null +++ b/src/custom/CatalogDetail/RightPanel.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import CaveatsSection from './CaveatsSection'; +import OverviewSection from './OverviewSection'; +import RelatedDesigns, { PatternsPerUser } from './RelatedDesigns'; +import { Class } from './types'; + +interface RightPanelProps { + details: Pattern; + type: string; + cardId?: string; + title: string; + getUrl: (type: string, id: string) => string; + showContentDetails: boolean; + ViewsComponent?: React.ReactNode; + showVersion: boolean; + showCaveats: boolean; + classes: Class[]; + patternsPerUser: PatternsPerUser; + handleCopyUrl: (type: string, name: string, id: string) => void; + onSuggestedPatternClick: (pattern: Pattern) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + useGetUserProfileByIdQuery: any; + fontFamily?: string; +} + +const RightPanel: React.FC = ({ + details, + type, + cardId = details.id, + title, + getUrl, + showContentDetails, + ViewsComponent, + showVersion, + showCaveats, + classes, + patternsPerUser, + onSuggestedPatternClick, + handleCopyUrl, + fontFamily, + useGetUserProfileByIdQuery +}) => { + const cleanedType = type.replace('my-', '').replace(/s$/, ''); + const { data: userProfile } = useGetUserProfileByIdQuery({ + id: details.user_id + }); + + return ( +
+ + {showCaveats && } + +
+ ); +}; + +export default RightPanel; diff --git a/src/custom/CatalogDetail/SocialSharePopper.tsx b/src/custom/CatalogDetail/SocialSharePopper.tsx new file mode 100644 index 000000000..3c24b32d8 --- /dev/null +++ b/src/custom/CatalogDetail/SocialSharePopper.tsx @@ -0,0 +1,125 @@ +import { Box, IconButton, Menu, MenuItem, Tooltip } from '@mui/material'; +import React, { useState } from 'react'; +import { FacebookShareButton, LinkedinShareButton, TwitterShareButton } from 'react-share'; +import { ChainIcon, FacebookIcon, LinkedinIcon, ShareIcon, TwitterIcon } from '../../icons'; +import { useTheme } from '../../theme'; +import { Pattern } from '../CustomCatalog/CustomCard'; +import { ErrorBoundary } from '../ErrorBoundary'; +import { CopyShareIconWrapper, VisibilityChip } from './style'; + +interface SocialSharePopperProps { + details: Pattern; + type: string; + cardId: string; + title: string; + getUrl: (type: string, id: string) => string; + handleCopyUrl: (type: string, name: string, id: string) => void; +} + +const SocialSharePopper: React.FC = ({ + details, + type, + cardId, + title, + getUrl, + handleCopyUrl +}) => { + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const cleanedType = type.replace('my-', '').replace(/s$/, ''); + + return ( + + + + {details?.visibility} + + + {details?.visibility !== 'private' && ( + + handleCopyUrl(cleanedType, details?.name, details?.id)} + > + + + + )} + + {(details?.visibility === 'published' || details?.visibility === 'public') && ( + <> + + + + + + + + + + + + + + + + + + + + + + + )} + + + ); +}; + +export default SocialSharePopper; diff --git a/src/custom/CatalogDetail/TechnologySection.tsx b/src/custom/CatalogDetail/TechnologySection.tsx new file mode 100644 index 000000000..4018c6255 --- /dev/null +++ b/src/custom/CatalogDetail/TechnologySection.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import { ListItemIcon } from '../../base'; +import { useTheme } from '../../theme'; +import CollapsibleSection from './CollapsibleSection'; +import { LabelDiv } from './style'; + +interface TechnologySectionProps { + technologies: string[]; + technologySVGPath: string; + technologySVGSubpath: string; +} + +const TechnologySection: React.FC = ({ + technologySVGPath, + technologySVGSubpath, + technologies +}) => { + const [openTechnologies, setOpenTechnologies] = useState(true); + const [validTechnologies, setValidTechnologies] = useState([]); + const theme = useTheme(); + + useEffect(() => { + // Function to check if SVG exists + const validateTechnologies = async () => { + const validTechs = await Promise.all( + technologies.map(async (tech) => { + const svg_path = `/${technologySVGPath}/${tech.toLowerCase()}/${technologySVGSubpath}/${tech.toLowerCase()}-color.svg`; + try { + const response = await fetch(svg_path); + return response.ok ? tech : null; + } catch { + return null; + } + }) + ); + setValidTechnologies(validTechs.filter((tech): tech is string => tech !== null)); + }; + + validateTechnologies(); + }, [technologies, technologySVGPath, technologySVGSubpath]); + + const renderTechnologyItem = (item: string, index: number) => { + const svg_path = `${technologySVGPath}/${item.toLowerCase()}/${technologySVGSubpath}/${item.toLowerCase()}-color.svg`; + return ( + + + {`${item} + + {item} + + ); + }; + + return ( + <> +
+ setOpenTechnologies((prev) => !prev)} + items={validTechnologies} + renderItem={renderTechnologyItem} + emptyState={'No technologies assigned to this design'} + tooltip={'Technologies used in this design'} + /> + + ); +}; + +export default TechnologySection; diff --git a/src/custom/CatalogDetail/UserInfo.tsx b/src/custom/CatalogDetail/UserInfo.tsx new file mode 100644 index 000000000..86029f960 --- /dev/null +++ b/src/custom/CatalogDetail/UserInfo.tsx @@ -0,0 +1,48 @@ +import { Pattern } from '../CustomCatalog/CustomCard'; +import { getVersion } from '../CustomCatalog/Helper'; +import { formatDate } from './helper'; +import { ContentDetailsPoints, ContentDetailsText, ContentRow, RedirectLink } from './style'; +import { UserProfile } from './types'; + +interface UserInfoProps { + details: Pattern; + showVersion?: boolean; + userProfile?: UserProfile; +} + +const UserInfo: React.FC = ({ details, showVersion = true, userProfile }) => { + return ( + <> + + CREATED BY + + + + {userProfile?.first_name} {userProfile?.last_name} + + + + + + CREATED AT + {formatDate(details?.created_at)} + + + UPDATED AT + {formatDate(details?.updated_at)} + + {showVersion && ( + + VERSION + {getVersion(details)} + + )} + + ); +}; + +export default UserInfo; diff --git a/src/custom/CatalogDetail/helper.ts b/src/custom/CatalogDetail/helper.ts new file mode 100644 index 000000000..2d6601f77 --- /dev/null +++ b/src/custom/CatalogDetail/helper.ts @@ -0,0 +1,61 @@ +import jsyaml from 'js-yaml'; + +export const downloadYaml = (filteredData: string, itemName: string): void => { + const yamlData = Array.isArray(filteredData) + ? jsyaml.dump(filteredData.find((item) => item.name === itemName)) + : filteredData; + const blob = new Blob([yamlData], { type: 'application/yaml' }); + const url = URL.createObjectURL(blob); + const element = document.createElement('a'); + element.href = url; + element.download = `${itemName}.yaml`; + // document.body.appendChild(element); // Required for this to work in FireFox + element.click(); + URL.revokeObjectURL(url); +}; + +export function slugify(str: string): string { + if (!str) return str; + str = str.replace(/^\s+|\s+$/g, ''); // trim leading/trailing whitespace + str = str.toLowerCase(); + + // remove accents, swap ñ for n, etc + const from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:;'; + const to = 'aaaaeeeeiiiioooouuuunc------'; + for (let i = 0, l = from.length; i < l; i++) { + str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)); + } + + str = str + .replace(/[^a-z0-9 -]/g, '') // remove invalid chars + .replace(/\s+/g, '-') // collapse whitespace and replace by - + .replace(/-+/g, '-'); // collapse dashes + + return str; +} + +export const downloadFilter = (id: string, name: string): void => { + const dataUri = `${process.env.API_ENDPOINT_PREFIX}/api/content/filters/download/${id}`; + + // Add the .wasm extension to the filename + const fileNameWithExtension = name + '.wasm'; + + const linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('download', fileNameWithExtension); + linkElement.click(); + linkElement.remove(); +}; + +export const formatToTitleCase = (value: string): string => { + if (typeof value === 'string') { + return value.substring(0, 1).toUpperCase().concat('', value.substring(1).toLowerCase()); + } + return ''; +}; + +export const formatDate = (date: Date) => { + const options = { year: 'numeric', month: 'short', day: 'numeric' }; + const formattedDate = new Date(date).toLocaleDateString('en-US', options); + return formattedDate; +}; diff --git a/src/custom/CatalogDetail/index.tsx b/src/custom/CatalogDetail/index.tsx new file mode 100644 index 000000000..87a3d0208 --- /dev/null +++ b/src/custom/CatalogDetail/index.tsx @@ -0,0 +1,31 @@ +import ActionButtons from './ActionButton'; +import CaveatsSection from './CaveatsSection'; +import ChallengesSection from './ChallengesSection'; +import CollapsibleSection from './CollapsibleSection'; +import LearningSection from './LearningSection'; +import LeftPanel from './LeftPanel'; +import MetricsDisplay from './MetricsDisplay'; +import OverviewSection from './OverviewSection'; +import PatternInfo from './PatternInfo'; +import RelatedDesigns from './RelatedDesigns'; +import RightPanel from './RightPanel'; +import SocialSharePopper from './SocialSharePopper'; +import TechnologySection from './TechnologySection'; +import UserInfo from './UserInfo'; + +export { + ActionButtons, + CaveatsSection, + ChallengesSection, + CollapsibleSection, + LearningSection, + LeftPanel, + MetricsDisplay, + OverviewSection, + PatternInfo, + RelatedDesigns, + RightPanel, + SocialSharePopper, + TechnologySection, + UserInfo +}; diff --git a/src/custom/CatalogDetail/style.tsx b/src/custom/CatalogDetail/style.tsx new file mode 100644 index 000000000..5d6320255 --- /dev/null +++ b/src/custom/CatalogDetail/style.tsx @@ -0,0 +1,255 @@ +import { Link, ListItemButton, Paper, Typography } from '../../base'; +import { styled } from '../../theme'; +import { Theme } from './types'; + +export const StyledActionWrapper = styled(Paper)(() => ({ + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.2)', + justifyContent: 'center', + width: '100%', + margin: '0', + marginTop: '1rem', + display: 'flex', + flexDirection: 'column', + gap: '1rem', + padding: '0.6rem', + alignItems: 'center' +})); + +export const LinkUrl = styled('a')(() => ({ + textDecoration: 'none' +})); + +interface ActionButtonProps { + disabled?: boolean; + theme?: Theme; +} + +export const ActionButton = styled('div')(({ disabled = false, theme }) => ({ + cursor: disabled ? 'not-allowed' : 'pointer', + opacity: disabled ? '0.5' : '1', + textAlign: 'center', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: '0.5rem', + backgroundColor: theme.palette.background.brand?.default, + padding: '0.5rem', + color: theme.palette.text.inverse, + gap: '0.625rem', + flex: '1' +})); + +export const ContentDetailsText = styled(Typography)(({ theme }) => ({ + fontFamily: 'inherit', + fontSize: '1rem', + color: theme.palette.text.default, + ['@media (min-width:1200px)']: { + fontSize: '1' + } +})); + +export const ContentHeading = styled('div')(() => ({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + marginBottom: '1rem' +})); + +export const CaveatsContainer = styled('div')(({ theme }) => ({ + width: '100%', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.2)', + backgroundColor: theme.palette.background.default, + textAlign: 'left', + justifyContent: 'start', + alignItems: 'start', + display: 'flex', + flexWrap: 'wrap', + flexDirection: 'column', + padding: '1.5rem', + marginTop: '1.5rem', + borderRadius: '0.4rem', + overflowWrap: 'anywhere' +})); + +interface LabelDivProps { + clickable?: boolean; +} + +export const LabelDiv = styled('div')(({ theme, clickable }) => ({ + display: 'flex', + justifyContent: 'start', + alignItems: 'center', + padding: '0.5rem 1.5rem', + width: '100%', + borderBottom: `1px solid ${theme.palette.border.default}`, + [' @media (min-width: 600px) and (max-width: 800px)']: { + padding: '0.5rem' + }, + ...(clickable && { + '&:hover': { + backgroundColor: theme.palette.background.hover + }, + cursor: 'pointer' + }) +})); + +export const SideContainer = styled('div')(({ theme }) => ({ + width: '100%', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.2)', + backgroundColor: theme.palette.background.default, + justifyContent: 'start', + alignItems: 'start', + display: 'flex', + flexDirection: 'column', + padding: '0.5rem', + borderRadius: '0.4rem' +})); + +export const SideTitleButton = styled(ListItemButton)(({ theme }) => ({ + backgroundColor: theme.palette.background.surfaces, + borderRadius: '0.5rem', + marginTop: 2, + width: '100%', + [' @media (min-width: 600px) and (max-width: 800px)']: { + padding: '0.5rem' + } +})); + +export const ContentDetailsPoints = styled(Typography)(() => ({ + fontSize: '.9rem', + fontWeight: 'bold', + lineHeight: '1.5rem', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: '1rem', + fontFamily: 'inherit' +})); + +export const MetricsSection = styled('div')(() => ({ + padding: '1.1rem', + marginTop: '0.5rem', + display: 'flex', + borderTop: '0.5px solid #3C494F', + justifyContent: 'center', + gap: '1.7rem', + flexWrap: 'wrap', + ['@media (max-width:1200px)']: { + justifyContent: 'flex-start' + } +})); + +export const MetricsContainer = styled('div')(() => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + [' @media (min-width: 280px) and (max-width: 700px)']: { + flexBasis: '35%' + }, + [' @media (max-width: 280px)']: { + flexBasis: '10%' + } +})); +export const MetricsType = styled('div')(({ theme }) => ({ + display: 'flex', + fontSize: '16px', + fontWeight: '400', + letterSpacing: '0.15px', + lineHeight: '1.5', + textTransform: 'lowercase', + color: theme.palette.background.supplementary, + [' @media (max-width: 285px)']: { + fontSize: '0.86rem' + } +})); +export const MetricsData = styled('div')(({ theme }) => ({ + color: theme.palette.background.supplementary, + fontSize: '1.2rem', + fontWeight: 'bold', + lineHeight: '1.5' +})); + +export const OverviewContainer = styled('div')(({ theme }) => ({ + width: '100%', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.2)', + backgroundColor: theme.palette.background.default, + textAlign: 'left', + justifyContent: 'start', + alignItems: 'start', + display: 'flex', + flexWrap: 'wrap', + flexDirection: 'column', + padding: '1.5rem', + borderRadius: '0.4rem' +})); + +export const DesignHeading = styled('h1')(({ theme }) => ({ + textAlign: 'left', + margin: '0rem 0rem 2rem 0rem', + color: theme.palette.text.default, + textTransform: 'capitalize', + fontWeight: '300', + flex: '1' +})); + +export const ContentRow = styled('div')(() => ({ + padding: '0.5rem 0', + overflowWrap: 'anywhere', + fontFamily: 'inherit' +})); + +export const ShowToggleBtn = styled('span')(({ theme }) => ({ + color: theme.palette.background.brand?.default, + cursor: 'pointer', + fontSize: '1rem', + fontWeight: 'normal', + marginLeft: '0.25rem' +})); + +export const AdditionalContainer = styled('div')(({ theme }) => ({ + width: '100%', + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.2)', + backgroundColor: theme.palette.background.default, + textAlign: 'left', + justifyContent: 'center', + alignItems: 'center', + display: 'flex', + flexWrap: 'wrap', + flexDirection: 'column', + padding: '1.5rem', + paddingBottom: '2rem', + marginTop: '1.5rem', + borderRadius: '0.4rem' +})); + +export const DesignCardContainer = styled('div')(() => ({ + display: 'flex', + flexWrap: 'wrap', + flex: '0 0 75%', + gap: '2rem', + justifyContent: 'space-around', + height: 'fit-content' +})); + +export const CopyShareIconWrapper = styled(ContentHeading)(() => ({ + justifyContent: 'flex-end', + gap: '1rem', + width: 'fit-content' +})); + +export const VisibilityChip = styled('div')(() => ({ + borderRadius: '0.5rem', + border: '1px solid gray', + padding: '0.2rem 0.5rem', + textTransform: 'capitalize', + color: '#1a1a1acc', + width: 'fit-content' +})); + +export const RedirectLink = styled(Link)(({ theme }) => ({ + color: theme.palette.background.brand?.default, + textDecoration: 'none', + cursor: 'pointer' +})); diff --git a/src/custom/CatalogDetail/types.ts b/src/custom/CatalogDetail/types.ts new file mode 100644 index 000000000..1cd2adc78 --- /dev/null +++ b/src/custom/CatalogDetail/types.ts @@ -0,0 +1,59 @@ +export interface User { + id: string; + first_name: string; + last_name: string; +} + +export interface FilteredAcademyData { + 'learning-path'?: string[]; + challenge?: string[]; + challenges?: string[]; +} + +export interface Class { + class: string; + description: string; +} + +export interface Theme { + palette: { + background: { + secondary: string; + inverse: string; + cta: { + default: string; + }; + }; + icon: { + default: string; + secondary: string; + }; + }; +} + +export const RESOURCE_TYPES = { + DESIGNS: 'design', + FILTERS: 'filter', + VIEWS: 'view' +}; + +export type ContentClassType = { + community: { + icon: React.ComponentType; + color: string; + }; + official: { + icon: React.ComponentType; + color: string; + }; + verified: { + icon: React.ComponentType; + color: string; + }; +}; + +export type UserProfile = { + first_name: string; + last_name: string; + avatar_url: string; +}; diff --git a/src/custom/CustomCatalog/CatalogCardDesignLogo.tsx b/src/custom/CustomCatalog/CatalogCardDesignLogo.tsx index 3fb392471..30e3473f4 100644 --- a/src/custom/CustomCatalog/CatalogCardDesignLogo.tsx +++ b/src/custom/CustomCatalog/CatalogCardDesignLogo.tsx @@ -43,7 +43,7 @@ const CatalogCardDesignLogo: React.FC = ({ return ( <> {imgURL && imgURL.length > 0 ? ( -
+
{!imgError ? ( <> { const CustomCatalogCard: React.FC = ({ pattern, patternType, - cardHeight, - cardWidth, + cardHeight = '18rem', + cardWidth = '15rem', cardStyles, - shouldFlip, - isDetailed, - cardTechnologies, + shouldFlip = true, + isDetailed = true, + cardTechnologies = true, avatarUrl, UserName, children, @@ -132,7 +138,12 @@ const CustomCatalogCard: React.FC = ({ const version = getVersion(pattern); useEffect(() => { - handleImage(technologies, basePath, subBasePath, setAvailableTechnologies); + handleImage({ + technologies, + basePath, + subBasePath, + setAvailableTechnologies + }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -266,7 +277,7 @@ const CustomCatalogCard: React.FC = ({ {isDetailed && ( - + void -) => { +interface HandleImageProps { + technologies: string[]; + basePath?: string; + subBasePath?: string; + setAvailableTechnologies: (technologies: string[]) => void; +} + +export const handleImage = async ({ + technologies, + basePath = '', + subBasePath = '', + setAvailableTechnologies +}: HandleImageProps) => { const validSvgPaths = await getValidSvgPaths(technologies, basePath, subBasePath); setAvailableTechnologies(validSvgPaths); }; - export const DEFAULT_DESIGN_VERSION = '0.0.0'; export const getVersion = (design: Pattern) => { diff --git a/src/custom/Markdown/style.tsx b/src/custom/Markdown/style.tsx index 9c54d8e1d..2043d2832 100644 --- a/src/custom/Markdown/style.tsx +++ b/src/custom/Markdown/style.tsx @@ -22,7 +22,8 @@ export const BasicAnchorMarkdown = styled('a')(() => ({ export const StyledMarkdownP = styled('p')(({ theme }) => ({ color: theme.palette.text.default, marginBlock: '0px', - ...theme.typography.textB1Regular + ...theme.typography.textB1Regular, + fontFamily: 'inherit' })); export const StyledMarkdownTooltipP = styled('p')(({ theme }) => ({ diff --git a/src/custom/Typography/index.tsx b/src/custom/Typography/index.tsx index 2f03bb473..011112891 100644 --- a/src/custom/Typography/index.tsx +++ b/src/custom/Typography/index.tsx @@ -140,7 +140,7 @@ export const ContentDetailsPoints = styled(TextB3Regular)(({ theme }) => ({ export const ContentDetailsText = styled(TextB1Regular)(({ theme }) => ({ ...commonTypographyStyles(theme), ['@media (min-width:1200px)']: { - fontSize: '1.3rem' + fontSize: '1' } })); diff --git a/src/custom/index.tsx b/src/custom/index.tsx index a7505ea50..14e2b14ab 100644 --- a/src/custom/index.tsx +++ b/src/custom/index.tsx @@ -130,5 +130,6 @@ export type { UniversalFilterProps }; +export * from './CatalogDetail'; export * from './Dialog'; export * from './permissions'; diff --git a/src/icons/Chain/ChainIcon.tsx b/src/icons/Chain/ChainIcon.tsx new file mode 100644 index 000000000..727bfa386 --- /dev/null +++ b/src/icons/Chain/ChainIcon.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +interface ChainIconProps { + width: string; + height: string; + fill?: string; + style?: React.CSSProperties; + secondaryFill?: string; +} + +const ChainIcon: React.FC = ({ width, height, style, fill = '#3C494F' }) => ( + + + +); + +export default ChainIcon; diff --git a/src/icons/Chain/index.ts b/src/icons/Chain/index.ts new file mode 100644 index 000000000..043710db5 --- /dev/null +++ b/src/icons/Chain/index.ts @@ -0,0 +1 @@ +export { default as ChainIcon } from './ChainIcon'; diff --git a/src/icons/Challenges/ChallengesIcon.tsx b/src/icons/Challenges/ChallengesIcon.tsx new file mode 100644 index 000000000..94b9b7b7a --- /dev/null +++ b/src/icons/Challenges/ChallengesIcon.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +interface ChallengesIconProps { + width?: string; + height?: string; + primaryFill?: string; + brandFill?: string; + secondaryFill?: string; + style?: React.CSSProperties; +} + +const ChallengesIcon: React.FC = ({ + width = '32px', + height = '32px', + primaryFill = '#B1B9BC', + brandFill = '#00B39F', + secondaryFill = '#51636B', + style = {} +}) => ( + + + + + + + + + + +); + +export default ChallengesIcon; diff --git a/src/icons/Challenges/index.ts b/src/icons/Challenges/index.ts new file mode 100644 index 000000000..e9cf9c6d5 --- /dev/null +++ b/src/icons/Challenges/index.ts @@ -0,0 +1 @@ +export { default as ChallengesIcon } from './ChallengesIcon'; diff --git a/src/icons/Copy/CopyIcon.tsx b/src/icons/Copy/CopyIcon.tsx new file mode 100644 index 000000000..89424292c --- /dev/null +++ b/src/icons/Copy/CopyIcon.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +interface CopyIconProps { + width: number; + height: number; + fill?: string; + style?: React.CSSProperties; + secondaryFill?: string; +} + +const CopyIcon: React.FC = ({ width, height, fill = 'white', style }) => ( + + + +); + +export default CopyIcon; diff --git a/src/icons/Copy/index.ts b/src/icons/Copy/index.ts index 48fc57203..cd7aaba8a 100644 --- a/src/icons/Copy/index.ts +++ b/src/icons/Copy/index.ts @@ -1 +1,2 @@ +export { default as CopyIcon } from './CopyIcon'; export { default as CopyLinkIcon } from './CopyLinkIcon'; diff --git a/src/icons/Kanvas/KanvasIcon.tsx b/src/icons/Kanvas/KanvasIcon.tsx new file mode 100644 index 000000000..affe38816 --- /dev/null +++ b/src/icons/Kanvas/KanvasIcon.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +interface KanvasIconProps { + width: number; + height: number; + fill?: string; + style?: React.CSSProperties; + primaryFill?: string; + secondaryFill?: string; +} + +const KanvasIcon: React.FC = ({ + width, + height, + fill, + style, + primaryFill = 'white' +}) => ( + + + + + + + + + + + + + + +); + +export default KanvasIcon; diff --git a/src/icons/Kanvas/index.ts b/src/icons/Kanvas/index.ts new file mode 100644 index 000000000..3d2dcdd5c --- /dev/null +++ b/src/icons/Kanvas/index.ts @@ -0,0 +1 @@ +export { default as KanvasIcon } from './KanvasIcon'; diff --git a/src/icons/Learning/LearningIcon.tsx b/src/icons/Learning/LearningIcon.tsx new file mode 100644 index 000000000..cf485d448 --- /dev/null +++ b/src/icons/Learning/LearningIcon.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +interface LearningIconProps { + width?: string; + height?: string; + primaryFill?: string; + secondaryFill?: string; + style?: React.CSSProperties; +} + +const LearningIcon: React.FC = ({ + width = '32px', + height = '32px', + primaryFill = '#FDFDFD', + secondaryFill = '#FDFDFD', + style = {} +}) => ( + + + + +); + +export default LearningIcon; diff --git a/src/icons/Learning/index.ts b/src/icons/Learning/index.ts new file mode 100644 index 000000000..4849b25e4 --- /dev/null +++ b/src/icons/Learning/index.ts @@ -0,0 +1 @@ +export { default as LearningIcon } from './LearningIcon'; diff --git a/src/icons/Share/ShareLineIcon.tsx b/src/icons/Share/ShareLineIcon.tsx new file mode 100644 index 000000000..6a4c6fd3a --- /dev/null +++ b/src/icons/Share/ShareLineIcon.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +interface ShareLineIconProps { + width: string; + height: string; + fill?: string; + style?: React.CSSProperties; + secondaryFill?: string; +} + +const ShareLineIcon: React.FC = ({ + width, + height, + style, + fill = '#3C494F' +}) => ( + + + +); + +export default ShareLineIcon; diff --git a/src/icons/Share/index.tsx b/src/icons/Share/index.tsx index 182307123..5abf944db 100644 --- a/src/icons/Share/index.tsx +++ b/src/icons/Share/index.tsx @@ -1,2 +1,3 @@ import ShareIcon from './ShareIcon'; -export { ShareIcon }; +import ShareLineIcon from './ShareLineIcon'; +export { ShareIcon, ShareLineIcon }; diff --git a/src/icons/SocialMedial/FacebookIcon.tsx b/src/icons/SocialMedial/FacebookIcon.tsx new file mode 100644 index 000000000..0fcf1ea65 --- /dev/null +++ b/src/icons/SocialMedial/FacebookIcon.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { IconProps } from './types'; + +const FacebookIcon: React.FC = ({ width = 40, height = 40 }) => ( + + + + + + + + + + + + + + + + + +); + +export default FacebookIcon; diff --git a/src/icons/SocialMedial/LinkedinIcon.tsx b/src/icons/SocialMedial/LinkedinIcon.tsx new file mode 100644 index 000000000..5ca0d4948 --- /dev/null +++ b/src/icons/SocialMedial/LinkedinIcon.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { IconProps } from './types'; + +const LinkedinIcon: React.FC = ({ width = 40, height = 40 }) => ( + + + + + + + + + + + + + + + + + +); + +export default LinkedinIcon; diff --git a/src/icons/SocialMedial/TwitterIcon.tsx b/src/icons/SocialMedial/TwitterIcon.tsx new file mode 100644 index 000000000..df8172d42 --- /dev/null +++ b/src/icons/SocialMedial/TwitterIcon.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { IconProps } from './types'; + +const TwitterIcon: React.FC = ({ width = 40, height = 40 }) => ( + + + + + + + + + + + + + + + + + +); + +export default TwitterIcon; diff --git a/src/icons/SocialMedial/index.ts b/src/icons/SocialMedial/index.ts new file mode 100644 index 000000000..1b3be9b14 --- /dev/null +++ b/src/icons/SocialMedial/index.ts @@ -0,0 +1,5 @@ +import FacebookIcon from './FacebookIcon'; +import LinkedinIcon from './LinkedinIcon'; +import TwitterIcon from './TwitterIcon'; + +export { FacebookIcon, LinkedinIcon, TwitterIcon }; diff --git a/src/icons/SocialMedial/types.ts b/src/icons/SocialMedial/types.ts new file mode 100644 index 000000000..f1505e285 --- /dev/null +++ b/src/icons/SocialMedial/types.ts @@ -0,0 +1,6 @@ +export interface IconProps { + width?: number; + height?: number; + fill?: string; + style?: React.CSSProperties; +} diff --git a/src/icons/index.ts b/src/icons/index.ts index c7bf87e66..e280c8992 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -40,6 +40,8 @@ export * from './Mesh'; // export { default as ModifiedApplicationFileIcon } from "./ModifiedApplicationFileIcon"; // export { default as OriginalApplicationFileIcon } from "./OriginalApplicationFileIcon"; export * from './Calender'; +export * from './Chain'; +export * from './Challenges'; export * from './ChevronLeft'; export * from './ContentClassIcons'; export * from './Deployments'; @@ -54,7 +56,9 @@ export * from './Feedback'; export * from './HelpIcon'; export * from './Idea'; export * from './InfoOutlined'; +export * from './Kanvas'; export * from './Kubernetes'; +export * from './Learning'; export * from './LeftAngledArrow'; export * from './LeftArrow'; export * from './Menu'; @@ -79,6 +83,7 @@ export * from './Screenshot'; export * from './Search'; export * from './Settings'; export * from './Share'; +export * from './SocialMedial'; export * from './Star'; export * from './Success'; export * from './TerminalIcon';