diff --git a/.lane/memory/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0F497YRARJ7QZ1R811-row-actions-must-be-hit-test.md b/.lane/attic/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0F497YRARJ7QZ1R811-row-actions-must-be-hit-test.md similarity index 100% rename from .lane/memory/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0F497YRARJ7QZ1R811-row-actions-must-be-hit-test.md rename to .lane/attic/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0F497YRARJ7QZ1R811-row-actions-must-be-hit-test.md diff --git a/.lane/memory/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0YMYWG1H4KR7RVJQ3N-the-enlarged-hit-area-only-t.md b/.lane/attic/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0YMYWG1H4KR7RVJQ3N-the-enlarged-hit-area-only-t.md similarity index 100% rename from .lane/memory/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0YMYWG1H4KR7RVJQ3N-the-enlarged-hit-area-only-t.md rename to .lane/attic/packages/ui/src/components/session-item/session-item-actions.tsx/01M4Z5DE0YMYWG1H4KR7RVJQ3N-the-enlarged-hit-area-only-t.md diff --git a/.lane/memory/packages/ui/src/components/session-item/session-item.tsx/01M4ZM8GB6RD86274YM563E355-the-gap-between-facepile-and.md b/.lane/attic/packages/ui/src/components/session-item/session-item.tsx/01M4ZM8GB6RD86274YM563E355-the-gap-between-facepile-and.md similarity index 100% rename from .lane/memory/packages/ui/src/components/session-item/session-item.tsx/01M4ZM8GB6RD86274YM563E355-the-gap-between-facepile-and.md rename to .lane/attic/packages/ui/src/components/session-item/session-item.tsx/01M4ZM8GB6RD86274YM563E355-the-gap-between-facepile-and.md diff --git a/.lane/memory/packages/ui/src/components/session-item/session-item.tsx/01M5AZ2FXC4FD834XGK86MA2NC-the-facepile-sits-inside-the.md b/.lane/memory/packages/ui/src/components/session-item/session-item.tsx/01M5AZ2FXC4FD834XGK86MA2NC-the-facepile-sits-inside-the.md new file mode 100644 index 00000000..cd86d5ca --- /dev/null +++ b/.lane/memory/packages/ui/src/components/session-item/session-item.tsx/01M5AZ2FXC4FD834XGK86MA2NC-the-facepile-sits-inside-the.md @@ -0,0 +1,12 @@ +--- +id: 01M5AZ2FXC4FD834XGK86MA2NC +anchor: function Presence +created: 2026-10-19T20:53:56Z +norm: '1' +sig: 5b691c6498a6be05 +body_hash: 6f560726b2f43907 +raw_hash: 533cb5f940611bea +lines: 58-90 +--- + +the facepile sits inside the row's button, so its trigger keeps an empty title or the row's summary tooltip covers the presence tooltip diff --git a/packages/ui/src/components/session-item/actions.ts b/packages/ui/src/components/session-item/actions.ts index 009fc01e..a49528b5 100644 --- a/packages/ui/src/components/session-item/actions.ts +++ b/packages/ui/src/components/session-item/actions.ts @@ -10,7 +10,3 @@ export type ActionProps = { export function canTogglePin({ pinned, onPin, onUnpin }: ActionProps) { return pinned ? Boolean(onUnpin) : Boolean(onPin); } - -export function hasVisibleActions(actions: ActionProps) { - return canTogglePin(actions) || Boolean(actions.onArchive); -} diff --git a/packages/ui/src/components/session-item/session-item-actions.tsx b/packages/ui/src/components/session-item/session-item-actions.tsx index df220b15..6e3002d6 100644 --- a/packages/ui/src/components/session-item/session-item-actions.tsx +++ b/packages/ui/src/components/session-item/session-item-actions.tsx @@ -1,12 +1,9 @@ -import { type ActionProps, canTogglePin, hasVisibleActions } from "./actions"; +import { type ActionProps, canTogglePin } from "./actions"; import type { MouseEvent, ReactNode } from "react"; import { useRef, useState } from "react"; import { ContextMenu } from "@base-ui/react/context-menu"; -import { hitArea } from "../../lib/hit-area"; -import { cn } from "../../lib/utils"; import { motion } from "../../ui/popup"; -import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip"; import type { MoveTarget, SidebarRow } from "./session-item.types"; import { @@ -35,20 +32,6 @@ const MENU_ITEM_DESTRUCTIVE = "text-destructive focus:bg-destructive/10 focus:text-destructive dark:focus:bg-destructive/20"; const MENU_ITEM_DESTRUCTIVE_FULL = `${MENU_ITEM} ${MENU_ITEM_DESTRUCTIVE}`; const MENU_SEPARATOR = "-mx-1 my-1 h-px bg-border/50"; -// The enlarged hit area spills into neighbouring rows, so only the hovered row's may catch presses. -const ACTION_BUTTON = cn( - hitArea, - "before:pointer-events-none group-hover/row:before:pointer-events-auto", - "grid size-6 place-items-center rounded-md squircle text-muted-foreground outline-2 outline-offset-[-1px] outline-transparent transition-[background-color,color,transform] duration-150 ease-out hover:bg-background/70 hover:text-foreground group-data-[primary=true]/menu:active:translate-y-px focus-visible:outline-ring/50", -); -// Hit-testing must not wait for :hover. A press that arrives before hover styles apply lands on the -// row, its release lands on the button, and the click then reaches neither. -const SESSION_ACTIONS = - "inline-flex items-center gap-0 -space-x-0.5 opacity-0 transition-opacity duration-150 ease-out motion-reduce:transition-none group-hover/row:opacity-100 group-focus-within/row:opacity-100 group-data-[menu-open=true]/menu:opacity-100"; - -type SessionActionsProps = ActionProps & { - data: SidebarRow; -}; type RowMenuProps = ActionProps & { data: SidebarRow; @@ -82,83 +65,6 @@ function stop(event: MouseEvent) { event.stopPropagation(); } -function ActionButton( - { - label, - children, - onClick, - }: { - label: string; - children: ReactNode; - onClick?: (event: MouseEvent) => void; - }, -) { - return ( - - - } - > - {children} - - - {label} - - - ); -} - -export function SessionActions( - { - data, - pinned, - onPin, - onUnpin, - onArchive, - }: SessionActionsProps, -) { - let canPin = canTogglePin({ pinned, onPin, onUnpin, onArchive }); - if (!hasVisibleActions({ pinned, onPin, onUnpin, onArchive })) return null; - - function pin(event: MouseEvent) { - stop(event); - if (pinned) onUnpin?.(data); - else onPin?.(data); - } - - function archive(event: MouseEvent) { - stop(event); - onArchive?.(data); - } - - return ( - - {canPin && ( - - {pinned ? : } - - )} - {onArchive && (data.lifecycle === "archived" - ? ( - - - - ) - : ( - - - - ))} - - ); -} - function MoveSection( { destinations, moving, onMove }: { destinations: Destinations; diff --git a/packages/ui/src/components/session-item/session-item.tsx b/packages/ui/src/components/session-item/session-item.tsx index f2012095..884654bb 100644 --- a/packages/ui/src/components/session-item/session-item.tsx +++ b/packages/ui/src/components/session-item/session-item.tsx @@ -5,8 +5,7 @@ import { Facepile } from "../facepile/facepile"; import { cn } from "../../lib/utils"; import { Tooltip, TooltipContent, TooltipTrigger } from "../../ui/tooltip"; -import { RowMenu, SessionActions } from "./session-item-actions"; -import { hasVisibleActions } from "./actions"; +import { RowMenu } from "./session-item-actions"; import { RowGlyph } from "./session-item-glyph"; import type { MoveTarget, SidebarRow } from "./session-item.types"; import { @@ -23,11 +22,6 @@ const EXPANDED_CONTENT_PADDING_BOTTOM = "data-[open=true]:pb-(--session-item-expanded-padding-bottom)"; const TITLE_TO_EXPANDED_CONTENT_GAP = "data-[open=true]:mt-(--session-item-title-to-content-gap)"; const EXPANDED_CONTENT_ROW_GAP = "gap-y-1"; -// The facepile and the actions sit side by side so pointing at a face never reaches pin, and -// neither moves on hover. The 8px gap equals the spill of the pin's 40px hit area around its 24px -// button, so that hit area ends at the facepile's edge; the facepile also stacks above it. -const TRAILING = - "pointer-events-none absolute top-1/2 right-1 z-20 inline-flex -translate-y-1/2 items-center gap-2 *:pointer-events-auto"; export type SessionItemProps = ComponentPropsWithRef<"button"> & { data: SidebarRow; @@ -72,6 +66,9 @@ export function Presence({ members }: { members: SidebarRow["online"] }) { render={ member.alt).join(", ")}`} + // The facepile sits inside the row's button; an empty title keeps the row's summary + // tooltip from covering this one. + title="" className="shrink-0 rounded-full" /> } @@ -232,12 +229,7 @@ function expandedBody(data: SidebarRow, content: SessionItemProps["expandedConte return typeof content === "function" ? content(data) : content ?? defaultExpandedContent(data); } -function buttonClass( - hoverExpand: boolean | undefined, - hasActions: boolean, - online: number, - className?: string, -) { +function buttonClass(hoverExpand: boolean | undefined, className?: string) { return cn( "relative flex h-[calc(var(--spacing)*8-2px)] w-full px-1 text-left", hoverExpand ? "flex-col items-stretch justify-center gap-0" : "items-center gap-1.5", @@ -247,14 +239,9 @@ function buttonClass( "data-[status=active]:selected-surface", "data-[status=active]:text-accent-text data-[status=active]:font-medium", hoverExpand - ? "transition-[background-color,filter,padding-right] duration-150 ease-out motion-reduce:transition-none data-[expanded=true]:rounded-b-none data-[expanded=true]:bg-popover data-[expanded=true]:hover:bg-popover" - : "transition-[padding-right] duration-150 ease-out motion-reduce:transition-none", + && "transition-[background-color,filter] duration-150 ease-out motion-reduce:transition-none data-[expanded=true]:rounded-b-none data-[expanded=true]:bg-popover data-[expanded=true]:hover:bg-popover", "group-data-[primary=true]/menu:active:translate-y-px group-data-[primary=true]/menu:active:brightness-95", "outline-2 outline-offset-0 outline-transparent focus-visible:outline-ring/50", - hasActions - && online === 0 - && "group-hover/row:pr-13 group-focus-within/row:pr-13 group-data-[menu-open=true]/menu:pr-13", - online > 0 && (hasActions ? "pr-24" : "pr-17"), className, ); } @@ -290,7 +277,7 @@ export function SessionItem( ...props }: SessionItemProps, ) { - let { onPin: actionPin, onUnpin: actionUnpin, ...menu } = actions(data, { + let menu = actions(data, { onPin, onUnpin, onFork, @@ -304,7 +291,6 @@ export function SessionItem( onMove, onCompute, }); - let hasActions = hasVisibleActions({ pinned, onPin: actionPin, onUnpin: actionUnpin }); let [hovered, setHovered] = useState(false); let timer = useRef | undefined>(undefined); let content = hoverExpand ? expandedBody(data, expandedContent) : null; @@ -320,7 +306,8 @@ export function SessionItem( - {data.name} + {data.name} + ); @@ -342,13 +329,7 @@ export function SessionItem( }, []); return ( - +
{hoverExpand @@ -411,19 +392,6 @@ export function SessionItem( ) : line} - - {data.online.length > 0 && ( - - - - )} - -
);