Skip to content

Commit

Permalink
feat: forward ref to <PrismicNextImage>
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Jan 29, 2025
1 parent a8ed344 commit 2666f1f
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 42 deletions.
18 changes: 18 additions & 0 deletions e2e-projects/app-router/app/PrismicNextImage/client/ClientTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import { useState } from "react";
import { ImageField } from "@prismicio/client";
import { PrismicNextImage } from "@prismicio/next";

export function ClientTest(props: { field: ImageField }) {
const { field } = props;

const [ref, setRef] = useState<Element | null>(null);

return (
<p>
<PrismicNextImage ref={setRef} field={field} />
<span data-testid="ref">tagname: {ref?.tagName}</span>
</p>
);
}
14 changes: 14 additions & 0 deletions e2e-projects/app-router/app/PrismicNextImage/client/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isFilled } from "@prismicio/client";
import assert from "assert";

import { createClient } from "@/prismicio";
import { ClientTest } from "./ClientTest";

export default async function Page() {
const client = await createClient();
const { data: tests } = await client.getSingle("image_test");

assert(isFilled.image(tests.filled));

return <ClientTest field={tests.filled} />;
}
35 changes: 35 additions & 0 deletions e2e-projects/pages-router/pages/PrismicNextImage/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useState } from "react";
import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
import { PrismicNextImage } from "@prismicio/next/pages";
import { isFilled } from "@prismicio/client";
import assert from "assert";

import { createClient } from "@/prismicio";

export default function Page({
field,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const [ref, setRef] = useState<Element | null>(null);

return (
<p>
<PrismicNextImage ref={setRef} field={field} />
<span data-testid="ref">tagname: {ref?.tagName}</span>
</p>
);
}

export async function getServerSideProps({ req }: GetServerSidePropsContext) {
const repositoryName = req.cookies["repository-name"];
assert(
repositoryName && typeof repositoryName === "string",
"A repository-name cookie is required.",
);

const client = createClient(repositoryName);
const { data: tests } = await client.getSingle("image_test");

assert(isFilled.image(tests.filled));

return { props: { field: tests.filled } };
}
89 changes: 47 additions & 42 deletions src/PrismicNextImage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
"use client";

import { FC } from "react";
import {
forwardRef,
ForwardRefExoticComponent,
PropsWithoutRef,
RefAttributes,
} from "react";
import Image, { ImageProps } from "next/image";
import { buildURL, ImgixURLParams } from "imgix-url-builder";
import { ImageFieldImage, isFilled } from "@prismicio/client";
Expand Down Expand Up @@ -80,37 +85,47 @@ export type PrismicNextImageProps = Omit<
*
* @see To learn more about `next/image`, see: https://nextjs.org/docs/api-reference/next/image
*/
export const PrismicNextImage: FC<PrismicNextImageProps> = ({
field,
imgixParams = {},
alt,
fallbackAlt,
fill,
width,
height,
fallback = null,
loader = imgixLoader,
...restProps
}) => {
if (DEV) {
if (typeof alt === "string" && alt !== "") {
console.warn(
`[PrismicNextImage] The "alt" prop can only be used to declare an image as decorative by passing an empty string (alt="") but was provided a non-empty string. You can resolve this warning by removing the "alt" prop or changing it to alt="". For more details, see ${devMsg(
"alt-must-be-an-empty-string",
)}`,
);
// The type annotation is necessary to avoid a type reference issue.
export const PrismicNextImage: ForwardRefExoticComponent<
PropsWithoutRef<PrismicNextImageProps> & RefAttributes<HTMLImageElement>
> = forwardRef<HTMLImageElement, PrismicNextImageProps>(
function PrismicNextImage(
{
field,
imgixParams = {},
alt,
fallbackAlt,
fill,
width,
height,
fallback = null,
loader = imgixLoader,
...restProps
},
ref,
) {
if (DEV) {
if (typeof alt === "string" && alt !== "") {
console.warn(
`[PrismicNextImage] The "alt" prop can only be used to declare an image as decorative by passing an empty string (alt="") but was provided a non-empty string. You can resolve this warning by removing the "alt" prop or changing it to alt="". For more details, see ${devMsg(
"alt-must-be-an-empty-string",
)}`,
);
}

if (typeof fallbackAlt === "string" && fallbackAlt !== "") {
console.warn(
`[PrismicNextImage] The "fallbackAlt" prop can only be used to declare an image as decorative by passing an empty string (fallbackAlt="") but was provided a non-empty string. You can resolve this warning by removing the "fallbackAlt" prop or changing it to fallbackAlt="". For more details, see ${devMsg(
"alt-must-be-an-empty-string",
)}`,
);
}
}

if (typeof fallbackAlt === "string" && fallbackAlt !== "") {
console.warn(
`[PrismicNextImage] The "fallbackAlt" prop can only be used to declare an image as decorative by passing an empty string (fallbackAlt="") but was provided a non-empty string. You can resolve this warning by removing the "fallbackAlt" prop or changing it to fallbackAlt="". For more details, see ${devMsg(
"alt-must-be-an-empty-string",
)}`,
);
if (!isFilled.imageThumbnail(field)) {
return <>{fallback}</>;
}
}

if (isFilled.imageThumbnail(field)) {
const resolvedImgixParams = imgixParams;
for (const x in imgixParams) {
if (resolvedImgixParams[x as keyof typeof resolvedImgixParams] === null) {
Expand All @@ -136,7 +151,6 @@ export const PrismicNextImage: FC<PrismicNextImageProps> = ({

// A non-null assertion is required since we can't statically
// know if an alt attribute is available.

const resolvedAlt = (alt ?? (field.alt || fallbackAlt))!;

if (DEV && typeof resolvedAlt !== "string") {
Expand All @@ -146,16 +160,9 @@ export const PrismicNextImage: FC<PrismicNextImageProps> = ({
);
}

// TODO: Remove once https://github.com/vercel/next.js/issues/52216 is resolved.
// `next/image` seems to be affected by a default + named export bundling bug.
let ResolvedImage = Image;
if ("default" in ResolvedImage) {
ResolvedImage = (ResolvedImage as unknown as { default: typeof Image })
.default;
}

return (
<ResolvedImage
<Image
ref={ref}
src={src}
width={fill ? undefined : resolvedWidth}
height={fill ? undefined : resolvedHeight}
Expand All @@ -165,7 +172,5 @@ export const PrismicNextImage: FC<PrismicNextImageProps> = ({
{...restProps}
/>
);
} else {
return <>{fallback}</>;
}
};
},
);
11 changes: 11 additions & 0 deletions tests/PrismicNextImage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,14 @@ test("supports imgix parameters when using the default loader", async ({
"/_next/image?url=https%3A%2F%2Fimages.prismic.io%2Fprismicio-next-test%2FZ1evSZbqstJ98PkD_image.jpg%3Fauto%3Dformat%252Ccompress%26sat%3D-100&w=1920&q=75",
);
});

test.describe("ref", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/PrismicNextImage/client");
});

test("forwards ref", async ({ page }) => {
const link = page.getByTestId("ref");
await expect(link).toContainText("tagname: IMG");
});
});

0 comments on commit 2666f1f

Please sign in to comment.