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:
@@ -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) => (
|
||||
<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"
|
||||
>
|
||||
<Dynamic
|
||||
component={o.icon}
|
||||
class="inline-block align-[-0.125em]"
|
||||
/>{" "}
|
||||
{o.label}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
<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={`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}
|
||||
size={16}
|
||||
class="not-prose max-w-none"
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<span />
|
||||
)
|
||||
}
|
||||
</For>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
|
||||
<DropdownButton
|
||||
@@ -208,20 +220,23 @@ export function LayerRow(props: LayerRowProps) {
|
||||
}
|
||||
title="对齐"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<Dynamic
|
||||
component={o.icon}
|
||||
size={16}
|
||||
class="not-prose max-w-none"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
<div class="grid grid-cols-[repeat(3,1.75rem)] gap-1 p-2">
|
||||
<For each={ALIGNS}>
|
||||
{(o) => (
|
||||
<button
|
||||
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}
|
||||
size={16}
|
||||
class="not-prose max-w-none"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
|
||||
<div class="relative" ref={fontRef}>
|
||||
|
||||
Reference in New Issue
Block a user