diff --git a/components/admin/extra-sheet-editor.tsx b/components/admin/extra-sheet-editor.tsx
index 4acecc6..f5c1013 100644
--- a/components/admin/extra-sheet-editor.tsx
+++ b/components/admin/extra-sheet-editor.tsx
@@ -6,18 +6,48 @@ import {
ArrowRightIcon,
ArrowUpIcon,
Columns3Icon,
+ GripHorizontalIcon,
+ GripVerticalIcon,
PlusIcon,
PencilIcon,
PaletteIcon,
+ Redo2Icon,
Rows3Icon,
Trash2Icon,
+ Undo2Icon,
} from "lucide-react";
-import Spreadsheet, { type ColumnIndicatorProps, type DataEditorProps, type Point } from "react-spreadsheet";
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import Spreadsheet, {
+ type ColumnIndicatorProps,
+ type DataEditorProps,
+ type Point,
+ type RowIndicatorProps,
+ type SpreadsheetRef,
+} from "react-spreadsheet";
+import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from "react";
+import {
+ closestCenter,
+ DndContext,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ type CollisionDetection,
+ type DragEndEvent,
+} from "@dnd-kit/core";
+import {
+ horizontalListSortingStrategy,
+ rectSortingStrategy,
+ sortableKeyboardCoordinates,
+ SortableContext,
+ useSortable,
+ verticalListSortingStrategy,
+} from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog";
import { RichTextContent } from "@/components/rich-text-content";
import { RichTextEditor } from "@/components/rich-text-editor";
+import { SimpleTooltip } from "@/components/tooltip";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -38,6 +68,7 @@ import {
} from "@/components/ui/dropdown-menu";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
+import { Kbd } from "@/components/ui/kbd";
import {
addExtraSheetColumn,
addExtraSheetRow,
@@ -48,15 +79,47 @@ import {
moveExtraSheetColumn,
moveExtraSheetRow,
nextExtraSheetColumnLabel,
+ styleExtraSheetCells,
updateExtraSheetCell,
updateExtraSheetCells,
type ExtraSheet,
type ExtraSheetMatrix,
} from "@/lib/guides/extra-sheet";
-import { normalizeExtraRichText } from "@/lib/rich-text";
+import {
+ normalizeExtraRichText,
+ richTextContentSignature,
+ type RichTextStylePatch,
+} from "@/lib/rich-text";
+import { cn } from "@/lib/utils";
type DeleteTarget = "column" | "row" | null;
+interface SheetHistory {
+ items: ExtraSheet[];
+ index: number;
+ value: ExtraSheet;
+}
+
const DEFAULT_CELL_BACKGROUND = "#16A34A";
+const SHEET_HISTORY_LIMIT = 80;
+const columnDragId = (id: string) => `column:${id}`;
+const rowDragId = (id: string) => `row:${id}`;
+
+const sameAxisCollision: CollisionDetection = (args) => {
+ const axis = String(args.active.id).split(":", 1)[0];
+ return closestCenter({
+ ...args,
+ droppableContainers: args.droppableContainers.filter(
+ (container) => String(container.id).startsWith(`${axis}:`),
+ ),
+ });
+};
+
+function movedIndex(current: number, from: number, to: number): number {
+ if (current === from) return to;
+ if (from < to && current > from && current <= to) return current - 1;
+ if (to < from && current >= to && current < from) return current + 1;
+ return current;
+}
function LiteralDataViewer({ cell }: { cell?: { value?: unknown; backgroundColor?: string } }) {
return (
@@ -86,18 +149,38 @@ function DialogDataEditor({
}
function EditableColumnIndicator({
+ id,
column,
label,
selected,
onRename,
-}: ColumnIndicatorProps & { onRename: (column: number) => void }) {
+}: ColumnIndicatorProps & { id: string; onRename: (column: number) => void }) {
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
+ id,
+ strategy: horizontalListSortingStrategy,
+ });
return (
-
+ |
+
|