Files
Snip/src/components/UrlRow.tsx
T
sittichok OuamsiriandClaude Opus 5.5 2e1133e39c feat: redesign UI and UX around design-engineering principles
- Ink-first palette, drop gradients/glow; tabular numbers
- Home: form stays put, results stack below and auto-copy; paste shortens instantly
- Hold-to-delete replaces the delete confirm modal
- Copy buttons morph to a check instead of toasting
- Motion: CSS press feedback, custom ease-out, origin-aware menu, faster exits,
  no animation on frequent/keyboard actions, reduced-motion respected
- Fix: scheme-less URLs ("example.com") were blocked by native validation

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 00:11:22 +07:00

83 lines
2.8 KiB
TypeScript

import type { ShortLink } from "../lib/types";
import { compactNumber, fullShortUrl, prettyHost, shortUrl } from "../lib/format";
import { Sparkline } from "./Sparkline";
import { CopyButton } from "./ui/CopyButton";
import { HoldToDelete } from "./ui/HoldToDelete";
import { Qr, Lock, Edit } from "./icons";
export interface LinkItemProps {
link: ShortLink;
onEdit: (link: ShortLink) => void;
onDelete: (link: ShortLink) => void;
onDeleteTap: () => void;
onManagePin: (link: ShortLink) => void;
onShowQr: (link: ShortLink) => void;
}
export const iconBtn =
"press focusable grid h-8 w-8 place-items-center rounded-lg text-muted hover:bg-surface-2 hover:text-ink";
export function UrlRow({
link,
onEdit,
onDelete,
onDeleteTap,
onManagePin,
onShowQr,
}: LinkItemProps) {
const weekTotal = link.last7Days.reduce((s, d) => s + d.count, 0);
return (
<div className="card flex flex-col gap-2 px-4 py-3 sm:flex-row sm:items-center sm:gap-4">
{/* identity */}
<div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5">
<p className="truncate font-mono text-sm font-semibold text-ink">
{shortUrl(link.code)}
</p>
{link.hasPin && <Lock size={12} className="shrink-0 text-muted" />}
</div>
<p className="truncate text-[12px] text-muted" title={link.longUrl}>
→ {prettyHost(link.longUrl)}
</p>
</div>
<div className="flex items-center justify-between gap-4">
{/* stats */}
<div className="flex shrink-0 items-center gap-3 tabular-nums">
<div className="hidden sm:block">
<Sparkline data={link.last7Days} />
</div>
<p className="text-sm font-semibold text-ink sm:w-14 sm:text-right">
{compactNumber(link.totalClicks)}
{weekTotal > 0 && (
<span className="block text-[11px] font-medium text-accent">
+{compactNumber(weekTotal)}
</span>
)}
</p>
</div>
{/* actions */}
<div className="-mr-1.5 flex shrink-0 items-center">
<CopyButton text={fullShortUrl(link.code)} className={iconBtn} />
<button onClick={() => onShowQr(link)} aria-label="Show QR code" className={iconBtn}>
<Qr size={15} />
</button>
<button onClick={() => onManagePin(link)} aria-label="Manage PIN" className={iconBtn}>
<Lock size={15} />
</button>
<button onClick={() => onEdit(link)} aria-label="Edit destination" className={iconBtn}>
<Edit size={15} />
</button>
<HoldToDelete
onConfirm={() => onDelete(link)}
onTap={onDeleteTap}
className="h-8 w-8 rounded-lg hover:bg-surface-2"
/>
</div>
</div>
</div>
);
}