Skip to content

Commit

Permalink
Add: Reorder control at the field level on the new view config UI. (W…
Browse files Browse the repository at this point in the history
…ordPress#64381)

Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
Co-authored-by: paulwilde <paulwilde@git.wordpress.org>
  • Loading branch information
4 people authored and bph committed Aug 31, 2024
1 parent ddce303 commit a41cf1a
Show file tree
Hide file tree
Showing 2 changed files with 277 additions and 38 deletions.
296 changes: 258 additions & 38 deletions packages/dataviews/src/components/dataviews-view-config/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import {
__experimentalHeading as Heading,
__experimentalText as Text,
privateApis as componentsPrivateApis,
BaseControl,
} from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';
import { __, _x, sprintf } from '@wordpress/i18n';
import { memo, useContext, useState, useMemo } from '@wordpress/element';
import { cog, seen, unseen } from '@wordpress/icons';
import { chevronDown, chevronUp, cog, seen, unseen } from '@wordpress/icons';
import warning from '@wordpress/warning';

/**
Expand All @@ -33,11 +34,12 @@ import warning from '@wordpress/warning';
import {
SORTING_DIRECTIONS,
LAYOUT_GRID,
LAYOUT_TABLE,
sortIcons,
sortLabels,
} from '../../constants';
import { VIEW_LAYOUTS, getMandatoryFields } from '../../dataviews-layouts';
import type { SupportedLayouts } from '../../types';
import type { NormalizedField, SupportedLayouts, View } from '../../types';
import DataViewsContext from '../dataviews-context';
import { unlock } from '../../lock-unlock';
import DensityPicker from '../../dataviews-layouts/grid/density-picker';
Expand Down Expand Up @@ -230,50 +232,268 @@ function ItemsPerPageControl() {
);
}

function FieldControl() {
const { view, fields, onChangeView } = useContext( DataViewsContext );
const mandatoryFields = getMandatoryFields( view );
const hidableFields = fields.filter(
( field ) =>
field.enableHiding !== false &&
! mandatoryFields.includes( field.id )
);
const viewFields = view.fields || fields.map( ( field ) => field.id );
if ( ! hidableFields?.length ) {
return null;
function FieldItem( {
fields,
fieldId,
mandatoryFields,
viewFields,
view,
onChangeView,
}: {
fields: NormalizedField< any >[];
fieldId: string;
mandatoryFields: string | any[];
viewFields: string[];
view: View;
onChangeView: ( view: View ) => void;
} ) {
let fieldLabel;
let fieldIsHidable;
const fieldObject = fields.find(
( f ) => f.id === fieldId
) as NormalizedField< any >;
if ( fieldObject ) {
fieldLabel = fieldObject.label;
fieldIsHidable =
fieldObject.enableHiding !== false &&
! mandatoryFields.includes( fieldId );
} else if ( view.type === LAYOUT_TABLE ) {
const combinedFieldObject = view.layout?.combinedFields?.find(
( f ) => f.id === fieldId
);
if ( combinedFieldObject ) {
fieldLabel = combinedFieldObject.label;
fieldIsHidable = ! mandatoryFields.includes( fieldId );
}
}

const index = view.fields?.indexOf( fieldId ) as number;
const isVisible = viewFields.includes( fieldId );
return (
<ItemGroup isBordered isSeparated>
{ hidableFields?.map( ( field ) => {
const isVisible = viewFields.includes( field.id );
return (
<Item key={ field.id }>
<HStack expanded>
<span>{ field.label }</span>
<Item key={ fieldId }>
<HStack
expanded
className={ `dataviews-field-control__field dataviews-field-control__field-${ fieldId }` }
>
<span>{ fieldLabel }</span>
<HStack
justify="flex-end"
expanded={ false }
className="dataviews-field-control__actions"
>
{ view.type === LAYOUT_TABLE && isVisible && (
<>
<Button
disabled={ ! isVisible || index < 1 }
accessibleWhenDisabled
size="compact"
onClick={ () =>
onClick={ () => {
if ( ! view.fields || index < 1 ) {
return;
}
onChangeView( {
...view,
fields: isVisible
? viewFields.filter(
( id ) => id !== field.id
)
: [ ...viewFields, field.id ],
} )
fields: [
...( view.fields.slice(
0,
index - 1
) ?? [] ),
fieldId,
view.fields[ index - 1 ],
...view.fields.slice( index + 1 ),
],
} );
} }
icon={ chevronUp }
label={ sprintf(
/* translators: %s: field label */
__( 'Move %s up' ),
fieldLabel
) }
/>
<Button
disabled={
! isVisible ||
! view.fields ||
index >= view.fields.length - 1
}
icon={ isVisible ? seen : unseen }
label={
isVisible
? __( 'Hide field' )
: __( 'Show field' )
accessibleWhenDisabled
size="compact"
onClick={ () => {
if (
! view.fields ||
index >= view.fields.length - 1
) {
return;
}
onChangeView( {
...view,
fields: [
...( view.fields.slice(
0,
index
) ?? [] ),
view.fields[ index + 1 ],
fieldId,
...view.fields.slice( index + 2 ),
],
} );
} }
icon={ chevronDown }
label={ sprintf(
/* translators: %s: field label */
__( 'Move %s down' ),
fieldLabel
) }
/>{ ' ' }
</>
) }
<Button
className="dataviews-field-control__field-visibility-button"
disabled={ ! fieldIsHidable }
accessibleWhenDisabled
size="compact"
onClick={ () => {
onChangeView( {
...view,
fields: isVisible
? viewFields.filter(
( id ) => id !== fieldId
)
: [ ...viewFields, fieldId ],
} );
// Focus the visibility button to avoid focus loss.
// Our code is safe against the component being unmounted, so we don't need to worry about cleaning the timeout.
// eslint-disable-next-line @wordpress/react-no-unsafe-timeout
setTimeout( () => {
const element = document.querySelector(
`.dataviews-field-control__field-${ fieldId } .dataviews-field-control__field-visibility-button`
);
if ( element instanceof HTMLElement ) {
element.focus();
}
/>
</HStack>
</Item>
}, 50 );
} }
icon={ isVisible ? seen : unseen }
label={
isVisible
? sprintf(
/* translators: %s: field label */
__( 'Hide %s' ),
fieldLabel
)
: sprintf(
/* translators: %s: field label */
__( 'Show %s' ),
fieldLabel
)
}
/>
</HStack>
</HStack>
</Item>
);
}

function FieldList( {
fields,
fieldIds,
mandatoryFields,
viewFields,
view,
onChangeView,
}: Omit< Parameters< typeof FieldItem >[ 0 ], 'fieldId' > & {
fieldIds: string[];
} ) {
return fieldIds.map( ( fieldId ) => (
<FieldItem
key={ fieldId }
fields={ fields }
fieldId={ fieldId }
mandatoryFields={ mandatoryFields }
viewFields={ viewFields }
view={ view }
onChangeView={ onChangeView }
/>
) );
}

function FieldControl() {
const { view, fields, onChangeView } = useContext( DataViewsContext );
const mandatoryFields = useMemo(
() => getMandatoryFields( view ),
[ view ]
);
const viewFields = view.fields || fields.map( ( field ) => field.id );
const visibleFields = view.fields;
const hiddenFields = useMemo( () => {
const nonViewFieldsList = fields
.filter(
( field ) =>
! viewFields.includes( field.id ) &&
! mandatoryFields?.includes( field.id )
)
.map( ( field ) => field.id );

if ( view.type !== LAYOUT_TABLE ) {
return nonViewFieldsList;
}
const nonViewFieldsAndNonCombinedList = nonViewFieldsList.filter(
( fieldId ) => {
return ! view.layout?.combinedFields?.some( ( combinedField ) =>
combinedField.children.includes( fieldId )
);
} ) }
</ItemGroup>
}
);
const nonViewFieldsCombinedFieldsList =
view.layout?.combinedFields
?.filter(
( combinedField ) =>
! viewFields.includes( combinedField.id )
)
.map( ( combinedField ) => combinedField.id ) || [];
return [
...nonViewFieldsAndNonCombinedList,
...nonViewFieldsCombinedFieldsList,
];
}, [ view, mandatoryFields, fields, viewFields ] );
if ( ! visibleFields?.length && ! hiddenFields?.length ) {
return null;
}
return (
<VStack spacing={ 6 } className="dataviews-field-control">
{ !! visibleFields?.length && (
<ItemGroup isBordered isSeparated>
<FieldList
fields={ fields }
fieldIds={ visibleFields }
mandatoryFields={ mandatoryFields }
viewFields={ viewFields }
view={ view }
onChangeView={ onChangeView }
/>
</ItemGroup>
) }
{ !! hiddenFields?.length && (
<>
<VStack spacing={ 4 }>
<BaseControl.VisualLabel style={ { margin: 0 } }>
{ __( 'Hidden' ) }
</BaseControl.VisualLabel>
<ItemGroup isBordered isSeparated>
<FieldList
fields={ fields }
fieldIds={ hiddenFields }
mandatoryFields={ mandatoryFields }
viewFields={ viewFields }
view={ view }
onChangeView={ onChangeView }
/>
</ItemGroup>
</VStack>
</>
) }
</VStack>
);
}

Expand Down
19 changes: 19 additions & 0 deletions packages/dataviews/src/components/dataviews-view-config/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,22 @@
}
}
}
.dataviews-field-control__field {
height: $grid-unit-40;
}

.dataviews-field-control__actions {
position: absolute;
top: -9999em;
}
.dataviews-field-control__actions.dataviews-field-control__actions {
gap: $grid-unit-05;
}

.dataviews-field-control__field:hover,
.dataviews-field-control__field:focus-within {
.dataviews-field-control__actions {
position: unset;
top: unset;
}
}

0 comments on commit a41cf1a

Please sign in to comment.