sn-modal 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ng-package.json +7 -0
- package/package.json +28 -0
- package/src/lib/sn-modal.component.spec.ts +56 -0
- package/src/lib/sn-modal.component.ts +35 -0
- package/src/lib/sn-modal.html +18 -0
- package/src/lib/sn-modal.scss +38 -0
- package/src/public-api.ts +1 -0
- package/tsconfig.lib.json +11 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +8 -0
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sn-modal",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "SnModal is an Angular 21 standalone modal dialog component",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Swapnil Nakate",
|
|
7
|
+
"email": "nakate.swapnil7@gmail.com",
|
|
8
|
+
"url": "https://swapnilnakate.in"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@angular/common": "^21.0.0",
|
|
12
|
+
"@angular/core": "^21.0.0"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"tslib": "^2.3.0"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/swapnilnakate7/sn-ui/issues",
|
|
20
|
+
"email": "nakate.swapnil7@gmail.com"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"url": "https://github.com/swapnilnakate7/sn-ui",
|
|
24
|
+
"type": "git",
|
|
25
|
+
"directory": "projects/sn-modal"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { SnModalComponent } from './sn-modal.component';
|
|
3
|
+
|
|
4
|
+
describe('SnModalComponent', () => {
|
|
5
|
+
let component: SnModalComponent;
|
|
6
|
+
let fixture: ComponentFixture<SnModalComponent>;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
await TestBed.configureTestingModule({
|
|
10
|
+
imports: [SnModalComponent],
|
|
11
|
+
}).compileComponents();
|
|
12
|
+
|
|
13
|
+
fixture = TestBed.createComponent(SnModalComponent);
|
|
14
|
+
component = fixture.componentInstance;
|
|
15
|
+
fixture.detectChanges();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should create', () => {
|
|
19
|
+
expect(component).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should default open to false', () => {
|
|
23
|
+
expect(component.open).toBeFalse();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should default size to "md"', () => {
|
|
27
|
+
expect(component.size).toBe('md');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should default closeOnBackdrop to true', () => {
|
|
31
|
+
expect(component.closeOnBackdrop).toBeTrue();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should emit closed and set open to false when close() is called', () => {
|
|
35
|
+
component.open = true;
|
|
36
|
+
let emitted = false;
|
|
37
|
+
component.closed.subscribe(() => (emitted = true));
|
|
38
|
+
component.close();
|
|
39
|
+
expect(component.open).toBeFalse();
|
|
40
|
+
expect(emitted).toBeTrue();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should close on backdrop click when closeOnBackdrop is true', () => {
|
|
44
|
+
component.open = true;
|
|
45
|
+
component.closeOnBackdrop = true;
|
|
46
|
+
component.onBackdropClick();
|
|
47
|
+
expect(component.open).toBeFalse();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should not close on backdrop click when closeOnBackdrop is false', () => {
|
|
51
|
+
component.open = true;
|
|
52
|
+
component.closeOnBackdrop = false;
|
|
53
|
+
component.onBackdropClick();
|
|
54
|
+
expect(component.open).toBeTrue();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, Output, booleanAttribute } from '@angular/core';
|
|
2
|
+
import { CommonModule, NgClass } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'sn-modal',
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [CommonModule, NgClass],
|
|
8
|
+
templateUrl: './sn-modal.html',
|
|
9
|
+
styleUrl: 'sn-modal.scss',
|
|
10
|
+
})
|
|
11
|
+
export class SnModalComponent {
|
|
12
|
+
@Input({ transform: booleanAttribute }) open: boolean = false;
|
|
13
|
+
@Input() title: string = '';
|
|
14
|
+
@Input() size: string = 'md';
|
|
15
|
+
@Input({ transform: booleanAttribute }) closeOnBackdrop: boolean = true;
|
|
16
|
+
@Output() closed = new EventEmitter<void>();
|
|
17
|
+
|
|
18
|
+
get panelClasses(): Record<string, boolean> {
|
|
19
|
+
return {
|
|
20
|
+
'sn-modal-panel': true,
|
|
21
|
+
[`size-${this.size}`]: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onBackdropClick(): void {
|
|
26
|
+
if (this.closeOnBackdrop) {
|
|
27
|
+
this.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close(): void {
|
|
32
|
+
this.open = false;
|
|
33
|
+
this.closed.emit();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@if (open) {
|
|
2
|
+
<div class="sn-modal-backdrop" (click)="onBackdropClick()">
|
|
3
|
+
<div [ngClass]="panelClasses" (click)="$event.stopPropagation()" role="dialog" [attr.aria-modal]="true" [attr.aria-label]="title">
|
|
4
|
+
<div class="sn-modal-header">
|
|
5
|
+
@if (title) {
|
|
6
|
+
<h2 class="sn-modal-title">{{ title }}</h2>
|
|
7
|
+
}
|
|
8
|
+
<button class="sn-modal-close" (click)="close()" aria-label="Close">×</button>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="sn-modal-body">
|
|
11
|
+
<ng-content select="[modal-body]"></ng-content>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="sn-modal-footer">
|
|
14
|
+
<ng-content select="[modal-footer]"></ng-content>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
.sn-modal-backdrop {
|
|
6
|
+
@apply fixed inset-0 z-50 flex items-center justify-center bg-black/50;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.sn-modal-panel {
|
|
10
|
+
@apply relative bg-white rounded-lg shadow-xl flex flex-col max-h-[90vh] overflow-hidden;
|
|
11
|
+
|
|
12
|
+
&.size-sm { @apply w-full max-w-sm; }
|
|
13
|
+
&.size-md { @apply w-full max-w-md; }
|
|
14
|
+
&.size-lg { @apply w-full max-w-2xl; }
|
|
15
|
+
&.size-full { @apply w-full h-full max-w-none max-h-none rounded-none; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.sn-modal-header {
|
|
19
|
+
@apply flex items-center justify-between px-6 py-4 border-b border-gray-200 flex-shrink-0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.sn-modal-title {
|
|
23
|
+
@apply text-lg font-semibold text-gray-900 m-0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.sn-modal-close {
|
|
27
|
+
@apply ml-auto -mr-2 p-1.5 rounded text-gray-400 hover:text-gray-700 hover:bg-gray-100 cursor-pointer bg-transparent border-none text-2xl leading-none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sn-modal-body {
|
|
31
|
+
@apply flex-1 overflow-y-auto px-6 py-4;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.sn-modal-footer {
|
|
35
|
+
@apply flex items-center justify-end gap-2 px-6 py-4 border-t border-gray-200 flex-shrink-0;
|
|
36
|
+
|
|
37
|
+
&:empty { @apply hidden; }
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/sn-modal.component';
|