@ng-zen/cli 21.2.0 β†’ 21.3.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 CHANGED
@@ -1,11 +1,13 @@
1
- ## [21.2.0](https://github.com/kstepien3/ng-zen/compare/v21.1.1...v21.2.0) (2026-03-11)
1
+ ## [21.3.0-next.1](https://github.com/kstepien3/ng-zen/compare/v21.2.0...v21.3.0-next.1) (2026-06-12)
2
2
 
3
3
  ### πŸš€ New Features
4
4
 
5
- * **dialog:** add reusable dialog component with service and tests ([#387](https://github.com/kstepien3/ng-zen/issues/387)) ([1cb90f6](https://github.com/kstepien3/ng-zen/commit/1cb90f6c5b89b63a461abe6d45da74869275ff12))
5
+ * **button:** enhance component's sizes, variants, and styling updates ([#407](https://github.com/kstepien3/ng-zen/issues/407)) ([f32aad3](https://github.com/kstepien3/ng-zen/commit/f32aad35d5853f832f2eea0d8157884ca7f5cc3e))
6
+ * **button:** migrate to dynamic CSS variables and refactor styles ([#411](https://github.com/kstepien3/ng-zen/issues/411)) ([e4464f7](https://github.com/kstepien3/ng-zen/commit/e4464f7580c91f0de30d9139177655915430fd43))
7
+ * **popover:** improve popover styles, stories, docs ([#404](https://github.com/kstepien3/ng-zen/issues/404)) ([6515443](https://github.com/kstepien3/ng-zen/commit/65154436cde09f77909638ef84e2213b04f2862c))
6
8
 
7
9
  ### πŸ“š Documentation
8
10
 
9
- * **README:** update storybook tag ([#395](https://github.com/kstepien3/ng-zen/issues/395)) ([114dfdc](https://github.com/kstepien3/ng-zen/commit/114dfdc87614345784b264a6bde6be73b5e93aa7))
11
+ * **README:** add DeepWiki link ([#409](https://github.com/kstepien3/ng-zen/issues/409)) ([7875347](https://github.com/kstepien3/ng-zen/commit/78753471c6e56036788c6371ce0f063ab06b524b))
10
12
 
11
13
  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%20Demo-FF4785?style=flat&logo=storybook&logoColor=white" alt="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": "21.2.0",
3
+ "version": "21.3.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": "^21.0.1",
74
- "@angular-devkit/schematics": "^21.0.1",
75
- "@angular/build": "21.0.1",
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.4s);
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
- padding: var(--zen-button-padding);
64
+ --zen-button-bg-color: var(--zen-button-neutral); // default
65
+
16
66
  background-color: var(--zen-button-bg-color);
17
- color: var(--zen-button-color);
18
- box-shadow: var(--zen-button-shadow);
19
- display: inline-block;
20
- font-size: 14px;
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: 99999px;
26
- transition-property: background-color, opacity;
27
- transition-duration: $transition-duration;
28
- transition-timing-function: ease;
29
- text-decoration: none;
30
- overflow: hidden;
31
- position: relative;
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
- /* Disabled state */
45
- :host[aria-disabled]:not([aria-disabled='false']),
46
- :host[disabled]:not([disabled='false']) {
102
+ :host([disabled]) {
47
103
  cursor: not-allowed;
48
- opacity: 0.6;
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 type { Meta, StoryObj } from '@storybook/angular';
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
- content: string;
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
- content: {
21
- control: 'text',
22
- table: {
23
- category: 'story parameters',
24
- type: {
25
- summary: 'ng-content',
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: ({ content, ...args }) => ({
47
+ render: ({ ...args }) => ({
40
48
  props: args,
41
- template: `<button zen-btn ${args.disabled ? 'disabled' : ''}>${content}</button>`,
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-bg-color-hover: lightgrey;
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: 300px;
12
- background: white;
13
- border: 1px solid #ccc;
14
- border-radius: 8px;
15
- padding: 0.5rem;
16
- box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
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
@@ -1 +1,2 @@
1
1
  export * from './popover';
2
+ export * from './popover-positions.type';
@@ -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’t provide a stable, cross-browser way to detect
25
- * which fallback placement was ultimately chosen, so an β€œarrow” implemented with `::before/::after`
26
- * can’t be reliably synchronized with the final placement without extra JavaScript.
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
- * You can customize the popover using CSS custom properties (see component styles/docs).
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,