diff --git a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx index 7f384f70ae1dc..0c3cadf210d84 100644 --- a/packages/components/src/toggle-group-control/toggle-group-control/component.tsx +++ b/packages/components/src/toggle-group-control/toggle-group-control/component.tsx @@ -52,7 +52,7 @@ function UnconnectedToggleGroupControl( const [ controlElement, setControlElement ] = useState< HTMLElement >(); const refs = useMergeRefs( [ setControlElement, forwardedRef ] ); const selectedRect = useTrackElementOffsetRect( - value ? selectedElement : undefined + value || value === 0 ? selectedElement : undefined ); useAnimatedOffsetRect( controlElement, selectedRect, { prefix: 'selected', diff --git a/packages/dataviews/src/components/dataviews-context/index.ts b/packages/dataviews/src/components/dataviews-context/index.ts index 3936288b3095b..39e5e09015658 100644 --- a/packages/dataviews/src/components/dataviews-context/index.ts +++ b/packages/dataviews/src/components/dataviews-context/index.ts @@ -26,7 +26,6 @@ type DataViewsContextType< Item > = { openedFilter: string | null; setOpenedFilter: ( openedFilter: string | null ) => void; getItemId: ( item: Item ) => string; - density: number; }; const DataViewsContext = createContext< DataViewsContextType< any > >( { @@ -43,7 +42,6 @@ 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 bae4071fe2f77..c981a8b79e028 100644 --- a/packages/dataviews/src/components/dataviews-layout/index.tsx +++ b/packages/dataviews/src/components/dataviews-layout/index.tsx @@ -27,7 +27,6 @@ export default function DataViewsLayout() { selection, onChangeSelection, setOpenedFilter, - density, } = useContext( DataViewsContext ); const ViewComponent = VIEW_LAYOUTS.find( ( v ) => v.type === view.type ) @@ -45,7 +44,6 @@ export default function DataViewsLayout() { selection={ selection } setOpenedFilter={ setOpenedFilter } view={ view } - density={ density } /> ); } diff --git a/packages/dataviews/src/components/dataviews-view-config/index.tsx b/packages/dataviews/src/components/dataviews-view-config/index.tsx index c8b26c5127589..01fe0e87979e1 100644 --- a/packages/dataviews/src/components/dataviews-view-config/index.tsx +++ b/packages/dataviews/src/components/dataviews-view-config/index.tsx @@ -35,7 +35,6 @@ import { useInstanceId } from '@wordpress/compose'; */ import { SORTING_DIRECTIONS, - LAYOUT_GRID, LAYOUT_TABLE, sortIcons, sortLabels, @@ -46,10 +45,10 @@ import { getVisibleFieldIds, getHiddenFieldIds, } from '../../dataviews-layouts'; +import { DensityOptions } from '../../types'; import type { SupportedLayouts, View, Field } from '../../types'; import DataViewsContext from '../dataviews-context'; import { unlock } from '../../lock-unlock'; -import DensityPicker from '../../dataviews-layouts/grid/density-picker'; const { Menu } = unlock( componentsPrivateApis ); @@ -239,6 +238,50 @@ function ItemsPerPageControl() { ); } +function DensityPicker() { + const { view, onChangeView } = useContext( DataViewsContext ); + if ( + ! VIEW_LAYOUTS.find( ( layout ) => layout.type === view.type ) + ?.supportsDensity + ) { + return null; + } + return ( + { + onChangeView( { + ...view, + density: value as DensityOptions, + } ); + } } + isBlock + > + + + + + ); +} + interface FieldItemProps { id: any; label: string; @@ -512,14 +555,7 @@ function SettingsSection( { ); } -function DataviewsViewConfigDropdown( { - density, - setDensity, -}: { - density: number; - setDensity: React.Dispatch< React.SetStateAction< number > >; -} ) { - const { view } = useContext( DataViewsContext ); +function DataviewsViewConfigDropdown() { const popoverId = useInstanceId( _DataViewsViewConfig, 'dataviews-view-config-dropdown' @@ -551,12 +587,7 @@ function DataviewsViewConfigDropdown( { - { view.type === LAYOUT_GRID && ( - - ) } + @@ -570,21 +601,14 @@ function DataviewsViewConfigDropdown( { } function _DataViewsViewConfig( { - density, - setDensity, defaultLayouts = { list: {}, grid: {}, table: {} }, }: { - density: number; - setDensity: React.Dispatch< React.SetStateAction< number > >; defaultLayouts?: SupportedLayouts; } ) { return ( <> - + ); } diff --git a/packages/dataviews/src/components/dataviews/index.tsx b/packages/dataviews/src/components/dataviews/index.tsx index da60ab15ecade..650fcdb5dee85 100644 --- a/packages/dataviews/src/components/dataviews/index.tsx +++ b/packages/dataviews/src/components/dataviews/index.tsx @@ -68,7 +68,6 @@ export default function DataViews< Item >( { header, }: DataViewsProps< Item > ) { const [ selectionState, setSelectionState ] = useState< string[] >( [] ); - const [ density, setDensity ] = useState< number >( 0 ); const isUncontrolled = selectionProperty === undefined || onChangeSelection === undefined; const selection = isUncontrolled ? selectionState : selectionProperty; @@ -110,7 +109,6 @@ export default function DataViews< Item >( { openedFilter, setOpenedFilter, getItemId, - density, } } >
@@ -142,8 +140,6 @@ export default function DataViews< Item >( { > { header } diff --git a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx b/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx deleted file mode 100644 index 34ddf6c3fe52f..0000000000000 --- a/packages/dataviews/src/dataviews-layouts/grid/density-picker.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/** - * WordPress dependencies - */ -import { RangeControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { useViewportMatch } from '@wordpress/compose'; -import { useEffect, useMemo } 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; -} - -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 ] ); - const breakValues = viewportBreaks[ viewport || 'mobile' ]; - const densityToUse = density || breakValues.default; - - const marks = useMemo( - () => - Array.from( - { length: breakValues.max - breakValues.min + 1 }, - ( _, i ) => { - return { - value: breakValues.min + i, - }; - } - ), - [ breakValues ] - ); - - if ( ! viewport ) { - return null; - } - - return ( - { - setDensity( breakValues.max + breakValues.min - value ); - } } - step={ 1 } - /> - ); -} diff --git a/packages/dataviews/src/dataviews-layouts/grid/index.tsx b/packages/dataviews/src/dataviews-layouts/grid/index.tsx index 230ffe0dc50b5..494bb43e0db01 100644 --- a/packages/dataviews/src/dataviews-layouts/grid/index.tsx +++ b/packages/dataviews/src/dataviews-layouts/grid/index.tsx @@ -22,8 +22,10 @@ import { __ } from '@wordpress/i18n'; import ItemActions from '../../components/dataviews-item-actions'; import SingleSelectionCheckbox from '../../components/dataviews-selection-checkbox'; import { useHasAPossibleBulkAction } from '../../components/dataviews-bulk-actions'; +import { DensityOptions } from '../../types'; import type { Action, NormalizedField, ViewGridProps } from '../../types'; import type { SetSelection } from '../../private-types'; +import useDensityOptions from './use-density-options'; interface GridItemProps< Item > { selection: string[]; @@ -172,7 +174,6 @@ export default function ViewGrid< Item >( { onChangeSelection, selection, view, - density, }: ViewGridProps< Item > ) { const mediaField = fields.find( ( field ) => field.id === view.layout?.mediaField @@ -203,9 +204,22 @@ export default function ViewGrid< Item >( { { visibleFields: [], badgeFields: [] } ); const hasData = !! data?.length; - const gridStyle = density - ? { gridTemplateColumns: `repeat(${ density }, minmax(0, 1fr))` } - : {}; + // The `DensityOptions.medium` (default) is handled with css. If another density is selected, + // we query the viewport to determine the number of columns to display per option. + const densityOptions = useDensityOptions(); + const gridStyle = + !! view.density && + [ DensityOptions.comfortable, DensityOptions.compact ].includes( + view.density as DensityOptions + ) + ? { + gridTemplateColumns: `repeat(${ + view.density === DensityOptions.compact + ? densityOptions.max + : densityOptions.min + }, minmax(0, 1fr))`, + } + : {}; return ( <> { hasData && ( diff --git a/packages/dataviews/src/dataviews-layouts/grid/use-density-options.ts b/packages/dataviews/src/dataviews-layouts/grid/use-density-options.ts new file mode 100644 index 0000000000000..6fa7df17fd4bb --- /dev/null +++ b/packages/dataviews/src/dataviews-layouts/grid/use-density-options.ts @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; + +export default function useDensityOptions() { + const isXHuge = useViewportMatch( 'xhuge' ); + const isHuge = useViewportMatch( 'huge' ); + const isXlarge = useViewportMatch( 'xlarge' ); + const isLarge = useViewportMatch( 'large' ); + if ( isXHuge ) { + return { min: 3, max: 6 }; + } + if ( isHuge ) { + return { min: 2, max: 4 }; + } + if ( isXlarge ) { + return { min: 2, max: 3 }; + } + if ( isLarge ) { + return { min: 1, max: 2 }; + } + // Default to mobile. + return { min: 1, max: 2 }; +} diff --git a/packages/dataviews/src/dataviews-layouts/index.ts b/packages/dataviews/src/dataviews-layouts/index.ts index eece17d0f4f10..e832ef3c4aa13 100644 --- a/packages/dataviews/src/dataviews-layouts/index.ts +++ b/packages/dataviews/src/dataviews-layouts/index.ts @@ -30,6 +30,7 @@ export const VIEW_LAYOUTS = [ label: __( 'Grid' ), component: ViewGrid, icon: category, + supportsDensity: true, }, { type: LAYOUT_LIST, diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 0ea0965704d18..c0e164f77499f 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -247,6 +247,12 @@ export interface NormalizedFilter { isPrimary: boolean; } +export enum DensityOptions { + medium = 0, + compact = 1, + comfortable = 2, +} + interface ViewBase { /** * The layout of the view. @@ -292,6 +298,12 @@ interface ViewBase { * The fields to render */ fields?: string[]; + + /** + * The density of the view. + */ + // TODO: maybe move to `layout` object? + density?: DensityOptions; } export interface CombinedField { @@ -499,7 +511,6 @@ export interface ViewBaseProps< Item > { selection: string[]; setOpenedFilter: ( fieldId: string ) => void; view: View; - density: number; } export interface ViewTableProps< Item > extends ViewBaseProps< Item > {