Three things moved the viewport independently — the shape-fit effect, focusNode, and React Flow's own fitView prop — so a fourth for "centre the node I just selected" would have been a fourth party to the argument. There is one effect now, and which branch it takes is decided by what changed rather than by what is true: selecting a node brings that node into the lane the panel leaves, and every other change — new wiring, a new endpoint, a panel opening — re-fits the whole flow into the same lane. A selection centres once, so the port edits that follow re-fit around it, which is what makes a new edge's far end visible. The refit triggers on the edge count, not the bindings key: that key changes on every keystroke in a message-name field, and refitting per character is not what "an edge was created" means. renderedNodes overwrote xyflow's own `selected` flag, so a box-selection of several nodes was invisible even though delete and copy acted on all of them. The logs panel was a popover anchored on its own button, which is why it sat off centre, hugged the button and closed on any outside click. It is a plain surface above the dock now, and the button is stateful. Escape still closes it. Expanding a node's editor gives the panel the whole inset and puts the code on the left with the settings beside it, while the toolbar and the flow name translate off screen. Narrowing the window past `md` gives the room back — the sheet it becomes has no second column to hold. The zoom buttons are gone: there is a mouse, or there is a pinch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uq8mtNb97A7praJLyeEYgs
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
import { motion } from "motion/react"
|
|
import type { ReactNode } from "react"
|
|
|
|
import { SidebarTrigger } from "@/components/ui/sidebar"
|
|
import { slideUp, transitions } from "@/lib/motion"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
/**
|
|
* What you are looking at, floating top-centre over a full-bleed canvas.
|
|
*
|
|
* Reference rather than navigation: switching to another flow or dashboard is
|
|
* what the overviews are for, so this bar carries the name and nothing that
|
|
* takes you away from it. Shared by the flow editor and the dashboards so the
|
|
* two shells read as one.
|
|
*
|
|
* The one exception sits beside it rather than in it: a dashboard assigned to
|
|
* a panel alongside others is edited with that panel's rail on screen, because
|
|
* the wall has it too and it takes room off the canvas.
|
|
*/
|
|
export function CanvasTitle({
|
|
children,
|
|
className,
|
|
}: {
|
|
children: ReactNode
|
|
/** Lets the shell move the bar out of the way. */
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<motion.div
|
|
variants={slideUp}
|
|
initial="hidden"
|
|
animate="visible"
|
|
exit="exit"
|
|
transition={transitions.emphasized}
|
|
className={cn(
|
|
"pointer-events-auto absolute left-1/2 top-4 z-10 flex max-w-[calc(100%-2rem)] -translate-x-1/2 items-center gap-1 rounded-full border border-border bg-card/80 px-1.5 py-1 shadow-e2 backdrop-blur-md",
|
|
className,
|
|
)}
|
|
>
|
|
{/* The sidebar carries its own collapse control; a phone has no sidebar
|
|
on screen to carry it. */}
|
|
<SidebarTrigger className="size-8 shrink-0 text-muted-foreground md:hidden" />
|
|
{children}
|
|
</motion.div>
|
|
)
|
|
}
|