diff --git a/packages/compose/src/hooks/use-viewport-match/index.js b/packages/compose/src/hooks/use-viewport-match/index.js index ebec934b480123..4186045f7d08c4 100644 --- a/packages/compose/src/hooks/use-viewport-match/index.js +++ b/packages/compose/src/hooks/use-viewport-match/index.js @@ -9,7 +9,7 @@ import { createContext, useContext } from '@wordpress/element'; import useMediaQuery from '../use-media-query'; /** - * @typedef {"huge" | "wide" | "large" | "medium" | "small" | "mobile"} WPBreakpoint + * @typedef {"xhuge" | "huge" | "wide" | "xlarge" | "large" | "medium" | "small" | "mobile"} WPBreakpoint */ /** @@ -20,8 +20,10 @@ import useMediaQuery from '../use-media-query'; * @type {Record} */ const BREAKPOINTS = { + xhuge: 1920, huge: 1440, wide: 1280, + xlarge: 1080, large: 960, medium: 782, small: 600, diff --git a/packages/dataviews/src/components/dataviews-context/index.ts b/packages/dataviews/src/components/dataviews-context/index.ts index 39e5e09015658e..3936288b3095b0 100644 --- a/packages/dataviews/src/components/dataviews-context/index.ts +++ b/packages/dataviews/src/components/dataviews-context/index.ts @@ -26,6 +26,7 @@ type DataViewsContextType< Item > = { openedFilter: string | null; setOpenedFilter: ( openedFilter: string | null ) => void; getItemId: ( item: Item ) => string; + density: number; }; const DataViewsContext = createContext< DataViewsContextType< any > >( { @@ -42,6 +43,7 @@ const DataViewsContext = createContext< DataViewsContextType< any > >( { setOpenedFilter: () => {}, openedFilter: null, getItemId: ( item ) => item.id, + density: 0, } ); export default DataViewsContext; diff --git a/packages/dataviews/src/components/dataviews-layout/index.tsx b/packages/dataviews/src/components/dataviews-layout/index.tsx index 960dcf304c0180..eac70763e143c1 100644 --- a/packages/dataviews/src/components/dataviews-layout/index.tsx +++ b/packages/dataviews/src/components/dataviews-layout/index.tsx @@ -27,6 +27,7 @@ export default function DataViewsLayout() { selection, onChangeSelection, setOpenedFilter, + density, } = useContext( DataViewsContext ); const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type ) @@ -44,6 +45,7 @@ export default function DataViewsLayout() { selection={ selection } setOpenedFilter={ setOpenedFilter } view={ view } + density={ density } /> ); } diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx index 618e04773c084e..5d45413f03b65a 100644 --- a/packages/dataviews/src/components/dataviews/index.tsx +++ b/packages/dataviews/src/components/dataviews/index.tsx @@ -18,6 +18,8 @@ import DataViewsViewConfig from '../dataviews-view-config'; import { normalizeFields } from '../../normalize-fields'; import type { Action, Field, View, SupportedLayouts } from '../../types'; import type { SelectionOrUpdater } from '../../private-types'; +import DensityPicker from '../../layouts/grid/density-picker'; +import { LAYOUT_GRID } from '../../constants'; type ItemWithId = { id: string }; @@ -59,6 +61,7 @@ export default function DataViews< Item >( { onChangeSelection, }: DataViewsProps< Item > ) { const [ selectionState, setSelectionState ] = useState< string[] >( [] ); + const [ density, setDensity ] = useState< number >( 0 ); const isUncontrolled = selectionProperty === undefined || onChangeSelection === undefined; const selection = isUncontrolled ? selectionState : selectionProperty; @@ -95,6 +98,7 @@ export default function DataViews< Item >( { openedFilter, setOpenedFilter, getItemId, + density, } } >
@@ -111,6 +115,12 @@ export default function DataViews< Item >( { { search && } + { view.type === LAYOUT_GRID && ( + + ) } diff --git a/packages/dataviews/src/layouts/grid/density-picker.tsx b/packages/dataviews/src/layouts/grid/density-picker.tsx new file mode 100644 index 00000000000000..df347507fb6340 --- /dev/null +++ b/packages/dataviews/src/layouts/grid/density-picker.tsx @@ -0,0 +1,136 @@ +/** + * WordPress dependencies + */ +import { RangeControl, Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useViewportMatch } from '@wordpress/compose'; +import { plus, lineSolid } from '@wordpress/icons'; +import { useEffect } from '@wordpress/element'; + +const viewportBreaks = { + xhuge: { min: 3, max: 6, default: 5 }, + huge: { min: 2, max: 4, default: 4 }, + xlarge: { min: 2, max: 3, default: 3 }, + large: { min: 1, max: 2, default: 2 }, + mobile: { min: 1, max: 2, default: 2 }, +}; + +function useViewPortBreakpoint() { + const isXHuge = useViewportMatch( 'xhuge', '>=' ); + const isHuge = useViewportMatch( 'huge', '>=' ); + const isXlarge = useViewportMatch( 'xlarge', '>=' ); + const isLarge = useViewportMatch( 'large', '>=' ); + const isMobile = useViewportMatch( 'mobile', '>=' ); + + if ( isXHuge ) { + return 'xhuge'; + } + if ( isHuge ) { + return 'huge'; + } + if ( isXlarge ) { + return 'xlarge'; + } + if ( isLarge ) { + return 'large'; + } + if ( isMobile ) { + return 'mobile'; + } + return null; +} + +// Value is number from 0 to 100 representing how big an item is in the grid +// 100 being the biggest and 0 being the smallest. +// The size is relative to the viewport size, if one a given viewport the +// number of allowed items in a grid is 3 to 6 a 0 ( the smallest ) will mean that the grid will +// have 6 items in a row, a 100 ( the biggest ) will mean that the grid will have 3 items in a row. +// A value of 75 will mean that the grid will have 4 items in a row. +function getRangeValue( + density: number, + breakValues: { min: number; max: number; default: number } +) { + const inverseDensity = breakValues.max - density; + const max = breakValues.max - breakValues.min; + return Math.round( ( inverseDensity * 100 ) / max ); +} + +export default function DensityPicker( { + density, + setDensity, +}: { + density: number; + setDensity: React.Dispatch< React.SetStateAction< number > >; +} ) { + const viewport = useViewPortBreakpoint(); + useEffect( () => { + setDensity( ( _density ) => { + if ( ! viewport || ! _density ) { + return 0; + } + const breakValues = viewportBreaks[ viewport ]; + if ( _density < breakValues.min ) { + return breakValues.min; + } + if ( _density > breakValues.max ) { + return breakValues.max; + } + return _density; + } ); + }, [ setDensity, viewport ] ); + if ( ! viewport ) { + return null; + } + const breakValues = viewportBreaks[ viewport ]; + const densityToUse = density || breakValues.default; + const rangeValue = getRangeValue( densityToUse, breakValues ); + + const step = 100 / ( breakValues.max - breakValues.min + 1 ); + return ( + <> +