import Link from "next/link";
import type { ComponentProps, ReactNode } from "react";
import {
Avatar as MuAvatar,
Button as MuButton,
EmptyState,
Kbd as MuKbd,
Label as MuLabel,
Led,
StatusBadge,
Surface,
type ButtonCap,
type LedKind,
} from "@unlocalhosted/metalui";
import { LockIcon } from "./icons";
/** Seeded 32-bit FNV-1a with a final avalanche, so similar names give unrelated patterns. */
function hash32(text: string, seed = 0) {
let h = (0x811c9dc5 ^ seed) >>> 0;
for (let i = 0; i < text.length; i++) {
h ^= text.charCodeAt(i);
h = Math.imul(h, 0x01000193);
}
h ^= h >>> 16;
h = Math.imul(h, 0x85ebca6b);
h ^= h >>> 13;
h = Math.imul(h, 0xc2b2ae35);
h ^= h >>> 16;
return h >>> 0;
}
/** A GitHub-style identicon: a horizontally mirrored 5×5 grid in one ink. */
function identicon(name: string) {
const key = name.trim().toLowerCase();
const bits = hash32(key);
const tone = hash32(key, 0x9e3779b9);
let path = "";
for (let row = 0; row < 5; row++) {
for (let col = 0; col < 3; col++) {
if (!(bits & (1 << (row * 3 + col)))) continue;
path += `M${col} ${row}h1v1h-1z`;
if (col < 2) path += `M${4 - col} ${row}h1v1h-1z`;
}
}
// MetalUI's feeling tints, so a repository's mark sits with the rest of the palette.
const tints = ["ember", "blush", "tide", "spark", "dusk", "iris", "graphite"];
return { path: path || "M2 0h1v5h-1z", color: `var(--mu-tint-${tints[tone % tints.length]})` };
}
function Identicon({ name, size }: { name: string; size: number }) {
const { path, color } = identicon(name);
return (
);
}
/**
* A person: MetalUI's raised disc with their initials. Its sizes are 24, 32 and 44; smaller avatars
* (inline beside a name) are the small disc scaled down.
*/
export function Avatar({ name, size = 20, className = "" }: { name: string; size?: number; className?: string }) {
const muSize = size >= 40 ? "large" : size >= 30 ? "regular" : "small";
const base = muSize === "large" ? 44 : muSize === "regular" ? 32 : 24;
return (
);
}
export type RepoImageRef = { owner: string; name: string; imageVersion: number | null };
export const repoImageUrl = (repo: RepoImageRef) => `/${repo.owner}/${repo.name}/image?v=${repo.imageVersion}`;
/**
* A repository's "logo" on a raised key: the image uploaded in the repository's settings, or else an
* identicon engraved from the owner's name.
*/
export function RepoAvatar({
repo,
size = 40,
className = "",
src,
}: {
repo: RepoImageRef;
size?: number;
className?: string;
/** Show this image instead, e.g. a local preview before uploading. */
src?: string;
}) {
const image = src ?? (repo.imageVersion ? repoImageUrl(repo) : null);
return (
{image ? (
// eslint-disable-next-line @next/next/no-img-element -- tiny, already-sized, auth-checked images
) : (
)}
);
}
/**
* The app's button variants on MetalUI's caps: `default` is the soft-touch standard cap, `primary`
* (and `success`, the old green "do it") the dark cap, `danger` the red cap, `invisible` a quiet
* ghost key that only fills on hover. One primary or danger cap per group.
*/
const caps = {
default: "standard",
primary: "primary",
success: "primary",
danger: "destructive",
} satisfies Record;
export type ButtonVariant = keyof typeof caps | "invisible";
// MetalUI's Button recipe (button.tsx), for elements that must look like a cap but can't be one:
// a