Skip to content

Commit b860887

Browse files
Try using responsive images in dataview grid layouts (#70493)
* Try using responsive images in dataview grid layouts * Move determination of sizes to dataviews * Simplify grid logic and sizes * use range of image widths for grid size picker * Make picker discrete and improve breakpoints * remove unused prop * ref no worky again * smol improvement * Update to use an object instead of sizes string and fix grid size at max * Adjust table max img width * Change `mediaAppearance` to the more generic `config` * Change `size` to `sizes` * Explain magic number in comment.
1 parent b679fe7 commit b860887

File tree

9 files changed

+133
-126
lines changed

9 files changed

+133
-126
lines changed

packages/dataviews/src/components/dataviews-context/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type DataViewsContextType< Item > = {
4747
isItemClickable: ( item: Item ) => boolean;
4848
containerWidth: number;
4949
containerRef: React.MutableRefObject< HTMLDivElement | null >;
50+
resizeObserverRef:
51+
| ( ( element?: HTMLDivElement | null ) => void )
52+
| React.RefObject< HTMLDivElement >;
5053
defaultLayouts: SupportedLayouts;
5154
filters: NormalizedFilter[];
5255
isShowingFilter: boolean;
@@ -72,6 +75,7 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( {
7275
renderItemLink: undefined,
7376
containerWidth: 0,
7477
containerRef: createRef(),
78+
resizeObserverRef: () => {},
7579
defaultLayouts: { list: {}, grid: {}, table: {} },
7680
filters: [],
7781
isShowingFilter: false,

packages/dataviews/src/components/dataviews/index.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { ReactNode, ComponentProps, ReactElement } from 'react';
88
*/
99
import { __experimentalHStack as HStack } from '@wordpress/components';
1010
import { useContext, useMemo, useRef, useState } from '@wordpress/element';
11-
import { useMergeRefs, useResizeObserver } from '@wordpress/compose';
11+
import { useResizeObserver } from '@wordpress/compose';
1212

1313
/**
1414
* Internal dependencies
@@ -193,17 +193,15 @@ function DataViews< Item >( {
193193
renderItemLink,
194194
containerWidth,
195195
containerRef,
196+
resizeObserverRef,
196197
defaultLayouts,
197198
filters,
198199
isShowingFilter,
199200
setIsShowingFilter,
200201
perPageSizes,
201202
} }
202203
>
203-
<div
204-
className="dataviews-wrapper"
205-
ref={ useMergeRefs( [ containerRef, resizeObserverRef ] ) }
206-
>
204+
<div className="dataviews-wrapper" ref={ containerRef }>
207205
{ children ?? (
208206
<DefaultUI
209207
header={ header }

packages/dataviews/src/dataviews-layouts/grid/index.tsx

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { ComponentProps, ReactElement } from 'react';
88
* WordPress dependencies
99
*/
1010
import {
11-
__experimentalGrid as Grid,
1211
__experimentalHStack as HStack,
1312
__experimentalVStack as VStack,
1413
Spinner,
@@ -19,13 +18,15 @@ import {
1918
import { __, sprintf } from '@wordpress/i18n';
2019
import { useInstanceId } from '@wordpress/compose';
2120
import { isAppleOS } from '@wordpress/keycodes';
21+
import { useContext } from '@wordpress/element';
2222

2323
/**
2424
* Internal dependencies
2525
*/
2626
import { unlock } from '../../lock-unlock';
2727
import ItemActions from '../../components/dataviews-item-actions';
2828
import DataViewsSelectionCheckbox from '../../components/dataviews-selection-checkbox';
29+
import DataViewsContext from '../../components/dataviews-context';
2930
import {
3031
useHasAPossibleBulkAction,
3132
useSomeItemHasAPossibleBulkAction,
@@ -38,7 +39,6 @@ import type {
3839
} from '../../types';
3940
import type { SetSelection } from '../../private-types';
4041
import { ItemClickWrapper } from '../utils/item-click-wrapper';
41-
import { useUpdatedPreviewSizeOnViewportChange } from './preview-size-picker';
4242
const { Badge } = unlock( componentsPrivateApis );
4343

4444
interface GridItemProps< Item > {
@@ -61,6 +61,9 @@ interface GridItemProps< Item > {
6161
regularFields: NormalizedField< Item >[];
6262
badgeFields: NormalizedField< Item >[];
6363
hasBulkActions: boolean;
64+
config: {
65+
sizes: string;
66+
};
6467
}
6568

6669
function GridItem< Item >( {
@@ -79,14 +82,19 @@ function GridItem< Item >( {
7982
regularFields,
8083
badgeFields,
8184
hasBulkActions,
85+
config,
8286
}: GridItemProps< Item > ) {
8387
const { showTitle = true, showMedia = true, showDescription = true } = view;
8488
const hasBulkAction = useHasAPossibleBulkAction( actions, item );
8589
const id = getItemId( item );
8690
const instanceId = useInstanceId( GridItem );
8791
const isSelected = selection.includes( id );
8892
const renderedMediaField = mediaField?.render ? (
89-
<mediaField.render item={ item } field={ mediaField } />
93+
<mediaField.render
94+
item={ item }
95+
field={ mediaField }
96+
config={ config }
97+
/>
9098
) : null;
9199
const renderedTitleField =
92100
showTitle && titleField?.render ? (
@@ -256,6 +264,7 @@ function ViewGrid< Item >( {
256264
view,
257265
className,
258266
}: ViewGridProps< Item > ) {
267+
const { resizeObserverRef } = useContext( DataViewsContext );
259268
const titleField = fields.find(
260269
( field ) => field.id === view?.titleField
261270
);
@@ -286,14 +295,15 @@ function ViewGrid< Item >( {
286295
{ regularFields: [], badgeFields: [] }
287296
);
288297
const hasData = !! data?.length;
289-
const updatedPreviewSize = useUpdatedPreviewSizeOnViewportChange();
290298
const hasBulkActions = useSomeItemHasAPossibleBulkAction( actions, data );
291-
const usedPreviewSize = updatedPreviewSize || view.layout?.previewSize;
292-
const gridStyle = usedPreviewSize
293-
? {
294-
gridTemplateColumns: `repeat(${ usedPreviewSize }, minmax(0, 1fr))`,
295-
}
296-
: {};
299+
const usedPreviewSize = view.layout?.previewSize;
300+
/*
301+
* This is the maximum width that an image can achieve in the grid. The reasoning is:
302+
* The biggest min image width available is 430px (see /dataviews-layouts/grid/preview-size-picker.tsx).
303+
* Because the grid is responsive, once there is room for another column, the images shrink to accommodate it.
304+
* So each image will never grow past 2*430px plus a little more to account for the gaps.
305+
*/
306+
const size = '900px';
297307

298308
const groupField = view.groupByField
299309
? fields.find( ( f ) => f.id === view.groupByField )
@@ -328,16 +338,18 @@ function ViewGrid< Item >( {
328338
groupName
329339
) }
330340
</h3>
331-
<Grid
332-
gap={ 8 }
333-
columns={ 2 }
334-
alignment="top"
341+
<div
335342
className={ clsx(
336343
'dataviews-view-grid',
337344
className
338345
) }
339-
style={ gridStyle }
346+
style={ {
347+
gridTemplateColumns:
348+
usedPreviewSize &&
349+
`repeat(auto-fill, minmax(${ usedPreviewSize }px, 1fr))`,
350+
} }
340351
aria-busy={ isLoading }
352+
ref={ resizeObserverRef }
341353
>
342354
{ groupItems.map( ( item ) => {
343355
return (
@@ -370,10 +382,13 @@ function ViewGrid< Item >( {
370382
hasBulkActions={
371383
hasBulkActions
372384
}
385+
config={ {
386+
sizes: size,
387+
} }
373388
/>
374389
);
375390
} ) }
376-
</Grid>
391+
</div>
377392
</VStack>
378393
)
379394
) }
@@ -384,13 +399,15 @@ function ViewGrid< Item >( {
384399
{
385400
// Render a single grid with all data.
386401
hasData && ! dataByGroup && (
387-
<Grid
388-
gap={ 8 }
389-
columns={ 2 }
390-
alignment="top"
402+
<div
391403
className={ clsx( 'dataviews-view-grid', className ) }
392-
style={ gridStyle }
404+
style={ {
405+
gridTemplateColumns:
406+
usedPreviewSize &&
407+
`repeat(auto-fill, minmax(${ usedPreviewSize }px, 1fr))`,
408+
} }
393409
aria-busy={ isLoading }
410+
ref={ resizeObserverRef }
394411
>
395412
{ data.map( ( item ) => {
396413
return (
@@ -411,10 +428,13 @@ function ViewGrid< Item >( {
411428
regularFields={ regularFields }
412429
badgeFields={ badgeFields }
413430
hasBulkActions={ hasBulkActions }
431+
config={ {
432+
sizes: size,
433+
} }
414434
/>
415435
);
416436
} ) }
417-
</Grid>
437+
</div>
418438
)
419439
}
420440
{

packages/dataviews/src/dataviews-layouts/grid/preview-size-picker.tsx

Lines changed: 48 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -3,108 +3,83 @@
33
*/
44
import { RangeControl } from '@wordpress/components';
55
import { __ } from '@wordpress/i18n';
6-
import { useMemo, useContext } from '@wordpress/element';
6+
import { useContext } from '@wordpress/element';
77

88
/**
99
* Internal dependencies
1010
*/
1111
import DataViewsContext from '../../components/dataviews-context';
1212
import type { ViewGrid } from '../../types';
1313

14-
const viewportBreaks: {
15-
[ key: string ]: { min: number; max: number; default: number };
16-
} = {
17-
xhuge: { min: 3, max: 6, default: 5 },
18-
huge: { min: 2, max: 4, default: 4 },
19-
xlarge: { min: 2, max: 3, default: 3 },
20-
large: { min: 1, max: 2, default: 2 },
21-
mobile: { min: 1, max: 2, default: 2 },
22-
};
23-
24-
/**
25-
* Breakpoints were adjusted from media queries breakpoints to account for
26-
* the sidebar width. This was done to match the existing styles we had.
27-
*/
28-
const BREAKPOINTS = {
29-
xhuge: 1520,
30-
huge: 1140,
31-
xlarge: 780,
32-
large: 480,
33-
mobile: 0,
34-
};
35-
36-
function useViewPortBreakpoint() {
37-
const containerWidth = useContext( DataViewsContext ).containerWidth;
38-
for ( const [ key, value ] of Object.entries( BREAKPOINTS ) ) {
39-
if ( containerWidth >= value ) {
40-
return key;
41-
}
42-
}
43-
return 'mobile';
44-
}
45-
46-
export function useUpdatedPreviewSizeOnViewportChange() {
47-
const view = useContext( DataViewsContext ).view as ViewGrid;
48-
const viewport = useViewPortBreakpoint();
49-
return useMemo( () => {
50-
const previewSize = view.layout?.previewSize;
51-
let newPreviewSize;
52-
if ( ! previewSize ) {
53-
return;
54-
}
55-
const breakValues = viewportBreaks[ viewport ];
56-
if ( previewSize < breakValues.min ) {
57-
newPreviewSize = breakValues.min;
58-
}
59-
if ( previewSize > breakValues.max ) {
60-
newPreviewSize = breakValues.max;
61-
}
62-
return newPreviewSize;
63-
}, [ viewport, view ] );
64-
}
14+
const imageSizes = [
15+
{
16+
value: 230,
17+
breakpoint: 1,
18+
},
19+
{
20+
value: 290,
21+
breakpoint: 1112, // at minimum image width, 4 images display at this container size
22+
},
23+
{
24+
value: 350,
25+
breakpoint: 1636, // at minimum image width, 6 images display at this container size
26+
},
27+
{
28+
value: 430,
29+
breakpoint: 588, // at minimum image width, 2 images display at this container size
30+
},
31+
];
6532

6633
export default function PreviewSizePicker() {
67-
const viewport = useViewPortBreakpoint();
6834
const context = useContext( DataViewsContext );
6935
const view = context.view as ViewGrid;
70-
const breakValues = viewportBreaks[ viewport ];
71-
const previewSizeToUse = view.layout?.previewSize || breakValues.default;
72-
const marks = useMemo(
73-
() =>
74-
Array.from(
75-
{ length: breakValues.max - breakValues.min + 1 },
76-
( _, i ) => {
77-
return {
78-
value: breakValues.min + i,
79-
};
80-
}
81-
),
82-
[ breakValues ]
83-
);
84-
if ( viewport === 'mobile' ) {
36+
37+
if ( context.containerWidth < 588 ) {
8538
return null;
8639
}
40+
41+
const breakValues = imageSizes.filter( ( size ) => {
42+
return context.containerWidth >= size.breakpoint;
43+
} );
44+
45+
// If the container has resized and the set preview size is no longer available,
46+
// we reset it to the next smallest size.
47+
const previewSizeToUse = view.layout?.previewSize
48+
? breakValues
49+
.map( ( size, index ) => ( { ...size, index } ) )
50+
.filter(
51+
( size ) => size.value <= ( view.layout?.previewSize ?? 0 ) // We know the view.layout?.previewSize exists at this point but the linter doesn't seem to.
52+
)
53+
.sort( ( a, b ) => b.value - a.value )[ 0 ].index
54+
: 0;
55+
56+
const marks = breakValues.map( ( size, index ) => {
57+
return {
58+
value: index,
59+
};
60+
} );
61+
8762
return (
8863
<RangeControl
8964
__nextHasNoMarginBottom
9065
__next40pxDefaultSize
9166
showTooltip={ false }
9267
label={ __( 'Preview size' ) }
93-
value={ breakValues.max + breakValues.min - previewSizeToUse }
94-
marks={ marks }
95-
min={ breakValues.min }
96-
max={ breakValues.max }
68+
value={ previewSizeToUse }
69+
min={ 0 }
70+
max={ breakValues.length - 1 }
9771
withInputField={ false }
9872
onChange={ ( value = 0 ) => {
9973
context.onChangeView( {
10074
...view,
10175
layout: {
10276
...view.layout,
103-
previewSize: breakValues.max + breakValues.min - value,
77+
previewSize: breakValues[ value ].value,
10478
},
10579
} );
10680
} }
10781
step={ 1 }
82+
marks={ marks }
10883
/>
10984
);
11085
}

0 commit comments

Comments
 (0)