Skip to content

Commit

Permalink
#CX-12946 : Explore and fix how multi select and grouping of selected…
Browse files Browse the repository at this point in the history
… records using new wc listbox component (#1855)

* feat(cx-12946): update list and example

* updating component

* feat(cx-12946): added the border bottom logic

* update

* feat(cx-12946): with meny overlay

* updating parent with overlay

* updating overlay parent

* updating grouping logic

* feat(cx-12946): fixed multiselect logic

* adding select all

* feat(cx-12946): select all grouping and reset filter done

* feat(cx-12946): fix of UI in sandbox

* adding select all and unselect all

* adding chevron to example

* feat(cx-12946): pre-select issue resolved

* updating css

* updating select all logic

* updating UI

* updating UI

* updating label and uts

* adding UTs for multi select

* fix(cx-12946): a11y fix for multiselect

* fix(cx-12946): separator issue fix

* fix(cx-12946): arrows, tab and space keyboard navi fixed

* fix(cx-12946): updated unit test

* fix(cx-12946): addressed review comment

* fix(cx-12946): fix for sandbox issue

* fix(cx-12946): made enter and space key for selection and deselection

* fix(cx-12946): version update

---------

Co-authored-by: Naman Goel <[email protected]>
Co-authored-by: nithishkumar98 <[email protected]>
Co-authored-by: nithishkumar98 <[email protected]>
  • Loading branch information
4 people authored Jan 23, 2025
1 parent b0bf911 commit dc95105
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 87 deletions.
2 changes: 1 addition & 1 deletion web-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@momentum-ui/web-components",
"version": "2.17.0",
"version": "2.17.1",
"author": "Yana Harris",
"license": "MIT",
"repository": "https://github.com/momentum-design/momentum-ui.git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export namespace ParentComponentGeneric {
@property({ type: Array }) items: any = [];
@internalProperty() page = 1;
@property({ type: Boolean }) isLoading = false;
@property({ type: String }) value = "";
@property({ type: Array }) value: string[] = [];
@property({ type: Boolean }) isError = false;
@internalProperty() totalRecords = 60000; // Total count is set to 6000

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export namespace ParentComponentPreSelect {
@property({ type: Array }) items: any = [];
@internalProperty() page = 1;
@property({ type: Boolean }) isLoading = false;
@property({ type: String }) value = "";
@property({ type: Array }) value: string[] = [];
@property({ type: Boolean }) isError = false;
@internalProperty() totalRecords = 0;

Expand All @@ -36,7 +36,7 @@ export namespace ParentComponentPreSelect {
this.totalRecords = 60000;
this.page += 1;
this.isLoading = false;
this.value = this.items[1].id;
this.value.push(this.items[1].id);
} catch (err) {
this.isLoading = false;
this.isError = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { html, internalProperty, LitElement, property, PropertyValues } from "lit-element";
import "@/components/advance-list/AdvanceList";
import "@/components/list/List";
import "@/components/list/ListItem";
import "@/components/menu-overlay/MenuOverlay";
import { customElementWithCheck } from "@/mixins/CustomElementCheck";

export namespace ParentComponentWithMdOverlay {
@customElementWithCheck("parent-component-with-overlay")
export class ELEMENT extends LitElement {
@property({ type: Array }) items: any = [];
@internalProperty() page = 1;
@property({ type: Boolean }) isLoading = false;
@property({ type: Array }) value: string[] = [];
@property({ type: Boolean }) isError = false;
@property({ type: Boolean }) groupOnMultiSelect = true;
@internalProperty() totalRecords = 0;
@internalProperty() loadedRecords = 0;
@internalProperty() lastSelectedIdByOrder = "";
@internalProperty() selectAllItems = false;
@internalProperty() isMenuOverlayOpen = false;
@internalProperty() disabledItems: string[] = [];
@internalProperty() inputIcon = "arrow-down-bold";
@internalProperty() overlayTriggerPlaceholder = "Search field with tabs";

private counter = 0;
constructor() {
super();
this.items = [];
this.page = 1;
this.isLoading = false;
this.isError = false;
this.loadMoreItems();
}

updated(changedProperties: PropertyValues) {
console.log("changedProperties", changedProperties);
if (changedProperties.has("value")) {
this.updatePlaceholder();
}
}

generateUUID() {
const baseUUID = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});

// Increment the counter and pad with zeros to ensure 3 characters
this.counter++;
const counterString = this.counter.toString().padStart(3, "0");

// Replace the last 3 characters of the UUID with the counter
return baseUUID.slice(0, -3) + counterString;
}

async loadMoreItems() {
try {
this.isLoading = true;
const newItems = await this.fetchItems(this.page);
this.items = [...this.items, ...newItems];
this.totalRecords = 60000;
this.page += 1;
this.value.push(this.items[1].id);
this.loadedRecords = this.items.length;
} catch (err) {
this.isError = true;
} finally {
this.isLoading = false;
}
}

async fetchItems(page: number) {
const newItems = this.generateItems(page);
return newItems;
}

private generateItems(page: number) {
const itemsPerPage = 2000;
return Array.from({ length: itemsPerPage }, (_, i) => {
const id = this.generateUUID(); // Generate a unique ID for each item
const itemName = `Item ${(page - 1) * itemsPerPage + i + 1}`;
const disabled = i % 2 === 0 ? "true" : "";
if (disabled) {
this.disabledItems.push(id);
}
return {
name: itemName,
id,
index: i,
ariaLabel: itemName,
template: (data: any, index: number) =>
html`<div
style="position:relative;min-height:1.25rem;box-sizing: border-box;display:flex;flex-flow:row nowrap;justify-content:flex-start;align-items:center;line-height:26px;"
?disabled=${disabled}
aria-hidden="true"
indexing="${index}"
>
<md-checkbox .disabled=${disabled} >${data.name}</md-checkbox>
</div>`
};
});
}

getOrderedItems() {
if (this.groupOnMultiSelect) {
const selectedItems = this.items.filter((item: any) => this.value.includes(item.id));
const unselectedItems = this.items.filter((item: any) => !this.value.includes(item.id));

selectedItems.sort((a: any, b: any) => this.naturalSort(a.name, b.name));
unselectedItems.sort((a: any, b: any) => this.naturalSort(a.name, b.name));

if (selectedItems.length > 0) {
this.lastSelectedIdByOrder = selectedItems[selectedItems.length - 1].id;
} else {
this.lastSelectedIdByOrder = "";
}
return [...selectedItems, ...unselectedItems];
} else {
return [...this.items].sort((a, b) => this.naturalSort(a.name, b.name));
}
}

private naturalSort(a: string, b: string): number {
return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" });
}

updatePlaceholder() {
const selectedItemsCount = this.value.length;
this.overlayTriggerPlaceholder = selectedItemsCount ? `${selectedItemsCount} selected` : "Search field with tabs";
}

private handleListItemChange(event: CustomEvent) {
this.value = event.detail.selected;
// Filter and sort the selected items based on the `name` property
const selectedItems = this.items
.filter((item: any) => this.value.includes(item.id))
.sort((a: any, b: any) => this.naturalSort(a.name, b.name));

// Update `this.value` with the sorted IDs
this.value = selectedItems.map((item: any) => item.id);

this.updatePlaceholder();
if (this.value.length === this.items.length - this.disabledItems.length) {
this.selectAllItems = true;
} else {
this.selectAllItems = false;
}
document.dispatchEvent(new CustomEvent("on-widget-update"));
}

updateSelectAllCheckboxOnClick() {
if (this.selectAllItems) {
this.resetFilter();
} else {
this.selectAllItems = true;
}
}

resetFilter() {
this.selectAllItems = false;
this.value = [];
}

getSelectAllLabel() {
return this.items.length < 100
? "Select all"
: `Select ${this.loadedRecords - this.disabledItems.length} of ${this.totalRecords}`;
}

render() {
return html`
<h2>With overlay</h2>
<md-menu-overlay
class="queueDropdown test-overlay"
custom-width="300px"
@menu-overlay-open=${() => {
this.items = this.getOrderedItems();
this.inputIcon = "arrow-up-bold";
this.isMenuOverlayOpen = true;
document.dispatchEvent(new CustomEvent("on-widget-update"));
}}
@menu-overlay-close=${() => {
this.inputIcon = "arrow-down-bold";
this.isMenuOverlayOpen = false;
}}
>
<md-input
value=${this.overlayTriggerPlaceholder}
slot="menu-trigger"
autoFocus
shape="pill"
readonly
newMomentum
>
<md-icon slot="input-section-right" name=${this.inputIcon} iconset="momentumDesign"></md-icon>
</md-input>
<div style="margin:0.5rem; width: 100%">
<md-input placeholder="Search..." shape="pill" clear autoFocus></md-input>
<div style="margin-top:0.5rem; width: 100%">
<div
style="padding-left:15px; padding-bottom:10px; border-bottom: 1px solid var(--md-gray-20);"
>
<md-checkbox
id="select-all-one"
class="selectAll"
@checkbox-change=${this.updateSelectAllCheckboxOnClick}
.checked=${this.selectAllItems}
>${this.getSelectAllLabel()}</md-checkbox
>
</div>
${this.isMenuOverlayOpen
? html`
<md-advance-list
.items=${this.items}
.isLoading=${this.isLoading}
.isError=${this.isError}
.value=${this.value}
ariaRoleList="listbox"
ariaLabelList="state selector"
.totalRecords=${this.totalRecords}
.lastSelectedIdByOrder=${this.lastSelectedIdByOrder}
is-multi
.groupOnMultiSelect=${this.groupOnMultiSelect}
.selectAllItems=${this.selectAllItems}
.disabledItems=${this.disabledItems}
@list-item-change=${this.handleListItemChange}
@load-more=${this.loadMoreItems}
>
<md-spinner size="24" slot="spin-loader"></md-spinner>
</md-advance-list>`
: html``
}
</div>
<div>
<md-button @button-click=${this.resetFilter} variant="primary">Reset the filter</md-button>
</div>
</div>
</md-menu-overlay>
`;
}
}
}

declare global {
interface HTMLElementTagNameMap {
"parent-component-with-overlay": ParentComponentWithMdOverlay.ELEMENT;
}
}
2 changes: 2 additions & 0 deletions web-components/src/[sandbox]/examples/advance-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { html } from "lit-element";
import "./AdvanceList/components/ParentComponentError";
import "./AdvanceList/components/ParentComponentGeneric";
import "./AdvanceList/components/ParentComponentPreSelect";
import "./AdvanceList/components/ParentComponentWithMdOverlay";

export const advanceListTemplate = html`
<div style="width: 300px">
<parent-component-with-overlay></parent-component-with-overlay>
<parent-component-generic></parent-component-generic>
<parent-component-pre-select></parent-component-pre-select>
<parent-component-error></parent-component-error>
Expand Down
Loading

0 comments on commit dc95105

Please sign in to comment.