feat : improve extra section
This commit is contained in:
@@ -159,6 +159,11 @@
|
|||||||
color: var(--accent-foreground);
|
color: var(--accent-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.extra-sheet-grid .Spreadsheet__data-viewer,
|
||||||
|
.extra-sheet-grid .Spreadsheet__data-viewer * {
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
[data-slot="button"],
|
[data-slot="button"],
|
||||||
[data-slot="toggle-group-item"],
|
[data-slot="toggle-group-item"],
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
|
||||||
ArrowLeftIcon,
|
|
||||||
ArrowRightIcon,
|
|
||||||
ArrowUpIcon,
|
|
||||||
Columns3Icon,
|
|
||||||
GripHorizontalIcon,
|
GripHorizontalIcon,
|
||||||
GripVerticalIcon,
|
GripVerticalIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
PaletteIcon,
|
PaletteIcon,
|
||||||
Redo2Icon,
|
Redo2Icon,
|
||||||
Rows3Icon,
|
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
Undo2Icon,
|
Undo2Icon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -57,23 +51,12 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
|
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Kbd } from "@/components/ui/kbd";
|
import { Kbd } from "@/components/ui/kbd";
|
||||||
import {
|
import {
|
||||||
addExtraSheetColumn,
|
addExtraSheetColumn,
|
||||||
addExtraSheetRow,
|
addExtraSheetRow,
|
||||||
deleteExtraSheetColumn,
|
|
||||||
deleteExtraSheetRow,
|
|
||||||
extraSheetToMatrix,
|
extraSheetToMatrix,
|
||||||
matrixToExtraSheet,
|
matrixToExtraSheet,
|
||||||
moveExtraSheetColumn,
|
moveExtraSheetColumn,
|
||||||
@@ -92,7 +75,6 @@ import {
|
|||||||
} from "@/lib/rich-text";
|
} from "@/lib/rich-text";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
type DeleteTarget = "column" | "row" | null;
|
|
||||||
interface SheetHistory {
|
interface SheetHistory {
|
||||||
items: ExtraSheet[];
|
items: ExtraSheet[];
|
||||||
index: number;
|
index: number;
|
||||||
@@ -132,20 +114,36 @@ function LiteralDataViewer({ cell }: { cell?: { value?: unknown; backgroundColor
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DialogDataEditor({
|
function InlineDataEditor({
|
||||||
cell,
|
cell,
|
||||||
row,
|
onChange,
|
||||||
column,
|
}: DataEditorProps<{ value: string }>) {
|
||||||
exitEditMode,
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
onEdit,
|
const text = useMemo(() => {
|
||||||
}: DataEditorProps<{ value: string }> & {
|
const document = new DOMParser().parseFromString(
|
||||||
onEdit: (point: Point, value: string) => void;
|
normalizeExtraRichText(String(cell?.value ?? "")),
|
||||||
}) {
|
"text/html",
|
||||||
|
);
|
||||||
|
document.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
|
||||||
|
document.querySelectorAll("p + p").forEach((p) => p.prepend("\n"));
|
||||||
|
return document.body.textContent ?? "";
|
||||||
|
}, [cell?.value]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onEdit({ row, column }, String(cell?.value ?? ""));
|
const input = inputRef.current;
|
||||||
exitEditMode();
|
input?.focus();
|
||||||
}, [cell?.value, column, exitEditMode, onEdit, row]);
|
input?.setSelectionRange(input.value.length, input.value.length);
|
||||||
return <LiteralDataViewer cell={cell} />;
|
}, []);
|
||||||
|
return (
|
||||||
|
<div className="Spreadsheet__data-editor">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
aria-label="แก้ไขข้อความเซลล์"
|
||||||
|
value={text}
|
||||||
|
onChange={(event) => onChange({ ...cell, value: event.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditableColumnIndicator({
|
function EditableColumnIndicator({
|
||||||
@@ -244,7 +242,6 @@ export function ExtraSheetEditor({
|
|||||||
const [labelDialogOpen, setLabelDialogOpen] = useState(false);
|
const [labelDialogOpen, setLabelDialogOpen] = useState(false);
|
||||||
const [renaming, setRenaming] = useState(false);
|
const [renaming, setRenaming] = useState(false);
|
||||||
const [columnLabel, setColumnLabel] = useState("");
|
const [columnLabel, setColumnLabel] = useState("");
|
||||||
const [deleteTarget, setDeleteTarget] = useState<DeleteTarget>(null);
|
|
||||||
const [removeOpen, setRemoveOpen] = useState(false);
|
const [removeOpen, setRemoveOpen] = useState(false);
|
||||||
const [cellDialogOpen, setCellDialogOpen] = useState(false);
|
const [cellDialogOpen, setCellDialogOpen] = useState(false);
|
||||||
const [cellDraft, setCellDraft] = useState("");
|
const [cellDraft, setCellDraft] = useState("");
|
||||||
@@ -411,9 +408,6 @@ export function ExtraSheetEditor({
|
|||||||
const rowIndicator = useCallback((props: RowIndicatorProps) => (
|
const rowIndicator = useCallback((props: RowIndicatorProps) => (
|
||||||
<SortableRowIndicator {...props} id={rowDragId(value.rows[props.row].id)} />
|
<SortableRowIndicator {...props} id={rowDragId(value.rows[props.row].id)} />
|
||||||
), [value.rows]);
|
), [value.rows]);
|
||||||
const dataEditor = useCallback((props: DataEditorProps<{ value: string }>) => (
|
|
||||||
<DialogDataEditor {...props} onEdit={openCellEditorAt} />
|
|
||||||
), [openCellEditorAt]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -494,92 +488,27 @@ export function ExtraSheetEditor({
|
|||||||
>
|
>
|
||||||
ล้างสีพื้นหลัง
|
ล้างสีพื้นหลัง
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
render={<Button type="button" variant="outline" size="sm" />}
|
|
||||||
>
|
|
||||||
<Rows3Icon data-icon="inline-start" />
|
|
||||||
แถว {active.row + 1}
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuLabel>จัดการแถว {active.row + 1}</DropdownMenuLabel>
|
|
||||||
<DropdownMenuItem
|
|
||||||
disabled={!activeRow || active.row === 0}
|
|
||||||
onClick={() => {
|
|
||||||
moveRow(active.row, active.row - 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowUpIcon />ย้ายขึ้น
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
disabled={!activeRow || active.row === value.rows.length - 1}
|
|
||||||
onClick={() => {
|
|
||||||
moveRow(active.row, active.row + 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowDownIcon />ย้ายลง
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem
|
|
||||||
variant="destructive"
|
|
||||||
disabled={value.rows.length === 1}
|
|
||||||
onClick={() => setDeleteTarget("row")}
|
|
||||||
>
|
|
||||||
<Trash2Icon />ลบแถว
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
render={<Button type="button" variant="outline" size="sm" />}
|
|
||||||
>
|
|
||||||
<Columns3Icon data-icon="inline-start" />
|
|
||||||
{activeColumn?.label ?? "คอลัมน์"}
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuLabel>{activeColumn?.label}</DropdownMenuLabel>
|
|
||||||
<DropdownMenuItem onClick={() => openRenameColumn()}>แก้ไขชื่อ</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
disabled={!activeColumn || active.column === 0}
|
|
||||||
onClick={() => {
|
|
||||||
moveColumn(active.column, active.column - 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowLeftIcon />ย้ายไปทางซ้าย
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
disabled={!activeColumn || active.column === value.columns.length - 1}
|
|
||||||
onClick={() => {
|
|
||||||
moveColumn(active.column, active.column + 1);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowRightIcon />ย้ายไปทางขวา
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem
|
|
||||||
variant="destructive"
|
|
||||||
disabled={value.columns.length === 1}
|
|
||||||
onClick={() => setDeleteTarget("column")}
|
|
||||||
>
|
|
||||||
<Trash2Icon />ลบคอลัมน์
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Button type="button" variant="ghost" size="sm" onClick={() => setRemoveOpen(true)}>
|
<Button type="button" variant="ghost" size="sm" onClick={() => setRemoveOpen(true)}>
|
||||||
<Trash2Icon data-icon="inline-start" />
|
<Trash2Icon data-icon="inline-start" />
|
||||||
ลบตาราง
|
ลบตาราง
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto rounded-lg border">
|
<div
|
||||||
|
className="overflow-x-auto rounded-lg border"
|
||||||
|
onClickCapture={(event) => {
|
||||||
|
if ((event.target as HTMLElement).closest(".Spreadsheet__active-cell--view")) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDoubleClickCapture={(event) => {
|
||||||
|
if ((event.target as HTMLElement).closest(".Spreadsheet__cell, .Spreadsheet__active-cell")) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
openCellEditorAt(active);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={sameAxisCollision}
|
collisionDetection={sameAxisCollision}
|
||||||
@@ -594,7 +523,7 @@ export function ExtraSheetEditor({
|
|||||||
ColumnIndicator={columnIndicator}
|
ColumnIndicator={columnIndicator}
|
||||||
RowIndicator={rowIndicator}
|
RowIndicator={rowIndicator}
|
||||||
DataViewer={LiteralDataViewer}
|
DataViewer={LiteralDataViewer}
|
||||||
DataEditor={dataEditor}
|
DataEditor={InlineDataEditor}
|
||||||
onActivate={(point) => {
|
onActivate={(point) => {
|
||||||
setActive(point);
|
setActive(point);
|
||||||
setSelectedCount(1);
|
setSelectedCount(1);
|
||||||
@@ -682,23 +611,6 @@ export function ExtraSheetEditor({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<DeleteConfirmationDialog
|
|
||||||
open={deleteTarget !== null}
|
|
||||||
onOpenChange={(open) => { if (!open) setDeleteTarget(null); }}
|
|
||||||
title={deleteTarget === "column" ? `ลบ ${activeColumn?.label}?` : `ลบแถว ${active.row + 1}?`}
|
|
||||||
description="ข้อมูลในรายการนี้จะถูกลบออกจากตาราง"
|
|
||||||
showSuccessToast={false}
|
|
||||||
onConfirm={() => {
|
|
||||||
if (deleteTarget === "column") {
|
|
||||||
commitSheet(deleteExtraSheetColumn(value, active.column));
|
|
||||||
activateOnly({ ...active, column: Math.max(0, active.column - 1) });
|
|
||||||
} else if (deleteTarget === "row") {
|
|
||||||
commitSheet(deleteExtraSheetRow(value, active.row));
|
|
||||||
activateOnly({ ...active, row: Math.max(0, active.row - 1) });
|
|
||||||
}
|
|
||||||
setDeleteTarget(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<DeleteConfirmationDialog
|
<DeleteConfirmationDialog
|
||||||
open={removeOpen}
|
open={removeOpen}
|
||||||
onOpenChange={setRemoveOpen}
|
onOpenChange={setRemoveOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user