- Run record
- loading
Loading this Run…
Loading the Revisions of this Run…
A published source file of Revision 0 of Boardly — Website Relaunch 1.0.0, shown as text in the browser.
import { memo } from "react";
import type { BoardMeta, Card } from "../types";
import { DONE_COLUMN_ID, isOverdue } from "../lib/board";
import { longDate, shortDate } from "../lib/dates";
import { Avatar, LabelChip } from "./Brand";
import { IconAlert, IconCalendar, IconCheckSquare, IconFlag, IconMove } from "./Icons";
export const PRIORITY_NAMES: Record<string, string> = {
low: "Low",
medium: "Medium",
high: "High",
urgent: "Urgent",
};
interface CardViewProps {
card: Card;
columnId: string;
meta: BoardMeta;
picked: boolean;
menuOpen: boolean;
onOpen: (cardId: string) => void;
onPointerDown: (e: React.PointerEvent<HTMLElement>, cardId: string) => void;
onKeyDown: (e: React.KeyboardEvent<HTMLElement>, cardId: string) => void;
onBlur: (e: React.FocusEvent<HTMLElement>, cardId: string) => void;
onMoveClick: (anchor: HTMLElement, cardId: string) => void;
}
export function DueBadge({ due, overdue, today, done }: { due: string; overdue: boolean; today: string; done: boolean }) {
const isToday = due === today && !done;
const tone = overdue ? "overdue" : isToday ? "today" : "normal";
return (
<span className="meta-chip due-chip" data-tone={tone}>
{overdue ? <IconAlert size={13} /> : <IconCalendar size={13} />}
{overdue ? (
<>
<span className="due-flag">Overdue</span>
<span aria-hidden="true">·</span>
<span>
<span className="sr-only">was due </span>
{shortDate(due)}
</span>
</>
) : isToday ? (
<span>Due today</span>
) : (
<span>
<span className="sr-only">Due {longDate(due)}</span>
<span aria-hidden="true">{shortDate(due)}</span>
</span>
)}
</span>
);
}
function CardViewImpl({ card, columnId, meta, picked, menuOpen, onOpen, onPointerDown, onKeyDown, onBlur, onMoveClick }: CardViewProps) {
const member = card.assignee ? meta.members.find((m) => m.id === card.assignee) ?? null : null;
const overdue = isOverdue(card, columnId, meta.today);
const done = columnId === DONE_COLUMN_ID;
const total = card.checklist.length;
const completed = card.checklist.filter((i) => i.done).length;
const titleId = `card-title-${card.id}`;
return (
<li className="card-slot">
<article
className="card"
data-card-id={card.id}
data-picked={picked || undefined}
data-overdue={overdue || undefined}
data-done={done || undefined}
tabIndex={0}
aria-labelledby={titleId}
aria-describedby="card-keyboard-help"
onClick={() => onOpen(card.id)}
onKeyDown={(e) => onKeyDown(e, card.id)}
onPointerDown={(e) => onPointerDown(e, card.id)}
onBlur={(e) => onBlur(e, card.id)}
>
{card.labels.length > 0 && (
<ul className="card-labels" aria-label="Labels">
{card.labels.map((id) => {
const label = meta.labels.find((l) => l.id === id);
return (
<li key={id}>
<LabelChip id={id} name={label?.name ?? id} />
</li>
);
})}
</ul>
)}
<h3 className="card-title" id={titleId}>
{card.title}
</h3>
{(card.due || total > 0 || card.priority === "high" || card.priority === "urgent") && (
<div className="card-meta">
{card.due && <DueBadge due={card.due} overdue={overdue} today={meta.today} done={done} />}
{total > 0 && (
<span className="meta-chip checklist-chip" data-complete={completed === total || undefined}>
<IconCheckSquare size={13} />
<span className="sr-only">Checklist: </span>
{completed}/{total}
<span className="sr-only"> done</span>
</span>
)}
{(card.priority === "high" || card.priority === "urgent") && (
<span className="meta-chip priority-chip" data-priority={card.priority}>
<IconFlag size={13} />
<span className="sr-only">Priority: </span>
{PRIORITY_NAMES[card.priority]}
</span>
)}
</div>
)}
<div className="card-foot">
<span className="card-assignee" data-unassigned={!member || undefined}>
<Avatar member={member} />
<span className="sr-only">Assignee: </span>
<span className="card-assignee-name">{member ? member.name.split(" ")[0] : "Unassigned"}</span>
</span>
<button
type="button"
className="card-move-btn"
aria-label={`Move “${card.title}” to another column`}
aria-haspopup="menu"
aria-expanded={menuOpen}
title="Move to another column"
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation();
onMoveClick(e.currentTarget, card.id);
}}
>
<IconMove size={14} />
<span className="card-move-text">Move</span>
</button>
</div>
</article>
</li>
);
}
export const CardView = memo(CardViewImpl);Source is browser-viewable only. There is no download or archive endpoint, and no additional license is granted beyond inspection and research.