@ng-zen/cli 22.0.0 → 22.1.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 +2 -16
- package/package.json +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/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.json +0 -1
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { NgComponentOutlet } from '@angular/common';
|
|
2
|
+
import { Component, signal } from '@angular/core';
|
|
3
|
+
import { form, FormField, FormRoot, 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 { ZenInput } from './input';
|
|
4
8
|
|
|
5
9
|
type Options = ZenInput;
|
|
@@ -8,54 +12,27 @@ export default {
|
|
|
8
12
|
title: 'Ui/Input',
|
|
9
13
|
component: ZenInput,
|
|
10
14
|
argTypes: {
|
|
15
|
+
...FormControlStories.argTypes,
|
|
11
16
|
value: {
|
|
12
|
-
control: 'text',
|
|
17
|
+
control: 'text' as const,
|
|
13
18
|
table: {
|
|
14
19
|
category: 'models',
|
|
15
|
-
type: {
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
defaultValue: {
|
|
19
|
-
summary: '',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
placeholder: { control: 'text', table: { type: { summary: 'string' } } },
|
|
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
|
-
},
|
|
20
|
+
type: { summary: 'string' },
|
|
21
|
+
defaultValue: { summary: "''" },
|
|
43
22
|
},
|
|
44
23
|
},
|
|
24
|
+
placeholder: { control: 'text' as const, table: { type: { summary: 'string' } } },
|
|
45
25
|
onInput: {
|
|
46
26
|
table: {
|
|
47
27
|
readonly: true,
|
|
48
|
-
type: {
|
|
49
|
-
summary: '(value: string) => void',
|
|
50
|
-
},
|
|
28
|
+
type: { summary: '(value: string) => void' },
|
|
51
29
|
},
|
|
52
30
|
},
|
|
53
31
|
},
|
|
54
32
|
args: {
|
|
33
|
+
...FormControlStories.args,
|
|
55
34
|
value: '',
|
|
56
35
|
placeholder: '',
|
|
57
|
-
disabled: false,
|
|
58
|
-
required: false,
|
|
59
36
|
},
|
|
60
37
|
} satisfies Meta<Options>;
|
|
61
38
|
|
|
@@ -73,3 +50,76 @@ export const WithLabel: Story = {
|
|
|
73
50
|
`,
|
|
74
51
|
}),
|
|
75
52
|
};
|
|
53
|
+
|
|
54
|
+
export const WithSignalForm: Story = {
|
|
55
|
+
render: () => ({
|
|
56
|
+
moduleMetadata: { imports: [NgComponentOutlet] },
|
|
57
|
+
props: { component: InputSignalFormComponent },
|
|
58
|
+
template: '<ng-container *ngComponentOutlet="component" />',
|
|
59
|
+
}),
|
|
60
|
+
parameters: {
|
|
61
|
+
docs: {
|
|
62
|
+
source: {
|
|
63
|
+
code: `<zen-input [formField]="form.name" />`,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const WithFormRoot: Story = {
|
|
70
|
+
render: () => ({
|
|
71
|
+
moduleMetadata: { imports: [NgComponentOutlet] },
|
|
72
|
+
props: { component: InputFormRootComponent },
|
|
73
|
+
template: '<ng-container *ngComponentOutlet="component" />',
|
|
74
|
+
}),
|
|
75
|
+
parameters: {
|
|
76
|
+
docs: {
|
|
77
|
+
source: {
|
|
78
|
+
code: `<form [formRoot]="loginForm">
|
|
79
|
+
<zen-input [formField]="loginForm.email" placeholder="Email" />
|
|
80
|
+
<zen-input [formField]="loginForm.password" placeholder="Password" type="password" />
|
|
81
|
+
<button type="submit">Sign in</button>
|
|
82
|
+
</form>`,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
@Component({
|
|
89
|
+
standalone: true,
|
|
90
|
+
template: `
|
|
91
|
+
<div style="display: flex; flex-direction: column; gap: 0.5rem; max-width: 300px;">
|
|
92
|
+
<zen-input [formField]="form.name" placeholder="Type something" />
|
|
93
|
+
@if (form.name().invalid()) {
|
|
94
|
+
<p style="color: red; margin: 0; font-size: 0.875rem;">
|
|
95
|
+
{{ form.name().errors()![0].message }}
|
|
96
|
+
</p>
|
|
97
|
+
}
|
|
98
|
+
</div>
|
|
99
|
+
`,
|
|
100
|
+
imports: [FormField, ZenInput],
|
|
101
|
+
})
|
|
102
|
+
class InputSignalFormComponent {
|
|
103
|
+
readonly form = form(signal({ name: '' }), s => {
|
|
104
|
+
required(s.name, { message: 'Name is required' });
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Component({
|
|
109
|
+
standalone: true,
|
|
110
|
+
template: `
|
|
111
|
+
<form [formRoot]="loginForm" style="display: flex; flex-direction: column; gap: 0.5rem; max-width: 300px;">
|
|
112
|
+
<zen-input [formField]="loginForm.email" placeholder="Email" />
|
|
113
|
+
<zen-input [formField]="loginForm.password" placeholder="Password" type="password" />
|
|
114
|
+
<button type="submit">Sign in</button>
|
|
115
|
+
</form>
|
|
116
|
+
`,
|
|
117
|
+
imports: [FormRoot, FormField, ZenInput],
|
|
118
|
+
})
|
|
119
|
+
class InputFormRootComponent {
|
|
120
|
+
readonly loginModel = signal({ email: '', password: '' });
|
|
121
|
+
readonly loginForm = form(this.loginModel, s => {
|
|
122
|
+
required(s.email, { message: 'Email is required' });
|
|
123
|
+
required(s.password, { message: 'Password is required' });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FormsModule } from '@angular/forms';
|
|
1
|
+
import { Component, input, model } from '@angular/core';
|
|
3
2
|
|
|
4
|
-
import { ZenFormControl
|
|
3
|
+
import { ZenFormControl } from '../form-control';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* ZenInput is a
|
|
8
|
-
* a consistent and customizable input style across the application.
|
|
9
|
-
* It supports Angular forms integration and provides two-way data binding.
|
|
6
|
+
* ZenInput is a single-line text input component backed by Signal Forms.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Connect it to a Signal Forms field with `[formField]`:
|
|
9
|
+
*
|
|
10
|
+
* ```html
|
|
11
|
+
* <zen-input [formField]="myForm.name" placeholder="Enter name" />
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The `[formField]` directive automatically synchronises value, disabled
|
|
15
|
+
* state, validation errors, and touched/dirty status.
|
|
13
16
|
*
|
|
14
17
|
* ### CSS Custom Properties
|
|
15
|
-
*
|
|
18
|
+
*
|
|
16
19
|
*
|
|
17
20
|
* ```css
|
|
18
21
|
* :root {
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* --zen-input-border: 1px solid hsl(0deg 0% 80%);
|
|
23
|
+
* --zen-input-border-radius: 8px;
|
|
24
|
+
* --zen-input-padding: 0.5rem 1rem;
|
|
25
|
+
* --zen-input-focus-shadow: 0 1px 4px hsl(0deg 0% 60% / 20%) inset;
|
|
26
|
+
* --zen-input-placeholder-color: hsl(0deg 0% 60%);
|
|
24
27
|
* }
|
|
25
28
|
* ```
|
|
26
29
|
*
|
|
27
|
-
* @
|
|
30
|
+
* @extends {ZenFormControl<string>}
|
|
28
31
|
*
|
|
29
32
|
* @author Konrad Stępień
|
|
30
33
|
* @license {@link https://github.com/kstepien3/ng-zen/blob/master/LICENSE|BSD-2-Clause}
|
|
@@ -34,24 +37,24 @@ import { ZenFormControl, ZenFormControlProvider } from '../form-control';
|
|
|
34
37
|
selector: 'zen-input',
|
|
35
38
|
template: `
|
|
36
39
|
<input
|
|
37
|
-
[
|
|
38
|
-
[attr.required]="required()"
|
|
40
|
+
[aria-invalid]="invalid() || null"
|
|
39
41
|
[disabled]="disabled()"
|
|
40
|
-
[
|
|
42
|
+
[placeholder]="placeholder()"
|
|
43
|
+
[required]="required()"
|
|
44
|
+
[type]="type()"
|
|
41
45
|
[value]="value()"
|
|
42
|
-
(
|
|
43
|
-
|
|
46
|
+
(input)="onInput(inputRef.value)"
|
|
47
|
+
#inputRef
|
|
44
48
|
/>
|
|
45
49
|
`,
|
|
46
50
|
styleUrls: ['./input.scss'],
|
|
47
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
48
|
-
providers: [ZenFormControlProvider(ZenInput)],
|
|
49
|
-
imports: [FormsModule],
|
|
50
51
|
})
|
|
51
52
|
export class ZenInput extends ZenFormControl<string> {
|
|
52
53
|
/** The current input value with two-way binding support. */
|
|
53
54
|
readonly value = model('');
|
|
54
55
|
|
|
55
56
|
/** The placeholder text for the form control. */
|
|
56
|
-
readonly placeholder = input<string>();
|
|
57
|
+
readonly placeholder = input<string>('');
|
|
58
|
+
|
|
59
|
+
readonly type = input<'text' | 'email' | 'url' | 'password'>('text');
|
|
57
60
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, input } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
import { PopoverPlacement } from '../popover-positions.type';
|
|
4
4
|
|
|
@@ -25,7 +25,6 @@ import { PopoverPlacement } from '../popover-positions.type';
|
|
|
25
25
|
<ng-content />
|
|
26
26
|
`,
|
|
27
27
|
styleUrl: './popover-host.scss',
|
|
28
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
29
28
|
host: {
|
|
30
29
|
popover: 'auto',
|
|
31
30
|
'[attr.id]': 'id()',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Service } from '@angular/core';
|
|
2
2
|
|
|
3
3
|
import { ZenRadio } from './radio';
|
|
4
4
|
|
|
5
|
-
@
|
|
5
|
+
@Service()
|
|
6
6
|
export class ZenRadioRegistry {
|
|
7
7
|
private readonly radios = new Map<string, ZenRadio[]>();
|
|
8
8
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { provideZonelessChangeDetection } from '@angular/core';
|
|
2
2
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
-
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
4
3
|
import { By } from '@angular/platform-browser';
|
|
5
4
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
5
|
|
|
@@ -14,7 +13,7 @@ describe('ZenRadio', () => {
|
|
|
14
13
|
|
|
15
14
|
beforeEach(async () => {
|
|
16
15
|
await TestBed.configureTestingModule({
|
|
17
|
-
imports: [ZenRadio
|
|
16
|
+
imports: [ZenRadio],
|
|
18
17
|
providers: [ZenRadioRegistry, provideZonelessChangeDetection()],
|
|
19
18
|
}).compileComponents();
|
|
20
19
|
|
|
@@ -73,7 +72,7 @@ describe('ZenRadio', () => {
|
|
|
73
72
|
});
|
|
74
73
|
|
|
75
74
|
it('should not call onInput when disabled', () => {
|
|
76
|
-
|
|
75
|
+
fixture.componentRef.setInput('disabled', true);
|
|
77
76
|
fixture.detectChanges();
|
|
78
77
|
|
|
79
78
|
const spy = vi.spyOn(component, 'onInput');
|
|
@@ -85,63 +84,8 @@ describe('ZenRadio', () => {
|
|
|
85
84
|
expect(spy).not.toHaveBeenCalled();
|
|
86
85
|
});
|
|
87
86
|
|
|
88
|
-
it('should integrate with ngModel', () => {
|
|
89
|
-
let selectedValue: string | null = null;
|
|
90
|
-
|
|
91
|
-
const testFixture = TestBed.createComponent(ZenRadio);
|
|
92
|
-
testFixture.componentRef.setInput('name', 'ng-model-group');
|
|
93
|
-
testFixture.componentRef.setInput('option', 'option1');
|
|
94
|
-
testFixture.detectChanges();
|
|
95
|
-
|
|
96
|
-
// Simulate ngModel binding
|
|
97
|
-
testFixture.componentInstance.registerOnChange((value: string | null) => {
|
|
98
|
-
selectedValue = value;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const inputElement = testFixture.debugElement.query(By.css('input')).nativeElement;
|
|
102
|
-
inputElement.checked = true;
|
|
103
|
-
inputElement.dispatchEvent(new Event('change'));
|
|
104
|
-
|
|
105
|
-
expect(selectedValue).toBe('option1');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should integrate with reactive forms', () => {
|
|
109
|
-
const formControl = new FormControl<string | null>(null);
|
|
110
|
-
const testFixture = TestBed.createComponent(ZenRadio);
|
|
111
|
-
testFixture.componentRef.setInput('name', 'reactive-group');
|
|
112
|
-
testFixture.componentRef.setInput('option', 'option1');
|
|
113
|
-
|
|
114
|
-
// Simulate form control binding
|
|
115
|
-
testFixture.componentInstance.writeValue(formControl.value);
|
|
116
|
-
|
|
117
|
-
testFixture.componentInstance.registerOnChange((value: string | null) => {
|
|
118
|
-
formControl.setValue(value);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
testFixture.detectChanges();
|
|
122
|
-
|
|
123
|
-
const inputElement = testFixture.debugElement.query(By.css('input')).nativeElement;
|
|
124
|
-
inputElement.checked = true;
|
|
125
|
-
inputElement.dispatchEvent(new Event('change'));
|
|
126
|
-
|
|
127
|
-
// The component's onInput method should call the onChange callback
|
|
128
|
-
// which should update form control value
|
|
129
|
-
expect(formControl.value).toBe('option1');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should handle disabled state from form control', () => {
|
|
133
|
-
component.setDisabledState(true);
|
|
134
|
-
fixture.detectChanges();
|
|
135
|
-
|
|
136
|
-
expect(component.disabled()).toBe(true);
|
|
137
|
-
|
|
138
|
-
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
|
|
139
|
-
expect(inputElement.disabled).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
87
|
it('should have correct ARIA attributes', () => {
|
|
143
88
|
component.value.set('test-value');
|
|
144
|
-
component.disabled.set(false);
|
|
145
89
|
fixture.detectChanges();
|
|
146
90
|
|
|
147
91
|
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
|
|
@@ -151,7 +95,7 @@ describe('ZenRadio', () => {
|
|
|
151
95
|
|
|
152
96
|
it('should update ARIA attributes when state changes', () => {
|
|
153
97
|
component.value.set('different-value');
|
|
154
|
-
|
|
98
|
+
fixture.componentRef.setInput('disabled', true);
|
|
155
99
|
fixture.detectChanges();
|
|
156
100
|
|
|
157
101
|
const inputElement = fixture.debugElement.query(By.css('input')).nativeElement;
|
|
@@ -159,24 +103,12 @@ describe('ZenRadio', () => {
|
|
|
159
103
|
expect(inputElement.getAttribute('aria-disabled')).toBe('true');
|
|
160
104
|
});
|
|
161
105
|
|
|
162
|
-
it('should call onTouched when host element loses focus', () => {
|
|
163
|
-
const onTouchedSpy = vi.fn();
|
|
164
|
-
component.registerOnTouched(onTouchedSpy);
|
|
165
|
-
|
|
166
|
-
const hostElement = fixture.debugElement.nativeElement;
|
|
167
|
-
hostElement.dispatchEvent(new Event('blur'));
|
|
168
|
-
|
|
169
|
-
expect(onTouchedSpy).toHaveBeenCalled();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
106
|
it('should register with radio registry on initialization', () => {
|
|
173
|
-
// Create a new radio to test registry registration
|
|
174
107
|
const newFixture = TestBed.createComponent(ZenRadio);
|
|
175
108
|
newFixture.componentRef.setInput('name', 'registry-test');
|
|
176
109
|
newFixture.componentRef.setInput('option', 'registry-option');
|
|
177
110
|
newFixture.detectChanges();
|
|
178
111
|
|
|
179
|
-
// The radio should be registered after next render
|
|
180
112
|
expect(newFixture.componentInstance).toBeTruthy();
|
|
181
113
|
});
|
|
182
114
|
|
|
@@ -190,7 +122,6 @@ describe('ZenRadio', () => {
|
|
|
190
122
|
|
|
191
123
|
const radio2 = radio2Fixture.componentInstance;
|
|
192
124
|
|
|
193
|
-
// Test registry selection
|
|
194
125
|
registry.select('test-group', 'option2');
|
|
195
126
|
|
|
196
127
|
expect(radio1.value()).toBe('option2');
|
|
@@ -1,87 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NgComponentOutlet } from '@angular/common';
|
|
2
|
+
import { Component, signal } from '@angular/core';
|
|
3
|
+
import { form, FormField } from '@angular/forms/signals';
|
|
2
4
|
import { Meta, StoryObj } from '@storybook/angular';
|
|
3
5
|
|
|
6
|
+
import FormControlStories from '../form-control/form-control.stories';
|
|
4
7
|
import { ZenRadio } from './radio';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
selectedColor?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type Options = ZenRadio & StoryParams;
|
|
9
|
+
type Options = ZenRadio;
|
|
11
10
|
|
|
12
11
|
export default {
|
|
13
12
|
title: 'Ui/Radio',
|
|
14
13
|
component: ZenRadio,
|
|
15
14
|
argTypes: {
|
|
15
|
+
...FormControlStories.argTypes,
|
|
16
16
|
value: {
|
|
17
17
|
table: {
|
|
18
18
|
category: 'models',
|
|
19
|
-
type: {
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
defaultValue: {
|
|
23
|
-
summary: 'null',
|
|
24
|
-
},
|
|
19
|
+
type: { summary: 'string | null' },
|
|
20
|
+
defaultValue: { summary: 'null' },
|
|
25
21
|
},
|
|
26
|
-
control: 'text',
|
|
22
|
+
control: 'text' as const,
|
|
27
23
|
},
|
|
28
24
|
name: {
|
|
29
25
|
table: {
|
|
30
26
|
category: 'models',
|
|
31
|
-
type: {
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
defaultValue: {
|
|
35
|
-
summary: 'radio',
|
|
36
|
-
},
|
|
27
|
+
type: { summary: 'string' },
|
|
28
|
+
defaultValue: { summary: 'radio' },
|
|
37
29
|
},
|
|
38
|
-
control: 'text',
|
|
30
|
+
control: 'text' as const,
|
|
39
31
|
},
|
|
40
32
|
option: {
|
|
41
33
|
table: {
|
|
42
34
|
category: 'inputs',
|
|
43
|
-
type: {
|
|
44
|
-
summary: 'string',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
control: 'text',
|
|
48
|
-
},
|
|
49
|
-
disabled: {
|
|
50
|
-
control: 'boolean',
|
|
51
|
-
table: {
|
|
52
|
-
category: 'models',
|
|
53
|
-
type: {
|
|
54
|
-
summary: 'boolean',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
required: {
|
|
59
|
-
control: 'boolean',
|
|
60
|
-
table: {
|
|
61
|
-
category: 'inputs',
|
|
62
|
-
type: {
|
|
63
|
-
summary: 'boolean',
|
|
64
|
-
},
|
|
65
|
-
defaultValue: {
|
|
66
|
-
summary: 'false',
|
|
67
|
-
},
|
|
35
|
+
type: { summary: 'string' },
|
|
68
36
|
},
|
|
37
|
+
control: 'text' as const,
|
|
69
38
|
},
|
|
70
39
|
onInput: {
|
|
71
40
|
table: {
|
|
72
41
|
readonly: true,
|
|
73
|
-
type: {
|
|
74
|
-
summary: '(value: string | null) => void',
|
|
75
|
-
},
|
|
42
|
+
type: { summary: '(value: string | null) => void' },
|
|
76
43
|
},
|
|
77
44
|
},
|
|
78
45
|
},
|
|
79
46
|
args: {
|
|
47
|
+
...FormControlStories.args,
|
|
80
48
|
value: null,
|
|
81
49
|
name: 'radio-group',
|
|
82
50
|
option: 'option1',
|
|
83
|
-
disabled: false,
|
|
84
|
-
required: false,
|
|
85
51
|
},
|
|
86
52
|
} satisfies Meta<Options>;
|
|
87
53
|
|
|
@@ -100,70 +66,52 @@ export const WithLabel: Story = {
|
|
|
100
66
|
}),
|
|
101
67
|
};
|
|
102
68
|
|
|
103
|
-
export const
|
|
69
|
+
export const WithSignalForm: Story = {
|
|
104
70
|
render: () => ({
|
|
105
|
-
moduleMetadata: {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
props: {
|
|
109
|
-
selectedColor: 'blue',
|
|
110
|
-
},
|
|
111
|
-
template: `
|
|
112
|
-
<div style="display: flex; flex-direction: column; gap: 1rem">
|
|
113
|
-
<div>
|
|
114
|
-
<strong>Selected value:</strong>
|
|
115
|
-
<p> {{ selectedColor || 'None' }}</p>
|
|
116
|
-
</div>
|
|
117
|
-
<div style="display: flex; flex-direction: column; gap: 0.5rem">
|
|
118
|
-
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
119
|
-
<zen-radio name="color-group" option="red" [(ngModel)]="selectedColor" />
|
|
120
|
-
<label>Red</label>
|
|
121
|
-
</div>
|
|
122
|
-
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
123
|
-
<zen-radio name="color-group" option="green" [(ngModel)]="selectedColor" />
|
|
124
|
-
<label>Green</label>
|
|
125
|
-
</div>
|
|
126
|
-
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
127
|
-
<zen-radio name="color-group" option="blue" [(ngModel)]="selectedColor" />
|
|
128
|
-
<label>Blue</label>
|
|
129
|
-
</div>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
`,
|
|
71
|
+
moduleMetadata: { imports: [NgComponentOutlet] },
|
|
72
|
+
props: { component: RadioSignalFormComponent },
|
|
73
|
+
template: '<ng-container *ngComponentOutlet="component" />',
|
|
133
74
|
}),
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
imports: [ReactiveFormsModule],
|
|
141
|
-
},
|
|
142
|
-
props: {
|
|
143
|
-
colorControl: new FormControl('green'),
|
|
75
|
+
parameters: {
|
|
76
|
+
docs: {
|
|
77
|
+
source: {
|
|
78
|
+
code: `<zen-radio [formField]="form.color" option="red" />
|
|
79
|
+
<zen-radio [formField]="form.color" option="green" />
|
|
80
|
+
<zen-radio [formField]="form.color" option="blue" />`,
|
|
144
81
|
},
|
|
145
|
-
|
|
146
|
-
<div style="display: flex; flex-direction: column; gap: 1rem">
|
|
147
|
-
<div>
|
|
148
|
-
<strong>Selected value:</strong>
|
|
149
|
-
<p> {{ colorControl.value || 'None' }}</p>
|
|
150
|
-
</div>
|
|
151
|
-
<div style="display: flex; flex-direction: column; gap: 0.5rem">
|
|
152
|
-
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
153
|
-
<zen-radio name="working-group" option="red" [formControl]="colorControl" />
|
|
154
|
-
<label>Red</label>
|
|
155
|
-
</div>
|
|
156
|
-
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
157
|
-
<zen-radio name="working-group" option="green" [formControl]="colorControl" />
|
|
158
|
-
<label>Green</label>
|
|
159
|
-
</div>
|
|
160
|
-
<div style="display: flex; align-items: center; gap: 0.25rem">
|
|
161
|
-
<zen-radio name="working-group" option="blue" [formControl]="colorControl" />
|
|
162
|
-
<label>Blue</label>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
`,
|
|
167
|
-
};
|
|
82
|
+
},
|
|
168
83
|
},
|
|
169
84
|
};
|
|
85
|
+
|
|
86
|
+
@Component({
|
|
87
|
+
standalone: true,
|
|
88
|
+
imports: [FormField, ZenRadio],
|
|
89
|
+
template: `
|
|
90
|
+
<div style="display: flex; flex-direction: column; gap: 1rem">
|
|
91
|
+
<div>
|
|
92
|
+
<strong>Selected:</strong>
|
|
93
|
+
{{ form.color().value() }}
|
|
94
|
+
</div>
|
|
95
|
+
<div style="display: flex; flex-direction: column; gap: 0.5rem">
|
|
96
|
+
<!-- eslint-disable-next-line @angular-eslint/template/label-has-associated-control -->
|
|
97
|
+
<label style="display: flex; align-items: center; gap: 0.25rem">
|
|
98
|
+
<zen-radio [formField]="form.color" option="red" />
|
|
99
|
+
Red
|
|
100
|
+
</label>
|
|
101
|
+
<!-- eslint-disable-next-line @angular-eslint/template/label-has-associated-control -->
|
|
102
|
+
<label style="display: flex; align-items: center; gap: 0.25rem">
|
|
103
|
+
<zen-radio [formField]="form.color" option="green" />
|
|
104
|
+
Green
|
|
105
|
+
</label>
|
|
106
|
+
<!-- eslint-disable-next-line @angular-eslint/template/label-has-associated-control -->
|
|
107
|
+
<label style="display: flex; align-items: center; gap: 0.25rem">
|
|
108
|
+
<zen-radio [formField]="form.color" option="blue" />
|
|
109
|
+
Blue
|
|
110
|
+
</label>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
`,
|
|
114
|
+
})
|
|
115
|
+
class RadioSignalFormComponent {
|
|
116
|
+
readonly form = form(signal({ color: 'blue' }));
|
|
117
|
+
}
|