Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(list): Add "filter-no-results" slot to display content when no filtered items are shown #8569

Merged
merged 7 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 59 additions & 59 deletions packages/calcite-components/src/components.d.ts

Large diffs are not rendered by default.

60 changes: 51 additions & 9 deletions packages/calcite-components/src/components/list/list.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { placeholderImage } from "../../../.storybook/placeholderImage";
import { html } from "../../../support/formatting";
import { E2EPage, newE2EPage } from "@stencil/core/testing";
import { debounceTimeout } from "./resources";
import { CSS, activeCellTestAttribute } from "../list-item/resources";
import { CSS as ListItemCSS, activeCellTestAttribute } from "../list-item/resources";
import { DEBOUNCE_TIMEOUT as FILTER_DEBOUNCE_TIMEOUT } from "../filter/resources";
import { GlobalTestProps, dragAndDrop, isElementFocused, getFocusedElementProp } from "../../tests/utils";
import { ListDragDetail } from "./interfaces";
Expand Down Expand Up @@ -106,6 +106,17 @@ describe("calcite-list", () => {
<calcite-list-item label="test" description="hello world"></calcite-list-item>
</calcite-list>`,
);
accessible(
html`<calcite-list filter-enabled filter-text="Bananas" selection-appearance="border" selection-mode="single">
<calcite-list-item label="Apples" value="apples"></calcite-list-item>
<calcite-list-item label="Oranges" value="oranges"></calcite-list-item>
<calcite-list-item label="Pears" value="pears"></calcite-list-item>
<calcite-notice slot="filter-no-results" icon kind="warning" scale="s" open>
<div slot="title">No fruits found</div>
<div slot="message">Try a different fruit?</div>
</calcite-notice>
</calcite-list>`,
);
});

describe("disabled", () => {
Expand Down Expand Up @@ -350,7 +361,7 @@ describe("calcite-list", () => {
expect(await items[2].getProperty("selected")).toBe(false);
expect(await items[3].getProperty("selected")).toBe(false);

await page.$eval("#item-4", clickItemContent, `.${CSS.contentContainer}`);
await page.$eval("#item-4", clickItemContent, `.${ListItemCSS.contentContainer}`);
await page.waitForChanges();
await page.waitForTimeout(listDebounceTimeout);
expect(eventSpy).toHaveReceivedEventTimes(2);
Expand All @@ -373,7 +384,7 @@ describe("calcite-list", () => {
expect(await items[2].getProperty("selected")).toBe(true);
expect(await items[3].getProperty("selected")).toBe(false);

await page.$eval("#item-1", clickItemContent, `.${CSS.contentContainer}`);
await page.$eval("#item-1", clickItemContent, `.${ListItemCSS.contentContainer}`);
await page.waitForChanges();
await page.waitForTimeout(listDebounceTimeout);
expect(eventSpy).toHaveReceivedEventTimes(4);
Expand Down Expand Up @@ -502,7 +513,9 @@ describe("calcite-list", () => {
await page.waitForChanges();
const list = await page.find("calcite-list");
const listItemOne = await page.find(`calcite-list-item[value=one]`);
const listItemOneContentContainer = await page.find(`calcite-list-item[value=one] >>> .${CSS.contentContainer}`);
const listItemOneContentContainer = await page.find(
`calcite-list-item[value=one] >>> .${ListItemCSS.contentContainer}`,
);

const calciteListChangeEvent = list.waitForEvent("calciteListChange");
await listItemOneContentContainer.click();
Expand Down Expand Up @@ -530,6 +543,35 @@ describe("calcite-list", () => {
expect(await list.getProperty("selectedItems")).toHaveLength(0);
});

it("should show no-results content when filter does not match", async () => {
const page = await newE2EPage();
await page.setContent(
html`<calcite-list>
<calcite-list-item label="Apples" value="apples"></calcite-list-item>
<calcite-list-item label="Oranges" value="oranges"></calcite-list-item>
<calcite-list-item label="Pears" value="pears"></calcite-list-item>
<calcite-notice slot="filter-no-results" icon kind="warning" scale="s" open>
<div slot="title">No fruits found</div>
<div slot="message">Try a different fruit?</div>
</calcite-notice>
</calcite-list>`,
);
await page.waitForChanges();

const noResultsContainer = await page.find(`calcite-list >>> [data-test-id="no-results-container"]`);

expect(await noResultsContainer.isVisible()).toBe(false);

const list = await page.find("calcite-list");
list.setProperty("filterText", "Bananas");
await page.waitForChanges();
expect(await noResultsContainer.isVisible()).toBe(false);

list.setProperty("filterEnabled", true);
await page.waitForChanges();
expect(await noResultsContainer.isVisible()).toBe(true);
});

describe("keyboard navigation", () => {
it("should navigate via ArrowUp, ArrowDown, Home, and End", async () => {
const page = await newE2EPage();
Expand Down Expand Up @@ -687,15 +729,15 @@ describe("calcite-list", () => {

await list.press("ArrowRight");

expect(await isElementFocused(page, `.${CSS.contentContainer}`, { shadowed: true })).toBe(true);
expect(await isElementFocused(page, `.${ListItemCSS.contentContainer}`, { shadowed: true })).toBe(true);

await list.press("ArrowRight");

expect(await isElementFocused(page, "calcite-action")).toBe(true);

await list.press("ArrowLeft");

expect(await isElementFocused(page, `.${CSS.contentContainer}`, { shadowed: true })).toBe(true);
expect(await isElementFocused(page, `.${ListItemCSS.contentContainer}`, { shadowed: true })).toBe(true);

await list.press("ArrowLeft");

Expand Down Expand Up @@ -755,15 +797,15 @@ describe("calcite-list", () => {

await list.press("ArrowRight");

expect(await isElementFocused(page, `.${CSS.contentContainer}`, { shadowed: true })).toBe(true);
expect(await isElementFocused(page, `.${ListItemCSS.contentContainer}`, { shadowed: true })).toBe(true);

await list.press("ArrowRight");

expect(await isElementFocused(page, "calcite-action")).toBe(true);

await list.press("ArrowLeft");

expect(await isElementFocused(page, `.${CSS.contentContainer}`, { shadowed: true })).toBe(true);
expect(await isElementFocused(page, `.${ListItemCSS.contentContainer}`, { shadowed: true })).toBe(true);

await list.press("ArrowLeft");

Expand Down Expand Up @@ -791,7 +833,7 @@ describe("calcite-list", () => {
`);
await page.waitForChanges();
const items = await page.findAll("calcite-list-item");
const secondHandleCell = await page.find(`#two >>> .${CSS.dragContainer}`);
const secondHandleCell = await page.find(`#two >>> .${ListItemCSS.dragContainer}`);

expect(await items[0].getProperty("active")).toBe(true);
expect(await items[1].getProperty("active")).toBe(false);
Expand Down
11 changes: 11 additions & 0 deletions packages/calcite-components/src/components/list/list.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -923,3 +923,14 @@ export const listWithGroupedAndSlottedItems_TestOnly = (): string =>
</calcite-list-item>
</calcite-list-item-group>
</calcite-list>`;

export const filteredListItemsNoResults_TestOnly = (): string =>
html`<calcite-list filter-enabled filter-text="Bananas" selection-appearance="border" selection-mode="single">
<calcite-list-item label="Apples" value="apples"></calcite-list-item>
<calcite-list-item label="Oranges" value="oranges"></calcite-list-item>
<calcite-list-item label="Pears" value="pears"></calcite-list-item>
<calcite-notice slot="filter-no-results" icon kind="warning" scale="s" open>
<div slot="title">No fruits found</div>
<div slot="message">Try a different fruit?</div>
</calcite-notice>
</calcite-list>`;
18 changes: 18 additions & 0 deletions packages/calcite-components/src/components/list/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ export class List

@State() hasFilterActionsStart = false;

@State() hasFilterNoResults = false;

listItems: HTMLCalciteListItemElement[] = [];

mutationObserver = createObserver("mutation", () => this.updateListItems());
Expand Down Expand Up @@ -499,8 +501,10 @@ export class List
filterEnabled,
filterPlaceholder,
filterText,
filteredItems,
hasFilterActionsStart,
hasFilterActionsEnd,
hasFilterNoResults,
} = this;
return (
<InteractiveContainer disabled={this.disabled}>
Expand Down Expand Up @@ -553,6 +557,16 @@ export class List
<slot onSlotchange={this.handleDefaultSlotChange} />
</tbody>
</table>
<div
aria-live="polite"
data-test-id="no-results-container"
hidden={!(hasFilterNoResults && filterEnabled && filterText && !filteredItems.length)}
>
<slot
name={SLOTS.filterNoResults}
onSlotchange={this.handleFilterNoResultsSlotChange}
/>
</div>
</div>
</InteractiveContainer>
);
Expand Down Expand Up @@ -663,6 +677,10 @@ export class List
this.hasFilterActionsEnd = slotChangeHasAssignedElement(event);
};

private handleFilterNoResultsSlotChange = (event: Event): void => {
this.hasFilterNoResults = slotChangeHasAssignedElement(event);
};

private setActiveListItem = (): void => {
const { focusableItems } = this;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const debounceTimeout = 0;
export type SelectionAppearance = "border" | "icon";

export const SLOTS = {
filterNoResults: "filter-no-results",
filterActionsStart: "filter-actions-start",
filterActionsEnd: "filter-actions-end",
};
4 changes: 4 additions & 0 deletions packages/calcite-components/src/demos/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ <h1 style="margin: 0 auto; text-align: center">List</h1>
style="color: var(--calcite-color-status-success)"
></calcite-icon>
</calcite-list-item>
<calcite-notice slot="filter-no-results" icon kind="warning" scale="s" open>
<div slot="title">No Results</div>
<div slot="message">Try a different filter</div>
</calcite-notice>
</calcite-list>
</div>
</div>
Expand Down
Loading