Skip to content

Commit fc49e62

Browse files
committed
Allow passing props to every instance of next/image using parameters
1 parent 97dbc82 commit fc49e62

File tree

7 files changed

+124
-8
lines changed

7 files changed

+124
-8
lines changed

code/e2e-tests/framework-nextjs.spec.ts

+24
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ test.describe('Next.js', () => {
2121
await new SbPage(page).waitUntilLoaded();
2222
});
2323

24+
test.describe('next/image', () => {
25+
let sbPage: SbPage;
26+
27+
test.beforeEach(async ({ page }) => {
28+
sbPage = new SbPage(page);
29+
});
30+
31+
test('should lazy load images by default', async () => {
32+
await sbPage.navigateToStory('frameworks/nextjs/Image', 'lazy');
33+
34+
const img = sbPage.previewRoot().locator('img');
35+
36+
expect(await img.evaluate<boolean, HTMLImageElement>((image) => image.complete)).toBeFalsy();
37+
});
38+
39+
test('should eager load images when loading parameter is set to eager', async () => {
40+
await sbPage.navigateToStory('frameworks/nextjs/Image', 'eager');
41+
42+
const img = sbPage.previewRoot().locator('img');
43+
44+
expect(await img.evaluate<boolean, HTMLImageElement>((image) => image.complete)).toBeTruthy();
45+
});
46+
});
47+
2448
test.describe('next/navigation', () => {
2549
let root: Locator;
2650
let sbPage: SbPage;

code/frameworks/nextjs/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,16 @@ export default {
159159
framework: {
160160
name: '@storybook/nextjs',
161161
options: {
162+
image: {
163+
loading: 'eager',
164+
},
162165
nextConfigPath: path.resolve(__dirname, '../next.config.js'),
163166
},
164167
},
165168
};
166169
```
167170

171+
- `image`: Props to pass to every instance of `next/image`
168172
- `nextConfigPath`: The absolute path to the `next.config.js`
169173

170174
### Next.js's Image Component
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createContext } from 'react';
2+
import type { ImageProps, StaticImageData } from 'next/image';
3+
import type { ImageProps as LegacyImageProps } from 'next/legacy/image';
4+
5+
// StaticRequire needs to be in scope for the TypeScript compiler to work.
6+
// See: https://github.com/microsoft/TypeScript/issues/5711
7+
// Since next/image doesn't export StaticRequire we need to re-define it here and set src's type to it.
8+
interface StaticRequire {
9+
default: StaticImageData;
10+
}
11+
12+
declare type StaticImport = StaticRequire | StaticImageData;
13+
14+
export const ImageContext = createContext<
15+
Partial<Omit<ImageProps, 'src'> & { src: string | StaticImport }> & Omit<LegacyImageProps, 'src'>
16+
>({});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as React from 'react';
2+
import type { Addon_StoryContext } from '@storybook/types';
3+
import { ImageContext } from './context';
4+
5+
export const ImageDecorator = (
6+
Story: React.FC,
7+
{ parameters }: Addon_StoryContext
8+
): React.ReactNode => {
9+
if (!parameters.nextjs?.image) {
10+
return <Story />;
11+
}
12+
13+
return (
14+
<ImageContext.Provider value={parameters.nextjs.image}>
15+
<Story />
16+
</ImageContext.Provider>
17+
);
18+
};

code/frameworks/nextjs/src/images/next-image-stub.tsx

+28-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react';
33
import type * as _NextImage from 'next/image';
44
import type * as _NextLegacyImage from 'next/legacy/image';
55
import semver from 'semver';
6+
import { ImageContext } from './context';
67

78
const defaultLoader = ({ src, width, quality }: _NextImage.ImageLoaderProps) => {
89
const missingValues = [];
@@ -38,7 +39,11 @@ const OriginalNextImage = NextImage.default;
3839
Object.defineProperty(NextImage, 'default', {
3940
configurable: true,
4041
value: (props: _NextImage.ImageProps) => {
41-
return <OriginalNextImage {...props} loader={props.loader ?? defaultLoader} />;
42+
const imageParameters = React.useContext(ImageContext);
43+
44+
return (
45+
<OriginalNextImage {...props} loader={props.loader ?? defaultLoader} {...imageParameters} />
46+
);
4247
},
4348
});
4449

@@ -48,9 +53,17 @@ if (semver.satisfies(process.env.__NEXT_VERSION!, '^13.0.0')) {
4853

4954
Object.defineProperty(OriginalNextLegacyImage, 'default', {
5055
configurable: true,
51-
value: (props: _NextLegacyImage.ImageProps) => (
52-
<OriginalNextLegacyImage {...props} loader={props.loader ?? defaultLoader} />
53-
),
56+
value: (props: _NextLegacyImage.ImageProps) => {
57+
const imageParameters = React.useContext(ImageContext);
58+
59+
return (
60+
<OriginalNextLegacyImage
61+
{...props}
62+
loader={props.loader ?? defaultLoader}
63+
{...imageParameters}
64+
/>
65+
);
66+
},
5467
});
5568
}
5669

@@ -60,8 +73,16 @@ if (semver.satisfies(process.env.__NEXT_VERSION!, '^12.2.0')) {
6073

6174
Object.defineProperty(OriginalNextFutureImage, 'default', {
6275
configurable: true,
63-
value: (props: _NextImage.ImageProps) => (
64-
<OriginalNextFutureImage {...props} loader={props.loader ?? defaultLoader} />
65-
),
76+
value: (props: _NextImage.ImageProps) => {
77+
const imageParameters = React.useContext(ImageContext);
78+
79+
return (
80+
<OriginalNextFutureImage
81+
{...props}
82+
loader={props.loader ?? defaultLoader}
83+
{...imageParameters}
84+
/>
85+
);
86+
},
6687
});
6788
}

code/frameworks/nextjs/src/preview.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import './config/preview';
2+
import { ImageDecorator } from './images/decorator';
23
import { RouterDecorator } from './routing/decorator';
34
import { StyledJsxDecorator } from './styledJsx/decorator';
45
import './images/next-image-stub';
@@ -13,7 +14,12 @@ function addNextHeadCount() {
1314

1415
addNextHeadCount();
1516

16-
export const decorators = [StyledJsxDecorator, RouterDecorator, HeadManagerDecorator];
17+
export const decorators = [
18+
StyledJsxDecorator,
19+
ImageDecorator,
20+
RouterDecorator,
21+
HeadManagerDecorator,
22+
];
1723

1824
export const parameters = {
1925
docs: {

code/frameworks/nextjs/template/stories/Image.stories.jsx

+27
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,30 @@ export const Sized = {
4848
],
4949
},
5050
};
51+
52+
export const Lazy = {
53+
args: {
54+
src: 'https://storybook.js.org/images/placeholders/50x50.png',
55+
width: 50,
56+
height: 50,
57+
},
58+
decorators: [
59+
(Story) => (
60+
<>
61+
<div style={{ height: '200vh' }} />
62+
{Story()}
63+
</>
64+
),
65+
],
66+
};
67+
68+
export const Eager = {
69+
...Lazy,
70+
parameters: {
71+
nextjs: {
72+
image: {
73+
loading: 'eager',
74+
},
75+
},
76+
},
77+
};

0 commit comments

Comments
 (0)