feat(md-deck): rework layer align/orientation pickers

Replace the flat dropdown lists with 3x3 icon grids. Alignment uses
arrow icons for the 8 edge/corner positions and a center-align icon
for the middle cell; orientation places its 4 arrows on a cross. Cells
use fixed-width tracks so they no longer collapse inside the
shrink-to-fit dropdown.
This commit is contained in:
2026-10-03 17:16:52 +08:00
parent c81cf221ef
commit bd7871a800
@@ -5,8 +5,6 @@ import { createSortable, useDragDropContext } from "@thisbeyond/solid-dnd";
import type { IconTypes } from "solid-icons";
import {
TbOutlineAlignCenter,
TbOutlineAlignLeft,
TbOutlineAlignRight,
TbOutlineArrowDown,
TbOutlineArrowDownLeft,
TbOutlineArrowDownRight,
@@ -50,17 +48,43 @@ const ORIENTATIONS: {
{ value: "w", label: "西", icon: TbOutlineArrowLeft },
];
const ALIGNS: { value: Align | ""; icon: IconTypes }[] = [
{ value: "", icon: TbOutlineAlignCenter },
{ value: "l", icon: TbOutlineAlignLeft },
{ value: "c", icon: TbOutlineAlignCenter },
{ value: "r", icon: TbOutlineAlignRight },
{ value: "tl", icon: TbOutlineArrowUpLeft },
{ value: "tc", icon: TbOutlineArrowUp },
{ value: "tr", icon: TbOutlineArrowUpRight },
{ value: "bl", icon: TbOutlineArrowDownLeft },
{ value: "bc", icon: TbOutlineArrowDown },
{ value: "br", icon: TbOutlineArrowDownRight },
/**
* The 4 orientations placed on a 3x3 cross (row-major); the remaining
* cells are empty so the layout matches the alignment grid:
* . n .
* w . e
* . s .
*/
const ORIENT_GRID: ((typeof ORIENTATIONS)[number] | null)[] = [
null,
ORIENTATIONS[0],
null,
ORIENTATIONS[3],
null,
ORIENTATIONS[1],
null,
ORIENTATIONS[2],
null,
];
/**
* The 9 alignments laid out as a 3x3 grid (row-major):
* tl tc tr
* l c r
* bl bc br
* Every icon is an arrow pointing toward the aligned corner/edge; the
* center cell uses the standard center-align icon.
*/
const ALIGNS: { value: Align; label: string; icon: IconTypes }[] = [
{ value: "tl", label: "左上", icon: TbOutlineArrowUpLeft },
{ value: "tc", label: "上", icon: TbOutlineArrowUp },
{ value: "tr", label: "右上", icon: TbOutlineArrowUpRight },
{ value: "l", label: "左", icon: TbOutlineArrowLeft },
{ value: "c", label: "居中", icon: TbOutlineAlignCenter },
{ value: "r", label: "右", icon: TbOutlineArrowRight },
{ value: "bl", label: "左下", icon: TbOutlineArrowDownLeft },
{ value: "bc", label: "下", icon: TbOutlineArrowDown },
{ value: "br", label: "右下", icon: TbOutlineArrowDownRight },
];
const FONT_PRESETS = [3, 5, 8, 12] as const;
@@ -81,26 +105,7 @@ function orientIcon(v: string): IconTypes {
}
function alignIcon(v: string): IconTypes {
switch (v) {
case "tl":
return TbOutlineArrowUpLeft;
case "tc":
return TbOutlineArrowUp;
case "tr":
return TbOutlineArrowUpRight;
case "bl":
return TbOutlineArrowDownLeft;
case "bc":
return TbOutlineArrowDown;
case "br":
return TbOutlineArrowDownRight;
case "l":
return TbOutlineAlignLeft;
case "r":
return TbOutlineAlignRight;
default:
return TbOutlineAlignCenter;
}
return ALIGNS.find((a) => a.value === v)?.icon ?? TbOutlineAlignCenter;
}
export function LayerRow(props: LayerRowProps) {
@@ -179,20 +184,27 @@ export function LayerRow(props: LayerRowProps) {
}
title="方向"
>
<For each={ORIENTATIONS}>
{(o) => (
<div class="grid grid-cols-[repeat(3,1.75rem)] gap-1 p-2">
<For each={ORIENT_GRID}>
{(o) =>
o ? (
<button
onClick={() => props.onUpdateOrientation(o.value)}
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
class={`w-7 h-7 rounded flex items-center justify-center cursor-pointer ${(props.layer.orientation ?? "n") === o.value ? "bg-blue-500 text-white" : "bg-gray-100 hover:bg-gray-200 text-gray-700"}`}
title={o.label}
>
<Dynamic
component={o.icon}
class="inline-block align-[-0.125em]"
/>{" "}
{o.label}
size={16}
class="not-prose max-w-none"
/>
</button>
)}
) : (
<span />
)
}
</For>
</div>
</DropdownButton>
<DropdownButton
@@ -208,11 +220,13 @@ export function LayerRow(props: LayerRowProps) {
}
title="对齐"
>
<div class="grid grid-cols-[repeat(3,1.75rem)] gap-1 p-2">
<For each={ALIGNS}>
{(o) => (
<button
onClick={() => props.onUpdateAlign(o.value || undefined)}
class="flex items-center gap-2 w-full px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
onClick={() => props.onUpdateAlign(o.value)}
class={`w-7 h-7 rounded flex items-center justify-center cursor-pointer ${(props.layer.align ?? "c") === o.value ? "bg-blue-500 text-white" : "bg-gray-100 hover:bg-gray-200 text-gray-700"}`}
title={o.label}
>
<Dynamic
component={o.icon}
@@ -222,6 +236,7 @@ export function LayerRow(props: LayerRowProps) {
</button>
)}
</For>
</div>
</DropdownButton>
<div class="relative" ref={fontRef}>