From 8854c0ab50aff548696a7b5f155d179bfc059b78 Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Wed, 26 Jul 2023 17:50:13 -0300 Subject: [PATCH] feat(homepage-posts): sortable post selection --- package-lock.json | 28 ++++++- package.json | 1 + src/components/autocomplete-tokenfield.js | 85 +++++++++++++++++++-- src/components/autocomplete-tokenfield.scss | 22 ++++++ src/components/query-controls.js | 3 +- 5 files changed, 130 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index b59b00a89..11df00d31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "lodash": "^4.17.21", "newspack-components": "^2.1.0", "react": "^17.0.2", + "react-sortable-hoc": "^2.0.0", "redux": "^4.2.1", "redux-saga": "^1.2.3", "regenerator-runtime": "^0.13.11", @@ -14703,7 +14704,6 @@ }, "node_modules/invariant": { "version": "2.2.4", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -26338,6 +26338,21 @@ "react": "^16.0.0 || ^17.0.0" } }, + "node_modules/react-sortable-hoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", + "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", + "dependencies": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + }, + "peerDependencies": { + "prop-types": "^15.5.7", + "react": "^16.3.0 || ^17.0.0", + "react-dom": "^16.3.0 || ^17.0.0" + } + }, "node_modules/react-test-renderer": { "version": "17.0.2", "dev": true, @@ -41910,7 +41925,6 @@ }, "invariant": { "version": "2.2.4", - "dev": true, "requires": { "loose-envify": "^1.0.0" } @@ -49783,6 +49797,16 @@ "react-is": "^16.12.0 || ^17.0.0" } }, + "react-sortable-hoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", + "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", + "requires": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + } + }, "react-test-renderer": { "version": "17.0.2", "dev": true, diff --git a/package.json b/package.json index 8d3a24c15..efcc7bc80 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "lodash": "^4.17.21", "newspack-components": "^2.1.0", "react": "^17.0.2", + "react-sortable-hoc": "^2.0.0", "redux": "^4.2.1", "redux-saga": "^1.2.3", "regenerator-runtime": "^0.13.11", diff --git a/src/components/autocomplete-tokenfield.js b/src/components/autocomplete-tokenfield.js index 184e257b9..988e66867 100644 --- a/src/components/autocomplete-tokenfield.js +++ b/src/components/autocomplete-tokenfield.js @@ -2,18 +2,56 @@ * External dependencies */ import { debounce } from 'lodash'; +import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc'; /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { FormTokenField, Spinner } from '@wordpress/components'; +import { Icon, dragHandle, close } from '@wordpress/icons'; /** * Internal dependencies */ import './autocomplete-tokenfield.scss'; +const DragHandle = SortableHandle( () => ( + + + +) ); + +const SortableItem = SortableElement( ( { value, onRemove } ) => ( +
  • + + { value } + +
  • +) ); + +const SortableList = SortableContainer( ( { items, onRemove = () => {} } ) => { + return ( + + ); +} ); + /** * An multi-selecting, api-driven autocomplete input suitable for use in block attributes. */ @@ -99,7 +137,7 @@ class AutocompleteTokenField extends Component { * @param {string} input Input to fetch suggestions for */ updateSuggestions( input ) { - const { fetchSuggestions } = this.props; + const { fetchSuggestions, tokens } = this.props; if ( ! fetchSuggestions ) { return; } @@ -119,6 +157,9 @@ class AutocompleteTokenField extends Component { const currentSuggestions = []; suggestions.forEach( suggestion => { + if ( tokens.includes( suggestion.value.toString() ) ) { + return; + } const trimmedSuggestionLabel = suggestion.label.trim(); const duplicatedSuggestionIndex = currentSuggestions.indexOf( trimmedSuggestionLabel ); if ( duplicatedSuggestionIndex >= 0 ) { @@ -148,8 +189,12 @@ class AutocompleteTokenField extends Component { * @param {Array} tokenStrings An array of token label strings. */ handleOnChange( tokenStrings ) { - const { onChange } = this.props; - onChange( this.getValuesForLabels( tokenStrings ) ); + const { onChange, tokens, sortable } = this.props; + onChange( + sortable // If sortable, the input doesn't carry the value. + ? [ ...tokens, ...this.getValuesForLabels( tokenStrings ) ] + : this.getValuesForLabels( tokenStrings ) + ); } /** @@ -162,21 +207,49 @@ class AutocompleteTokenField extends Component { return this.getLabelsForValues( tokens ); } + onSortEnd = ( { oldIndex, newIndex } ) => { + const { tokens, onChange } = this.props; + const newTokens = [ ...tokens ]; + newTokens.splice( newIndex, 0, newTokens.splice( oldIndex, 1 )[ 0 ] ); + onChange( newTokens ); + }; + + handleRemoveItem = label => () => { + const value = this.getValuesForLabels( [ label ] )[ 0 ]; + const { tokens, onChange } = this.props; + const newTokens = tokens.filter( token => token !== value ); + onChange( newTokens ); + }; + /** * Render. */ render() { - const { help, label = '' } = this.props; + const { help, label = '', placeholder = '', sortable = false } = this.props; const { suggestions, loading } = this.state; - + const value = this.getTokens(); return (
    + { sortable && value?.length ? ( +
    + { value?.length > 1 && ( +

    { __( 'Click and drag the items for sorting:', 'newspack-blocks' ) }

    + ) } + +
    + ) : null } this.handleOnChange( tokens ) } onInputChange={ input => this.debouncedUpdateSuggestions( input ) } label={ label } + placeholder={ placeholder } /> { loading && } { help &&

    { help }

    } diff --git a/src/components/autocomplete-tokenfield.scss b/src/components/autocomplete-tokenfield.scss index 5e1da1a9f..90b5ff261 100644 --- a/src/components/autocomplete-tokenfield.scss +++ b/src/components/autocomplete-tokenfield.scss @@ -15,4 +15,26 @@ .autocomplete-tokenfield__help { font-style: italic; } + + &__sortable__item { + list-style: none; + padding: 12px 6px; + margin: 0; + border: 1px solid #ddd; + margin-bottom: -1px; + background: #fff; + display: flex; + align-items: center; + justify-content: center; + &__drag { + margin-right: 6px; + cursor: grab; + } + button { + background: transparent; + border: 0; + padding: 0; + cursor: pointer; + } + } } diff --git a/src/components/query-controls.js b/src/components/query-controls.js index d2a22e14e..5b85454e6 100644 --- a/src/components/query-controls.js +++ b/src/components/query-controls.js @@ -261,11 +261,12 @@ class QueryControls extends Component { ) } { specificMode ? (