feat: tune session sidebar tooltip timing

Adds a shared sidebar tooltip provider with delayed open and instant close behavior
Removes per-button tooltip delays so sidebar actions feel more consistent
Aligns session sidebar hover interactions with the opencode-style tooltip experience
This commit is contained in:
Bohdan Triapitsyn
2026-08-30 18:03:29 +03:00
parent 654b3d2441
commit d0a5538bff
5 changed files with 19 additions and 20 deletions
@@ -592,13 +592,12 @@ const SessionSidebarComponent: React.FC<SessionSidebarProps> = ({
}, [mobileVariant, openNewSessionDraft, setSessionSwitcherOpen]); }, [mobileVariant, openNewSessionDraft, setSessionSwitcherOpen]);
return ( return (
// One shared tooltip provider for the whole sidebar: session tooltips open // One shared tooltip provider for the whole sidebar, matching the opencode
// instantly, and moving between rows hands the tooltip over (grouping) // sidebar feel: 400ms before the first tooltip opens, instant close on
// instead of replaying the exit/enter animation for each row. // leave, and grouping — moving between rows within 600ms hands the tooltip
// closeDelay bridges the small gap between rows: the tooltip survives the // over to the next row without replaying the open delay or exit/enter
// pointer crossing row margins, and the grouping timeout hands it over to // animation.
// the next row without an exit/enter cycle. <TooltipProvider delay={400} closeDelay={0} timeout={300}>
<TooltipProvider delay={0} closeDelay={150} timeout={600}>
<div <div
ref={sessionSearchContainerRef} ref={sessionSearchContainerRef}
className={cn( className={cn(
@@ -1200,7 +1200,7 @@ function SessionGroupSectionBase(props: SessionGroupSectionProps): React.ReactNo
</div> </div>
{group.isArchivedBucket && allGroupSessions.length > 0 ? ( {group.isArchivedBucket && allGroupSessions.length > 0 ? (
<div className={cn('absolute right-0.5 top-1/2 -translate-y-1/2 z-10 transition-opacity', alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/gh:opacity-100 group-focus-within/gh:opacity-100')}> <div className={cn('absolute right-0.5 top-1/2 -translate-y-1/2 z-10 transition-opacity', alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/gh:opacity-100 group-focus-within/gh:opacity-100')}>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -1223,7 +1223,7 @@ function SessionGroupSectionBase(props: SessionGroupSectionProps): React.ReactNo
) : null} ) : null}
{group.directory && !group.isMain && group.worktree ? ( {group.directory && !group.isMain && group.worktree ? (
<div className={cn('absolute right-7 top-1/2 -translate-y-1/2 z-10 transition-opacity', alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/gh:opacity-100 group-focus-within/gh:opacity-100')}> <div className={cn('absolute right-7 top-1/2 -translate-y-1/2 z-10 transition-opacity', alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/gh:opacity-100 group-focus-within/gh:opacity-100')}>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -1247,7 +1247,7 @@ function SessionGroupSectionBase(props: SessionGroupSectionProps): React.ReactNo
) : null} ) : null}
{group.directory ? ( {group.directory ? (
<div className={cn('absolute right-0.5 top-1/2 -translate-y-1/2 z-10 transition-opacity', alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/gh:opacity-100 group-focus-within/gh:opacity-100')}> <div className={cn('absolute right-0.5 top-1/2 -translate-y-1/2 z-10 transition-opacity', alwaysShowActions ? 'opacity-100' : 'opacity-0 group-hover/gh:opacity-100 group-focus-within/gh:opacity-100')}>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -342,7 +342,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
showCreateButtons ? 'right-7' : 'right-0.5', showCreateButtons ? 'right-7' : 'right-0.5',
)}> )}>
{showCreateButtons && isRepo && !hideDirectoryControls && onNewWorktreeSession ? ( {showCreateButtons && isRepo && !hideDirectoryControls && onNewWorktreeSession ? (
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -398,7 +398,7 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
{showCreateButtons && onNewSession ? ( {showCreateButtons && onNewSession ? (
<div className="absolute right-0.5 top-1/2 z-10 -translate-y-1/2"> <div className="absolute right-0.5 top-1/2 z-10 -translate-y-1/2">
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -233,7 +233,7 @@ const QuickSessionAction = React.memo(function QuickSessionAction({
}; };
return ( return (
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -90,7 +90,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
icon inset inside the 24px buttons so the first glyph lines up icon inset inside the 24px buttons so the first glyph lines up
with the New-session icon above (16px from the sidebar edge). */} with the New-session icon above (16px from the sidebar edge). */}
<div className="ml-[3px] flex items-center gap-1.5"> <div className="ml-[3px] flex items-center gap-1.5">
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -104,7 +104,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.addProject')}</p></TooltipContent> <TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.addProject')}</p></TooltipContent>
</Tooltip> </Tooltip>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -118,7 +118,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.scheduledTasks')}</p></TooltipContent> <TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.scheduledTasks')}</p></TooltipContent>
</Tooltip> </Tooltip>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -133,7 +133,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.newMultiRun')}</p></TooltipContent> <TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.newMultiRun')}</p></TooltipContent>
</Tooltip> </Tooltip>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -149,7 +149,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -164,7 +164,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
<TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.searchSessions')}</p></TooltipContent> <TooltipContent side="bottom" sideOffset={4}><p>{t('sessions.sidebar.header.actions.searchSessions')}</p></TooltipContent>
</Tooltip> </Tooltip>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<button <button
type="button" type="button"
@@ -186,7 +186,7 @@ export function SidebarHeader(props: Props): React.ReactNode {
</Tooltip> </Tooltip>
<DropdownMenu> <DropdownMenu>
<Tooltip delayDuration={500}> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button <button