chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
import { Link } from '#/components/atoms/link.jsx';
import { cn } from '#/lib/utils.js';
/**
* One row in a list: an optional leading `media` slot, a content column of
* `title` and `description`, and a trailing `actions` slot. A navigational row
* takes an `href` and renders as a real anchor; an action row takes `onClick`
* and behaves as a button; without either it is static. `plain` drops the card
* border for a flush list.
*/
export function ListRow({
media,
title,
description,
actions,
background,
href,
onNavigate,
onClick,
variant = 'card',
className,
}) {
const interactive = href || typeof onClick === 'function';
const classes = cn(
'flex w-full items-center gap-4 text-left outline-none transition-colors',
variant === 'card'
? 'rounded-2xl border border-border bg-card px-4 py-3'
: 'px-1 py-3',
// A background slot paints behind the content, clipped to the row and kept
// below it: `isolate` scopes the stacking context so `-z-10` sits under the
// row's own card background rather than the whole page.
background && 'relative isolate overflow-hidden',
interactive &&
'cursor-pointer focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
interactive && variant === 'card' && 'hover:border-input hover:bg-accent',
interactive && variant === 'plain' && 'rounded-xl hover:bg-muted',
className,
);
const content = (
<>
{background && (
<div className="pointer-events-none absolute inset-0 -z-10">
{background}
</div>
)}
{media}
<div className="flex min-w-0 flex-1 flex-col gap-1">
{title && (
<div className="flex min-w-0 flex-wrap items-center gap-2 font-extrabold text-[15px] text-foreground leading-snug">
{title}
</div>
)}
{description && (
<div className="min-w-0 text-[12px] text-muted-foreground leading-normal">
{description}
</div>
)}
</div>
{actions}
</>
);
if (href) {
return (
<Link href={href} onNavigate={onNavigate} className={classes}>
{content}
</Link>
);
}
return (
// biome-ignore lint/a11y/noStaticElementInteractions: a row can hold action buttons, so it carries role="button" and a key handler rather than being one
<div
data-slot="list-row"
onClick={onClick}
onKeyDown={
typeof onClick === 'function'
? (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
onClick(event);
}
}
: undefined
}
role={typeof onClick === 'function' ? 'button' : undefined}
tabIndex={typeof onClick === 'function' ? 0 : undefined}
className={classes}
>
{content}
</div>
);
}