diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 022e160034866a..9d84283b802003 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -6,6 +6,7 @@ ### Breaking Changes - Replace the `hiddenFields` property in the view prop of `DataViews` with a `fields` property that accepts an array of visible fields instead. +- Replace the `supportedLayouts` prop in the `DataViews` component with a `defaultLayouts` prop that accepts an object whose keys are the layout names and values are the default view objects for these layouts. ### New features diff --git a/packages/dataviews/README.md b/packages/dataviews/README.md index 1c3becf9a1e5b4..fac5c9f116ac8a 100644 --- a/packages/dataviews/README.md +++ b/packages/dataviews/README.md @@ -274,9 +274,9 @@ Function that receives an item and returns an unique identifier for it. By defau Whether the data is loading. `false` by default. -### `supportedLayouts`: `String[]` +### `defaultLayouts`: `Record< string, view >` -Array of layouts supported. By default, all are: `table`, `grid`, `list`. +Default layouts. By default, uses empty layouts: `table`, `grid`, `list`. ### `onSelectionChange`: `function` diff --git a/packages/dataviews/src/dataviews.tsx b/packages/dataviews/src/dataviews.tsx index 8dd81e9013af8e..d6a1d5194ba9d1 100644 --- a/packages/dataviews/src/dataviews.tsx +++ b/packages/dataviews/src/dataviews.tsx @@ -24,7 +24,13 @@ import { } from './bulk-actions'; import { normalizeFields } from './normalize-fields'; import BulkActionsToolbar from './bulk-actions-toolbar'; -import type { Action, Field, View, ViewBaseProps } from './types'; +import type { + Action, + Field, + View, + ViewBaseProps, + SupportedLayouts, +} from './types'; import type { SetSelection, SelectionOrUpdater } from './private-types'; type ItemWithId = { id: string }; @@ -42,7 +48,7 @@ type DataViewsProps< Item > = { totalItems: number; totalPages: number; }; - supportedLayouts: string[]; + defaultLayouts: SupportedLayouts; selection?: string[]; setSelection?: SetSelection; onSelectionChange?: ( items: Item[] ) => void; @@ -65,7 +71,7 @@ export default function DataViews< Item >( { getItemId = defaultGetItemId, isLoading = false, paginationInfo, - supportedLayouts, + defaultLayouts, selection: selectionProperty, setSelection: setSelectionProperty, onSelectionChange = defaultOnSelectionChange, @@ -142,7 +148,7 @@ export default function DataViews< Item >( { fields={ _fields } view={ view } onChangeView={ onChangeView } - supportedLayouts={ supportedLayouts } + defaultLayouts={ defaultLayouts } /> ( } ); } - if ( view.filters?.length > 0 ) { + if ( view.filters && view.filters?.length > 0 ) { view.filters.forEach( ( filter ) => { const field = _fields.find( ( _field ) => _field.id === filter.field diff --git a/packages/dataviews/src/filter-summary.tsx b/packages/dataviews/src/filter-summary.tsx index da4b2e840a297f..95b06324d95dba 100644 --- a/packages/dataviews/src/filter-summary.tsx +++ b/packages/dataviews/src/filter-summary.tsx @@ -157,7 +157,7 @@ function OperatorSelector( { value: operator, label: OPERATORS[ operator ]?.label, } ) ); - const currentFilter = view.filters.find( + const currentFilter = view.filters?.find( ( _filter ) => _filter.field === filter.field ); const value = currentFilter?.operator || filter.operators[ 0 ]; @@ -180,18 +180,22 @@ function OperatorSelector( { const operator = newValue as Operator; const newFilters = currentFilter ? [ - ...view.filters.map( ( _filter ) => { - if ( _filter.field === filter.field ) { - return { - ..._filter, - operator, - }; + ...( view.filters ?? [] ).map( + ( _filter ) => { + if ( + _filter.field === filter.field + ) { + return { + ..._filter, + operator, + }; + } + return _filter; } - return _filter; - } ), + ), ] : [ - ...view.filters, + ...( view.filters ?? [] ), { field: filter.field, operator, @@ -220,7 +224,9 @@ export default function FilterSummary( { }: FilterSummaryProps ) { const toggleRef = useRef< HTMLDivElement >( null ); const { filter, view, onChangeView } = commonProps; - const filterInView = view.filters.find( ( f ) => f.field === filter.field ); + const filterInView = view.filters?.find( + ( f ) => f.field === filter.field + ); const activeElements = filter.elements.filter( ( element ) => { if ( filter.singleSelection ) { return element.value === filterInView?.value; @@ -290,7 +296,7 @@ export default function FilterSummary( { onChangeView( { ...view, page: 1, - filters: view.filters.filter( + filters: view.filters?.filter( ( _filter ) => _filter.field !== filter.field ), diff --git a/packages/dataviews/src/filters.tsx b/packages/dataviews/src/filters.tsx index 187f34b532ddef..ca3de9626b48e7 100644 --- a/packages/dataviews/src/filters.tsx +++ b/packages/dataviews/src/filters.tsx @@ -52,7 +52,7 @@ function _Filters< Item >( { operators, isVisible: isPrimary || - view.filters.some( + !! view.filters?.some( ( f ) => f.field === field.id && ALL_OPERATORS.includes( f.operator ) diff --git a/packages/dataviews/src/search-widget.tsx b/packages/dataviews/src/search-widget.tsx index 6a5f4b6644f677..d65d58635405a3 100644 --- a/packages/dataviews/src/search-widget.tsx +++ b/packages/dataviews/src/search-widget.tsx @@ -93,7 +93,7 @@ function ListBox( { view, filter, onChangeView }: SearchWidgetProps ) { // so the first item is not selected, since the focus is on the operators control. defaultActiveId: filter.operators?.length === 1 ? undefined : null, } ); - const currentFilter = view.filters.find( + const currentFilter = view.filters?.find( ( f ) => f.field === filter.field ); const currentValue = getCurrentValue( filter, currentFilter ); @@ -130,7 +130,7 @@ function ListBox( { view, filter, onChangeView }: SearchWidgetProps ) { onClick={ () => { const newFilters = currentFilter ? [ - ...view.filters.map( + ...( view.filters ?? [] ).map( ( _filter ) => { if ( _filter.field === @@ -154,7 +154,7 @@ function ListBox( { view, filter, onChangeView }: SearchWidgetProps ) { ), ] : [ - ...view.filters, + ...( view.filters ?? [] ), { field: filter.field, operator: filter.operators[ 0 ], @@ -201,7 +201,7 @@ function ListBox( { view, filter, onChangeView }: SearchWidgetProps ) { function ComboboxList( { view, filter, onChangeView }: SearchWidgetProps ) { const [ searchValue, setSearchValue ] = useState( '' ); const deferredSearchValue = useDeferredValue( searchValue ); - const currentFilter = view.filters.find( + const currentFilter = view.filters?.find( ( _filter ) => _filter.field === filter.field ); const currentValue = getCurrentValue( filter, currentFilter ); @@ -218,7 +218,7 @@ function ComboboxList( { view, filter, onChangeView }: SearchWidgetProps ) { setSelectedValue={ ( value ) => { const newFilters = currentFilter ? [ - ...view.filters.map( ( _filter ) => { + ...( view.filters ?? [] ).map( ( _filter ) => { if ( _filter.field === filter.field ) { return { ..._filter, @@ -232,7 +232,7 @@ function ComboboxList( { view, filter, onChangeView }: SearchWidgetProps ) { } ), ] : [ - ...view.filters, + ...( view.filters ?? [] ), { field: filter.field, operator: filter.operators[ 0 ], diff --git a/packages/dataviews/src/stories/index.story.js b/packages/dataviews/src/stories/index.story.js index bf4c1e1b1dba86..c04e89a92baa85 100644 --- a/packages/dataviews/src/stories/index.story.js +++ b/packages/dataviews/src/stories/index.story.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useState, useMemo, useCallback } from '@wordpress/element'; +import { useState, useMemo } from '@wordpress/element'; /** * Internal dependencies @@ -17,36 +17,11 @@ const meta = { }; export default meta; -const defaultConfigPerViewType = { - [ LAYOUT_TABLE ]: { - primaryField: 'title', - }, - [ LAYOUT_GRID ]: { - mediaField: 'image', - primaryField: 'title', - }, -}; - export const Default = ( props ) => { const [ view, setView ] = useState( DEFAULT_VIEW ); const { data: shownData, paginationInfo } = useMemo( () => { return filterSortAndPaginate( data, view, fields ); }, [ view ] ); - const onChangeView = useCallback( - ( newView ) => { - if ( newView.type !== view.type ) { - newView = { - ...newView, - layout: { - ...defaultConfigPerViewType[ newView.type ], - }, - }; - } - - setView( newView ); - }, - [ view.type, setView ] - ); return ( { data={ shownData } view={ view } fields={ fields } - onChangeView={ onChangeView } + onChangeView={ setView } /> ); }; Default.args = { actions, - supportedLayouts: [ LAYOUT_TABLE, LAYOUT_GRID ], + defaultLayouts: { + [ LAYOUT_TABLE ]: { + layout: { + primaryField: 'title', + }, + }, + [ LAYOUT_GRID ]: { + layout: { + mediaField: 'image', + primaryField: 'title', + }, + }, + }, }; diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 36dcfb2e29f35d..9cc9ffb0f76c0c 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -221,7 +221,7 @@ interface ViewBase { /** * The filters to apply. */ - filters: Filter[]; + filters?: Filter[]; /** * The sorting configuration. @@ -257,7 +257,7 @@ interface ViewBase { export interface ViewTable extends ViewBase { type: 'table'; - layout: { + layout?: { /** * The field to use as the primary field. */ @@ -273,7 +273,7 @@ export interface ViewTable extends ViewBase { export interface ViewList extends ViewBase { type: 'list'; - layout: { + layout?: { /** * The field to use as the primary field. */ @@ -289,7 +289,7 @@ export interface ViewList extends ViewBase { export interface ViewGrid extends ViewBase { type: 'grid'; - layout: { + layout?: { /** * The field to use as the primary field. */ @@ -429,3 +429,9 @@ export type ViewProps< Item > = | ViewTableProps< Item > | ViewGridProps< Item > | ViewListProps< Item >; + +export interface SupportedLayouts { + list?: Omit< ViewList, 'type' >; + grid?: Omit< ViewGrid, 'type' >; + table?: Omit< ViewTable, 'type' >; +} diff --git a/packages/dataviews/src/view-actions.tsx b/packages/dataviews/src/view-actions.tsx index eac09443197cc4..fd9aff28b6479b 100644 --- a/packages/dataviews/src/view-actions.tsx +++ b/packages/dataviews/src/view-actions.tsx @@ -21,7 +21,7 @@ import { cog } from '@wordpress/icons'; import { unlock } from './lock-unlock'; import { SORTING_DIRECTIONS, sortLabels } from './constants'; import { VIEW_LAYOUTS } from './layouts'; -import type { NormalizedField, View } from './types'; +import type { NormalizedField, View, SupportedLayouts } from './types'; const { DropdownMenuV2: DropdownMenu, @@ -35,7 +35,7 @@ const { interface ViewTypeMenuProps { view: View; onChangeView: ( view: View ) => void; - supportedLayouts?: string[]; + defaultLayouts?: SupportedLayouts; } interface PageSizeMenuProps { @@ -59,30 +59,29 @@ interface ViewActionsProps< Item > { fields: NormalizedField< Item >[]; view: View; onChangeView: ( view: View ) => void; - supportedLayouts?: string[]; + defaultLayouts?: SupportedLayouts; } function ViewTypeMenu( { view, onChangeView, - supportedLayouts, + defaultLayouts = { list: {}, grid: {}, table: {} }, }: ViewTypeMenuProps ) { - let _availableViews = VIEW_LAYOUTS; - if ( supportedLayouts ) { - _availableViews = _availableViews.filter( ( _view ) => - supportedLayouts.includes( _view.type ) - ); - } - if ( _availableViews.length === 1 ) { + const availableLayouts = Object.keys( defaultLayouts ); + if ( availableLayouts.length <= 1 ) { return null; } - return _availableViews.map( ( availableView ) => { + return availableLayouts.map( ( layout ) => { + const config = VIEW_LAYOUTS.find( ( v ) => v.type === layout ); + if ( ! config ) { + return null; + } return ( ) => { switch ( e.target.value ) { @@ -92,15 +91,13 @@ function ViewTypeMenu( { return onChangeView( { ...view, type: e.target.value, - layout: {}, + ...defaultLayouts[ e.target.value ], } ); } throw new Error( 'Invalid dataview' ); } } > - - { availableView.label } - + { config.label } ); } ); @@ -152,7 +149,8 @@ function FieldsVisibilityMenu< Item >( { }: FieldsVisibilityMenuProps< Item > ) { const hidableFields = fields.filter( ( field ) => - field.enableHiding !== false && field.id !== view.layout.mediaField + field.enableHiding !== false && + field.id !== view?.layout?.mediaField ); const viewFields = view.fields || fields.map( ( field ) => field.id ); if ( ! hidableFields?.length ) { @@ -287,7 +285,7 @@ function _ViewActions< Item >( { fields, view, onChangeView, - supportedLayouts, + defaultLayouts, }: ViewActionsProps< Item > ) { const activeView = VIEW_LAYOUTS.find( ( v ) => view.type === v.type ); return ( @@ -309,7 +307,7 @@ function _ViewActions< Item >( { ( { view, }: ViewGridProps< Item > ) { const mediaField = fields.find( - ( field ) => field.id === view.layout.mediaField + ( field ) => field.id === view.layout?.mediaField ); const primaryField = fields.find( - ( field ) => field.id === view.layout.primaryField + ( field ) => field.id === view.layout?.primaryField ); const viewFields = view.fields || fields.map( ( field ) => field.id ); const { visibleFields, badgeFields } = fields.reduce( ( accumulator: Record< string, NormalizedField< Item >[] >, field ) => { if ( ! viewFields.includes( field.id ) || - [ view.layout.mediaField, view.layout.primaryField ].includes( - field.id - ) + [ + view.layout?.mediaField, + view?.layout?.primaryField, + ].includes( field.id ) ) { return accumulator; } // If the field is a badge field, add it to the badgeFields array // otherwise add it to the rest visibleFields array. - const key = view.layout.badgeFields?.includes( field.id ) + const key = view.layout?.badgeFields?.includes( field.id ) ? 'badgeFields' : 'visibleFields'; accumulator[ key ].push( field ); @@ -230,7 +231,7 @@ export default function ViewGrid< Item >( { primaryField={ primaryField } visibleFields={ visibleFields } badgeFields={ badgeFields } - columnFields={ view.layout.columnFields } + columnFields={ view.layout?.columnFields } /> ); } ) } diff --git a/packages/dataviews/src/view-list.tsx b/packages/dataviews/src/view-list.tsx index 6a56f05e83202c..7e8491f5c7780f 100644 --- a/packages/dataviews/src/view-list.tsx +++ b/packages/dataviews/src/view-list.tsx @@ -320,16 +320,16 @@ export default function ViewList< Item >( props: ViewListProps< Item > ) { ); const mediaField = fields.find( - ( field ) => field.id === view.layout.mediaField + ( field ) => field.id === view.layout?.mediaField ); const primaryField = fields.find( - ( field ) => field.id === view.layout.primaryField + ( field ) => field.id === view.layout?.primaryField ); const viewFields = view.fields || fields.map( ( field ) => field.id ); const visibleFields = fields.filter( ( field ) => viewFields.includes( field.id ) && - ! [ view.layout.primaryField, view.layout.mediaField ].includes( + ! [ view.layout?.primaryField, view.layout?.mediaField ].includes( field.id ) ); diff --git a/packages/dataviews/src/view-table.tsx b/packages/dataviews/src/view-table.tsx index 79118d2f095616..1ba489ebe07ef5 100644 --- a/packages/dataviews/src/view-table.tsx +++ b/packages/dataviews/src/view-table.tsx @@ -463,12 +463,12 @@ function ViewTable< Item >( { const visibleFields = fields.filter( ( field ) => viewFields.includes( field.id ) || - [ view.layout.mediaField ].includes( field.id ) + [ view.layout?.mediaField ].includes( field.id ) ); const hasData = !! data?.length; const primaryField = fields.find( - ( field ) => field.id === view.layout.primaryField + ( field ) => field.id === view.layout?.primaryField ); return ( diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index cf9411a6caeead..41ca9c75ced6fc 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -13,13 +13,7 @@ import { Flex, } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; -import { - useState, - useMemo, - useCallback, - useId, - useEffect, -} from '@wordpress/element'; +import { useState, useMemo, useId, useEffect } from '@wordpress/element'; import { BlockPreview, privateApis as blockEditorPrivateApis, @@ -64,14 +58,18 @@ const { usePostActions } = unlock( editorPrivateApis ); const { useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; -const defaultConfigPerViewType = { +const defaultLayouts = { [ LAYOUT_TABLE ]: { - primaryField: 'title', + layout: { + primaryField: 'title', + }, }, [ LAYOUT_GRID ]: { - mediaField: 'preview', - primaryField: 'title', - badgeFields: [ 'sync-status' ], + layout: { + mediaField: 'preview', + primaryField: 'title', + badgeFields: [ 'sync-status' ], + }, }, }; const DEFAULT_VIEW = { @@ -79,9 +77,7 @@ const DEFAULT_VIEW = { search: '', page: 1, perPage: 20, - layout: { - ...defaultConfigPerViewType[ LAYOUT_GRID ], - }, + layout: defaultLayouts[ LAYOUT_GRID ].layout, fields: [ 'title', 'sync-status' ], filters: [], }; @@ -379,20 +375,6 @@ export default function DataviewsPatterns() { } return [ editAction, ...patternActions ].filter( Boolean ); }, [ editAction, type, templatePartActions, patternActions ] ); - const onChangeView = useCallback( - ( newView ) => { - if ( newView.type !== view.type ) { - newView = { - ...newView, - layout: { - ...defaultConfigPerViewType[ newView.type ], - }, - }; - } - setView( newView ); - }, - [ view.type, setView ] - ); const id = useId(); const settings = usePatternSettings(); // Wrap everything in a block editor provider. @@ -419,8 +401,8 @@ export default function DataviewsPatterns() { getItemId={ ( item ) => item.name ?? item.id } isLoading={ isResolving } view={ view } - onChangeView={ onChangeView } - supportedLayouts={ [ LAYOUT_GRID, LAYOUT_TABLE ] } + onChangeView={ setView } + defaultLayouts={ defaultLayouts } /> diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 5ba29d25a432f5..4715c7dd983de7 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -54,18 +54,24 @@ const { useHistory, useLocation } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; -const defaultConfigPerViewType = { +const defaultLayouts = { [ LAYOUT_TABLE ]: { - primaryField: 'title', + layout: { + primaryField: 'title', + }, }, [ LAYOUT_GRID ]: { - mediaField: 'preview', - primaryField: 'title', - columnFields: [ 'description' ], + layout: { + mediaField: 'preview', + primaryField: 'title', + columnFields: [ 'description' ], + }, }, [ LAYOUT_LIST ]: { - primaryField: 'title', - mediaField: 'preview', + layout: { + primaryField: 'title', + mediaField: 'preview', + }, }, }; @@ -79,7 +85,7 @@ const DEFAULT_VIEW = { direction: 'asc', }, fields: [ 'title', 'description', 'author' ], - layout: defaultConfigPerViewType[ LAYOUT_GRID ], + layout: defaultLayouts[ LAYOUT_GRID ].layout, filters: [], }; @@ -192,7 +198,7 @@ export default function PageTemplates() { return { ...DEFAULT_VIEW, type: usedType, - layout: defaultConfigPerViewType[ usedType ], + layout: defaultLayouts[ usedType ].layout, filters: activeView !== 'all' ? [ @@ -336,13 +342,6 @@ export default function PageTemplates() { const onChangeView = useCallback( ( newView ) => { if ( newView.type !== view.type ) { - newView = { - ...newView, - layout: { - ...defaultConfigPerViewType[ newView.type ], - }, - }; - history.push( { ...params, layout: newView.type, @@ -371,6 +370,7 @@ export default function PageTemplates() { onSelectionChange={ onSelectionChange } selection={ selection } setSelection={ setSelection } + defaultLayouts={ defaultLayouts } /> );