Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,10 @@ export default function Filter( {

const isPrimary = filter.isPrimary;
const isLocked = filterInView?.isLocked;
const isRequired = filterInView?.isRequired;
const hasValues = ! isLocked && filterInView?.value !== undefined;
const canResetOrRemove = ! isLocked && ( ! isPrimary || hasValues );
const canResetOrRemove =
! isLocked && ! isRequired && ( ! isPrimary || hasValues );
return (
<Dropdown
defaultOpen={ openedFilter === filter.field }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function ResetFilter( {
! view.filters?.some(
( _filter ) =>
! _filter.isLocked &&
! _filter.isRequired &&
( _filter.value !== undefined || ! isPrimary( _filter.field ) )
);
return (
Expand All @@ -44,7 +45,9 @@ export default function ResetFilter( {
page: 1,
search: '',
filters:
view.filters?.filter( ( f ) => !! f.isLocked ) || [],
view.filters?.filter(
( f ) => !! f.isLocked || !! f.isRequired
) || [],
} );
} }
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ function useFilters( fields: NormalizedField< any >[], view: View ) {
view.filters?.some(
( f ) => f.field === field.id && !! f.isLocked
) ?? false;
const isRequired =
view.filters?.some(
( f ) => f.field === field.id && !! f.isRequired
) ?? false;
filters.push( {
field: field.id,
name: field.label,
Expand All @@ -41,6 +45,7 @@ function useFilters( fields: NormalizedField< any >[], view: View ) {
operators,
isVisible:
isLocked ||
isRequired ||
isPrimary ||
!! view.filters?.some(
( f ) =>
Expand All @@ -49,11 +54,13 @@ function useFilters( fields: NormalizedField< any >[], view: View ) {
),
isPrimary,
isLocked,
isRequired,
} );
} );

// Sort filters by:
// - locked filters go first
// - required filters go next
// - primary filters go next
// - then, sort by name
filters.sort( ( a, b ) => {
Expand All @@ -63,6 +70,12 @@ function useFilters( fields: NormalizedField< any >[], view: View ) {
if ( ! a.isLocked && b.isLocked ) {
return 1;
}
if ( a.isRequired && ! b.isRequired ) {
return -1;
}
if ( ! a.isRequired && b.isRequired ) {
return 1;
}
if ( a.isPrimary && ! b.isPrimary ) {
return -1;
}
Expand Down
17 changes: 17 additions & 0 deletions packages/dataviews/src/dataviews/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import WithCardComponent from './with-card';
import FreeCompositionComponent from './free-composition';
import MinimalUIComponent from './minimal-ui';
import EmptyComponent from './empty';
import RequiredFilterComponent from './required-filter';

import './style.css';

Expand Down Expand Up @@ -242,3 +243,19 @@ export const WithCard = {
export const InfiniteScroll = {
render: InfiniteScrollComponent,
};

export const RequiredFilter = {
name: 'Filter Behavior Comparison',
render: RequiredFilterComponent,
args: {
filterMode: 'all',
},
argTypes: {
filterMode: {
control: 'select',
options: [ 'isPrimary', 'isRequired', 'isLocked', 'all' ],
description:
'Which filter type to demonstrate. "all" shows isPrimary, isRequired, and isLocked side by side for comparison.',
},
},
};
284 changes: 284 additions & 0 deletions packages/dataviews/src/dataviews/stories/required-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
/**
* WordPress dependencies
*/
import { useState, useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import DataViews from '../index';
import { LAYOUT_TABLE } from '../../constants';
import filterSortAndPaginate from '../../utils/filter-sort-and-paginate';
import type { View, Field } from '../../types';
import { actions, data, fields, type SpaceObject } from './fixtures';

/**
* Story demonstrating the differences between isPrimary, isRequired, and isLocked filters:
*
* - isPrimary: Field-level setting. Filter is always visible, can be changed and reset to empty.
* - isRequired: View-level setting. Filter can be changed but cannot be removed or reset.
* - isLocked: View-level setting. Filter is completely locked - no interaction at all.
*/
export const RequiredFilterComponent = ( {
filterMode = 'all',
}: {
filterMode?: 'isPrimary' | 'isRequired' | 'isLocked' | 'all';
} ) => {
// Create modified fields with isPrimary on the 'type' field when needed
const modifiedFields = useMemo( () => {
const showPrimary = filterMode === 'isPrimary' || filterMode === 'all';

return fields.map( ( field ): Field< SpaceObject > => {
// Make 'isPlanet' a primary filter to demonstrate isPrimary behavior
if ( field.id === 'isPlanet' && showPrimary ) {
return {
...field,
filterBy: {
operators: [ 'is' ],
isPrimary: true,
},
};
}
return field;
} );
}, [ filterMode ] );

const [ view, setView ] = useState< View >( () => {
const filters: View[ 'filters' ] = [];

if ( filterMode === 'isRequired' || filterMode === 'all' ) {
filters.push( {
field: 'type',
operator: 'is',
value: 'Satellite',
isRequired: true,
} );
}

if ( filterMode === 'isLocked' || filterMode === 'all' ) {
filters.push( {
field: 'categories',
operator: 'isAny',
value: [ 'Solar system' ],
isLocked: true,
} );
}

return {
type: LAYOUT_TABLE,
search: '',
page: 1,
perPage: 10,
filters,
fields: [ 'type', 'categories', 'satellites', 'isPlanet' ],
titleField: 'title',
descriptionField: 'description',
mediaField: 'image',
};
} );

// Reset view filters when filterMode changes
const [ prevFilterMode, setPrevFilterMode ] = useState( filterMode );
if ( prevFilterMode !== filterMode ) {
setPrevFilterMode( filterMode );
const filters: View[ 'filters' ] = [];

if ( filterMode === 'isRequired' || filterMode === 'all' ) {
filters.push( {
field: 'type',
operator: 'is',
value: 'Satellite',
isRequired: true,
} );
}

if ( filterMode === 'isLocked' || filterMode === 'all' ) {
filters.push( {
field: 'categories',
operator: 'isAny',
value: [ 'Solar system' ],
isLocked: true,
} );
}

setView( ( prev ) => ( { ...prev, filters } ) );
}

const { data: shownData, paginationInfo } = useMemo( () => {
return filterSortAndPaginate( data, view, modifiedFields );
}, [ view, modifiedFields ] );

const showPrimary = filterMode === 'isPrimary' || filterMode === 'all';
const showRequired = filterMode === 'isRequired' || filterMode === 'all';
const showLocked = filterMode === 'isLocked' || filterMode === 'all';

const cellStyle = {
border: '1px solid #ccc',
padding: '8px',
};

const headerCellStyle = {
...cellStyle,
textAlign: 'left' as const,
};

const centerCellStyle = {
...cellStyle,
textAlign: 'center' as const,
};

return (
<div>
<div
style={ {
marginBottom: '1rem',
padding: '1rem',
backgroundColor: '#f0f0f0',
borderRadius: '4px',
} }
>
<h3 style={ { marginTop: 0 } }>Filter Behavior Comparison</h3>
<table
style={ {
width: '100%',
borderCollapse: 'collapse',
marginBottom: '1rem',
} }
>
<thead>
<tr>
<th style={ headerCellStyle }>Property</th>
<th style={ headerCellStyle }>Defined on</th>
<th style={ centerCellStyle }>Always visible</th>
<th style={ centerCellStyle }>Can interact</th>
<th style={ centerCellStyle }>Can reset value</th>
<th style={ centerCellStyle }>Can remove</th>
</tr>
</thead>
<tbody>
<tr
style={
showPrimary
? { backgroundColor: '#e8f4e8' }
: {}
}
>
<td style={ cellStyle }>
<code>isPrimary</code>
</td>
<td style={ cellStyle }>
Field (<code>filterBy.isPrimary</code>)
</td>
<td style={ centerCellStyle }>Yes</td>
<td style={ centerCellStyle }>Yes</td>
<td style={ centerCellStyle }>
<strong>Yes</strong>
</td>
<td style={ centerCellStyle }>No</td>
</tr>
<tr
style={
showRequired
? { backgroundColor: '#e8f0f8' }
: {}
}
>
<td style={ cellStyle }>
<code>isRequired</code>
</td>
<td style={ cellStyle }>
View filter (<code>filters[].isRequired</code>)
</td>
<td style={ centerCellStyle }>Yes</td>
<td style={ centerCellStyle }>Yes</td>
<td style={ centerCellStyle }>No</td>
<td style={ centerCellStyle }>No</td>
</tr>
<tr
style={
showLocked ? { backgroundColor: '#f8f0e8' } : {}
}
>
<td style={ cellStyle }>
<code>isLocked</code>
</td>
<td style={ cellStyle }>
View filter (<code>filters[].isLocked</code>)
</td>
<td style={ centerCellStyle }>Yes</td>
<td style={ centerCellStyle }>No</td>
<td style={ centerCellStyle }>No</td>
<td style={ centerCellStyle }>No</td>
</tr>
</tbody>
</table>

<p>
<strong>Current mode:</strong> <code>{ filterMode }</code>
</p>

<h4>Active filters in this demo:</h4>
<ul>
{ showPrimary && (
<li>
<strong
style={ {
backgroundColor: '#e8f4e8',
padding: '2px 6px',
} }
>
Is Planet
</strong>{ ' ' }
- <code>isPrimary</code>: Always visible, can select
a value and reset it (X button clears value but
filter stays)
</li>
) }
{ showRequired && (
<li>
<strong
style={ {
backgroundColor: '#e8f0f8',
padding: '2px 6px',
} }
>
Type is Satellite
</strong>{ ' ' }
- <code>isRequired</code>: Can change value, but no
X button (cannot reset or remove)
</li>
) }
{ showLocked && (
<li>
<strong
style={ {
backgroundColor: '#f8f0e8',
padding: '2px 6px',
} }
>
Categories is Solar system
</strong>{ ' ' }
- <code>isLocked</code>: Cannot click, no X button
(completely read-only)
</li>
) }
</ul>
</div>
<DataViews
getItemId={ ( item ) => item.id.toString() }
paginationInfo={ paginationInfo }
data={ shownData }
view={ view }
fields={ modifiedFields }
onChangeView={ setView }
actions={ actions }
isItemClickable={ () => false }
defaultLayouts={ {
[ LAYOUT_TABLE ]: {},
} }
/>
</div>
);
};

export default RequiredFilterComponent;
Loading
Loading