@canopy-iiif/app 1.4.5 → 1.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
@@ -7806,6 +7806,25 @@ var ACCENT_COLOR_NAMES = [
7806
7806
  "sky"
7807
7807
  ];
7808
7808
  var GRAY_COLOR_NAMES = ["gray", "mauve", "slate", "sage", "olive", "sand"];
7809
+ var APPEARANCES = ["light", "dark"];
7810
+ var DEFAULTS = {
7811
+ appearance: "light",
7812
+ accentColor: "indigo",
7813
+ grayColor: "slate"
7814
+ };
7815
+ var LEVELS = COLOR_STOPS;
7816
+ var STEP_MAP = {
7817
+ 50: 1,
7818
+ 100: 3,
7819
+ 200: 4,
7820
+ 300: 6,
7821
+ 400: 7,
7822
+ 500: 8,
7823
+ 600: 9,
7824
+ 700: 10,
7825
+ 800: 11,
7826
+ 900: 12
7827
+ };
7809
7828
  var Section = ({ title, description, children }) => /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__section" }, /* @__PURE__ */ React41.createElement("h3", { className: "canopy-theme-showcase__section-title" }, title), description ? /* @__PURE__ */ React41.createElement("p", { className: "canopy-theme-showcase__section-description" }, description) : null, children);
7810
7829
  var ColorScaleRow = ({ label, prefix }) => /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__scale-row" }, /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__scale-label" }, /* @__PURE__ */ React41.createElement("strong", null, label)), /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__scale-track" }, COLOR_STOPS.map((stop) => /* @__PURE__ */ React41.createElement(
7811
7830
  "div",
@@ -7822,6 +7841,209 @@ var ColorScaleRow = ({ label, prefix }) => /* @__PURE__ */ React41.createElement
7822
7841
  ),
7823
7842
  /* @__PURE__ */ React41.createElement("span", { className: "canopy-theme-showcase__scale-token" }, stop)
7824
7843
  ))));
7844
+ var AVAILABLE = new Set(
7845
+ Object.keys(colors_exports).filter(
7846
+ (key) => /^[a-z]+$/i.test(key) && colors_exports[key] && colors_exports[key][`${key}1`]
7847
+ )
7848
+ );
7849
+ function normalizeAppearance(raw) {
7850
+ if (!raw) return "light";
7851
+ return String(raw).trim().toLowerCase() === "dark" ? "dark" : "light";
7852
+ }
7853
+ function darkenHex(hex, amount = 0.15) {
7854
+ if (!hex) return hex;
7855
+ const normalized = hex.replace("#", "");
7856
+ if (!/^[0-9a-fA-F]{6}$/.test(normalized)) return hex;
7857
+ const num = parseInt(normalized, 16);
7858
+ const r = num >> 16 & 255;
7859
+ const g = num >> 8 & 255;
7860
+ const b = num & 255;
7861
+ const clamp = (value) => Math.max(0, Math.min(255, Math.round(value)));
7862
+ const toHex = (value) => clamp(value).toString(16).padStart(2, "0");
7863
+ const factor = 1 - amount;
7864
+ return `#${toHex(r * factor)}${toHex(g * factor)}${toHex(b * factor)}`;
7865
+ }
7866
+ function lightenHex(hex, amount = 0.15) {
7867
+ if (!hex) return hex;
7868
+ const normalized = hex.replace("#", "");
7869
+ if (!/^[0-9a-fA-F]{6}$/.test(normalized)) return hex;
7870
+ const num = parseInt(normalized, 16);
7871
+ const r = num >> 16 & 255;
7872
+ const g = num >> 8 & 255;
7873
+ const b = num & 255;
7874
+ const clamp = (value) => Math.max(0, Math.min(255, Math.round(value)));
7875
+ const toHex = (value) => clamp(value).toString(16).padStart(2, "0");
7876
+ const adjust = (value) => value + (255 - value) * amount;
7877
+ return `#${toHex(adjust(r))}${toHex(adjust(g))}${toHex(adjust(b))}`;
7878
+ }
7879
+ function adjustSaturation(hex, amount = 0.15) {
7880
+ if (!hex) return hex;
7881
+ const normalized = hex.replace("#", "");
7882
+ if (!/^[0-9a-fA-F]{6}$/.test(normalized)) return hex;
7883
+ const num = parseInt(normalized, 16);
7884
+ let r = (num >> 16 & 255) / 255;
7885
+ let g = (num >> 8 & 255) / 255;
7886
+ let b = (num & 255) / 255;
7887
+ const max = Math.max(r, g, b);
7888
+ const min = Math.min(r, g, b);
7889
+ let h = 0;
7890
+ let s = 0;
7891
+ const l = (max + min) / 2;
7892
+ if (max !== min) {
7893
+ const d = max - min;
7894
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
7895
+ switch (max) {
7896
+ case r:
7897
+ h = (g - b) / d + (g < b ? 6 : 0);
7898
+ break;
7899
+ case g:
7900
+ h = (b - r) / d + 2;
7901
+ break;
7902
+ default:
7903
+ h = (r - g) / d + 4;
7904
+ }
7905
+ h /= 6;
7906
+ }
7907
+ const delta = Number(amount);
7908
+ if (!Number.isFinite(delta) || delta === 0) return hex;
7909
+ s = Math.max(0, Math.min(1, s + delta));
7910
+ const hueToRgb = (p, q, t) => {
7911
+ if (t < 0) t += 1;
7912
+ if (t > 1) t -= 1;
7913
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
7914
+ if (t < 1 / 2) return q;
7915
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
7916
+ return p;
7917
+ };
7918
+ let rOut;
7919
+ let gOut;
7920
+ let bOut;
7921
+ if (s === 0) {
7922
+ rOut = gOut = bOut = l;
7923
+ } else {
7924
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
7925
+ const p = 2 * l - q;
7926
+ rOut = hueToRgb(p, q, h + 1 / 3);
7927
+ gOut = hueToRgb(p, q, h);
7928
+ bOut = hueToRgb(p, q, h - 1 / 3);
7929
+ }
7930
+ const toHex = (value) => Math.round(Math.max(0, Math.min(1, value)) * 255).toString(16).padStart(2, "0");
7931
+ return `#${toHex(rOut)}${toHex(gOut)}${toHex(bOut)}`;
7932
+ }
7933
+ function mixHexColors(colorA, colorB, amount = 0.5) {
7934
+ const normalize = (hex) => hex && /^[0-9a-fA-F]{6}$/.test(hex.replace("#", "")) ? hex.replace("#", "") : null;
7935
+ const first = normalize(colorA);
7936
+ const second = normalize(colorB);
7937
+ if (!first || !second) return colorA || colorB || null;
7938
+ const a = parseInt(first, 16);
7939
+ const b = parseInt(second, 16);
7940
+ const clampAmount = Math.max(0, Math.min(1, Number(amount) || 0));
7941
+ const mixChannel = (shift) => Math.round(
7942
+ (a >> shift & 255) + ((b >> shift & 255) - (a >> shift & 255)) * clampAmount
7943
+ );
7944
+ const toHex = (value) => value.toString(16).padStart(2, "0");
7945
+ const r = mixChannel(16);
7946
+ const g = mixChannel(8);
7947
+ const bl = mixChannel(0);
7948
+ return `#${toHex(r)}${toHex(g)}${toHex(bl)}`;
7949
+ }
7950
+ function normalizeDarkenAmount(raw) {
7951
+ const value = Number(raw);
7952
+ if (!Number.isFinite(value)) return null;
7953
+ return Math.min(0.95, Math.max(0, value));
7954
+ }
7955
+ function resolveRadixPalette(name, appearance) {
7956
+ if (!name || !AVAILABLE.has(name)) return null;
7957
+ const paletteKey = appearance === "dark" ? `${name}Dark` : name;
7958
+ const palette = colors_exports[paletteKey];
7959
+ if (palette && palette[`${name}1`]) return palette;
7960
+ const fallback = colors_exports[name];
7961
+ return fallback && fallback[`${name}1`] ? fallback : null;
7962
+ }
7963
+ function toTailwindScale(name, options = {}) {
7964
+ if (!name || !AVAILABLE.has(name)) return null;
7965
+ const appearance = normalizeAppearance(options.appearance);
7966
+ const palette = resolveRadixPalette(name, appearance);
7967
+ if (!palette) return null;
7968
+ const scale = {};
7969
+ const darken900Amount = normalizeDarkenAmount(options.darken900Amount);
7970
+ for (const lvl of LEVELS) {
7971
+ const radixStep = STEP_MAP[lvl];
7972
+ const key = `${name}${radixStep}`;
7973
+ const value = palette[key];
7974
+ if (!value) return null;
7975
+ scale[lvl] = value;
7976
+ }
7977
+ const saturate700 = options.saturate700 !== false;
7978
+ if (scale["700"]) {
7979
+ let adjusted = appearance === "dark" ? lightenHex(scale["700"], 0.15) : darkenHex(scale["700"], 0.15);
7980
+ if (saturate700) adjusted = adjustSaturation(adjusted, 0.15);
7981
+ scale["700"] = adjusted;
7982
+ }
7983
+ const darkestKey = `${name}${STEP_MAP["900"]}`;
7984
+ if (scale["800"] && palette[darkestKey]) {
7985
+ const amount = darken900Amount != null ? darken900Amount : 0.25;
7986
+ scale["900"] = appearance === "dark" ? lightenHex(palette[darkestKey], amount) : darkenHex(palette[darkestKey], amount);
7987
+ }
7988
+ if (scale["800"] && scale["900"]) {
7989
+ scale["800"] = mixHexColors(scale["800"], scale["900"], 0.65);
7990
+ }
7991
+ return scale;
7992
+ }
7993
+ function buildPreviewData() {
7994
+ const data = {
7995
+ appearances: APPEARANCES,
7996
+ accentColors: ACCENT_COLOR_NAMES,
7997
+ grayColors: GRAY_COLOR_NAMES,
7998
+ defaults: DEFAULTS,
7999
+ scales: {}
8000
+ };
8001
+ for (const appearance of APPEARANCES) {
8002
+ const accentScales = {};
8003
+ const grayScales = {};
8004
+ for (const accent of ACCENT_COLOR_NAMES) {
8005
+ const scale = toTailwindScale(accent, { appearance });
8006
+ if (scale) accentScales[accent] = scale;
8007
+ }
8008
+ for (const gray2 of GRAY_COLOR_NAMES) {
8009
+ const scale = toTailwindScale(gray2, {
8010
+ appearance,
8011
+ darken900Amount: 0.4,
8012
+ saturate700: false
8013
+ });
8014
+ if (scale) grayScales[gray2] = scale;
8015
+ }
8016
+ data.scales[appearance] = { accent: accentScales, gray: grayScales };
8017
+ }
8018
+ return data;
8019
+ }
8020
+ var PREVIEW_DATA = buildPreviewData();
8021
+ function encodeJson(value) {
8022
+ return JSON.stringify(value).replace(/</g, "\\u003c");
8023
+ }
8024
+ var ColorsLabeled = ({ colors, type, getRadixSwatch }) => /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__swatch-grid" }, colors.map((name) => {
8025
+ const colorValue = getRadixSwatch(name);
8026
+ return /* @__PURE__ */ React41.createElement(
8027
+ "button",
8028
+ {
8029
+ key: `${type}-${name}`,
8030
+ type: "button",
8031
+ className: "canopy-theme-showcase__swatch",
8032
+ "data-theme-swatch": true,
8033
+ "data-theme-swatch-type": type,
8034
+ "data-theme-swatch-value": name,
8035
+ "aria-pressed": "false"
8036
+ },
8037
+ /* @__PURE__ */ React41.createElement(
8038
+ "span",
8039
+ {
8040
+ className: "canopy-theme-showcase__swatch-chip",
8041
+ style: { background: colorValue || "var(--color-gray-200)" }
8042
+ }
8043
+ ),
8044
+ /* @__PURE__ */ React41.createElement("span", { className: "canopy-theme-showcase__swatch-label" }, name)
8045
+ );
8046
+ }));
7825
8047
  function ThemeShowcase() {
7826
8048
  const accentColors = ACCENT_COLOR_NAMES;
7827
8049
  const grayColors = GRAY_COLOR_NAMES;
@@ -7831,20 +8053,49 @@ function ThemeShowcase() {
7831
8053
  if (!scale) return null;
7832
8054
  return scale[`${name}9`] || Object.values(scale)[8];
7833
8055
  };
7834
- const ColorsLabeled = ({ colors }) => /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__swatch-grid" }, colors.map((name) => {
7835
- const colorValue = getRadixSwatch(name);
7836
- return /* @__PURE__ */ React41.createElement("div", { key: name, className: "canopy-theme-showcase__swatch" }, /* @__PURE__ */ React41.createElement(
7837
- "div",
7838
- {
7839
- className: "canopy-theme-showcase__swatch-chip",
7840
- style: { background: colorValue || "var(--color-gray-200)" }
7841
- }
7842
- ), /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__swatch-label" }, name));
7843
- }));
7844
8056
  const styles = `
8057
+ .canopy-theme-showcase {
8058
+ margin: 2.618rem 0;
8059
+ padding: 1.5rem;
8060
+ border: 1px solid color-mix(in srgb, var(--color-gray-400) 40%, transparent);
8061
+ border-radius: 0.85rem;
8062
+ background: color-mix(in srgb, var(--color-gray-50) 78%, transparent);
8063
+ }
8064
+ .canopy-theme-showcase__appearance-buttons {
8065
+ display: inline-flex;
8066
+ gap: 0.35rem;
8067
+ flex-wrap: wrap;
8068
+ }
8069
+ .canopy-theme-showcase__appearance-button {
8070
+ border-radius: 999px;
8071
+ border: 1px solid var(--color-gray-300);
8072
+ background: var(--color-gray-50);
8073
+ color: var(--color-gray-900);
8074
+ padding: 0.3rem 0.9rem;
8075
+ font-size: 0.85rem;
8076
+ cursor: pointer;
8077
+ transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
8078
+ }
8079
+ .canopy-theme-showcase__appearance-button.is-active {
8080
+ border-color: var(--color-accent-default);
8081
+ color: var(--color-accent-default);
8082
+ background: color-mix(in srgb, var(--color-accent-100) 65%, transparent);
8083
+ }
8084
+ .canopy-theme-showcase__reset {
8085
+ border-radius: 999px;
8086
+ border: 1px solid var(--color-gray-400);
8087
+ background: transparent;
8088
+ padding: 0.35rem 1.2rem;
8089
+ font-size: 0.85rem;
8090
+ cursor: pointer;
8091
+ color: var(--color-gray-900);
8092
+ }
7845
8093
  .canopy-theme-showcase__section {
7846
8094
  margin: 2.618rem 0;
7847
8095
  }
8096
+ .canopy-theme-showcase__section:first-of-type {
8097
+ margin-top: 0;
8098
+ }
7848
8099
  .canopy-theme-showcase__section:last-of-type {
7849
8100
  margin-bottom: 0;
7850
8101
  }
@@ -7912,18 +8163,72 @@ function ThemeShowcase() {
7912
8163
  flex-wrap: wrap;
7913
8164
  gap: 1rem;
7914
8165
  }
8166
+ .canopy-theme-showcase__swatch {
8167
+ width: 5.5rem;
8168
+ border: 1px solid var(--color-gray-200);
8169
+ border-radius: 0.75rem;
8170
+ background: var(--color-gray-50);
8171
+ padding: 0.5rem;
8172
+ display: flex;
8173
+ flex-direction: column;
8174
+ align-items: center;
8175
+ gap: 0.35rem;
8176
+ cursor: pointer;
8177
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
8178
+ }
8179
+ .canopy-theme-showcase__swatch:focus-visible {
8180
+ outline: 2px solid var(--color-accent-default);
8181
+ outline-offset: 3px;
8182
+ }
8183
+ .canopy-theme-showcase__swatch[data-swatch-active="true"] {
8184
+ border-color: var(--color-accent-default);
8185
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent-200) 70%, transparent);
8186
+ }
7915
8187
  .canopy-theme-showcase__swatch-chip {
7916
- width: 4.236rem;
8188
+ width: 100%;
7917
8189
  height: 2.618rem;
7918
- border-radius: 3px;
8190
+ border-radius: 0.5rem;
7919
8191
  }
7920
8192
  .canopy-theme-showcase__swatch-label {
7921
8193
  font-size: 0.8333rem;
7922
- margin-top: 0.382rem;
7923
- font-weight: 300;
8194
+ margin-top: 0.1rem;
8195
+ font-weight: 500;
8196
+ text-transform: capitalize;
7924
8197
  }
8198
+ .canopy-theme-showcase__swatch-controls { display: none; }
8199
+ .canopy-theme-showcase__clear-button { display: none; }
7925
8200
  `;
7926
- return /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase" }, /* @__PURE__ */ React41.createElement("style", { dangerouslySetInnerHTML: { __html: styles } }), /* @__PURE__ */ React41.createElement(
8201
+ return /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase", "data-theme-showcase": true }, /* @__PURE__ */ React41.createElement("style", { dangerouslySetInnerHTML: { __html: styles } }), /* @__PURE__ */ React41.createElement("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: "1rem", marginBottom: "1rem" } }, /* @__PURE__ */ React41.createElement(
8202
+ Section,
8203
+ {
8204
+ title: "Appearance",
8205
+ description: "Pick the base light or dark mode for the theme preview."
8206
+ },
8207
+ /* @__PURE__ */ React41.createElement("div", { className: "canopy-theme-showcase__appearance-buttons" }, ["light", "dark"].map((mode) => {
8208
+ const label = `${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
8209
+ const baseClass = "canopy-theme-showcase__appearance-button";
8210
+ const isDefault = mode === DEFAULTS.appearance;
8211
+ const className = isDefault ? `${baseClass} is-active` : baseClass;
8212
+ return /* @__PURE__ */ React41.createElement(
8213
+ "button",
8214
+ {
8215
+ key: mode,
8216
+ type: "button",
8217
+ className,
8218
+ "data-theme-appearance": mode
8219
+ },
8220
+ label
8221
+ );
8222
+ }))
8223
+ ), /* @__PURE__ */ React41.createElement(
8224
+ "button",
8225
+ {
8226
+ type: "button",
8227
+ className: "canopy-theme-showcase__reset",
8228
+ "data-theme-reset": true
8229
+ },
8230
+ "Reset"
8231
+ )), /* @__PURE__ */ React41.createElement(
7927
8232
  Section,
7928
8233
  {
7929
8234
  title: "Color scales",
@@ -7941,16 +8246,23 @@ function ThemeShowcase() {
7941
8246
  Section,
7942
8247
  {
7943
8248
  title: "Accent color palette options",
7944
- description: "Primary color steps used for buttons, links, and highlights."
8249
+ description: "Click a swatch to temporarily override the accent palette."
7945
8250
  },
7946
- /* @__PURE__ */ React41.createElement(ColorsLabeled, { colors: accentColors })
8251
+ /* @__PURE__ */ React41.createElement(ColorsLabeled, { colors: accentColors, type: "accent", getRadixSwatch })
7947
8252
  ), /* @__PURE__ */ React41.createElement(
7948
8253
  Section,
7949
8254
  {
7950
8255
  title: "Gray color palette options",
7951
- description: "Neutral color steps used for backgrounds, borders, and text."
8256
+ description: "Click a swatch to preview the neutral ramp for surfaces and text."
7952
8257
  },
7953
- /* @__PURE__ */ React41.createElement(ColorsLabeled, { colors: grayColors })
8258
+ /* @__PURE__ */ React41.createElement(ColorsLabeled, { colors: grayColors, type: "gray", getRadixSwatch })
8259
+ ), /* @__PURE__ */ React41.createElement(
8260
+ "script",
8261
+ {
8262
+ type: "application/json",
8263
+ "data-theme-showcase-values": true,
8264
+ dangerouslySetInnerHTML: { __html: encodeJson(PREVIEW_DATA) }
8265
+ }
7954
8266
  ));
7955
8267
  }
7956
8268
  export {