feat: Image Slider Blocks
This commit is contained in:
parent
88ca451fd9
commit
269a89be71
934
.yarn/releases/yarn-4.6.0.cjs
vendored
Normal file
934
.yarn/releases/yarn-4.6.0.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
3
.yarnrc.yml
Normal file
3
.yarnrc.yml
Normal file
@ -0,0 +1,3 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.6.0.cjs
|
@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"dev": "NEXT_DEBUG_TURBOPACK=1 next dev --turbopack",
|
||||
"dev-old": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
@ -60,5 +60,6 @@
|
||||
"prettier": "^3.4.2",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
}
|
8
public/assets/images/google-provider.svg
Normal file
8
public/assets/images/google-provider.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
|
||||
<path fill="#4285F4" d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"/>
|
||||
<path fill="#34A853" d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"/>
|
||||
<path fill="#FBBC05" d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"/>
|
||||
<path fill="#EA4335" d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
9
src/app/(main)/coba/page.tsx
Normal file
9
src/app/(main)/coba/page.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { ImageSliderBlock } from "@/components/Blocks/ImageSlider";
|
||||
|
||||
export default function CobaPage() {
|
||||
return (
|
||||
<>
|
||||
<ImageSliderBlock />
|
||||
</>
|
||||
);
|
||||
}
|
22
src/blocks/ImageSlider.ts
Normal file
22
src/blocks/ImageSlider.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Block } from "payload";
|
||||
|
||||
export const ImageSliderBlock: Block = {
|
||||
slug: "imageSliderBlock",
|
||||
labels: { plural: "Image Slider", singular: "Image Slider" },
|
||||
fields: [
|
||||
{
|
||||
label: "Images",
|
||||
name: "images",
|
||||
type: "array",
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
label: "Image",
|
||||
name: "image",
|
||||
type: "upload",
|
||||
relationTo: "media",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
import { BeforeFooterBlock } from "@/blocks/BeforeFooter";
|
||||
import { ContentBlock } from "@/blocks/Content";
|
||||
import { HorizontalImageContentBlock } from "@/blocks/HorizontalImageContent";
|
||||
import { ImageSliderBlock } from "@/blocks/ImageSlider";
|
||||
import { OurTeamBlock } from "@/blocks/OurTeam";
|
||||
import formatSlug from "@/utils/formatSlug";
|
||||
import { CollectionConfig } from "payload";
|
||||
@ -36,7 +37,7 @@ export const Pages: CollectionConfig = {
|
||||
label: "Page Layout",
|
||||
type: "blocks",
|
||||
minRows: 1,
|
||||
blocks: [ContentBlock, BeforeFooterBlock, OurTeamBlock, HorizontalImageContentBlock],
|
||||
blocks: [ContentBlock, BeforeFooterBlock, OurTeamBlock, HorizontalImageContentBlock, ImageSliderBlock],
|
||||
},
|
||||
{
|
||||
name: "meta",
|
||||
|
84
src/components/Blocks/GoogleReviews/index.tsx
Normal file
84
src/components/Blocks/GoogleReviews/index.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import { CardStarRating } from "@/components/Ratings/CardStarRating";
|
||||
import StarRating from "@/components/Ratings/StarRating";
|
||||
import { teamMembers } from "@/data/team";
|
||||
import Image from "next/image";
|
||||
import { Navigation } from "swiper/modules";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
|
||||
export function GoogleReviewsBlock() {
|
||||
return (
|
||||
<section className="bg-scroll relative py-20">
|
||||
<div className="decoration-14" />
|
||||
<div className="decoration-15" />
|
||||
<div className="decoration-16 opacity-035 d-none d-md-block">
|
||||
<Image src="/assets/images/demo-slick/decoration-2.svg" alt="Image Description" width={128} height={228} />
|
||||
</div>
|
||||
<div className="decoration-17 opacity-035 d-none d-md-block">
|
||||
<Image src="/assets/images/demo-slick/decoration-2.svg" alt="Image Description" width={128} height={228} />
|
||||
</div>
|
||||
|
||||
<div className="container px-6">
|
||||
<h2 className="text-3xl font-bold text-center">Reviews</h2>
|
||||
<div className="flex justify-center space-x-2">
|
||||
<h2 className="text-xl text-center font-bold">4.8</h2>
|
||||
<div className="mt-1">
|
||||
<StarRating size={20} value={3} />
|
||||
</div>
|
||||
<div className="text-xs font-semibold mt-1 text-neutral-500">Over 200 Reviews</div>
|
||||
</div>
|
||||
<div className="mt-20 relative">
|
||||
<Swiper
|
||||
loop
|
||||
spaceBetween={0}
|
||||
slidesPerView={3}
|
||||
breakpoints={{
|
||||
900: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
500: {
|
||||
slidesPerView: 2, // When window width is <= 480px
|
||||
},
|
||||
0: {
|
||||
slidesPerView: 1,
|
||||
},
|
||||
}}
|
||||
modules={[Navigation]}
|
||||
navigation={{
|
||||
prevEl: ".snbp1",
|
||||
nextEl: ".snbn1",
|
||||
}}
|
||||
watchSlidesProgress
|
||||
resizeObserver
|
||||
className="team-carousel owl-carousel owl-theme overflow-hidden position-static"
|
||||
style={{
|
||||
opacity: 1,
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
{/* Group item */}
|
||||
{teamMembers.map((member, index) => (
|
||||
<SwiperSlide className="owl-item py-2 mt-5" key={index}>
|
||||
<div>
|
||||
<CardStarRating />
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
<div className="owl-controls clickable">
|
||||
<div className="owl-buttons">
|
||||
<div className="owl-prev snbp1 lef" role="button" tabIndex={0}>
|
||||
<span className="visually-hidden">Previous Slide</span>
|
||||
<i className="mi-arrow-left" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div className="owl-next snbn1" role="button" tabIndex={0}>
|
||||
<span className="visually-hidden">Next Slide</span>
|
||||
<i className="mi-arrow-right" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* End Group item */}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
75
src/components/Blocks/ImageSlider/index.tsx
Normal file
75
src/components/Blocks/ImageSlider/index.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { Navigation } from "swiper/modules";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
|
||||
interface ImageSliderBlockProps {
|
||||
images: { id: string; image: { url: string; alt: string } }[];
|
||||
}
|
||||
|
||||
export function ImageSliderBlock({ images }: ImageSliderBlockProps) {
|
||||
return (
|
||||
<div className="container px-6 py-4">
|
||||
<div className="mt-20 relative">
|
||||
<Swiper
|
||||
loop
|
||||
spaceBetween={0}
|
||||
slidesPerView={3}
|
||||
breakpoints={{
|
||||
900: {
|
||||
slidesPerView: 3,
|
||||
},
|
||||
500: {
|
||||
slidesPerView: 2, // When window width is <= 480px
|
||||
},
|
||||
0: {
|
||||
slidesPerView: 1,
|
||||
},
|
||||
}}
|
||||
modules={[Navigation]}
|
||||
navigation={{
|
||||
prevEl: ".snbp1",
|
||||
nextEl: ".snbn1",
|
||||
}}
|
||||
watchSlidesProgress
|
||||
resizeObserver
|
||||
className="team-carousel owl-carousel owl-theme overflow-hidden position-static"
|
||||
style={{
|
||||
opacity: 1,
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
{/* Group item */}
|
||||
{images.map((item, index: number) => (
|
||||
<SwiperSlide className="owl-item py-2 mt-5" key={index}>
|
||||
<div className="w-full h-[250px]">
|
||||
<Image
|
||||
src={item?.image?.url ?? ""}
|
||||
width={0}
|
||||
height={0}
|
||||
sizes="100vw"
|
||||
className="w-full h-auto rounded-md"
|
||||
alt={item?.image?.alt ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
<div className="owl-controls clickable">
|
||||
<div className="owl-buttons">
|
||||
<div className="owl-prev snbp1 lef" role="button" tabIndex={0}>
|
||||
<span className="visually-hidden">Previous Slide</span>
|
||||
<i className="mi-arrow-left" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div className="owl-next snbn1" role="button" tabIndex={0}>
|
||||
<span className="visually-hidden">Next Slide</span>
|
||||
<i className="mi-arrow-right" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* End Group item */}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -5,12 +5,14 @@ import { ContentBlock } from "./Content";
|
||||
import { BeforeFooterBlock } from "./BeforeFooter";
|
||||
import { OurTeamBlock } from "./OurTeam";
|
||||
import { HorizontalImageContentBlock } from "./HorizontalImageContent";
|
||||
import { ImageSliderBlock } from "./ImageSlider";
|
||||
|
||||
const blockComponents = {
|
||||
contentBlock: ContentBlock,
|
||||
beforeFooterBlock: BeforeFooterBlock,
|
||||
ourTeamBlock: OurTeamBlock,
|
||||
horizontalImageContentBlock: HorizontalImageContentBlock,
|
||||
imageSliderBlock: ImageSliderBlock,
|
||||
};
|
||||
|
||||
export const RenderBlocks: React.FC<{
|
||||
|
14
src/components/Ratings/CardStarRating.tsx
Normal file
14
src/components/Ratings/CardStarRating.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import Image from "next/image";
|
||||
import StarRating from "./StarRating";
|
||||
|
||||
export function CardStarRating() {
|
||||
return (
|
||||
<div className="bg-white p-2 rounded-lg shadow-md">
|
||||
<div className="flex justify-between">
|
||||
<StarRating size={20} value={3} />
|
||||
<Image src="/assets/images/google-provider.svg" width={20} height={20} alt="" />
|
||||
</div>
|
||||
<p className="text-xs pl-1 mt-4">Today my cat scan</p>
|
||||
</div>
|
||||
);
|
||||
}
|
67
src/components/Ratings/StarRating.tsx
Normal file
67
src/components/Ratings/StarRating.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
|
||||
interface StarRatingProps {
|
||||
value: number;
|
||||
baseColor?: string;
|
||||
filledColor?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const StarRating: React.FC<StarRatingProps> = ({
|
||||
value,
|
||||
baseColor = "#F2EFE7",
|
||||
filledColor = "#FFD95F",
|
||||
size = 24,
|
||||
}) => {
|
||||
const stars = Array.from({ length: 5 }, (_, i) => {
|
||||
if (value >= i + 1) {
|
||||
return (
|
||||
<svg
|
||||
key={i}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill={filledColor}
|
||||
width={size}
|
||||
height={size}
|
||||
>
|
||||
<path
|
||||
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
} else if (value > i && value < i + 1) {
|
||||
return (
|
||||
<svg key={i} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={size} height={size}>
|
||||
<defs>
|
||||
<linearGradient id={`half-grad-${i}`}>
|
||||
<stop offset="50%" stopColor={filledColor} />
|
||||
<stop offset="50%" stopColor={baseColor} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
fill={`url(#half-grad-${i})`}
|
||||
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<svg key={i} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill={baseColor} width={size} height={size}>
|
||||
<path
|
||||
d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return <div className="flex">{stars}</div>;
|
||||
};
|
||||
|
||||
export default StarRating;
|
0
src/components/Stars
Normal file
0
src/components/Stars
Normal file
@ -197,6 +197,17 @@ export interface Page {
|
||||
blockName?: string | null;
|
||||
blockType: 'horizontalImageContentBlock';
|
||||
}
|
||||
| {
|
||||
images?:
|
||||
| {
|
||||
image?: (number | null) | Media;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'imageSliderBlock';
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
meta?: {
|
||||
@ -569,6 +580,18 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
imageSliderBlock?:
|
||||
| T
|
||||
| {
|
||||
images?:
|
||||
| T
|
||||
| {
|
||||
image?: T;
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
meta?:
|
||||
| T
|
||||
|
Loading…
x
Reference in New Issue
Block a user