feat(accounts): leading drag handle that slides in on hover

Replace the corner/icon-swap handle with a leading handle placed before
the bank logo and name in both cards. It is collapsed to zero width by
default and expands on hover, gently pushing the logo and name to the
right. Same consistent behaviour on the dashboard and accounts cards,
and it works for accounts with no bank logo.
This commit is contained in:
Víctor Falcón 2026-06-20 19:24:19 +02:00
parent 0bac60774f
commit 201974a724
3 changed files with 48 additions and 54 deletions

View File

@ -136,19 +136,15 @@ export function AccountListCard({
return (
<Card className="w-full py-0">
{dragHandle && (
<span className="absolute top-2 right-2 z-10 opacity-0 transition-opacity group-hover:opacity-100">
{dragHandle}
</span>
)}
<CardContent className="p-4">
<div className="flex flex-col gap-4">
<div className="flex max-w-full flex-col sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 items-start gap-3">
<div className="flex min-w-0 items-start">
{dragHandle}
<div className="flex min-w-0 flex-col gap-1">
<Link
href={show.url(account.id)}
className="-my-1 -ml-1.5 flex min-w-0 items-center rounded-md px-1.5 py-1 transition-colors hover:bg-muted"
className="-my-1 flex min-w-0 items-center rounded-md px-1.5 py-1 transition-colors hover:bg-muted"
>
<h3 className="flex min-w-0 items-center gap-2 font-semibold">
{account.bank && (

View File

@ -6,7 +6,6 @@ import { AmountDisplay } from '@/components/ui/amount-display';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useChartColors } from '@/hooks/use-chart-color-scheme';
import { AccountWithMetrics } from '@/hooks/use-dashboard-data';
import { cn } from '@/lib/utils';
import { supportsInvestedAmount } from '@/types/account';
import { __ } from '@/utils/i18n';
import { Link } from '@inertiajs/react';
@ -158,57 +157,54 @@ export function AccountBalanceCard({
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<div className="flex flex-col gap-0.5">
<CardTitle className="text-sm font-medium">
<Link
href={show.url(account.id)}
className="-my-1 -ml-1.5 flex items-center rounded-md px-1.5 py-1 transition-colors hover:bg-muted"
>
<BankLogo
src={account.bank?.logo ?? null}
name={account.bank?.name}
className="mr-2 inline-block size-5"
/>
<AccountName
account={account}
length={{ min: 5, max: 15 }}
/>
</Link>
</CardTitle>
{hasMortgage && linkedLoanMetrics.loanAccount && (
<span className="flex items-center gap-1 pt-0.5 text-xs text-muted-foreground">
{__('Mortgage at')}{' '}
{linkedLoanMetrics.loanAccount.bank && (
<div className="flex min-w-0 items-start">
{dragHandle}
<div className="flex min-w-0 flex-col gap-0.5">
<CardTitle className="text-sm font-medium">
<Link
href={show.url(account.id)}
className="-my-1 flex items-center rounded-md px-1.5 py-1 transition-colors hover:bg-muted"
>
<BankLogo
src={
linkedLoanMetrics.loanAccount.bank.logo
}
name={
linkedLoanMetrics.loanAccount.bank.name
}
className="size-3.5 shrink-0"
fallback="letter"
src={account.bank?.logo ?? null}
name={account.bank?.name}
className="mr-2 inline-block size-5"
/>
)}
{linkedLoanMetrics.loanAccount.bank?.name ??
linkedLoanMetrics.loanAccount.name}
</span>
)}
<AccountName
account={account}
length={{ min: 5, max: 15 }}
/>
</Link>
</CardTitle>
{hasMortgage && linkedLoanMetrics.loanAccount && (
<span className="flex items-center gap-1 pt-0.5 text-xs text-muted-foreground">
{__('Mortgage at')}{' '}
{linkedLoanMetrics.loanAccount.bank && (
<BankLogo
src={
linkedLoanMetrics.loanAccount.bank
.logo
}
name={
linkedLoanMetrics.loanAccount.bank
.name
}
className="size-3.5 shrink-0"
fallback="letter"
/>
)}
{linkedLoanMetrics.loanAccount.bank?.name ??
linkedLoanMetrics.loanAccount.name}
</span>
)}
</div>
</div>
<div className="relative mr-1 size-5 shrink-0">
<div className="text-xs font-medium text-muted-foreground">
<AccountTypeIcon
type={account.type}
className={cn(
'transition-opacity',
dragHandle && 'group-hover:opacity-0',
)}
className="mr-1 inline-block"
/>
{dragHandle && (
<span className="absolute inset-0 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100">
{dragHandle}
</span>
)}
</div>
</CardHeader>
<CardContent>

View File

@ -110,12 +110,14 @@ function SortableItem({
isDragging,
} = useSortable({ id });
// Collapsed to zero width by default; on card hover it expands and slides
// in, pushing the card's leading content (logo + name) to the right.
const dragHandle = (
<button
ref={setActivatorNodeRef}
type="button"
aria-label={__('Drag to reorder')}
className="cursor-grab touch-none text-muted-foreground transition-colors hover:text-foreground active:cursor-grabbing"
className="flex w-0 shrink-0 cursor-grab touch-none items-center overflow-hidden text-muted-foreground/70 opacity-0 transition-all duration-200 ease-out group-hover:w-6 group-hover:opacity-100 hover:text-foreground active:cursor-grabbing"
{...attributes}
{...listeners}
>