utils.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. import { clsx, type ClassValue } from "clsx";
  2. import { twMerge } from "tailwind-merge";
  3. export function cn(...inputs: ClassValue[]) {
  4. return twMerge(clsx(inputs));
  5. }
  6. export function formatDateTime(value?: string | null) {
  7. if (!value) return "Never";
  8. return new Intl.DateTimeFormat(undefined, {
  9. month: "short",
  10. day: "numeric",
  11. hour: "2-digit",
  12. minute: "2-digit",
  13. }).format(new Date(value));
  14. }
  15. export function relativeTime(value?: string | null) {
  16. if (!value) return "Never";
  17. const deltaSeconds = Math.round((new Date(value).getTime() - Date.now()) / 1000);
  18. const units: Array<[Intl.RelativeTimeFormatUnit, number]> = [
  19. ["year", 31536000],
  20. ["month", 2592000],
  21. ["day", 86400],
  22. ["hour", 3600],
  23. ["minute", 60],
  24. ["second", 1],
  25. ];
  26. const formatter = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" });
  27. const [unit, seconds] = units.find(([, amount]) => Math.abs(deltaSeconds) >= amount) ?? ["second", 1];
  28. return formatter.format(Math.round(deltaSeconds / seconds), unit);
  29. }
  30. export function truncateMiddle(value: string, max = 18) {
  31. if (value.length <= max) return value;
  32. const keep = Math.max(4, Math.floor((max - 3) / 2));
  33. return `${value.slice(0, keep)}...${value.slice(-keep)}`;
  34. }
  35. export async function copyToClipboard(value: string) {
  36. await navigator.clipboard.writeText(value);
  37. }
  38. export function slugifyName(value: string, fallback = "item") {
  39. const slug = value
  40. .trim()
  41. .toLowerCase()
  42. .replace(/[^a-z0-9]+/g, "_")
  43. .replace(/^_+|_+$/g, "");
  44. return slug || `${fallback}_${Date.now().toString(36)}`;
  45. }