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 type { IconTypes } from "solid-icons";
import { import {
TbOutlineAlignCenter, TbOutlineAlignCenter,
TbOutlineAlignLeft,
TbOutlineAlignRight,
TbOutlineArrowDown, TbOutlineArrowDown,
TbOutlineArrowDownLeft, TbOutlineArrowDownLeft,
TbOutlineArrowDownRight, TbOutlineArrowDownRight,
@@ -50,17 +48,43 @@ const ORIENTATIONS: {
{ value: "w", label: "西", icon: TbOutlineArrowLeft }, { value: "w", label: "西", icon: TbOutlineArrowLeft },
]; ];
const ALIGNS: { value: Align | ""; icon: IconTypes }[] = [ /**
{ value: "", icon: TbOutlineAlignCenter }, * The 4 orientations placed on a 3x3 cross (row-major); the remaining
{ value: "l", icon: TbOutlineAlignLeft }, * cells are empty so the layout matches the alignment grid:
{ value: "c", icon: TbOutlineAlignCenter }, * . n .
{ value: "r", icon: TbOutlineAlignRight }, * w . e
{ value: "tl", icon: TbOutlineArrowUpLeft }, * . s .
{ value: "tc", icon: TbOutlineArrowUp }, */
{ value: "tr", icon: TbOutlineArrowUpRight }, const ORIENT_GRID: ((typeof ORIENTATIONS)[number] | null)[] = [
{ value: "bl", icon: TbOutlineArrowDownLeft }, null,
{ value: "bc", icon: TbOutlineArrowDown }, ORIENTATIONS[0],
{ value: "br", icon: TbOutlineArrowDownRight }, 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; const FONT_PRESETS = [3, 5, 8, 12] as const;
@@ -81,26 +105,7 @@ function orientIcon(v: string): IconTypes {
} }
function alignIcon(v: string): IconTypes { function alignIcon(v: string): IconTypes {
switch (v) { return ALIGNS.find((a) => a.value === v)?.icon ?? TbOutlineAlignCenter;
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;
}
} }
export function LayerRow(props: LayerRowProps) { export function LayerRow(props: LayerRowProps) {
@@ -179,20 +184,27 @@ export function LayerRow(props: LayerRowProps) {
} }
title="方向" title="方向"
> >
<For each={ORIENTATIONS}> <div class="grid grid-cols-[repeat(3,1.75rem)] gap-1 p-2">
{(o) => ( <For each={ORIENT_GRID}>
<button {(o) =>
onClick={() => props.onUpdateOrientation(o.value)} o ? (
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap" <button
> onClick={() => props.onUpdateOrientation(o.value)}
<Dynamic 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"}`}
component={o.icon} title={o.label}
class="inline-block align-[-0.125em]" >
/>{" "} <Dynamic
{o.label} component={o.icon}
</button> size={16}
)} class="not-prose max-w-none"
</For> />
</button>
) : (
<span />
)
}
</For>
</div>
</DropdownButton> </DropdownButton>
<DropdownButton <DropdownButton
@@ -208,20 +220,23 @@ export function LayerRow(props: LayerRowProps) {
} }
title="对齐" title="对齐"
> >
<For each={ALIGNS}> <div class="grid grid-cols-[repeat(3,1.75rem)] gap-1 p-2">
{(o) => ( <For each={ALIGNS}>
<button {(o) => (
onClick={() => props.onUpdateAlign(o.value || undefined)} <button
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"}`}
<Dynamic title={o.label}
component={o.icon} >
size={16} <Dynamic
class="not-prose max-w-none" component={o.icon}
/> size={16}
</button> class="not-prose max-w-none"
)} />
</For> </button>
)}
</For>
</div>
</DropdownButton> </DropdownButton>
<div class="relative" ref={fontRef}> <div class="relative" ref={fontRef}>