diff --git a/pages/docs/scroll.mdx b/pages/docs/scroll.mdx
index 61bcb1f6..5eb59223 100644
--- a/pages/docs/scroll.mdx
+++ b/pages/docs/scroll.mdx
@@ -28,9 +28,7 @@ Provide a [IIIF Presentation API](https://iiif.io/api/presentation/3.0/) Manifes
- Renders the Canvases of a IIIF Manifest as HTML5 `article` elements
- Outputs `Annotation` textual content along with `OpenSeadragon` for images
-- Automatically creates a client-side search index for full-text search of Annotations
- Supports vertical scrolling and textual discovery with a fixed **Search...** input
-- Supports `commenting`, `transcribing`, and `translating` motivations
## Installation
@@ -104,11 +102,12 @@ const MyCustomScroll = () => {
`Scroll` can configured through an `options` prop, which will serve as a object for common options.
-| Prop | Type | Required | Default |
-| ---------------- | -------- | -------- | ------- |
-| `iiifContent` | `string` | Yes | |
-| `options` | `object` | No | |
-| `options.offset` | `number` | No | `0` |
+| Prop | Type | Required | Default |
+| ---------------- | --------------------- | -------- | ------- |
+| `iiifContent` | `string` | Yes | |
+| `options` | `object` | No | |
+| `options.offset` | `number` | No | `0` |
+| `options.figure` | [See Figure](#figure) | No | |
### Offset
@@ -120,3 +119,26 @@ The `options.offset` refers to the number of pixels to offset the fixed **Search
options={{ offset: 90 }}
/>
```
+
+### Figure
+
+The Scroll component renders a `figure` element for each Canvas. The `options.figure` object allows for customization of the `figure` width, aspect ratio, and display. This can be useful for customizing the Scroll component to fit within a specific layout alongside other image viewers.
+
+| Prop | Type | Required | Default |
+| ---------------------------- | ---------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ |
+| `options.figure.aspectRatio` | `number` | No | `1.618` [Golden Ratio](https://en.wikipedia.org/wiki/Golden_ratio) |
+| `options.figure.display` | `thumbnail`, `image-viewer` | No | `image-viewer` |
+| `options.figure.width` | `string` [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/width) | No | `38.2%` |
+
+```jsx
+
+```
diff --git a/src/components/Scroll/Figure/Figure.styled.tsx b/src/components/Scroll/Figure/Figure.styled.tsx
index 8a3830c7..c49f2fdc 100644
--- a/src/components/Scroll/Figure/Figure.styled.tsx
+++ b/src/components/Scroll/Figure/Figure.styled.tsx
@@ -1,5 +1,8 @@
+import { AspectRatio } from "@radix-ui/react-aspect-ratio";
import { styled } from "src/styles/stitches.config";
+const StyledFigurePlaceholder = styled(AspectRatio, {});
+
const StyledFigure = styled("figure", {
figcaption: {
display: "flex",
@@ -16,4 +19,4 @@ const StyledFigure = styled("figure", {
},
});
-export { StyledFigure };
+export { StyledFigure, StyledFigurePlaceholder };
diff --git a/src/components/Scroll/Figure/Figure.tsx b/src/components/Scroll/Figure/Figure.tsx
index 3e84fac3..b1d3f5e7 100644
--- a/src/components/Scroll/Figure/Figure.tsx
+++ b/src/components/Scroll/Figure/Figure.tsx
@@ -1,10 +1,14 @@
import React, { useContext } from "react";
+import { ScrollContext, initialState } from "src/context/scroll-context";
+import {
+ StyledFigure,
+ StyledFigurePlaceholder,
+} from "src/components/Scroll/Figure/Figure.styled";
import { CanvasNormalized } from "@iiif/presentation-3";
import FigureCaption from "src/components/Scroll/Figure/Caption";
-import ImageViewer from "src/components/Scroll/Figure/ImageViewer";
-import { ScrollContext } from "src/context/scroll-context";
-import { StyledFigure } from "src/components/Scroll/Figure/Figure.styled";
+import FigureImageViewer from "src/components/Scroll/Figure/ImageViewer";
+import FigureThumbnail from "./Thumbnail";
import { getPaintingResource } from "src/hooks/use-iiif";
interface CanvasProps {
@@ -17,7 +21,15 @@ interface CanvasProps {
const ScrollCanvasFigure: React.FC = ({ canvas, canvasInfo }) => {
const { state } = useContext(ScrollContext);
- const { vault } = state;
+ const { vault, options } = state;
+ const { figure } = options;
+
+ const display = figure.display
+ ? figure.display
+ : initialState.options.figure.display;
+ const aspectRatio = figure.aspectRatio
+ ? figure.aspectRatio
+ : initialState.options.figure.aspectRatio;
const painting = getPaintingResource(vault, canvas.id);
@@ -25,9 +37,26 @@ const ScrollCanvasFigure: React.FC = ({ canvas, canvasInfo }) => {
return (
- {painting?.map((body) => (
-
- ))}
+ {painting?.map((body) => {
+ return (
+
+ {display === "thumbnail" && (
+
+ )}
+ {display === "image-viewer" && (
+
+ )}
+
+ );
+ })}
);
diff --git a/src/components/Scroll/Figure/ImageViewer.styled.tsx b/src/components/Scroll/Figure/ImageViewer.styled.tsx
index bf1697f2..78534bf4 100644
--- a/src/components/Scroll/Figure/ImageViewer.styled.tsx
+++ b/src/components/Scroll/Figure/ImageViewer.styled.tsx
@@ -2,7 +2,7 @@ import { styled } from "src/styles/stitches.config";
const StyledImageViewer = styled("div", {
width: "100%",
- height: "360px",
+ height: "100%",
background: "#6662",
backgroundSize: "contain",
color: "white",
diff --git a/src/components/Scroll/Figure/Thumbnail.test.tsx b/src/components/Scroll/Figure/Thumbnail.test.tsx
new file mode 100644
index 00000000..8bca6223
--- /dev/null
+++ b/src/components/Scroll/Figure/Thumbnail.test.tsx
@@ -0,0 +1,55 @@
+import {
+ IIIFExternalWebResource,
+ InternationalString,
+} from "@iiif/presentation-3";
+import { render, screen } from "@testing-library/react";
+
+import FigureThumbnail from "./Thumbnail";
+import React from "react";
+
+const body: IIIFExternalWebResource = {
+ id: "https://example.com/iiif/2/image/example.jpg",
+ type: "Image",
+ format: "image/jpeg",
+ width: 500,
+ height: 500,
+ service: [
+ {
+ id: "https://example.com/iiif/2/image",
+ type: "ImageService2",
+ profile: "level2",
+ },
+ ],
+};
+
+const label: InternationalString = {
+ en: ["Test label"],
+};
+
+describe("", () => {
+ it("renders the thumbnail with label as alt", () => {
+ render();
+
+ const thumbnail = screen.getByTestId("scroll-figure-thumbnail");
+ expect(thumbnail).toBeInTheDocument();
+
+ const img = thumbnail.querySelector("img");
+ expect(img).toBeInTheDocument();
+ expect(img).toHaveAttribute(
+ "src",
+ "https://example.com/iiif/2/image/full/500,500/0/default.jpg",
+ );
+ expect(img).toHaveAttribute("alt", "Test label");
+ });
+
+ it("renders the thumbnail without a service", () => {
+ render();
+
+ const img = screen.getByRole("img");
+ expect(img).toBeInTheDocument();
+ expect(img).toHaveAttribute(
+ "src",
+ "https://example.com/iiif/2/image/example.jpg",
+ );
+ });
+});
diff --git a/src/components/Scroll/Figure/Thumbnail.tsx b/src/components/Scroll/Figure/Thumbnail.tsx
new file mode 100644
index 00000000..3f2a4c3d
--- /dev/null
+++ b/src/components/Scroll/Figure/Thumbnail.tsx
@@ -0,0 +1,32 @@
+import {
+ IIIFExternalWebResource,
+ InternationalString,
+} from "@iiif/presentation-3";
+
+import { Thumbnail as CloverPrimitivesThumbnail } from "src/components/Primitives";
+import React from "react";
+import { StyledImageViewer } from "src/components/Scroll/Figure/ImageViewer.styled";
+
+interface FigureThumbnailProps {
+ body: IIIFExternalWebResource;
+ label?: InternationalString | null;
+}
+
+const FigureThumbnail: React.FC = ({ body, label }) => {
+ return (
+
+
+
+ );
+};
+
+export default React.memo(FigureThumbnail);
diff --git a/src/components/Scroll/Items/Item.tsx b/src/components/Scroll/Items/Item.tsx
index d46d20cd..64f361ae 100644
--- a/src/components/Scroll/Items/Item.tsx
+++ b/src/components/Scroll/Items/Item.tsx
@@ -9,9 +9,9 @@ import {
StyledItemFigure,
StyledItemTextualBodies,
} from "src/components/Scroll/Items/Items.styled";
+import { ScrollContext, initialState } from "src/context/scroll-context";
import React from "react";
-import { ScrollContext } from "src/context/scroll-context";
import ScrollFigure from "src/components/Scroll/Figure/Figure";
import ScrollItemBody from "src/components/Scroll/Annotation/Body";
@@ -33,7 +33,8 @@ const ScrollItem: React.FC = ({
itemNumber,
}) => {
const { state } = React.useContext(ScrollContext);
- const { annotations, vault } = state;
+ const { annotations, vault, options } = state;
+ const { figure } = options;
const canvas = vault?.get(item) as CanvasNormalized;
@@ -67,7 +68,14 @@ const ScrollItem: React.FC = ({
data-page-number={itemNumber}
data-last-item={isLastItem}
>
-
+
{canvas && }
diff --git a/src/components/Scroll/Items/Items.styled.tsx b/src/components/Scroll/Items/Items.styled.tsx
index 5a10e327..46ab37b2 100644
--- a/src/components/Scroll/Items/Items.styled.tsx
+++ b/src/components/Scroll/Items/Items.styled.tsx
@@ -10,12 +10,12 @@ const StyledItem = styled("article", {
const StyledItemFigure = styled("div", {
transition: "$all",
- width: "38.2%",
+ flexShrink: 0,
});
const StyledItemTextualBodies = styled("div", {
- width: "61.8%",
display: "flex",
+ flexGrow: 1,
flexDirection: "column",
justifyContent: "flex-start",
diff --git a/src/components/Scroll/Items/Items.tsx b/src/components/Scroll/Items/Items.tsx
index 61967826..532125ec 100644
--- a/src/components/Scroll/Items/Items.tsx
+++ b/src/components/Scroll/Items/Items.tsx
@@ -5,25 +5,23 @@ import { StyledScrollItems } from "src/components/Scroll/Items/Items.styled";
const ScrollItems = ({ items }: { items: Reference<"Canvas">[] }) => {
return (
- <>
-
- {items.map((item, index) => {
- const itemNumber = index + 1;
- const isLastItem = itemNumber === items.length;
+
+ {items.map((item, index) => {
+ const itemNumber = index + 1;
+ const isLastItem = itemNumber === items.length;
- return (
-
- );
- })}
-
- >
+ return (
+
+ );
+ })}
+
);
};
diff --git a/src/context/scroll-context.tsx b/src/context/scroll-context.tsx
index c5e7948a..11075b2a 100644
--- a/src/context/scroll-context.tsx
+++ b/src/context/scroll-context.tsx
@@ -8,6 +8,11 @@ interface StateType {
manifest?: ManifestNormalized;
options: {
offset: number;
+ figure: {
+ display: "thumbnail" | "image-viewer";
+ aspectRatio?: number;
+ width?: CSSStyleDeclaration["width"];
+ };
};
searchActiveMatch?: string;
searchMatches?: {
@@ -25,11 +30,16 @@ interface ActionType {
type: string;
}
-const initialState: StateType = {
+export const initialState: StateType = {
annotations: [],
manifest: undefined,
options: {
offset: 0,
+ figure: {
+ display: "image-viewer",
+ aspectRatio: 100 / 61.8, // golden ratio
+ width: "38.2%",
+ },
},
searchActiveMatch: undefined,
searchMatches: undefined,