mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 14:18:02 +00:00
fix: replace cursor-follow tooltip with standard Radix tooltip in sidebar rows
Replaced the Cursor/CursorFollow animate-ui primitives in StackRow with the standard Radix Tooltip/TooltipTrigger/TooltipContent already used throughout the app. The custom cursor dot that followed the mouse is gone; tooltips now appear as static popovers on hover/touch.
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { GitBranch, Loader2, AlertCircle } from 'lucide-react';
|
import { GitBranch, Loader2, AlertCircle } from 'lucide-react';
|
||||||
import type { CheckStatus } from '@/types/imageUpdates';
|
import type { CheckStatus } from '@/types/imageUpdates';
|
||||||
import { Cursor, CursorContainer, CursorFollow, CursorProvider } from '@/components/animate-ui/primitives/animate/cursor';
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import type { Label } from '@/components/label-types';
|
import type { Label } from '@/components/label-types';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
import { sidebarRowActive, sidebarRowBase, sidebarRowCheckboxSlot } from './sidebar-styles';
|
import { sidebarRowActive, sidebarRowBase, sidebarRowCheckboxSlot } from './sidebar-styles';
|
||||||
import { statusText, statusColor } from './stack-status-utils';
|
import { statusText, statusColor } from './stack-status-utils';
|
||||||
import type { StackRowStatus } from './stack-status-utils';
|
import type { StackRowStatus } from './stack-status-utils';
|
||||||
@@ -34,15 +34,14 @@ interface StackRowProps {
|
|||||||
|
|
||||||
function RowTooltip({ trigger, label }: { trigger: ReactNode; label: string }) {
|
function RowTooltip({ trigger, label }: { trigger: ReactNode; label: string }) {
|
||||||
return (
|
return (
|
||||||
<CursorProvider>
|
<TooltipProvider delayDuration={300}>
|
||||||
<CursorContainer className="inline-flex items-center shrink-0">{trigger}</CursorContainer>
|
<Tooltip>
|
||||||
<Cursor><div className="h-2 w-2 rounded-full bg-brand" /></Cursor>
|
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
||||||
<CursorFollow side="bottom" sideOffset={4} align="center" transition={{ stiffness: 400, damping: 40, bounce: 0 }}>
|
<TooltipContent side="bottom" sideOffset={4} align="center">
|
||||||
<div className="rounded-md border border-card-border bg-popover/95 backdrop-blur-[10px] backdrop-saturate-[1.15] px-2.5 py-1.5 shadow-md">
|
|
||||||
<span className="font-mono text-xs tabular-nums text-stat-value">{label}</span>
|
<span className="font-mono text-xs tabular-nums text-stat-value">{label}</span>
|
||||||
</div>
|
</TooltipContent>
|
||||||
</CursorFollow>
|
</Tooltip>
|
||||||
</CursorProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,17 +43,15 @@ describe('StackRow', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('wraps the partial pill in a hover tooltip', () => {
|
it('wraps the partial pill in a hover tooltip', () => {
|
||||||
// jsdom does not mount the cursor-follow label, so assert the PT trigger is
|
|
||||||
// wrapped in the RowTooltip cursor-container; the visible "3/5 running"
|
|
||||||
// tooltip text is verified in the Playwright drive.
|
|
||||||
const { container } = render(<StackRow {...base({ status: 'partial', running: 3, total: 5 })} />);
|
const { container } = render(<StackRow {...base({ status: 'partial', running: 3, total: 5 })} />);
|
||||||
expect(screen.getByText('PT').closest('[data-slot="cursor-container"]')).not.toBeNull();
|
const trigger = screen.getByText('PT');
|
||||||
expect(container.querySelector('[data-slot="cursor-container"]')).not.toBeNull();
|
// Radix TooltipTrigger adds data-state to the wrapped element.
|
||||||
|
expect(trigger.getAttribute('data-state')).toBe('closed');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not wrap a non-partial pill in a tooltip', () => {
|
it('does not wrap a non-partial pill in a tooltip', () => {
|
||||||
render(<StackRow {...base({ status: 'running' })} />);
|
render(<StackRow {...base({ status: 'running' })} />);
|
||||||
expect(screen.getByText('UP').closest('[data-slot="cursor-container"]')).toBeNull();
|
expect(screen.getByText('UP').getAttribute('data-state')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders cyan rail only when active', () => {
|
it('renders cyan rail only when active', () => {
|
||||||
@@ -103,12 +101,12 @@ describe('StackRow', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Image-update check status indicator ────────────────────────────────
|
// ── Image-update check status indicator ────────────────────────────────
|
||||||
// status='running' renders the pill as plain text (no tooltip), so the only
|
|
||||||
// cursor-container in these rows is the trailing update/check indicator.
|
|
||||||
|
|
||||||
it('shows a muted check-failed indicator when the last check failed and there is no update', () => {
|
it('shows a muted check-failed indicator when the last check failed and there is no update', () => {
|
||||||
const { container } = render(<StackRow {...base({ status: 'running', hasUpdate: false, checkStatus: 'failed', lastError: 'Registry unreachable' })} />);
|
const { container } = render(<StackRow {...base({ status: 'running', hasUpdate: false, checkStatus: 'failed', lastError: 'Registry unreachable' })} />);
|
||||||
expect(container.querySelector('[data-slot="cursor-container"]')).not.toBeNull();
|
// The trailing slot renders a tooltip-wrapped AlertCircle icon (an SVG).
|
||||||
|
const trailingSlot = container.querySelector('[data-state="closed"]');
|
||||||
|
expect(trailingSlot).not.toBeNull();
|
||||||
// It is not the update dot.
|
// It is not the update dot.
|
||||||
expect(container.querySelector('.bg-update')).toBeNull();
|
expect(container.querySelector('.bg-update')).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -120,7 +118,7 @@ describe('StackRow', () => {
|
|||||||
|
|
||||||
it('shows no trailing indicator for a clean ok check with no update', () => {
|
it('shows no trailing indicator for a clean ok check with no update', () => {
|
||||||
const { container } = render(<StackRow {...base({ status: 'running', hasUpdate: false, checkStatus: 'ok' })} />);
|
const { container } = render(<StackRow {...base({ status: 'running', hasUpdate: false, checkStatus: 'ok' })} />);
|
||||||
expect(container.querySelector('[data-slot="cursor-container"]')).toBeNull();
|
expect(container.querySelector('.lucide-alert-circle')).toBeNull();
|
||||||
expect(container.querySelector('.bg-update')).toBeNull();
|
expect(container.querySelector('.bg-update')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user