♻️ refactor!: massive refactor

This commit is contained in:
2026-05-31 03:04:30 +07:00
parent c1f12c7201
commit df3c944547
86 changed files with 3691 additions and 1129 deletions
+169 -27
View File
@@ -70,17 +70,27 @@ export function EditorHeader({
onSetPointerTool,
}: EditorHeaderProps) {
const menuClass = `absolute left-0 top-full z-30 min-w-[180px] rounded border p-1 text-[11px] opacity-0 shadow-xl transition-opacity pointer-events-none group-hover:pointer-events-auto group-hover:opacity-100 hover:pointer-events-auto hover:opacity-100 ${
isDark ? "border-white/10 bg-[#2a2c31] text-[#e2e5ea]" : "border-black/10 bg-white text-[#1f2430]"
isDark
? "border-white/10 bg-[#2a2c31] text-[#e2e5ea]"
: "border-black/10 bg-white text-[#1f2430]"
}`;
return (
<header className={`flex h-14 items-center justify-between border-b px-4 ${isDark ? "border-white/10 bg-[#2b2d31]" : "border-black/10 bg-white"}`}>
<header
className={`flex h-14 items-center justify-between border-b px-4 ${isDark ? "border-white/10 bg-[#2b2d31]" : "border-black/10 bg-white"}`}
>
<div className="flex items-center gap-4">
<Link href="/" className="group">
<p className={`text-[10px] uppercase tracking-[0.2em] group-hover:opacity-80 ${isDark ? "text-[#a8abb2]" : "text-[#6c7382]"}`}>
<p
className={`text-[10px] uppercase tracking-[0.2em] group-hover:opacity-80 ${isDark ? "text-[#a8abb2]" : "text-[#6c7382]"}`}
>
pien.studio
</p>
<h1 className={`text-sm font-semibold group-hover:opacity-80 ${isDark ? "text-[#f5f7fa]" : "text-[#1f2430]"}`}>{projectTitle}</h1>
<h1
className={`text-sm font-semibold group-hover:opacity-80 ${isDark ? "text-[#f5f7fa]" : "text-[#1f2430]"}`}
>
{projectTitle}
</h1>
</Link>
<nav className="flex items-center gap-2 text-xs font-medium">
@@ -98,12 +108,31 @@ export function EditorHeader({
/>
</MenuShell>
<MenuShell isDark={isDark} label={labels.edit} menuClass={menuClass}>
<EditMenu isDark={isDark} labels={labels} canUndo={canUndo} canRedo={canRedo} onUndo={onUndo} onRedo={onRedo} onCopy={onCopy} onCut={onCut} onPaste={onPaste} />
<EditMenu
isDark={isDark}
labels={labels}
canUndo={canUndo}
canRedo={canRedo}
onUndo={onUndo}
onRedo={onRedo}
onCopy={onCopy}
onCut={onCut}
onPaste={onPaste}
/>
</MenuShell>
<MenuShell isDark={isDark} label={labels.view} menuClass={menuClass}>
<ViewMenu isDark={isDark} labels={labels} onSetHandTool={onSetHandTool} onSetPointerTool={onSetPointerTool} />
<ViewMenu
isDark={isDark}
labels={labels}
onSetHandTool={onSetHandTool}
onSetPointerTool={onSetPointerTool}
/>
</MenuShell>
<MenuShell isDark={isDark} label={labels.settings} menuClass={menuClass}>
<MenuShell
isDark={isDark}
label={labels.settings}
menuClass={menuClass}
>
<SettingsMenu isDark={isDark} preferences={labels.preferences} />
</MenuShell>
</nav>
@@ -116,7 +145,9 @@ export function EditorHeader({
disabled={!canUndo}
title={labels.undo}
className={`rounded border px-2.5 py-1.5 text-xs font-medium ${
isDark ? "border-white/15 bg-[#25272b] text-[#d7dae0]" : "border-black/15 bg-[#f2f4f8] text-[#1f2430]"
isDark
? "border-white/15 bg-[#25272b] text-[#d7dae0]"
: "border-black/15 bg-[#f2f4f8] text-[#1f2430]"
} ${!canUndo ? "opacity-50" : "hover:opacity-90"}`}
>
<Undo2 className="h-3.5 w-3.5" />
@@ -127,12 +158,16 @@ export function EditorHeader({
disabled={!canRedo}
title={labels.redo}
className={`rounded border px-2.5 py-1.5 text-xs font-medium ${
isDark ? "border-white/15 bg-[#25272b] text-[#d7dae0]" : "border-black/15 bg-[#f2f4f8] text-[#1f2430]"
isDark
? "border-white/15 bg-[#25272b] text-[#d7dae0]"
: "border-black/15 bg-[#f2f4f8] text-[#1f2430]"
} ${!canRedo ? "opacity-50" : "hover:opacity-90"}`}
>
<Redo2 className="h-3.5 w-3.5" />
</button>
<span className={`text-[10px] font-semibold uppercase tracking-[0.2em] ${isDark ? "text-[#a8abb2]" : "text-[#6c7382]"}`}>
<span
className={`text-[10px] font-semibold uppercase tracking-[0.2em] ${isDark ? "text-[#a8abb2]" : "text-[#6c7382]"}`}
>
{isDirty ? labels.unsavedChanges : labels.saved}
</span>
</div>
@@ -140,10 +175,23 @@ export function EditorHeader({
);
}
function MenuShell({ isDark, label, menuClass, children }: { isDark: boolean; label: string; menuClass: string; children: React.ReactNode }) {
function MenuShell({
isDark,
label,
menuClass,
children,
}: {
isDark: boolean;
label: string;
menuClass: string;
children: React.ReactNode;
}) {
return (
<div className="relative group">
<button type="button" className={`rounded px-2 py-1 ${isDark ? "text-[#d7dae0] hover:bg-white/10" : "text-[#1f2430] hover:bg-black/5"}`}>
<button
type="button"
className={`rounded px-2 py-1 ${isDark ? "text-[#d7dae0] hover:bg-white/10" : "text-[#1f2430] hover:bg-black/5"}`}
>
{label}
</button>
<div className={menuClass} onClick={(event) => event.stopPropagation()}>
@@ -176,13 +224,43 @@ function FileMenu({
}) {
return (
<div className="space-y-1">
<button type="button" onClick={onImportImage} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.importImage}</button>
<button type="button" onClick={onSave} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.save}</button>
<button
type="button"
onClick={onImportImage}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.importImage}
</button>
<button
type="button"
onClick={onSave}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.save}
</button>
<div className={`my-1 h-px ${dividerClass(isDark)}`} />
<button type="button" onClick={onExportPng} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.exportPng}</button>
<button type="button" onClick={onExportProjectFile} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.exportProjectFile}</button>
<button
type="button"
onClick={onExportPng}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.exportPng}
</button>
<button
type="button"
onClick={onExportProjectFile}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.exportProjectFile}
</button>
<div className={`my-1 h-px ${dividerClass(isDark)}`} />
<button type="button" onClick={onOpenCanvasSize} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.canvasSize} ({canvasWidth} x {canvasHeight})</button>
<button
type="button"
onClick={onOpenCanvasSize}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.canvasSize} ({canvasWidth} x {canvasHeight})
</button>
</div>
);
}
@@ -210,29 +288,93 @@ function EditMenu({
}) {
return (
<div className="space-y-1">
<button type="button" onClick={onUndo} disabled={!canUndo} className={`w-full rounded px-2 py-1 text-left ${!canUndo ? "opacity-50" : hoverSubtleClass(isDark)}`}>{labels.undo}</button>
<button type="button" onClick={onRedo} disabled={!canRedo} className={`w-full rounded px-2 py-1 text-left ${!canRedo ? "opacity-50" : hoverSubtleClass(isDark)}`}>{labels.redo}</button>
<button
type="button"
onClick={onUndo}
disabled={!canUndo}
className={`w-full rounded px-2 py-1 text-left ${!canUndo ? "opacity-50" : hoverSubtleClass(isDark)}`}
>
{labels.undo}
</button>
<button
type="button"
onClick={onRedo}
disabled={!canRedo}
className={`w-full rounded px-2 py-1 text-left ${!canRedo ? "opacity-50" : hoverSubtleClass(isDark)}`}
>
{labels.redo}
</button>
<div className={`my-1 h-px ${dividerClass(isDark)}`} />
<button type="button" onClick={onCopy} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.copy}</button>
<button type="button" onClick={onCut} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.cut}</button>
<button type="button" onClick={onPaste} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.paste}</button>
<button
type="button"
onClick={onCopy}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.copy}
</button>
<button
type="button"
onClick={onCut}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.cut}
</button>
<button
type="button"
onClick={onPaste}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.paste}
</button>
</div>
);
}
function ViewMenu({ isDark, labels, onSetHandTool, onSetPointerTool }: { isDark: boolean; labels: EditorHeaderProps["labels"]; onSetHandTool: () => void; onSetPointerTool: () => void }) {
function ViewMenu({
isDark,
labels,
onSetHandTool,
onSetPointerTool,
}: {
isDark: boolean;
labels: EditorHeaderProps["labels"];
onSetHandTool: () => void;
onSetPointerTool: () => void;
}) {
return (
<div className="space-y-1">
<button type="button" onClick={onSetHandTool} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.panTool}</button>
<button type="button" onClick={onSetPointerTool} className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}>{labels.pointerTool}</button>
<button
type="button"
onClick={onSetHandTool}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.panTool}
</button>
<button
type="button"
onClick={onSetPointerTool}
className={`w-full rounded px-2 py-1 text-left ${hoverSubtleClass(isDark)}`}
>
{labels.pointerTool}
</button>
</div>
);
}
function SettingsMenu({ isDark, preferences }: { isDark: boolean; preferences: string }) {
function SettingsMenu({
isDark,
preferences,
}: {
isDark: boolean;
preferences: string;
}) {
return (
<div className="space-y-2 p-1">
<p className={`px-2 text-[10px] font-semibold uppercase tracking-wide ${isDark ? "text-[#9aa1ad]" : "text-[#6c7382]"}`}>{preferences}</p>
<p
className={`px-2 text-[10px] font-semibold uppercase tracking-wide ${isDark ? "text-[#9aa1ad]" : "text-[#6c7382]"}`}
>
{preferences}
</p>
<UiPreferences />
</div>
);