mirror of
https://github.com/dyzulk/trustlab.git
synced 2026-01-26 21:41:52 +07:00
First commit
This commit is contained in:
45
src/components/common/ChartTab.tsx
Normal file
45
src/components/common/ChartTab.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const ChartTab: React.FC = () => {
|
||||
const [selected, setSelected] = useState<
|
||||
"optionOne" | "optionTwo" | "optionThree"
|
||||
>("optionOne");
|
||||
|
||||
const getButtonClass = (option: "optionOne" | "optionTwo" | "optionThree") =>
|
||||
selected === option
|
||||
? "shadow-theme-xs text-gray-900 dark:text-white bg-white dark:bg-gray-800"
|
||||
: "text-gray-500 dark:text-gray-400";
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 rounded-lg bg-gray-100 p-0.5 dark:bg-gray-900">
|
||||
<button
|
||||
onClick={() => setSelected("optionOne")}
|
||||
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
|
||||
"optionOne"
|
||||
)}`}
|
||||
>
|
||||
Monthly
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setSelected("optionTwo")}
|
||||
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
|
||||
"optionTwo"
|
||||
)}`}
|
||||
>
|
||||
Quarterly
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setSelected("optionThree")}
|
||||
className={`px-3 py-2 font-medium w-full rounded-md text-theme-sm hover:text-gray-900 dark:hover:text-white ${getButtonClass(
|
||||
"optionThree"
|
||||
)}`}
|
||||
>
|
||||
Annually
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChartTab;
|
||||
17
src/components/common/CommonGridShape.tsx
Normal file
17
src/components/common/CommonGridShape.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
export default function CommonGridShape() {
|
||||
return (
|
||||
<div className="absolute inset-0 z-0 opacity-10">
|
||||
{/* Simple grid pattern SVG or similar, replacing the image for now if static */}
|
||||
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="1"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
src/components/common/ComponentCard.tsx
Normal file
49
src/components/common/ComponentCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
|
||||
interface ComponentCardProps {
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string; // Additional custom classes for styling
|
||||
desc?: string; // Description text
|
||||
headerAction?: React.ReactNode; // Optional action in the header
|
||||
}
|
||||
|
||||
const ComponentCard: React.FC<ComponentCardProps> = ({
|
||||
title,
|
||||
children,
|
||||
className = "",
|
||||
desc = "",
|
||||
headerAction,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03] ${className}`}
|
||||
>
|
||||
{/* Card Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between px-4 sm:px-7 py-5 gap-4 sm:gap-0">
|
||||
<div>
|
||||
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
|
||||
{title}
|
||||
</h3>
|
||||
{desc && (
|
||||
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{desc}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{headerAction && (
|
||||
<div className="flex-shrink-0 w-full sm:w-auto">
|
||||
{headerAction}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Card Body */}
|
||||
<div className="p-4 border-t border-gray-100 dark:border-gray-800 sm:p-6">
|
||||
<div className="space-y-6">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentCard;
|
||||
130
src/components/common/ConfirmationModal.tsx
Normal file
130
src/components/common/ConfirmationModal.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Modal } from "../ui/modal";
|
||||
import Button from "../ui/button/Button";
|
||||
import { AlertIcon } from "@/icons";
|
||||
|
||||
interface ConfirmationModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
title: string;
|
||||
message: string;
|
||||
isLoading?: boolean;
|
||||
confirmLabel?: string;
|
||||
cancelLabel?: string;
|
||||
variant?: "danger" | "warning" | "info";
|
||||
requiredInput?: string; // Text that must be entered to enable the confirm button
|
||||
requiredInputPlaceholder?: string;
|
||||
}
|
||||
|
||||
const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
title,
|
||||
message,
|
||||
isLoading = false,
|
||||
confirmLabel = "Confirm",
|
||||
cancelLabel = "Cancel",
|
||||
variant = "danger",
|
||||
requiredInput,
|
||||
requiredInputPlaceholder = "Type to confirm...",
|
||||
}) => {
|
||||
const [inputValue, setInputValue] = React.useState("");
|
||||
|
||||
// Reset input when modal opens/closes
|
||||
React.useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setInputValue("");
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const isConfirmDisabled = isLoading || (requiredInput !== undefined && inputValue !== requiredInput);
|
||||
|
||||
const getVariantStyles = () => {
|
||||
switch (variant) {
|
||||
case "danger":
|
||||
return {
|
||||
iconBg: "bg-error-50 dark:bg-error-500/10",
|
||||
iconColor: "text-error-600 dark:text-error-500",
|
||||
confirmBtn: "primary", // Assuming Button variant
|
||||
confirmBtnClass: "bg-error-600 hover:bg-error-700 text-white border-none",
|
||||
};
|
||||
case "warning":
|
||||
return {
|
||||
iconBg: "bg-warning-50 dark:bg-warning-500/10",
|
||||
iconColor: "text-warning-600 dark:text-warning-500",
|
||||
confirmBtn: "primary",
|
||||
confirmBtnClass: "bg-warning-600 hover:bg-warning-700 text-white border-none",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
iconBg: "bg-brand-50 dark:bg-brand-500/10",
|
||||
iconColor: "text-brand-600 dark:text-brand-500",
|
||||
confirmBtn: "primary",
|
||||
confirmBtnClass: "",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const styles = getVariantStyles();
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="max-w-[440px]" showCloseButton={true}>
|
||||
<div className="p-6">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className={`mb-4 flex h-14 w-14 items-center justify-center rounded-full ${styles.iconBg}`}>
|
||||
<AlertIcon className={`h-7 w-7 ${styles.iconColor}`} />
|
||||
</div>
|
||||
|
||||
<h3 className="mb-2 text-xl font-bold text-gray-800 dark:text-white/90">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
<p className="mb-8 text-sm text-gray-500 dark:text-gray-400">
|
||||
{message}
|
||||
</p>
|
||||
|
||||
{requiredInput !== undefined && (
|
||||
<div className="mb-6 w-full text-left">
|
||||
<label className="mb-2 block text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
Type <span className="text-gray-800 dark:text-white font-bold">"{requiredInput}"</span> to confirm
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded-lg border border-gray-200 bg-transparent px-4 py-2.5 text-sm text-gray-800 outline-hidden transition focus:border-brand-500 dark:border-gray-800 dark:text-white"
|
||||
placeholder={requiredInputPlaceholder}
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex w-full flex-col gap-3 sm:flex-row">
|
||||
<Button
|
||||
className="w-full sm:flex-1"
|
||||
variant="outline"
|
||||
onClick={onClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button
|
||||
className={`w-full sm:flex-1 ${styles.confirmBtnClass}`}
|
||||
onClick={onConfirm}
|
||||
loading={isLoading}
|
||||
disabled={isConfirmDisabled}
|
||||
>
|
||||
{confirmLabel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmationModal;
|
||||
25
src/components/common/GridShape.tsx
Normal file
25
src/components/common/GridShape.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
export default function GridShape() {
|
||||
return (
|
||||
<>
|
||||
<div className="absolute right-0 top-0 -z-1 w-full max-w-[250px] xl:max-w-[450px]">
|
||||
<Image
|
||||
width={540}
|
||||
height={254}
|
||||
src="/images/shape/grid-01.svg"
|
||||
alt="grid"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute bottom-0 left-0 -z-1 w-full max-w-[250px] rotate-180 xl:max-w-[450px]">
|
||||
<Image
|
||||
width={540}
|
||||
height={254}
|
||||
src="/images/shape/grid-01.svg"
|
||||
alt="grid"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
137
src/components/common/ImageCropper.tsx
Normal file
137
src/components/common/ImageCropper.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useCallback } from "react";
|
||||
import Cropper from "react-easy-crop";
|
||||
import Button from "../ui/button/Button";
|
||||
import { Modal } from "../ui/modal";
|
||||
|
||||
interface ImageCropperProps {
|
||||
image: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onCropComplete: (croppedImage: Blob) => void;
|
||||
aspectRatio?: number;
|
||||
}
|
||||
|
||||
const ImageCropper: React.FC<ImageCropperProps> = ({
|
||||
image,
|
||||
isOpen,
|
||||
onClose,
|
||||
onCropComplete,
|
||||
aspectRatio = 1,
|
||||
}) => {
|
||||
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
||||
const [zoom, setZoom] = useState(1);
|
||||
const [croppedAreaPixels, setCroppedAreaPixels] = useState<any>(null);
|
||||
|
||||
const onCropChange = useCallback((crop: any) => {
|
||||
setCrop(crop);
|
||||
}, []);
|
||||
|
||||
const onZoomChange = useCallback((zoom: any) => {
|
||||
setZoom(zoom);
|
||||
}, []);
|
||||
|
||||
const onCropCompleteInternal = useCallback(
|
||||
(croppedArea: any, croppedAreaPixels: any) => {
|
||||
setCroppedAreaPixels(croppedAreaPixels);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const createImage = (url: string): Promise<HTMLImageElement> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.addEventListener("load", () => resolve(image));
|
||||
image.addEventListener("error", (error) => reject(error));
|
||||
image.setAttribute("crossOrigin", "anonymous");
|
||||
image.src = url;
|
||||
});
|
||||
|
||||
const getCroppedImg = async (
|
||||
imageSrc: string,
|
||||
pixelCrop: any
|
||||
): Promise<Blob | null> => {
|
||||
const image = await createImage(imageSrc);
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (!ctx) return null;
|
||||
|
||||
canvas.width = pixelCrop.width;
|
||||
canvas.height = pixelCrop.height;
|
||||
|
||||
ctx.drawImage(
|
||||
image,
|
||||
pixelCrop.x,
|
||||
pixelCrop.y,
|
||||
pixelCrop.width,
|
||||
pixelCrop.height,
|
||||
0,
|
||||
0,
|
||||
pixelCrop.width,
|
||||
pixelCrop.height
|
||||
);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
resolve(blob);
|
||||
}, "image/png");
|
||||
});
|
||||
};
|
||||
|
||||
const showCroppedImage = useCallback(async () => {
|
||||
try {
|
||||
const croppedImage = await getCroppedImg(image, croppedAreaPixels);
|
||||
if (croppedImage) {
|
||||
onCropComplete(croppedImage);
|
||||
onClose();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, [croppedAreaPixels, image, onCropComplete, onClose]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} className="max-w-[600px]">
|
||||
<div className="p-6">
|
||||
<h3 className="mb-4 text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Crop Your Avatar
|
||||
</h3>
|
||||
<div className="relative h-80 w-full overflow-hidden rounded-lg bg-gray-100 dark:bg-gray-800">
|
||||
<Cropper
|
||||
image={image}
|
||||
crop={crop}
|
||||
zoom={zoom}
|
||||
aspect={aspectRatio}
|
||||
onCropChange={onCropChange}
|
||||
onCropComplete={onCropCompleteInternal}
|
||||
onZoomChange={onZoomChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6 flex items-center gap-4">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="range"
|
||||
value={zoom}
|
||||
min={1}
|
||||
max={3}
|
||||
step={0.1}
|
||||
aria-labelledby="Zoom"
|
||||
onChange={(e) => setZoom(Number(e.target.value))}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={showCroppedImage}>Update Avatar</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageCropper;
|
||||
52
src/components/common/PageBreadCrumb.tsx
Normal file
52
src/components/common/PageBreadCrumb.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
interface BreadcrumbProps {
|
||||
pageTitle: string;
|
||||
}
|
||||
|
||||
const PageBreadcrumb: React.FC<BreadcrumbProps> = ({ pageTitle }) => {
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 mb-6">
|
||||
<h2
|
||||
className="text-xl font-semibold text-gray-800 dark:text-white/90"
|
||||
x-text="pageName"
|
||||
>
|
||||
{pageTitle}
|
||||
</h2>
|
||||
<nav>
|
||||
<ol className="flex items-center gap-1.5">
|
||||
<li>
|
||||
<Link
|
||||
className="inline-flex items-center gap-1.5 text-sm text-gray-500 dark:text-gray-400"
|
||||
href="/dashboard"
|
||||
>
|
||||
Home
|
||||
<svg
|
||||
className="stroke-current"
|
||||
width="17"
|
||||
height="16"
|
||||
viewBox="0 0 17 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.0765 12.667L10.2432 8.50033L6.0765 4.33366"
|
||||
stroke=""
|
||||
strokeWidth="1.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Link>
|
||||
</li>
|
||||
<li className="text-sm text-gray-800 dark:text-white/90">
|
||||
{pageTitle}
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageBreadcrumb;
|
||||
11
src/components/common/Preloader.tsx
Normal file
11
src/components/common/Preloader.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export default function Preloader() {
|
||||
return (
|
||||
<div className="flex h-full min-h-[60vh] w-full items-center justify-center">
|
||||
<div className="h-12 w-12 animate-spin rounded-full border-4 border-solid border-brand-500 border-t-transparent"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/components/common/ThemeToggle.tsx
Normal file
45
src/components/common/ThemeToggle.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="relative flex items-center justify-center text-gray-500 transition-colors bg-white border border-gray-200 rounded-full hover:text-dark-900 h-11 w-11 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<svg
|
||||
className="hidden dark:block"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.99998 1.5415C10.4142 1.5415 10.75 1.87729 10.75 2.2915V3.5415C10.75 3.95572 10.4142 4.2915 9.99998 4.2915C9.58577 4.2915 9.24998 3.95572 9.24998 3.5415V2.2915C9.24998 1.87729 9.58577 1.5415 9.99998 1.5415ZM10.0009 6.79327C8.22978 6.79327 6.79402 8.22904 6.79402 10.0001C6.79402 11.7712 8.22978 13.207 10.0009 13.207C11.772 13.207 13.2078 11.7712 13.2078 10.0001C13.2078 8.22904 11.772 6.79327 10.0009 6.79327ZM5.29402 10.0001C5.29402 7.40061 7.40135 5.29327 10.0009 5.29327C12.6004 5.29327 14.7078 7.40061 14.7078 10.0001C14.7078 12.5997 12.6004 14.707 10.0009 14.707C7.40135 14.707 5.29402 12.5997 5.29402 10.0001ZM15.9813 5.08035C16.2742 4.78746 16.2742 4.31258 15.9813 4.01969C15.6884 3.7268 15.2135 3.7268 14.9207 4.01969L14.0368 4.90357C13.7439 5.19647 13.7439 5.67134 14.0368 5.96423C14.3297 6.25713 14.8045 6.25713 15.0974 5.96423L15.9813 5.08035ZM18.4577 10.0001C18.4577 10.4143 18.1219 10.7501 17.7077 10.7501H16.4577C16.0435 10.7501 15.7077 10.4143 15.7077 10.0001C15.7077 9.58592 16.0435 9.25013 16.4577 9.25013H17.7077C18.1219 9.25013 18.4577 9.58592 18.4577 10.0001ZM14.9207 15.9806C15.2135 16.2735 15.6884 16.2735 15.9813 15.9806C16.2742 15.6877 16.2742 15.2128 15.9813 14.9199L15.0974 14.036C14.8045 13.7431 14.3297 13.7431 14.0368 14.036C13.7439 14.3289 13.7439 14.8038 14.0368 15.0967L14.9207 15.9806ZM9.99998 15.7088C10.4142 15.7088 10.75 16.0445 10.75 16.4588V17.7088C10.75 18.123 10.4142 18.4588 9.99998 18.4588C9.58577 18.4588 9.24998 18.123 9.24998 17.7088V16.4588C9.24998 16.0445 9.58577 15.7088 9.99998 15.7088ZM5.96356 15.0972C6.25646 14.8043 6.25646 14.3295 5.96356 14.0366C5.67067 13.7437 5.1958 13.7437 4.9029 14.0366L4.01902 14.9204C3.72613 15.2133 3.72613 15.6882 4.01902 15.9811C4.31191 16.274 4.78679 16.274 5.07968 15.9811L5.96356 15.0972ZM4.29224 10.0001C4.29224 10.4143 3.95645 10.7501 3.54224 10.7501H2.29224C1.87802 10.7501 1.54224 10.4143 1.54224 10.0001C1.54224 9.58592 1.87802 9.25013 2.29224 9.25013H3.54224C3.95645 9.25013 4.29224 9.58592 4.29224 10.0001ZM4.9029 5.9637C5.1958 6.25659 5.67067 6.25659 5.96356 5.9637C6.25646 5.6708 6.25646 5.19593 5.96356 4.90303L5.07968 4.01915C4.78679 3.72626 4.31191 3.72626 4.01902 4.01915C3.72613 4.31204 3.72613 4.78692 4.01902 5.07981L4.9029 5.9637Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
className="dark:hidden"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.4547 11.97L18.1799 12.1611C18.265 11.8383 18.1265 11.4982 17.8401 11.3266C17.5538 11.1551 17.1885 11.1934 16.944 11.4207L17.4547 11.97ZM8.0306 2.5459L8.57989 3.05657C8.80718 2.81209 8.84554 2.44682 8.67398 2.16046C8.50243 1.8741 8.16227 1.73559 7.83948 1.82066L8.0306 2.5459ZM12.9154 13.0035C9.64678 13.0035 6.99707 10.3538 6.99707 7.08524H5.49707C5.49707 11.1823 8.81835 14.5035 12.9154 14.5035V13.0035ZM16.944 11.4207C15.8869 12.4035 14.4721 13.0035 12.9154 13.0035V14.5035C14.8657 14.5035 16.6418 13.7499 17.9654 12.5193L16.944 11.4207ZM16.7295 11.7789C15.9437 14.7607 13.2277 16.9586 10.0003 16.9586V18.4586C13.9257 18.4586 17.2249 15.7853 18.1799 12.1611L16.7295 11.7789ZM10.0003 16.9586C6.15734 16.9586 3.04199 13.8433 3.04199 10.0003H1.54199C1.54199 14.6717 5.32892 18.4586 10.0003 18.4586V16.9586ZM3.04199 10.0003C3.04199 6.77289 5.23988 4.05695 8.22173 3.27114L7.83948 1.82066C4.21532 2.77574 1.54199 6.07486 1.54199 10.0003H3.04199ZM6.99707 7.08524C6.99707 5.52854 7.5971 4.11366 8.57989 3.05657L7.48132 2.03522C6.25073 3.35885 5.49707 5.13487 5.49707 7.08524H6.99707Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
42
src/components/common/ThemeToggleButton.tsx
Normal file
42
src/components/common/ThemeToggleButton.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
|
||||
export const ThemeToggleButton: React.FC = () => {
|
||||
const { toggleTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="relative flex items-center justify-center text-gray-500 transition-colors bg-white border border-gray-200 rounded-full hover:text-dark-900 h-11 w-11 hover:bg-gray-100 hover:text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
>
|
||||
<svg
|
||||
className="hidden dark:block"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.99998 1.5415C10.4142 1.5415 10.75 1.87729 10.75 2.2915V3.5415C10.75 3.95572 10.4142 4.2915 9.99998 4.2915C9.58577 4.2915 9.24998 3.95572 9.24998 3.5415V2.2915C9.24998 1.87729 9.58577 1.5415 9.99998 1.5415ZM10.0009 6.79327C8.22978 6.79327 6.79402 8.22904 6.79402 10.0001C6.79402 11.7712 8.22978 13.207 10.0009 13.207C11.772 13.207 13.2078 11.7712 13.2078 10.0001C13.2078 8.22904 11.772 6.79327 10.0009 6.79327ZM5.29402 10.0001C5.29402 7.40061 7.40135 5.29327 10.0009 5.29327C12.6004 5.29327 14.7078 7.40061 14.7078 10.0001C14.7078 12.5997 12.6004 14.707 10.0009 14.707C7.40135 14.707 5.29402 12.5997 5.29402 10.0001ZM15.9813 5.08035C16.2742 4.78746 16.2742 4.31258 15.9813 4.01969C15.6884 3.7268 15.2135 3.7268 14.9207 4.01969L14.0368 4.90357C13.7439 5.19647 13.7439 5.67134 14.0368 5.96423C14.3297 6.25713 14.8045 6.25713 15.0974 5.96423L15.9813 5.08035ZM18.4577 10.0001C18.4577 10.4143 18.1219 10.7501 17.7077 10.7501H16.4577C16.0435 10.7501 15.7077 10.4143 15.7077 10.0001C15.7077 9.58592 16.0435 9.25013 16.4577 9.25013H17.7077C18.1219 9.25013 18.4577 9.58592 18.4577 10.0001ZM14.9207 15.9806C15.2135 16.2735 15.6884 16.2735 15.9813 15.9806C16.2742 15.6877 16.2742 15.2128 15.9813 14.9199L15.0974 14.036C14.8045 13.7431 14.3297 13.7431 14.0368 14.036C13.7439 14.3289 13.7439 14.8038 14.0368 15.0967L14.9207 15.9806ZM9.99998 15.7088C10.4142 15.7088 10.75 16.0445 10.75 16.4588V17.7088C10.75 18.123 10.4142 18.4588 9.99998 18.4588C9.58577 18.4588 9.24998 18.123 9.24998 17.7088V16.4588C9.24998 16.0445 9.58577 15.7088 9.99998 15.7088ZM5.96356 15.0972C6.25646 14.8043 6.25646 14.3295 5.96356 14.0366C5.67067 13.7437 5.1958 13.7437 4.9029 14.0366L4.01902 14.9204C3.72613 15.2133 3.72613 15.6882 4.01902 15.9811C4.31191 16.274 4.78679 16.274 5.07968 15.9811L5.96356 15.0972ZM4.29224 10.0001C4.29224 10.4143 3.95645 10.7501 3.54224 10.7501H2.29224C1.87802 10.7501 1.54224 10.4143 1.54224 10.0001C1.54224 9.58592 1.87802 9.25013 2.29224 9.25013H3.54224C3.95645 9.25013 4.29224 9.58592 4.29224 10.0001ZM4.9029 5.9637C5.1958 6.25659 5.67067 6.25659 5.96356 5.9637C6.25646 5.6708 6.25646 5.19593 5.96356 4.90303L5.07968 4.01915C4.78679 3.72626 4.31191 3.72626 4.01902 4.01915C3.72613 4.31204 3.72613 4.78692 4.01902 5.07981L4.9029 5.9637Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
className="dark:hidden"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.4547 11.97L18.1799 12.1611C18.265 11.8383 18.1265 11.4982 17.8401 11.3266C17.5538 11.1551 17.1885 11.1934 16.944 11.4207L17.4547 11.97ZM8.0306 2.5459L8.57989 3.05657C8.80718 2.81209 8.84554 2.44682 8.67398 2.16046C8.50243 1.8741 8.16227 1.73559 7.83948 1.82066L8.0306 2.5459ZM12.9154 13.0035C9.64678 13.0035 6.99707 10.3538 6.99707 7.08524H5.49707C5.49707 11.1823 8.81835 14.5035 12.9154 14.5035V13.0035ZM16.944 11.4207C15.8869 12.4035 14.4721 13.0035 12.9154 13.0035V14.5035C14.8657 14.5035 16.6418 13.7499 17.9654 12.5193L16.944 11.4207ZM16.7295 11.7789C15.9437 14.7607 13.2277 16.9586 10.0003 16.9586V18.4586C13.9257 18.4586 17.2249 15.7853 18.1799 12.1611L16.7295 11.7789ZM10.0003 16.9586C6.15734 16.9586 3.04199 13.8433 3.04199 10.0003H1.54199C1.54199 14.6717 5.32892 18.4586 10.0003 18.4586V16.9586ZM3.04199 10.0003C3.04199 6.77289 5.23988 4.05695 8.22173 3.27114L7.83948 1.82066C4.21532 2.77574 1.54199 6.07486 1.54199 10.0003H3.04199ZM6.99707 7.08524C6.99707 5.52854 7.5971 4.11366 8.57989 3.05657L7.48132 2.03522C6.25073 3.35885 5.49707 5.13487 5.49707 7.08524H6.99707Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
42
src/components/common/ThemeTogglerTwo.tsx
Normal file
42
src/components/common/ThemeTogglerTwo.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
import { useTheme } from "@/context/ThemeContext";
|
||||
import React from "react";
|
||||
|
||||
export default function ThemeTogglerTwo() {
|
||||
const { toggleTheme } = useTheme();
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="inline-flex size-14 items-center justify-center rounded-full bg-brand-500 text-white transition-colors hover:bg-brand-600"
|
||||
>
|
||||
<svg
|
||||
className="hidden dark:block"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.99998 1.5415C10.4142 1.5415 10.75 1.87729 10.75 2.2915V3.5415C10.75 3.95572 10.4142 4.2915 9.99998 4.2915C9.58577 4.2915 9.24998 3.95572 9.24998 3.5415V2.2915C9.24998 1.87729 9.58577 1.5415 9.99998 1.5415ZM10.0009 6.79327C8.22978 6.79327 6.79402 8.22904 6.79402 10.0001C6.79402 11.7712 8.22978 13.207 10.0009 13.207C11.772 13.207 13.2078 11.7712 13.2078 10.0001C13.2078 8.22904 11.772 6.79327 10.0009 6.79327ZM5.29402 10.0001C5.29402 7.40061 7.40135 5.29327 10.0009 5.29327C12.6004 5.29327 14.7078 7.40061 14.7078 10.0001C14.7078 12.5997 12.6004 14.707 10.0009 14.707C7.40135 14.707 5.29402 12.5997 5.29402 10.0001ZM15.9813 5.08035C16.2742 4.78746 16.2742 4.31258 15.9813 4.01969C15.6884 3.7268 15.2135 3.7268 14.9207 4.01969L14.0368 4.90357C13.7439 5.19647 13.7439 5.67134 14.0368 5.96423C14.3297 6.25713 14.8045 6.25713 15.0974 5.96423L15.9813 5.08035ZM18.4577 10.0001C18.4577 10.4143 18.1219 10.7501 17.7077 10.7501H16.4577C16.0435 10.7501 15.7077 10.4143 15.7077 10.0001C15.7077 9.58592 16.0435 9.25013 16.4577 9.25013H17.7077C18.1219 9.25013 18.4577 9.58592 18.4577 10.0001ZM14.9207 15.9806C15.2135 16.2735 15.6884 16.2735 15.9813 15.9806C16.2742 15.6877 16.2742 15.2128 15.9813 14.9199L15.0974 14.036C14.8045 13.7431 14.3297 13.7431 14.0368 14.036C13.7439 14.3289 13.7439 14.8038 14.0368 15.0967L14.9207 15.9806ZM9.99998 15.7088C10.4142 15.7088 10.75 16.0445 10.75 16.4588V17.7088C10.75 18.123 10.4142 18.4588 9.99998 18.4588C9.58577 18.4588 9.24998 18.123 9.24998 17.7088V16.4588C9.24998 16.0445 9.58577 15.7088 9.99998 15.7088ZM5.96356 15.0972C6.25646 14.8043 6.25646 14.3295 5.96356 14.0366C5.67067 13.7437 5.1958 13.7437 4.9029 14.0366L4.01902 14.9204C3.72613 15.2133 3.72613 15.6882 4.01902 15.9811C4.31191 16.274 4.78679 16.274 5.07968 15.9811L5.96356 15.0972ZM4.29224 10.0001C4.29224 10.4143 3.95645 10.7501 3.54224 10.7501H2.29224C1.87802 10.7501 1.54224 10.4143 1.54224 10.0001C1.54224 9.58592 1.87802 9.25013 2.29224 9.25013H3.54224C3.95645 9.25013 4.29224 9.58592 4.29224 10.0001ZM4.9029 5.9637C5.1958 6.25659 5.67067 6.25659 5.96356 5.9637C6.25646 5.6708 6.25646 5.19593 5.96356 4.90303L5.07968 4.01915C4.78679 3.72626 4.31191 3.72626 4.01902 4.01915C3.72613 4.31204 3.72613 4.78692 4.01902 5.07981L4.9029 5.9637Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
className="dark:hidden"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.4547 11.97L18.1799 12.1611C18.265 11.8383 18.1265 11.4982 17.8401 11.3266C17.5538 11.1551 17.1885 11.1934 16.944 11.4207L17.4547 11.97ZM8.0306 2.5459L8.57989 3.05657C8.80718 2.81209 8.84554 2.44682 8.67398 2.16046C8.50243 1.8741 8.16227 1.73559 7.83948 1.82066L8.0306 2.5459ZM12.9154 13.0035C9.64678 13.0035 6.99707 10.3538 6.99707 7.08524H5.49707C5.49707 11.1823 8.81835 14.5035 12.9154 14.5035V13.0035ZM16.944 11.4207C15.8869 12.4035 14.4721 13.0035 12.9154 13.0035V14.5035C14.8657 14.5035 16.6418 13.7499 17.9654 12.5193L16.944 11.4207ZM16.7295 11.7789C15.9437 14.7607 13.2277 16.9586 10.0003 16.9586V18.4586C13.9257 18.4586 17.2249 15.7853 18.1799 12.1611L16.7295 11.7789ZM10.0003 16.9586C6.15734 16.9586 3.04199 13.8433 3.04199 10.0003H1.54199C1.54199 14.6717 5.32892 18.4586 10.0003 18.4586V16.9586ZM3.04199 10.0003C3.04199 6.77289 5.23988 4.05695 8.22173 3.27114L7.83948 1.82066C4.21532 2.77574 1.54199 6.07486 1.54199 10.0003H3.04199ZM6.99707 7.08524C6.99707 5.52854 7.5971 4.11366 8.57989 3.05657L7.48132 2.03522C6.25073 3.35885 5.49707 5.13487 5.49707 7.08524H6.99707Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user