修复并整理锚点预设 SVG 图标

提取 AnchorPresetIcon SVG 图标组件并区分定位与拉伸方向。

将 AnchorMode 统一为轴向无关的 start、center、end、stretch。

保留现有 4×4 锚点预设交互与数值语义。
This commit is contained in:
2026-08-18 16:26:04 +08:00
parent 3012519911
commit cc4b89c0f4
2 changed files with 153 additions and 21 deletions
@@ -0,0 +1,142 @@
export type AnchorMode = 'start' | 'center' | 'end' | 'stretch';
const PRESET_ICON_BOUNDARY = 3.5;
const PRESET_ICON_CENTER = 10;
const PRESET_ICON_EDGE = 16.5;
function anchorCoordinate(mode: Exclude<AnchorMode, 'stretch'>): number {
return mode === 'start'
? PRESET_ICON_BOUNDARY
: mode === 'center'
? PRESET_ICON_CENTER
: PRESET_ICON_EDGE;
}
export function AnchorPresetIcon({ x, y }: { x: AnchorMode; y: AnchorMode }) {
const xStretch = x === 'stretch';
const yStretch = y === 'stretch';
const xPosition = xStretch ? PRESET_ICON_CENTER : anchorCoordinate(x);
const yPosition = yStretch ? PRESET_ICON_CENTER : anchorCoordinate(y);
return (
<svg aria-hidden="true" className="size-5" viewBox="0 0 20 20" fill="none">
<rect
x="2.5"
y="2.5"
width="15"
height="15"
rx="0.5"
stroke="currentColor"
strokeOpacity="0.38"
/>
{!xStretch ? (
<line
x1={xPosition}
y1={PRESET_ICON_BOUNDARY}
x2={xPosition}
y2={PRESET_ICON_EDGE}
stroke="#d8665f"
strokeWidth="1.15"
/>
) : null}
{!yStretch ? (
<line
x1={PRESET_ICON_BOUNDARY}
y1={yPosition}
x2={PRESET_ICON_EDGE}
y2={yPosition}
stroke="#d8665f"
strokeWidth="1.15"
/>
) : null}
{xStretch ? (
<>
<line
x1={PRESET_ICON_BOUNDARY}
y1={PRESET_ICON_CENTER}
x2={PRESET_ICON_EDGE}
y2={PRESET_ICON_CENTER}
stroke="#3b82f6"
strokeWidth="1.2"
/>
<path
d="M3.5 10 5.5 8.6v2.8L3.5 10Zm13 0-2-1.4v2.8l2-1.4Z"
fill="#3b82f6"
/>
</>
) : null}
{yStretch ? (
<>
<line
x1={PRESET_ICON_CENTER}
y1={PRESET_ICON_BOUNDARY}
x2={PRESET_ICON_CENTER}
y2={PRESET_ICON_EDGE}
stroke="#3b82f6"
strokeWidth="1.2"
/>
<path
d="m10 3.5-1.4 2h2.8L10 3.5Zm0 13 1.4-2H8.6l1.4 2Z"
fill="#3b82f6"
/>
</>
) : null}
{!xStretch && !yStretch ? (
<circle cx={xPosition} cy={yPosition} r="1.25" fill="#eab308" />
) : null}
{xStretch && !yStretch ? (
<>
<circle
cx={PRESET_ICON_BOUNDARY}
cy={yPosition}
r="1"
fill="#eab308"
/>
<circle cx={PRESET_ICON_EDGE} cy={yPosition} r="1" fill="#eab308" />
</>
) : null}
{!xStretch && yStretch ? (
<>
<circle
cx={xPosition}
cy={PRESET_ICON_BOUNDARY}
r="1"
fill="#eab308"
/>
<circle cx={xPosition} cy={PRESET_ICON_EDGE} r="1" fill="#eab308" />
</>
) : null}
{xStretch && yStretch ? (
<>
<circle
cx={PRESET_ICON_BOUNDARY}
cy={PRESET_ICON_BOUNDARY}
r="1"
fill="#eab308"
/>
<circle
cx={PRESET_ICON_EDGE}
cy={PRESET_ICON_BOUNDARY}
r="1"
fill="#eab308"
/>
<circle
cx={PRESET_ICON_BOUNDARY}
cy={PRESET_ICON_EDGE}
r="1"
fill="#eab308"
/>
<circle
cx={PRESET_ICON_EDGE}
cy={PRESET_ICON_EDGE}
r="1"
fill="#eab308"
/>
</>
) : null}
</svg>
);
}
@@ -5,7 +5,6 @@ import {
Crosshair,
Info,
LockKeyhole,
Maximize2,
Move,
SlidersHorizontal,
Unlock,
@@ -13,6 +12,7 @@ import {
import { useEffect, useMemo, useRef, useState } from 'react';
import type { Transform } from '../../../../../features/ui-editor/types/Transform';
import { type AnchorMode, AnchorPresetIcon } from './AnchorPresetIcon';
export type TransformEditorParentSize = {
width: number;
@@ -27,7 +27,6 @@ export type TransformEditorProps = {
};
type Axis = 0 | 1;
type AnchorMode = 'left' | 'center' | 'right' | 'stretch';
type AnchorPreset = {
id: string;
label: string;
@@ -37,19 +36,19 @@ type AnchorPreset = {
const AXIS_LABELS = ['X', 'Y'] as const;
const AXIS_MODE_LABELS: Record<AnchorMode, string> = {
left: '左侧',
start: '左侧',
center: '居中',
right: '右侧',
end: '右侧',
stretch: '拉伸',
};
const ROW_MODE_LABELS: Record<AnchorMode, string> = {
left: '顶部',
start: '顶部',
center: '居中',
right: '底部',
end: '底部',
stretch: '拉伸',
};
const COLUMN_MODES: AnchorMode[] = ['left', 'center', 'right', 'stretch'];
const ROW_MODES: AnchorMode[] = ['left', 'center', 'right', 'stretch'];
const COLUMN_MODES: AnchorMode[] = ['start', 'center', 'end', 'stretch'];
const ROW_MODES: AnchorMode[] = ['start', 'center', 'end', 'stretch'];
const PRESETS: AnchorPreset[] = ROW_MODES.flatMap((y) =>
COLUMN_MODES.map((x) => ({
@@ -63,8 +62,8 @@ const PRESETS: AnchorPreset[] = ROW_MODES.flatMap((y) =>
const CUSTOM_PRESET: AnchorPreset = {
id: 'custom',
label: '自定义',
x: 'left',
y: 'left',
x: 'start',
y: 'start',
};
const FIELD_HINTS = {
@@ -91,7 +90,7 @@ function modeAnchors(mode: AnchorMode): [number, number] {
if (mode === 'stretch') {
return [0, 1];
}
const value = mode === 'left' ? 0 : mode === 'center' ? 0.5 : 1;
const value = mode === 'start' ? 0 : mode === 'center' ? 0.5 : 1;
return [value, value];
}
@@ -501,16 +500,7 @@ export function TransformEditor({
setPresetOpen(false);
}}
>
<span className="relative grid size-5 place-items-center rounded border border-current/35">
<span className="size-1 rounded-full bg-current" />
{column === 'stretch' || row === 'stretch' ? (
<Maximize2
size={12}
className="absolute"
aria-hidden="true"
/>
) : null}
</span>
<AnchorPresetIcon x={column} y={row} />
</button>
);
}),