@ng-zen/cli 21.1.1 → 21.2.0
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/CHANGELOG.md +4 -438
- package/README.md +3 -2
- package/package.json +2 -1
- package/schematics/ui/files/dialog/dialog.scss +144 -0
- package/schematics/ui/files/dialog/dialog.service.stories.ts +174 -0
- package/schematics/ui/files/dialog/dialog.service.ts +211 -0
- package/schematics/ui/files/dialog/dialog.spec.ts +249 -0
- package/schematics/ui/files/dialog/dialog.stories.ts +105 -0
- package/schematics/ui/files/dialog/dialog.ts +143 -0
- package/schematics/ui/files/dialog/index.ts +3 -0
- package/schematics/ui/schema.js.map +1 -1
- package/schematics/ui/schema.json +1 -0
- package/schematics/ui/schema.ts +1 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
import { argsToTemplate, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
|
|
3
|
+
|
|
4
|
+
import { ZenButton } from '../button';
|
|
5
|
+
import { ZenDialog } from './dialog';
|
|
6
|
+
|
|
7
|
+
type DialogSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
8
|
+
|
|
9
|
+
type Story = StoryObj<ZenDialog>;
|
|
10
|
+
|
|
11
|
+
const meta = {
|
|
12
|
+
title: 'UI/Dialog/Dialog',
|
|
13
|
+
component: ZenDialog,
|
|
14
|
+
tags: ['autodocs'],
|
|
15
|
+
decorators: [
|
|
16
|
+
moduleMetadata({
|
|
17
|
+
imports: [ZenButton, ZenDialog],
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
argTypes: {
|
|
21
|
+
size: {
|
|
22
|
+
name: 'size',
|
|
23
|
+
control: 'select',
|
|
24
|
+
options: ['sm', 'md', 'lg', 'xl', 'full'] satisfies DialogSize[],
|
|
25
|
+
table: {
|
|
26
|
+
category: 'inputs',
|
|
27
|
+
type: { summary: 'string' },
|
|
28
|
+
defaultValue: { summary: 'md' satisfies DialogSize },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
header: { control: 'text', table: { category: 'inputs' } },
|
|
32
|
+
closable: { control: 'boolean', table: { category: 'inputs', defaultValue: { summary: 'true' } } },
|
|
33
|
+
backdrop: { control: 'boolean', table: { category: 'inputs', defaultValue: { summary: 'true' } } },
|
|
34
|
+
closeOnEscape: { control: 'boolean', table: { category: 'inputs', defaultValue: { summary: 'true' } } },
|
|
35
|
+
open: { control: 'boolean', table: { readonly: false, category: 'models' } },
|
|
36
|
+
},
|
|
37
|
+
args: {
|
|
38
|
+
size: 'md',
|
|
39
|
+
header: 'Dialog Title',
|
|
40
|
+
closable: true,
|
|
41
|
+
backdrop: true,
|
|
42
|
+
closeOnEscape: true,
|
|
43
|
+
open: false,
|
|
44
|
+
},
|
|
45
|
+
} satisfies Meta<ZenDialog>;
|
|
46
|
+
|
|
47
|
+
export default meta;
|
|
48
|
+
|
|
49
|
+
export const Default: Story = {
|
|
50
|
+
render: ({ open, ...args }) => {
|
|
51
|
+
const mutatedArgs = { ...args, open: signal(open) };
|
|
52
|
+
return {
|
|
53
|
+
props: { ...mutatedArgs },
|
|
54
|
+
template: `
|
|
55
|
+
<button zen-btn (click)="open.set(true)">Open Dialog</button>
|
|
56
|
+
|
|
57
|
+
<dialog zen-dialog [(open)]="open" ${argsToTemplate(args)}>
|
|
58
|
+
<p>This is the dialog content. You can put any content here.</p>
|
|
59
|
+
<button zen-btn (click)="open.set(false)">Close</button>
|
|
60
|
+
</dialog>
|
|
61
|
+
`,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const Sizes: Story = {
|
|
67
|
+
render: () => ({
|
|
68
|
+
props: {
|
|
69
|
+
openSm: signal(false),
|
|
70
|
+
openMd: signal(false),
|
|
71
|
+
openLg: signal(false),
|
|
72
|
+
openXl: signal(false),
|
|
73
|
+
openFull: signal(false),
|
|
74
|
+
},
|
|
75
|
+
template: `
|
|
76
|
+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
77
|
+
<button zen-btn (click)="openSm.set(true)">Small</button>
|
|
78
|
+
<button zen-btn (click)="openMd.set(true)">Medium</button>
|
|
79
|
+
<button zen-btn (click)="openLg.set(true)">Large</button>
|
|
80
|
+
<button zen-btn (click)="openXl.set(true)">Extra Large</button>
|
|
81
|
+
<button zen-btn (click)="openFull.set(true)">Full Screen</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<dialog zen-dialog [(open)]="openSm" header="Small Dialog" size="sm">
|
|
85
|
+
<p>Small dialog content</p>
|
|
86
|
+
</dialog>
|
|
87
|
+
|
|
88
|
+
<dialog zen-dialog [(open)]="openMd" header="Medium Dialog" size="md">
|
|
89
|
+
<p>Medium dialog content</p>
|
|
90
|
+
</dialog>
|
|
91
|
+
|
|
92
|
+
<dialog zen-dialog [(open)]="openLg" header="Large Dialog" size="lg">
|
|
93
|
+
<p>Large dialog content with more space for complex layouts.</p>
|
|
94
|
+
</dialog>
|
|
95
|
+
|
|
96
|
+
<dialog zen-dialog [(open)]="openXl" header="Extra Large Dialog" size="xl">
|
|
97
|
+
<p>Extra large dialog content for data tables, forms, etc.</p>
|
|
98
|
+
</dialog>
|
|
99
|
+
|
|
100
|
+
<dialog zen-dialog [(open)]="openFull" header="Full Screen Dialog" size="full">
|
|
101
|
+
<p>Full screen dialog content</p>
|
|
102
|
+
</dialog>
|
|
103
|
+
`,
|
|
104
|
+
}),
|
|
105
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, effect, ElementRef, inject, input, model, untracked } from '@angular/core';
|
|
2
|
+
import { Cancel01Icon } from '@hugeicons/core-free-icons';
|
|
3
|
+
|
|
4
|
+
import { ZenIcon } from '../icon';
|
|
5
|
+
|
|
6
|
+
type DialogSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ZenDialog is a reusable dialog component built on the native HTML `<dialog>` element.
|
|
10
|
+
* It provides a modal dialog with customizable header, size, and content.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <dialog zen-dialog [(open)]="isOpen" header="Dialog Title" size="md">
|
|
15
|
+
* <p>Dialog content</p>
|
|
16
|
+
* </dialog>
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* ### CSS Custom Properties
|
|
20
|
+
*
|
|
21
|
+
* You can customize the component using CSS custom properties:
|
|
22
|
+
* ```css
|
|
23
|
+
* :root {
|
|
24
|
+
* --zen-dialog-padding: 1rem;
|
|
25
|
+
* --zen-dialog-bg: white;
|
|
26
|
+
* --zen-dialog-border-radius: 8px;
|
|
27
|
+
* --zen-dialog-shadow: 0 4px 24px rgb(0 0 0 / 20%);
|
|
28
|
+
* --zen-dialog-max-height: 90vh;
|
|
29
|
+
* --zen-dialog-max-width: 90vw;
|
|
30
|
+
* --zen-dialog-backdrop-bg: rgba(0, 0, 0, 0.5);
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @author Konrad Stępień
|
|
35
|
+
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
36
|
+
* @see [GitHub](https://github.com/kstepien3/ng-zen)
|
|
37
|
+
* @see [MDN Dialog Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
|
|
38
|
+
*/
|
|
39
|
+
@Component({
|
|
40
|
+
// eslint-disable-next-line @angular-eslint/component-selector
|
|
41
|
+
selector: 'dialog[zen-dialog]',
|
|
42
|
+
template: `
|
|
43
|
+
@if (header()) {
|
|
44
|
+
<header class="zen-dialog-header">
|
|
45
|
+
<h2>{{ header() }}</h2>
|
|
46
|
+
@if (closable()) {
|
|
47
|
+
<button (click)="onClose()" aria-label="Close dialog" class="zen-dialog-close" type="button">
|
|
48
|
+
<zen-icon [icon]="closeIcon" [size]="20" />
|
|
49
|
+
</button>
|
|
50
|
+
}
|
|
51
|
+
</header>
|
|
52
|
+
}
|
|
53
|
+
<div class="zen-dialog-content">
|
|
54
|
+
<ng-content />
|
|
55
|
+
</div>
|
|
56
|
+
`,
|
|
57
|
+
styleUrl: './dialog.scss',
|
|
58
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
59
|
+
imports: [ZenIcon],
|
|
60
|
+
host: {
|
|
61
|
+
'[attr.aria-label]': 'header()',
|
|
62
|
+
'[attr.data-size]': 'size()',
|
|
63
|
+
'(close)': 'onClose()',
|
|
64
|
+
'(cancel)': 'onCancel($event)',
|
|
65
|
+
'(click)': 'onDialogClick($event)',
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
export class ZenDialog {
|
|
69
|
+
/**
|
|
70
|
+
* Controls the open state of the dialog.
|
|
71
|
+
* Supports two-way binding via `[(open)]` syntax.
|
|
72
|
+
* When set to `true`, calls `showModal()` on the native dialog element.
|
|
73
|
+
* When set to `false`, calls `close()` on the native dialog element.
|
|
74
|
+
*/
|
|
75
|
+
readonly open = model<boolean>(false);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Header title displayed at the top of the dialog.
|
|
79
|
+
* Also used as the `aria-label` for accessibility.
|
|
80
|
+
*/
|
|
81
|
+
readonly header = input<string>('');
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Size variant of the dialog.
|
|
85
|
+
* Affects the width of the dialog via the `data-size` attribute.
|
|
86
|
+
*/
|
|
87
|
+
readonly size = input<DialogSize>('md');
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether to show the close button (X) in the header.
|
|
91
|
+
*/
|
|
92
|
+
readonly closable = input(true);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Whether clicking the backdrop closes the dialog.
|
|
96
|
+
* This is a native dialog feature.
|
|
97
|
+
*/
|
|
98
|
+
readonly backdrop = input(true);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether pressing the Escape key closes the dialog.
|
|
102
|
+
* This is a native dialog feature.
|
|
103
|
+
*/
|
|
104
|
+
readonly closeOnEscape = input(true);
|
|
105
|
+
|
|
106
|
+
protected readonly closeIcon = Cancel01Icon;
|
|
107
|
+
|
|
108
|
+
private readonly elementRef = inject(ElementRef<HTMLDialogElement>);
|
|
109
|
+
|
|
110
|
+
constructor() {
|
|
111
|
+
effect(() => {
|
|
112
|
+
const isOpen = this.open();
|
|
113
|
+
const element = this.elementRef.nativeElement;
|
|
114
|
+
|
|
115
|
+
untracked(() => {
|
|
116
|
+
if (isOpen) {
|
|
117
|
+
element.showModal();
|
|
118
|
+
} else if (element.open) {
|
|
119
|
+
element.close();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Close on backdrop click */
|
|
126
|
+
protected onDialogClick(event: MouseEvent): void {
|
|
127
|
+
if (!this.backdrop()) return;
|
|
128
|
+
|
|
129
|
+
if ((event.target as HTMLElement).tagName === 'DIALOG') {
|
|
130
|
+
this.onClose();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected onCancel(event: Event): void {
|
|
135
|
+
if (!this.closeOnEscape()) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected onClose(): void {
|
|
141
|
+
this.open.set(false);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/schema.ts"],"names":[],"mappings":"","sourcesContent":["import { GeneratorSchemaBase } from '../../types';\n\nexport type UiType =\n | 'alert'\n | 'avatar'\n | 'button'\n | 'checkbox'\n | 'divider'\n | 'form-control'\n | 'icon'\n | 'input'\n | 'popover'\n | 'radio'\n | 'skeleton'\n | 'switch'\n | 'textarea';\n\nexport interface Schema extends GeneratorSchemaBase {\n ui: UiType[];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../../../src/schematics/ui/schema.ts"],"names":[],"mappings":"","sourcesContent":["import { GeneratorSchemaBase } from '../../types';\n\nexport type UiType =\n | 'alert'\n | 'avatar'\n | 'button'\n | 'checkbox'\n | 'dialog'\n | 'divider'\n | 'form-control'\n | 'icon'\n | 'input'\n | 'popover'\n | 'radio'\n | 'skeleton'\n | 'switch'\n | 'textarea';\n\nexport interface Schema extends GeneratorSchemaBase {\n ui: UiType[];\n}\n"]}
|