优化 TransformEditor:
- 本地化锚点、偏移及相关 UI 文本。 - 添加字段提示说明,提升编辑体验。 - 改进输入框布局和样式,增强可读性和交互性。 - 引入 `FieldLabel` 组件,支持带提示的字段标签渲染。
This commit is contained in:
+73
-31
@@ -3,6 +3,7 @@ import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Crosshair,
|
||||
Info,
|
||||
LockKeyhole,
|
||||
Maximize2,
|
||||
Move,
|
||||
@@ -36,16 +37,16 @@ type AnchorPreset = {
|
||||
|
||||
const AXIS_LABELS = ['X', 'Y'] as const;
|
||||
const AXIS_MODE_LABELS: Record<AnchorMode, string> = {
|
||||
left: 'Left',
|
||||
center: 'Center',
|
||||
right: 'Right',
|
||||
stretch: 'Stretch',
|
||||
left: '左侧',
|
||||
center: '居中',
|
||||
right: '右侧',
|
||||
stretch: '拉伸',
|
||||
};
|
||||
const ROW_MODE_LABELS: Record<AnchorMode, string> = {
|
||||
left: 'Top',
|
||||
center: 'Middle',
|
||||
right: 'Bottom',
|
||||
stretch: 'Stretch',
|
||||
left: '顶部',
|
||||
center: '居中',
|
||||
right: '底部',
|
||||
stretch: '拉伸',
|
||||
};
|
||||
const COLUMN_MODES: AnchorMode[] = ['left', 'center', 'right', 'stretch'];
|
||||
const ROW_MODES: AnchorMode[] = ['left', 'center', 'right', 'stretch'];
|
||||
@@ -61,11 +62,20 @@ const PRESETS: AnchorPreset[] = ROW_MODES.flatMap((y) =>
|
||||
|
||||
const CUSTOM_PRESET: AnchorPreset = {
|
||||
id: 'custom',
|
||||
label: 'Custom',
|
||||
label: '自定义',
|
||||
x: 'left',
|
||||
y: 'left',
|
||||
};
|
||||
|
||||
const FIELD_HINTS = {
|
||||
anchor_min:
|
||||
'锚点最小值:用父容器的比例位置(0 到 1)定义元素的左上边界。0 表示左侧或顶部,1 表示右侧或底部。',
|
||||
anchor_max:
|
||||
'锚点最大值:用父容器的比例位置(0 到 1)定义元素的右下边界。与最小值不同可让元素随父容器拉伸。',
|
||||
offset_min: '偏移最小值:相对于最小锚点的像素偏移,控制元素左侧和顶部的位置。',
|
||||
offset_max: '偏移最大值:相对于最大锚点的像素偏移,控制元素右侧和底部的位置。',
|
||||
} as const;
|
||||
|
||||
function cloneTransform(transform: Transform): Transform {
|
||||
return {
|
||||
anchor_min: [...transform.anchor_min],
|
||||
@@ -176,22 +186,22 @@ function formatValue(value: number): string {
|
||||
|
||||
function VectorInputRow({
|
||||
label,
|
||||
hint,
|
||||
values,
|
||||
step,
|
||||
readOnly,
|
||||
onCommit,
|
||||
}: {
|
||||
label: string;
|
||||
hint: string;
|
||||
values: readonly [number, number];
|
||||
step: number;
|
||||
readOnly: boolean;
|
||||
onCommit: (axis: Axis, value: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="grid grid-cols-[5.6rem_minmax(0,1fr)_minmax(0,1fr)] items-center gap-2">
|
||||
<span className="text-[11px] font-semibold tracking-wide text-(--platform-text-soft)">
|
||||
{label}
|
||||
</span>
|
||||
<div className="grid grid-cols-[6.5rem_minmax(0,1fr)_minmax(0,1fr)] items-center gap-2">
|
||||
<FieldLabel label={label} hint={hint} />
|
||||
{AXIS_LABELS.map((axisLabel, axis) => (
|
||||
<ScalarInput
|
||||
key={axisLabel}
|
||||
@@ -206,6 +216,31 @@ function VectorInputRow({
|
||||
);
|
||||
}
|
||||
|
||||
function FieldLabel({ label, hint }: { label: string; hint: string }) {
|
||||
return (
|
||||
<span
|
||||
className="group/field relative inline-flex items-center gap-1 text-[11px] font-semibold tracking-wide text-(--platform-text-soft)"
|
||||
title={hint}
|
||||
>
|
||||
{label}
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${label}说明`}
|
||||
title={hint}
|
||||
className="grid size-4 shrink-0 place-items-center rounded-full text-(--platform-text-soft) outline-none transition hover:bg-orange-100 hover:text-orange-700 focus-visible:ring-2 focus-visible:ring-orange-200"
|
||||
>
|
||||
<Info size={11} aria-hidden="true" />
|
||||
</button>
|
||||
<span
|
||||
role="tooltip"
|
||||
className="pointer-events-none absolute bottom-[calc(100%+0.4rem)] left-0 z-40 hidden w-64 rounded-lg border border-(--platform-subpanel-border) bg-(--platform-neutral-bg) px-2.5 py-2 text-[10px] font-normal leading-relaxed text-(--platform-neutral-text) shadow-lg group-hover/field:block group-focus-within/field:block"
|
||||
>
|
||||
{hint}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function ScalarInput({
|
||||
ariaLabel,
|
||||
value,
|
||||
@@ -276,7 +311,7 @@ function ScalarInput({
|
||||
<span className="absolute right-1 top-1/2 flex -translate-y-1/2 flex-col gap-0.5">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${ariaLabel} increase`}
|
||||
aria-label={`${ariaLabel} 增加`}
|
||||
disabled={readOnly}
|
||||
className="grid size-5 place-items-center rounded text-(--platform-text-soft) transition hover:bg-orange-100 hover:text-orange-700 disabled:cursor-default disabled:opacity-40"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
@@ -286,7 +321,7 @@ function ScalarInput({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${ariaLabel} decrease`}
|
||||
aria-label={`${ariaLabel} 减少`}
|
||||
disabled={readOnly}
|
||||
className="grid size-5 place-items-center rounded text-(--platform-text-soft) transition hover:bg-orange-100 hover:text-orange-700 disabled:cursor-default disabled:opacity-40"
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
@@ -361,21 +396,24 @@ export function TransformEditor({
|
||||
<SlidersHorizontal size={15} aria-hidden="true" />
|
||||
</span>
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold">Rect Transform</h2>
|
||||
<p className="text-[10px] text-(--platform-text-soft)">
|
||||
Anchors & offsets
|
||||
<h2 className="text-sm font-semibold">矩形变换</h2>
|
||||
<p
|
||||
className="text-[10px] text-(--platform-text-soft)"
|
||||
title="通过锚点比例和像素偏移控制元素在父容器中的位置与尺寸。"
|
||||
>
|
||||
锚点与偏移
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{readOnly ? (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-slate-300 bg-slate-200 px-2 py-1 text-[10px] font-semibold text-slate-600">
|
||||
<LockKeyhole size={12} aria-hidden="true" />
|
||||
Read only
|
||||
只读
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-emerald-200 bg-emerald-50 px-2 py-1 text-[10px] font-semibold text-emerald-600">
|
||||
<Unlock size={12} aria-hidden="true" />
|
||||
Editable
|
||||
可编辑
|
||||
</span>
|
||||
)}
|
||||
</header>
|
||||
@@ -386,6 +424,7 @@ export function TransformEditor({
|
||||
type="button"
|
||||
aria-expanded={presetOpen}
|
||||
aria-haspopup="dialog"
|
||||
title="锚点预设:选择元素相对于父容器的锚点位置;拉伸模式会让元素边界随父容器变化。"
|
||||
disabled={readOnly}
|
||||
className="flex h-11 w-full items-center justify-between rounded-xl border border-(--platform-subpanel-border) bg-white/65 px-3 text-left transition hover:border-orange-300 hover:bg-white disabled:cursor-default disabled:opacity-65"
|
||||
onClick={() => setPresetOpen((open) => !open)}
|
||||
@@ -396,7 +435,7 @@ export function TransformEditor({
|
||||
</span>
|
||||
<span>
|
||||
<span className="block text-[10px] font-semibold uppercase tracking-[0.08em] text-(--platform-text-soft)">
|
||||
Anchor preset
|
||||
锚点预设
|
||||
</span>
|
||||
<span className="block text-xs font-semibold">
|
||||
{selectedPreset.label}
|
||||
@@ -414,11 +453,11 @@ export function TransformEditor({
|
||||
<div
|
||||
className="absolute inset-x-0 top-[calc(100%+0.5rem)] z-30 rounded-2xl border border-(--platform-subpanel-border) bg-white p-3 shadow-[0_18px_42px_rgb(41_31_24/18%)]"
|
||||
role="dialog"
|
||||
aria-label="Anchor presets"
|
||||
aria-label="锚点预设"
|
||||
>
|
||||
<div className="mb-2 flex items-center justify-between px-1">
|
||||
<span className="text-[10px] font-bold uppercase tracking-widest text-(--platform-text-soft)">
|
||||
Anchor
|
||||
锚点
|
||||
</span>
|
||||
<span className="font-mono text-[10px] text-(--platform-text-soft)">
|
||||
4 × 4
|
||||
@@ -485,7 +524,7 @@ export function TransformEditor({
|
||||
setPresetOpen(false);
|
||||
}}
|
||||
>
|
||||
<span>Custom Anchor</span>
|
||||
<span>自定义锚点</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -495,10 +534,11 @@ export function TransformEditor({
|
||||
<div className="grid gap-2 rounded-xl border border-dashed border-orange-200 bg-orange-50/45 p-3">
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-[0.08em] text-orange-700">
|
||||
<Move size={13} aria-hidden="true" />
|
||||
Custom Anchor
|
||||
自定义锚点
|
||||
</div>
|
||||
<VectorInputRow
|
||||
label="Anchor Min"
|
||||
label="锚点最小值"
|
||||
hint={FIELD_HINTS.anchor_min}
|
||||
values={transform.anchor_min}
|
||||
step={0.05}
|
||||
readOnly={readOnly}
|
||||
@@ -507,7 +547,8 @@ export function TransformEditor({
|
||||
}
|
||||
/>
|
||||
<VectorInputRow
|
||||
label="Anchor Max"
|
||||
label="锚点最大值"
|
||||
hint={FIELD_HINTS.anchor_max}
|
||||
values={transform.anchor_max}
|
||||
step={0.05}
|
||||
readOnly={readOnly}
|
||||
@@ -529,22 +570,23 @@ export function TransformEditor({
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
Anchor values are outside the standard range or produce an invalid
|
||||
rectangle.
|
||||
锚点值超出标准范围,或当前配置会产生无效的矩形区域。
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<VectorInputRow
|
||||
label="Offset Min"
|
||||
label="偏移最小值"
|
||||
hint={FIELD_HINTS.offset_min}
|
||||
values={transform.offset_min}
|
||||
step={1}
|
||||
readOnly={readOnly}
|
||||
onCommit={(axis, value) => updateVector('offset_min', axis, value)}
|
||||
/>
|
||||
<VectorInputRow
|
||||
label="Offset Max"
|
||||
label="偏移最大值"
|
||||
hint={FIELD_HINTS.offset_max}
|
||||
values={transform.offset_max}
|
||||
step={1}
|
||||
readOnly={readOnly}
|
||||
|
||||
Reference in New Issue
Block a user