~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
94 lines · 2.8 KB Raw History
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>
  );
}