bsmnt 0.11.0 → 0.12.0
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 +1 -1
- package/src/modules/features/cms/sanity/files/components/ui/sanity-image/index.tsx +36 -4
- package/src/modules/features/cms/sanity/files/lib/integrations/sanity/components/rich-text.tsx +7 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/sanity/rich-text.tsx +7 -1
- package/src/modules/features/cms/sanity-pagebuilder/files/components/ui/sanity-image/index.tsx +36 -4
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-input.tsx +26 -7
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +242 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +62 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/page-builder-config.ts +72 -34
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/components/studio-preview.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/schemas/index.ts +3 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/singletons.ts +2 -0
- package/src/modules/features/cms/sanity-pagebuilder/files/lib/integrations/sanity/structure.ts +5 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/sanity/rich-text.tsx +7 -1
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/components/ui/sanity-image/index.tsx +36 -4
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-input.tsx +26 -7
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/page-builder-insert-menu.tsx +242 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/components/use-studio-preview-urls.ts +62 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/page-builder-config.ts +72 -34
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/index.ts +1 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/components/studio-preview.ts +37 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/schemas/index.ts +3 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/singletons.ts +2 -0
- package/src/modules/features/cms/sanity-pagebuilder-cache/files/lib/integrations/sanity/structure.ts +5 -0
- package/src/templates/next-default/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-default/biome.json +2 -1
- package/src/templates/next-default/components/ui/image/index.tsx +20 -12
- package/src/templates/next-default/next.config.ts +10 -4
- package/src/templates/next-experiments/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-experiments/biome.json +2 -1
- package/src/templates/next-experiments/components/ui/image/index.tsx +20 -12
- package/src/templates/next-experiments/next.config.ts +10 -4
- package/src/templates/next-pagebuilder/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-pagebuilder/biome.json +2 -1
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/index.tsx +5 -0
- package/src/templates/next-pagebuilder/components/page-builder/components/article-content/related-post-item.tsx +1 -0
- package/src/templates/next-pagebuilder/components/ui/image/index.tsx +20 -12
- package/src/templates/next-pagebuilder/next.config.ts +4 -3
- package/src/templates/next-pagebuilder-cache/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-pagebuilder-cache/biome.json +2 -1
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/index.tsx +5 -0
- package/src/templates/next-pagebuilder-cache/components/page-builder/components/article-content/related-post-item.tsx +1 -0
- package/src/templates/next-pagebuilder-cache/components/ui/image/index.tsx +21 -12
- package/src/templates/next-pagebuilder-cache/lib/utils/image-sizes.ts +72 -0
- package/src/templates/next-pagebuilder-cache/next.config.ts +4 -3
- package/src/templates/next-webgl/.biome/plugins/no-fill-without-sizes.grit +11 -0
- package/src/templates/next-webgl/biome.json +2 -1
- package/src/templates/next-webgl/components/ui/image/index.tsx +20 -12
- package/src/templates/next-webgl/next.config.ts +10 -4
|
@@ -98,7 +98,6 @@ const getFinalPlaceholder = (
|
|
|
98
98
|
* Enhanced Image component with responsive sizing and automatic optimizations.
|
|
99
99
|
*
|
|
100
100
|
* Always use this component instead of next/image directly. Provides:
|
|
101
|
-
* - Automatic responsive sizes generation
|
|
102
101
|
* - Smart blur placeholders with aspect ratio support
|
|
103
102
|
* - Performance optimizations (lazy loading by default)
|
|
104
103
|
* - Priority flag for LCP images
|
|
@@ -147,14 +146,14 @@ export function Image({
|
|
|
147
146
|
style,
|
|
148
147
|
className,
|
|
149
148
|
objectFit = "cover",
|
|
150
|
-
quality =
|
|
149
|
+
quality = 75,
|
|
151
150
|
alt = "",
|
|
152
151
|
fill,
|
|
153
152
|
block = !fill,
|
|
154
|
-
width
|
|
155
|
-
height
|
|
156
|
-
mobileSize
|
|
157
|
-
desktopSize
|
|
153
|
+
width,
|
|
154
|
+
height,
|
|
155
|
+
mobileSize,
|
|
156
|
+
desktopSize,
|
|
158
157
|
sizes,
|
|
159
158
|
src,
|
|
160
159
|
unoptimized,
|
|
@@ -164,9 +163,18 @@ export function Image({
|
|
|
164
163
|
priority = false,
|
|
165
164
|
...props
|
|
166
165
|
}: ImageProps) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
const responsiveSizes =
|
|
167
|
+
mobileSize || desktopSize
|
|
168
|
+
? buildImageSizes({
|
|
169
|
+
base: mobileSize ?? "100vw",
|
|
170
|
+
desktop: desktopSize ?? mobileSize ?? "100vw",
|
|
171
|
+
})
|
|
172
|
+
: undefined
|
|
173
|
+
|
|
174
|
+
const hasIntrinsicSize = width !== undefined && height !== undefined
|
|
175
|
+
const finalSizes = sizes ?? responsiveSizes ?? (hasIntrinsicSize ? undefined : "100vw")
|
|
176
|
+
const finalWidth = width ?? (block ? 1 : undefined)
|
|
177
|
+
const finalHeight = height ?? (block ? 1 : undefined)
|
|
170
178
|
|
|
171
179
|
// Early return after hooks
|
|
172
180
|
if (!src) return null
|
|
@@ -194,8 +202,8 @@ export function Image({
|
|
|
194
202
|
<NextImage
|
|
195
203
|
ref={ref}
|
|
196
204
|
fill={!block}
|
|
197
|
-
{...(
|
|
198
|
-
{...(
|
|
205
|
+
{...(finalWidth !== undefined && { width: finalWidth })}
|
|
206
|
+
{...(finalHeight !== undefined && { height: finalHeight })}
|
|
199
207
|
priority={priority}
|
|
200
208
|
quality={quality}
|
|
201
209
|
alt={alt}
|
|
@@ -204,7 +212,7 @@ export function Image({
|
|
|
204
212
|
...style,
|
|
205
213
|
}}
|
|
206
214
|
className={cn(className, block && "block w-full")}
|
|
207
|
-
|
|
215
|
+
{...(finalSizes && { sizes: finalSizes })}
|
|
208
216
|
src={src}
|
|
209
217
|
unoptimized={unoptimized || isSvg}
|
|
210
218
|
draggable={false}
|
|
@@ -77,7 +77,6 @@ const nextConfig: NextConfig = {
|
|
|
77
77
|
],
|
|
78
78
|
},
|
|
79
79
|
images: {
|
|
80
|
-
dangerouslyAllowSVG: true,
|
|
81
80
|
remotePatterns: [
|
|
82
81
|
{
|
|
83
82
|
protocol: "https",
|
|
@@ -85,9 +84,11 @@ const nextConfig: NextConfig = {
|
|
|
85
84
|
},
|
|
86
85
|
],
|
|
87
86
|
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
|
|
88
|
-
qualities: [90],
|
|
87
|
+
qualities: [75, 90, 100],
|
|
89
88
|
formats: ["image/avif", "image/webp"],
|
|
90
|
-
deviceSizes: [
|
|
89
|
+
deviceSizes: [
|
|
90
|
+
640, 750, 828, 1080, 1200, 1320, 1620, 1920, 2048, 2560, 3024, 3456, 3840,
|
|
91
|
+
],
|
|
91
92
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
92
93
|
},
|
|
93
94
|
headers: async () => [
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`<Image $attrs />`,
|
|
5
|
+
`<SanityImage $attrs />`
|
|
6
|
+
} as $image where {
|
|
7
|
+
$attrs <: contains `fill`,
|
|
8
|
+
! $attrs <: contains `fill={false}`,
|
|
9
|
+
! $attrs <: contains or { `sizes`, `mobileSize`, `desktopSize` },
|
|
10
|
+
register_diagnostic(span=$image, message="A `fill` image has no intrinsic width, so without `sizes` the browser assumes 100vw and requests the largest candidate. Pass `sizes`, or `mobileSize`/`desktopSize`.", severity="error")
|
|
11
|
+
}
|
|
@@ -247,7 +247,8 @@
|
|
|
247
247
|
"plugins": [
|
|
248
248
|
"./.biome/plugins/no-anchor-element.grit",
|
|
249
249
|
"./.biome/plugins/no-unnecessary-forwardref.grit",
|
|
250
|
-
"./.biome/plugins/no-relative-parent-imports.grit"
|
|
250
|
+
"./.biome/plugins/no-relative-parent-imports.grit",
|
|
251
|
+
"./.biome/plugins/no-fill-without-sizes.grit"
|
|
251
252
|
],
|
|
252
253
|
"vcs": {
|
|
253
254
|
"clientKind": "git",
|
|
@@ -28,6 +28,9 @@ export function ArticleContent({ block }: ArticleContentProps) {
|
|
|
28
28
|
<SanityImage
|
|
29
29
|
image={author.avatar}
|
|
30
30
|
alt={author.name || ""}
|
|
31
|
+
width={40}
|
|
32
|
+
height={40}
|
|
33
|
+
maxWidth={80}
|
|
31
34
|
className="size-10 rounded-full object-cover"
|
|
32
35
|
/>
|
|
33
36
|
) : null}
|
|
@@ -50,6 +53,8 @@ export function ArticleContent({ block }: ArticleContentProps) {
|
|
|
50
53
|
image={thumbnail}
|
|
51
54
|
alt={thumbnail.alt || ""}
|
|
52
55
|
priority
|
|
56
|
+
quality={90}
|
|
57
|
+
sizes="(min-width: 896px) 896px, 100vw"
|
|
53
58
|
className="aspect-video w-full object-cover"
|
|
54
59
|
/>
|
|
55
60
|
</div>
|
|
@@ -14,6 +14,7 @@ export const RelatedPostItem = ({ post }: { post: ArticleRelatedPost }) => {
|
|
|
14
14
|
<SanityImage
|
|
15
15
|
image={post.metadata.image}
|
|
16
16
|
alt={post.title || ""}
|
|
17
|
+
sizes="(min-width: 768px) 768px, 100vw"
|
|
17
18
|
className="aspect-[16/9] w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
18
19
|
/>
|
|
19
20
|
</div>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import cn from "clsx"
|
|
10
10
|
import NextImage, { type ImageProps as NextImageProps } from "next/image"
|
|
11
11
|
import type { CSSProperties, Ref } from "react"
|
|
12
|
+
import { buildImageSizes } from "@/lib/utils/image-sizes"
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Enhanced Image component props extending Next.js Image.
|
|
@@ -97,7 +98,6 @@ const getFinalPlaceholder = (
|
|
|
97
98
|
* Enhanced Image component with responsive sizing and automatic optimizations.
|
|
98
99
|
*
|
|
99
100
|
* Always use this component instead of next/image directly. Provides:
|
|
100
|
-
* - Automatic responsive sizes generation
|
|
101
101
|
* - Smart blur placeholders with aspect ratio support
|
|
102
102
|
* - Performance optimizations (lazy loading by default)
|
|
103
103
|
* - Priority flag for LCP images
|
|
@@ -146,14 +146,14 @@ export function Image({
|
|
|
146
146
|
style,
|
|
147
147
|
className,
|
|
148
148
|
objectFit = "cover",
|
|
149
|
-
quality =
|
|
149
|
+
quality = 75,
|
|
150
150
|
alt = "",
|
|
151
151
|
fill,
|
|
152
152
|
block = !fill,
|
|
153
|
-
width
|
|
154
|
-
height
|
|
155
|
-
mobileSize
|
|
156
|
-
desktopSize
|
|
153
|
+
width,
|
|
154
|
+
height,
|
|
155
|
+
mobileSize,
|
|
156
|
+
desktopSize,
|
|
157
157
|
sizes,
|
|
158
158
|
src,
|
|
159
159
|
unoptimized,
|
|
@@ -163,9 +163,18 @@ export function Image({
|
|
|
163
163
|
priority = false,
|
|
164
164
|
...props
|
|
165
165
|
}: ImageProps) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
const responsiveSizes =
|
|
167
|
+
mobileSize || desktopSize
|
|
168
|
+
? buildImageSizes({
|
|
169
|
+
base: mobileSize ?? "100vw",
|
|
170
|
+
desktop: desktopSize ?? mobileSize ?? "100vw",
|
|
171
|
+
})
|
|
172
|
+
: undefined
|
|
173
|
+
|
|
174
|
+
const hasIntrinsicSize = width !== undefined && height !== undefined
|
|
175
|
+
const finalSizes = sizes ?? responsiveSizes ?? (hasIntrinsicSize ? undefined : "100vw")
|
|
176
|
+
const finalWidth = width ?? (block ? 1 : undefined)
|
|
177
|
+
const finalHeight = height ?? (block ? 1 : undefined)
|
|
169
178
|
|
|
170
179
|
// Early return after hooks
|
|
171
180
|
if (!src) return null
|
|
@@ -193,8 +202,8 @@ export function Image({
|
|
|
193
202
|
<NextImage
|
|
194
203
|
ref={ref}
|
|
195
204
|
fill={!block}
|
|
196
|
-
{...(
|
|
197
|
-
{...(
|
|
205
|
+
{...(finalWidth !== undefined && { width: finalWidth })}
|
|
206
|
+
{...(finalHeight !== undefined && { height: finalHeight })}
|
|
198
207
|
priority={priority}
|
|
199
208
|
quality={quality}
|
|
200
209
|
alt={alt}
|
|
@@ -203,7 +212,7 @@ export function Image({
|
|
|
203
212
|
...style,
|
|
204
213
|
}}
|
|
205
214
|
className={cn(className, block && "block w-full")}
|
|
206
|
-
|
|
215
|
+
{...(finalSizes && { sizes: finalSizes })}
|
|
207
216
|
src={src}
|
|
208
217
|
unoptimized={unoptimized || isSvg}
|
|
209
218
|
draggable={false}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Mirrors the theme's `--breakpoint-*` scale, which clears Tailwind's defaults. */
|
|
2
|
+
export const BASEMENT_BREAKPOINTS = {
|
|
3
|
+
mobile: 640,
|
|
4
|
+
tablet: 768,
|
|
5
|
+
"tablet-lg": 1024,
|
|
6
|
+
desktop: 1440,
|
|
7
|
+
"desktop-large": 1920,
|
|
8
|
+
} as const
|
|
9
|
+
|
|
10
|
+
export type BreakpointMap = Readonly<Record<string, number>>
|
|
11
|
+
|
|
12
|
+
/** Mirrors the element's variants: `grid-cols-1 tablet:grid-cols-2` → `{ base: 1, tablet: 2 }`. */
|
|
13
|
+
export type ByBreakpoint<
|
|
14
|
+
T,
|
|
15
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
16
|
+
> = { base: T } & Partial<Record<Extract<keyof B, string>, T>>
|
|
17
|
+
|
|
18
|
+
function columnWidth(columns: number): string {
|
|
19
|
+
return `${Math.round(100 / columns)}vw`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Widest first, since `sizes` takes the first matching condition and the last
|
|
24
|
+
* entry must be bare. `min-width` matches the Tailwind variant's boundary; a
|
|
25
|
+
* `max-width: <next> - 1` one would leave a fractional-pixel gap.
|
|
26
|
+
*/
|
|
27
|
+
function toSizes(
|
|
28
|
+
base: string,
|
|
29
|
+
overrides: Readonly<Record<string, string | undefined>>,
|
|
30
|
+
scale: BreakpointMap
|
|
31
|
+
): string {
|
|
32
|
+
const conditions: string[] = []
|
|
33
|
+
|
|
34
|
+
for (const [name, min] of Object.entries(scale).sort(
|
|
35
|
+
([, a], [, b]) => b - a
|
|
36
|
+
)) {
|
|
37
|
+
const value = overrides[name]
|
|
38
|
+
if (value !== undefined) {
|
|
39
|
+
conditions.push(`(min-width: ${min}px) ${value}`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [...conditions, base].join(", ")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Builds `sizes` from the CSS length the image renders at per breakpoint. */
|
|
47
|
+
export function buildImageSizes<
|
|
48
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
49
|
+
>(widths: ByBreakpoint<string, B>, breakpoints?: B): string {
|
|
50
|
+
const { base, ...overrides } = widths
|
|
51
|
+
return toSizes(base, overrides, breakpoints ?? BASEMENT_BREAKPOINTS)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Builds `sizes` from a grid's column count per breakpoint. Rounded: `sizes`
|
|
56
|
+
* only steers which srcset candidate the browser picks, and gaps make the real
|
|
57
|
+
* column narrower than `100 / N` anyway.
|
|
58
|
+
*/
|
|
59
|
+
export function buildGridImageSizes<
|
|
60
|
+
B extends BreakpointMap = typeof BASEMENT_BREAKPOINTS,
|
|
61
|
+
>(columns: ByBreakpoint<number, B>, breakpoints?: B): string {
|
|
62
|
+
const { base, ...overrides } = columns
|
|
63
|
+
const widths: Record<string, string> = {}
|
|
64
|
+
|
|
65
|
+
for (const [name, count] of Object.entries(overrides)) {
|
|
66
|
+
if (count !== undefined) {
|
|
67
|
+
widths[name] = columnWidth(count)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return toSizes(columnWidth(base), widths, breakpoints ?? BASEMENT_BREAKPOINTS)
|
|
72
|
+
}
|
|
@@ -82,7 +82,6 @@ const nextConfig: NextConfig = {
|
|
|
82
82
|
],
|
|
83
83
|
},
|
|
84
84
|
images: {
|
|
85
|
-
dangerouslyAllowSVG: true,
|
|
86
85
|
remotePatterns: [
|
|
87
86
|
{
|
|
88
87
|
protocol: "https",
|
|
@@ -90,9 +89,11 @@ const nextConfig: NextConfig = {
|
|
|
90
89
|
},
|
|
91
90
|
],
|
|
92
91
|
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
|
|
93
|
-
qualities: [90],
|
|
92
|
+
qualities: [75, 90, 100],
|
|
94
93
|
formats: ["image/avif", "image/webp"],
|
|
95
|
-
deviceSizes: [
|
|
94
|
+
deviceSizes: [
|
|
95
|
+
640, 750, 828, 1080, 1200, 1320, 1620, 1920, 2048, 2560, 3024, 3456, 3840,
|
|
96
|
+
],
|
|
96
97
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
97
98
|
},
|
|
98
99
|
headers: async () => [
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`<Image $attrs />`,
|
|
5
|
+
`<SanityImage $attrs />`
|
|
6
|
+
} as $image where {
|
|
7
|
+
$attrs <: contains `fill`,
|
|
8
|
+
! $attrs <: contains `fill={false}`,
|
|
9
|
+
! $attrs <: contains or { `sizes`, `mobileSize`, `desktopSize` },
|
|
10
|
+
register_diagnostic(span=$image, message="A `fill` image has no intrinsic width, so without `sizes` the browser assumes 100vw and requests the largest candidate. Pass `sizes`, or `mobileSize`/`desktopSize`.", severity="error")
|
|
11
|
+
}
|
|
@@ -234,7 +234,8 @@
|
|
|
234
234
|
"plugins": [
|
|
235
235
|
"./.biome/plugins/no-anchor-element.grit",
|
|
236
236
|
"./.biome/plugins/no-unnecessary-forwardref.grit",
|
|
237
|
-
"./.biome/plugins/no-relative-parent-imports.grit"
|
|
237
|
+
"./.biome/plugins/no-relative-parent-imports.grit",
|
|
238
|
+
"./.biome/plugins/no-fill-without-sizes.grit"
|
|
238
239
|
],
|
|
239
240
|
"vcs": {
|
|
240
241
|
"clientKind": "git",
|
|
@@ -98,7 +98,6 @@ const getFinalPlaceholder = (
|
|
|
98
98
|
* Enhanced Image component with responsive sizing and automatic optimizations.
|
|
99
99
|
*
|
|
100
100
|
* Always use this component instead of next/image directly. Provides:
|
|
101
|
-
* - Automatic responsive sizes generation
|
|
102
101
|
* - Smart blur placeholders with aspect ratio support
|
|
103
102
|
* - Performance optimizations (lazy loading by default)
|
|
104
103
|
* - Priority flag for LCP images
|
|
@@ -147,14 +146,14 @@ export function Image({
|
|
|
147
146
|
style,
|
|
148
147
|
className,
|
|
149
148
|
objectFit = "cover",
|
|
150
|
-
quality =
|
|
149
|
+
quality = 75,
|
|
151
150
|
alt = "",
|
|
152
151
|
fill,
|
|
153
152
|
block = !fill,
|
|
154
|
-
width
|
|
155
|
-
height
|
|
156
|
-
mobileSize
|
|
157
|
-
desktopSize
|
|
153
|
+
width,
|
|
154
|
+
height,
|
|
155
|
+
mobileSize,
|
|
156
|
+
desktopSize,
|
|
158
157
|
sizes,
|
|
159
158
|
src,
|
|
160
159
|
unoptimized,
|
|
@@ -164,9 +163,18 @@ export function Image({
|
|
|
164
163
|
priority = false,
|
|
165
164
|
...props
|
|
166
165
|
}: ImageProps) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
const responsiveSizes =
|
|
167
|
+
mobileSize || desktopSize
|
|
168
|
+
? buildImageSizes({
|
|
169
|
+
base: mobileSize ?? "100vw",
|
|
170
|
+
desktop: desktopSize ?? mobileSize ?? "100vw",
|
|
171
|
+
})
|
|
172
|
+
: undefined
|
|
173
|
+
|
|
174
|
+
const hasIntrinsicSize = width !== undefined && height !== undefined
|
|
175
|
+
const finalSizes = sizes ?? responsiveSizes ?? (hasIntrinsicSize ? undefined : "100vw")
|
|
176
|
+
const finalWidth = width ?? (block ? 1 : undefined)
|
|
177
|
+
const finalHeight = height ?? (block ? 1 : undefined)
|
|
170
178
|
|
|
171
179
|
// Early return after hooks
|
|
172
180
|
if (!src) return null
|
|
@@ -194,8 +202,8 @@ export function Image({
|
|
|
194
202
|
<NextImage
|
|
195
203
|
ref={ref}
|
|
196
204
|
fill={!block}
|
|
197
|
-
{...(
|
|
198
|
-
{...(
|
|
205
|
+
{...(finalWidth !== undefined && { width: finalWidth })}
|
|
206
|
+
{...(finalHeight !== undefined && { height: finalHeight })}
|
|
199
207
|
priority={priority}
|
|
200
208
|
quality={quality}
|
|
201
209
|
alt={alt}
|
|
@@ -204,7 +212,7 @@ export function Image({
|
|
|
204
212
|
...style,
|
|
205
213
|
}}
|
|
206
214
|
className={cn(className, block && "block w-full")}
|
|
207
|
-
|
|
215
|
+
{...(finalSizes && { sizes: finalSizes })}
|
|
208
216
|
src={src}
|
|
209
217
|
unoptimized={unoptimized || isSvg}
|
|
210
218
|
draggable={false}
|
|
@@ -77,12 +77,18 @@ const nextConfig: NextConfig = {
|
|
|
77
77
|
],
|
|
78
78
|
},
|
|
79
79
|
images: {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
remotePatterns: [
|
|
81
|
+
{
|
|
82
|
+
protocol: "https",
|
|
83
|
+
hostname: "cdn.sanity.io",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
82
86
|
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
|
|
83
|
-
qualities: [90],
|
|
87
|
+
qualities: [75, 90, 100],
|
|
84
88
|
formats: ["image/avif", "image/webp"],
|
|
85
|
-
deviceSizes: [
|
|
89
|
+
deviceSizes: [
|
|
90
|
+
640, 750, 828, 1080, 1200, 1320, 1620, 1920, 2048, 2560, 3024, 3456, 3840,
|
|
91
|
+
],
|
|
86
92
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
87
93
|
},
|
|
88
94
|
headers: async () => [
|