feat: add certificate gallery and certifications section components
- Implemented CertificateGallery component to display certification images in a gallery format. - Created CertificationsSection component to showcase certifications with titles and descriptions. - Enhanced Gallery component to support animations and keyboard navigation. - Introduced Select component for dropdown selection with images. - Added utility functions for API responses and pagination handling. - Implemented localization functions for text and content. - Created a custom hook for detecting clicks outside of a component.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
IContentCTA,
|
||||
IContentData,
|
||||
IContentDataLink,
|
||||
IContentImage,
|
||||
IContentQuote,
|
||||
IDynamicContent,
|
||||
} from '@/models/dynamicContent';
|
||||
|
||||
function ImageContent(data: IContentImage) {
|
||||
function imageContent(data: IContentImage) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<img
|
||||
@@ -26,12 +28,23 @@ function titleContent(data: string) {
|
||||
return <h2 className="text-2xl font-bold text-gray-500">{data}</h2>;
|
||||
}
|
||||
|
||||
function fullContent(data: IContentData) {
|
||||
const splittedData = data.data?.split('\n') || [];
|
||||
return splittedData.map((e) => {
|
||||
function subtitleContent(data: string) {
|
||||
return <h3 className="text-xl font-semibold text-gray-500">{data}</h3>;
|
||||
}
|
||||
|
||||
function fullContent(data: IContentData | string) {
|
||||
const normalizedData =
|
||||
typeof data === 'string'
|
||||
? ({
|
||||
data,
|
||||
} as IContentData)
|
||||
: data;
|
||||
|
||||
const splittedData = normalizedData.data?.split('\n') || [];
|
||||
return splittedData.map((e, index) => {
|
||||
return (
|
||||
<p className="indent-5 text-base text-gray-400">
|
||||
{generateLinkedContent(e, data.links)}
|
||||
<p key={index} className="indent-5 text-base text-gray-400">
|
||||
{generateLinkedContent(e, normalizedData.links)}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
@@ -80,6 +93,34 @@ function listContent(data: string[], type: 'bullet' | 'numberList') {
|
||||
);
|
||||
}
|
||||
|
||||
function quoteContent(data: IContentQuote) {
|
||||
return (
|
||||
<blockquote className="rounded-xl border-l-4 border-primary bg-gray-100 px-4 py-3 text-gray-500">
|
||||
<p className="text-base italic">{data.text}</p>
|
||||
{data.author ? (
|
||||
<cite className="mt-2 block text-sm text-gray-400">— {data.author}</cite>
|
||||
) : null}
|
||||
</blockquote>
|
||||
);
|
||||
}
|
||||
|
||||
function ctaContent(data: IContentCTA) {
|
||||
return (
|
||||
<a
|
||||
href={data.link}
|
||||
target={data.target || '_self'}
|
||||
rel={data.target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||
className="bg-primary inline-flex w-fit rounded-lg px-4 py-2 text-sm text-white"
|
||||
>
|
||||
{data.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function dividerContent() {
|
||||
return <hr className="my-2 border-gray-200" />;
|
||||
}
|
||||
|
||||
export default function ContentGenerator({
|
||||
data,
|
||||
className,
|
||||
@@ -89,17 +130,27 @@ export default function ContentGenerator({
|
||||
}) {
|
||||
return (
|
||||
<div className={`flex flex-col gap-3 md:gap-6 ${className}`}>
|
||||
{data.map((e) => {
|
||||
{data.map((e, index) => {
|
||||
switch (e.type) {
|
||||
case 'title':
|
||||
return titleContent(e.data as string);
|
||||
return <div key={index}>{titleContent(e.data)}</div>;
|
||||
case 'subtitle':
|
||||
return <div key={index}>{subtitleContent(e.data)}</div>;
|
||||
case 'content':
|
||||
return <div>{fullContent(e.data as IContentData)}</div>;
|
||||
return <div key={index}>{fullContent(e.data)}</div>;
|
||||
case 'image':
|
||||
return ImageContent(e.data as IContentImage);
|
||||
return <div key={index}>{imageContent(e.data)}</div>;
|
||||
case 'bullet':
|
||||
case 'numberList':
|
||||
return listContent(e.data as string[], e.type);
|
||||
return <div key={index}>{listContent(e.data, e.type)}</div>;
|
||||
case 'quote':
|
||||
return <div key={index}>{quoteContent(e.data)}</div>;
|
||||
case 'cta':
|
||||
return <div key={index}>{ctaContent(e.data)}</div>;
|
||||
case 'divider':
|
||||
return <div key={index}>{dividerContent()}</div>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user