Long names stay in their box: overview cards and Home's dashboard tiles truncate with the full name on hover, the node panel's flow path and a device id leave half the row to the name field, dialog titles and select items wrap an unbroken name, and a select menu is capped at the room it has; the flow canvas shows a loader until the layout engine has placed the nodes, so a slow link no longer looks like an empty flow
This commit is contained in:
@@ -165,8 +165,11 @@ function Tile({
|
||||
)}
|
||||
>
|
||||
<Footprint dashboard={dashboard} />
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="truncate text-sm font-medium">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<span
|
||||
className="truncate text-sm font-medium"
|
||||
title={dashboard.title || dashboard.name}
|
||||
>
|
||||
{dashboard.title || dashboard.name}
|
||||
</span>
|
||||
{dashboard.has_draft ? (
|
||||
|
||||
@@ -68,7 +68,7 @@ export function OverviewCard({
|
||||
}: {
|
||||
link: LinkProps
|
||||
icon: LucideIcon
|
||||
title: ReactNode
|
||||
title: string
|
||||
detail: ReactNode
|
||||
/** Has unpublished changes. */
|
||||
draft?: boolean
|
||||
@@ -132,9 +132,11 @@ export function OverviewCard({
|
||||
if (held.current) event.preventDefault()
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{title}</span>
|
||||
<span className="truncate" title={title}>
|
||||
{title}
|
||||
</span>
|
||||
{draft ? (
|
||||
<span className="size-1.5 shrink-0 rounded-full bg-primary">
|
||||
<span className="sr-only">Unpublished changes</span>
|
||||
|
||||
@@ -175,7 +175,7 @@ function Basics({
|
||||
}) {
|
||||
return (
|
||||
<PanelSection title="Device">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Input
|
||||
// Saved on blur or Enter, not per keystroke. Remounted with whatever
|
||||
// was saved, so a refused title snaps back.
|
||||
@@ -194,7 +194,10 @@ function Basics({
|
||||
if (title !== device.title) onChange({ title })
|
||||
}}
|
||||
/>
|
||||
<span className="shrink-0 font-mono text-sm text-muted-foreground">
|
||||
<span
|
||||
className="max-w-1/2 shrink-0 truncate font-mono text-sm text-muted-foreground"
|
||||
title={device.id}
|
||||
>
|
||||
{device.id}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { FluksioLoader } from "@/components/ui/fluksio-loader"
|
||||
import useAuth from "@/hooks/useAuth"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { useIsMobile } from "@/hooks/useMobile"
|
||||
@@ -110,10 +111,10 @@ const nodeTypes = { flow: FlowNode, [ENDPOINT_TYPE]: EndpointNode }
|
||||
* Where every node sits and how every edge runs, once the engine has said so.
|
||||
*
|
||||
* The layout is a chunk of its own and answers asynchronously, so a canvas has
|
||||
* a moment before it knows where anything goes. It draws nothing at all until
|
||||
* then — a route already shows a skeleton for that — and an *edit* keeps the
|
||||
* arrangement it had, so only the node that changed lands a frame late rather
|
||||
* than the whole graph flickering through the corner.
|
||||
* a moment before it knows where anything goes. It draws no node until then,
|
||||
* only a loader, and an *edit* keeps the arrangement it had, so only the node
|
||||
* that changed lands a frame late rather than the whole graph flickering
|
||||
* through the corner.
|
||||
*/
|
||||
function useLayout(
|
||||
ids: string[],
|
||||
@@ -1565,6 +1566,17 @@ function FlowEditorInner({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* On a slow link the layout engine arrives well after the document,
|
||||
and a bare canvas until then reads as a flow with no nodes. */}
|
||||
{definitions.length > 0 && !positions ? (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 flex items-center justify-center"
|
||||
data-testid="flow-loading"
|
||||
>
|
||||
<FluksioLoader className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{definitions.length === 0 ? (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 flex items-center justify-center"
|
||||
|
||||
@@ -2520,7 +2520,10 @@ export function NodePanel({
|
||||
<div className="flex min-w-0 flex-1 items-center gap-1.5">
|
||||
{/* Where the node lives: the flow is the namespace of its
|
||||
messages, so it reads like the folder holding them. */}
|
||||
<span className="min-w-0 truncate font-mono text-sm text-muted-foreground">
|
||||
<span
|
||||
className="min-w-0 max-w-1/2 truncate font-mono text-sm text-muted-foreground"
|
||||
title={`/${flow}/`}
|
||||
>
|
||||
/{flow}/
|
||||
</span>
|
||||
<PanelTitle
|
||||
|
||||
@@ -110,7 +110,7 @@ function DialogTitle({
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
className={cn("text-lg leading-none font-semibold wrap-anywhere", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -60,7 +60,7 @@ function SelectContent({
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-e2",
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) max-w-(--radix-select-content-available-width) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-e2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
@@ -107,7 +107,7 @@ function SelectItem({
|
||||
<SelectPrimitive.Item
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm wrap-anywhere outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user