diff --git a/admin-spa/src/routes/Appearance/Pages/components/SectionEditor.tsx b/admin-spa/src/routes/Appearance/Pages/components/SectionEditor.tsx
index 717ecd1..07636de 100644
--- a/admin-spa/src/routes/Appearance/Pages/components/SectionEditor.tsx
+++ b/admin-spa/src/routes/Appearance/Pages/components/SectionEditor.tsx
@@ -1,4 +1,21 @@
import React, { useState } from 'react';
+import {
+ DndContext,
+ closestCenter,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ DragEndEvent,
+} from '@dnd-kit/core';
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ useSortable,
+ verticalListSortingStrategy,
+} from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
import { __ } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
@@ -30,6 +47,7 @@ interface SectionEditorProps {
onAddSection: (type: string) => void;
onDeleteSection: (id: string) => void;
onMoveSection: (id: string, direction: 'up' | 'down') => void;
+ onReorderSections: (sections: Section[]) => void;
isTemplate: boolean;
cpt?: string;
isLoading: boolean;
@@ -44,6 +62,120 @@ const SECTION_TYPES = [
{ type: 'contact-form', label: 'Contact Form', icon: MessageSquare },
];
+// Sortable Section Card Component
+function SortableSectionCard({
+ section,
+ index,
+ totalCount,
+ isSelected,
+ onSelect,
+ onDelete,
+ onMove,
+}: {
+ section: Section;
+ index: number;
+ totalCount: number;
+ isSelected: boolean;
+ onSelect: () => void;
+ onDelete: () => void;
+ onMove: (direction: 'up' | 'down') => void;
+}) {
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ isDragging,
+ } = useSortable({ id: section.id });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ };
+
+ const sectionType = SECTION_TYPES.find(s => s.type === section.type);
+ const Icon = sectionType?.icon || LayoutTemplate;
+ const hasDynamic = Object.values(section.props).some(
+ p => typeof p === 'object' && p?.type === 'dynamic'
+ );
+
+ return (
+
+
+
e.stopPropagation()}
+ >
+
+
+
+
+
+
+
+
+
+ {sectionType?.label || section.type}
+
+
+ {section.layoutVariant || 'default'}
+ {hasDynamic && (
+ ◆ {__('Dynamic')}
+ )}
+
+
+
+
+
e.stopPropagation()}>
+
+
+
+
+
+
+ );
+}
+
export function SectionEditor({
sections,
selectedSection,
@@ -51,10 +183,33 @@ export function SectionEditor({
onAddSection,
onDeleteSection,
onMoveSection,
+ onReorderSections,
isTemplate,
cpt,
isLoading,
}: SectionEditorProps) {
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 8,
+ },
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event;
+
+ if (over && active.id !== over.id) {
+ const oldIndex = sections.findIndex(s => s.id === active.id);
+ const newIndex = sections.findIndex(s => s.id === over.id);
+ const newSections = arrayMove(sections, oldIndex, newIndex);
+ onReorderSections(newSections);
+ }
+ };
+
if (isLoading) {
return (
@@ -75,91 +230,39 @@ export function SectionEditor({
)}
- {/* Sections List */}
-
- {sections.map((section, index) => {
- const sectionType = SECTION_TYPES.find(s => s.type === section.type);
- const Icon = sectionType?.icon || LayoutTemplate;
- const hasDynamic = Object.values(section.props).some(
- p => typeof p === 'object' && p?.type === 'dynamic'
- );
-
- return (
-
onSelectSection(section)}
- >
-
-
-
-
-
-
-
-
-
- {sectionType?.label || section.type}
-
-
- {section.layoutVariant || 'default'}
- {hasDynamic && (
- ◆ {__('Dynamic')}
- )}
-
-
-
-
-
e.stopPropagation()}>
-
-
-
-
-
-
- );
- })}
-
- {sections.length === 0 && (
-
-
-
{__('No sections yet. Add your first section.')}
+ {/* Sections List with Drag-and-Drop */}
+
+ s.id)}
+ strategy={verticalListSortingStrategy}
+ >
+
+ {sections.map((section, index) => (
+ onSelectSection(section)}
+ onDelete={() => onDeleteSection(section.id)}
+ onMove={(direction) => onMoveSection(section.id, direction)}
+ />
+ ))}
- )}
-
+
+
+
+ {sections.length === 0 && (
+
+
+
{__('No sections yet. Add your first section.')}
+
+ )}
{/* Add Section Button */}
diff --git a/admin-spa/src/routes/Appearance/Pages/index.tsx b/admin-spa/src/routes/Appearance/Pages/index.tsx
index 77f69ab..7d93a5c 100644
--- a/admin-spa/src/routes/Appearance/Pages/index.tsx
+++ b/admin-spa/src/routes/Appearance/Pages/index.tsx
@@ -161,6 +161,13 @@ export default function AppearancePages() {
setHasUnsavedChanges(true);
};
+ // Reorder sections (drag-and-drop)
+ const handleReorderSections = (newSections: Section[]) => {
+ if (!structure) return;
+ setStructure({ ...structure, sections: newSections });
+ setHasUnsavedChanges(true);
+ };
+
return (
{/* Header */}
@@ -217,6 +224,7 @@ export default function AppearancePages() {
onAddSection={handleAddSection}
onDeleteSection={handleDeleteSection}
onMoveSection={handleMoveSection}
+ onReorderSections={handleReorderSections}
isTemplate={selectedPage.type === 'template'}
cpt={selectedPage.cpt}
isLoading={pageLoading}