feat(admin): full-screen forms + WYSIWYG rich-text editor
- AdminResource + TemplatesAdmin modals are now large full-height panels (max-w-5xl, sticky header/footer, scrolling body, 2-column field grid; textarea/richtext span full width) - RichTextField: dependency-free contentEditable WYSIWYG (bold/italic/underline, H2/H3/¶, lists, link, clear) emitting HTML, dir="auto" for fa/en - blog content + category description now use the rich editor Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,11 +4,12 @@ import { useCallback, useEffect, useState, type ReactNode } from "react";
|
||||
|
||||
import { FileUploadField } from "@/components/admin/FileUploadField";
|
||||
import { AdminThumb } from "@/components/admin/AdminThumb";
|
||||
import { RichTextField } from "@/components/admin/RichTextField";
|
||||
|
||||
export interface FieldDef {
|
||||
key: string;
|
||||
label: string;
|
||||
type?: "text" | "textarea" | "number" | "checkbox" | "select" | "image" | "file";
|
||||
type?: "text" | "textarea" | "richtext" | "number" | "checkbox" | "select" | "image" | "file";
|
||||
options?: { value: string; label: string }[];
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
@@ -193,21 +194,26 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
</div>
|
||||
|
||||
{(creating || editing) && config.fields && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4" onClick={closeForm}>
|
||||
<div className={`${card} w-full max-w-lg p-5`} onClick={(e) => e.stopPropagation()}>
|
||||
<h2 className="text-sm font-semibold text-white">
|
||||
{editing ? "ویرایش" : "افزودن"} — {config.title}
|
||||
</h2>
|
||||
<div className="mt-4 grid max-h-[60vh] gap-3 overflow-y-auto pr-1">
|
||||
<div className="fixed inset-0 z-50 flex items-stretch justify-center bg-black/70 p-2 sm:p-6" onClick={closeForm}>
|
||||
<div className={`${card} flex max-h-full w-full max-w-5xl flex-col`} onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-between border-b border-[#1e2235] px-5 py-3">
|
||||
<h2 className="text-sm font-semibold text-white">
|
||||
{editing ? "ویرایش" : "افزودن"} — {config.title}
|
||||
</h2>
|
||||
<button className="rounded-lg px-2 py-1 text-gray-400 hover:bg-[#161a2e] hover:text-white" onClick={closeForm}>✕</button>
|
||||
</div>
|
||||
<div className="grid flex-1 grid-cols-1 gap-4 overflow-y-auto p-5 sm:grid-cols-2">
|
||||
{config.fields.map((f) => (
|
||||
<div key={f.key}>
|
||||
<div key={f.key} className={f.type === "textarea" || f.type === "richtext" ? "sm:col-span-2" : ""}>
|
||||
{f.type !== "checkbox" && (
|
||||
<label className="mb-1 block text-xs font-medium text-gray-400">
|
||||
{f.label}{f.required && <span className="text-red-400"> *</span>}
|
||||
</label>
|
||||
)}
|
||||
{f.type === "textarea" ? (
|
||||
<textarea className={`${inputCls} min-h-[80px]`} placeholder={f.placeholder}
|
||||
{f.type === "richtext" ? (
|
||||
<RichTextField value={String(form[f.key] ?? "")} onChange={(html) => setForm({ ...form, [f.key]: html })} />
|
||||
) : f.type === "textarea" ? (
|
||||
<textarea className={`${inputCls} min-h-[160px]`} placeholder={f.placeholder}
|
||||
value={String(form[f.key] ?? "")} onChange={(e) => setForm({ ...form, [f.key]: e.target.value })} />
|
||||
) : f.type === "select" ? (
|
||||
<select className={inputCls} value={String(form[f.key] ?? "")}
|
||||
@@ -234,7 +240,7 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-5 flex items-center justify-end gap-2">
|
||||
<div className="flex items-center justify-end gap-2 border-t border-[#1e2235] px-5 py-3">
|
||||
<button className={btnGhost} onClick={closeForm}>انصراف</button>
|
||||
<button className={btn} onClick={submit} disabled={saving}>{saving ? "در حال ذخیره…" : "ذخیره"}</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user