Skip to content

Commit 6995780

Browse files
njfamirmarashagp
authored andcommitted
refactor: snackbar (#20)
2 parents 151bf23 + 41ac484 commit 6995780

File tree

10 files changed

+187
-233
lines changed

10 files changed

+187
-233
lines changed

README.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ This repository contains numerous small utility packages. These packages serve v
44

55
Here is a brief overview of the included libraries:
66

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

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

package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"name": "nanolib-monorepo",
2+
"name": "design-system-monorepo",
33
"private": true,
44
"description": "Necessary library for all ECMAScript (JavaScript/TypeScript) projects.",
5-
"repository": "https://github.com/the-nexim/nanolib",
5+
"repository": "https://github.com/the-nexim/design-system",
66
"license": "AGPL-3.0-only",
77
"author": "S. Amir Mohammad Najafi <[email protected]> (https://www.njfamirm.ir)",
88
"contributors": [
@@ -42,9 +42,9 @@
4242
"@lerna-lite/publish": "^3.10.1",
4343
"@lerna-lite/run": "^3.10.1",
4444
"@lerna-lite/version": "^3.10.1",
45-
"@nexim/eslint-config": "workspace:^",
46-
"@nexim/prettier-config": "workspace:^",
47-
"@nexim/typescript-config": "workspace:^",
45+
"@nexim/eslint-config": "^1.0.1",
46+
"@nexim/prettier-config": "^1.0.1",
47+
"@nexim/typescript-config": "^1.0.1",
4848
"@types/node": "^22.10.2",
4949
"@typescript-eslint/eslint-plugin": "^7.15.0",
5050
"@typescript-eslint/parser": "^7.15.0",

packages/snackbar/README.md

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
# @nexim/snackbar
22

3-
This package provides a customizable snackbar component for displaying brief messages to users. It includes utilities for managing the snackbar's state and animations.
4-
53
![NPM Version](https://img.shields.io/npm/v/%40nexim%2Fsnackbar)
64
![npm bundle size](https://img.shields.io/bundlephobia/min/%40nexim%2Fsnackbar)
7-
![Build & Lint & Test](https://github.com/the-nexim/nanolib/actions/workflows/build-lint-test.yaml/badge.svg)
5+
![Build & Lint & Test](https://github.com/the-nexim/design-system/actions/workflows/build-lint-test.yaml/badge.svg)
86
![NPM Downloads](https://img.shields.io/npm/dm/%40nexim%2Fsnackbar)
97
![NPM License](https://img.shields.io/npm/l/%40nexim%2Fsnackbar)
108

119
## Overview
1210

13-
`@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.
11+
Snackbar component. It includes utilities for managing the snackbar's state and animations.
1412

1513
## Installation
1614

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

3533
snackbarSignal.notify({
3634
content: 'This is a snackbar message',
37-
// The following properties are optional.
3835
action: {
3936
label: 'Undo',
40-
handler: () => {
41-
console.log('Action button clicked');
42-
},
37+
signalId: 'undo-handler',
4338
},
44-
duration: '4s',
39+
duration: '5s',
4540
addCloseButton: true,
4641
});
4742
```

packages/snackbar/package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
{
22
"name": "@nexim/snackbar",
33
"version": "0.0.0",
4-
"description": "A customizable snackbar component for displaying brief messages to users, with state management and animation utilities.",
4+
"description": "Snackbar component, It includes utilities for managing the snackbar's state and animations.",
55
"keywords": [
66
"snackbar",
77
"notification",
88
"web-component",
99
"typescript",
1010
"nexim"
1111
],
12-
"homepage": "https://github.com/the-nexim/nanolib/tree/next/packages/snackbar#readme",
12+
"homepage": "https://github.com/the-nexim/design-system/tree/next/packages/snackbar#readme",
1313
"bugs": {
14-
"url": "https://github.com/the-nexim/nanolib/issues"
14+
"url": "https://github.com/the-nexim/design-system/issues"
1515
},
1616
"repository": {
1717
"type": "git",
18-
"url": "https://github.com/the-nexim/nanolib",
18+
"url": "https://github.com/the-nexim/design-system",
1919
"directory": "packages/snackbar"
2020
},
2121
"license": "AGPL-3.0-only",
@@ -62,13 +62,13 @@
6262
"@alwatr/package-tracer": "^5.0.0",
6363
"@alwatr/parse-duration": "^5.0.0",
6464
"@alwatr/wait": "^1.1.16",
65-
"@nexim/element": "workspace:^",
65+
"@nexim/element": "^1.0.6",
6666
"lit": "^3.2.1"
6767
},
6868
"devDependencies": {
6969
"@alwatr/nano-build": "^5.0.0",
7070
"@alwatr/type-helper": "^5.0.0",
71-
"@nexim/typescript-config": "workspace:^",
71+
"@nexim/typescript-config": "^1.0.1",
7272
"ava": "^6.2.0",
7373
"typescript": "^5.6.3"
7474
},

packages/snackbar/src/lib/element.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ import {LightDomMixin, LoggerMixin} from '@nexim/element';
33
import {html, LitElement, nothing, type PropertyValues, type TemplateResult} from 'lit';
44
import {customElement, property} from 'lit/decorators.js';
55

6-
import {snackbarActionButtonClickedSignal} from './handler.js';
6+
import {snackbarActionButtonClickedSignal} from './signal.js';
77
import {waitForNextFrame} from './utils.js';
88

99
declare global {
1010
interface HTMLElementTagNameMap {
11-
'snack-bar': SnackbarComponent;
11+
'snack-bar': SnackbarElement;
1212
}
1313
}
1414

1515
@customElement('snack-bar')
16-
export class SnackbarComponent extends LightDomMixin(LoggerMixin(LitElement)) {
16+
export class SnackbarElement extends LightDomMixin(LoggerMixin(LitElement)) {
1717
/**
1818
* The content to be displayed inside the snackbar.
1919
*/
@@ -52,7 +52,7 @@ export class SnackbarComponent extends LightDomMixin(LoggerMixin(LitElement)) {
5252

5353
this.removeAttribute('open');
5454

55-
await waitForTimeout(SnackbarComponent.openAndCloseAnimationDuration__);
55+
await waitForTimeout(SnackbarElement.openAndCloseAnimationDuration__);
5656
this.remove();
5757
}
5858

packages/snackbar/src/lib/handler.ts

+57-70
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,79 @@
1-
import {AlwatrSignal, AlwatrTrigger} from '@alwatr/flux';
21
import {createLogger} from '@alwatr/logger';
3-
import {parseDuration, type Duration} from '@alwatr/parse-duration';
2+
import {parseDuration} from '@alwatr/parse-duration';
43
import {waitForTimeout} from '@alwatr/wait';
54

6-
import type {SnackbarComponent} from './element.js';
5+
import {snackbarActionButtonClickedSignal, snackbarSignal} from './signal.js';
6+
7+
import type {SnackbarElement} from './element.js';
8+
import type {SnackbarOptions} from './type.js';
79

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

1012
/**
11-
* @property content - Content to be displayed in the snackbar.
12-
* @property {action} - The action button configuration.
13-
* @property action.label - The label for the action button.
14-
* @property action.handler - The handler function for the action button.
15-
* @property duration - Duration for which the snackbar is displayed. `infinite` for infinite duration.
16-
* Duration for which the snackbar is displayed.
17-
* `infinite` for infinite duration.
18-
* @property addCloseButton - Whether to add a close button to the snackbar.
13+
* Store the function to close the last snackbar.
1914
*/
20-
export type SnackbarOptions = {
21-
content: string;
22-
action?: {
23-
label: string;
24-
handler: () => void;
25-
};
26-
duration?: Duration | 'infinite';
27-
addCloseButton?: boolean;
28-
};
15+
let closeLastSnackbar: (() => Promise<void>) | null = null;
2916

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

3722
/**
38-
* Signal for displaying the snackbar.
23+
* Create snackbar element with given options.
3924
*
40-
* @example
41-
* import {snackbarSignal} from '@nexim/snackbar';
25+
* @param options - Options for configuring the snackbar.
26+
* @returns The created snackbar element.
27+
*/
28+
function createSnackbarElement(options: SnackbarOptions): SnackbarElement {
29+
const element = document.createElement('snack-bar');
30+
element.setAttribute('content', options.content);
31+
32+
if (options.addCloseButton === true) {
33+
element.setAttribute('add-close-button', '');
34+
}
35+
36+
if (options.action != null) {
37+
element.setAttribute('action-button-label', options.action.label);
38+
}
39+
40+
return element;
41+
}
42+
43+
/**
44+
* Handle action button click.
4245
*
43-
* snackbarSignal.notify({
44-
* content: 'This is a snackbar message',
45-
* // The following properties are optional.
46-
* action: {
47-
* label: 'Undo',
48-
* handler: () => {
49-
* console.log('Action button clicked');
50-
* },
51-
* },
52-
* duration: '4s',
53-
* addCloseButton: true,
54-
* });
46+
* @param options - Options for configuring the snackbar.
47+
* @param closeSnackbar - Function to close the snackbar.
5548
*/
56-
export const snackbarSignal = /* @__PURE__ */ new AlwatrSignal<SnackbarOptions>({name: 'snackbar'});
49+
function handleActionButtonClick(closeSnackbar: () => Promise<void>): void {
50+
const actionButtonClickHandler = () => {
51+
logger.logOther?.('Snackbar action button clicked.');
5752

58-
// Subscribe to the snackbar signal to show the snackbar when the signal is emitted.
59-
snackbarSignal.subscribe((options) => {
60-
showSnackbar(options);
61-
});
53+
return closeSnackbar();
54+
};
6255

63-
let closeLastSnackbar: (() => Promise<void>) | null = null;
64-
let unsubscribeActionButtonHandler: (() => void) | null = null;
56+
// Subscribe to the action button click
57+
unsubscribeActionButtonHandler = snackbarActionButtonClickedSignal.subscribe(actionButtonClickHandler.bind(null), {
58+
once: true,
59+
}).unsubscribe;
60+
}
6561

6662
/**
6763
* Displays the snackbar with the given options.
64+
*
6865
* @param options - Options for configuring the snackbar.
6966
*/
7067
async function showSnackbar(options: SnackbarOptions): Promise<void> {
7168
logger.logMethodArgs?.('showSnackbar', {options});
7269

73-
// Parse the duration
74-
7570
// Set default duration if not provided
76-
options.duration ??= '4s';
77-
78-
const element = document.createElement('snack-bar') as SnackbarComponent;
71+
options.duration ??= '5s';
7972

80-
element.setAttribute('content', options.content);
81-
82-
if (options.addCloseButton === true) {
83-
element.setAttribute('add-close-button', '');
84-
}
85-
86-
if (options.action != null) {
87-
element.setAttribute('action-button-label', options.action.label);
88-
89-
// Subscribe to the action button click
90-
unsubscribeActionButtonHandler = snackbarActionButtonClickedSignal.subscribe(() => {
91-
options.action!.handler();
92-
93-
return closeSnackbar_();
94-
}).unsubscribe;
95-
}
73+
const element = createSnackbarElement(options);
9674

9775
let closed = false;
98-
const closeSnackbar_ = async () => {
76+
const closeSnackbar = async () => {
9977
if (closed === true) return;
10078
logger.logMethodArgs?.('closeSnackbar', {options});
10179

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

85+
if (options.action != null) {
86+
handleActionButtonClick(closeSnackbar);
87+
}
88+
10789
// Close the last snackbar if it exists
10890
await closeLastSnackbar?.();
109-
closeLastSnackbar = closeSnackbar_;
91+
closeLastSnackbar = closeSnackbar;
11092
document.body.appendChild(element);
11193

11294
// Set a timeout to close the snackbar if duration is not infinite
11395
if (options.duration !== 'infinite') {
114-
waitForTimeout(parseDuration(options.duration)).then(closeSnackbar_);
96+
waitForTimeout(parseDuration(options.duration)).then(closeSnackbar);
11597
}
11698
}
99+
100+
// Subscribe to the snackbar signal to show the snackbar when the signal is emitted.
101+
snackbarSignal.subscribe((options) => {
102+
showSnackbar(options);
103+
});

packages/snackbar/src/lib/signal.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {AlwatrSignal, AlwatrTrigger} from '@alwatr/flux';
2+
3+
import type {SnackbarOptions} from './type.js';
4+
5+
/**
6+
* Signal triggered when the snackbar action button is clicked to close snackbar.
7+
*/
8+
export const snackbarActionButtonClickedSignal = /* @__PURE__ */ new AlwatrTrigger({
9+
name: 'snackbar-action-button-clicked',
10+
});
11+
12+
/**
13+
* Signal for displaying the snackbar.
14+
*
15+
* @example
16+
* import {snackbarSignal} from '@nexim/snackbar';
17+
*
18+
* snackbarSignal.notify({
19+
* content: 'This is a snackbar message',
20+
* action: {
21+
* label: 'Undo',
22+
* handler: () => {
23+
* console.log('Action button clicked');
24+
* },
25+
* },
26+
* duration: '5s',
27+
* addCloseButton: true,
28+
* });
29+
*/
30+
export const snackbarSignal = /* @__PURE__ */ new AlwatrSignal<SnackbarOptions>({name: 'snackbar'});

packages/snackbar/src/lib/type.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type {Duration} from '@alwatr/parse-duration';
2+
3+
/**
4+
* @property content - Content to be displayed in the snackbar.
5+
* @property [action] - The action button configuration.
6+
* @property action.label - The label for the action button.
7+
* @property action.signalId - The signal ID to be emitted when the action button is clicked.
8+
* @property duration - Duration for which the snackbar is displayed. `infinite` for infinite duration.
9+
* @property addCloseButton - Whether to add a close button to the snackbar.
10+
*/
11+
export type SnackbarOptions = {
12+
content: string;
13+
action?: {
14+
signalId: string;
15+
label: string;
16+
};
17+
duration?: Duration | 'infinite';
18+
addCloseButton?: boolean;
19+
};

packages/snackbar/src/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ import {packageTracer} from '@alwatr/package-tracer';
33
__dev_mode__: packageTracer.add(__package_name__, __package_version__);
44

55
export * from './lib/element.js';
6-
export * from './lib/handler.js';
6+
export {snackbarSignal} from './lib/signal.js';

0 commit comments

Comments
 (0)