add setting

This commit is contained in:
hamid zarghami
2026-05-06 12:33:23 +03:30
parent e15dc76468
commit 192036e519
7 changed files with 384 additions and 30 deletions
@@ -0,0 +1,261 @@
import { useEffect, useRef, useState } from 'react'
import { clx } from '@/helpers/utils'
import { useEditorStore } from '../store/editorStore'
import type { DisplayStyle, BackgroundType } from '../store/editorStore'
import UploadBoxDraggble from '@/components/UploadBoxDraggble'
import { useSingleUpload } from '@/pages/uploader/hooks/useUploaderData'
import ColorsImage from '@/assets/images/colors.png'
const PRESET_COLORS = [
'#a8edcf',
'#e91e8c',
'#ff9800',
'#6d8b8b',
'#111111',
'#e53935',
'#e0e0e0',
]
const DISPLAY_STYLE_OPTIONS: { value: DisplayStyle; label: string }[] = [
{ value: 'single', label: 'تک صفحه ای' },
{ value: 'double', label: 'دو صفحه ای' },
]
const BACKGROUND_TYPE_OPTIONS: { value: BackgroundType; label: string }[] = [
{ value: 'color', label: 'رنگ' },
{ value: 'image', label: 'تصویر' },
]
const SettingsPanel = () => {
const { documentSettings, updateDocumentSettings } = useEditorStore()
const colorInputRef = useRef<HTMLInputElement>(null)
const { mutateAsync: uploadFile, isPending: imageLoading } = useSingleUpload()
const [uploadedPreviews, setUploadedPreviews] = useState<string[]>(
documentSettings.backgroundImageUrl ? [documentSettings.backgroundImageUrl] : []
)
const {
displayStyle,
autoPlay,
pageFlipSound,
leftToRightFlip,
backgroundType,
backgroundColor,
} = documentSettings
useEffect(() => {
setUploadedPreviews(
documentSettings.backgroundImageUrl ? [documentSettings.backgroundImageUrl] : []
)
}, [documentSettings.backgroundImageUrl])
const handleImageFiles = async (files: File[]) => {
if (files.length === 0) return
const file = files[files.length - 1]
if (!file) return
try {
const result = await uploadFile(file)
const imageUrl = result?.data?.url
if (!imageUrl) return
updateDocumentSettings({ backgroundImageUrl: imageUrl })
setUploadedPreviews([imageUrl])
} catch {
// TODO: show error toast
}
}
const handlePreviewChange = (previews: string[]) => {
setUploadedPreviews(previews)
if (previews.length === 0) {
updateDocumentSettings({ backgroundImageUrl: '' })
}
}
return (
<div className="flex flex-col gap-0 overflow-y-auto h-full text-right" dir="rtl">
{/* ─── تنظیمات نمایش ─── */}
<section className="p-4 border-b border-border">
<div className="flex items-center gap-2 mb-4">
<h3 className="text-sm font-semibold text-gray-800">تنظیمات نمایش</h3>
</div>
{/* استایل نمایش */}
<div className="mb-4">
<label className="block text-xs text-gray-500 mb-2">استایل نمایش</label>
<div className="relative">
<select
value={displayStyle}
onChange={(e) =>
updateDocumentSettings({ displayStyle: e.target.value as DisplayStyle })
}
className="w-full appearance-none border border-border rounded-xl px-3 py-2.5 text-sm bg-white text-right pr-3 pl-8 cursor-pointer focus:outline-none focus:ring-2 focus:ring-gray-200"
>
{DISPLAY_STYLE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
<span className="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-gray-400">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</div>
</div>
{/* چک‌باکس‌ها */}
<div className="flex flex-col gap-3">
<CheckboxRow
label="پخش خودکار"
checked={autoPlay}
onChange={(v) => updateDocumentSettings({ autoPlay: v })}
/>
<CheckboxRow
label="صدای برگ خوردن"
checked={pageFlipSound}
onChange={(v) => updateDocumentSettings({ pageFlipSound: v })}
/>
<CheckboxRow
label="چپ به راست ورق خوردن"
checked={leftToRightFlip}
onChange={(v) => updateDocumentSettings({ leftToRightFlip: v })}
/>
</div>
</section>
{/* ─── پس زمینه ─── */}
<section className="p-4">
<h3 className="text-sm font-semibold text-gray-800 mb-4">پس زمینه</h3>
{/* نوع پس‌زمینه */}
<div className="flex items-center gap-5 mb-4">
{BACKGROUND_TYPE_OPTIONS.map((opt) => (
<label key={opt.value} className="flex items-center gap-1.5 cursor-pointer select-none">
<span className="text-xs text-gray-600">{opt.label}</span>
<div
onClick={() => updateDocumentSettings({ backgroundType: opt.value })}
className={clx(
'w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors cursor-pointer',
backgroundType === opt.value
? 'border-gray-800 bg-gray-800'
: 'border-gray-300 bg-white'
)}
>
{backgroundType === opt.value && (
<div className="w-1.5 h-1.5 rounded-full bg-white" />
)}
</div>
</label>
))}
</div>
{/* ─── انتخاب رنگ ─── */}
{backgroundType === 'color' && (
<div>
<p className="text-xs text-gray-500 mb-3">انتخاب رنگ</p>
<div className="flex items-center gap-2 flex-wrap">
{PRESET_COLORS.map((color) => (
<button
key={color}
onClick={() => updateDocumentSettings({ backgroundColor: color })}
className={clx(
'w-9 h-9 rounded-xl border-2 transition-all',
backgroundColor === color
? 'border-gray-800 scale-110'
: 'border-transparent hover:scale-105'
)}
style={{ backgroundColor: color }}
title={color}
/>
))}
{/* Color Wheel */}
<button
onClick={() => colorInputRef.current?.click()}
className="w-9 h-9 rounded-xl border-2 border-transparent hover:scale-105 transition-all overflow-hidden relative"
title="رنگ دلخواه"
>
<img src={ColorsImage} alt="انتخاب رنگ" className="w-full h-full object-cover" />
<input
ref={colorInputRef}
type="color"
value={backgroundColor}
onChange={(e) => updateDocumentSettings({ backgroundColor: e.target.value })}
className="absolute inset-0 opacity-0 w-full h-full cursor-pointer"
/>
</button>
</div>
{/* نمایش رنگ انتخاب‌شده */}
<div className="mt-3 flex items-center gap-2">
<div
className="w-6 h-6 rounded-lg border border-border"
style={{ backgroundColor }}
/>
<span className="text-xs text-gray-500 font-mono">{backgroundColor}</span>
</div>
</div>
)}
{/* ─── آپلود تصویر ─── */}
{backgroundType === 'image' && (
<div>
{imageLoading ? (
<div className="flex flex-col items-center justify-center gap-3 py-8">
<span className="h-6 w-6 animate-spin rounded-full border-2 border-gray-300 border-t-gray-800" />
<span className="text-xs text-gray-500">در حال آپلود...</span>
</div>
) : (
<UploadBoxDraggble
label="تصویر پس‌زمینه را آپلود کنید"
onChange={handleImageFiles}
isMultiple={false}
preview={uploadedPreviews}
onChangePreview={handlePreviewChange}
/>
)}
</div>
)}
</section>
</div>
)
}
type CheckboxRowProps = {
label: string
checked: boolean
onChange: (value: boolean) => void
}
const CheckboxRow = ({ label, checked, onChange }: CheckboxRowProps) => (
<label className="flex items-center justify-between cursor-pointer select-none group">
<span className="text-sm text-gray-700">{label}</span>
<div
onClick={() => onChange(!checked)}
className={clx(
'w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all',
checked
? 'border-gray-800 bg-gray-800'
: 'border-gray-300 bg-white group-hover:border-gray-400'
)}
>
{checked && (
<svg width="10" height="8" viewBox="0 0 10 8" fill="none">
<path
d="M1 4l3 3 5-6"
stroke="white"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
</label>
)
export default SettingsPanel