add shimmer in all image

This commit is contained in:
Sabda Yagra 2025-03-07 19:32:47 +07:00
parent 06ad352c89
commit 976661c963
15 changed files with 349 additions and 41 deletions

View File

@ -464,6 +464,23 @@ const DetailInfo = () => {
});
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<>
<div className="min-h-screen px-4 md:px-24 py-4">
@ -477,7 +494,7 @@ const DetailInfo = () => {
</div>
) : (
<div className="relative">
<Image width={2560} height={1440} src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={detailDataImage?.files[selectedImage]?.url} alt="Main" className="rounded-lg w-auto h-fit" />
<div className="absolute top-4 left-4"></div>
</div>
)}
@ -493,7 +510,7 @@ const DetailInfo = () => {
<div className="py-4 flex flex-row gap-3">
{detailDataImage?.files?.map((file: any, index: number) => (
<a onClick={() => setSelectedImage(index)} key={file?.id}>
<Image width={1920} height={1080} alt="image-small" src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} alt="image-small" src={file?.url} className="w-[120px] h-[90px] object-cover rounded-md cursor-pointer hover:ring-2 hover:ring-red-600" />
</a>
))}
</div>
@ -650,7 +667,7 @@ const DetailInfo = () => {
{listSuggestion?.map((data: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:px-14">
<Image width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
@ -699,7 +716,7 @@ const DetailInfo = () => {
? data.children?.map((child1: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
<Image width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "}
@ -754,7 +771,7 @@ const DetailInfo = () => {
? child1.children?.map((child2: any) => (
<div className="">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
<Image width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "}

View File

@ -367,6 +367,22 @@ const FilterPage = () => {
clearTimeout(typingTimer);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="flex flex-col">
{/* Header */}

View File

@ -152,6 +152,22 @@ const IndeksDetail = () => {
console.log(dataId);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<>
<div className="p-4 lg:px-60 lg:p-12">
@ -162,7 +178,7 @@ const IndeksDetail = () => {
</div>
{/* Gambar Utama */}
<div className="flex items-center justify-center">
<Image width={2560} height={1440} src={indeksData?.thumbnailLink} alt="Main" className="h-fit lg:h-[550px] w-full rounded-lg" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeksData?.thumbnailLink} alt="Main" className="h-fit lg:h-[550px] w-full rounded-lg" />
</div>
{/* Footer Informasi */}
<div className="text-gray-500 flex border-t mt-4">
@ -196,7 +212,7 @@ const IndeksDetail = () => {
{listComments?.map((data: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:px-14">
<Image width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
<div className="border border-slate-300 w-full p-4 bg-white gap-1">
<p className="flex justify-between text-sm text-slate-500 lg:text-base border-b-2 border-slate-200 mb-2">
<b>{Number(data.commentFrom?.roleId) == 2 || Number(data.commentFrom?.roleId) == 3 || Number(data.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : data.commentFrom?.fullname}</b>
@ -234,7 +250,7 @@ const IndeksDetail = () => {
? data.children?.map((child1: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-12 lg:pl-32">
<Image width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
<div className="border border-slate-300 w-full p-4 bg-white gap-1">
<p className="flex justify-between text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
<b>{Number(child1.commentFrom?.roleId) == 2 || Number(child1.commentFrom?.roleId) == 3 || Number(child1.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child1.commentFrom?.fullname}</b>
@ -274,7 +290,7 @@ const IndeksDetail = () => {
? child1.children?.map((child2: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-20 lg:pl-48">
<Image width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={512} height={512} className="h-10 lg:h-20 w-10 lg:w-20" src="/assets/img/user-avatar-yellow.svg" alt="#" />
<div className="border border-slate-300 w-full p-4 bg-white gap-2">
<p className="flex justify-between text-slate-500 text-xs lg:text-base border-b-2 border-slate-200 mb-2">
<b>{Number(child2.commentFrom?.roleId) == 2 || Number(child2.commentFrom?.roleId) == 3 || Number(child2.commentFrom?.roleId) == 4 ? "HUMAS POLRI" : child2.commentFrom?.fullname}</b>
@ -329,7 +345,14 @@ const IndeksDetail = () => {
{indexData?.map((relate: any) => (
<CarouselItem key={relate?.id} className="md:basis-1/2 lg:basis-1/3">
<Link href={`/indeks/detail/${relate?.slug}`} className="relative group overflow-hidden shadow-md hover:shadow-lg">
<Image alt="" width={2560} height={1440} src={relate?.thumbnailLink} className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
alt=""
width={2560}
height={1440}
src={relate?.thumbnailLink}
className="w-full rounded-lg h-40 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-sm px-4 py-1">{relate?.categoryName}</span>
<h1 className="text-sm lg:text-lg mb-2 font-semibold h-5 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{relate?.title}</h1>

View File

@ -40,6 +40,23 @@ const Indeks: React.FC = () => {
setIndeksData(response?.data?.data?.content);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="px-4 lg:px-14">
{/* Hero Left */}
@ -54,7 +71,7 @@ const Indeks: React.FC = () => {
(indeks: any, index: number) =>
index == count && (
<div key={indeks?.id} className="relative h-[310px] lg:h-[435px]">
<Image width={2560} height={1440} src={indeks?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeks?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[435px] rounded-lg object-cover" />
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeks?.categoryName}</span>
<Link href={`/indeks/detail/${indeks?.slug}`}>
@ -92,7 +109,7 @@ const Indeks: React.FC = () => {
(indeksRight: any, index: number) =>
(index == count + 1 || index == count + 2) && (
<div key={indeksRight?.id} className="relative h-[310px] lg:h-[215px]">
<Image width={1920} height={1080} src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={indeksRight?.thumbnailLink} alt="image" className="w-full h-[310px] lg:h-[215px] rounded-lg " />
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{indeksRight?.categoryName}</span>
<Link href={`/indeks/detail/${indeksRight?.slug}`}>
@ -149,7 +166,7 @@ const Indeks: React.FC = () => {
(indeksBottom: any, index: number) =>
index < 3 && (
<div key={indeksBottom?.id} className="flex flex-col md:flex-row items-start p-4 gap-4">
<Image width={2560} height={1440} src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={2560} height={1440} src={indeksBottom?.thumbnailLink} alt="" className="h-40 object-cover rounded-lg w-full lg:w-full lg:h-[300px]" />
<div className="flex flex-col justify-between w-full">
<p className="text-sm">{indeksBottom?.date}</p>
<Link href={`/indeks/detail/${indeksBottom?.slug}`} className="text-2xl font-semibold text-gray-800 dark:text-white">

View File

@ -417,6 +417,22 @@ const DetailVideo = () => {
}
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<>
<div className="px-4 md:px-24 py-4">
@ -596,7 +612,7 @@ const DetailVideo = () => {
{listSuggestion?.map((data: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:px-14">
<Image width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={data?.suggestionFrom?.profilePictureUrl} className="h-12 lg:h-16 w-12 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{Number(data.suggestionFrom?.roleId) == 2 || Number(data.suggestionFrom?.roleId) == 3 || Number(data.suggestionFrom?.roleId) == 4 ? "HUMAS POLRI" : data.suggestionFrom?.fullname}
@ -645,7 +661,7 @@ const DetailVideo = () => {
? data.children?.map((child1: any) => (
<div className="flex flex-col">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-16 lg:pl-32">
<Image width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child1.suggestionFrom?.profilePictureUrl} onError={addDefaultProfile} alt="" className="h-10 lg:h-16 w-10 lg:w-16 mr-2" />
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "}
@ -699,7 +715,7 @@ const DetailVideo = () => {
? child1.children?.map((child2: any) => (
<div className="">
<div className="flex flex-row mt-2 px-4 lg:pr-14 pl-28 lg:pl-48">
<Image width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1080} height={1080} src={child2.suggestionFrom?.profilePictureUrl} className="h-9 lg:h-16 w-9 lg:w-16 mr-2" onError={addDefaultProfile} alt="" />
<div className="border border-slate-300 w-full p-2 lg:p-4 bg-white gap-1">
<p className="text-slate-500 text-sm lg:text-base border-b-2 border-slate-200 mb-2">
{" "}

View File

@ -1,9 +1,10 @@
"use client";
import React from "react";
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import Image from "next/image";
import Coverage from "./coverage";
import Division from "./division";
const AreaCoverageWorkUnits = () => {
return (
@ -19,7 +20,7 @@ const AreaCoverageWorkUnits = () => {
<Image width={1920} height={1080} alt="indo" src="/assets/indo.png" className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300" />
<p className="text-base font-bold">Polda Jajaran</p>
</DialogTrigger>
<DialogContent size="lg">
<DialogContent size="md">
<DialogHeader className="flex flex-col justify-center">
<DialogTitle>
<p className="text-center">Polda Jajaran</p>
@ -40,10 +41,10 @@ const AreaCoverageWorkUnits = () => {
{/* SATKER */}
<Dialog>
<DialogTrigger className="flex flex-col gap-2 justify-center items-center shadow-lg group rounded-xl py-5 px-24 border-2 border-transparent hover:border-[#bb3523] transition-all duration-300">
<Image width={1920} height={1080} alt="indo" src="/assets/logo-polri.png" className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300" />
<p className="text-base font-bold">Polda Jajaran</p>
<Image width={1920} height={1080} alt="polri" src="/assets/logo-polri.png" className="h-32 w-32 group-hover:scale-110 group-hover:border-[#bb3523] transition-transform duration-300" />
<p className="text-base font-bold">Satuan Kerja Polri</p>
</DialogTrigger>
<DialogContent size="lg">
<DialogContent size="md">
<DialogHeader className="flex flex-col justify-center">
<DialogTitle>
<p className="text-center">Satuan Kerja Polri</p>
@ -52,7 +53,7 @@ const AreaCoverageWorkUnits = () => {
<div className="h-1 w-[150px] bg-[#bb3523] mx-auto mb-6 rounded"></div>
</DialogTitle>
</DialogHeader>
<Coverage />
<Division />
<div className="text-right mt-4">
<DialogClose asChild>
<button className="text-[#bb3523] font-bold">Tutup</button>

View File

@ -33,6 +33,23 @@ const ContentCategory = (props: { group?: string }) => {
const [seeAllValue, setSeeAllValue] = useState(false);
const pathname = usePathname();
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="mx-auto px-4 lg:px-24 py-10 max-w-screen-2xl ">
<Reveal>
@ -56,7 +73,7 @@ const ContentCategory = (props: { group?: string }) => {
!seeAllValue ? (
index < 4 ? (
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
<Image alt="category" width={2560} height={1440} src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} alt="category" width={2560} height={1440} src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
<div className="absolute bottom-0 rounded-lg left-0 right-0 bg-gray-400 border-l-4 mb-4 border-[#bb3523] text-white p-2">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
</div>
@ -66,7 +83,7 @@ const ContentCategory = (props: { group?: string }) => {
)
) : (
<Link key={category?.id} href={`all/filter?category=${category?.id}`} className="relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
<Image alt="category" width={2560} height={1440} src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} alt="category" width={2560} height={1440} src={category?.thumbnailLink} className="w-full h-48 sm:h-40 object-cover group-hover:scale-110 transition-transform duration-300" />
<div className="absolute bottom-0 left-0 right-0 bg-gray-400 border-l-4 mb-4 border-[#bb3523] rounded-lg text-white p-2">
<h3 className="text-sm font-semibold truncate">{category?.name}</h3>
</div>

View File

@ -5,6 +5,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
import Image from "next/image";
const regions = [
{ name: "Polda Metro Jaya", slug: "metro-jaya", logo: "/assets/polda/polda-metro.png" },
@ -59,8 +60,24 @@ const Coverage: React.FC = () => {
setFilteredList(filtered);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="mx-auto px-4">
<div className="w-full">
{/* Header */}
{/* <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
{pathname?.split("/")[1] == "in" ? (
@ -91,13 +108,13 @@ const Coverage: React.FC = () => {
</div> */}
{/* Grid Wilayah */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 overflow-y-scroll px-4 max-h-[60vh]">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 overflow-y-auto px-4 max-h-[60vh]">
{filteredList?.map((region: any) => (
<Link key={region.slug} href={`/polda/${region.slug}`} className="flex flex-col items-center text-center group p-3 border rounded-lg shadow-sm hover:shadow-md transition-all">
<div className="w-20 h-20 shadow-lg rounded-lg mb-2 flex items-center justify-center">
<img src={region.logo} alt={region.name} className="w-3/4 h-3/4 object-contain" />
<Link key={region.slug} href={`/polda/${region.slug}`} className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all">
<div className="mb-1 flex items-center justify-center">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={region.logo} alt={region.name} className="w-14 h-14 object-contain" />
</div>
<p className="text-md font-semibold">{region.name}</p>
<p className="text-xs font-semibold">{region.name}</p>
</Link>
))}
</div>

View File

@ -5,6 +5,7 @@ import { Icon } from "@iconify/react/dist/iconify.js";
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { usePathname } from "next/navigation";
import Image from "next/image";
const regions = [
{ name: "SIBER", slug: "siber", logo: "/assets/satker/siber.png" },
@ -62,8 +63,24 @@ const Division = () => {
setFilteredList(filtered);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="max-w-screen-xl mx-auto px-4 lg:px-12 py-6">
<div className="mx-auto px-4 w-full">
{/* Header */}
{/* <Reveal> */}
{/* <h2 className="text-center text-2xl font-bold text-gray-800 dark:text-white mb-4">
@ -90,13 +107,13 @@ const Division = () => {
</div> */}
{/* Grid Wilayah */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 overflow-y-scroll px-4 max-h-[60vh]">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-6 px-4 overflow-y-auto max-h-[60vh]">
{filteredList?.map((region: any) => (
<Link href={`/satker/${region.slug}`} className="flex flex-col items-center text-center group p-3 border rounded-lg shadow-sm hover:shadow-md transition-all">
<div className="w-20 h-20 shadow-lg rounded-lg mb-2 flex items-center justify-center">
<img src={region.logo} alt={region.name} className="w-3/4 h-3/4 object-contain" />
<Link href={`/satker/${region.slug}`} className="flex flex-col items-center text-center p-3 border rounded-lg shadow-md hover:shadow-lg transition-all">
<div className="mb-1 flex items-center justify-center">
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={1920} height={1080} src={region.logo} alt={region.name} className="w-14 h-14 object-contain" />
</div>
<p className="text-md font-semibold">{region.name}</p>
<p className="text-xs font-semibold">{region.name}</p>
</Link>
))}
</div>

View File

@ -1,4 +1,4 @@
import { formatDateToIndonesian, textEllipsis } from "@/utils/globals";
import { formatDateToIndonesian, shimmer, toBase64 } from "@/utils/globals";
import React, { useEffect, useState } from "react";
import "swiper/css/bundle";
import "swiper/css/navigation";
@ -53,6 +53,22 @@ const Hero: React.FC = () => {
setHeroData(response?.data?.data?.content);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="flex flex-col lg:flex-row items-start justify-center gap-8 px-4 lg:px-20 py-4 mx-auto w-auto mt-6">
{/* Section Gambar Utama */}
@ -70,7 +86,15 @@ const Hero: React.FC = () => {
{heroData?.map((list: any) => (
<CarouselItem key={list?.id}>
<div className="relative h-[310px] lg:h-[420px]">
<Image src={list?.thumbnailLink} alt="gambar-utama" width={1920} height={1080} className="w-full lg:w-[850px] h-[310px] lg:h-[420px] rounded-lg object-cover" />
<Image
src={list?.thumbnailLink}
alt="gambar-utama"
width={1920}
height={1080}
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
className="w-full lg:w-[850px] h-[310px] lg:h-[420px] rounded-lg object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 bg-transparent backdrop-blur-sm text-black dark:text-white p-4 rounded-b-lg">
<span className="text-white bg-[#bb3523] rounded-md w-full h-full font-semibold uppercase text-xs px-2 py-1">{list?.categoryName}</span>
<Link href={`${locale}/image/detail/${list?.slug}`}>
@ -141,7 +165,7 @@ const Hero: React.FC = () => {
{heroData?.map((item: any) => (
<li key={item?.id} className="flex gap-4 flex-row lg:w-full ">
<div className="flex-shrink-0 w-24 rounded-lg">
<Image width={720} height={480} src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} width={720} height={480} src={item?.thumbnailLink} alt={item?.title} className="w-full h-[73px] object-cover rounded-lg" />
</div>
<div className="w-[280px] lg:w-auto">
<span className="text-white bg-[#bb3523] px-4 py-1 rounded-lg flex text-[8px] font-bold uppercase w-fit">{item?.categoryName}</span>

View File

@ -54,6 +54,22 @@ const NewContent = (props: { group: string; type: string }) => {
setNewContent(response?.data?.data?.content);
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
return (
<div className="px-4 lg:px-16 py-4">
<Reveal>
@ -120,7 +136,14 @@ const NewContent = (props: { group: string; type: string }) => {
{newContent?.map((image: any) => (
<CarouselItem key={image?.id} className="md:basis-1/2 lg:basis-1/3">
<div onClick={() => router.push(prefixPath + `/image/detail/${image?.slug}`)} className="cursor-pointer relative group overflow-hidden shadow-md hover:shadow-lg">
<Image width={2560} height={1440} alt="image" src={image?.thumbnailLink} className="w-full rounded-lg h-48 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300" />
<Image
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
width={2560}
height={1440}
alt="image"
src={image?.thumbnailLink}
className="w-full rounded-lg h-48 lg:h-60 object-cover group-hover:scale-100 transition-transform duration-300"
/>
<div className="absolute bottom-0 left-0 right-0 bg-gray-600 border-l-4 border-[#bb3523] rounded-lg backdrop-blur-sm text-white p-2">
<p className="text-sm lg:text-base mb-2 font-semibold h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{image?.title}</p>
<p className="flex flex-row items-center text-[10px] gap-2">
@ -184,7 +207,7 @@ const NewContent = (props: { group: string; type: string }) => {
{newContent?.map((video: any) => (
<CarouselItem key={video?.id} className="md:basis-1/2 lg:basis-1/3">
<div onClick={() => router.push(prefixPath + `/video/detail/${video?.slug}`)} className="cursor-pointer relative group rounded-md overflow-hidden shadow-md hover:shadow-lg">
<Image alt="video" width={2560} height={1440} src={video?.thumbnailLink} className="w-full h-40 lg:h-60 object-cover rounded-lg group-hover:scale-100 transition-transform duration-300" />
<Image placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`} alt="video" width={2560} height={1440} src={video?.thumbnailLink} className="w-full h-40 lg:h-60 object-cover rounded-lg group-hover:scale-100 transition-transform duration-300" />
<div className="absolute bottom-0 rounded-lg left-0 right-0 border-l-4 border-[#bb3523] bg-gray-600 text-white p-2">
<p className="text-sm lg:text-base mb-2 font-semibold h-6 hover:h-auto truncate hover:whitespace-normal hover:overflow-visible">{video?.title}</p>
<p className="flex flex-row items-center text-sm gap-2">

View File

@ -0,0 +1,39 @@
import Image from "next/image";
import ViewSource from "../view-source/view-source";
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#333" offset="20%" />
<stop stop-color="#222" offset="50%" />
<stop stop-color="#333" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#333" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));
const Shimmer = () => (
<div>
<ViewSource pathname="app/shimmer/page.tsx" />
<h1>Image Component With Shimmer Data URL</h1>
<Image
alt="Mountains"
src="/mountains.jpg"
placeholder={`data:image/svg+xml;base64,${toBase64(shimmer(700, 475))}`}
width={700}
height={475}
style={{
maxWidth: "100%",
height: "auto",
}}
/>
</div>
);
export default Shimmer;

View File

@ -0,0 +1,48 @@
.svg {
position: absolute;
top: 0;
right: 0;
}
.arm {
transform-origin: 130px 106px;
}
.svg:hover .arm {
animation: wave 560ms ease-in-out;
}
@keyframes wave {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(-25deg);
}
40% {
transform: rotate(10deg);
}
60% {
transform: rotate(-25deg);
}
80% {
transform: rotate(10deg);
}
100% {
transform: rotate(0deg);
}
}
@media (max-width: 500px) {
.svg:hover .arm {
animation: none;
}
.svg:hover .arm {
animation: wave 560ms ease-in-out;
}
}

View File

@ -0,0 +1,17 @@
import styles from "./view-source.module.css";
type ViewSourceProps = {
pathname: string;
};
const ViewSource = ({ pathname }: ViewSourceProps) => (
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 250 250" fill="#151513" className={styles.svg}>
<a title="View Source" href={`https://github.com/vercel/next.js/blob/canary/examples/image-component/${pathname}`}>
<path d="M0 0l115 115h15l12 27 108 108V0z" fill="#fff" />
<path className={styles.arm} d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16" />
<path d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z" />
</a>
</svg>
);
export default ViewSource;

View File

@ -137,3 +137,19 @@ export function checkMaliciousText(str: any) {
return "";
}
}
export const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#333" offset="20%" />
<stop stop-color="#222" offset="50%" />
<stop stop-color="#333" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#333" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
export const toBase64 = (str: string) => (typeof window === "undefined" ? Buffer.from(str).toString("base64") : window.btoa(str));