Skip to content

Commit b63db29

Browse files
Migrate CategoryOptions dropdown to KMultiSelect (#6092)
* Refactor CategoryOptions dropdown to KMultiSelect, keeping expanded mode unchanged * Rewrite CategoryOptions tests with Testing Library * Move shared KMultiSelect strings to commonStrings * done the changes asked in the review * updated
1 parent 31c56ae commit b63db29

4 files changed

Lines changed: 254 additions & 92 deletions

File tree

contentcuration/contentcuration/frontend/shared/strings/commonStrings.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,40 @@ export const commonStrings = createTranslator('CommonStrings', {
4646
message: 'Options',
4747
context: 'Tooltip for the generic options menu icon',
4848
},
49+
openMenuAction: {
50+
message: 'Open menu',
51+
context: 'Accessible label for the button that opens a dropdown menu',
52+
},
53+
closeMenuAction: {
54+
message: 'Close menu',
55+
context: 'Accessible label for the button that closes a dropdown menu',
56+
},
57+
optionsClickableLabel: {
58+
message: 'Options are clickable',
59+
context: 'Announced to screen reader users when a list of selectable options appears',
60+
},
61+
allOptionsSelectedLabel: {
62+
message: 'All options selected',
63+
context: 'Announced when every option in a list is selected',
64+
},
65+
allOptionsDeselectedLabel: {
66+
message: 'No options selected',
67+
context: 'Announced when no options in a list are selected',
68+
},
69+
optionDeselectedLabel: {
70+
message: 'Option deselected',
71+
context: 'Announced when an option is removed from the selection',
72+
},
73+
partiallySelectedLabel: {
74+
message: 'Partially selected',
75+
context: 'Announced for an option when only some of the options under it are selected',
76+
},
77+
optionSelectedLabel: {
78+
message: 'Selected {label}',
79+
context: 'Announced when an option is selected. {label} is the name of the option',
80+
},
81+
optionRemovedLabel: {
82+
message: 'Removed {label}',
83+
context: 'Announced when an option is removed. {label} is the name of the option',
84+
},
4985
});

contentcuration/contentcuration/frontend/shared/strings/communityChannelsStrings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ export const communityChannelsStrings = createTranslator('CommunityChannelsStrin
445445
message: 'New',
446446
context: 'Label indicating the section for new notifications',
447447
},
448+
// TODO: clearAllAction should be moved to commonStrings.js in next major Studio release
448449
clearAllAction: {
449450
message: 'Clear all',
450451
context: 'Action button to clear all notifications',

contentcuration/contentcuration/frontend/shared/views/contentNodeFields/CategoryOptions.vue

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,39 @@
11
<template>
22

33
<div>
4-
<DropdownWrapper>
4+
<KMultiSelect
5+
v-if="!expanded"
6+
:value="autocompleteValues"
7+
:options="categoriesList"
8+
itemValue="value"
9+
itemText="text"
10+
:label="translateMetadataString('category')"
11+
:autoPromoteParent="false"
12+
clearable
13+
:noResultsText="$tr('noCategoryFoundText')"
14+
:messages="messages"
15+
@input="onKMultiSelectInput"
16+
>
17+
<template #chip="{ option, remove: removeChip }">
18+
<span :ref="'category-chip-' + option.value">
19+
<KChip
20+
:text="option.text"
21+
:removeLabel="$tr('removeCategory', { label: tooltipText(option.value) })"
22+
close
23+
@close="removeChip"
24+
@mousedown.native.prevent
25+
/>
26+
</span>
27+
<KTooltip
28+
:reference="'category-chip-' + option.value"
29+
:refs="$refs"
30+
placement="top"
31+
:text="tooltipText(option.value)"
32+
/>
33+
</template>
34+
</KMultiSelect>
35+
36+
<DropdownWrapper v-if="expanded">
537
<template #default="{ attach, menuProps }">
638
<VAutocomplete
739
:value="autocompleteValues"
@@ -18,8 +50,8 @@
1850
:menu-props="{
1951
...menuProps,
2052
zIndex: 4,
21-
height: expanded ? 0 : 'auto',
22-
maxHeight: expanded ? 0 : 300,
53+
height: 0,
54+
maxHeight: 0,
2355
}"
2456
:attach="attach"
2557
@click:clear="$nextTick(() => removeAll())"
@@ -44,34 +76,6 @@
4476
</div>
4577
</VTooltip>
4678
</template>
47-
48-
<template #no-data>
49-
<VListTile v-if="categoryText && categoryText.trim()">
50-
<VListTileContent>
51-
<VListTileTitle>
52-
{{ $tr('noCategoryFoundText', { text: categoryText.trim() }) }}
53-
</VListTileTitle>
54-
</VListTileContent>
55-
</VListTile>
56-
</template>
57-
58-
<template #item="{ item }">
59-
<VListTile
60-
:value="isSelected(item.value)"
61-
:class="{ parentOption: !item.value.includes('.') }"
62-
@mousedown.prevent
63-
@click="onChange(item.value)"
64-
>
65-
<KCheckbox
66-
:checked="isSelected(item.value)"
67-
:label="item.text"
68-
:value="item.value"
69-
style="margin-top: 10px"
70-
:style="treeItemStyle(item)"
71-
:ripple="false"
72-
/>
73-
</VListTile>
74-
</template>
7579
</VAutocomplete>
7680
</template>
7781
</DropdownWrapper>
@@ -104,13 +108,17 @@
104108
<script>
105109
106110
import camelCase from 'lodash/camelCase';
111+
import KMultiSelect from 'kolibri-design-system/lib/candidate/multiselect/KMultiSelect';
112+
import KChip from 'kolibri-design-system/lib/candidate/multiselect/KChip';
107113
import { getSortedCategories } from 'shared/utils/helpers';
114+
import { commonStrings } from 'shared/strings/commonStrings';
115+
import { communityChannelsStrings } from 'shared/strings/communityChannelsStrings';
108116
import DropdownWrapper from 'shared/views/form/DropdownWrapper';
109117
import { constantsTranslationMixin, metadataTranslationMixin } from 'shared/mixins';
110118
111119
export default {
112120
name: 'CategoryOptions',
113-
components: { DropdownWrapper },
121+
components: { KMultiSelect, KChip, DropdownWrapper },
114122
mixins: [constantsTranslationMixin, metadataTranslationMixin],
115123
props: {
116124
/**
@@ -177,6 +185,34 @@
177185
option.text.toLowerCase().includes(searchQuery),
178186
);
179187
},
188+
messages() {
189+
const {
190+
openMenuAction$,
191+
closeMenuAction$,
192+
optionsClickableLabel$,
193+
allOptionsSelectedLabel$,
194+
allOptionsDeselectedLabel$,
195+
optionDeselectedLabel$,
196+
partiallySelectedLabel$,
197+
optionSelectedLabel$,
198+
optionRemovedLabel$,
199+
} = commonStrings;
200+
const { clearAllAction$ } = communityChannelsStrings;
201+
return {
202+
clearText: clearAllAction$,
203+
open: openMenuAction$,
204+
close: closeMenuAction$,
205+
clickable: optionsClickableLabel$,
206+
allOptionsSelected: allOptionsSelectedLabel$,
207+
allOptionsDeselected: allOptionsDeselectedLabel$,
208+
optionDeselected: optionDeselectedLabel$,
209+
partiallySelected: partiallySelectedLabel$,
210+
itemsSelected: ({ count }) => this.$tr('itemsSelected', { count }),
211+
selected: optionSelectedLabel$,
212+
removed: optionRemovedLabel$,
213+
cleared: () => this.$tr('allCategoriesCleared'),
214+
};
215+
},
180216
},
181217
methods: {
182218
treeItemStyle(item) {
@@ -201,6 +237,11 @@
201237
removeAll() {
202238
this.selected = {};
203239
},
240+
// Dropdown mode is only rendered when a single node is edited, so every
241+
// selected category simply applies to all of nodeIds.
242+
onKMultiSelectInput(newValues) {
243+
this.selected = Object.fromEntries(newValues.map(value => [value, this.nodeIds]));
244+
},
204245
tooltipText(optionId) {
205246
const option = this.categoriesList.find(option => option.value === optionId);
206247
if (!option) {
@@ -275,6 +316,9 @@
275316
},
276317
$trs: {
277318
noCategoryFoundText: 'Category not found',
319+
itemsSelected: '{count, plural, one {# category selected} other {# categories selected}}',
320+
allCategoriesCleared: 'All categories cleared',
321+
removeCategory: 'Remove {label}',
278322
},
279323
};
280324
@@ -283,10 +327,6 @@
283327
284328
<style lang="scss" scoped>
285329
286-
.parentOption:not(:first-child) {
287-
border-top: 1px solid rgba(0, 0, 0, 0.12);
288-
}
289-
290330
.checkbox-list-wrapper {
291331
height: 250px;
292332
overflow-y: auto;

0 commit comments

Comments
 (0)