@canopy-iiif/app 0.10.23 → 0.10.26

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.
@@ -1,14 +1,24 @@
1
1
  const React = require('react');
2
2
 
3
- let PageContext = null;
3
+ const CONTEXT_KEY =
4
+ typeof Symbol === 'function' ? Symbol.for('__CANOPY_PAGE_CONTEXT__') : '__CANOPY_PAGE_CONTEXT__';
5
+
6
+ function getGlobalRoot() {
7
+ if (typeof globalThis !== 'undefined') return globalThis;
8
+ if (typeof global !== 'undefined') return global;
9
+ if (typeof window !== 'undefined') return window;
10
+ return {};
11
+ }
4
12
 
5
13
  function getPageContext() {
6
- if (!PageContext) {
7
- PageContext = React.createContext({ navigation: null, page: null });
8
- }
9
- return PageContext;
14
+ const root = getGlobalRoot();
15
+ if (root[CONTEXT_KEY]) return root[CONTEXT_KEY];
16
+ const ctx = React.createContext({ navigation: null, page: null });
17
+ root[CONTEXT_KEY] = ctx;
18
+ return ctx;
10
19
  }
11
20
 
12
21
  module.exports = {
13
22
  getPageContext,
23
+ CONTEXT_KEY,
14
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "0.10.23",
3
+ "version": "0.10.26",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
package/ui/dist/index.mjs CHANGED
@@ -958,12 +958,50 @@ function HeaderScript() {
958
958
  }
959
959
  );
960
960
  }
961
+ var CONTEXT_KEY = typeof Symbol === "function" ? Symbol.for("__CANOPY_PAGE_CONTEXT__") : "__CANOPY_PAGE_CONTEXT__";
962
+ function getSharedRoot() {
963
+ if (typeof globalThis !== "undefined") return globalThis;
964
+ if (typeof window !== "undefined") return window;
965
+ if (typeof global !== "undefined") return global;
966
+ return null;
967
+ }
968
+ function getSafePageContext() {
969
+ const root = getSharedRoot();
970
+ if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
971
+ const ctx = React14.createContext({ navigation: null, page: null });
972
+ if (root) root[CONTEXT_KEY] = ctx;
973
+ return ctx;
974
+ }
961
975
  function ensureArray(navLinks) {
962
976
  if (!Array.isArray(navLinks)) return [];
963
977
  return navLinks.filter(
964
978
  (link) => link && typeof link === "object" && typeof link.href === "string"
965
979
  );
966
980
  }
981
+ function SectionNavList({ root }) {
982
+ if (!root || !Array.isArray(root.children) || !root.children.length) return null;
983
+ return /* @__PURE__ */ React14.createElement("ul", { className: "canopy-modal__section-list", role: "list" }, root.children.map((node) => /* @__PURE__ */ React14.createElement(SectionNavItem, { key: node.slug || node.href || node.title, node, depth: 0 })));
984
+ }
985
+ function SectionNavItem({ node, depth }) {
986
+ if (!node) return null;
987
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0;
988
+ const Tag = node.href ? "a" : "span";
989
+ const classes = [
990
+ "canopy-modal__section-link",
991
+ `depth-${Math.min(5, Math.max(0, depth + 1))}`
992
+ ];
993
+ if (!node.href) classes.push("is-label");
994
+ if (node.isActive) classes.push("is-active");
995
+ return /* @__PURE__ */ React14.createElement("li", { className: "canopy-modal__section-item", "data-depth": depth }, /* @__PURE__ */ React14.createElement(
996
+ Tag,
997
+ {
998
+ className: classes.join(" "),
999
+ href: node.href || void 0,
1000
+ "aria-current": node.isActive ? "page" : void 0
1001
+ },
1002
+ node.title || node.slug
1003
+ ), hasChildren ? /* @__PURE__ */ React14.createElement("ul", { className: "canopy-modal__section-list canopy-modal__section-list--nested", role: "list" }, node.children.map((child) => /* @__PURE__ */ React14.createElement(SectionNavItem, { key: child.slug || child.href || child.title, node: child, depth: depth + 1 }))) : null);
1004
+ }
967
1005
  function CanopyHeader(props = {}) {
968
1006
  const {
969
1007
  navigation: navLinksProp,
@@ -975,6 +1013,13 @@ function CanopyHeader(props = {}) {
975
1013
  logo: SiteLogo
976
1014
  } = props;
977
1015
  const navLinks = ensureArray(navLinksProp);
1016
+ const PageContext = getSafePageContext();
1017
+ const context = React14.useContext(PageContext);
1018
+ const sectionNavigation = context && context.navigation && context.navigation.root ? context.navigation : null;
1019
+ const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
1020
+ const hasSectionNav = !!(sectionNavigation && sectionNavigation.root && Array.isArray(sectionNavigation.root.children) && sectionNavigation.root.children.length);
1021
+ const sectionLabel = sectionHeading ? `More in ${sectionHeading}` : "More in this section";
1022
+ const sectionAriaLabel = sectionHeading ? `${sectionHeading} section navigation` : "Section navigation";
978
1023
  return /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
979
1024
  "header",
980
1025
  {
@@ -1085,7 +1130,16 @@ function CanopyHeader(props = {}) {
1085
1130
  "aria-label": "Primary navigation"
1086
1131
  },
1087
1132
  navLinks.map((link) => /* @__PURE__ */ React14.createElement("a", { key: link.href, href: link.href }, link.label || link.href))
1088
- )
1133
+ ),
1134
+ hasSectionNav ? /* @__PURE__ */ React14.createElement(
1135
+ "nav",
1136
+ {
1137
+ className: "canopy-modal__section-nav",
1138
+ "aria-label": sectionAriaLabel
1139
+ },
1140
+ /* @__PURE__ */ React14.createElement("div", { className: "canopy-modal__section-label" }, sectionLabel),
1141
+ /* @__PURE__ */ React14.createElement(SectionNavList, { root: sectionNavigation.root })
1142
+ ) : null
1089
1143
  ), /* @__PURE__ */ React14.createElement(
1090
1144
  CanopyModal,
1091
1145
  {
@@ -1212,7 +1266,7 @@ var Slider = (props) => {
1212
1266
  } catch (_) {
1213
1267
  json = "{}";
1214
1268
  }
1215
- return /* @__PURE__ */ React17.createElement("div", { "data-canopy-slider": "1", className: "not-prose" }, /* @__PURE__ */ React17.createElement(
1269
+ return /* @__PURE__ */ React17.createElement("div", { className: "canopy-slider", "data-canopy-slider": "1" }, /* @__PURE__ */ React17.createElement(
1216
1270
  "script",
1217
1271
  {
1218
1272
  type: "application/json",
@@ -1220,7 +1274,7 @@ var Slider = (props) => {
1220
1274
  }
1221
1275
  ));
1222
1276
  }
1223
- return /* @__PURE__ */ React17.createElement(CloverSlider, { ...props });
1277
+ return /* @__PURE__ */ React17.createElement(CloverSlider, { ...props, className: "canopy-slider" });
1224
1278
  };
1225
1279
 
1226
1280
  // ui/src/iiif/Scroll.jsx
@@ -1325,7 +1379,13 @@ function MdxRelatedItems(props) {
1325
1379
  } catch (_) {
1326
1380
  json = "{}";
1327
1381
  }
1328
- return /* @__PURE__ */ React20.createElement("div", { "data-canopy-related-items": "1", className: "not-prose" }, /* @__PURE__ */ React20.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
1382
+ return /* @__PURE__ */ React20.createElement("div", { "data-canopy-related-items": "1" }, /* @__PURE__ */ React20.createElement(
1383
+ "script",
1384
+ {
1385
+ type: "application/json",
1386
+ dangerouslySetInnerHTML: { __html: json }
1387
+ }
1388
+ ));
1329
1389
  }
1330
1390
 
1331
1391
  // ui/src/search/MdxSearchResults.jsx
@@ -1488,7 +1548,7 @@ function SearchResults({
1488
1548
  }
1489
1549
 
1490
1550
  // ui/src/search/SearchTabs.jsx
1491
- import React25 from "react";
1551
+ import React25, { useRef as useRef2, useState as useState6, useEffect as useEffect6 } from "react";
1492
1552
  function SearchTabs({
1493
1553
  type = "all",
1494
1554
  onTypeChange,
@@ -1503,13 +1563,62 @@ function SearchTabs({
1503
1563
  const toLabel = (t) => t && t.length ? t.charAt(0).toUpperCase() + t.slice(1) : "";
1504
1564
  const hasFilters = typeof onOpenFilters === "function";
1505
1565
  const filterBadge = activeFilterCount > 0 ? ` (${activeFilterCount})` : "";
1566
+ const [itemBoundingBox, setItemBoundingBox] = useState6(null);
1567
+ const [wrapperBoundingBox, setWrapperBoundingBox] = useState6(null);
1568
+ const [highlightedTab, setHighlightedTab] = useState6(null);
1569
+ const [isHoveredFromNull, setIsHoveredFromNull] = useState6(true);
1570
+ const wrapperRef = useRef2(null);
1571
+ const highlightRef = useRef2(null);
1572
+ const repositionHighlight = (e, tabKey) => {
1573
+ if (!tabKey || !wrapperRef.current) return;
1574
+ const item = e.currentTarget;
1575
+ setItemBoundingBox(item.getBoundingClientRect());
1576
+ setWrapperBoundingBox(wrapperRef.current.getBoundingClientRect());
1577
+ setIsHoveredFromNull(!highlightedTab);
1578
+ setHighlightedTab(tabKey);
1579
+ };
1580
+ const resetHighlight = () => {
1581
+ setHighlightedTab(null);
1582
+ };
1583
+ useEffect6(() => {
1584
+ if (!wrapperRef.current) return;
1585
+ const activeButton = wrapperRef.current.querySelector(
1586
+ `button[data-tab-value="${type}"]`
1587
+ );
1588
+ if (!activeButton) return;
1589
+ const itemBox = activeButton.getBoundingClientRect();
1590
+ const wrapperBox = wrapperRef.current.getBoundingClientRect();
1591
+ setItemBoundingBox(itemBox);
1592
+ setWrapperBoundingBox(wrapperBox);
1593
+ setIsHoveredFromNull(true);
1594
+ setHighlightedTab(type);
1595
+ }, [type]);
1596
+ let highlightStyles = {};
1597
+ if (itemBoundingBox && wrapperBoundingBox) {
1598
+ highlightStyles = {
1599
+ opacity: highlightedTab ? 1 : 0,
1600
+ transform: `translateX(${itemBoundingBox.left - wrapperBoundingBox.left}px)`,
1601
+ transitionDuration: isHoveredFromNull ? "0ms" : "200ms",
1602
+ width: `${itemBoundingBox.width}px`
1603
+ };
1604
+ }
1506
1605
  return /* @__PURE__ */ React25.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React25.createElement(
1507
1606
  "div",
1508
1607
  {
1509
1608
  role: "tablist",
1510
1609
  "aria-label": "Search types",
1511
- className: "canopy-search-tabs"
1610
+ className: "canopy-search-tabs",
1611
+ ref: wrapperRef,
1612
+ onMouseLeave: resetHighlight
1512
1613
  },
1614
+ /* @__PURE__ */ React25.createElement(
1615
+ "div",
1616
+ {
1617
+ ref: highlightRef,
1618
+ className: "canopy-search-tabs__highlight",
1619
+ style: highlightStyles
1620
+ }
1621
+ ),
1513
1622
  orderedTypes.map((t) => {
1514
1623
  const active = String(type).toLowerCase() === String(t).toLowerCase();
1515
1624
  const cRaw = counts && Object.prototype.hasOwnProperty.call(counts, t) ? counts[t] : void 0;
@@ -1521,7 +1630,10 @@ function SearchTabs({
1521
1630
  role: "tab",
1522
1631
  "aria-selected": active,
1523
1632
  type: "button",
1524
- onClick: () => onTypeChange && onTypeChange(t)
1633
+ "data-tab-value": t,
1634
+ onClick: () => onTypeChange && onTypeChange(t),
1635
+ onMouseOver: (e) => repositionHighlight(e, t),
1636
+ className: `canopy-search-tab ${active ? "is-active" : ""}`
1525
1637
  },
1526
1638
  toLabel(t),
1527
1639
  " (",
@@ -1749,11 +1861,18 @@ function MarkdownTable({ className = "", ...rest }) {
1749
1861
  const merged = ["markdown-table", className].filter(Boolean).join(" ");
1750
1862
  return /* @__PURE__ */ React28.createElement("div", { className: "markdown-table__frame" }, /* @__PURE__ */ React28.createElement("table", { className: merged, ...rest }));
1751
1863
  }
1864
+
1865
+ // ui/src/docs/Diagram.jsx
1866
+ import React29 from "react";
1867
+ function CanopyDiagram() {
1868
+ return /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram" }, /* @__PURE__ */ React29.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--collections" }, /* @__PURE__ */ React29.createElement("h3", null, "IIIF Collection(s)"), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__section-summary" }, "Source collections contribute 170 total manifests that Canopy retrieves as-is via IIIF endpoints."), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Collection A"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "70 Manifests"), /* @__PURE__ */ React29.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React29.createElement("li", null, "Textual Annotations"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Collection B"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "35 Manifests"), /* @__PURE__ */ React29.createElement("li", null, "IIIF Images + A/V"), /* @__PURE__ */ React29.createElement("li", null, "Textual Annotations"))))), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React29.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--build" }, /* @__PURE__ */ React29.createElement("h3", null, "Canopy Build Process"), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__section-summary" }, "Canopy syncs manifests, authored pages, and annotations before bundling data for the static site."), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Automated content"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "105 manifests \u2192 105 work pages"), /* @__PURE__ */ React29.createElement("li", null, "One page per manifest"), /* @__PURE__ */ React29.createElement("li", null, "Customize layout per work"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Contextual content"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Markdown & MDX pages"), /* @__PURE__ */ React29.createElement("li", null, "Author narratives & tours"), /* @__PURE__ */ React29.createElement("li", null, "Reference manifests inline"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Search index"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Combines works + pages"), /* @__PURE__ */ React29.createElement("li", null, "FlexSearch powered"), /* @__PURE__ */ React29.createElement("li", null, "Optional annotations"))))), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__arrow", "aria-hidden": "true" }, /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-line" }), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__arrow-head" })), /* @__PURE__ */ React29.createElement("section", { className: "canopy-diagram__section canopy-diagram__section--output" }, /* @__PURE__ */ React29.createElement("h3", null, "Static Digital Project"), /* @__PURE__ */ React29.createElement("span", { className: "canopy-diagram__section-summary" }, "The output is a lightweight bundle of HTML, CSS, JS, and JSON assets that can deploy anywhere."), /* @__PURE__ */ React29.createElement("div", { className: "canopy-diagram__grid" }, /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Work pages"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "105 generated HTML pages"), /* @__PURE__ */ React29.createElement("li", null, "Each links back to source manifests"), /* @__PURE__ */ React29.createElement("li", null, "Styled with Canopy components"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Custom pages"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Markdown & MDX-authored content"), /* @__PURE__ */ React29.createElement("li", null, "Reusable layouts for narratives"), /* @__PURE__ */ React29.createElement("li", null, "Embed IIIF media & interstitials"))), /* @__PURE__ */ React29.createElement("article", null, /* @__PURE__ */ React29.createElement("h4", null, "Search bundle"), /* @__PURE__ */ React29.createElement("ul", null, /* @__PURE__ */ React29.createElement("li", null, "Static FlexSearch index"), /* @__PURE__ */ React29.createElement("li", null, "Works + pages share records"), /* @__PURE__ */ React29.createElement("li", null, "Optional annotation dataset"))))));
1869
+ }
1752
1870
  export {
1753
1871
  ArticleCard,
1754
1872
  Button,
1755
1873
  ButtonWrapper,
1756
1874
  CanopyBrand,
1875
+ CanopyDiagram,
1757
1876
  CanopyFooter,
1758
1877
  CanopyHeader,
1759
1878
  CanopyModal,