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(alert): add 'queue' property to prioritize the ordering of alerts when opened #10029

Merged
merged 12 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
13 changes: 13 additions & 0 deletions packages/calcite-components/.storybook/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { TimeZoneMode } from "../src/components/input-time-zone/interfaces.ts";
import { DisplayMode } from "../src/components/sheet/interfaces.ts";
import { ShellDisplayMode } from "../src/components/shell/interfaces.ts";
import { OverlayPositioning } from "../src/components";
import { AlertDuration } from "../src/components/alert/interfaces";

interface AttributeMetadata<T> {
values: T[];
Expand All @@ -35,6 +36,7 @@ interface AttributeMetadata<T> {
interface CommonAttributes {
alignment: AttributeMetadata<Alignment>;
appearance: AttributeMetadata<Appearance>;
duration: AttributeMetadata<AlertDuration>;
scale: AttributeMetadata<Scale>;
logicalFlowPosition: AttributeMetadata<LogicalFlowPosition>;
position: AttributeMetadata<Position>;
Expand All @@ -60,11 +62,13 @@ interface CommonAttributes {
selectionAppearance: AttributeMetadata<SelectionAppearance>;
shellDisplayMode: AttributeMetadata<ShellDisplayMode>;
overlayPositioning: AttributeMetadata<OverlayPositioning>;
numberingSystem: AttributeMetadata<string>;
}

const logicalFlowPositionOptions: LogicalFlowPosition[] = ["inline-start", "inline-end", "block-start", "block-end"];
const positionOptions: Position[] = ["start", "end", "top", "bottom"];
const scaleOptions: Scale[] = ["s", "m", "l"];
const durationOptions: AlertDuration[] = ["slow", "medium", "fast"];
const alignmentOptions: Alignment[] = ["start", "center", "end"];
const appearanceOptions: Appearance[] = ["solid", "outline", "outline-fill", "transparent"];
const statusOptions: Status[] = ["invalid", "valid", "idle"];
Expand Down Expand Up @@ -93,6 +97,7 @@ const layoutOptions: Layout[] = [
"none",
"horizontal-single",
];
const numberingSystems = ["arab", "arabext", "latn"];
const dirOptions: Dir[] = ["ltr", "rtl"];
const buttonTypeOptions: TileSelectType[] = ["radio", "checkbox"];
const interactionModeOptions: TableInteractionMode[] = ["interactive", "static"];
Expand Down Expand Up @@ -128,6 +133,10 @@ export const ATTRIBUTES: CommonAttributes = {
values: appearanceOptions,
defaultValue: appearanceOptions[0],
},
duration: {
values: durationOptions,
defaultValue: durationOptions[1],
},
logicalFlowPosition: {
values: logicalFlowPositionOptions,
defaultValue: logicalFlowPositionOptions[2],
Expand Down Expand Up @@ -228,4 +237,8 @@ export const ATTRIBUTES: CommonAttributes = {
values: shellDisplayModeOptions,
defaultValue: shellDisplayModeOptions[0],
},
numberingSystem: {
values: numberingSystems,
defaultValue: numberingSystems[2],
},
};
8 changes: 8 additions & 0 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,10 @@ export namespace Components {
* Sets focus on the component's "close" button, the first focusable item.
*/
"setFocus": () => Promise<void>;
/**
* Specifies the priority of the component. urgent alerts will be shown first.
*/
"urgent": boolean;
}
interface CalciteAvatar {
/**
Expand Down Expand Up @@ -8464,6 +8468,10 @@ declare namespace LocalJSX {
* Specifies the size of the component.
*/
"scale"?: Scale;
/**
* Specifies the priority of the component. urgent alerts will be shown first.
*/
"urgent"?: boolean;
}
interface CalciteAvatar {
/**
Expand Down
72 changes: 70 additions & 2 deletions packages/calcite-components/src/components/alert/alert.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { E2EElement, E2EPage, newE2EPage } from "@stencil/core/testing";
import { html } from "../../../support/formatting";
import { accessible, defaults, hidden, HYDRATED_ATTR, renders, t9n } from "../../tests/commonTests";
import { getElementXY } from "../../tests/utils";
import { accessible, defaults, hidden, HYDRATED_ATTR, reflects, renders, t9n } from "../../tests/commonTests";
import { getElementXY, skipAnimations } from "../../tests/utils";
import { openClose } from "../../tests/commonTests";
import { CSS, DURATIONS } from "./resources";

Expand All @@ -15,6 +15,19 @@ describe("defaults", () => {
propertyName: "embedded",
defaultValue: false,
},
{
propertyName: "urgent",
defaultValue: false,
},
]);
});

describe("reflects", () => {
reflects("calcite-alert", [
{
propertyName: "urgent",
value: true,
},
]);
});

Expand Down Expand Up @@ -189,6 +202,61 @@ describe("calcite-alert", () => {
expect(await alert3.isVisible()).toBe(true);
});

it("should handle urgent alerts", async () => {
const page = await newE2EPage();
await skipAnimations(page);
await page.setContent(`
driskull marked this conversation as resolved.
Show resolved Hide resolved
<div>
driskull marked this conversation as resolved.
Show resolved Hide resolved
<calcite-alert id="alert-1">
${alertContent}
</calcite-alert>
<calcite-alert id="alert-2">
${alertContent}
</calcite-alert>
<calcite-alert id="alert-3">
${alertContent}
</calcite-alert>
</div>`);

const alert1 = await page.find("#alert-1");
const alert2 = await page.find("#alert-2");
const alert3 = await page.find("#alert-3");

expect(await alert1.isVisible()).toBe(false);
expect(await alert2.isVisible()).toBe(false);
expect(await alert3.isVisible()).toBe(false);

alert1.setProperty("open", true);
await page.waitForChanges();
alert2.setProperty("open", true);
await page.waitForChanges();
alert3.setProperty("urgent", true);
await page.waitForChanges();
alert3.setProperty("open", true);
await page.waitForChanges();
await page.waitForTimeout(animationDurationInMs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check if this is needed? #9958 fixed some issues that prevented alert (among other components) from honoring --calcite-duration-factor (used by skipAnimations). If you still need to wait for the alert to be open, you could listen for the open event instead.

Copy link
Member Author

@driskull driskull Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is still needed because the queue uses a setTimeout to wait for the animation to end.

private openAlert(): void {
window.clearTimeout(this.queueTimeout);
this.queueTimeout = window.setTimeout(() => (this.queued = false), 300);
}

I think that above would need to change to pull from the CSS var --calcite-internal-animation-timing-slow and convert the string MS value to an integer.

Maybe a good follow up issue?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up issue sounds good. Thanks for checking!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


expect(await alert1.isVisible()).toBe(true);
expect(await alert2.isVisible()).toBe(true);
expect(await alert3.isVisible()).toBe(true);

const alert1Container = await page.find(`#alert-1 >>> .${CSS.container}`);
const alert2Container = await page.find(`#alert-2 >>> .${CSS.container}`);
const alert3Container = await page.find(`#alert-3 >>> .${CSS.container}`);

expect(await alert1Container.isVisible()).toBe(false);
expect(await alert2Container.isVisible()).toBe(false);
expect(await alert3Container.isVisible()).toBe(true);

alert2.setProperty("urgent", true);
await page.waitForChanges();
await page.waitForTimeout(animationDurationInMs);

expect(await alert1Container.isVisible()).toBe(false);
expect(await alert2Container.isVisible()).toBe(true);
expect(await alert3Container.isVisible()).toBe(false);
});

it("correctly assigns a default placement class", async () => {
const page = await newE2EPage();
await page.setContent(`
Expand Down
112 changes: 110 additions & 2 deletions packages/calcite-components/src/components/alert/alert.stories.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,68 @@
import { iconNames } from "../../../.storybook/helpers";
import { modesDarkDefault } from "../../../.storybook/utils";
import { boolean, modesDarkDefault } from "../../../.storybook/utils";
import { html } from "../../../support/formatting";
import { ATTRIBUTES } from "../../../.storybook/resources";
import { menuPlacements } from "../../utils/floating-ui";
import { Alert } from "./Alert";
const { scale, duration, kind, numberingSystem } = ATTRIBUTES;

interface AlertStoryArgs
extends Pick<
Alert,
| "autoClose"
| "autoCloseDuration"
| "icon"
| "iconFlipRtl"
| "kind"
| "label"
| "numberingSystem"
| "open"
| "placement"
| "scale"
| "urgent"
> {}

export default {
title: "Components/Alert",
args: {
autoClose: false,
autoCloseDuration: duration.defaultValue,
icon: "",
iconFlipRtl: false,
kind: kind.defaultValue,
label: "Alert",
numberingSystem: numberingSystem[2],
open: true,
placement: menuPlacements[4],
scale: "m",
urgent: false,
},
argTypes: {
autoCloseDuration: {
options: duration.values,
control: { type: "select" },
},
icon: {
options: iconNames,
control: { type: "select" },
},
kind: {
options: kind.values.filter((option) => option !== "inverse" && option !== "neutral"),
control: { type: "select" },
},
numberingSystem: {
options: numberingSystem,
control: { type: "select" },
},
placement: {
options: menuPlacements,
control: { type: "select" },
},
scale: {
options: scale.values,
control: { type: "select" },
},
},
parameters: {
chromatic: {
delay: 500,
Expand All @@ -23,6 +82,29 @@ const wrapperStyles = html`
</style>
`;

export const simple = (args: AlertStoryArgs): string => html`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨🏆✨

${wrapperStyles}
<div class="wrapper">
<calcite-alert
${boolean("auto-close", args.autoClose)}
${boolean("open", args.open)}
${boolean("icon-flip-rtl", args.iconFlipRtl)}
${boolean("urgent", args.urgent)}
auto-close-duration="${args.autoCloseDuration}"
scale="${args.scale}"
kind="${args.kind}"
icon="${args.icon}"
label="${args.label}"
numbering-system="${args.numberingSystem}"
placement="${args.placement}"
>
<div slot="title">Here's a general bit of information</div>
<div slot="message">Some kind of contextually relevant content</div>
<calcite-link slot="link" title="my action">Take action</calcite-link>
</calcite-alert>
</div>
`;

export const titleMessageLink = (): string => html`
${wrapperStyles}
<div class="wrapper">
Expand Down Expand Up @@ -178,7 +260,7 @@ export const actionsEndQueued_TestOnly = (): string => html`
<script>
setTimeout(() => {
document.querySelector("#two").open = true;
}, "1000");
}, 250);
</script>
</div>
`;
Expand All @@ -200,3 +282,29 @@ export const textAlignDoesNotAffectComponentAlignment_TestOnly = (): string => h
</calcite-alert>
</div>
`;

export const withUrgent = (): string => html`
${wrapperStyles}
<div class="wrapper">
<calcite-alert id="one" kind="brand" open>
<div slot="title">Open by default</div>
<div slot="message">We thought you might want to take a look</div>
</calcite-alert>
<calcite-alert id="two" urgent kind="danger">
<div slot="title">Urgent Alert</div>
<div slot="message">We thought you might want to take a look</div>
</calcite-alert>
<calcite-alert id="three" kind="success">
<div slot="title">Third Alert</div>
<div slot="message">We thought you might want to take a look</div>
</calcite-alert>
<script>
setTimeout(() => {
document.querySelector("#two").open = true;
}, 100);
setTimeout(() => {
document.querySelector("#three").open = true;
}, 250);
</script>
</div>
`;
Loading
Loading