From ac4bae7d20d7ccac58cb2311a48cc694d7db4d8e Mon Sep 17 00:00:00 2001 From: Jakub Brzegowski Date: Fri, 20 Dec 2024 15:17:10 +0100 Subject: [PATCH] IBX-9069: Initial Product Tab (#1397) --- .../scss/ui/modules/_universal.discovery.scss | 12 ++ .../universal-discovery/_collapsible.scss | 81 ++++++++ .../_content.type.selector.list.scss | 23 +++ .../universal-discovery/_filters-panel.scss | 3 + .../universal-discovery/_filters-row.scss | 3 + .../modules/universal-discovery/_filters.scss | 138 +------------ .../modules/universal-discovery/_search.scss | 18 +- .../_selected.items.panel.item.scss | 3 + .../_selected.items.panel.scss | 3 + .../_selected.locations.item.scss | 55 +----- .../_selected.locations.scss | 53 +---- .../mixins/_collapsible-arrow.scss | 32 +++ .../mixins/_filters-panel.scss | 79 ++++++++ .../mixins/_filters-row.scss | 12 ++ .../mixins/_selected.items.panel.item.scss | 56 ++++++ .../mixins/_selected.items.panel.scss | 77 ++++++++ .../ibexa_universal_discovery_widget.en.xliff | 25 +++ .../modules/common/table/table.body.cell.js | 47 +++++ .../src/modules/common/table/table.body.js | 25 +++ .../modules/common/table/table.body.row.js | 37 ++++ .../modules/common/table/table.head.cell.js | 59 ++++++ .../src/modules/common/table/table.head.js | 18 ++ .../modules/common/table/table.head.row.js | 25 +++ .../ui-dev/src/modules/common/table/table.js | 28 +++ .../components/actions-menu/actions.menu.js | 26 ++- .../components/collapsible/collapsible.js | 10 +- .../content.type.selector.js | 4 +- .../components/filters/filters.js | 43 ++--- .../components/filters/filters.panel.js | 46 +++++ .../components/filters/filters.row.js | 30 +++ .../components/search/search.js | 10 +- .../selected.items.panel.item.js | 84 ++++++++ .../selected-items/selected.items.panel.js | 154 +++++++++++++++ .../selected.locations.item.js | 14 +- .../selected-locations/selected.locations.js | 10 +- .../components/sort-switcher/sort.switcher.js | 18 +- .../universal-discovery/components/tab/tab.js | 20 +- .../toggle-selection/toggle.item.selection.js | 42 ++++ .../components/top-menu/top.menu.js | 6 +- .../top-menu/top.menu.search.input.js | 17 +- .../components/view-switcher/view.switcher.js | 7 +- .../hooks/usePaginableFetch.js | 64 ++++++ .../hooks/useSelectedItemsReducer.js | 80 ++++++++ .../universal-discovery/search.tab.module.js | 4 +- .../universal.discovery.module.js | 182 +++++++++++------- 45 files changed, 1376 insertions(+), 407 deletions(-) create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/_collapsible.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/_content.type.selector.list.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-panel.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-row.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_collapsible-arrow.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-panel.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-row.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.item.scss create mode 100644 src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.scss create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.body.cell.js create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.body.js create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.body.row.js create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.head.cell.js create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.head.js create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.head.row.js create mode 100644 src/bundle/ui-dev/src/modules/common/table/table.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/hooks/usePaginableFetch.js create mode 100644 src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js diff --git a/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss b/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss index 022c324534..13bfec06d7 100644 --- a/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss +++ b/src/bundle/Resources/public/scss/ui/modules/_universal.discovery.scss @@ -1,13 +1,21 @@ +@import 'universal-discovery/mixins/collapsible-arrow'; +@import 'universal-discovery/mixins/filters-panel'; +@import 'universal-discovery/mixins/filters-row'; +@import 'universal-discovery/mixins/selected.items.panel.item'; +@import 'universal-discovery/mixins/selected.items.panel'; + @import 'universal-discovery/main'; @import 'universal-discovery/tab'; @import 'universal-discovery/top.menu'; @import 'universal-discovery/top.menu.search.input'; @import 'universal-discovery/actions.menu'; +@import 'universal-discovery/collapsible'; @import 'universal-discovery/content.create'; @import 'universal-discovery/content.create.widget'; @import 'universal-discovery/content.create.button'; @import 'universal-discovery/content.edit'; @import 'universal-discovery/content.edit.button'; +@import 'universal-discovery/content.type.selector.list'; @import 'universal-discovery/sort.switcher'; @import 'universal-discovery/view.switcher'; @import 'universal-discovery/tab.selector'; @@ -15,6 +23,8 @@ @import 'universal-discovery/finder.branch'; @import 'universal-discovery/finder.leaf'; @import 'universal-discovery/content.meta.preview'; +@import 'universal-discovery/selected.items.panel.item'; +@import 'universal-discovery/selected.items.panel'; @import 'universal-discovery/selected.locations'; @import 'universal-discovery/selected.locations.item'; @import 'universal-discovery/grid'; @@ -23,6 +33,8 @@ @import 'universal-discovery/search.tags'; @import 'universal-discovery/content.table.item'; @import 'universal-discovery/content.table'; +@import 'universal-discovery/filters-panel'; +@import 'universal-discovery/filters-row'; @import 'universal-discovery/filters'; @import 'universal-discovery/bookmarks.list'; @import 'universal-discovery/translation.selector'; diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_collapsible.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_collapsible.scss new file mode 100644 index 0000000000..112a96f281 --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_collapsible.scss @@ -0,0 +1,81 @@ +.c-collapsible { + $self: &; + + & + .c-filters-row, + & + .c-filters__row { + border-top: calculateRem(1px) solid $ibexa-color-light; + } + + &--hidden { + #{$self} { + &__title { + box-shadow: none; + border-bottom-color: transparent; + + &:before { + transform: rotate(135deg); + } + + &:after { + transform: rotate(-135deg); + } + } + + &__content { + transform: scaleY(0); + opacity: 0; + height: 0; + } + } + } + + &__title { + position: relative; + cursor: pointer; + padding: 0 calculateRem(24px); + font-size: $ibexa-text-font-size-medium; + font-weight: 600; + min-height: calculateRem(40px); + display: flex; + align-items: center; + border-style: solid; + border-color: $ibexa-color-light; + border-width: calculateRem(1px) 0; + box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.05); + + @include collapsible-arrow; + } + + &__content { + transform: scaleY(1); + transform-origin: top center; + } + + &__content-wrapper { + padding: calculateRem(16px) calculateRem(24px); + } + + &__list { + padding: 0; + margin-bottom: 0; + list-style-type: none; + } + + &__list-item { + padding: calculateRem(6px) 0; + + .form-check { + width: 100%; + } + + .form-check-label { + width: 100%; + text-overflow: ellipsis; + overflow: hidden; + } + + .ibexa-input--checkbox { + margin-right: calculateRem(8px); + } + } +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_content.type.selector.list.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_content.type.selector.list.scss new file mode 100644 index 0000000000..569997d169 --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_content.type.selector.list.scss @@ -0,0 +1,23 @@ +.c-content-type-selector-list { + padding: 0; + margin-bottom: 0; + list-style-type: none; + + &__item { + padding: calculateRem(6px) 0; + + .form-check { + width: 100%; + } + + .form-check-label { + width: 100%; + text-overflow: ellipsis; + overflow: hidden; + } + + .ibexa-input--checkbox { + margin-right: calculateRem(8px); + } + } +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-panel.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-panel.scss new file mode 100644 index 0000000000..420e40af0c --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-panel.scss @@ -0,0 +1,3 @@ +.c-filters-panel { + @include c-filters-panel('.c-filters-row'); +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-row.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-row.scss new file mode 100644 index 0000000000..3113adaae9 --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters-row.scss @@ -0,0 +1,3 @@ +.c-filters-row { + @include c-filters-row('__title'); +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters.scss index 3cff3be9d1..50260db629 100644 --- a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters.scss +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_filters.scss @@ -1,145 +1,11 @@ .c-filters { - position: relative; - width: 100%; - height: 100%; - overflow: auto; - padding-bottom: calculateRem(24px); - - &__header { - display: flex; - position: sticky; - top: 0; - background-color: $ibexa-color-white; - z-index: 2; - align-items: center; - justify-content: space-between; - padding: calculateRem(16px) calculateRem(12px); - border-bottom: calculateRem(1px) solid $ibexa-color-light; - } - - &__header-content { - font-size: $ibexa-text-font-size-large; - font-weight: 600; - margin-right: calculateRem(8px); - } - - &__header-actions { - display: flex; - flex-wrap: nowrap; - } - - &__row-title { - position: relative; - font-size: $ibexa-text-font-size-medium; - font-weight: 600; - min-height: calculateRem(40px); - display: flex; - align-items: center; - } - - &__collapsible { - & + .c-filters__row { - border-top: calculateRem(1px) solid $ibexa-color-light; - } - - &--hidden { - .c-filters { - &__collapsible-title { - box-shadow: none; - border-bottom-color: transparent; - - &:before { - transform: rotate(135deg); - } - - &:after { - transform: rotate(-135deg); - } - } - - &__collapsible-content { - transform: scaleY(0); - opacity: 0; - height: 0; - } - } - } - } - - &__collapsible-title { - position: relative; - cursor: pointer; - padding: 0 calculateRem(24px); - font-size: $ibexa-text-font-size-medium; - font-weight: 600; - min-height: calculateRem(40px); - display: flex; - align-items: center; - border-style: solid; - border-color: $ibexa-color-light; - border-width: calculateRem(1px) 0; - box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.05); - - &::before, - &::after { - content: ''; - position: absolute; - z-index: 1; - top: 50%; - width: calculateRem(6px); - height: calculateRem(1px); - background: $ibexa-color-dark; - } - - &::before { - transform: rotate(225deg); - right: calculateRem(12px); - } - - &::after { - transform: rotate(-225deg); - right: calculateRem(16px); - } - } - - &__collapsible-content { - transform: scaleY(1); - transform-origin: top center; - } - - &__collapsible-content-wrapper { - padding: calculateRem(16px) calculateRem(24px); - } + @include c-filters-panel('.c-filters__row'); &__row { - padding: calculateRem(4px) calculateRem(24px); + @include c-filters-row('-title'); &--language { padding-bottom: calculateRem(16px); } } - - &__collapsible-list { - padding: 0; - margin-bottom: 0; - list-style-type: none; - } - - &__collapsible-list-item { - padding: calculateRem(6px) 0; - - .form-check { - width: 100%; - } - - .form-check-label { - width: 100%; - text-overflow: ellipsis; - overflow: hidden; - } - - .ibexa-input--checkbox { - margin-right: calculateRem(8px); - } - } } diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss index 102cc9048f..ed8dc3ce20 100644 --- a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_search.scss @@ -19,13 +19,15 @@ height: 100%; } - &__sidebar { - display: flex; + &__content-meta-preview:not(:empty) { + flex: 1; height: 100%; - min-width: calculateRem(270px); - margin-right: calculateRem(24px); - border-right: calculateRem(1px) solid $ibexa-color-light; - background-color: $ibexa-color-white; + overflow: auto; + border-left: calculateRem(1px) solid $ibexa-color-light; + } + + &__filters { + border-left: calculateRem(1px) solid $ibexa-color-light; } &__spinner-wrapper { @@ -41,12 +43,13 @@ } &__content { + flex: 2; display: flex; flex-direction: column; overflow: auto; width: 100%; flex-shrink: 1; - padding: 0 calculateRem(8px); + padding: calculateRem(24px); background-color: $ibexa-color-white; position: relative; } @@ -73,7 +76,6 @@ display: grid; grid-template: 'title clear-search-btn' 'subtitle subtitle'; justify-content: start; - margin-top: calculateRem(16px); } &__table-tile { diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss new file mode 100644 index 0000000000..8a6936f1ed --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.item.scss @@ -0,0 +1,3 @@ +.c-selected-items-panel-item { + @include c-selected-items-panel-item; +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss new file mode 100644 index 0000000000..ee365ddb9b --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.items.panel.scss @@ -0,0 +1,3 @@ +.c-selected-items-panel { + @include c-selected-items-panel; +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.item.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.item.scss index e117b7fd2f..aeb0695b08 100644 --- a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.item.scss +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.item.scss @@ -1,56 +1,3 @@ .c-selected-locations-item { - display: flex; - align-items: center; - padding: calculateRem(5px); - margin-bottom: calculateRem(8px); - border: calculateRem(1px) solid $ibexa-color-light; - border-radius: $ibexa-border-radius; - box-shadow: calculateRem(4px) calculateRem(2px) calculateRem(17px) 0 rgba($ibexa-color-black, 0.05); - - &__image-wrapper { - width: calculateRem(42px); - height: calculateRem(42px); - background-color: $ibexa-color-light-300; - border-radius: $ibexa-border-radius-small; - display: flex; - justify-content: center; - align-items: center; - } - - &__info { - display: flex; - flex-direction: column; - justify-content: center; - flex: 1; - width: calculateRem(185px); - padding: 0 calculateRem(15px); - } - - &__info-name { - font-size: $ibexa-text-font-size; - } - - &__info-description { - font-size: $ibexa-text-font-size-small; - color: $ibexa-color-dark-400; - } - - &__actions-wrapper { - display: flex; - justify-content: flex-end; - align-items: center; - } - - &__remove-button { - width: calculateRem(32px); - height: calculateRem(32px); - padding: 0; - display: inline-flex; - justify-content: center; - align-items: center; - - .ibexa-icon { - fill: $ibexa-color-dark; - } - } + @include c-selected-items-panel-item; } diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss index f4f33d1e42..4bd72c23a6 100644 --- a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/_selected.locations.scss @@ -1,54 +1,3 @@ .c-selected-locations { - background-color: $ibexa-color-white; - position: fixed; - top: calculateRem(95px); - right: calculateRem(16px); - min-height: calculateRem(60px); - min-width: calculateRem(390px); - border: calculateRem(1px) solid $ibexa-color-light; - border-top-left-radius: $ibexa-border-radius; - border-bottom-left-radius: $ibexa-border-radius; - box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.15); - z-index: 1; - transition: all $ibexa-admin-transition-duration $ibexa-admin-transition; - - &__header { - display: flex; - justify-content: space-between; - align-items: center; - padding: calculateRem(7px) calculateRem(12px) calculateRem(7px) calculateRem(22px); - } - - &__selection-counter { - color: $ibexa-color-dark; - font-size: calculateRem(22px); - font-weight: 600; - padding-right: calculateRem(16px); - } - - &--expanded { - bottom: calculateRem(100px); - min-width: calculateRem(491px); - overflow: hidden; - - .c-selected-locations { - &__items-wrapper { - display: block; - } - } - } - - &__items-wrapper { - display: none; - overflow: auto; - padding: 0 calculateRem(38px) calculateRem(16px) calculateRem(22px); - border-top: calculateRem(1px) solid $ibexa-color-light; - height: calc(100% - calculateRem(70px)); - } - - &__actions { - padding: calculateRem(16px) 0; - display: flex; - justify-content: flex-end; - } + @include c-selected-items-panel; } diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_collapsible-arrow.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_collapsible-arrow.scss new file mode 100644 index 0000000000..c6f1e12c42 --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_collapsible-arrow.scss @@ -0,0 +1,32 @@ +@mixin collapsible-arrow { + &::before, + &::after { + content: ''; + position: absolute; + z-index: 1; + top: 50%; + width: calculateRem(6px); + height: calculateRem(1px); + background: $ibexa-color-dark; + } + + &::before { + transform: rotate(225deg); + right: calculateRem(12px); + } + + &::after { + transform: rotate(-225deg); + right: calculateRem(16px); + } +} + +@mixin collapsible-arrow-collapsed-state { + &::before { + transform: rotate(135deg); + } + + &::after { + transform: rotate(-135deg); + } +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-panel.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-panel.scss new file mode 100644 index 0000000000..2cee7df40e --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-panel.scss @@ -0,0 +1,79 @@ +@mixin c-filters-panel($filterRowCssClass) { + position: relative; + width: 100%; + height: 100%; + overflow: auto; + padding-bottom: calculateRem(24px); + + &__header { + display: flex; + position: sticky; + top: 0; + background-color: $ibexa-color-white; + z-index: 2; + align-items: center; + justify-content: space-between; + padding: calculateRem(16px) calculateRem(12px); + border-bottom: calculateRem(1px) solid $ibexa-color-light; + } + + &__header-content { + font-size: $ibexa-text-font-size-large; + font-weight: 600; + margin-right: calculateRem(8px); + } + + &__header-actions { + display: flex; + flex-wrap: nowrap; + } + + &__collapsible { + & + #{$filterRowCssClass} { + border-top: calculateRem(1px) solid $ibexa-color-light; + } + + &--hidden { + .c-filters { + &__collapsible-title { + box-shadow: none; + border-bottom-color: transparent; + + @include collapsible-arrow-collapsed-state; + } + + &__collapsible-content { + transform: scaleY(0); + opacity: 0; + height: 0; + } + } + } + } + + &__collapsible-title { + position: relative; + cursor: pointer; + padding: 0 calculateRem(24px); + font-size: $ibexa-text-font-size-medium; + font-weight: 600; + min-height: calculateRem(40px); + display: flex; + align-items: center; + border-style: solid; + border-color: $ibexa-color-light; + border-width: calculateRem(1px) 0; + box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.05); + + @include collapsible-arrow; + } + + &__collapsible-content { + transform: scaleY(1); + transform-origin: top center; + } + + &__collapsible-content-wrapper { + padding: calculateRem(16px) calculateRem(24px); + } +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-row.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-row.scss new file mode 100644 index 0000000000..1ebb2856ae --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_filters-row.scss @@ -0,0 +1,12 @@ +@mixin c-filters-row($titleSubclass) { + padding: calculateRem(4px) calculateRem(24px); + + &#{$titleSubclass} { + position: relative; + font-size: $ibexa-text-font-size-medium; + font-weight: 600; + min-height: calculateRem(40px); + display: flex; + align-items: center; + } +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.item.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.item.scss new file mode 100644 index 0000000000..ca34cfdee4 --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.item.scss @@ -0,0 +1,56 @@ +@mixin c-selected-items-panel-item { + display: flex; + align-items: center; + padding: calculateRem(5px); + margin-bottom: calculateRem(8px); + border: calculateRem(1px) solid $ibexa-color-light; + border-radius: $ibexa-border-radius; + box-shadow: calculateRem(4px) calculateRem(2px) calculateRem(17px) 0 rgba($ibexa-color-black, 0.05); + + &__image-wrapper { + width: calculateRem(42px); + height: calculateRem(42px); + background-color: $ibexa-color-light-300; + border-radius: $ibexa-border-radius-small; + display: flex; + justify-content: center; + align-items: center; + } + + &__info { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1; + width: calculateRem(185px); + padding: 0 calculateRem(15px); + } + + &__info-name { + font-size: $ibexa-text-font-size; + } + + &__info-description { + font-size: $ibexa-text-font-size-small; + color: $ibexa-color-dark-400; + } + + &__actions-wrapper { + display: flex; + justify-content: flex-end; + align-items: center; + } + + &__remove-button { + width: calculateRem(32px); + height: calculateRem(32px); + padding: 0; + display: inline-flex; + justify-content: center; + align-items: center; + + .ibexa-icon { + fill: $ibexa-color-dark; + } + } +} diff --git a/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.scss b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.scss new file mode 100644 index 0000000000..1b6caa6d38 --- /dev/null +++ b/src/bundle/Resources/public/scss/ui/modules/universal-discovery/mixins/_selected.items.panel.scss @@ -0,0 +1,77 @@ +@mixin c-selected-items-panel { + $self: &; + + background-color: $ibexa-color-white; + position: fixed; + top: calc(100vh - calculateRem(98px)); + bottom: calculateRem(31px); + left: calculateRem(16px); + min-height: calculateRem(60px); + border: calculateRem(1px) solid $ibexa-color-light; + border-top-right-radius: $ibexa-border-radius; + border-bottom-right-radius: $ibexa-border-radius; + box-shadow: calculateRem(4px) calculateRem(22px) calculateRem(47px) 0 rgba($ibexa-color-info, 0.15); + z-index: 1; + transition: all $ibexa-admin-transition-duration $ibexa-admin-transition; + + &__header { + display: flex; + justify-content: start; + align-items: center; + padding: calculateRem(16px); + } + + &__selection-counter { + color: $ibexa-color-dark; + font-size: calculateRem(22px); + font-weight: 600; + padding-right: calculateRem(16px); + } + + &--expanded { + bottom: calculateRem(16px); + top: calculateRem(88px); + min-width: calculateRem(491px); + overflow: hidden; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: $ibexa-border-radius; + + #{$self} { + &__items-wrapper { + display: block; + } + + &__toggle-button-icon { + transform: rotate(0); + } + } + } + + &__items-wrapper { + display: none; + overflow: auto; + padding: 0 calculateRem(38px) calculateRem(16px) calculateRem(22px); + border-top: calculateRem(1px) solid $ibexa-color-light; + height: calc(100% - calculateRem(70px)); + } + + &__actions { + padding: calculateRem(16px) 0; + display: flex; + justify-content: flex-end; + } + + &__toggle-button { + display: flex; + width: calculateRem(32px); + height: calculateRem(32px); + justify-content: center; + align-items: center; + margin-right: calculateRem(32px); + } + + &__toggle-button-icon { + transform: rotate(180deg); + } +} diff --git a/src/bundle/Resources/translations/ibexa_universal_discovery_widget.en.xliff b/src/bundle/Resources/translations/ibexa_universal_discovery_widget.en.xliff index d6db65d262..2395ecfb49 100644 --- a/src/bundle/Resources/translations/ibexa_universal_discovery_widget.en.xliff +++ b/src/bundle/Resources/translations/ibexa_universal_discovery_widget.en.xliff @@ -296,6 +296,31 @@ Cannot find children Locations with ID %idList% key: select_location.error + + Collapse sidebar + Collapse sidebar + key: selected_items.collapse.sidebar + + + {1}Deselect|[2,Inf]Deselect all + {1}Deselect|[2,Inf]Deselect all + key: selected_items.deselect_all + + + Expand sidebar + Expand sidebar + key: selected_items.expand.sidebar + + + {1}%count% selected item|[2,Inf]%count% selected items + {1}%count% selected item|[2,Inf]%count% selected items + key: selected_items.selection_info + + + Clear selection + Clear selection + key: selected_items_panel.item.remove_item + Clear selection Clear selection diff --git a/src/bundle/ui-dev/src/modules/common/table/table.body.cell.js b/src/bundle/ui-dev/src/modules/common/table/table.body.cell.js new file mode 100644 index 0000000000..f867c5570a --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.body.cell.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../helpers/css.class.names'; + +const TableBodyCell = ({ extraClasses, children, hasCheckbox, hasActionBtns, hasIcon, isCloseLeft, isCenterContent }) => { + const className = createCssClassNames({ + 'ibexa-table__cell': true, + 'ibexa-table__cell--has-checkbox': hasCheckbox, + 'ibexa-table__cell--has-action-btns': hasActionBtns, + 'ibexa-table__cell--has-icon': hasIcon, + 'ibexa-table__cell--close-left': isCloseLeft, + 'ibexa-table__cell--content-center': isCenterContent, + [extraClasses]: true, + }); + const wrapChildrenIfNeeded = (childrenToWrap) => { + if (hasActionBtns) { + return
{childrenToWrap}
; + } + + return childrenToWrap; + }; + + return {wrapChildrenIfNeeded(children)}; +}; + +TableBodyCell.propTypes = { + extraClasses: PropTypes.string, + children: PropTypes.element, + hasCheckbox: PropTypes.bool, + hasActionBtns: PropTypes.bool, + hasIcon: PropTypes.bool, + isCloseLeft: PropTypes.bool, + isCenterContent: PropTypes.bool, +}; + +TableBodyCell.defaultProps = { + extraClasses: '', + children: null, + hasCheckbox: false, + hasActionBtns: false, + hasIcon: false, + isCloseLeft: false, + isCenterContent: false, +}; + +export default TableBodyCell; diff --git a/src/bundle/ui-dev/src/modules/common/table/table.body.js b/src/bundle/ui-dev/src/modules/common/table/table.body.js new file mode 100644 index 0000000000..d4885cec98 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.body.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../helpers/css.class.names'; + +const TableBody = ({ extraClasses, children }) => { + const className = createCssClassNames({ + 'ibexa-table__body': true, + [extraClasses]: true, + }); + + return {children}; +}; + +TableBody.propTypes = { + extraClasses: PropTypes.string, + children: PropTypes.element, +}; + +TableBody.defaultProps = { + extraClasses: '', + children: null, +}; + +export default TableBody; diff --git a/src/bundle/ui-dev/src/modules/common/table/table.body.row.js b/src/bundle/ui-dev/src/modules/common/table/table.body.row.js new file mode 100644 index 0000000000..4954659d30 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.body.row.js @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../helpers/css.class.names'; + +const TableBodyRow = ({ extraClasses, children, isSelectable, isNotSelectable, onClick }) => { + const className = createCssClassNames({ + 'ibexa-table__row': true, + 'ibexa-table__row--selectable': isSelectable, + 'ibexa-table__row--not-selectable': isNotSelectable, + [extraClasses]: true, + }); + + return ( + + {children} + + ); +}; + +TableBodyRow.propTypes = { + extraClasses: PropTypes.string, + children: PropTypes.element, + isSelectable: PropTypes.bool, + isNotSelectable: PropTypes.bool, + onClick: PropTypes.func, +}; + +TableBodyRow.defaultProps = { + extraClasses: '', + children: null, + isSelectable: false, + isNotSelectable: false, + onClick: () => {}, +}; + +export default TableBodyRow; diff --git a/src/bundle/ui-dev/src/modules/common/table/table.head.cell.js b/src/bundle/ui-dev/src/modules/common/table/table.head.cell.js new file mode 100644 index 0000000000..5a119f9802 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.head.cell.js @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../helpers/css.class.names'; + +const TableHeadCell = ({ + extraClasses, + wrapperExtraClasses, + children, + sortColumnName, + hasCheckbox, + hasIcon, + isCloseLeft, + isCenterContent, +}) => { + const className = createCssClassNames({ + 'ibexa-table__header-cell': true, + 'ibexa-table__header-cell--has-checkbox': hasCheckbox, + 'ibexa-table__header-cell--has-icon': hasIcon, + 'ibexa-table__header-cell--close-left': isCloseLeft, + 'ibexa-table__header-cell--content-center': isCenterContent, + [extraClasses]: true, + }); + const cellTextWrapperClassName = createCssClassNames({ + 'ibexa-table__header-cell-text-wrapper': true, + [`ibexa-table__sort-column--${sortColumnName}`]: sortColumnName, + [wrapperExtraClasses]: true, + }); + + return ( + + {children} + + ); +}; + +TableHeadCell.propTypes = { + extraClasses: PropTypes.string, + wrapperExtraClasses: PropTypes.string, + children: PropTypes.element, + sortColumnName: PropTypes.string, + hasCheckbox: PropTypes.bool, + hasIcon: PropTypes.bool, + isCloseLeft: PropTypes.bool, + isCenterContent: PropTypes.bool, +}; + +TableHeadCell.defaultProps = { + extraClasses: '', + wrapperExtraClasses: '', + children: null, + sortColumnName: null, + hasCheckbox: false, + hasIcon: false, + isCloseLeft: false, + isCenterContent: false, +}; + +export default TableHeadCell; diff --git a/src/bundle/ui-dev/src/modules/common/table/table.head.js b/src/bundle/ui-dev/src/modules/common/table/table.head.js new file mode 100644 index 0000000000..388b94da1e --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.head.js @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const TableHead = ({ extraClasses, children }) => { + return {children}; +}; + +TableHead.propTypes = { + extraClasses: PropTypes.string, + children: PropTypes.element, +}; + +TableHead.defaultProps = { + extraClasses: '', + children: null, +}; + +export default TableHead; diff --git a/src/bundle/ui-dev/src/modules/common/table/table.head.row.js b/src/bundle/ui-dev/src/modules/common/table/table.head.row.js new file mode 100644 index 0000000000..5851601d62 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.head.row.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../helpers/css.class.names'; + +const TableHeadRow = ({ extraClasses, children }) => { + const className = createCssClassNames({ + 'ibexa-table__head-row': true, + [extraClasses]: true, + }); + + return {children}; +}; + +TableHeadRow.propTypes = { + extraClasses: PropTypes.string, + children: PropTypes.element, +}; + +TableHeadRow.defaultProps = { + extraClasses: '', + children: null, +}; + +export default TableHeadRow; diff --git a/src/bundle/ui-dev/src/modules/common/table/table.js b/src/bundle/ui-dev/src/modules/common/table/table.js new file mode 100644 index 0000000000..94611b02ff --- /dev/null +++ b/src/bundle/ui-dev/src/modules/common/table/table.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../helpers/css.class.names'; + +const Table = ({ extraClasses, children, isLastColumnSticky }) => { + const className = createCssClassNames({ + 'ibexa-table table': true, + 'ibexa-table--last-column-sticky': isLastColumnSticky, + [extraClasses]: true, + }); + + return {children}
; +}; + +Table.propTypes = { + extraClasses: PropTypes.string, + children: PropTypes.element, + isLastColumnSticky: PropTypes.bool, +}; + +Table.defaultProps = { + extraClasses: '', + children: null, + isLastColumnSticky: false, +}; + +export default Table; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/actions-menu/actions.menu.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/actions-menu/actions.menu.js index d6deb6ef8c..01d477a775 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/actions-menu/actions.menu.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/actions-menu/actions.menu.js @@ -1,17 +1,35 @@ -import React, { useContext } from 'react'; +import React, { useCallback, useContext } from 'react'; -import { AllowConfirmationContext, ConfirmContext, CancelContext, SelectedLocationsContext } from '../../universal.discovery.module'; +import { + AllowConfirmationContext, + ConfirmContext, + CancelContext, + SelectedLocationsContext, + SelectedItemsContext, + ConfirmItemsContext, +} from '../../universal.discovery.module'; import { getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; const ActionsMenu = () => { const Translator = getTranslator(); const onConfirm = useContext(ConfirmContext); + const onItemsConfirm = useContext(ConfirmItemsContext); const cancelUDW = useContext(CancelContext); const allowConfirmation = useContext(AllowConfirmationContext); const [selectedLocations] = useContext(SelectedLocationsContext); + const { selectedItems } = useContext(SelectedItemsContext); const confirmLabel = Translator.trans(/*@Desc("Confirm")*/ 'actions_menu.confirm', {}, 'ibexa_universal_discovery_widget'); const cancelLabel = Translator.trans(/*@Desc("Discard")*/ 'actions_menu.cancel', {}, 'ibexa_universal_discovery_widget'); - const isConfirmDisabled = selectedLocations.length === 0; + const isConfirmDisabled = selectedLocations.length === 0 && selectedItems.length === 0; + const handleConfirmBtnClick = useCallback(() => { + if (selectedLocations.length > 0) { + onConfirm(); + + return; + } + + onItemsConfirm(); + }, [onConfirm, selectedLocations, onItemsConfirm, selectedItems]); const renderActionsContent = () => { if (!allowConfirmation) { return null; @@ -23,7 +41,7 @@ const ActionsMenu = () => { ); }; - const filtersLabel = Translator.trans(/*@Desc("Filters")*/ 'filters.title', {}, 'ibexa_universal_discovery_widget'); const languageLabel = Translator.trans(/*@Desc("Language")*/ 'filters.language', {}, 'ibexa_universal_discovery_widget'); const sectionLabel = Translator.trans(/*@Desc("Section")*/ 'filters.section', {}, 'ibexa_universal_discovery_widget'); const subtreeLabel = Translator.trans(/*@Desc("Subtree")*/ 'filters.subtree', {}, 'ibexa_universal_discovery_widget'); - const clearLabel = Translator.trans(/*@Desc("Clear")*/ 'filters.clear', {}, 'ibexa_universal_discovery_widget'); - const applyLabel = Translator.trans(/*@Desc("Apply")*/ 'filters.apply', {}, 'ibexa_universal_discovery_widget'); const languageOptions = Object.values(adminUiConfig.languages.mappings) .filter((language) => language.enabled) .map((language) => ({ @@ -150,25 +150,8 @@ const Filters = ({ search }) => { return ( <> {isNestedUdwOpened && ReactDOM.createPortal(, nestedUdwContainer.current)} -
-
-
{filtersLabel}
-
- - -
-
-
-
{languageLabel}
+ + { options={languageOptions} extraClasses="c-udw-dropdown" /> -
+ -
-
{sectionLabel}
+ { options={sectionOptions} extraClasses="c-udw-dropdown" /> -
-
-
{subtreeLabel}
+ +
{renderSubtreeBreadcrumbs()} {renderSelectContentButton()}
-
-
+ + ); }; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js new file mode 100644 index 0000000000..d0d85f5adc --- /dev/null +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.panel.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; + +const FiltersPanel = ({ children, isApplyButtonEnabled, makeSearch, clearFilters }) => { + const Translator = getTranslator(); + const filtersLabel = Translator.trans(/*@Desc("Filters")*/ 'filters.title', {}, 'ibexa_universal_discovery_widget'); + const clearLabel = Translator.trans(/*@Desc("Clear")*/ 'filters.clear', {}, 'ibexa_universal_discovery_widget'); + const applyLabel = Translator.trans(/*@Desc("Apply")*/ 'filters.apply', {}, 'ibexa_universal_discovery_widget'); + + return ( +
+
+
{filtersLabel}
+
+ + +
+
+ {children} +
+ ); +}; + +FiltersPanel.propTypes = { + children: PropTypes.node, + isApplyButtonEnabled: PropTypes.bool.isRequired, + makeSearch: PropTypes.func.isRequired, + clearFilters: PropTypes.func.isRequired, +}; + +FiltersPanel.defaultProps = { + children: null, +}; + +export default FiltersPanel; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js new file mode 100644 index 0000000000..7e50b12dd3 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/filters/filters.row.js @@ -0,0 +1,30 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../../../common/helpers/css.class.names'; + +const FiltersRow = ({ children, title, extraClasses }) => { + const className = createCssClassNames({ + 'c-filters-row': true, + [extraClasses]: true, + }); + + return ( +
+
{title}
+ {children} +
+ ); +}; + +FiltersRow.propTypes = { + children: PropTypes.node.isRequired, + title: PropTypes.string.isRequired, + extraClasses: PropTypes.string, +}; + +FiltersRow.defaultProps = { + extraClasses: '', +}; + +export default FiltersRow; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js index 1b316dab8a..2e352cd08a 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/search/search.js @@ -11,6 +11,7 @@ import Icon from '../../../common/icon/icon'; import Spinner from '../../../common/spinner/spinner'; import ContentTable from '../content-table/content.table'; import Filters from '../filters/filters'; +import ContentMetaPreview from '../../content.meta.preview.module'; import SearchTags from './search.tags'; import { useSearchByQueryFetch } from '../../hooks/useSearchByQueryFetch'; import { ActiveTabContext, AllowedContentTypesContext, MarkedLocationIdContext, SearchTextContext } from '../../universal.discovery.module'; @@ -193,15 +194,18 @@ const Search = ({ itemsPerPage }) => {
-
- -
{renderSearchResults()}
+
+ +
+
+ +
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js new file mode 100644 index 0000000000..f369cde86d --- /dev/null +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.item.js @@ -0,0 +1,84 @@ +import React, { useContext, useMemo, useRef } from 'react'; + +import { + parse as parseTooltip, + hideAll as hideAllTooltips, +} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/tooltips.helper'; +import { getAdminUiConfig, getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; + +import Icon from '../../../common/icon/icon'; +import Thumbnail from '../../../common/thumbnail/thumbnail'; + +import { SelectedItemsContext } from '../../universal.discovery.module'; + +import { REMOVE_SELECTED_ITEMS } from '../../hooks/useSelectedItemsReducer'; + +const SelectedItemsPanelItem = ({ item, thumbnailData, name, description }) => { + const adminUiConfig = getAdminUiConfig(); + const Translator = getTranslator(); + const refSelectedLocationsItem = useRef(null); + const { dispatchSelectedItemsAction } = useContext(SelectedItemsContext); + const removeItemLabel = Translator.trans( + /*@Desc("Clear selection")*/ 'selected_items_panel.item.remove_item', + {}, + 'ibexa_universal_discovery_widget', + ); + const removeFromSelection = () => { + hideAllTooltips(refSelectedLocationsItem.current); + dispatchSelectedItemsAction({ type: REMOVE_SELECTED_ITEMS, ids: [{ id: item.id, type: item.type }] }); + }; + const sortedActions = useMemo(() => { + const { universalSelectItemActions } = adminUiConfig.universalDiscoveryWidget; + const actions = universalSelectItemActions ? [...universalSelectItemActions] : []; + + return actions.sort((actionA, actionB) => { + return actionB.priority - actionA.priority; + }); + }, []); + + return ( +
{ + refSelectedLocationsItem.current = node; + parseTooltip(node); + }} + > +
+ +
+
+ {name} + {description} +
+
+ {sortedActions.map((action) => { + const Component = action.component; + + return ; + })} + +
+
+ ); +}; + +SelectedItemsPanelItem.propTypes = { + item: PropTypes.object.isRequired, + thumbnailData: PropTypes.shape({ + mimeType: PropTypes.string.isRequired, + resource: PropTypes.string.isRequired, + }).isRequired, + name: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, +}; + +export default SelectedItemsPanelItem; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js new file mode 100644 index 0000000000..50199d90dd --- /dev/null +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-items/selected.items.panel.js @@ -0,0 +1,154 @@ +import React, { useContext, useState, useEffect, useRef, useMemo } from 'react'; + +import { + parse as parseTooltip, + hideAll as hideAllTooltips, +} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/tooltips.helper'; +import { + getBootstrap, + getAdminUiConfig, + getTranslator, +} from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; + +import Icon from '../../../common/icon/icon'; +import { createCssClassNames } from '../../../common/helpers/css.class.names'; + +import { AllowConfirmationContext, SelectedItemsContext } from '../../universal.discovery.module'; + +import { CLEAR_SELECTED_ITEMS } from '../../hooks/useSelectedItemsReducer'; + +const SelectedItemsPanel = () => { + const Translator = getTranslator(); + const adminUiConfig = getAdminUiConfig(); + const itemsComponentsMap = useMemo(() => { + const { universalSelectItemsComponentsConfigs } = adminUiConfig.universalDiscoveryWidget; + const configsArray = universalSelectItemsComponentsConfigs ? [...universalSelectItemsComponentsConfigs] : []; + + return configsArray.reduce((configsMap, config) => { + configsMap[config.itemType] = config; + + return configsMap; + }, {}); + }, [adminUiConfig]); + const refSelectedLocations = useRef(null); + const { selectedItems, dispatchSelectedItemsAction } = useContext(SelectedItemsContext); + const allowConfirmation = useContext(AllowConfirmationContext); + const [isExpanded, setIsExpanded] = useState(false); + const className = createCssClassNames({ + 'c-selected-items-panel': true, + 'c-selected-items-panel--expanded': isExpanded, + }); + const expandLabel = Translator.trans( + /*@Desc("Expand sidebar")*/ 'selected_items.expand.sidebar', + {}, + 'ibexa_universal_discovery_widget', + ); + const collapseLabel = Translator.trans( + /*@Desc("Collapse sidebar")*/ 'selected_items.collapse.sidebar', + {}, + 'ibexa_universal_discovery_widget', + ); + const togglerLabel = isExpanded ? collapseLabel : expandLabel; + const clearSelection = () => { + hideAllTooltips(refSelectedLocations.current); + dispatchSelectedItemsAction({ type: CLEAR_SELECTED_ITEMS }); + }; + const toggleExpanded = () => { + setIsExpanded(!isExpanded); + }; + const renderSelectionCounter = () => { + const selectedLabel = Translator.transChoice( + /*@Desc("{1}%count% selected item|[2,Inf]%count% selected items")*/ 'selected_items.selection_info', + selectedItems.length, + { count: selectedItems.length }, + 'ibexa_universal_discovery_widget', + ); + + return
{selectedLabel}
; + }; + const renderToggleBtn = () => { + return ( + + ); + }; + const renderActionBtns = () => { + const removeLabel = Translator.transChoice( + /*@Desc("{1}Deselect|[2,Inf]Deselect all")*/ 'selected_items.deselect_all', + selectedItems.length, + {}, + 'ibexa_universal_discovery_widget', + ); + + return ( +
+ +
+ ); + }; + const renderLocationsList = () => { + if (!isExpanded) { + return null; + } + + return ( +
+ {renderActionBtns()} +
+ {selectedItems.map((selectedItem) => { + const ItemComponent = itemsComponentsMap[selectedItem.type].component; + + if (!ItemComponent) { + throw new Error(`SelectedItemsPanel: component for ${selectedItem.type} not provided in configuration.`); + } + + return ; + })} +
+
+ ); + }; + + useEffect(() => { + if (!allowConfirmation) { + return; + } + + parseTooltip(refSelectedLocations.current); + hideAllTooltips(); + + const bootstrap = getBootstrap(); + const toggleBtnTooltip = bootstrap.Tooltip.getOrCreateInstance('.c-selected-items-panel__toggle-button'); + + toggleBtnTooltip.setContent({ '.tooltip-inner': togglerLabel }); + }, [isExpanded]); + + if (!allowConfirmation) { + return null; + } + + return ( +
+
+ {renderToggleBtn()} + {renderSelectionCounter()} +
+ {renderLocationsList()} +
+ ); +}; + +export default SelectedItemsPanel; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.item.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.item.js index 010f4b082d..57a1821f45 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.item.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.item.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useRef } from 'react'; +import React, { useContext, useMemo, useRef } from 'react'; import PropTypes from 'prop-types'; import { @@ -38,12 +38,14 @@ const SelectedLocationsItem = ({ location, permissions }) => { const version = location.ContentInfo.Content.CurrentVersion.Version; const thumbnailData = version ? version.Thumbnail : {}; - useEffect(() => { - parseTooltip(refSelectedLocationsItem.current); - }, []); - return ( -
+
{ + refSelectedLocationsItem.current = node; + parseTooltip(node); + }} + >
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js index 25ecc75839..7f809b2365 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/selected-locations/selected.locations.js @@ -15,7 +15,6 @@ import { SelectedLocationsContext, AllowConfirmationContext } from '../../univer const SelectedLocations = () => { const Translator = getTranslator(); const refSelectedLocations = useRef(null); - const refTogglerButton = useRef(null); const [selectedLocations, dispatchSelectedLocationsAction] = useContext(SelectedLocationsContext); const allowConfirmation = useContext(AllowConfirmationContext); const [isExpanded, setIsExpanded] = useState(false); @@ -52,18 +51,15 @@ const SelectedLocations = () => { return
{selectedLabel}
; }; const renderToggleButton = () => { - const iconName = isExpanded ? 'caret-double-next' : 'caret-double-back'; - return ( ); }; @@ -129,8 +125,8 @@ const SelectedLocations = () => { return (
- {renderSelectionCounter()} {renderToggleButton()} + {renderSelectionCounter()}
{renderLocationsList()}
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js index c398082678..f98cdc18ac 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/sort-switcher/sort.switcher.js @@ -1,10 +1,12 @@ import React, { useContext } from 'react'; import PropTypes from 'prop-types'; +import { parse as parseTooltip } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/tooltips.helper'; + import SimpleDropdown from '../../../common/simple-dropdown/simple.dropdown'; import { SortingContext, SortOrderContext, SORTING_OPTIONS } from '../../universal.discovery.module'; -const SortSwitcher = ({ isDisabled }) => { +const SortSwitcher = ({ isDisabled, disabledConfig }) => { const [sorting, setSorting] = useContext(SortingContext); const [sortOrder, setSortOrder] = useContext(SortOrderContext); const selectedOption = SORTING_OPTIONS.find((option) => option.sortClause === sorting && option.sortOrder === sortOrder); @@ -12,9 +14,19 @@ const SortSwitcher = ({ isDisabled }) => { setSorting(option.sortClause); setSortOrder(option.sortOrder); }; + const disabledParams = {}; + + if (isDisabled && disabledConfig) { + disabledParams.title = disabledConfig.disabledInfoTooltipLabel; + } return ( -
+
parseTooltip(node)} + className="c-sort-switcher" + data-tooltip-container-selector=".c-udw-tab" + {...disabledParams} + > { SortSwitcher.propTypes = { isDisabled: PropTypes.bool, + disabledConfig: PropTypes.object, }; SortSwitcher.defaultProps = { isDisabled: false, + disabledConfig: null, }; export const SortSwitcherMenuButton = { diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js index 2ac0160ae2..4ef0812cd4 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/tab/tab.js @@ -8,15 +8,16 @@ import SelectedLocations from '../selected-locations/selected.locations'; import ContentCreateWidget from '../content-create-widget/content.create.widget'; import ContentMetaPreview from '../../content.meta.preview.module'; -import { SelectedLocationsContext, DropdownPortalRefContext } from '../../universal.discovery.module'; +import { SelectedLocationsContext, DropdownPortalRefContext, SelectedItemsContext } from '../../universal.discovery.module'; +import SelectedItemsPanel from '../selected-items/selected.items.panel'; -const Tab = ({ children, actionsDisabledMap }) => { +const Tab = ({ children, actionsDisabledMap, isRightSidebarHidden }) => { const topBarRef = useRef(); const bottomBarRef = useRef(); const [contentHeight, setContentHeight] = useState('100%'); const [selectedLocations] = useContext(SelectedLocationsContext); + const { selectedItems } = useContext(SelectedItemsContext); const dropdownPortalRef = useContext(DropdownPortalRefContext); - const selectedLocationsComponent = !!selectedLocations.length ? : null; const contentStyles = { height: contentHeight, }; @@ -40,12 +41,15 @@ const Tab = ({ children, actionsDisabledMap }) => {
{children}
-
- {ContentMetaPreview && } - {selectedLocationsComponent} -
+ {!isRightSidebarHidden && ( +
+ +
+ )}
+ {!!selectedLocations.length && } + {!!selectedItems.length && }
@@ -56,6 +60,7 @@ const Tab = ({ children, actionsDisabledMap }) => { Tab.propTypes = { children: PropTypes.any.isRequired, actionsDisabledMap: PropTypes.object, + isRightSidebarHidden: PropTypes.bool, }; Tab.defaultProps = { @@ -64,6 +69,7 @@ Tab.defaultProps = { 'sort-switcher': false, 'view-switcher': false, }, + isRightSidebarHidden: false, }; export default Tab; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js new file mode 100644 index 0000000000..e6a4a34b83 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/toggle-selection/toggle.item.selection.js @@ -0,0 +1,42 @@ +import React, { useContext } from 'react'; +import PropTypes from 'prop-types'; + +import { createCssClassNames } from '../../../common/helpers/css.class.names'; +import { MultipleConfigContext, SelectedItemsContext } from '../../universal.discovery.module'; + +const ToggleItemSelection = ({ item, isDisabled, isHidden }) => { + const { selectedItems } = useContext(SelectedItemsContext); + const [multiple, multipleItemsLimit] = useContext(MultipleConfigContext); + const isSelected = selectedItems.some((selectedItem) => selectedItem.type === item.type && selectedItem.id === item.id); + const isSelectionBlocked = multipleItemsLimit !== 0 && selectedItems.length >= multipleItemsLimit && !isSelected; + const className = createCssClassNames({ + 'c-udw-toggle-selection ibexa-input': true, + 'ibexa-input--checkbox': multiple, + 'ibexa-input--radio': !multiple, + 'c-udw-toggle-selection--hidden': isHidden, + }); + const inputType = multiple ? 'checkbox' : 'radio'; + + return ( + + ); +}; + +ToggleItemSelection.propTypes = { + item: PropTypes.object.isRequired, + isHidden: PropTypes.bool, + isDisabled: PropTypes.bool, +}; + +ToggleItemSelection.defaultProps = { + isHidden: false, + isDisabled: false, +}; + +export default ToggleItemSelection; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js index 4e6555c1cb..a04dc24624 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.js @@ -36,8 +36,12 @@ const TopMenu = ({ actionsDisabledMap }) => {
{sortedActions.map((action) => { const Component = action.component; + const disabledData = actionsDisabledMap[action.id]; + const hasDisabledConfig = disabledData instanceof Object; - return ; + return ( + + ); })}
diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js index d66c4dbe7a..167a643fbe 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/top-menu/top.menu.search.input.js @@ -4,14 +4,12 @@ import PropTypes from 'prop-types'; import { createCssClassNames } from '../../../common/helpers/css.class.names'; import Icon from '../../../common/icon/icon'; -import { ActiveTabContext, SearchTextContext } from '../../universal.discovery.module'; +import { SearchTextContext } from '../../universal.discovery.module'; const ENTER_CHAR_CODE = 13; -const SEARCH_TAB_ID = 'search'; const TopMenuSearchInput = ({ isSearchOpened, setIsSearchOpened }) => { - const [activeTab, setActiveTab] = useContext(ActiveTabContext); - const [searchText, setSearchText] = useContext(SearchTextContext); + const [searchText, , makeSearch] = useContext(SearchTextContext); const [inputValue, setInputValue] = useState(searchText); const inputRef = useRef(); const className = createCssClassNames({ @@ -24,16 +22,9 @@ const TopMenuSearchInput = ({ isSearchOpened, setIsSearchOpened }) => { 'ibexa-btn--tertiary': !isSearchOpened, }); const updateInputValue = ({ target: { value } }) => setInputValue(value); - const search = (value) => { - if (activeTab !== SEARCH_TAB_ID) { - setActiveTab('search'); - } - - setSearchText(value); - }; const handleSearchBtnClick = () => { if (isSearchOpened) { - search(inputValue); + makeSearch(inputValue); setIsSearchOpened(false); } else { setIsSearchOpened(true); @@ -41,7 +32,7 @@ const TopMenuSearchInput = ({ isSearchOpened, setIsSearchOpened }) => { }; const handleKeyPressed = ({ charCode }) => { if (charCode === ENTER_CHAR_CODE) { - search(inputValue); + makeSearch(inputValue); } }; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js b/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js index 770e5df0fc..e4ef9a5178 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/components/view-switcher/view.switcher.js @@ -3,13 +3,14 @@ import PropTypes from 'prop-types'; import SimpleDropdown from '../../../common/simple-dropdown/simple.dropdown'; import { getTranslator } from '../../../../../../Resources/public/js/scripts/helpers/context.helper'; -import { CurrentViewContext, VIEWS } from '../../universal.discovery.module'; +import { CurrentViewContext, ViewContext } from '../../universal.discovery.module'; const ViewSwitcher = ({ isDisabled }) => { const Translator = getTranslator(); const viewLabel = Translator.trans(/*@Desc("View")*/ 'view_switcher.view', {}, 'ibexa_universal_discovery_widget'); const [currentView, setCurrentView] = useContext(CurrentViewContext); - const selectedOption = VIEWS.find((option) => option.value === currentView); + const { views } = useContext(ViewContext); + const selectedOption = views.find((option) => option.value === currentView); const onOptionClick = ({ value }) => { setCurrentView(value); }; @@ -17,7 +18,7 @@ const ViewSwitcher = ({ isDisabled }) => { return (
{ + switch (action.type) { + case FETCH_START: + return { + ...state, + data: null, + isLoading: true, + }; + case FETCH_END: + return { ...state, data: action.data, isLoading: false }; + case CHANGE_PAGE: { + const isCurrentPageIndex = action.pageIndex === state.pageIndex; + + if (isCurrentPageIndex) { + return state; + } + + return { + ...state, + data: null, + pageIndex: action.pageIndex, + }; + } + default: + throw new Error(); + } +}; + +export const usePaginableFetch = ({ itemsPerPage, extraFetchParams }, fetchFunction) => { + const restInfo = useContext(RestInfoContext); + const [state, dispatch] = useReducer(fetchReducer, fetchInitialState); + const changePage = (pageIndex) => dispatch({ type: CHANGE_PAGE, pageIndex }); + + useEffect(() => { + dispatch({ type: FETCH_START }); + + const offset = state.pageIndex * itemsPerPage; + const { abortController } = fetchFunction({ ...restInfo, limit: itemsPerPage, offset, ...extraFetchParams }, (data) => + dispatch({ type: FETCH_END, data }), + ); + + return () => { + if (abortController) { + abortController.abort(); + } + }; + }, [state.pageIndex, restInfo, itemsPerPage, extraFetchParams]); + + return [state.data, state.isLoading, state.pageIndex, changePage]; +}; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js b/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js new file mode 100644 index 0000000000..33ca0c9c79 --- /dev/null +++ b/src/bundle/ui-dev/src/modules/universal-discovery/hooks/useSelectedItemsReducer.js @@ -0,0 +1,80 @@ +import { useReducer } from 'react'; + +export const ADD_SELECTED_ITEMS = 'ADD_SELECTED_ITEMS'; +export const REMOVE_SELECTED_ITEMS = 'REMOVE_SELECTED_ITEMS'; +export const TOGGLE_SELECTED_ITEMS = 'TOGGLE_SELECTED_ITEMS'; +export const CLEAR_SELECTED_ITEMS = 'CLEAR_SELECTED_ITEMS'; +export const CHANGE_MULTIPLE_SETTING = 'CHANGE_MULTIPLE_SETTING'; + +const checkIsItemSelected = (selectedItems, item) => + selectedItems.some((selectedItem) => selectedItem.type === item.type && selectedItem.id === item.id); + +const filterOutSelectedItems = (selectedItems, items) => items.filter((item) => !checkIsItemSelected(selectedItems, item)); + +const checkIsValidSelection = (items, isMultiple, multipleItemsLimit) => + (!isMultiple && items.length > 1) || (isMultiple && multipleItemsLimit !== 0 && items.length > multipleItemsLimit); + +const selectedItemsReducer = (state, action) => { + const { items, isMultiple, multipleItemsLimit } = state; + + switch (action.type) { + case ADD_SELECTED_ITEMS: { + const oldItemsWithoutNewItems = filterOutSelectedItems(action.items, items); + const newItems = [...oldItemsWithoutNewItems, ...action.items]; + + if (checkIsValidSelection(newItems, isMultiple, multipleItemsLimit)) { + throw new Error('useSelectedItemsReducer ADD_SELECTED_ITEMS: cannot select more than one item with single select.'); + } + + return { + ...state, + items: newItems, + }; + } + case REMOVE_SELECTED_ITEMS: + return filterOutSelectedItems(action.itemsIdsWithTypes, items); + case TOGGLE_SELECTED_ITEMS: { + const oldItemsWithoutDeselectedItems = filterOutSelectedItems(action.items, items); + const newItemsWithoutDeselectedItems = filterOutSelectedItems(items, action.items); + const newItems = [...oldItemsWithoutDeselectedItems, ...newItemsWithoutDeselectedItems]; + + if (checkIsValidSelection(newItems, isMultiple, multipleItemsLimit)) { + throw new Error('useSelectedItemsReducer ADD_SELECTED_ITEMS: cannot select more than one item with single select.'); + } + + return { + ...state, + items: newItems, + }; + } + case CLEAR_SELECTED_ITEMS: + return { + ...state, + items: [], + }; + case CHANGE_MULTIPLE_SETTING: + if (!action.isMultiple && items.length > 1) { + throw new Error( + 'useSelectedItemsReducer CHANGE_MULTIPLE_SETTING: cannot set to single select when multiple items are selected.', + ); + } + + return { + ...state, + isMultiple: action.isMultiple, + }; + default: + throw new Error(); + } +}; + +export const useSelectedItemsReducer = ({ items = [], isMultiple, multipleItemsLimit }) => { + const initialState = { + isMultiple, + multipleItemsLimit, + items, + }; + const [{ items: selectedItems }, dispatchSelectedItemsAction] = useReducer(selectedItemsReducer, initialState); + + return { selectedItems, dispatchSelectedItemsAction }; +}; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js b/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js index 226936e185..930afaa736 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/search.tab.module.js @@ -42,7 +42,7 @@ const SearchTabModule = () => { return (
- +
@@ -61,4 +61,4 @@ const SearchTab = { isHiddenOnList: true, }; -export { SearchTabModule as ValueTypeDefault, SearchTab }; +export { SearchTabModule as default, SearchTab }; diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js b/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js index cec9ce9c2b..c54583d26a 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/universal.discovery.module.js @@ -23,9 +23,11 @@ import { getTranslator, SYSTEM_ROOT_LOCATION_ID, } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; +import { useSelectedItemsReducer } from './hooks/useSelectedItemsReducer'; const { document } = window; const CLASS_SCROLL_DISABLED = 'ibexa-scroll-disabled'; +const SEARCH_TAB_ID = 'search'; const defaultRestInfo = { accsessToken: null, instanceUrl: window.location.origin, @@ -178,6 +180,7 @@ export const TabsContext = createContext(); export const TitleContext = createContext(); export const CancelContext = createContext(); export const ConfirmContext = createContext(); +export const ConfirmItemsContext = createContext(); export const SortingContext = createContext(); export const SortOrderContext = createContext(); export const CurrentViewContext = createContext(); @@ -186,6 +189,7 @@ export const StartingLocationIdContext = createContext(); export const LoadedLocationsMapContext = createContext(); export const RootLocationIdContext = createContext(); export const SelectedLocationsContext = createContext(); +export const SelectedItemsContext = createContext(); export const CreateContentWidgetContext = createContext(); export const ContentOnTheFlyDataContext = createContext(); export const ContentOnTheFlyConfigContext = createContext(); @@ -196,6 +200,7 @@ export const DropdownPortalRefContext = createContext(); export const SuggestionsStorageContext = createContext(); export const GridActiveLocationIdContext = createContext(); export const SnackbarActionsContext = createContext(); +export const ViewContext = createContext(); const UniversalDiscoveryModule = (props) => { const { restInfo } = props; @@ -231,6 +236,10 @@ const UniversalDiscoveryModule = (props) => { { parentLocationId: props.rootLocationId, subitems: [] }, ]); const [selectedLocations, dispatchSelectedLocationsAction] = useSelectedLocationsReducer(); + const { selectedItems, dispatchSelectedItemsAction } = useSelectedItemsReducer({ + isMultiple: props.multiple, + multipleItemsLimit: props.multipleItemsLimit, + }); const activeTabConfig = tabs.find((tab) => tab.id === activeTab); const Tab = activeTabConfig.component; const className = createCssClassNames({ @@ -280,9 +289,9 @@ const UniversalDiscoveryModule = (props) => { [adminUiConfig.contentTypes], ); const onConfirm = useCallback( - (selectedItems = selectedLocations) => { + (selection = selectedLocations) => { loadVersions().then((locationsWithVersions) => { - const clonedSelectedLocation = deepClone(selectedItems); + const clonedSelectedLocation = deepClone(selection); if (Array.isArray(locationsWithVersions)) { locationsWithVersions.forEach((content) => { @@ -308,8 +317,19 @@ const UniversalDiscoveryModule = (props) => { props.onConfirm(updatedLocations); }); }, - [selectedLocations, contentTypesInfoMap], + [selectedLocations, contentTypesInfoMap, props.onConfirm], + ); + const onItemsConfirm = useCallback( + (selection = selectedItems) => props.onItemsConfirm(selection), + [selectedItems, props.onItemsConfirm], ); + const makeSearch = (value) => { + if (activeTab !== SEARCH_TAB_ID) { + setActiveTab('search'); + } + + setSearchText(value); + }; useEffect(() => { const addContentTypesInfo = (contentTypes) => { @@ -487,100 +507,118 @@ const UniversalDiscoveryModule = (props) => { - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -605,6 +643,7 @@ const UniversalDiscoveryModule = (props) => { UniversalDiscoveryModule.propTypes = { onConfirm: PropTypes.func.isRequired, + onItemsConfirm: PropTypes.func, onCancel: PropTypes.func, title: PropTypes.string.isRequired, activeTab: PropTypes.string, @@ -645,6 +684,7 @@ UniversalDiscoveryModule.propTypes = { }; UniversalDiscoveryModule.defaultProps = { + onItemsConfirm: () => {}, onCancel: null, activeTab: 'browse', rootLocationId: 1,