Skip to content

Commit daa5639

Browse files
jorgefilipecostajameskosterstokesmant-hamanocolorful-tones
authored andcommitted
Update: Grid layout: Allow users to adjust grid density (#63367)
Co-authored-by: jorgefilipecosta <[email protected]> Co-authored-by: jameskoster <[email protected]> Co-authored-by: stokesman <[email protected]> Co-authored-by: t-hamano <[email protected]> Co-authored-by: colorful-tones <[email protected]>
1 parent 9d25bb7 commit daa5639

File tree

8 files changed

+183
-13
lines changed

8 files changed

+183
-13
lines changed

packages/compose/src/hooks/use-viewport-match/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createContext, useContext } from '@wordpress/element';
99
import useMediaQuery from '../use-media-query';
1010

1111
/**
12-
* @typedef {"huge" | "wide" | "large" | "medium" | "small" | "mobile"} WPBreakpoint
12+
* @typedef {"xhuge" | "huge" | "wide" | "xlarge" | "large" | "medium" | "small" | "mobile"} WPBreakpoint
1313
*/
1414

1515
/**
@@ -20,8 +20,10 @@ import useMediaQuery from '../use-media-query';
2020
* @type {Record<WPBreakpoint, number>}
2121
*/
2222
const BREAKPOINTS = {
23+
xhuge: 1920,
2324
huge: 1440,
2425
wide: 1280,
26+
xlarge: 1080,
2527
large: 960,
2628
medium: 782,
2729
small: 600,

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

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type DataViewsContextType< Item > = {
2626
openedFilter: string | null;
2727
setOpenedFilter: ( openedFilter: string | null ) => void;
2828
getItemId: ( item: Item ) => string;
29+
density: number;
2930
};
3031

3132
const DataViewsContext = createContext< DataViewsContextType< any > >( {
@@ -42,6 +43,7 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( {
4243
setOpenedFilter: () => {},
4344
openedFilter: null,
4445
getItemId: ( item ) => item.id,
46+
density: 0,
4547
} );
4648

4749
export default DataViewsContext;

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

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export default function DataViewsLayout() {
2727
selection,
2828
onChangeSelection,
2929
setOpenedFilter,
30+
density,
3031
} = useContext( DataViewsContext );
3132

3233
const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type )
@@ -44,6 +45,7 @@ export default function DataViewsLayout() {
4445
selection={ selection }
4546
setOpenedFilter={ setOpenedFilter }
4647
view={ view }
48+
density={ density }
4749
/>
4850
);
4951
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import DataViewsViewConfig from '../dataviews-view-config';
1818
import { normalizeFields } from '../../normalize-fields';
1919
import type { Action, Field, View, SupportedLayouts } from '../../types';
2020
import type { SelectionOrUpdater } from '../../private-types';
21+
import DensityPicker from '../../layouts/grid/density-picker';
22+
import { LAYOUT_GRID } from '../../constants';
2123

2224
type ItemWithId = { id: string };
2325

@@ -59,6 +61,7 @@ export default function DataViews< Item >( {
5961
onChangeSelection,
6062
}: DataViewsProps< Item > ) {
6163
const [ selectionState, setSelectionState ] = useState< string[] >( [] );
64+
const [ density, setDensity ] = useState< number >( 0 );
6265
const isUncontrolled =
6366
selectionProperty === undefined || onChangeSelection === undefined;
6467
const selection = isUncontrolled ? selectionState : selectionProperty;
@@ -95,6 +98,7 @@ export default function DataViews< Item >( {
9598
openedFilter,
9699
setOpenedFilter,
97100
getItemId,
101+
density,
98102
} }
99103
>
100104
<div className="dataviews-wrapper">
@@ -111,6 +115,12 @@ export default function DataViews< Item >( {
111115
{ search && <DataViewsSearch label={ searchLabel } /> }
112116
<DataViewsFilters />
113117
</HStack>
118+
{ view.type === LAYOUT_GRID && (
119+
<DensityPicker
120+
density={ density }
121+
setDensity={ setDensity }
122+
/>
123+
) }
114124
<DataViewsBulkActions />
115125
<DataViewsViewConfig defaultLayouts={ defaultLayouts } />
116126
</HStack>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { RangeControl, Button } from '@wordpress/components';
5+
import { __ } from '@wordpress/i18n';
6+
import { useViewportMatch } from '@wordpress/compose';
7+
import { plus, lineSolid } from '@wordpress/icons';
8+
import { useEffect } from '@wordpress/element';
9+
10+
const viewportBreaks = {
11+
xhuge: { min: 3, max: 6, default: 5 },
12+
huge: { min: 2, max: 4, default: 4 },
13+
xlarge: { min: 2, max: 3, default: 3 },
14+
large: { min: 1, max: 2, default: 2 },
15+
mobile: { min: 1, max: 2, default: 2 },
16+
};
17+
18+
function useViewPortBreakpoint() {
19+
const isXHuge = useViewportMatch( 'xhuge', '>=' );
20+
const isHuge = useViewportMatch( 'huge', '>=' );
21+
const isXlarge = useViewportMatch( 'xlarge', '>=' );
22+
const isLarge = useViewportMatch( 'large', '>=' );
23+
const isMobile = useViewportMatch( 'mobile', '>=' );
24+
25+
if ( isXHuge ) {
26+
return 'xhuge';
27+
}
28+
if ( isHuge ) {
29+
return 'huge';
30+
}
31+
if ( isXlarge ) {
32+
return 'xlarge';
33+
}
34+
if ( isLarge ) {
35+
return 'large';
36+
}
37+
if ( isMobile ) {
38+
return 'mobile';
39+
}
40+
return null;
41+
}
42+
43+
// Value is number from 0 to 100 representing how big an item is in the grid
44+
// 100 being the biggest and 0 being the smallest.
45+
// The size is relative to the viewport size, if one a given viewport the
46+
// number of allowed items in a grid is 3 to 6 a 0 ( the smallest ) will mean that the grid will
47+
// have 6 items in a row, a 100 ( the biggest ) will mean that the grid will have 3 items in a row.
48+
// A value of 75 will mean that the grid will have 4 items in a row.
49+
function getRangeValue(
50+
density: number,
51+
breakValues: { min: number; max: number; default: number }
52+
) {
53+
const inverseDensity = breakValues.max - density;
54+
const max = breakValues.max - breakValues.min;
55+
return Math.round( ( inverseDensity * 100 ) / max );
56+
}
57+
58+
export default function DensityPicker( {
59+
density,
60+
setDensity,
61+
}: {
62+
density: number;
63+
setDensity: React.Dispatch< React.SetStateAction< number > >;
64+
} ) {
65+
const viewport = useViewPortBreakpoint();
66+
useEffect( () => {
67+
setDensity( ( _density ) => {
68+
if ( ! viewport || ! _density ) {
69+
return 0;
70+
}
71+
const breakValues = viewportBreaks[ viewport ];
72+
if ( _density < breakValues.min ) {
73+
return breakValues.min;
74+
}
75+
if ( _density > breakValues.max ) {
76+
return breakValues.max;
77+
}
78+
return _density;
79+
} );
80+
}, [ setDensity, viewport ] );
81+
if ( ! viewport ) {
82+
return null;
83+
}
84+
const breakValues = viewportBreaks[ viewport ];
85+
const densityToUse = density || breakValues.default;
86+
const rangeValue = getRangeValue( densityToUse, breakValues );
87+
88+
const step = 100 / ( breakValues.max - breakValues.min + 1 );
89+
return (
90+
<>
91+
<Button
92+
size="compact"
93+
icon={ lineSolid }
94+
disabled={ rangeValue <= 0 }
95+
accessibleWhenDisabled
96+
label={ __( 'Decrease size' ) }
97+
onClick={ () => {
98+
setDensity( densityToUse + 1 );
99+
} }
100+
/>
101+
<RangeControl
102+
__nextHasNoMarginBottom
103+
showTooltip={ false }
104+
className="dataviews-density-picker__range-control"
105+
label={ __( 'Item size' ) }
106+
hideLabelFromVision
107+
value={ rangeValue }
108+
min={ 0 }
109+
max={ 100 }
110+
withInputField={ false }
111+
onChange={ ( value = 0 ) => {
112+
const inverseValue = 100 - value;
113+
setDensity(
114+
Math.round(
115+
( inverseValue *
116+
( breakValues.max - breakValues.min ) ) /
117+
100 +
118+
breakValues.min
119+
)
120+
);
121+
} }
122+
step={ step }
123+
/>
124+
<Button
125+
size="compact"
126+
icon={ plus }
127+
disabled={ rangeValue >= 100 }
128+
accessibleWhenDisabled
129+
label={ __( 'Increase size' ) }
130+
onClick={ () => {
131+
setDensity( densityToUse - 1 );
132+
} }
133+
/>
134+
</>
135+
);
136+
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export default function ViewGrid< Item >( {
172172
onChangeSelection,
173173
selection,
174174
view,
175+
density,
175176
}: ViewGridProps< Item > ) {
176177
const mediaField = fields.find(
177178
( field ) => field.id === view.layout?.mediaField
@@ -202,6 +203,9 @@ export default function ViewGrid< Item >( {
202203
{ visibleFields: [], badgeFields: [] }
203204
);
204205
const hasData = !! data?.length;
206+
const gridStyle = density
207+
? { gridTemplateColumns: `repeat(${ density }, minmax(0, 1fr))` }
208+
: {};
205209
return (
206210
<>
207211
{ hasData && (
@@ -210,6 +214,7 @@ export default function ViewGrid< Item >( {
210214
columns={ 2 }
211215
alignment="top"
212216
className="dataviews-view-grid"
217+
style={ gridStyle }
213218
aria-busy={ isLoading }
214219
>
215220
{ data.map( ( item ) => {

packages/dataviews/src/layouts/grid/style.scss

+24-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
11
.dataviews-view-grid {
22
margin-bottom: auto;
3-
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
43
grid-template-rows: max-content;
54
padding: 0 $grid-unit-60 $grid-unit-30;
65
transition: padding ease-out 0.1s;
76
@include reduce-motion("transition");
87

9-
@include break-mobile() {
10-
grid-template-columns: repeat(2, minmax(0, 1fr)) !important; // Todo: eliminate !important dependency
11-
}
12-
13-
@include break-xlarge() {
14-
grid-template-columns: repeat(3, minmax(0, 1fr)) !important; // Todo: eliminate !important dependency
15-
}
16-
17-
@include break-huge() {
18-
grid-template-columns: repeat(4, minmax(0, 1fr)) !important; // Todo: eliminate !important dependency
19-
}
208

219
.dataviews-view-grid__card {
2210
height: 100%;
@@ -122,6 +110,30 @@
122110
}
123111
}
124112

113+
.dataviews-view-grid.dataviews-view-grid {
114+
grid-template-columns: repeat(1, minmax(0, 1fr));
115+
116+
@include break-mobile() {
117+
grid-template-columns: repeat(2, minmax(0, 1fr));
118+
}
119+
120+
@include break-xlarge() {
121+
grid-template-columns: repeat(3, minmax(0, 1fr));
122+
}
123+
124+
@include break-huge() {
125+
grid-template-columns: repeat(4, minmax(0, 1fr));
126+
}
127+
128+
@include break-xhuge() {
129+
grid-template-columns: repeat(5, minmax(0, 1fr));
130+
}
131+
}
132+
133+
.dataviews-density-picker__range-control {
134+
width: 200px;
135+
}
136+
125137
.dataviews-view-grid__field-value:empty,
126138
.dataviews-view-grid__field:empty {
127139
display: none;

packages/dataviews/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ export interface ViewBaseProps< Item > {
434434
selection: string[];
435435
setOpenedFilter: ( fieldId: string ) => void;
436436
view: View;
437+
density: number;
437438
}
438439

439440
export interface ViewTableProps< Item > extends ViewBaseProps< Item > {

0 commit comments

Comments
 (0)