Skip to content

Commit

Permalink
refactor: snackbar (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
njfamirm authored and arashagp committed Dec 17, 2024
2 parents 151bf23 + 41ac484 commit 6995780
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 233 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ This repository contains numerous small utility packages. These packages serve v

Here is a brief overview of the included libraries:

1. [`element`](./packages/element): Utility functions and mixins for building high-performance web components with Lit.
2. [`alpine`](./packages/alpine): Utility functions to enhance Alpine.js usage with backup support.
3. [`typescript-config`](./packages/typescript-config): Base TypeScript configuration for Nexim projects.
4. [`prettier-config`](./packages/prettier-config): Base Prettier configuration for Nexim projects.
5. [`eslint-config`](./packages/eslint-config): Base Eslint configuration for Nexim projects.
1. [`snackbar`](./packages/snackbar): Snackbar component, It includes utilities for managing the snackbar's state and animations.

For more detailed information and guidelines on how to use each package, please refer to each package's README.

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "nanolib-monorepo",
"name": "design-system-monorepo",
"private": true,
"description": "Necessary library for all ECMAScript (JavaScript/TypeScript) projects.",
"repository": "https://github.com/the-nexim/nanolib",
"repository": "https://github.com/the-nexim/design-system",
"license": "AGPL-3.0-only",
"author": "S. Amir Mohammad Najafi <[email protected]> (https://www.njfamirm.ir)",
"contributors": [
Expand Down Expand Up @@ -42,9 +42,9 @@
"@lerna-lite/publish": "^3.10.1",
"@lerna-lite/run": "^3.10.1",
"@lerna-lite/version": "^3.10.1",
"@nexim/eslint-config": "workspace:^",
"@nexim/prettier-config": "workspace:^",
"@nexim/typescript-config": "workspace:^",
"@nexim/eslint-config": "^1.0.1",
"@nexim/prettier-config": "^1.0.1",
"@nexim/typescript-config": "^1.0.1",
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "^7.15.0",
"@typescript-eslint/parser": "^7.15.0",
Expand Down
13 changes: 4 additions & 9 deletions packages/snackbar/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# @nexim/snackbar

This package provides a customizable snackbar component for displaying brief messages to users. It includes utilities for managing the snackbar's state and animations.

![NPM Version](https://img.shields.io/npm/v/%40nexim%2Fsnackbar)
![npm bundle size](https://img.shields.io/bundlephobia/min/%40nexim%2Fsnackbar)
![Build & Lint & Test](https://github.com/the-nexim/nanolib/actions/workflows/build-lint-test.yaml/badge.svg)
![Build & Lint & Test](https://github.com/the-nexim/design-system/actions/workflows/build-lint-test.yaml/badge.svg)
![NPM Downloads](https://img.shields.io/npm/dm/%40nexim%2Fsnackbar)
![NPM License](https://img.shields.io/npm/l/%40nexim%2Fsnackbar)

## Overview

`@nexim/snackbar` is a versatile library designed to provide a customizable snackbar component for displaying brief messages to users. It includes utilities for managing the snackbar's state and animations, ensuring efficiency and scalability in high-performance projects.
Snackbar component. It includes utilities for managing the snackbar's state and animations.

## Installation

Expand All @@ -34,14 +32,11 @@ import {snackbarSignal} from '@nexim/snackbar';

snackbarSignal.notify({
content: 'This is a snackbar message',
// The following properties are optional.
action: {
label: 'Undo',
handler: () => {
console.log('Action button clicked');
},
signalId: 'undo-handler',
},
duration: '4s',
duration: '5s',
addCloseButton: true,
});
```
12 changes: 6 additions & 6 deletions packages/snackbar/package.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"name": "@nexim/snackbar",
"version": "0.0.0",
"description": "A customizable snackbar component for displaying brief messages to users, with state management and animation utilities.",
"description": "Snackbar component, It includes utilities for managing the snackbar's state and animations.",
"keywords": [
"snackbar",
"notification",
"web-component",
"typescript",
"nexim"
],
"homepage": "https://github.com/the-nexim/nanolib/tree/next/packages/snackbar#readme",
"homepage": "https://github.com/the-nexim/design-system/tree/next/packages/snackbar#readme",
"bugs": {
"url": "https://github.com/the-nexim/nanolib/issues"
"url": "https://github.com/the-nexim/design-system/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/the-nexim/nanolib",
"url": "https://github.com/the-nexim/design-system",
"directory": "packages/snackbar"
},
"license": "AGPL-3.0-only",
Expand Down Expand Up @@ -62,13 +62,13 @@
"@alwatr/package-tracer": "^5.0.0",
"@alwatr/parse-duration": "^5.0.0",
"@alwatr/wait": "^1.1.16",
"@nexim/element": "workspace:^",
"@nexim/element": "^1.0.6",
"lit": "^3.2.1"
},
"devDependencies": {
"@alwatr/nano-build": "^5.0.0",
"@alwatr/type-helper": "^5.0.0",
"@nexim/typescript-config": "workspace:^",
"@nexim/typescript-config": "^1.0.1",
"ava": "^6.2.0",
"typescript": "^5.6.3"
},
Expand Down
8 changes: 4 additions & 4 deletions packages/snackbar/src/lib/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import {LightDomMixin, LoggerMixin} from '@nexim/element';
import {html, LitElement, nothing, type PropertyValues, type TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators.js';

import {snackbarActionButtonClickedSignal} from './handler.js';
import {snackbarActionButtonClickedSignal} from './signal.js';
import {waitForNextFrame} from './utils.js';

declare global {
interface HTMLElementTagNameMap {
'snack-bar': SnackbarComponent;
'snack-bar': SnackbarElement;
}
}

@customElement('snack-bar')
export class SnackbarComponent extends LightDomMixin(LoggerMixin(LitElement)) {
export class SnackbarElement extends LightDomMixin(LoggerMixin(LitElement)) {
/**
* The content to be displayed inside the snackbar.
*/
Expand Down Expand Up @@ -52,7 +52,7 @@ export class SnackbarComponent extends LightDomMixin(LoggerMixin(LitElement)) {

this.removeAttribute('open');

await waitForTimeout(SnackbarComponent.openAndCloseAnimationDuration__);
await waitForTimeout(SnackbarElement.openAndCloseAnimationDuration__);
this.remove();
}

Expand Down
127 changes: 57 additions & 70 deletions packages/snackbar/src/lib/handler.ts
Original file line number Diff line number Diff line change
@@ -1,101 +1,79 @@
import {AlwatrSignal, AlwatrTrigger} from '@alwatr/flux';
import {createLogger} from '@alwatr/logger';
import {parseDuration, type Duration} from '@alwatr/parse-duration';
import {parseDuration} from '@alwatr/parse-duration';
import {waitForTimeout} from '@alwatr/wait';

import type {SnackbarComponent} from './element.js';
import {snackbarActionButtonClickedSignal, snackbarSignal} from './signal.js';

import type {SnackbarElement} from './element.js';
import type {SnackbarOptions} from './type.js';

const logger = /* @__PURE__ */ createLogger(`${__package_name__}/handler`);

/**
* @property content - Content to be displayed in the snackbar.
* @property {action} - The action button configuration.
* @property action.label - The label for the action button.
* @property action.handler - The handler function for the action button.
* @property duration - Duration for which the snackbar is displayed. `infinite` for infinite duration.
* Duration for which the snackbar is displayed.
* `infinite` for infinite duration.
* @property addCloseButton - Whether to add a close button to the snackbar.
* Store the function to close the last snackbar.
*/
export type SnackbarOptions = {
content: string;
action?: {
label: string;
handler: () => void;
};
duration?: Duration | 'infinite';
addCloseButton?: boolean;
};
let closeLastSnackbar: (() => Promise<void>) | null = null;

/**
* Signal for when the snackbar action button is clicked.
* Store the function to unsubscribe the action button handler after close or action button clicked.
*/
export const snackbarActionButtonClickedSignal = new AlwatrTrigger({
name: 'snackbar-action-button-clicked',
});
let unsubscribeActionButtonHandler: (() => void) | null = null;

/**
* Signal for displaying the snackbar.
* Create snackbar element with given options.
*
* @example
* import {snackbarSignal} from '@nexim/snackbar';
* @param options - Options for configuring the snackbar.
* @returns The created snackbar element.
*/
function createSnackbarElement(options: SnackbarOptions): SnackbarElement {
const element = document.createElement('snack-bar');
element.setAttribute('content', options.content);

if (options.addCloseButton === true) {
element.setAttribute('add-close-button', '');
}

if (options.action != null) {
element.setAttribute('action-button-label', options.action.label);
}

return element;
}

/**
* Handle action button click.
*
* snackbarSignal.notify({
* content: 'This is a snackbar message',
* // The following properties are optional.
* action: {
* label: 'Undo',
* handler: () => {
* console.log('Action button clicked');
* },
* },
* duration: '4s',
* addCloseButton: true,
* });
* @param options - Options for configuring the snackbar.
* @param closeSnackbar - Function to close the snackbar.
*/
export const snackbarSignal = /* @__PURE__ */ new AlwatrSignal<SnackbarOptions>({name: 'snackbar'});
function handleActionButtonClick(closeSnackbar: () => Promise<void>): void {
const actionButtonClickHandler = () => {
logger.logOther?.('Snackbar action button clicked.');

// Subscribe to the snackbar signal to show the snackbar when the signal is emitted.
snackbarSignal.subscribe((options) => {
showSnackbar(options);
});
return closeSnackbar();
};

let closeLastSnackbar: (() => Promise<void>) | null = null;
let unsubscribeActionButtonHandler: (() => void) | null = null;
// Subscribe to the action button click
unsubscribeActionButtonHandler = snackbarActionButtonClickedSignal.subscribe(actionButtonClickHandler.bind(null), {
once: true,
}).unsubscribe;
}

/**
* Displays the snackbar with the given options.
*
* @param options - Options for configuring the snackbar.
*/
async function showSnackbar(options: SnackbarOptions): Promise<void> {
logger.logMethodArgs?.('showSnackbar', {options});

// Parse the duration

// Set default duration if not provided
options.duration ??= '4s';

const element = document.createElement('snack-bar') as SnackbarComponent;
options.duration ??= '5s';

element.setAttribute('content', options.content);

if (options.addCloseButton === true) {
element.setAttribute('add-close-button', '');
}

if (options.action != null) {
element.setAttribute('action-button-label', options.action.label);

// Subscribe to the action button click
unsubscribeActionButtonHandler = snackbarActionButtonClickedSignal.subscribe(() => {
options.action!.handler();

return closeSnackbar_();
}).unsubscribe;
}
const element = createSnackbarElement(options);

let closed = false;
const closeSnackbar_ = async () => {
const closeSnackbar = async () => {
if (closed === true) return;
logger.logMethodArgs?.('closeSnackbar', {options});

Expand All @@ -104,13 +82,22 @@ async function showSnackbar(options: SnackbarOptions): Promise<void> {
closed = true;
};

if (options.action != null) {
handleActionButtonClick(closeSnackbar);
}

// Close the last snackbar if it exists
await closeLastSnackbar?.();
closeLastSnackbar = closeSnackbar_;
closeLastSnackbar = closeSnackbar;
document.body.appendChild(element);

// Set a timeout to close the snackbar if duration is not infinite
if (options.duration !== 'infinite') {
waitForTimeout(parseDuration(options.duration)).then(closeSnackbar_);
waitForTimeout(parseDuration(options.duration)).then(closeSnackbar);
}
}

// Subscribe to the snackbar signal to show the snackbar when the signal is emitted.
snackbarSignal.subscribe((options) => {
showSnackbar(options);
});
30 changes: 30 additions & 0 deletions packages/snackbar/src/lib/signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {AlwatrSignal, AlwatrTrigger} from '@alwatr/flux';

import type {SnackbarOptions} from './type.js';

/**
* Signal triggered when the snackbar action button is clicked to close snackbar.
*/
export const snackbarActionButtonClickedSignal = /* @__PURE__ */ new AlwatrTrigger({
name: 'snackbar-action-button-clicked',
});

/**
* Signal for displaying the snackbar.
*
* @example
* import {snackbarSignal} from '@nexim/snackbar';
*
* snackbarSignal.notify({
* content: 'This is a snackbar message',
* action: {
* label: 'Undo',
* handler: () => {
* console.log('Action button clicked');
* },
* },
* duration: '5s',
* addCloseButton: true,
* });
*/
export const snackbarSignal = /* @__PURE__ */ new AlwatrSignal<SnackbarOptions>({name: 'snackbar'});
19 changes: 19 additions & 0 deletions packages/snackbar/src/lib/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type {Duration} from '@alwatr/parse-duration';

/**
* @property content - Content to be displayed in the snackbar.
* @property [action] - The action button configuration.
* @property action.label - The label for the action button.
* @property action.signalId - The signal ID to be emitted when the action button is clicked.
* @property duration - Duration for which the snackbar is displayed. `infinite` for infinite duration.
* @property addCloseButton - Whether to add a close button to the snackbar.
*/
export type SnackbarOptions = {
content: string;
action?: {
signalId: string;
label: string;
};
duration?: Duration | 'infinite';
addCloseButton?: boolean;
};
2 changes: 1 addition & 1 deletion packages/snackbar/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import {packageTracer} from '@alwatr/package-tracer';
__dev_mode__: packageTracer.add(__package_name__, __package_version__);

export * from './lib/element.js';
export * from './lib/handler.js';
export {snackbarSignal} from './lib/signal.js';
Loading

0 comments on commit 6995780

Please sign in to comment.