Ctrl K
ComunidadGuías…

shared/reportAppearance.mjs

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
export const reportColorLabels = {
  page: "Page",
  header: "Header",
  headerText: "Header text",
  surface: "Chart cards",
  text: "Primary text",
  mutedText: "Secondary text",
  border: "Borders",
  accent: "Accent",
  accentText: "Accent text",
};

const light = {
  page: "#F6F5F4", header: "#FFFFFF", headerText: "#242322", surface: "#FFFFFF",
  text: "#242322", mutedText: "#666360", border: "#DFDEDD", accent: "#0276BE", accentText: "#FFFFFF",
};
const dark = {
  page: "#111110", header: "#1C1B1A", headerText: "#FAFAF9", surface: "#1C1B1A",
  text: "#FAFAF9", mutedText: "#AAA6A2", border: "#2A2929", accent: "#98D2F5", accentText: "#102B3D",
};

export const reportThemes = [
  { id: "chartbrew", name: "Chartbrew", appearance: { mode: "system", light, dark } },
  {
    id: "neutral", name: "Neutral",
    appearance: {
      mode: "system",
      light: { ...light, page: "#F4F4F5", header: "#FAFAFA", accent: "#3F3F46", accentText: "#FFFFFF" },
      dark: { ...dark, page: "#141416", header: "#202023", surface: "#202023", accent: "#E4E4E7", accentText: "#27272A" },
    },
  },
  {
    id: "forest", name: "Forest",
    appearance: {
      mode: "system",
      light: { ...light, page: "#F3F6F4", header: "#173F35", headerText: "#F4FBF7", accent: "#236B50" },
      dark: { ...dark, page: "#101915", header: "#19382C", surface: "#1B2822", accent: "#9AD5B4", accentText: "#143224", border: "#7B9384" },
    },
  },
  {
    id: "ocean", name: "Ocean",
    appearance: {
      mode: "system",
      light: { ...light, page: "#F1F7F8", header: "#164653", headerText: "#F0FAFC", accent: "#096778" },
      dark: { ...dark, page: "#10191C", header: "#18343D", surface: "#1B282D", accent: "#8AD5E3", accentText: "#12333B", border: "#82979D" },
    },
  },
  {
    id: "rose", name: "Rose",
    appearance: {
      mode: "system",
      light: { ...light, page: "#FAF4F6", header: "#F5E5EB", headerText: "#54283B", accent: "#9B365D" },
      dark: { ...dark, page: "#1C1418", header: "#3B2330", surface: "#2B2026", accent: "#F0A8C1", accentText: "#492035", border: "#A08491" },
    },
  },
  {
    id: "citrus", name: "Citrus",
    appearance: {
      mode: "system",
      light: { ...light, page: "#F7F8F2", header: "#ECF0C9", headerText: "#363F1B", accent: "#596B1E" },
      dark: { ...dark, page: "#171A12", header: "#30391E", surface: "#252A1D", accent: "#CEDD88", accentText: "#2E3914", border: "#919A7D" },
    },
  },
];

export function normalizeReportColor(value) {
  if (typeof value !== "string" || !/^#(?:[\da-f]{3}|[\da-f]{6})$/i.test(value)) {
    throw new Error("Enter a color such as #0276BE.");
  }
  const hex = value.slice(1);
  return `#${hex.length === 3 ? [...hex].map((digit) => digit + digit).join("") : hex}`.toUpperCase();
}

export function validateReportAppearance(value) {
  const object = (item) => item && typeof item === "object" && !Array.isArray(item);
  if (!object(value) || Object.keys(value).length !== 3
    || !["system", "light", "dark"].includes(value.mode)) {
    throw new Error("Choose an appearance and supply both light and dark colors.");
  }
  const result = { mode: value.mode };
  for (const mode of ["light", "dark"]) {
    if (!object(value[mode]) || Object.keys(value[mode]).length !== Object.keys(reportColorLabels).length) {
      throw new Error(`Supply all ${mode} colors.`);
    }
    result[mode] = Object.fromEntries(Object.keys(reportColorLabels).map((key) => [key, normalizeReportColor(value[mode][key])]));
  }
  return result;
}

export function initialReportAppearance(project) {
  if (project.reportAppearance) return project.reportAppearance;
  const appearance = structuredClone(reportThemes[0].appearance);
  for (const mode of ["light", "dark"]) {
    for (const [field, key] of [["backgroundColor", "header"], ["titleColor", "headerText"]]) {
      try {
        if (project[field]) appearance[mode][key] = normalizeReportColor(project[field]);
      } catch { /* Legacy CSS colors remain unchanged until a theme is saved. */ }
    }
  }
  return appearance;
}

export function resolveReportMode({ preview, query, mode, prefersDark }) {
  for (const value of [preview, query, mode]) {
    if (value === "light" || value === "dark") return value;
  }
  return prefersDark ? "dark" : "light";
}

export function reportColorVariables(colors) {
  if (!colors) return {};
  return {
    "--background": colors.page,
    "--foreground": colors.text,
    "--surface": colors.surface,
    "--surface-foreground": colors.text,
    "--surface-secondary": `color-mix(in srgb, ${colors.surface} 92%, ${colors.text})`,
    "--surface-secondary-foreground": colors.text,
    "--surface-tertiary": `color-mix(in srgb, ${colors.surface} 85%, ${colors.text})`,
    "--surface-tertiary-foreground": colors.text,
    "--overlay": colors.surface,
    "--overlay-foreground": colors.text,
    "--field-background": colors.surface,
    "--field-foreground": colors.text,
    "--field-placeholder": colors.mutedText,
    "--default": `color-mix(in srgb, ${colors.surface} 92%, ${colors.text})`,
    "--default-foreground": colors.text,
    "--muted": colors.mutedText,
    "--border": colors.border,
    "--separator": colors.border,
    "--accent": colors.accent,
    "--accent-foreground": colors.accentText,
    "--focus": colors.accent,
    "--link": colors.accent,
    "--cb-content1": colors.surface,
    "--cb-content2": colors.page,
    "--cb-content3": colors.border,
    "--cb-content4": colors.border,
    "--cb-default": `color-mix(in srgb, ${colors.surface} 92%, ${colors.text})`,
    "--cb-default-foreground": colors.text,
    "--cb-default-50": colors.surface,
    "--cb-default-100": colors.page,
    "--cb-default-200": colors.border,
    "--cb-default-300": colors.border,
    "--cb-default-400": colors.mutedText,
    "--cb-default-500": colors.mutedText,
    "--cb-default-600": colors.mutedText,
    "--cb-default-700": colors.text,
    "--cb-default-800": colors.text,
    "--cb-default-900": colors.text,
  };
}

export function reportContrastWarnings(colors) {
  const luminance = (hex) => {
    const rgb = normalizeReportColor(hex).slice(1).match(/../g).map((part) => {
      const channel = parseInt(part, 16) / 255;
      return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
    });
    return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722;
  };
  return [
    ["headerText", "header", 4.5], ["text", "surface", 4.5], ["text", "page", 4.5],
    ["mutedText", "surface", 4.5], ["mutedText", "page", 4.5],
    ["accentText", "accent", 4.5], ["accent", "surface", 3],
  ].filter(([text, background, minimum]) => {
    const a = luminance(colors[text]);
    const b = luminance(colors[background]);
    return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05) < minimum;
  }).map(([text, background]) => ({ field: text, message: `${reportColorLabels[text]} has low contrast against ${reportColorLabels[background].toLowerCase()}.` }));
}