@ng-zen/cli 21.2.0 β 22.0.0-next.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/CHANGELOG.md +5 -5
- package/README.md +7 -1
- package/package.json +5 -8
- package/schematics/ui/files/button/button.scss +160 -24
- package/schematics/ui/files/button/button.spec.ts +69 -0
- package/schematics/ui/files/button/button.stories.ts +119 -13
- package/schematics/ui/files/button/button.ts +22 -7
- package/schematics/ui/files/dialog/dialog.service.stories.ts +2 -2
- package/schematics/ui/files/dialog/dialog.stories.ts +1 -1
- package/schematics/ui/files/popover/host/popover-host.scss +12 -6
- package/schematics/ui/files/popover/index.ts +1 -0
- package/schematics/ui/files/popover/popover.stories.ts +18 -1
- package/schematics/ui/files/popover/popover.ts +36 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [22.0.0-next.1](https://github.com/kstepien3/ng-zen/compare/v21.3.0-next.1...v22.0.0-next.1) (2026-06-12)
|
|
2
2
|
|
|
3
|
-
###
|
|
3
|
+
### β BREAKING CHANGES
|
|
4
4
|
|
|
5
|
-
* **
|
|
5
|
+
* **deps:** clean up unused deps and update dependencies (#414)
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### chore
|
|
8
8
|
|
|
9
|
-
* **
|
|
9
|
+
* **deps:** clean up unused deps and update dependencies ([#414](https://github.com/kstepien3/ng-zen/issues/414)) ([04b8c7d](https://github.com/kstepien3/ng-zen/commit/04b8c7da59e70181147f105e95b6471234ff21f5))
|
|
10
10
|
|
|
11
11
|
Development mode - changelog will be generated during build.
|
package/README.md
CHANGED
|
@@ -22,7 +22,13 @@
|
|
|
22
22
|
<img src="https://img.shields.io/badge/-Repository-181818?style=flat&logo=github&logoColor=white" alt="Repository" />
|
|
23
23
|
</a>
|
|
24
24
|
<a href="https://kstepien3.github.io/ng-zen/">
|
|
25
|
-
<img src="https://img.shields.io/badge/-Storybook
|
|
25
|
+
<img src="https://img.shields.io/badge/-Storybook-FF4785?style=flat&logo=storybook&logoColor=white" alt="Storybook" />
|
|
26
|
+
</a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p>
|
|
30
|
+
<a href="https://deepwiki.com/kstepien3/ng-zen">
|
|
31
|
+
<img src="https://deepwiki.com/badge.svg" alt="DeepWiki" />
|
|
26
32
|
</a>
|
|
27
33
|
</p>
|
|
28
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-zen/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "22.0.0-next.1",
|
|
4
4
|
"description": "Angular UI components generator β Zen UI Kit CLI for schematics-based creation of customizable components like avatar, button, checkbox, divider, form-control, icon, input, skeleton, switch, textarea with Storybook demos.",
|
|
5
5
|
"license": "BSD-2-Clause",
|
|
6
6
|
"private": false,
|
|
@@ -65,14 +65,11 @@
|
|
|
65
65
|
},
|
|
66
66
|
"schematics": "./schematics/collection.json",
|
|
67
67
|
"packageManager": "pnpm@10.24.0",
|
|
68
|
-
"peerDependencies": {
|
|
69
|
-
"@angular/common": ">=20.0.0",
|
|
70
|
-
"@angular/core": ">=20.0.0"
|
|
71
|
-
},
|
|
68
|
+
"peerDependencies": {},
|
|
72
69
|
"dependencies": {
|
|
73
|
-
"@angular-devkit/core": "^
|
|
74
|
-
"@angular-devkit/schematics": "^
|
|
75
|
-
"@angular/build": "
|
|
70
|
+
"@angular-devkit/core": "^22.0.1",
|
|
71
|
+
"@angular-devkit/schematics": "^22.0.1",
|
|
72
|
+
"@angular/build": "22.0.1",
|
|
76
73
|
"tslib": "^2.8.1"
|
|
77
74
|
}
|
|
78
75
|
}
|
|
@@ -1,49 +1,185 @@
|
|
|
1
|
+
@use 'sass:map';
|
|
2
|
+
|
|
1
3
|
// Globals
|
|
2
|
-
$transition-duration: var(--zen-transition-duration, 0.
|
|
4
|
+
$transition-duration: var(--zen-transition-duration, 0.2s);
|
|
3
5
|
$outline: var(--zen-outline, 2px solid hsl(200deg 100% 50% / 50%));
|
|
4
6
|
|
|
5
7
|
:host {
|
|
6
|
-
--zen-button-padding: 0.5rem 1rem;
|
|
7
|
-
--zen-button-bg-color: hsl(0deg 0% 0%);
|
|
8
|
-
--zen-button-bg-color-hover: hsl(0deg 0% 20%);
|
|
9
|
-
--zen-button-color: hsl(0deg 0% 100%);
|
|
10
8
|
--zen-button-shadow: 0 2px 4px hsl(0deg 0% 0% / 10%);
|
|
9
|
+
--zen-button-neutral: var(--zen-neutral, hsl(30deg 5% 10%));
|
|
10
|
+
--zen-button-primary: var(--zen-primary, hsl(193deg 58% 34%));
|
|
11
|
+
--zen-button-success: var(--zen-success, hsl(142deg 38% 36%));
|
|
12
|
+
--zen-button-warning: var(--zen-warning, hsl(36deg 72% 46%));
|
|
13
|
+
--zen-button-danger: var(--zen-danger, hsl(348deg 52% 42%));
|
|
14
|
+
--zen-button-info: var(--zen-info, hsl(214deg 34% 46%));
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
$sizes: (
|
|
18
|
+
sm: (
|
|
19
|
+
padding: 0.35rem 0.625rem,
|
|
20
|
+
font-size: 0.75rem,
|
|
21
|
+
gap: 0.375rem,
|
|
22
|
+
),
|
|
23
|
+
md: (
|
|
24
|
+
padding: 0.5rem 0.875rem,
|
|
25
|
+
font-size: 0.875rem,
|
|
26
|
+
gap: 0.5rem,
|
|
27
|
+
),
|
|
28
|
+
lg: (
|
|
29
|
+
padding: 0.625rem 1rem,
|
|
30
|
+
font-size: 1rem,
|
|
31
|
+
gap: 0.5rem,
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
$colors: (
|
|
35
|
+
primary: var(--zen-button-primary),
|
|
36
|
+
success: var(--zen-button-success),
|
|
37
|
+
warning: var(--zen-button-warning),
|
|
38
|
+
danger: var(--zen-button-danger),
|
|
39
|
+
info: var(--zen-button-info),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Color mix percentages for filled variant states
|
|
43
|
+
$filled-base-opacity: 8%;
|
|
44
|
+
$filled-hover-opacity: 15%;
|
|
45
|
+
$filled-active-opacity: 25%;
|
|
46
|
+
|
|
47
|
+
// Ghost/Link variant opacity levels
|
|
48
|
+
$ghost-hover-opacity: 10%;
|
|
49
|
+
$ghost-active-opacity: 20%;
|
|
50
|
+
|
|
51
|
+
// Outline variant opacity levels
|
|
52
|
+
$outline-hover-opacity: 70%;
|
|
53
|
+
$outline-active-bg-opacity: 20%;
|
|
54
|
+
|
|
55
|
+
// Disabled state
|
|
56
|
+
$disabled-opacity: 0.6;
|
|
57
|
+
|
|
58
|
+
// Default state hover/active mixing ratios (oklch)
|
|
59
|
+
$default-hover-mix: 85%;
|
|
60
|
+
$default-active-mix: 75%;
|
|
61
|
+
|
|
13
62
|
/* stylelint-disable-next-line no-duplicate-selectors -- separate variables and styles */
|
|
14
63
|
:host {
|
|
15
|
-
|
|
64
|
+
--zen-button-bg-color: var(--zen-button-neutral); // default
|
|
65
|
+
|
|
16
66
|
background-color: var(--zen-button-bg-color);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: center;
|
|
70
|
+
line-height: 1;
|
|
21
71
|
font-weight: bold;
|
|
22
72
|
text-align: center;
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
user-select: none;
|
|
23
76
|
cursor: pointer;
|
|
24
77
|
border: none;
|
|
25
|
-
border-radius:
|
|
26
|
-
|
|
27
|
-
transition
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
78
|
+
border-radius: 0.625rem;
|
|
79
|
+
box-shadow: var(--zen-button-shadow);
|
|
80
|
+
transition:
|
|
81
|
+
background-color $transition-duration ease,
|
|
82
|
+
transform $transition-duration ease,
|
|
83
|
+
color $transition-duration ease,
|
|
84
|
+
border-color $transition-duration ease;
|
|
85
|
+
color: var(--zen-button-color, contrast-color(var(--zen-button-bg-color)));
|
|
32
86
|
}
|
|
33
87
|
|
|
34
|
-
/* Hover state */
|
|
35
88
|
:host:hover {
|
|
36
|
-
background-color: var(--zen-button-bg-color-hover);
|
|
89
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $default-hover-mix, var(--zen-button-color, white));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:host:active {
|
|
93
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $default-active-mix, var(--zen-button-color, white));
|
|
94
|
+
transform: translateY(1px);
|
|
37
95
|
}
|
|
38
96
|
|
|
39
|
-
/* Focus state */
|
|
40
97
|
:host:focus-visible {
|
|
41
98
|
outline: $outline;
|
|
99
|
+
outline-offset: 2px;
|
|
42
100
|
}
|
|
43
101
|
|
|
44
|
-
|
|
45
|
-
:host[aria-disabled]:not([aria-disabled='false']),
|
|
46
|
-
:host[disabled]:not([disabled='false']) {
|
|
102
|
+
:host([disabled]) {
|
|
47
103
|
cursor: not-allowed;
|
|
48
|
-
opacity:
|
|
104
|
+
opacity: $disabled-opacity;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// --- SIZES ---
|
|
109
|
+
@each $name, $config in $sizes {
|
|
110
|
+
:host([data-size='#{$name}']) {
|
|
111
|
+
padding: map.get($config, padding);
|
|
112
|
+
font-size: map.get($config, font-size);
|
|
113
|
+
gap: map.get($config, gap);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- COLORS ---
|
|
118
|
+
@each $name, $bg-color in $colors {
|
|
119
|
+
:host([data-color='#{$name}']) {
|
|
120
|
+
--zen-button-bg-color: #{$bg-color};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// --- VARIANTS ---
|
|
125
|
+
|
|
126
|
+
/* Outline variant */
|
|
127
|
+
:host([data-variant='outline']) {
|
|
128
|
+
background-color: transparent;
|
|
129
|
+
color: var(--zen-button-bg-color);
|
|
130
|
+
border-width: 1px;
|
|
131
|
+
border-style: solid;
|
|
132
|
+
border-color: var(--zen-button-bg-color);
|
|
133
|
+
box-shadow: none;
|
|
134
|
+
|
|
135
|
+
&:hover {
|
|
136
|
+
color: color-mix(in oklch, var(--zen-button-bg-color) $outline-hover-opacity, transparent);
|
|
137
|
+
border-color: color-mix(in oklch, var(--zen-button-bg-color) $outline-hover-opacity, transparent);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:active {
|
|
141
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $outline-active-bg-opacity, transparent);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Filled variant */
|
|
146
|
+
:host([data-variant='filled']) {
|
|
147
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $filled-base-opacity, transparent);
|
|
148
|
+
color: var(--zen-button-bg-color);
|
|
149
|
+
box-shadow: none;
|
|
150
|
+
|
|
151
|
+
&:hover {
|
|
152
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $filled-hover-opacity, transparent);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
&:active {
|
|
156
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $filled-active-opacity, transparent);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Ghost and Link variants */
|
|
161
|
+
:host([data-variant='ghost']),
|
|
162
|
+
:host([data-variant='link']) {
|
|
163
|
+
background-color: transparent;
|
|
164
|
+
color: var(--zen-button-bg-color);
|
|
165
|
+
box-shadow: none;
|
|
166
|
+
|
|
167
|
+
&:hover {
|
|
168
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $ghost-hover-opacity, transparent);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&:active {
|
|
172
|
+
background-color: color-mix(in oklch, var(--zen-button-bg-color) $ghost-active-opacity, transparent);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Link variant only */
|
|
177
|
+
:host([data-variant='link']) {
|
|
178
|
+
text-decoration: underline;
|
|
179
|
+
text-underline-offset: 0.2em;
|
|
180
|
+
text-decoration-thickness: 1px;
|
|
181
|
+
|
|
182
|
+
&:hover {
|
|
183
|
+
text-decoration-thickness: 2px;
|
|
184
|
+
}
|
|
49
185
|
}
|
|
@@ -17,4 +17,73 @@ describe('ZenButton', () => {
|
|
|
17
17
|
const component = fixture.componentInstance;
|
|
18
18
|
expect(component).toBeTruthy();
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
it('should have default size md', () => {
|
|
22
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
23
|
+
fixture.detectChanges();
|
|
24
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
25
|
+
expect(button.getAttribute('data-size')).toBe('md');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should have default color neutral', () => {
|
|
29
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
30
|
+
fixture.detectChanges();
|
|
31
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
32
|
+
expect(button.getAttribute('data-color')).toBe('neutral');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should have default variant solid', () => {
|
|
36
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
37
|
+
fixture.detectChanges();
|
|
38
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
39
|
+
expect(button.getAttribute('data-variant')).toBe('solid');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should apply size sm', () => {
|
|
43
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
44
|
+
fixture.componentRef.setInput('size', 'sm');
|
|
45
|
+
fixture.detectChanges();
|
|
46
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
47
|
+
expect(button.getAttribute('data-size')).toBe('sm');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should apply size lg', () => {
|
|
51
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
52
|
+
fixture.componentRef.setInput('size', 'lg');
|
|
53
|
+
fixture.detectChanges();
|
|
54
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
55
|
+
expect(button.getAttribute('data-size')).toBe('lg');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should apply color primary', () => {
|
|
59
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
60
|
+
fixture.componentRef.setInput('color', 'primary');
|
|
61
|
+
fixture.detectChanges();
|
|
62
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
63
|
+
expect(button.getAttribute('data-color')).toBe('primary');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should apply variant outline', () => {
|
|
67
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
68
|
+
fixture.componentRef.setInput('variant', 'outline');
|
|
69
|
+
fixture.detectChanges();
|
|
70
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
71
|
+
expect(button.getAttribute('data-variant')).toBe('outline');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should apply variant ghost', () => {
|
|
75
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
76
|
+
fixture.componentRef.setInput('variant', 'ghost');
|
|
77
|
+
fixture.detectChanges();
|
|
78
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
79
|
+
expect(button.getAttribute('data-variant')).toBe('ghost');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should apply variant link', () => {
|
|
83
|
+
const fixture = TestBed.createComponent(ZenButton);
|
|
84
|
+
fixture.componentRef.setInput('variant', 'link');
|
|
85
|
+
fixture.detectChanges();
|
|
86
|
+
const button = fixture.nativeElement as HTMLButtonElement;
|
|
87
|
+
expect(button.getAttribute('data-variant')).toBe('link');
|
|
88
|
+
});
|
|
20
89
|
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FavouriteIcon } from '@hugeicons/core-free-icons';
|
|
2
|
+
import { argsToTemplate, Meta, moduleMetadata, StoryObj } from '@storybook/angular';
|
|
2
3
|
|
|
4
|
+
import { ZenIcon } from '../icon';
|
|
3
5
|
import { ZenButton } from './button';
|
|
4
6
|
|
|
5
7
|
interface StoryParams {
|
|
6
|
-
|
|
8
|
+
color: 'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
|
9
|
+
variant: 'solid' | 'filled' | 'outline' | 'ghost' | 'link';
|
|
7
10
|
disabled: boolean;
|
|
8
11
|
}
|
|
9
12
|
|
|
@@ -13,18 +16,23 @@ export default {
|
|
|
13
16
|
title: 'Ui/Button',
|
|
14
17
|
component: ZenButton,
|
|
15
18
|
args: {
|
|
16
|
-
content: 'Test',
|
|
17
19
|
disabled: false,
|
|
20
|
+
size: 'md',
|
|
21
|
+
color: 'neutral',
|
|
22
|
+
variant: 'solid',
|
|
18
23
|
},
|
|
19
24
|
argTypes: {
|
|
20
|
-
|
|
21
|
-
control: '
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
size: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: ['sm', 'md', 'lg'],
|
|
28
|
+
},
|
|
29
|
+
color: {
|
|
30
|
+
control: 'select',
|
|
31
|
+
options: ['neutral', 'primary', 'success', 'warning', 'danger', 'info'],
|
|
32
|
+
},
|
|
33
|
+
variant: {
|
|
34
|
+
control: 'select',
|
|
35
|
+
options: ['solid', 'filled', 'outline', 'ghost', 'link'],
|
|
28
36
|
},
|
|
29
37
|
disabled: {
|
|
30
38
|
control: 'boolean',
|
|
@@ -36,12 +44,110 @@ export default {
|
|
|
36
44
|
},
|
|
37
45
|
},
|
|
38
46
|
},
|
|
39
|
-
render: ({
|
|
47
|
+
render: ({ ...args }) => ({
|
|
40
48
|
props: args,
|
|
41
|
-
template: `<button zen-btn ${args
|
|
49
|
+
template: `<button zen-btn ${argsToTemplate(args)}>Button</button>`,
|
|
42
50
|
}),
|
|
43
51
|
} satisfies Meta<Options>;
|
|
44
52
|
|
|
45
53
|
type Story = StoryObj<Options>;
|
|
46
54
|
|
|
47
55
|
export const Default: Story = {};
|
|
56
|
+
|
|
57
|
+
export const IconButton: Story = {
|
|
58
|
+
decorators: [moduleMetadata({ imports: [ZenIcon] })],
|
|
59
|
+
render: args => ({
|
|
60
|
+
props: { FavouriteIcon, ...args },
|
|
61
|
+
template: `<button zen-btn variant="ghost" ${argsToTemplate(args, { exclude: ['variant'] })}>
|
|
62
|
+
<zen-icon [icon]="FavouriteIcon" [size]="16" [strokeWidth]="3" />
|
|
63
|
+
</button>`,
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const AllVariants: Story = {
|
|
68
|
+
render: () => ({
|
|
69
|
+
template: `
|
|
70
|
+
<div style="display: flex; gap: 0.5rem">
|
|
71
|
+
<button zen-button variant="solid">Solid</button>
|
|
72
|
+
<button zen-button variant="filled">Filled</button>
|
|
73
|
+
<button zen-button variant="outline">Outline</button>
|
|
74
|
+
<button zen-button variant="ghost">Ghost</button>
|
|
75
|
+
<button zen-button variant="link">Link</button>
|
|
76
|
+
</div>
|
|
77
|
+
`,
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const AllSizes: Story = {
|
|
82
|
+
render: () => ({
|
|
83
|
+
template: `
|
|
84
|
+
<div style="display: flex; align-items: center; gap: 0.5rem">
|
|
85
|
+
<button zen-button size="sm">Small</button>
|
|
86
|
+
<button zen-button size="md">Medium</button>
|
|
87
|
+
<button zen-button size="lg">Large</button>
|
|
88
|
+
</div>
|
|
89
|
+
`,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const AllColorVariants: Story = {
|
|
94
|
+
render: () => ({
|
|
95
|
+
template: `
|
|
96
|
+
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
|
97
|
+
<!-- Default (No color attribute) -->
|
|
98
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
99
|
+
<button zen-button variant="solid">Solid</button>
|
|
100
|
+
<button zen-button variant="filled">Filled</button>
|
|
101
|
+
<button zen-button variant="outline">Outline</button>
|
|
102
|
+
<button zen-button variant="ghost">Ghost</button>
|
|
103
|
+
<button zen-button variant="link">Link</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Primary -->
|
|
107
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
108
|
+
<button zen-button color="primary" variant="solid">Solid</button>
|
|
109
|
+
<button zen-button color="primary" variant="filled">Filled</button>
|
|
110
|
+
<button zen-button color="primary" variant="outline">Outline</button>
|
|
111
|
+
<button zen-button color="primary" variant="ghost">Ghost</button>
|
|
112
|
+
<button zen-button color="primary" variant="link">Link</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Success -->
|
|
116
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
117
|
+
<button zen-button color="success" variant="solid">Solid</button>
|
|
118
|
+
<button zen-button color="success" variant="filled">Filled</button>
|
|
119
|
+
<button zen-button color="success" variant="outline">Outline</button>
|
|
120
|
+
<button zen-button color="success" variant="ghost">Ghost</button>
|
|
121
|
+
<button zen-button color="success" variant="link">Link</button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<!-- Warning -->
|
|
125
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
126
|
+
<button zen-button color="warning" variant="solid">Solid</button>
|
|
127
|
+
<button zen-button color="warning" variant="filled">Filled</button>
|
|
128
|
+
<button zen-button color="warning" variant="outline">Outline</button>
|
|
129
|
+
<button zen-button color="warning" variant="ghost">Ghost</button>
|
|
130
|
+
<button zen-button color="warning" variant="link">Link</button>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<!-- Danger -->
|
|
134
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
135
|
+
<button zen-button color="danger" variant="solid">Solid</button>
|
|
136
|
+
<button zen-button color="danger" variant="filled">Filled</button>
|
|
137
|
+
<button zen-button color="danger" variant="outline">Outline</button>
|
|
138
|
+
<button zen-button color="danger" variant="ghost">Ghost</button>
|
|
139
|
+
<button zen-button color="danger" variant="link">Link</button>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Info -->
|
|
143
|
+
<div style="display: flex; gap: 0.5rem;">
|
|
144
|
+
<button zen-button color="info" variant="solid">Solid</button>
|
|
145
|
+
<button zen-button color="info" variant="filled">Filled</button>
|
|
146
|
+
<button zen-button color="info" variant="outline">Outline</button>
|
|
147
|
+
<button zen-button color="info" variant="ghost">Ghost</button>
|
|
148
|
+
<button zen-button color="info" variant="link">Link</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
`,
|
|
152
|
+
}),
|
|
153
|
+
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ZenButton is a reusable button component designed to provide
|
|
5
5
|
* a consistent and customizable button style across the application.
|
|
6
6
|
* It can be used with both `<button>` and `<a>` HTML elements by applying
|
|
7
|
-
* the `zen-button` attribute.
|
|
7
|
+
* the `zen-button` or `zen-btn` attribute.
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
|
+
* <button zen-button> ... </button>
|
|
10
11
|
* <button zen-btn> ... </button>
|
|
12
|
+
* <a zen-button href="..."> ... </a>
|
|
11
13
|
*
|
|
12
14
|
* ### CSS Custom Properties
|
|
13
15
|
*
|
|
@@ -15,11 +17,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
|
15
17
|
*
|
|
16
18
|
* ```css
|
|
17
19
|
* :root {
|
|
18
|
-
* --zen-button-
|
|
19
|
-
* --zen-button-bg-color: grey;
|
|
20
|
-
* --zen-button-color: white;
|
|
21
|
-
* --zen-button-padding: 0.5rem 2rem;
|
|
20
|
+
* --zen-button-radius: 0.625rem;
|
|
22
21
|
* --zen-button-shadow: 0 2px 4px hsl(0deg 0% 0% / 10%);
|
|
22
|
+
* --zen-button-neutral: hsl(30deg 5% 10%);
|
|
23
|
+
* --zen-button-primary: hsl(95deg 15% 50%);
|
|
24
|
+
* --zen-button-success: hsl(105deg 20% 35%);
|
|
25
|
+
* --zen-button-warning: hsl(40deg 45% 60%);
|
|
26
|
+
* --zen-button-danger: hsl(5deg 50% 45%);
|
|
27
|
+
* --zen-button-info: hsl(205deg 30% 40%);
|
|
23
28
|
* }
|
|
24
29
|
* ```
|
|
25
30
|
*
|
|
@@ -35,5 +40,15 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
|
35
40
|
`,
|
|
36
41
|
styleUrl: './button.scss',
|
|
37
42
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
43
|
+
host: {
|
|
44
|
+
'[attr.type]': '"button"',
|
|
45
|
+
'[attr.data-size]': 'size()',
|
|
46
|
+
'[attr.data-variant]': 'variant()',
|
|
47
|
+
'[attr.data-color]': 'color()',
|
|
48
|
+
},
|
|
38
49
|
})
|
|
39
|
-
export class ZenButton {
|
|
50
|
+
export class ZenButton {
|
|
51
|
+
readonly size = input<'sm' | 'md' | 'lg'>('md');
|
|
52
|
+
readonly color = input<'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info'>('neutral');
|
|
53
|
+
readonly variant = input<'solid' | 'outline' | 'filled' | 'ghost' | 'link'>('solid');
|
|
54
|
+
}
|
|
@@ -66,9 +66,9 @@ See [GitHub](https://github.com/kstepien3/ng-zen), [MDN Dialog Element](https://
|
|
|
66
66
|
@Component({
|
|
67
67
|
template: `
|
|
68
68
|
<p>{{ message() }}</p>
|
|
69
|
-
<div style="display: flex; gap: 0.5rem; margin-top: 1rem;">
|
|
69
|
+
<div style="display: flex; gap: 0.5rem; margin-top: 1rem; justify-content: end">
|
|
70
|
+
<button (click)="cancel()" color="danger" variant="filled" zen-btn>Cancel</button>
|
|
70
71
|
<button (click)="confirmClick.emit('confirmed!')" zen-btn>Confirm</button>
|
|
71
|
-
<button (click)="cancel()" zen-btn>Cancel</button>
|
|
72
72
|
</div>
|
|
73
73
|
`,
|
|
74
74
|
standalone: true,
|
|
@@ -56,7 +56,7 @@ export const Default: Story = {
|
|
|
56
56
|
|
|
57
57
|
<dialog zen-dialog [(open)]="open" ${argsToTemplate(args)}>
|
|
58
58
|
<p>This is the dialog content. You can put any content here.</p>
|
|
59
|
-
<button zen-btn (click)="open.set(false)">Close</button>
|
|
59
|
+
<button zen-btn (click)="open.set(false)" variant="filled" color="danger">Close</button>
|
|
60
60
|
</dialog>
|
|
61
61
|
`,
|
|
62
62
|
};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
:host {
|
|
2
2
|
--zen-popover-offset: 0.5rem;
|
|
3
|
+
--zen-popover-bg: white;
|
|
4
|
+
--zen-popover-border: 1px solid #ccc;
|
|
5
|
+
--zen-popover-border-radius: 8px;
|
|
6
|
+
--zen-popover-padding: 0.5rem;
|
|
7
|
+
--zen-popover-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
|
8
|
+
--zen-popover-max-width: 300px;
|
|
3
9
|
}
|
|
4
10
|
|
|
5
11
|
/* stylelint-disable-next-line no-duplicate-selectors -- separate variables and styles */
|
|
@@ -8,12 +14,12 @@
|
|
|
8
14
|
inset: auto;
|
|
9
15
|
margin: var(--zen-popover-offset);
|
|
10
16
|
width: max-content;
|
|
11
|
-
max-width:
|
|
12
|
-
background:
|
|
13
|
-
border:
|
|
14
|
-
border-radius:
|
|
15
|
-
padding:
|
|
16
|
-
box-shadow:
|
|
17
|
+
max-width: var(--zen-popover-max-width);
|
|
18
|
+
background: var(--zen-popover-bg);
|
|
19
|
+
border: var(--zen-popover-border);
|
|
20
|
+
border-radius: var(--zen-popover-border-radius);
|
|
21
|
+
padding: var(--zen-popover-padding);
|
|
22
|
+
box-shadow: var(--zen-popover-shadow);
|
|
17
23
|
overflow: visible;
|
|
18
24
|
|
|
19
25
|
// Auto-reposition if the popover would overflow
|
|
@@ -43,7 +43,7 @@ const meta = {
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
id: { control: 'text', table: { defaultValue: { summary: 'zen-popover-*X*' } } },
|
|
46
|
-
content: { name: 'zenPopover', control: 'text' },
|
|
46
|
+
content: { name: 'zenPopover', control: 'text', table: { defaultValue: { summary: '' } } },
|
|
47
47
|
},
|
|
48
48
|
args: {
|
|
49
49
|
placement: 'top',
|
|
@@ -64,3 +64,20 @@ export const Default: Story = {
|
|
|
64
64
|
`,
|
|
65
65
|
}),
|
|
66
66
|
};
|
|
67
|
+
|
|
68
|
+
export const WithTemplate: Story = {
|
|
69
|
+
render: ({ placement, content }) => ({
|
|
70
|
+
template: `
|
|
71
|
+
<ng-template #popoverContent>
|
|
72
|
+
<i>${content}</i>
|
|
73
|
+
</ng-template>
|
|
74
|
+
<button zen-btn [zenPopover]="popoverContent" [zenPopoverPlacement]="'${placement}'">
|
|
75
|
+
Template
|
|
76
|
+
</button>
|
|
77
|
+
`,
|
|
78
|
+
}),
|
|
79
|
+
args: {
|
|
80
|
+
id: 'zen-popover-template-story',
|
|
81
|
+
placement: 'top',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
import { ZenPopoverHost } from './host/popover-host';
|
|
15
15
|
import { PopoverPlacement } from './popover-positions.type';
|
|
16
16
|
|
|
17
|
+
type HostProperties = keyof ZenPopoverHost;
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* ZenPopover is a reusable popover directive built on the native Popover API.
|
|
19
21
|
* It provides a consistent, themeable popover surface with CSS-based positioning,
|
|
@@ -21,25 +23,51 @@ import { PopoverPlacement } from './popover-positions.type';
|
|
|
21
23
|
*
|
|
22
24
|
* ### Limitations (arrow)
|
|
23
25
|
* ZenPopover may use CSS fallback positioning (e.g. via `position-try-fallbacks`) to keep the
|
|
24
|
-
* popover inside the viewport. Today CSS doesn
|
|
25
|
-
* which fallback placement was ultimately chosen, so an
|
|
26
|
-
* can
|
|
26
|
+
* popover inside the viewport. Today CSS doesn't provide a stable, cross-browser way to detect
|
|
27
|
+
* which fallback placement was ultimately chosen, so an "arrow" implemented with `::before/::after`
|
|
28
|
+
* can't be reliably synchronized with the final placement without extra JavaScript.
|
|
27
29
|
* A CSS-only solution becomes possible with Anchored Container Queries (e.g. `container-type: anchored`
|
|
28
30
|
* and `@container anchored(fallback: ...)`) where supported.
|
|
29
31
|
*
|
|
30
|
-
* @see https://developer.chrome.com/blog/anchored-container-queries
|
|
32
|
+
* @see [Detect fallback positions with anchored container queries](https://developer.chrome.com/blog/anchored-container-queries)
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
*
|
|
36
|
+
* ```html
|
|
37
|
+
* <!-- String content -->
|
|
38
|
+
* <button [zenPopover]="'Hello World'" [zenPopoverPlacement]="'bottom'">
|
|
39
|
+
* Trigger
|
|
40
|
+
* </button>
|
|
41
|
+
*
|
|
42
|
+
* <!-- TemplateRef content -->
|
|
43
|
+
* <button [zenPopover]="popoverContent" zenPopoverPlacement="right">
|
|
44
|
+
* Trigger
|
|
45
|
+
* </button>
|
|
46
|
+
* <ng-template #popoverContent>
|
|
47
|
+
* <div>Custom content</div>
|
|
48
|
+
* </ng-template>
|
|
49
|
+
* ```
|
|
31
50
|
*
|
|
32
51
|
* ### CSS Custom Properties
|
|
33
|
-
*
|
|
52
|
+
*
|
|
53
|
+
* You can customize the component using CSS custom properties:
|
|
54
|
+
* ```css
|
|
55
|
+
* :root {
|
|
56
|
+
* --zen-popover-offset: 0.5rem;
|
|
57
|
+
* --zen-popover-bg: white;
|
|
58
|
+
* --zen-popover-border: 1px solid #ccc;
|
|
59
|
+
* --zen-popover-border-radius: 8px;
|
|
60
|
+
* --zen-popover-padding: 0.5rem;
|
|
61
|
+
* --zen-popover-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
|
62
|
+
* --zen-popover-max-width: 300px;
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
34
65
|
*
|
|
35
66
|
* @author Konrad StΔpieΕ
|
|
36
67
|
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
37
68
|
* @see [GitHub](https://github.com/kstepien3/ng-zen)
|
|
38
69
|
* @see [MDN Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)
|
|
39
70
|
*/
|
|
40
|
-
|
|
41
|
-
type HostProperties = keyof ZenPopoverHost;
|
|
42
|
-
|
|
43
71
|
@Directive({
|
|
44
72
|
selector: '[zenPopover]',
|
|
45
73
|
standalone: true,
|