The Kit/Utils

Display Formatters

Pure presentation helpers: clockMMSS, pct, surname, ordinal, luminance/inkOn (readable ink over a colour), rgba/hex. No domain logic.

loading…
format.ts
// Generic display formatters (isomorphic). No domain logic — pure presentation.

/** Seconds → "M:SS" (clock). */
export const clockMMSS = (secs: number): string => {
  const s = Math.max(0, Math.floor(secs));
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
};

/** 0..1 (or 0..100) → "NN%". Pass `of100` when the input is already a percentage. */
export const pct = (v: number, digits = 0, of100 = false): string =>
  `${(of100 ? v : v * 100).toFixed(digits)}%`;

/** Last token of a full name ("Vinícius Júnior" → "Júnior"). */
export const surname = (name?: string | null): string => {
  const p = (name || "").trim().split(/\s+/);
  return p.length > 1 ? p[p.length - 1] : name || "";
};

/** Ordinal suffix ("1st", "2nd", "3rd", "11th"). */
export const ordinal = (n: number): string => {
  const s = ["th", "st", "nd", "rd"];
  const v = n % 100;
  return n + (s[(v - 20) % 10] || s[v] || s[0]);
};

/** HTML-escape for values interpolated into raw SVG/markup strings. */
export const esc = (s?: string | null): string =>
  (s || "").replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

/** Relative luminance of a hex colour (0..1). */
export const luminance = (hex?: string | null): number => {
  if (!hex) return 0;
  const h = hex.replace("#", "");
  if (h.length < 6) return 0;
  return (
    (0.299 * parseInt(h.slice(0, 2), 16) +
      0.587 * parseInt(h.slice(2, 4), 16) +
      0.114 * parseInt(h.slice(4, 6), 16)) /
    255
  );
};

/** Readable ink (#08080A / #fff) over a given background hex. */
export const inkOn = (hex?: string | null): string => (luminance(hex) > 0.6 ? "#08080A" : "#FFFFFF");

/** hex → "rgba(r,g,b,a)". */
export const rgba = (hex: string, a: number): string => {
  const h = (hex || "").replace("#", "");
  return `rgba(${parseInt(h.slice(0, 2), 16)},${parseInt(h.slice(2, 4), 16)},${parseInt(h.slice(4, 6), 16)},${a})`;
};

/** Normalize a colour to a leading-# hex, or null. */
export const hex = (c?: string | null): string | null =>
  c ? (c.startsWith("#") ? c : "#" + c) : null;
← The whole kit