Fix workflow add menu layout to use vertical stacking (#4568)
This commit is contained in:
@@ -18,8 +18,8 @@ interface RadialMenuProps {
|
|||||||
*/
|
*/
|
||||||
buttonSize?: string;
|
buttonSize?: string;
|
||||||
/**
|
/**
|
||||||
* The gap between items in degrees. If not provided, items are evenly spaced
|
* The gap between items in degrees (for radial layout) or pixels (for vertical layout).
|
||||||
* around the circle.
|
* If not provided, items are evenly spaced around the circle (radial) or use 8px gap (vertical).
|
||||||
*/
|
*/
|
||||||
gap?: number;
|
gap?: number;
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +35,11 @@ interface RadialMenuProps {
|
|||||||
* If true, rotates the text so its baseline runs parallel to the radial line.
|
* If true, rotates the text so its baseline runs parallel to the radial line.
|
||||||
*/
|
*/
|
||||||
rotateText?: boolean;
|
rotateText?: boolean;
|
||||||
|
/**
|
||||||
|
* Layout mode: "radial" (circular) or "vertical" (stacked list to the right).
|
||||||
|
* Defaults to "radial".
|
||||||
|
*/
|
||||||
|
layout?: "radial" | "vertical";
|
||||||
}
|
}
|
||||||
|
|
||||||
const proportionalAngle = (
|
const proportionalAngle = (
|
||||||
@@ -76,6 +81,7 @@ export function RadialMenu({
|
|||||||
gap,
|
gap,
|
||||||
startAt,
|
startAt,
|
||||||
rotateText,
|
rotateText,
|
||||||
|
layout = "radial",
|
||||||
}: RadialMenuProps) {
|
}: RadialMenuProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [calculatedRadius, setCalculatedRadius] = useState<number>(100);
|
const [calculatedRadius, setCalculatedRadius] = useState<number>(100);
|
||||||
@@ -137,13 +143,72 @@ export function RadialMenu({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{visibleItems.map((item, index) => {
|
{visibleItems.map((item, index) => {
|
||||||
|
const isEnabled = item.enabled !== false;
|
||||||
|
const btnSize = buttonSize ?? "40px";
|
||||||
|
const btnSizeNum = parseFloat(btnSize);
|
||||||
|
|
||||||
|
if (layout === "vertical") {
|
||||||
|
// Vertical layout: stack items to the right of center
|
||||||
|
const verticalGap = gap ?? 8;
|
||||||
|
const xOffset = numRadius;
|
||||||
|
// Center the stack vertically around the middle item
|
||||||
|
const totalHeight =
|
||||||
|
visibleItems.length * btnSizeNum +
|
||||||
|
(visibleItems.length - 1) * verticalGap;
|
||||||
|
const startY = -totalHeight / 2 + btnSizeNum / 2;
|
||||||
|
const yOffset = startY + index * (btnSizeNum + verticalGap);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => {
|
||||||
|
if (isEnabled) {
|
||||||
|
item.onClick();
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="absolute left-1/2 top-1/2 z-30 flex cursor-pointer items-center gap-2 transition-all duration-300 ease-out"
|
||||||
|
style={{
|
||||||
|
transform: isOpen
|
||||||
|
? `translate(0%, -50%) translate(${xOffset}px, ${yOffset}px) scale(1)`
|
||||||
|
: `translate(0%, -50%) translate(0, 0) scale(0)`,
|
||||||
|
opacity: isOpen ? (isEnabled ? 1 : 0.5) : 0,
|
||||||
|
pointerEvents: isOpen ? (isEnabled ? "auto" : "none") : "none",
|
||||||
|
transitionDelay: isOpen ? `${index * 50}ms` : "0ms",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
disabled={!isEnabled}
|
||||||
|
className="flex items-center justify-center rounded-full bg-white shadow-lg hover:bg-gray-50 disabled:cursor-not-allowed"
|
||||||
|
style={{
|
||||||
|
width: btnSize,
|
||||||
|
height: btnSize,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-gray-700">{item.icon}</div>
|
||||||
|
</button>
|
||||||
|
{item.text && (
|
||||||
|
<span
|
||||||
|
className="whitespace-nowrap rounded bg-white px-2 py-1 text-xs text-gray-700 shadow-md"
|
||||||
|
style={{
|
||||||
|
opacity: isEnabled ? 1 : 0.5,
|
||||||
|
cursor: isEnabled ? "pointer" : "not-allowed",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.text}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radial layout (default)
|
||||||
const angle =
|
const angle =
|
||||||
gap !== undefined
|
gap !== undefined
|
||||||
? gappedAngle(index, gap, startAt)
|
? gappedAngle(index, gap, startAt)
|
||||||
: proportionalAngle(index, visibleItems.length, startAt);
|
: proportionalAngle(index, visibleItems.length, startAt);
|
||||||
const x = Math.cos(angle) * parseFloat(radiusValue);
|
const x = Math.cos(angle) * parseFloat(radiusValue);
|
||||||
const y = Math.sin(angle) * parseFloat(radiusValue);
|
const y = Math.sin(angle) * parseFloat(radiusValue);
|
||||||
const isEnabled = item.enabled !== false;
|
|
||||||
|
|
||||||
// calculate text offset along the radial line
|
// calculate text offset along the radial line
|
||||||
const textDistance = 0.375 * numRadius;
|
const textDistance = 0.375 * numRadius;
|
||||||
@@ -170,8 +235,8 @@ export function RadialMenu({
|
|||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
className="absolute left-1/2 top-1/2 z-30 flex items-center justify-center rounded-full bg-white shadow-lg transition-all duration-300 ease-out hover:bg-gray-50 disabled:cursor-not-allowed"
|
className="absolute left-1/2 top-1/2 z-30 flex items-center justify-center rounded-full bg-white shadow-lg transition-all duration-300 ease-out hover:bg-gray-50 disabled:cursor-not-allowed"
|
||||||
style={{
|
style={{
|
||||||
width: buttonSize ?? "40px",
|
width: btnSize,
|
||||||
height: buttonSize ?? "40px",
|
height: btnSize,
|
||||||
transform: isOpen
|
transform: isOpen
|
||||||
? `translate(-50%, -50%) translate(${x}px, ${y}px) scale(1)`
|
? `translate(-50%, -50%) translate(${x}px, ${y}px) scale(1)`
|
||||||
: "translate(-50%, -50%) translate(0, 0) scale(0)",
|
: "translate(-50%, -50%) translate(0, 0) scale(0)",
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ function WorkflowAddMenu({
|
|||||||
startAt={startAt}
|
startAt={startAt}
|
||||||
gap={gap}
|
gap={gap}
|
||||||
rotateText={rotateText}
|
rotateText={rotateText}
|
||||||
|
layout="vertical"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</RadialMenu>
|
</RadialMenu>
|
||||||
|
|||||||
Reference in New Issue
Block a user