@ng-zen/cli 22.0.0 β†’ 22.1.0-next.2

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +2 -16
  2. package/README.md +18 -17
  3. package/package.json +3 -3
  4. package/schematics/ui/files/alert/alert.scss +1 -1
  5. package/schematics/ui/files/alert/alert.ts +1 -2
  6. package/schematics/ui/files/avatar/avatar.ts +1 -2
  7. package/schematics/ui/files/button/button.ts +1 -2
  8. package/schematics/ui/files/card/card.scss +57 -0
  9. package/schematics/ui/files/card/card.spec.ts +20 -0
  10. package/schematics/ui/files/card/card.stories.ts +157 -0
  11. package/schematics/ui/files/card/card.ts +64 -0
  12. package/schematics/ui/files/card/index.ts +1 -0
  13. package/schematics/ui/files/checkbox/checkbox.stories.ts +59 -33
  14. package/schematics/ui/files/checkbox/checkbox.ts +22 -26
  15. package/schematics/ui/files/dialog/dialog.ts +1 -2
  16. package/schematics/ui/files/divider/divider.ts +1 -2
  17. package/schematics/ui/files/form-control/form-control.spec.ts +20 -5
  18. package/schematics/ui/files/form-control/form-control.stories.ts +58 -36
  19. package/schematics/ui/files/form-control/form-control.ts +55 -70
  20. package/schematics/ui/files/icon/icon.ts +1 -2
  21. package/schematics/ui/files/input/input.stories.ts +84 -34
  22. package/schematics/ui/files/input/input.ts +27 -24
  23. package/schematics/ui/files/popover/host/popover-host.ts +1 -2
  24. package/schematics/ui/files/radio/radio.registry.ts +2 -2
  25. package/schematics/ui/files/radio/radio.spec.ts +3 -72
  26. package/schematics/ui/files/radio/radio.stories.ts +60 -112
  27. package/schematics/ui/files/radio/radio.ts +21 -33
  28. package/schematics/ui/files/skeleton/skeleton.ts +1 -2
  29. package/schematics/ui/files/switch/switch.stories.ts +46 -30
  30. package/schematics/ui/files/switch/switch.ts +20 -20
  31. package/schematics/ui/files/textarea/textarea.ts +1 -2
  32. package/schematics/ui/index.js +17 -2
  33. package/schematics/ui/index.js.map +1 -1
  34. package/schematics/ui/index.ts +26 -4
  35. package/schematics/ui/schema.js.map +1 -1
  36. package/schematics/ui/schema.json +1 -1
  37. package/schematics/ui/schema.ts +9 -1
  38. package/utils/apply-file-template.util.js.map +1 -1
  39. package/utils/apply-file-template.util.ts +2 -2
  40. package/types/files-config.js +0 -3
  41. package/types/files-config.js.map +0 -1
  42. package/types/files-config.ts +0 -7
  43. package/types/generator-schema-base.js +0 -3
  44. package/types/generator-schema-base.js.map +0 -1
  45. package/types/generator-schema-base.ts +0 -8
  46. package/types/index.js +0 -7
  47. package/types/index.js.map +0 -1
  48. package/types/index.ts +0 -3
  49. package/types/schematics-folder.js +0 -3
  50. package/types/schematics-folder.js.map +0 -1
  51. package/types/schematics-folder.ts +0 -3
package/CHANGELOG.md CHANGED
@@ -1,21 +1,7 @@
1
- ## [22.0.0](https://github.com/kstepien3/ng-zen/compare/v21.2.0...v22.0.0) (2026-06-16)
2
-
3
- ### ⚠ BREAKING CHANGES
4
-
5
- * **deps:** clean up unused deps and update dependencies (#414)
6
-
7
- ### chore
8
-
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))
1
+ ## [22.1.0-next.2](https://github.com/kstepien3/ng-zen/compare/v22.1.0-next.1...v22.1.0-next.2) (2026-06-30)
10
2
 
11
3
  ### πŸš€ New Features
12
4
 
13
- * **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))
14
- * **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))
15
- * **popover:** improve popover styles, stories, docs ([#404](https://github.com/kstepien3/ng-zen/issues/404)) ([6515443](https://github.com/kstepien3/ng-zen/commit/65154436cde09f77909638ef84e2213b04f2862c))
16
-
17
- ### πŸ“š Documentation
18
-
19
- * **README:** add DeepWiki link ([#409](https://github.com/kstepien3/ng-zen/issues/409)) ([7875347](https://github.com/kstepien3/ng-zen/commit/78753471c6e56036788c6371ce0f063ab06b524b))
5
+ * trigger 22.2.0-next.1 release ([803e0fa](https://github.com/kstepien3/ng-zen/commit/803e0faccbd41c77b8996227f034dbbda0fa2852))
20
6
 
21
7
  Development mode - changelog will be generated during build.
package/README.md CHANGED
@@ -45,7 +45,7 @@ Unlike traditional UI libraries that give you `<library-button>` black boxes, @n
45
45
 
46
46
  ### 🏎️ **Instant Productivity**
47
47
 
48
- - **Production-Ready UI Elements:** Alert, Avatar, Button, Checkbox, Dialog, Divider, Form Control, Icon, Input, Popover, Radio, Skeleton, Switch, Textarea
48
+ - **Production-Ready UI Elements:** Alert, Avatar, Button, Card, Checkbox, Dialog, Divider, Form Control, Icon, Input, Popover, Radio, Skeleton, Switch, Textarea
49
49
  - **Zero Configuration:** Works with Angular 20+ out of the box
50
50
  - **Complete Setup:** Each component includes unit tests, Storybook stories, and documentation
51
51
 
@@ -146,22 +146,23 @@ export class MyComponent {}
146
146
 
147
147
  ## 🧩 Available UIs
148
148
 
149
- | Component | Description | Features |
150
- | ---------------- | -------------------------------- | ------------------------------------------------------------------------- |
151
- | **Avatar** | User profile images and initials | Image fallback, size variants, rounded styles |
152
- | **Alert** | Informational messages | Customizable styles, dismissible options |
153
- | **Button** | Interactive buttons | Primary/secondary variants, loading states, icons |
154
- | **Checkbox** | Form checkboxes | Indeterminate state, custom styling, validation |
155
- | **Dialog** | Native modal dialogs | Native dialog element, service API, size variants, backdrop config |
156
- | **Divider** | Visual separators | Horizontal/vertical, with labels, custom thickness |
157
- | **Form Control** | Form field wrapper | Labels, validation messages, required indicators |
158
- | **Icon** | SVG icon system | Huge Icons integration, size variants, colors |
159
- | **Input** | Text input fields | Validation states, prefixes/suffixes, types |
160
- | **Popover** | Native Popover API directive | Template/string content, placements, click toggle, CSS anchor positioning |
161
- | **Radio** | Form radio buttons | Group selection, two-way binding, custom styling, disabled state |
162
- | **Skeleton** | Loading placeholders | Multiple shapes, animation, responsive |
163
- | **Switch** | Toggle controls | On/off states, disabled state, custom labels |
164
- | **Textarea** | Multi-line text input | Auto-resize, character counts, validation |
149
+ | Component | Description | Features |
150
+ | ---------------- | -------------------------------- | ------------------------------------------------------------------------------ |
151
+ | **Avatar** | User profile images and initials | Image fallback, size variants, rounded styles |
152
+ | **Alert** | Informational messages | Customizable styles, dismissible options |
153
+ | **Button** | Interactive buttons | Primary/secondary variants, loading states, icons |
154
+ | **Card** | Content container | Multi-slot projection (header, title, subtitle, footer), CSS custom properties |
155
+ | **Checkbox** | Form checkboxes | Indeterminate state, custom styling, validation |
156
+ | **Dialog** | Native modal dialogs | Native dialog element, service API, size variants, backdrop config |
157
+ | **Divider** | Visual separators | Horizontal/vertical, with labels, custom thickness |
158
+ | **Form Control** | Form field wrapper | Labels, validation messages, required indicators |
159
+ | **Icon** | SVG icon system | Huge Icons integration, size variants, colors |
160
+ | **Input** | Text input fields | Validation states, prefixes/suffixes, types |
161
+ | **Popover** | Native Popover API directive | Template/string content, placements, click toggle, CSS anchor positioning |
162
+ | **Radio** | Form radio buttons | Group selection, two-way binding, custom styling, disabled state |
163
+ | **Skeleton** | Loading placeholders | Multiple shapes, animation, responsive |
164
+ | **Switch** | Toggle controls | On/off states, disabled state, custom labels |
165
+ | **Textarea** | Multi-line text input | Auto-resize, character counts, validation |
165
166
 
166
167
  > πŸ’‘ **More components coming soon!** Planned: Directives, Pipes, Services, and additional UI elements.
167
168
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ng-zen/cli",
3
- "version": "22.0.0",
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.",
3
+ "version": "22.1.0-next.2",
4
+ "description": "Angular UI components generator – Zen UI Kit CLI for schematics-based creation of customizable components like alert, avatar, button, card, checkbox, dialog, divider, form-control, icon, input, popover, radio, skeleton, switch, textarea with Storybook demos.",
5
5
  "license": "BSD-2-Clause",
6
6
  "private": false,
7
7
  "repository": {
@@ -48,6 +48,7 @@
48
48
  "alert",
49
49
  "avatar",
50
50
  "button",
51
+ "card",
51
52
  "checkbox",
52
53
  "dialog",
53
54
  "divider",
@@ -69,7 +70,6 @@
69
70
  "dependencies": {
70
71
  "@angular-devkit/core": "^22.0.1",
71
72
  "@angular-devkit/schematics": "^22.0.1",
72
- "@angular/build": "22.0.1",
73
73
  "tslib": "^2.8.1"
74
74
  }
75
75
  }
@@ -33,7 +33,7 @@ $outline: var(--zen-outline, 2px solid hsl(200deg 100% 50% / 50%));
33
33
  }
34
34
 
35
35
  /* Title styles */
36
- ::ng-deep [alert-title] {
36
+ :host ::ng-deep [alert-title] {
37
37
  padding: 0;
38
38
  margin: 0;
39
39
  }
@@ -1,4 +1,4 @@
1
- import { ChangeDetectionStrategy, Component } from '@angular/core';
1
+ import { Component } from '@angular/core';
2
2
 
3
3
  /**
4
4
  * ZenAlert is a reusable alert component designed to provide
@@ -42,6 +42,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
42
42
  </div>
43
43
  `,
44
44
  styleUrl: './alert.scss',
45
- changeDetection: ChangeDetectionStrategy.OnPush,
46
45
  })
47
46
  export class ZenAlert {}
@@ -1,5 +1,5 @@
1
1
  import { NgOptimizedImage } from '@angular/common';
2
- import { ChangeDetectionStrategy, Component, input } from '@angular/core';
2
+ import { Component, input } from '@angular/core';
3
3
 
4
4
  /**
5
5
  * A component to display an avatar image.
@@ -46,7 +46,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
46
46
  }
47
47
  `,
48
48
  styleUrl: './avatar.scss',
49
- changeDetection: ChangeDetectionStrategy.OnPush,
50
49
  })
51
50
  export class ZenAvatar {
52
51
  /** Source path for the image which will be processed by Angular's image loader. */
@@ -1,4 +1,4 @@
1
- import { ChangeDetectionStrategy, Component, input } from '@angular/core';
1
+ import { Component, input } from '@angular/core';
2
2
 
3
3
  /**
4
4
  * ZenButton is a reusable button component designed to provide
@@ -39,7 +39,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
39
39
  <ng-content />
40
40
  `,
41
41
  styleUrl: './button.scss',
42
- changeDetection: ChangeDetectionStrategy.OnPush,
43
42
  host: {
44
43
  '[attr.type]': '"button"',
45
44
  '[attr.data-size]': 'size()',
@@ -0,0 +1,57 @@
1
+ :host {
2
+ --zen-card-border-radius: 0.75rem;
3
+ --zen-card-ring: 0 0 0 1px hsl(0deg 0% 0% / 10%);
4
+ --zen-card-shadow: 0 1px 3px 0 rgb(0 0 0 / 10%);
5
+ --zen-card-bg: hsl(0deg 0% 100%);
6
+ --zen-card-color: hsl(0deg 0% 10%);
7
+ --zen-card-padding: 1.5rem;
8
+ --zen-card-gap: 1rem;
9
+ --zen-card-header-gap: 0.25rem;
10
+ --zen-card-title-font-size: 1.125rem;
11
+ --zen-card-title-font-weight: 600;
12
+ --zen-card-subtitle-font-size: 0.875rem;
13
+ --zen-card-subtitle-color: hsl(0deg 0% 40%);
14
+ --zen-card-border: 1px solid hsl(0deg 0% 0% / 10%);
15
+ --zen-card-footer-bg: hsl(0deg 0% 95%);
16
+ }
17
+
18
+ /* stylelint-disable-next-line no-duplicate-selectors -- separate variables and styles */
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ font-size: 0.875rem;
23
+ border-radius: var(--zen-card-border-radius);
24
+ box-shadow: var(--zen-card-ring), var(--zen-card-shadow);
25
+ background: var(--zen-card-bg);
26
+ color: var(--zen-card-color);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .zen-card-body {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: var(--zen-card-gap);
34
+ padding: var(--zen-card-padding);
35
+ }
36
+
37
+ .zen-card-footer {
38
+ &:not(:empty) {
39
+ padding: var(--zen-card-padding);
40
+ border-top: var(--zen-card-border);
41
+ background: var(--zen-card-footer-bg);
42
+ }
43
+ }
44
+
45
+ :host ::ng-deep [card-title] {
46
+ margin: 0;
47
+ font-size: var(--zen-card-title-font-size);
48
+ font-weight: var(--zen-card-title-font-weight);
49
+ line-height: 1.4;
50
+ }
51
+
52
+ :host ::ng-deep [card-subtitle] {
53
+ margin: 0;
54
+ font-size: var(--zen-card-subtitle-font-size);
55
+ color: var(--zen-card-subtitle-color);
56
+ line-height: 1.5;
57
+ }
@@ -0,0 +1,20 @@
1
+ import { provideZonelessChangeDetection } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { beforeEach, describe, expect, it } from 'vitest';
4
+
5
+ import { ZenCard } from './card';
6
+
7
+ describe('ZenCard', () => {
8
+ beforeEach(async () => {
9
+ await TestBed.configureTestingModule({
10
+ imports: [ZenCard],
11
+ providers: [provideZonelessChangeDetection()],
12
+ }).compileComponents();
13
+ });
14
+
15
+ it('should create', () => {
16
+ const fixture = TestBed.createComponent(ZenCard);
17
+ const component = fixture.componentInstance;
18
+ expect(component).toBeTruthy();
19
+ });
20
+ });
@@ -0,0 +1,157 @@
1
+ import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
2
+
3
+ import { ZenButton } from '../button';
4
+ import { ZenCard } from './card';
5
+
6
+ interface StoryParams {
7
+ content: string;
8
+ title: string;
9
+ subtitle: string;
10
+ }
11
+
12
+ type Options = ZenCard & StoryParams;
13
+
14
+ export default {
15
+ title: 'Ui/Card',
16
+ component: ZenCard,
17
+ decorators: [moduleMetadata({ imports: [ZenButton] })],
18
+ args: {
19
+ content: 'This is the card content.',
20
+ title: 'Card Title',
21
+ subtitle: 'Card subtitle',
22
+ },
23
+ argTypes: {
24
+ content: {
25
+ control: 'text',
26
+ table: { category: 'story parameters', type: { summary: 'ng-content' } },
27
+ },
28
+ title: {
29
+ control: 'text',
30
+ table: { category: 'story parameters', type: { summary: 'string' } },
31
+ },
32
+ subtitle: {
33
+ control: 'text',
34
+ table: { category: 'story parameters', type: { summary: 'string' } },
35
+ },
36
+ },
37
+ render: ({ content, title, subtitle }) => ({
38
+ props: {},
39
+ template: `
40
+ <zen-card>
41
+ <h3 card-title>${title}</h3>
42
+ <p card-subtitle>${subtitle}</p>
43
+ ${content}
44
+ </zen-card>
45
+ `,
46
+ }),
47
+ } satisfies Meta<Options>;
48
+
49
+ type Story = StoryObj<Options>;
50
+
51
+ export const Default: Story = {};
52
+
53
+ export const Basic: Story = {
54
+ args: {
55
+ content: 'This is a simple card with only default content.',
56
+ },
57
+ render: ({ content }) => ({
58
+ props: {},
59
+ template: `
60
+ <zen-card>
61
+ ${content}
62
+ </zen-card>
63
+ `,
64
+ }),
65
+ };
66
+
67
+ export const WithFooter: Story = {
68
+ args: {
69
+ content: 'Card content with a footer action.',
70
+ },
71
+ render: ({ content, title, subtitle }) => ({
72
+ props: {},
73
+ template: `
74
+ <zen-card>
75
+ <h3 card-title>${title}</h3>
76
+ <p card-subtitle>${subtitle}</p>
77
+ ${content}
78
+ <div card-footer>
79
+ <button zen-button>Save changes</button>
80
+ </div>
81
+ </zen-card>
82
+ `,
83
+ }),
84
+ };
85
+
86
+ export const Size: Story = {
87
+ args: {
88
+ content: '',
89
+ title: '',
90
+ subtitle: '',
91
+ },
92
+ render: () => ({
93
+ props: {},
94
+ template: `
95
+ <zen-card style="--zen-card-padding: 1rem; --zen-card-gap: 0.75rem; --zen-card-header-gap: 0.125rem;">
96
+ <h3 card-title>Scheduled reports</h3>
97
+ <p card-subtitle>Weekly snapshots. No more manual exports.</p>
98
+ <ul style="margin: 0; padding-left: 1.25rem; display: flex; flex-direction: column; gap: 0.25rem;">
99
+ <li>Choose a schedule (daily, or weekly).</li>
100
+ <li>Send to channels or specific teammates.</li>
101
+ <li>Include charts, tables, and key metrics.</li>
102
+ </ul>
103
+ <div card-footer style="display: flex; gap: 0.5rem;">
104
+ <button zen-button>Set up scheduled reports</button>
105
+ <button zen-button variant="outline">See what's new</button>
106
+ </div>
107
+ </zen-card>
108
+ `,
109
+ }),
110
+ };
111
+
112
+ export const LoginForm: Story = {
113
+ args: {
114
+ content: '',
115
+ title: '',
116
+ subtitle: '',
117
+ },
118
+ render: () => ({
119
+ props: {},
120
+ template: `
121
+ <zen-card>
122
+ <h3 card-title>Login to your account</h3>
123
+ <p card-subtitle>Enter your email below to login to your account</p>
124
+ <div style="display: grid; gap: 0.75rem;">
125
+ <input type="email" placeholder="Email" style="border: 1px solid hsl(0deg 0% 80%); border-radius: 6px; padding: 0.5rem 0.75rem; font-size: 0.875rem;">
126
+ <input type="password" placeholder="Password" style="border: 1px solid hsl(0deg 0% 80%); border-radius: 6px; padding: 0.5rem 0.75rem; font-size: 0.875rem;">
127
+ <a href="#" style="font-size: 0.75rem; color: hsl(0deg 0% 40%); text-align: right;">Forgot your password?</a>
128
+ </div>
129
+ <div card-footer style="display: flex; flex-direction: column; gap: 0.5rem;">
130
+ <button zen-button>Login</button>
131
+ <button zen-button variant="filled">Login with Google</button>
132
+ </div>
133
+ </zen-card>
134
+ `,
135
+ }),
136
+ };
137
+
138
+ export const ImageCard: Story = {
139
+ args: {
140
+ content: '',
141
+ title: '',
142
+ subtitle: '',
143
+ },
144
+ render: () => ({
145
+ props: {},
146
+ template: `
147
+ <zen-card>
148
+ <img card-header src="https://picsum.photos/seed/ng-zen/600/400" alt="Event cover" style="display: block; width: 100%; height: 300px; object-fit: cover;">
149
+
150
+ <h3 card-title style="margin: 0;">Design systems meetup</h3>
151
+ <p style="margin: 0;">A practical talk on component APIs, accessibility, and shipping faster.</p>
152
+
153
+ <button card-footer zen-button variant="ghost" style="width: 100%;">View Event</button>
154
+ </zen-card>
155
+ `,
156
+ }),
157
+ };
@@ -0,0 +1,64 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ /**
4
+ * ZenCard is a reusable card component that provides a consistent container
5
+ * for grouping related content. It supports multi-slot content projection
6
+ * through attribute selectors.
7
+ *
8
+ * @example
9
+ *
10
+ * ```html
11
+ * <zen-card>
12
+ * <div card-header>
13
+ * <app-badge variant="secondary">Featured</app-badge>
14
+ * </div>
15
+ * <h3 card-title>Design systems meetup</h3>
16
+ * <p card-subtitle>
17
+ * A practical talk on component APIs, accessibility, and shipping faster.
18
+ * </p>
19
+ * <img src="..." alt="Event cover" />
20
+ * <div card-footer>
21
+ * <app-button>View Event</app-button>
22
+ * </div>
23
+ * </zen-card>
24
+ * ```
25
+ *
26
+ * ### CSS Custom Properties
27
+ *
28
+ * You can customize the component using CSS custom properties:
29
+ *
30
+ * ```css
31
+ * :root {
32
+ * --zen-card-border-radius: 0.75rem;
33
+ * --zen-card-ring: 0 0 0 1px hsl(0deg 0% 0% / 0.1);
34
+ * --zen-card-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
35
+ * --zen-card-bg: hsl(0deg 0% 100%);
36
+ * --zen-card-color: hsl(0deg 0% 10%);
37
+ * --zen-card-padding: 1.5rem;
38
+ * --zen-card-gap: 1rem;
39
+ * --zen-card-title-font-size: 1.125rem;
40
+ * --zen-card-subtitle-font-size: 0.875rem;
41
+ * --zen-card-footer-bg: hsl(0deg 0% 95%);
42
+ * }
43
+ * ```
44
+ *
45
+ * @author Konrad StΔ™pieΕ„
46
+ * @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
47
+ * @see [GitHub](https://github.com/kstepien3/ng-zen)
48
+ */
49
+ @Component({
50
+ selector: 'zen-card',
51
+ template: `
52
+ <ng-content select="[card-header]" />
53
+ <div class="zen-card-body">
54
+ <ng-content select="[card-title]" />
55
+ <ng-content select="[card-subtitle]" />
56
+ <ng-content />
57
+ </div>
58
+ <div class="zen-card-footer">
59
+ <ng-content select="[card-footer]" />
60
+ </div>
61
+ `,
62
+ styleUrl: './card.scss',
63
+ })
64
+ export class ZenCard {}
@@ -0,0 +1 @@
1
+ export * from './card';
@@ -1,5 +1,9 @@
1
+ import { NgComponentOutlet } from '@angular/common';
2
+ import { Component, signal } from '@angular/core';
3
+ import { form, FormField, required } from '@angular/forms/signals';
1
4
  import { Meta, StoryObj } from '@storybook/angular';
2
5
 
6
+ import FormControlStories from '../form-control/form-control.stories';
3
7
  import { ZenCheckbox } from './checkbox';
4
8
 
5
9
  type Options = ZenCheckbox;
@@ -8,53 +12,26 @@ export default {
8
12
  title: 'Ui/Checkbox',
9
13
  component: ZenCheckbox,
10
14
  argTypes: {
15
+ ...FormControlStories.argTypes,
11
16
  value: {
12
17
  table: {
13
18
  category: 'models',
14
- type: {
15
- summary: 'boolean | null',
16
- },
17
- defaultValue: {
18
- summary: 'false',
19
- },
19
+ type: { summary: 'boolean | null' },
20
+ defaultValue: { summary: 'false' },
20
21
  },
21
- control: 'radio',
22
+ control: 'radio' as const,
22
23
  options: [true, false, null],
23
24
  },
24
- disabled: {
25
- control: 'boolean',
26
- table: {
27
- category: 'models',
28
- type: {
29
- summary: 'boolean',
30
- },
31
- },
32
- },
33
- required: {
34
- control: 'boolean',
35
- table: {
36
- category: 'inputs',
37
- type: {
38
- summary: 'boolean',
39
- },
40
- defaultValue: {
41
- summary: 'false',
42
- },
43
- },
44
- },
45
25
  onInput: {
46
26
  table: {
47
27
  readonly: true,
48
- type: {
49
- summary: '(value: boolean | null) => void',
50
- },
28
+ type: { summary: '(value: boolean | null) => void' },
51
29
  },
52
30
  },
53
31
  },
54
32
  args: {
33
+ ...FormControlStories.args,
55
34
  value: false,
56
- disabled: false,
57
- required: false,
58
35
  },
59
36
  } satisfies Meta<Options>;
60
37
 
@@ -72,3 +49,52 @@ export const WithLabel: Story = {
72
49
  `,
73
50
  }),
74
51
  };
52
+
53
+ export const WithSignalForm: Story = {
54
+ render: () => ({
55
+ moduleMetadata: { imports: [NgComponentOutlet] },
56
+ props: { component: CheckboxSignalFormComponent },
57
+ template: '<ng-container *ngComponentOutlet="component" />',
58
+ }),
59
+ parameters: {
60
+ docs: {
61
+ source: {
62
+ code: `
63
+ <div style="display: flex; flex-direction: column; gap: 0.5rem;">
64
+ <div style="display: flex; align-items: center; gap: 0.25rem">
65
+ <zen-checkbox id="agree" [formField]="form.agree" />
66
+ <label for="agree">I agree to the terms</label>
67
+ </div>
68
+ @if (form.agree().invalid()) {
69
+ <p style="color: red; margin: 0; font-size: 0.875rem;">
70
+ {{ form.agree().errors()![0].message }}
71
+ </p>
72
+ }
73
+ </div>`,
74
+ },
75
+ },
76
+ },
77
+ };
78
+
79
+ @Component({
80
+ standalone: true,
81
+ template: `
82
+ <div style="display: flex; flex-direction: column; gap: 0.5rem;">
83
+ <div style="display: flex; align-items: center; gap: 0.25rem">
84
+ <zen-checkbox [formField]="form.agree" id="agree" />
85
+ <label for="agree">I agree to the terms</label>
86
+ </div>
87
+ @if (form.agree().invalid()) {
88
+ <p style="color: red; margin: 0; font-size: 0.875rem;">
89
+ {{ form.agree().errors()![0].message }}
90
+ </p>
91
+ }
92
+ </div>
93
+ `,
94
+ imports: [FormField, ZenCheckbox],
95
+ })
96
+ class CheckboxSignalFormComponent {
97
+ readonly form = form(signal({ agree: false }), s => {
98
+ required(s.agree, { message: 'You must agree to continue' });
99
+ });
100
+ }
@@ -1,34 +1,35 @@
1
- import { ChangeDetectionStrategy, Component, effect, ElementRef, model, viewChild } from '@angular/core';
2
- import { FormsModule } from '@angular/forms';
1
+ import { Component, effect, ElementRef, model, viewChild } from '@angular/core';
3
2
 
4
- import { ZenFormControl, ZenFormControlProvider } from '../form-control';
3
+ import { ZenFormControl } from '../form-control';
5
4
 
6
5
  /**
7
- * ZenCheckbox is a reusable checkbox component designed to provide
8
- * a consistent and customizable checkbox style across the application.
9
- * It supports Angular forms integration and provides two-way data binding
10
- * for boolean values.
6
+ * ZenCheckbox is a checkbox component supporting checked (`true`),
7
+ * unchecked (`false`), and indeterminate (`null`) states.
11
8
  *
12
- * @example
13
- * <zen-checkbox [value]="true" /> Checked
14
- * <zen-checkbox [value]="false" /> Unchecked
15
- * <zen-checkbox [value]="null" /> Indeterminate
9
+ * Connect it to a Signal Forms field with `[formField]`:
10
+ *
11
+ * ```html
12
+ * <zen-checkbox [formField]="myForm.agree" />
13
+ * <label for="agree">I agree to the terms</label>
14
+ * ```
15
+ *
16
+ * When `value` is `null` the native checkbox is set to indeterminate via
17
+ * the `indeterminate` DOM property.
16
18
  *
17
19
  * ### CSS Custom Properties
18
20
  *
19
- * You can customize the component using CSS custom properties:
20
21
  *
21
22
  * ```css
22
23
  * :root {
23
- * --zen-checkbox-size: 1rem;
24
- * --zen-checkbox-border-radius: 0.375rem;
25
- * --zen-checkbox-appearance: hsl(0deg 0% 10%);
26
- * --zen-checkbox-disabled-opacity: 0.6;
27
- * --zen-checkbox-border: 1px solid hsl(0deg 0% 80%);
24
+ * --zen-checkbox-size: 1rem;
25
+ * --zen-checkbox-border-radius: 0.375rem;
26
+ * --zen-checkbox-appearance: hsl(0deg 0% 10%);
27
+ * --zen-checkbox-disabled-opacity: 0.6;
28
+ * --zen-checkbox-border: 1px solid hsl(0deg 0% 80%);
28
29
  * }
29
30
  * ```
30
31
  *
31
- * @implements {ZenFormControl<boolean>}
32
+ * @extends {ZenFormControl<boolean | null>}
32
33
  *
33
34
  * @author Konrad StΔ™pieΕ„
34
35
  * @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
@@ -39,9 +40,10 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
39
40
  template: `
40
41
  <input
41
42
  [attr.aria-disabled]="disabled()"
43
+ [attr.aria-invalid]="invalid() || null"
44
+ [checked]="value()"
42
45
  [disabled]="disabled()"
43
- [ngModel]="value()"
44
- (ngModelChange)="onInput($event)"
46
+ (change)="onInput(inputElement.checked)"
45
47
  #inputElement
46
48
  type="checkbox"
47
49
  />
@@ -53,12 +55,6 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
53
55
  <!-- @else { βœ• } -->
54
56
  `,
55
57
  styleUrls: ['./checkbox.scss'],
56
- changeDetection: ChangeDetectionStrategy.OnPush,
57
- imports: [FormsModule],
58
- providers: [ZenFormControlProvider(ZenCheckbox)],
59
- host: {
60
- '(blur)': 'onTouched()',
61
- },
62
58
  })
63
59
  export class ZenCheckbox extends ZenFormControl<boolean | null> {
64
60
  /**