Files
clinicpro/assets/admin/components/ImageCropModal.tsx
T
hamed 55ab2f5dfc Implement comprehensive dark/light mode overhaul for admin panel
- Refactor color palette in `ui-design-spec.md` to utilize CSS variables exclusively, eliminating fixed hex values and Tailwind utility classes.
- Complete dark mode implementation in `uiStore.ts`, ensuring proper theme application via `applyTheme()` and `applyBrand()`.
- Create `admin-theme-dark-light-audit.md` to document the transition process, outlining issues with inline styles and fixed colors.
- Introduce `theme-tokens.test.ts` to enforce rules against fixed hex colors and ensure compliance with the design system.
- Update various components and styles to replace inline styles and fixed colors with CSS variables, ensuring consistent theming across light and dark modes.
- Ensure all changes maintain visual integrity in both light and dark modes, with a focus on accessibility and contrast standards.
2026-07-27 16:41:52 +03:30

78 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useState } from 'react';
import { createPortal } from 'react-dom';
import Cropper, { type Area } from 'react-easy-crop';
import { getCroppedImage } from '../lib/cropImage';
interface ImageCropModalProps {
src: string;
fileName: string;
onCancel: () => void;
onConfirm: (file: File) => void;
}
export default function ImageCropModal({ src, fileName, onCancel, onConfirm }: ImageCropModalProps) {
const [crop, setCrop] = useState({ x: 0, y: 0 });
const [zoom, setZoom] = useState(1);
const [area, setArea] = useState<Area | null>(null);
const [processing, setProcessing] = useState(false);
const onCropComplete = useCallback((_: Area, pixels: Area) => setArea(pixels), []);
const handleConfirm = async () => {
if (!area) return;
setProcessing(true);
try {
const file = await getCroppedImage(src, area, fileName);
onConfirm(file);
} finally {
setProcessing(false);
}
};
return createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4" dir="rtl">
<div className="w-full max-w-md rounded-2xl bg-[var(--surface)] shadow-2xl overflow-hidden">
<div className="flex items-center justify-between px-5 py-4 border-b border-[var(--border)]">
<p className="text-base font-bold text-[var(--text)]">برش تصویر</p>
<button onClick={onCancel}
className="text-[var(--text-3)] hover:text-[var(--text-2)] text-xl leading-none">×</button>
</div>
<div className="relative w-full h-72 bg-[var(--surface-2)]">
<Cropper
image={src}
crop={crop}
zoom={zoom}
aspect={1}
cropShape="round"
showGrid={false}
onCropChange={setCrop}
onZoomChange={setZoom}
onCropComplete={onCropComplete}
/>
</div>
<div className="px-5 py-4 space-y-4">
<div className="flex items-center gap-3">
<span className="text-xs text-[var(--text-2)] shrink-0">بزرگ‌نمایی</span>
<input type="range" min={1} max={3} step={0.01} value={zoom}
onChange={e => setZoom(Number(e.target.value))}
className="w-full accent-indigo-600" />
</div>
<div className="flex items-center justify-end gap-3">
<button onClick={onCancel} disabled={processing}
className="px-4 py-2 rounded-xl text-sm font-medium text-[var(--text-2)] bg-[var(--surface-2)] hover:bg-[var(--surface-3)] dark:hover:bg-[var(--surface-2)] disabled:opacity-50">
انصراف
</button>
<button onClick={handleConfirm} disabled={processing || !area}
className="px-4 py-2 rounded-xl text-sm font-medium text-[var(--on-primary)] bg-[var(--primary-600)] hover:bg-[var(--primary-700)] disabled:opacity-50">
{processing ? 'در حال برش...' : 'ذخیره'}
</button>
</div>
</div>
</div>
</div>,
document.body,
);
}