ph/components

Post card

Date block, title, authors and summary.

Code

Copy into src/components/ph/ .

dates.ts
const asDate = (d: Date | string) => (typeof d === "string" ? new Date(d) : d);

export function dateBlock(d: Date | string) {
  const date = asDate(d);
  return {
    month: date.toLocaleDateString("en-US", { month: "short", timeZone: "UTC" }).toUpperCase(),
    dayYear: `${date.getUTCDate()}, ${date.getUTCFullYear()}`,
    iso: date.toISOString(),
  };
}

export function longDate(d: Date | string) {
  const date = asDate(d);
  return {
    label: date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC" }),
    iso: date.toISOString(),
  };
}
PostCard.astro
---
import Author from "./Author.astro";
import Icon from "./Icon.astro";
import { dateBlock } from "./dates";

type Props = {
  title: string;
  href: string;
  date: Date | string;
  description?: string;
  authors?: { name: string; avatar?: string }[];
  class?: string;
};

const { title, href, date, description, authors = [], class: className } = Astro.props;
const { month, dayYear, iso } = dateBlock(date);
const external = /^https?:\/\//.test(href);
const host = external ? new URL(href).hostname : null;
---

<a
  href={href}
  target={external ? "_blank": undefined}
  rel={external ? "noopener noreferrer": undefined}
  class:list={[
    "flex gap-4 border border-foreground bg-card p-4 text-card-foreground no-underline transition-transform duration-150 ease-quint active:scale-[0.99] sm:gap-5 sm:p-5",
    className,
  ]}
>
  <div class="flex shrink-0 flex-col">
    <time
      datetime={iso}
      class="flex w-16 flex-col items-center justify-center overflow-hidden bg-foreground px-2 py-1.5 font-pixel leading-none text-background uppercase sm:w-20 dark:bg-ph-yellow dark:text-ph-black"
    >
      <span class="text-2xl tracking-tight sm:text-3xl">{month}</span>
      <span class="mt-1 text-[10px] tracking-wide whitespace-nowrap sm:text-[11px]">{dayYear}</span>
    </time>
    {host && <span class="mt-1 font-pixel text-[10px] tracking-wide break-all uppercase">{host}</span>}
  </div>
  <div class="flex min-w-0 flex-1 flex-col gap-2">
    <h3 class="flex items-start gap-2 font-pixel text-2xl leading-[1.05] uppercase sm:text-3xl">
      <span>{title}</span>
      {external && <Icon name="external-link" class="mt-1 size-4"/>}
    </h3>
    {
      authors.length > 0 && (
        <div class="flex flex-wrap gap-x-3 gap-y-1">
          {authors.map((a) => (
            <Author name={a.name} avatar={a.avatar} size="sm"/>
          ))}
        </div>
      )
    }
    {description && <p class="line-clamp-2 text-sm">{description}</p>}
  </div>
</a>

Also needs