fix : warning from tailwindcss

This commit is contained in:
2026-09-20 00:55:57 +07:00 Unverified
parent 7c10b2c995
commit cd72f1fa5f
14 changed files with 499 additions and 209 deletions
+114 -18
View File
@@ -7,39 +7,135 @@ import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import type { AutosaveState } from "@/hooks/use-autosave";
export function AutosaveStatus({ state, issues = [], onSave, conflictVersion, canOverwrite = false, onReloadLatest, onOverwrite }: { state: AutosaveState; issues?: readonly string[]; onSave?: () => void; conflictVersion?: number | null; canOverwrite?: boolean; onReloadLatest?: () => void; onOverwrite?: () => void }) {
export function AutosaveStatus({
state,
issues = [],
onSave,
conflictVersion,
canOverwrite = false,
onReloadLatest,
onOverwrite,
}: {
state: AutosaveState;
issues?: readonly string[];
onSave?: () => void;
conflictVersion?: number | null;
canOverwrite?: boolean;
onReloadLatest?: () => void;
onOverwrite?: () => void;
}) {
const [showIssues, setShowIssues] = useState(false);
if (state === "saving") return <Badge variant="secondary"><Spinner data-icon="inline-start" />Saving…</Badge>;
if (state === "unsaved") return <Badge variant="outline"><ClockIcon data-icon="inline-start" />Waiting to save…</Badge>;
if (state === "incomplete") return <div className="flex flex-col items-end gap-2">
<Button type="button" variant="outline" size="sm" onClick={() => setShowIssues((current) => !current)} aria-expanded={showIssues}>
<AlertCircleIcon data-icon="inline-start" />{showIssues ? "ซ่อนรายการ" : "ตรวจสอบข้อมูลที่ยังไม่ครบ"}
if (state === "saving")
return (
<Badge variant="secondary">
<Spinner data-icon="inline-start" />
Saving…
</Badge>
);
if (state === "unsaved")
return (
<Badge variant="outline">
<ClockIcon data-icon="inline-start" />
Waiting to save…
</Badge>
);
if (state === "incomplete")
return (
<div className="flex flex-col items-end gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={() => setShowIssues((current) => !current)}
aria-expanded={showIssues}
>
<AlertCircleIcon data-icon="inline-start" />
{showIssues ? "ซ่อนรายการ" : "ตรวจสอบข้อมูลที่ยังไม่ครบ"}
</Button>
{showIssues ? <Alert variant="destructive" className="max-w-md">
{showIssues ? (
<Alert variant="destructive" className="max-w-md">
<AlertCircleIcon aria-hidden="true" />
<AlertTitle>กรุณากรอกข้อมูลให้ครบ</AlertTitle>
<AlertDescription>
<div className="flex flex-col gap-2">
<p>แก้ไขรายการด้านล่าง แล้วระบบจะบันทึกให้อัตโนมัติ ข้อมูลที่กรอกไว้ยังไม่หาย</p>
{issues.length ? <ul className="flex flex-col gap-1">{issues.map((issue, index) => <li key={`${issue}-${index}`}>{issue}</li>)}</ul> : null}
<p>
แก้ไขรายการด้านล่าง แล้วระบบจะบันทึกให้อัตโนมัติ
ข้อมูลที่กรอกไว้ยังไม่หาย
</p>
{issues.length ? (
<ul className="flex flex-col gap-1">
{issues.map((issue, index) => (
<li key={`${issue}-${index}`}>{issue}</li>
))}
</ul>
) : null}
</div>
</AlertDescription>
</Alert> : null}
</div>;
if (state === "conflict") return <Alert id="autosave-conflict" variant="destructive" className="max-w-xl scroll-m-6">
</Alert>
) : null}
</div>
);
if (state === "conflict")
return (
<Alert
id="autosave-conflict"
variant="destructive"
className="max-w-xl scroll-m-6"
>
<AlertCircleIcon aria-hidden="true" />
<AlertTitle>มีการบันทึกข้อมูลใหม่จากผู้แก้ไขคนอื่น</AlertTitle>
<AlertDescription>
<div className="flex flex-col gap-3">
<p>ระบบหยุด Autosave ไว้เพื่อไม่ให้ Draft นี้เขียนทับข้อมูลล่าสุด{conflictVersion ? ` (version ${conflictVersion})` : ""}</p>
<p>
ระบบหยุด Autosave ไว้เพื่อไม่ให้ Draft นี้เขียนทับข้อมูลล่าสุด
{conflictVersion ? ` (version ${conflictVersion})` : ""}
</p>
<div className="flex flex-wrap gap-2">
{onReloadLatest ? <Button type="button" variant="outline" size="sm" onClick={onReloadLatest}>โหลดข้อมูลล่าสุด</Button> : null}
{onOverwrite ? <Button type="button" variant="destructive" size="sm" disabled={!canOverwrite} onClick={onOverwrite}>เขียนทับด้วย Draft นี้</Button> : null}
{onReloadLatest ? (
<Button
type="button"
variant="outline"
size="sm"
onClick={onReloadLatest}
>
โหลดข้อมูลล่าสุด
</Button>
) : null}
{onOverwrite ? (
<Button
type="button"
variant="destructive"
size="sm"
disabled={!canOverwrite}
onClick={onOverwrite}
>
เขียนทับด้วย Draft นี้
</Button>
) : null}
</div>
</div>
</AlertDescription>
</Alert>;
if (state === "error") return <div className="flex items-center gap-2"><Badge variant="destructive"><AlertCircleIcon data-icon="inline-start" />Autosave failed</Badge>{onSave ? <Button type="button" variant="outline" size="sm" onClick={onSave}>Save now</Button> : null}</div>;
return <Badge variant="secondary"><CheckIcon data-icon="inline-start" />Saved</Badge>;
</Alert>
);
if (state === "error")
return (
<div className="flex items-center gap-2">
<Badge variant="destructive">
<AlertCircleIcon data-icon="inline-start" />
Autosave failed
</Badge>
{onSave ? (
<Button type="button" variant="outline" size="sm" onClick={onSave}>
Save now
</Button>
) : null}
</div>
);
return (
<Badge variant="secondary">
<CheckIcon data-icon="inline-start" />
Saved
</Badge>
);
}
+282 -88
View File
@@ -17,7 +17,14 @@ import Spreadsheet, {
type RowIndicatorProps,
type SpreadsheetRef,
} from "react-spreadsheet";
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from "react";
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type KeyboardEvent,
} from "react";
import {
closestCenter,
DndContext,
@@ -90,8 +97,8 @@ 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}:`),
droppableContainers: args.droppableContainers.filter((container) =>
String(container.id).startsWith(`${axis}:`),
),
});
};
@@ -103,13 +110,23 @@ function movedIndex(current: number, from: number, to: number): number {
return current;
}
function LiteralDataViewer({ cell }: { cell?: { value?: unknown; backgroundColor?: string } }) {
function LiteralDataViewer({
cell,
}: {
cell?: { value?: unknown; backgroundColor?: string };
}) {
return (
<div
className="Spreadsheet__data-viewer Spreadsheet__data-viewer--preserve-breaks size-full"
style={cell?.backgroundColor ? { backgroundColor: cell.backgroundColor } : undefined}
style={
cell?.backgroundColor
? { backgroundColor: cell.backgroundColor }
: undefined
}
>
<RichTextContent html={normalizeExtraRichText(String(cell?.value ?? ""))} />
<RichTextContent
html={normalizeExtraRichText(String(cell?.value ?? ""))}
/>
</div>
);
}
@@ -153,14 +170,25 @@ function EditableColumnIndicator({
selected,
onRename,
}: ColumnIndicatorProps & { id: string; onRename: (column: number) => void }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id,
strategy: horizontalListSortingStrategy,
});
return (
<th
ref={setNodeRef}
className={cn("Spreadsheet__header", selected && "Spreadsheet__header--selected", isDragging && "opacity-60")}
className={cn(
"Spreadsheet__header",
selected && "Spreadsheet__header--selected",
isDragging && "opacity-60",
)}
style={{ transform: CSS.Transform.toString(transform), transition }}
>
<Button
@@ -187,15 +215,32 @@ function EditableColumnIndicator({
);
}
function SortableRowIndicator({ id, row, label, selected, onSelect }: RowIndicatorProps & { id: string }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
function SortableRowIndicator({
id,
row,
label,
selected,
onSelect,
}: RowIndicatorProps & { id: string }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({
id,
strategy: verticalListSortingStrategy,
});
return (
<th
ref={setNodeRef}
className={cn("Spreadsheet__header", selected && "Spreadsheet__header--selected", isDragging && "opacity-60")}
className={cn(
"Spreadsheet__header",
selected && "Spreadsheet__header--selected",
isDragging && "opacity-60",
)}
style={{ transform: CSS.Transform.toString(transform), transition }}
>
<Button
@@ -235,7 +280,9 @@ export function ExtraSheetEditor({
}) {
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const spreadsheetRef = useRef<SpreadsheetRef>(null);
const [active, setActive] = useState<Point>({ row: 0, column: 0 });
@@ -248,35 +295,50 @@ export function ExtraSheetEditor({
const [originalCellDraft, setOriginalCellDraft] = useState("");
const [stylePatch, setStylePatch] = useState<RichTextStylePatch>({});
const [selectedCount, setSelectedCount] = useState(1);
const [history, setHistory] = useState<SheetHistory>({ items: [value], index: 0, value });
const [history, setHistory] = useState<SheetHistory>({
items: [value],
index: 0,
value,
});
const selectedPointsRef = useRef<Point[]>([active]);
const matrix = useMemo(() => extraSheetToMatrix(value), [value]);
const activeColumn = value.columns[active.column];
const activeRow = value.rows[active.row];
const canUndo = history.index > 0;
const canRedo = history.index < history.items.length - 1;
const sortableIds = useMemo(() => [
const sortableIds = useMemo(
() => [
...value.columns.map((column) => columnDragId(column.id)),
...value.rows.map((row) => rowDragId(row.id)),
], [value.columns, value.rows]);
],
[value.columns, value.rows],
);
if (history.value !== value) {
setHistory(history.items[history.index] === value
setHistory(
history.items[history.index] === value
? { ...history, value }
: { items: [value], index: 0, value });
: { items: [value], index: 0, value },
);
}
const commitSheet = useCallback((next: ExtraSheet) => {
const commitSheet = useCallback(
(next: ExtraSheet) => {
if (JSON.stringify(next) === JSON.stringify(value)) return;
setHistory((current) => {
const items = [...current.items.slice(0, current.index + 1), next]
.slice(-SHEET_HISTORY_LIMIT);
const items = [
...current.items.slice(0, current.index + 1),
next,
].slice(-SHEET_HISTORY_LIMIT);
return { items, index: items.length - 1, value: next };
});
onChange(next);
}, [onChange, value]);
},
[onChange, value],
);
const travel = useCallback((direction: -1 | 1) => {
const travel = useCallback(
(direction: -1 | 1) => {
const index = history.index + direction;
const next = history.items[index];
if (!next) return;
@@ -289,8 +351,12 @@ export function ExtraSheetEditor({
setActive(point);
selectedPointsRef.current = [point];
setSelectedCount(1);
window.requestAnimationFrame(() => spreadsheetRef.current?.activate(point));
}, [active, history, onChange]);
window.requestAnimationFrame(() =>
spreadsheetRef.current?.activate(point),
);
},
[active, history, onChange],
);
const activateOnly = useCallback((point: Point) => {
setActive(point);
@@ -299,45 +365,62 @@ export function ExtraSheetEditor({
window.requestAnimationFrame(() => spreadsheetRef.current?.activate(point));
}, []);
const moveRow = useCallback((from: number, to: number) => {
const moveRow = useCallback(
(from: number, to: number) => {
const next = moveExtraSheetRow(value, from, to);
if (next === value) return;
const point = { ...active, row: movedIndex(active.row, from, to) };
commitSheet(next);
activateOnly(point);
}, [active, activateOnly, commitSheet, value]);
},
[active, activateOnly, commitSheet, value],
);
const moveColumn = useCallback((from: number, to: number) => {
const moveColumn = useCallback(
(from: number, to: number) => {
const next = moveExtraSheetColumn(value, from, to);
if (next === value) return;
const point = { ...active, column: movedIndex(active.column, from, to) };
commitSheet(next);
activateOnly(point);
}, [active, activateOnly, commitSheet, value]);
},
[active, activateOnly, commitSheet, value],
);
const handleDragEnd = useCallback(({ active: dragged, over }: DragEndEvent) => {
const handleDragEnd = useCallback(
({ active: dragged, over }: DragEndEvent) => {
if (!over || dragged.id === over.id) return;
const draggedId = String(dragged.id);
const overId = String(over.id);
if (draggedId.startsWith("row:") && overId.startsWith("row:")) {
const from = value.rows.findIndex((row) => rowDragId(row.id) === draggedId);
const from = value.rows.findIndex(
(row) => rowDragId(row.id) === draggedId,
);
const to = value.rows.findIndex((row) => rowDragId(row.id) === overId);
if (from !== -1 && to !== -1) moveRow(from, to);
}
if (draggedId.startsWith("column:") && overId.startsWith("column:")) {
const from = value.columns.findIndex((column) => columnDragId(column.id) === draggedId);
const to = value.columns.findIndex((column) => columnDragId(column.id) === overId);
const from = value.columns.findIndex(
(column) => columnDragId(column.id) === draggedId,
);
const to = value.columns.findIndex(
(column) => columnDragId(column.id) === overId,
);
if (from !== -1 && to !== -1) moveColumn(from, to);
}
}, [moveColumn, moveRow, value.columns, value.rows]);
},
[moveColumn, moveRow, value.columns, value.rows],
);
const handleSheetKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
const target = event.target as HTMLElement;
if (target.closest("input, textarea, [contenteditable='true']")) return;
const key = event.key.toLowerCase();
const undo = (event.metaKey || event.ctrlKey) && !event.shiftKey && key === "z";
const redo = (event.metaKey || event.ctrlKey) && event.shiftKey && key === "z"
|| event.ctrlKey && !event.shiftKey && key === "y";
const undo =
(event.metaKey || event.ctrlKey) && !event.shiftKey && key === "z";
const redo =
((event.metaKey || event.ctrlKey) && event.shiftKey && key === "z") ||
(event.ctrlKey && !event.shiftKey && key === "y");
if (undo && canUndo) {
event.preventDefault();
travel(-1);
@@ -347,7 +430,10 @@ export function ExtraSheetEditor({
}
};
const normalizedLabel = columnLabel.trim().toLocaleLowerCase();
const labelInvalid = !normalizedLabel || value.columns.some((column, index) =>
const labelInvalid =
!normalizedLabel ||
value.columns.some(
(column, index) =>
index !== (renaming ? active.column : -1) &&
column.label.toLocaleLowerCase() === normalizedLabel,
);
@@ -356,58 +442,81 @@ export function ExtraSheetEditor({
setColumnLabel(nextExtraSheetColumnLabel(value.columns));
setLabelDialogOpen(true);
};
const openRenameColumn = useCallback((columnIndex = active.column) => {
const openRenameColumn = useCallback(
(columnIndex = active.column) => {
const column = value.columns[columnIndex];
if (!column) return;
setActive((current) => ({ ...current, column: columnIndex }));
setRenaming(true);
setColumnLabel(column.label);
setLabelDialogOpen(true);
}, [active.column, value.columns]);
},
[active.column, value.columns],
);
const saveColumnLabel = () => {
if (labelInvalid) return;
if (renaming) {
commitSheet({
...value,
columns: value.columns.map((column, index) =>
index === active.column ? { ...column, label: columnLabel.trim() } : column,
index === active.column
? { ...column, label: columnLabel.trim() }
: column,
),
});
} else {
const column = value.columns.length;
commitSheet(addExtraSheetColumn(
commitSheet(
addExtraSheetColumn(
value,
columnLabel.trim(),
createId("extra-sheet-column"),
));
),
);
activateOnly({ ...active, column });
}
setLabelDialogOpen(false);
};
const openCellEditorAt = useCallback((point: Point, content?: string) => {
const openCellEditorAt = useCallback(
(point: Point, content?: string) => {
setActive(point);
const column = value.columns[point.column];
const row = value.rows[point.row];
setCellDraft(normalizeExtraRichText(
content ?? (column && row ? row.cells[column.id] ?? "" : ""),
));
setOriginalCellDraft(normalizeExtraRichText(
content ?? (column && row ? row.cells[column.id] ?? "" : ""),
));
setCellDraft(
normalizeExtraRichText(
content ?? (column && row ? (row.cells[column.id] ?? "") : ""),
),
);
setOriginalCellDraft(
normalizeExtraRichText(
content ?? (column && row ? (row.cells[column.id] ?? "") : ""),
),
);
setStylePatch({});
setCellDialogOpen(true);
}, [value.columns, value.rows]);
},
[value.columns, value.rows],
);
const openActiveCellEditor = () => openCellEditorAt(active);
const columnIndicator = useCallback((props: ColumnIndicatorProps) => (
const columnIndicator = useCallback(
(props: ColumnIndicatorProps) => (
<EditableColumnIndicator
{...props}
id={columnDragId(value.columns[props.column].id)}
onRename={openRenameColumn}
/>
), [openRenameColumn, value.columns]);
const rowIndicator = useCallback((props: RowIndicatorProps) => (
<SortableRowIndicator {...props} id={rowDragId(value.rows[props.row].id)} />
), [value.rows]);
),
[openRenameColumn, value.columns],
);
const rowIndicator = useCallback(
(props: RowIndicatorProps) => (
<SortableRowIndicator
{...props}
id={rowDragId(value.rows[props.row].id)}
/>
),
[value.rows],
);
return (
<div
@@ -416,7 +525,13 @@ export function ExtraSheetEditor({
onKeyDown={handleSheetKeyDown}
>
<div className="flex flex-wrap items-center gap-2">
<SimpleTooltip text={<>ย้อนกลับ <Kbd>Ctrl/⌘ Z</Kbd></>}>
<SimpleTooltip
text={
<>
ย้อนกลับ <Kbd>Ctrl/⌘ Z</Kbd>
</>
}
>
<Button
type="button"
variant="ghost"
@@ -428,7 +543,13 @@ export function ExtraSheetEditor({
<Undo2Icon />
</Button>
</SimpleTooltip>
<SimpleTooltip text={<>ทำซ้ำ <Kbd>Ctrl/⌘ Shift Z</Kbd></>}>
<SimpleTooltip
text={
<>
ทำซ้ำ <Kbd>Ctrl/⌘ Shift Z</Kbd>
</>
}
>
<Button
type="button"
variant="ghost"
@@ -453,16 +574,32 @@ export function ExtraSheetEditor({
<PlusIcon data-icon="inline-start" />
เพิ่มแถว
</Button>
<Button type="button" variant="outline" size="sm" onClick={openAddColumn}>
<Button
type="button"
variant="outline"
size="sm"
onClick={openAddColumn}
>
<PlusIcon data-icon="inline-start" />
เพิ่มคอลัมน์
</Button>
<Button type="button" variant="outline" size="sm" onClick={openActiveCellEditor}>
<Button
type="button"
variant="outline"
size="sm"
onClick={openActiveCellEditor}
>
<PencilIcon data-icon="inline-start" />
แก้ไข {selectedCount > 1 ? `${selectedCount} เซลล์` : "เซลล์"}
</Button>
<label className="relative">
<Button type="button" variant="outline" size="sm" render={<span />} nativeButton={false}>
<Button
type="button"
variant="outline"
size="sm"
render={<span />}
nativeButton={false}
>
<PaletteIcon data-icon="inline-start" />
สีพื้นหลัง {selectedCount > 1 ? `${selectedCount} เซลล์` : "เซลล์"}
</Button>
@@ -470,13 +607,16 @@ export function ExtraSheetEditor({
type="color"
aria-label="เลือกสีพื้นหลังเซลล์"
className="absolute inset-0 cursor-pointer opacity-0"
value={activeRow?.backgrounds?.[activeColumn?.id ?? ""] ?? DEFAULT_CELL_BACKGROUND}
value={
activeRow?.backgrounds?.[activeColumn?.id ?? ""] ??
DEFAULT_CELL_BACKGROUND
}
onChange={(event) => {
commitSheet(updateExtraSheetCells(
value,
selectedPointsRef.current,
{ backgroundColor: event.target.value },
));
commitSheet(
updateExtraSheetCells(value, selectedPointsRef.current, {
backgroundColor: event.target.value,
}),
);
}}
/>
</label>
@@ -484,11 +624,22 @@ export function ExtraSheetEditor({
type="button"
variant="ghost"
size="sm"
onClick={() => commitSheet(updateExtraSheetCells(value, selectedPointsRef.current, { backgroundColor: null }))}
onClick={() =>
commitSheet(
updateExtraSheetCells(value, selectedPointsRef.current, {
backgroundColor: null,
}),
)
}
>
ล้างสีพื้นหลัง
</Button>
<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" />
ลบตาราง
</Button>
@@ -497,12 +648,20 @@ export function ExtraSheetEditor({
<div
className="overflow-x-auto rounded-lg border"
onClickCapture={(event) => {
if ((event.target as HTMLElement).closest(".Spreadsheet__active-cell--view")) {
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")) {
if (
(event.target as HTMLElement).closest(
".Spreadsheet__cell, .Spreadsheet__active-cell",
)
) {
event.preventDefault();
event.stopPropagation();
openCellEditorAt(active);
@@ -536,11 +695,11 @@ export function ExtraSheetEditor({
setSelectedCount(range.size());
}
}}
onChange={(next) => commitSheet(matrixToExtraSheet(
value,
next as ExtraSheetMatrix,
createId,
))}
onChange={(next) =>
commitSheet(
matrixToExtraSheet(value, next as ExtraSheetMatrix, createId),
)
}
/>
</SortableContext>
</DndContext>
@@ -561,23 +720,48 @@ export function ExtraSheetEditor({
onChange={setCellDraft}
showBubbleMenu={false}
formatWholeContent={selectedCount > 1}
onFormatChange={(patch) => setStylePatch((current) => ({ ...current, ...patch }))}
onFormatChange={(patch) =>
setStylePatch((current) => ({ ...current, ...patch }))
}
/>
<DialogFooter showCloseButton>
<Button type="button" onClick={() => {
const contentUnchanged = richTextContentSignature(cellDraft)
=== richTextContentSignature(originalCellDraft);
<Button
type="button"
onClick={() => {
const contentUnchanged =
richTextContentSignature(cellDraft) ===
richTextContentSignature(originalCellDraft);
if (selectedCount > 1 && contentUnchanged) {
if (Object.keys(stylePatch).length > 0) {
commitSheet(styleExtraSheetCells(value, selectedPointsRef.current, stylePatch));
commitSheet(
styleExtraSheetCells(
value,
selectedPointsRef.current,
stylePatch,
),
);
}
} else {
commitSheet(selectedCount > 1
? updateExtraSheetCells(value, selectedPointsRef.current, { value: cellDraft })
: updateExtraSheetCell(value, active.row, active.column, cellDraft));
commitSheet(
selectedCount > 1
? updateExtraSheetCells(
value,
selectedPointsRef.current,
{ value: cellDraft },
)
: updateExtraSheetCell(
value,
active.row,
active.column,
cellDraft,
),
);
}
setCellDialogOpen(false);
}}>บันทึกเซลล์</Button>
}}
>
บันทึกเซลล์
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
@@ -585,12 +769,18 @@ export function ExtraSheetEditor({
<Dialog open={labelDialogOpen} onOpenChange={setLabelDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{renaming ? "แก้ไขชื่อคอลัมน์" : "เพิ่มคอลัมน์"}</DialogTitle>
<DialogDescription>ตั้งชื่อที่ใช้อธิบายข้อมูลในคอลัมน์นี้</DialogDescription>
<DialogTitle>
{renaming ? "แก้ไขชื่อคอลัมน์" : "เพิ่มคอลัมน์"}
</DialogTitle>
<DialogDescription>
ตั้งชื่อที่ใช้อธิบายข้อมูลในคอลัมน์นี้
</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field data-invalid={labelInvalid || undefined}>
<FieldLabel htmlFor="extra-sheet-column-label">ชื่อคอลัมน์</FieldLabel>
<FieldLabel htmlFor="extra-sheet-column-label">
ชื่อคอลัมน์
</FieldLabel>
<Input
id="extra-sheet-column-label"
value={columnLabel}
@@ -604,7 +794,11 @@ export function ExtraSheetEditor({
</Field>
</FieldGroup>
<DialogFooter showCloseButton>
<Button type="button" disabled={labelInvalid} onClick={saveColumnLabel}>
<Button
type="button"
disabled={labelInvalid}
onClick={saveColumnLabel}
>
{renaming ? "บันทึกชื่อ" : "เพิ่มคอลัมน์"}
</Button>
</DialogFooter>
+1 -1
View File
@@ -3890,7 +3890,7 @@ function TeamEditor({
<DialogContent className="max-h-[90svh] grid-rows-[auto_minmax(0,1fr)_auto] overflow-hidden sm:max-w-3xl">
<DialogHeader>
<DialogTitle>Multiple Apply</DialogTitle>
<DialogDescription className="break-words">คัดลอกค่าจาก {teams.find((team) => team.clientId === sourceTeamId)?.title || "Source Team"} ไปยัง {selectedTeamIds.size} Team ที่เลือก: {[...selectedTeamIds].map((id) => teams.find((team) => team.clientId === id)?.title || "Untitled Team").join(", ")}</DialogDescription>
<DialogDescription className="wrap-break-word">คัดลอกค่าจาก {teams.find((team) => team.clientId === sourceTeamId)?.title || "Source Team"} ไปยัง {selectedTeamIds.size} Team ที่เลือก: {[...selectedTeamIds].map((id) => teams.find((team) => team.clientId === id)?.title || "Untitled Team").join(", ")}</DialogDescription>
</DialogHeader>
<div className="flex min-h-0 flex-col gap-5 overflow-y-auto pr-1">
{teams.find((team) => team.clientId === sourceTeamId)?.members.map((member, memberIndex) => {
+1 -1
View File
@@ -167,7 +167,7 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
</Field>
{turnstileEnabled ? (
<Field>
<div ref={turnstileContainerRef} className="min-h-[65px]" />
<div ref={turnstileContainerRef} className="min-h-16.25" />
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
strategy="afterInteractive"
@@ -279,7 +279,7 @@ function OverviewPoster({
<DndContext sensors={posterSensors} onDragEnd={handlePosterDragEnd}>
<div
ref={posterRef}
className="relative isolate aspect-square size-full overflow-hidden bg-black text-white [container-type:inline-size]"
className="relative isolate aspect-square size-full overflow-hidden bg-black text-white @container"
style={{ fontFamily: '"Genshin Overview", sans-serif' }}
>
{composition.backgroundUrl ? (
@@ -369,7 +369,7 @@ function OverviewPoster({
offset={composition.fadeOffset}
interactive={interactive && activeLayer === "fade"}
className={cn(
"absolute inset-x-0 -top-[3cqw] bottom-0",
"absolute inset-x-0 top-[-3cqw] bottom-0",
activeLayer === "fade"
? "touch-none cursor-grab active:cursor-grabbing"
: "pointer-events-none",
@@ -1497,7 +1497,7 @@ export function OverviewImageGenerator({
<div
ref={captureRef}
aria-hidden="true"
className="pointer-events-none fixed top-0 -left-[10000px] order-5 size-[1000px]"
className="pointer-events-none fixed top-0 left-[-10000px] order-5 size-250"
>
<OverviewPoster
composition={composition}
+6 -6
View File
@@ -22,7 +22,7 @@ export function ConstellationImage({
const footer = group.note;
return (
<div className="relative isolate size-[1500px] overflow-hidden bg-[#070b20] font-heading text-white">
<div className="relative isolate size-375 overflow-hidden bg-[#070b20] font-heading text-white">
<GuideExportBranding backgroundUrl={background} variant="constellation" />
<div className="absolute inset-x-20 top-7 z-10 flex items-start justify-center gap-5">
@@ -45,14 +45,14 @@ export function ConstellationImage({
>
{`${guide.character.name}’s Constellations`}
</h2>
<p className="mt-3 line-clamp-2 max-w-[1100px] whitespace-pre-line text-[42px] leading-tight font-bold text-[#f1badd]">
<p className="mt-3 line-clamp-2 max-w-275 whitespace-pre-line text-[42px] leading-tight font-bold text-[#f1badd]">
<ExportText th="ดาเมจทั้งทีม" en="Team Damage" />
</p>
</div>
</div>
<div className="absolute top-[160px] bottom-[115px] left-[154px] z-20 w-1 bg-black/90" />
<div className="absolute inset-x-14 top-[205px] bottom-[145px] z-20 flex flex-col justify-between">
<div className="absolute top-40 bottom-28.75 left-38.5 z-20 w-1 bg-black/90" />
<div className="absolute inset-x-14 top-51.25 bottom-36.25 z-20 flex flex-col justify-between">
{rows.map((row) => (
<div
key={row.level}
@@ -61,7 +61,7 @@ export function ConstellationImage({
<div className="text-right text-[58px] leading-none font-bold drop-shadow-[0_5px_4px_rgba(0,0,0,0.9)]">
C{row.level}
</div>
<div className="flex h-[100px] items-center">
<div className="flex h-25 items-center">
<div
className={cn(
"h-full shrink-0 rounded-r-xl",
@@ -83,7 +83,7 @@ export function ConstellationImage({
</div>
{footer ? (
<p data-export-note className="absolute bottom-10 left-[154px] z-30 max-w-[1000px] truncate text-[28px] font-semibold drop-shadow-[0_4px_3px_rgba(0,0,0,0.9)]">
<p data-export-note className="absolute bottom-10 left-38.5 z-30 max-w-275 truncate text-[28px] font-semibold drop-shadow-[0_4px_3px_rgba(0,0,0,0.9)]">
{footer}
</p>
) : null}
+1 -1
View File
@@ -6,7 +6,7 @@ export function DirectorySkeleton() {
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{Array.from({ length: 8 }, (_, index) => (
<Card key={index} className="gap-0 py-0">
<Skeleton className="aspect-[4/3] rounded-b-none" />
<Skeleton className="aspect-4/3 rounded-b-none" />
<CardHeader className="pt-4">
<Skeleton className="h-5 w-2/3" />
<Skeleton className="h-4 w-1/2" />
+2 -2
View File
@@ -28,8 +28,8 @@ export function GuideExportBranding({
loading="eager"
className={
constellation
? "guide-export-watermark absolute right-4 bottom-0 z-30 h-auto w-[280px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
: "guide-export-watermark absolute top-4 right-4 z-30 h-auto w-[180px] object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
? "guide-export-watermark absolute right-4 bottom-0 z-30 h-auto w-70 object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
: "guide-export-watermark absolute top-4 right-4 z-30 h-auto w-45 object-contain drop-shadow-[0_4px_3px_rgba(0,0,0,0.8)]"
}
/>
</>
+1 -1
View File
@@ -254,7 +254,7 @@ export function GuideImageExport({
? createPortal(
<div
aria-hidden="true"
className="pointer-events-none fixed top-0 -left-[10000px]"
className="pointer-events-none fixed top-0 left-[-10000px]"
>
<div
ref={captureRef}
+2 -2
View File
@@ -120,7 +120,7 @@ export function MaterialPoster({
return (
<div
className={cn("@container relative aspect-[4/5] w-full select-none overflow-hidden bg-black text-white", interactive && characterArtUrl && "cursor-move", className)}
className={cn("@container relative aspect-4/5 w-full select-none overflow-hidden bg-black text-white", interactive && characterArtUrl && "cursor-move", className)}
style={{ backgroundImage: background }}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
@@ -178,7 +178,7 @@ export function MaterialPoster({
className="pointer-events-none object-cover opacity-50"
priority={!interactive}
/>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-black/10 via-transparent to-black/80" />
<div className="pointer-events-none absolute inset-0 bg-linear-to-b from-black/10 via-transparent to-black/80" />
<div
className="pointer-events-none absolute inset-x-0 top-0 h-[1.35cqw]"
style={{ backgroundImage: accentGradient(...topColors) }}
+2 -2
View File
@@ -14,14 +14,14 @@ export function SiteFooter() {
guide.sudloh.com is not affiliated with HoYoverse. Game content and
materials are trademarks and copyrights of HoYoverse. With sincere
thanks to{" "}
<a
<Link
href="https://lunaris.moe/"
target="_blank"
rel="noreferrer"
className="underline underline-offset-2 hover:text-foreground"
>
Lunaris
</a>{" "}
</Link>{" "}
for generously providing the data that helps make these guides
possible.
</p>
+1 -1
View File
@@ -1359,7 +1359,7 @@ function ExtraSectionContent({
<CardContent
className={cn(
"pb-5",
exporting && "[&_[data-slot=table-container]]:overflow-visible",
exporting && "**:data-[slot=table-container]:overflow-visible",
)}
>
<Table
+1 -1
View File
@@ -23,7 +23,7 @@ export function VisitorPresence({ guideId }: { guideId?: string }) {
className=" tabular-nums"
title="ผู้เข้าชม Guide นี้ที่กำลังออนไลน์"
>
Guide นี้ {counts?.guide ?? "-"}
ไกด์นี้ {counts?.guide ?? "-"}
</Badge>
) : null}
</div>
+1 -1
View File
@@ -13,7 +13,7 @@ export function getDb(): Database {
if (!databaseUrl) {
throw new Error(
"DATABASE_URL is required for database-backed routes. Copy .env.example to .env and configure it.",
"DATABASE_URL is required for database-backed routes.",
);
}