diff --git a/src/components/notification/bl-notification.mdx b/src/components/notification/bl-notification.mdx
new file mode 100644
index 00000000..3be36ef9
--- /dev/null
+++ b/src/components/notification/bl-notification.mdx
@@ -0,0 +1,88 @@
+import { Meta, Canvas, ArgsTable, Story } from "@storybook/addon-docs";
+import * as NotificationStories from "./bl-notification.stories";
+
+
+
+# Notification
+
+[ADR](https://github.com/Trendyol/baklava/issues/141)
+[Figma](https://www.figma.com/file/RrcLH0mWpIUy4vwuTlDeKN/Baklava-Design-Guide?node-id=2790%3A13792)
+
+Notifications are messages that communicate information to the user.
+
+## Design Rules
+
+- Notification **has fixed width (396px)** by default.
+- Notifications are **temporary** by default but can be set permanent.
+- Temporary notifications are **dismissed automatically** after a certain period of time.
+- Temporary notifications remaning time will stop when hovered.
+- It can be dismissed by swiping up on mobile.
+- Notification takes position on top on small screens, and on top right on large screens.
+- Multiple notifications would be visible with a vertical stack. New notifications will come to top on large screens, and will come to bottom on small screens.
+
+## Basic Usage
+
+The `bl-notification` component is a versatile tool for displaying notifications. It exposes two public methods: `addNotification` and `removeNotification`.
+
+The `addNotification` method accepts a notification object as a parameter and returns a notification object that includes the props, an id, and a remove method. The remove method is a wrapper that calls the `removeNotification` method with the id of the notification.
+
+The `removeNotification` method accepts a notification id as a parameter and returns a Promise that resolves when the notification removal animation is complete.
+
+### Adding a Notification
+
+A notification could be added by calling the `addNotification` method with a notification object.
+
+
+
+#### Notification Object
+
+
+
+### Removing a Notification
+
+A notification could be removed by calling the `removeNotification` method with the notification's id.
+
+
+
+#### Await for Removal
+
+The `removeNotification` method returns a Promise that resolves when the notification removal animation is complete. This could be used to await the removal of a notification.
+
+
+
+### Actions
+
+A notification could have a primary and a secondary action. These actions are displayed as buttons on the notification.
+
+
+
+#### Removing a Notification on Action Click
+
+A notification could be removed by calling notification's remove method.
+
+
+
+### Permanent
+
+A notification could be permanent. Permanent notifications are not dismissed automatically. They could be dismissed by clicking the close button.
+
+
+
+### Variants
+
+A notification could have one of the following variants: info, success, warning, error. The variant changes the color of the notification.
+
+
+
+## Reference
+
+
diff --git a/src/components/notification/bl-notification.stories.mdx b/src/components/notification/bl-notification.stories.mdx
deleted file mode 100644
index c7991440..00000000
--- a/src/components/notification/bl-notification.stories.mdx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { Meta, Canvas, Story } from '@storybook/addon-docs';
-import { html } from 'lit';
-import { ifDefined } from 'lit/directives/if-defined.js';
-import { repeat } from 'lit/directives/repeat.js';
-
-
-
-
-# BlNotification
-
-`BlNotification` is a component to display notifications. It supports adding and removing notifications dynamically.
-
-## Props
-
-- `noAnimation`: Disable animations. It will not be possible to use animations if the user has disabled them. Animations will respect the user's preferences regardless of this property.
-- `duration`: Sets the default duration of notifications in seconds
-
-## Methods
-
-- `addNotification(props: NotificationProps)`: Adds a notification to the list of notifications.
-- `removeNotification(id: string)`: Removes a notification from the list of notifications.
-
-
diff --git a/src/components/notification/bl-notification.stories.ts b/src/components/notification/bl-notification.stories.ts
new file mode 100644
index 00000000..3f6d3a14
--- /dev/null
+++ b/src/components/notification/bl-notification.stories.ts
@@ -0,0 +1,332 @@
+import { html } from "lit";
+import { ifDefined } from "lit/directives/if-defined.js";
+import { Meta, StoryObj } from "@storybook/web-components";
+import type BlNotification from "./bl-notification";
+import type { NotificationProps, Notification } from "./bl-notification";
+
+const meta: Meta = {
+ title: "Components/Notification",
+ component: "bl-notification",
+ argTypes: {
+ noAnimation: {
+ control: "boolean",
+ },
+ duration: {
+ control: "number",
+ },
+ },
+};
+
+export default meta;
+
+const addNotification = (selector: string, options: NotificationProps) => {
+ const el = document.querySelector(selector) as BlNotification;
+
+ return el?.addNotification(options);
+};
+
+export type NotificationArgs = {
+ noAnimation: boolean;
+ duration: number;
+};
+
+export type Story = StoryObj;
+
+export const AddExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Add Notification
+
+
+ `;
+ },
+ play: () => {
+ addNotification("#basic", {
+ description: "Notification Description",
+ });
+ },
+};
+
+export const RemoveExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Remove Notification
+
+
+ `;
+ },
+};
+
+export const RemoveAwaitExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Remove Notifications
+
+
+ `;
+ },
+};
+
+export const ActionsExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Add Notification
+
+
+ `;
+ },
+ play: () => {
+ addNotification("#basic", {
+ caption: "Notification Caption",
+ description: "Notification Description",
+ permanent: true,
+ primaryAction: {
+ label: "Primary Action",
+ onClick: () => {
+ window.alert("Primary Action Clicked");
+ },
+ },
+ secondaryAction: {
+ label: "Secondary Action",
+ onClick: () => {
+ window.alert("Secondary Action Clicked");
+ },
+ },
+ });
+ },
+};
+
+export const ActionsRemoveExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Add Notification
+
+
+ `;
+ },
+ play: () => {
+ addNotification("#basic", {
+ caption: "Notification Caption",
+ description: "Notification Description",
+ permanent: true,
+ primaryAction: {
+ label: "Primary Action",
+ onClick: (notification: Notification) => {
+ notification.remove();
+ },
+ },
+ secondaryAction: {
+ label: "Secondary Action",
+ onClick: (notification: Notification) => {
+ notification.remove();
+ },
+ },
+ });
+ },
+};
+
+export const PermanentExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Add Notification
+
+
+ `;
+ },
+ play: () => {
+ addNotification("#basic", {
+ caption: "Notification Caption",
+ description: "Notification Description",
+ permanent: true,
+ });
+ },
+};
+
+export const VariantsExample: Story = {
+ render(args: NotificationArgs) {
+ return html`
+
+ Add Notifications
+
+
+ `;
+ },
+ play: () => {
+ const variants = ["info", "success", "warning", "error"] as const;
+
+ for (const variant of variants) {
+ addNotification("#basic", {
+ caption: variant,
+ description: variant,
+ permanent: true,
+ variant,
+ });
+ }
+ },
+};
diff --git a/src/components/notification/bl-notification.test.ts b/src/components/notification/bl-notification.test.ts
index cb93b6c4..e48a59bc 100644
--- a/src/components/notification/bl-notification.test.ts
+++ b/src/components/notification/bl-notification.test.ts
@@ -91,11 +91,11 @@ describe("bl-notification", () => {
.to.have.attribute("style")
.match(/height: \d+px/);
assert.isTrue(notificationEl.classList.contains("removing"));
- assert.lengthOf(el.notifications, 1);
+ assert.lengthOf(el.notificationList, 1);
await animationPromise;
await el.updateComplete;
- assert.lengthOf(el.notifications, 0);
+ assert.lengthOf(el.notificationList, 0);
});
it("should return false if notification does not exist", async () => {
@@ -350,7 +350,7 @@ describe("bl-notification", () => {
duration="5"
icon="academy"
variant="info"
- id="${el.notifications[0].id}"
+ id="${el.notificationList[0].id}"
>
test
@@ -382,7 +382,7 @@ describe("bl-notification", () => {
await el.updateComplete;
- assert.equal(el.notifications[0], notification);
+ assert.equal(el.notificationList[0], notification);
});
it("should return functional remove method", async () => {
@@ -404,7 +404,7 @@ describe("bl-notification", () => {
await el.updateComplete;
- assert.lengthOf(el.notifications, 0);
+ assert.lengthOf(el.notificationList, 0);
});
});
});
@@ -463,7 +463,7 @@ describe("bl-notification", () => {
sendTouchEvent(100, -100, notificationEl, "touchend");
- expect(removeSpy).to.have.been.calledOnceWith(el.notifications[0].id);
+ expect(removeSpy).to.have.been.calledOnceWith(el.notificationList[0].id);
});
it("should not remove notification when user swipes up less than 50px", async () => {
diff --git a/src/components/notification/bl-notification.ts b/src/components/notification/bl-notification.ts
index 914c9209..d5cbe20c 100644
--- a/src/components/notification/bl-notification.ts
+++ b/src/components/notification/bl-notification.ts
@@ -41,9 +41,6 @@ export default class BlNotification extends LitElement {
return [style];
}
- @state()
- notifications: Notification[] = [];
-
/**
* Disable animations.
* It will not be possible to use animations if the user has disabled them.
@@ -58,6 +55,13 @@ export default class BlNotification extends LitElement {
@property({ type: Number })
duration = 7;
+ @state()
+ private notifications: Notification[] = [];
+
+ public get notificationList() {
+ return this.notifications;
+ }
+
private touchStartY = 0;
public get touchStart() {
@@ -79,9 +83,9 @@ export default class BlNotification extends LitElement {
// TODO id generation
const id = Math.random().toString(36).substr(2, 9);
const notification: Notification = {
- duration: this.duration,
...props,
id,
+ duration: props.duration || this.duration,
remove: () => this.removeNotification(id),
};
diff --git a/src/components/notification/card/bl-notification-card.test.ts b/src/components/notification/card/bl-notification-card.test.ts
index d126b7d9..332b0105 100644
--- a/src/components/notification/card/bl-notification-card.test.ts
+++ b/src/components/notification/card/bl-notification-card.test.ts
@@ -119,6 +119,14 @@ describe("bl-notification-card", () => {
});
expect(style.getPropertyValue("animation-play-state")).to.equal("paused");
});
+
+ it("should close automatically if duration is 0", async () => {
+ const el = await fixture(
+ html` Description `
+ );
+
+ expect(el.closed).to.be.true;
+ });
});
describe("Permanent", () => {
diff --git a/src/components/notification/card/bl-notification-card.ts b/src/components/notification/card/bl-notification-card.ts
index f787af15..cd839365 100644
--- a/src/components/notification/card/bl-notification-card.ts
+++ b/src/components/notification/card/bl-notification-card.ts
@@ -56,7 +56,7 @@ export default class BlNotificationCard extends LitElement {
/**
* Sets notification variant.
* @attr variant
- * @type {AlertVariant}
+ * @type {NotificationVariant}
* @default "info"
*/
@property({ reflect: true })
@@ -116,6 +116,11 @@ export default class BlNotificationCard extends LitElement {
return;
}
+ if (this.duration <= 0) {
+ this.close(CloseSource.DurationEnded);
+ return;
+ }
+
setTimeout(() => {
this.shadowRoot?.querySelector(".remaining")?.addEventListener(
"animationend",
@@ -128,6 +133,7 @@ export default class BlNotificationCard extends LitElement {
}
private close(source: CloseSource) {
+ console.log("close");
const requestCloseEvent = this.onRequestClose({ source }, { cancelable: true });
if (requestCloseEvent.defaultPrevented) {