@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.
- package/CHANGELOG.md +2 -16
- package/README.md +18 -17
- package/package.json +3 -3
- package/schematics/ui/files/alert/alert.scss +1 -1
- package/schematics/ui/files/alert/alert.ts +1 -2
- package/schematics/ui/files/avatar/avatar.ts +1 -2
- package/schematics/ui/files/button/button.ts +1 -2
- package/schematics/ui/files/card/card.scss +57 -0
- package/schematics/ui/files/card/card.spec.ts +20 -0
- package/schematics/ui/files/card/card.stories.ts +157 -0
- package/schematics/ui/files/card/card.ts +64 -0
- package/schematics/ui/files/card/index.ts +1 -0
- package/schematics/ui/files/checkbox/checkbox.stories.ts +59 -33
- package/schematics/ui/files/checkbox/checkbox.ts +22 -26
- package/schematics/ui/files/dialog/dialog.ts +1 -2
- package/schematics/ui/files/divider/divider.ts +1 -2
- package/schematics/ui/files/form-control/form-control.spec.ts +20 -5
- package/schematics/ui/files/form-control/form-control.stories.ts +58 -36
- package/schematics/ui/files/form-control/form-control.ts +55 -70
- package/schematics/ui/files/icon/icon.ts +1 -2
- package/schematics/ui/files/input/input.stories.ts +84 -34
- package/schematics/ui/files/input/input.ts +27 -24
- package/schematics/ui/files/popover/host/popover-host.ts +1 -2
- package/schematics/ui/files/radio/radio.registry.ts +2 -2
- package/schematics/ui/files/radio/radio.spec.ts +3 -72
- package/schematics/ui/files/radio/radio.stories.ts +60 -112
- package/schematics/ui/files/radio/radio.ts +21 -33
- package/schematics/ui/files/skeleton/skeleton.ts +1 -2
- package/schematics/ui/files/switch/switch.stories.ts +46 -30
- package/schematics/ui/files/switch/switch.ts +20 -20
- package/schematics/ui/files/textarea/textarea.ts +1 -2
- package/schematics/ui/index.js +17 -2
- package/schematics/ui/index.js.map +1 -1
- package/schematics/ui/index.ts +26 -4
- package/schematics/ui/schema.js.map +1 -1
- package/schematics/ui/schema.json +1 -1
- package/schematics/ui/schema.ts +9 -1
- package/utils/apply-file-template.util.js.map +1 -1
- package/utils/apply-file-template.util.ts +2 -2
- package/types/files-config.js +0 -3
- package/types/files-config.js.map +0 -1
- package/types/files-config.ts +0 -7
- package/types/generator-schema-base.js +0 -3
- package/types/generator-schema-base.js.map +0 -1
- package/types/generator-schema-base.ts +0 -8
- package/types/index.js +0 -7
- package/types/index.js.map +0 -1
- package/types/index.ts +0 -3
- package/types/schematics-folder.js +0 -3
- package/types/schematics-folder.js.map +0 -1
- package/types/schematics-folder.ts +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
## [22.0.
|
|
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
|
-
*
|
|
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
|
-
| **
|
|
155
|
-
| **
|
|
156
|
-
| **
|
|
157
|
-
| **
|
|
158
|
-
| **
|
|
159
|
-
| **
|
|
160
|
-
| **
|
|
161
|
-
| **
|
|
162
|
-
| **
|
|
163
|
-
| **
|
|
164
|
-
| **
|
|
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.
|
|
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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import { FormsModule } from '@angular/forms';
|
|
1
|
+
import { Component, effect, ElementRef, model, viewChild } from '@angular/core';
|
|
3
2
|
|
|
4
|
-
import { ZenFormControl
|
|
3
|
+
import { ZenFormControl } from '../form-control';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* ZenCheckbox is a
|
|
8
|
-
*
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* <zen-checkbox [
|
|
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
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
/**
|