@egose/shadcn-theme-ng 0.3.0 → 0.5.0
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/README.md +1 -1
- package/form-autocomplete/README.md +20 -19
- package/form-autocomplete/fesm2022/form-autocomplete.mjs +44 -9
- package/form-autocomplete/types/form-autocomplete.d.ts +22 -1
- package/form-checkbox/README.md +20 -19
- package/form-checkbox/fesm2022/form-checkbox.mjs +99 -15
- package/form-checkbox/types/form-checkbox.d.ts +43 -3
- package/form-combobox/README.md +21 -20
- package/form-combobox/fesm2022/form-combobox.mjs +44 -9
- package/form-combobox/types/form-combobox.d.ts +22 -1
- package/form-date-picker/README.md +28 -27
- package/form-date-picker/fesm2022/form-date-picker.mjs +45 -15
- package/form-date-picker/types/form-date-picker.d.ts +22 -1
- package/form-date-picker-multi/README.md +28 -27
- package/form-date-picker-multi/fesm2022/form-date-picker-multi.mjs +45 -15
- package/form-date-picker-multi/types/form-date-picker-multi.d.ts +22 -1
- package/form-date-range-picker/README.md +28 -27
- package/form-date-range-picker/fesm2022/form-date-range-picker.mjs +45 -15
- package/form-date-range-picker/types/form-date-range-picker.d.ts +22 -1
- package/form-field/README.md +9 -0
- package/form-field/fesm2022/form-field.mjs +97 -5
- package/form-field/types/form-field.d.ts +96 -2
- package/form-field-simple/fesm2022/form-field-simple.mjs +15 -1
- package/form-field-simple/types/form-field-simple.d.ts +9 -2
- package/form-input-otp/README.md +17 -16
- package/form-input-otp/fesm2022/form-input-otp.mjs +43 -8
- package/form-input-otp/types/form-input-otp.d.ts +22 -1
- package/form-month-year-picker/README.md +26 -25
- package/form-month-year-picker/fesm2022/form-month-year-picker.mjs +45 -15
- package/form-month-year-picker/types/form-month-year-picker.d.ts +22 -1
- package/form-native-select/README.md +18 -17
- package/form-native-select/fesm2022/form-native-select.mjs +44 -14
- package/form-native-select/types/form-native-select.d.ts +22 -1
- package/form-phone-input/README.md +23 -22
- package/form-phone-input/fesm2022/form-phone-input.mjs +44 -14
- package/form-phone-input/types/form-phone-input.d.ts +22 -1
- package/form-radio-group/README.md +18 -17
- package/form-radio-group/fesm2022/form-radio-group.mjs +44 -14
- package/form-radio-group/types/form-radio-group.d.ts +22 -1
- package/form-searchable-multiselect/README.md +18 -17
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +44 -14
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +22 -1
- package/form-select/README.md +20 -19
- package/form-select/fesm2022/form-select.mjs +44 -14
- package/form-select/types/form-select.d.ts +22 -1
- package/form-slider/README.md +19 -18
- package/form-slider/fesm2022/form-slider.mjs +43 -8
- package/form-slider/types/form-slider.d.ts +22 -1
- package/form-switch/README.md +16 -15
- package/form-switch/fesm2022/form-switch.mjs +44 -14
- package/form-switch/types/form-switch.d.ts +22 -1
- package/form-text-input/README.md +27 -26
- package/form-text-input/fesm2022/form-text-input.mjs +44 -14
- package/form-text-input/types/form-text-input.d.ts +22 -1
- package/form-textarea/README.md +23 -22
- package/form-textarea/fesm2022/form-textarea.mjs +44 -14
- package/form-textarea/types/form-textarea.d.ts +22 -1
- package/form-toggle/README.md +17 -16
- package/form-toggle/fesm2022/form-toggle.mjs +47 -10
- package/form-toggle/types/form-toggle.d.ts +22 -3
- package/form-toggle-group/README.md +20 -19
- package/form-toggle-group/fesm2022/form-toggle-group.mjs +44 -14
- package/form-toggle-group/types/form-toggle-group.d.ts +22 -1
- package/package.json +5 -1
- package/stepper/README.md +353 -0
- package/stepper/fesm2022/stepper.mjs +396 -0
- package/stepper/package.json +24 -0
- package/stepper/types/stepper.d.ts +116 -0
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, inject, input, computed, Component } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormGroupDirective, ReactiveFormsModule, ControlContainer } from '@angular/forms';
|
|
5
|
-
import { HlmFormIdGenerator, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
|
|
5
|
+
import { injectEgFormErrorMessages, HlmFormIdGenerator, injectEgFormSharedConfig, resolveEgFormError, HlmError, HlmHint } from '@egose/shadcn-theme-ng/form-field';
|
|
6
6
|
import { EgFormField } from '@egose/shadcn-theme-ng/form-field-simple';
|
|
7
7
|
import { HlmLabel } from '@egose/shadcn-theme-ng/label';
|
|
8
8
|
import { HlmToggleGroup, HlmToggleGroupItem } from '@egose/shadcn-theme-ng/toggle-group';
|
|
@@ -18,8 +18,10 @@ function injectEgFormToggleGroupConfig() {
|
|
|
18
18
|
|
|
19
19
|
class EgFormToggleGroup {
|
|
20
20
|
formGroupDirective = inject(FormGroupDirective);
|
|
21
|
+
_errorMessages = injectEgFormErrorMessages();
|
|
21
22
|
generatedId = inject(HlmFormIdGenerator).generate('eg-form-toggle-group');
|
|
22
23
|
_config = injectEgFormToggleGroupConfig();
|
|
24
|
+
_shared = injectEgFormSharedConfig();
|
|
23
25
|
label = input(undefined, /* @ts-ignore */
|
|
24
26
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
25
27
|
controlId = input(undefined, /* @ts-ignore */
|
|
@@ -28,6 +30,13 @@ class EgFormToggleGroup {
|
|
|
28
30
|
...(ngDevMode ? [{ debugName: "controlName" }] : /* istanbul ignore next */ []));
|
|
29
31
|
error = input(undefined, /* @ts-ignore */
|
|
30
32
|
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
33
|
+
/**
|
|
34
|
+
* Auto-resolve the displayed message from the control's `ValidationErrors`
|
|
35
|
+
* when `error()` is unset. Explicit `error()` always wins. Set to `false`
|
|
36
|
+
* for manual-only messages.
|
|
37
|
+
*/
|
|
38
|
+
autoError = input(true, /* @ts-ignore */
|
|
39
|
+
...(ngDevMode ? [{ debugName: "autoError" }] : /* istanbul ignore next */ []));
|
|
31
40
|
hint = input(undefined, /* @ts-ignore */
|
|
32
41
|
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
33
42
|
id = input(undefined, /* @ts-ignore */
|
|
@@ -50,6 +59,32 @@ class EgFormToggleGroup {
|
|
|
50
59
|
...(ngDevMode ? [{ debugName: "errorId" }] : /* istanbul ignore next */ []));
|
|
51
60
|
hintId = computed(() => `${this.effectiveId()}-hint`, /* @ts-ignore */
|
|
52
61
|
...(ngDevMode ? [{ debugName: "hintId" }] : /* istanbul ignore next */ []));
|
|
62
|
+
/**
|
|
63
|
+
* Displayed message: explicit `error()` wins; otherwise (when `autoError()`)
|
|
64
|
+
* auto-resolved from the control's `ValidationErrors`. Plain method (not a
|
|
65
|
+
* computed): `ValidationErrors` is not a signal, so the control must be read
|
|
66
|
+
* fresh on every change-detection pass.
|
|
67
|
+
*/
|
|
68
|
+
resolvedError() {
|
|
69
|
+
const explicit = this.error();
|
|
70
|
+
if (explicit)
|
|
71
|
+
return explicit;
|
|
72
|
+
if (!this.autoError())
|
|
73
|
+
return undefined;
|
|
74
|
+
const control = this.formGroupDirective.form.get(this.controlName());
|
|
75
|
+
return resolveEgFormError(control?.errors ?? null, this.label(), this._errorMessages);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Whether the error is surfaced: message present + control invalid +
|
|
79
|
+
* touched/dirty/submitted. Gates the visual `<hlm-error>` (and
|
|
80
|
+
* `describedBy()`, where present) so sighted and screen-reader output agree.
|
|
81
|
+
*/
|
|
82
|
+
showError() {
|
|
83
|
+
const control = this.formGroupDirective.form.get(this.controlName());
|
|
84
|
+
return (!!this.resolvedError() &&
|
|
85
|
+
!!control?.invalid &&
|
|
86
|
+
(control.dirty || control.touched || this.formGroupDirective.submitted));
|
|
87
|
+
}
|
|
53
88
|
/**
|
|
54
89
|
* Combined lock for the brain `linkedSignal`-based disabled state: the raw
|
|
55
90
|
* input write always carries the resolved value, so a same-pass
|
|
@@ -59,12 +94,7 @@ class EgFormToggleGroup {
|
|
|
59
94
|
return this.disabled() || !!this.formGroupDirective.form.get(this.controlName())?.disabled;
|
|
60
95
|
}
|
|
61
96
|
describedBy() {
|
|
62
|
-
|
|
63
|
-
return this.error() && control?.invalid && (control.dirty || control.touched)
|
|
64
|
-
? this.errorId()
|
|
65
|
-
: this.hint()
|
|
66
|
-
? this.hintId()
|
|
67
|
-
: null;
|
|
97
|
+
return this.showError() ? this.errorId() : this.hint() ? this.hintId() : null;
|
|
68
98
|
}
|
|
69
99
|
// Styling
|
|
70
100
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
@@ -79,16 +109,16 @@ class EgFormToggleGroup {
|
|
|
79
109
|
// Computed classes (library base < global config < per-instance)
|
|
80
110
|
$userClass = computed(() => hlm("w-full", this.userClass()), /* @ts-ignore */
|
|
81
111
|
...(ngDevMode ? [{ debugName: "$userClass" }] : /* istanbul ignore next */ []));
|
|
82
|
-
$labelClass = computed(() => hlm("mb-1", this._config.labelClass, this.labelClass()), /* @ts-ignore */
|
|
112
|
+
$labelClass = computed(() => hlm("mb-1", this._shared.labelClass, this._config.labelClass, this.labelClass()), /* @ts-ignore */
|
|
83
113
|
...(ngDevMode ? [{ debugName: "$labelClass" }] : /* istanbul ignore next */ []));
|
|
84
|
-
$groupClass = computed(() => hlm(this._config.groupClass, this.groupClass()), /* @ts-ignore */
|
|
114
|
+
$groupClass = computed(() => hlm(this._shared.groupClass, this._config.groupClass, this.groupClass()), /* @ts-ignore */
|
|
85
115
|
...(ngDevMode ? [{ debugName: "$groupClass" }] : /* istanbul ignore next */ []));
|
|
86
|
-
$errorClass = computed(() => hlm("mt-0", this._config.errorClass, this.errorClass()), /* @ts-ignore */
|
|
116
|
+
$errorClass = computed(() => hlm("mt-0", this._shared.errorClass, this._config.errorClass, this.errorClass()), /* @ts-ignore */
|
|
87
117
|
...(ngDevMode ? [{ debugName: "$errorClass" }] : /* istanbul ignore next */ []));
|
|
88
|
-
$hintClass = computed(() => hlm("mt-0", this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
118
|
+
$hintClass = computed(() => hlm("mt-0", this._shared.hintClass, this._config.hintClass, this.hintClass()), /* @ts-ignore */
|
|
89
119
|
...(ngDevMode ? [{ debugName: "$hintClass" }] : /* istanbul ignore next */ []));
|
|
90
120
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormToggleGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
91
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormToggleGroup, isStandalone: true, selector: "eg-form-toggle-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let
|
|
121
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgFormToggleGroup, isStandalone: true, selector: "eg-form-toggle-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, controlId: { classPropertyName: "controlId", publicName: "controlId", isSignal: true, isRequired: false, transformFunction: null }, controlName: { classPropertyName: "controlName", publicName: "controlName", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, autoError: { classPropertyName: "autoError", publicName: "autoError", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, labelClass: { classPropertyName: "labelClass", publicName: "labelClass", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, errorClass: { classPropertyName: "errorClass", publicName: "errorClass", isSignal: true, isRequired: false, transformFunction: null }, hintClass: { classPropertyName: "hintClass", publicName: "hintClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "$userClass()" } }, providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }], ngImport: i0, template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <eg-form-field>\n @if (lbl) {\n <span hlmLabel [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n }\n\n <hlm-toggle-group\n [formControlName]=\"cnm\"\n [type]=\"type()\"\n [disabled]=\"effectiveDisabled()\"\n [variant]=\"variant()\"\n [size]=\"size()\"\n [class]=\"$groupClass()\"\n >\n @for (option of options(); track option.value) {\n <button\n hlmToggleGroupItem\n type=\"button\"\n [value]=\"option.value\"\n [disabled]=\"option.disabled ?? false\"\n [attr.aria-describedby]=\"describedBy()\"\n >\n {{ option.label }}\n </button>\n }\n </hlm-toggle-group>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </eg-form-field>\n ", isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: HlmError, selector: "hlm-error", inputs: ["class"] }, { kind: "directive", type: HlmHint, selector: "hlm-hint", inputs: ["class"] }, { kind: "directive", type: HlmLabel, selector: "[hlmLabel]", inputs: ["class"] }, { kind: "directive", type: HlmToggleGroup, selector: "[hlmToggleGroup],hlm-toggle-group", inputs: ["variant", "size", "spacing", "orientation"] }, { kind: "directive", type: HlmToggleGroupItem, selector: "button[hlmToggleGroupItem]", inputs: ["variant", "size"] }, { kind: "component", type: EgFormField, selector: "eg-form-field", inputs: ["class"] }] });
|
|
92
122
|
}
|
|
93
123
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormToggleGroup, decorators: [{
|
|
94
124
|
type: Component,
|
|
@@ -100,9 +130,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
100
130
|
},
|
|
101
131
|
imports: [ReactiveFormsModule, HlmError, HlmHint, HlmLabel, HlmToggleGroup, HlmToggleGroupItem, EgFormField],
|
|
102
132
|
providers: [{ provide: ControlContainer, useExisting: FormGroupDirective }],
|
|
103
|
-
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let
|
|
133
|
+
template: "\n @let lbl = label();\n @let cnm = controlName();\n @let hnt = hint();\n @let rqrd = required();\n\n <eg-form-field>\n @if (lbl) {\n <span hlmLabel [class]=\"$labelClass()\"\n >{{ lbl }}\n @if (rqrd) {\n <span class=\"text-red-500\">*</span>\n }\n </span>\n }\n\n <hlm-toggle-group\n [formControlName]=\"cnm\"\n [type]=\"type()\"\n [disabled]=\"effectiveDisabled()\"\n [variant]=\"variant()\"\n [size]=\"size()\"\n [class]=\"$groupClass()\"\n >\n @for (option of options(); track option.value) {\n <button\n hlmToggleGroupItem\n type=\"button\"\n [value]=\"option.value\"\n [disabled]=\"option.disabled ?? false\"\n [attr.aria-describedby]=\"describedBy()\"\n >\n {{ option.label }}\n </button>\n }\n </hlm-toggle-group>\n\n @if (showError()) {\n <hlm-error [id]=\"errorId()\" [class]=\"$errorClass()\">\n {{ resolvedError() }}\n </hlm-error>\n }\n\n @if (hnt) {\n <hlm-hint [id]=\"hintId()\" [class]=\"$hintClass()\">\n {{ hnt }}\n </hlm-hint>\n }\n </eg-form-field>\n ",
|
|
104
134
|
}]
|
|
105
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], groupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
135
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], controlId: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlId", required: false }] }], controlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlName", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], autoError: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoError", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], labelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelClass", required: false }] }], groupClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupClass", required: false }] }], errorClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorClass", required: false }] }], hintClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "hintClass", required: false }] }] } });
|
|
106
136
|
|
|
107
137
|
/*
|
|
108
138
|
* Public API Surface of form-toggle-group
|
|
@@ -9,12 +9,20 @@ interface ToggleGroupOption {
|
|
|
9
9
|
}
|
|
10
10
|
declare class EgFormToggleGroup {
|
|
11
11
|
private readonly formGroupDirective;
|
|
12
|
+
private readonly _errorMessages;
|
|
12
13
|
private readonly generatedId;
|
|
13
14
|
private readonly _config;
|
|
15
|
+
private readonly _shared;
|
|
14
16
|
label: _angular_core.InputSignal<string | undefined>;
|
|
15
17
|
controlId: _angular_core.InputSignal<string | undefined>;
|
|
16
18
|
controlName: _angular_core.InputSignal<string>;
|
|
17
19
|
error: _angular_core.InputSignal<string | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Auto-resolve the displayed message from the control's `ValidationErrors`
|
|
22
|
+
* when `error()` is unset. Explicit `error()` always wins. Set to `false`
|
|
23
|
+
* for manual-only messages.
|
|
24
|
+
*/
|
|
25
|
+
autoError: _angular_core.InputSignal<boolean>;
|
|
18
26
|
hint: _angular_core.InputSignal<string | undefined>;
|
|
19
27
|
id: _angular_core.InputSignal<string | undefined>;
|
|
20
28
|
type: _angular_core.InputSignal<"single" | "multiple">;
|
|
@@ -26,6 +34,19 @@ declare class EgFormToggleGroup {
|
|
|
26
34
|
readonly effectiveId: _angular_core.Signal<string>;
|
|
27
35
|
readonly errorId: _angular_core.Signal<string>;
|
|
28
36
|
readonly hintId: _angular_core.Signal<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Displayed message: explicit `error()` wins; otherwise (when `autoError()`)
|
|
39
|
+
* auto-resolved from the control's `ValidationErrors`. Plain method (not a
|
|
40
|
+
* computed): `ValidationErrors` is not a signal, so the control must be read
|
|
41
|
+
* fresh on every change-detection pass.
|
|
42
|
+
*/
|
|
43
|
+
protected resolvedError(): string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the error is surfaced: message present + control invalid +
|
|
46
|
+
* touched/dirty/submitted. Gates the visual `<hlm-error>` (and
|
|
47
|
+
* `describedBy()`, where present) so sighted and screen-reader output agree.
|
|
48
|
+
*/
|
|
49
|
+
protected showError(): boolean;
|
|
29
50
|
/**
|
|
30
51
|
* Combined lock for the brain `linkedSignal`-based disabled state: the raw
|
|
31
52
|
* input write always carries the resolved value, so a same-pass
|
|
@@ -44,7 +65,7 @@ declare class EgFormToggleGroup {
|
|
|
44
65
|
$errorClass: _angular_core.Signal<string>;
|
|
45
66
|
$hintClass: _angular_core.Signal<string>;
|
|
46
67
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgFormToggleGroup, never>;
|
|
47
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormToggleGroup, "eg-form-toggle-group", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "groupClass": { "alias": "groupClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
68
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgFormToggleGroup, "eg-form-toggle-group", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "controlName": { "alias": "controlName"; "required": false; "isSignal": true; }; "error": { "alias": "error"; "required": false; "isSignal": true; }; "autoError": { "alias": "autoError"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "labelClass": { "alias": "labelClass"; "required": false; "isSignal": true; }; "groupClass": { "alias": "groupClass"; "required": false; "isSignal": true; }; "errorClass": { "alias": "errorClass"; "required": false; "isSignal": true; }; "hintClass": { "alias": "hintClass"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
interface EgFormToggleGroupConfig {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@egose/shadcn-theme-ng",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Angular shadcn UI theme",
|
|
5
5
|
"author": "Junmin Ahn",
|
|
6
6
|
"bundles": [
|
|
@@ -350,6 +350,10 @@
|
|
|
350
350
|
"types": "./spinner/types/spinner.d.ts",
|
|
351
351
|
"default": "./spinner/fesm2022/spinner.mjs"
|
|
352
352
|
},
|
|
353
|
+
"./stepper": {
|
|
354
|
+
"types": "./stepper/types/stepper.d.ts",
|
|
355
|
+
"default": "./stepper/fesm2022/stepper.mjs"
|
|
356
|
+
},
|
|
353
357
|
"./switch": {
|
|
354
358
|
"types": "./switch/types/switch.d.ts",
|
|
355
359
|
"default": "./switch/fesm2022/switch.mjs"
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# Stepper (`@egose/shadcn-theme-ng/stepper`)
|
|
2
|
+
|
|
3
|
+
Multi-step workflow navigation, ported from the spartan stepper blocks. This subpath ships seven pieces — `HlmStepper` (root), `HlmStep` (step), `HlmStepHeader` (clickable header with indicator), `HlmStepLabel` (rich label template), `HlmStepContent` (lazy body), `HlmStepperNext` / `HlmStepperPrevious` (navigation buttons) — plus `provideHlmStepperConfig` / `injectHlmStepperConfig` for global defaults. All are thin shadcn-styled components over Angular CDK's `@angular/cdk/stepper` primitives (`CdkStepper`, `CdkStep`, `CdkStepHeader`, `CdkStepLabel`).
|
|
4
|
+
|
|
5
|
+
> **Ships as:** `@egose/shadcn-theme-ng/stepper` and `@egose/shadcn-theme-ng-tw/stepper` (the `tw:`-prefixed Tailwind variant). See the [package README](../../README.md) for install steps, peer dependencies, Tailwind setup, and testing/release guidance. Do not publish this project directory independently.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Plain Tailwind (no prefix)
|
|
11
|
+
npm install @egose/shadcn-theme-ng
|
|
12
|
+
|
|
13
|
+
# Or the tw:-prefixed variant
|
|
14
|
+
npm install @egose/shadcn-theme-ng-tw
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`@angular/cdk` (stepper, portal) arrives via the package peer dependencies. See the [package README](../../README.md) for the full peer-dependency table.
|
|
18
|
+
|
|
19
|
+
## Imports
|
|
20
|
+
|
|
21
|
+
All public symbols are re-exported from `projects/stepper/src/public-api.ts`:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
HlmStep,
|
|
26
|
+
HlmStepContent,
|
|
27
|
+
HlmStepHeader,
|
|
28
|
+
HlmStepLabel,
|
|
29
|
+
HlmStepper,
|
|
30
|
+
HlmStepperImports,
|
|
31
|
+
HlmStepperModule,
|
|
32
|
+
HlmStepperNext,
|
|
33
|
+
HlmStepperPrevious,
|
|
34
|
+
injectHlmStepperConfig,
|
|
35
|
+
provideHlmStepperConfig,
|
|
36
|
+
} from '@egose/shadcn-theme-ng/stepper';
|
|
37
|
+
// tw variant: swap to '@egose/shadcn-theme-ng-tw/stepper'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Standalone-component usage (preferred):
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { Component } from '@angular/core';
|
|
44
|
+
import { HlmStepperImports } from '@egose/shadcn-theme-ng/stepper';
|
|
45
|
+
|
|
46
|
+
@Component({
|
|
47
|
+
selector: 'app-demo',
|
|
48
|
+
standalone: true,
|
|
49
|
+
imports: [...HlmStepperImports],
|
|
50
|
+
template: `<!-- stepper markup here -->`,
|
|
51
|
+
})
|
|
52
|
+
export class DemoComponent {}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
NgModule usage:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { NgModule } from '@angular/core';
|
|
59
|
+
import { HlmStepperModule } from '@egose/shadcn-theme-ng/stepper';
|
|
60
|
+
|
|
61
|
+
@NgModule({ imports: [HlmStepperModule] })
|
|
62
|
+
export class FeatureModule {}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Symbol | Kind | Description |
|
|
66
|
+
| ------------------------ | ------------- | ------------------------------------------------------------------------ |
|
|
67
|
+
| `HlmStepper` | Component | Root: `<hlm-stepper>` — extends `CdkStepper`. |
|
|
68
|
+
| `HlmStep` | Component | Step: `<hlm-step label="…">` — extends `CdkStep`, adds `icon` input. |
|
|
69
|
+
| `HlmStepHeader` | Component | Header: `<hlm-step-header>` — extends `CdkStepHeader`, renders indicator. |
|
|
70
|
+
| `HlmStepLabel` | Directive | Rich label: `<ng-template hlmStepLabel>` — extends `CdkStepLabel`. |
|
|
71
|
+
| `HlmStepContent` | Directive | Lazy body: `<ng-template hlmStepContent>` — attached on first selection. |
|
|
72
|
+
| `HlmStepperNext` | Directive | Next button: `button[hlmStepperNext]` — extends `CdkStepperNext`. |
|
|
73
|
+
| `HlmStepperPrevious` | Directive | Back button: `button[hlmStepperPrevious]` — extends `CdkStepperPrevious`. |
|
|
74
|
+
| `HlmStepperImports` | `const` array | All seven, spread into `imports: [...]`. |
|
|
75
|
+
| `HlmStepperModule` | NgModule | Imports + re-exports all seven. |
|
|
76
|
+
| `provideHlmStepperConfig`| Function | Global defaults for animations + indicator mode. |
|
|
77
|
+
| `injectHlmStepperConfig` | Function | Reads the (optionally provided) global config. |
|
|
78
|
+
|
|
79
|
+
## Anatomy / Structure
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<hlm-stepper>
|
|
83
|
+
<hlm-step label="Step One">
|
|
84
|
+
<p>Content 1</p>
|
|
85
|
+
<button hlmBtn hlmStepperNext>Next</button>
|
|
86
|
+
</hlm-step>
|
|
87
|
+
|
|
88
|
+
<hlm-step label="Step Two">
|
|
89
|
+
<p>Content 2</p>
|
|
90
|
+
<button hlmBtn variant="outline" hlmStepperPrevious>Back</button>
|
|
91
|
+
<button hlmBtn hlmStepperNext>Next</button>
|
|
92
|
+
</hlm-step>
|
|
93
|
+
|
|
94
|
+
<hlm-step label="Step Three">
|
|
95
|
+
<p>Content 3</p>
|
|
96
|
+
<button hlmBtn variant="outline" hlmStepperPrevious>Back</button>
|
|
97
|
+
<button hlmBtn>Finish</button>
|
|
98
|
+
</hlm-step>
|
|
99
|
+
</hlm-stepper>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Rich label via template (takes precedence over the `label` string):
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<hlm-step [stepControl]="form">
|
|
106
|
+
<ng-template hlmStepLabel>Security</ng-template>
|
|
107
|
+
<!-- … -->
|
|
108
|
+
</hlm-step>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Lazy body (attached only when the step is first selected):
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<hlm-step label="Analytics">
|
|
115
|
+
<ng-template hlmStepContent>
|
|
116
|
+
<p>Heavy content…</p>
|
|
117
|
+
</ng-template>
|
|
118
|
+
</hlm-step>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## API reference
|
|
122
|
+
|
|
123
|
+
### `HlmStepper` — selector `hlm-stepper` (component, extends `CdkStepper`)
|
|
124
|
+
|
|
125
|
+
| Input | Type | Default | Description |
|
|
126
|
+
| -------------------- | ----------------------------- | ---------- | ------------------------------------------------------------------ |
|
|
127
|
+
| `orientation` | forwarded to `CdkStepper` | — | `'horizontal' \| 'vertical'`. |
|
|
128
|
+
| `linear` | forwarded to `CdkStepper` | — | Current step must be valid (via `stepControl`) before advancing. |
|
|
129
|
+
| `selectedIndex` | forwarded to `CdkStepper` | — | Controlled selected-step index. |
|
|
130
|
+
| `labelPosition` | `'end' \| 'bottom'` | `'end'` | Horizontal layout: label beside vs. below the indicator. |
|
|
131
|
+
| `headerPosition` | `'top' \| 'bottom'` | `'top'` | Horizontal layout: headers above vs. below the content panel. |
|
|
132
|
+
| `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering (see below). |
|
|
133
|
+
| `stepperAriaLabel` | `string \| null` | `'Progress'` | `aria-label` for the tablist (ignored when labelledby is set). |
|
|
134
|
+
| `stepperAriaLabelledby` | `string \| null` | `null` | `aria-labelledby` for the tablist. |
|
|
135
|
+
| `animationsEnabled` | `boolean` | from config (`true`) | Toggle step transitions per instance. |
|
|
136
|
+
| `animationDuration` | `number` (ms) | from config (`300`) | Transition duration per instance. |
|
|
137
|
+
|
|
138
|
+
`next()` is overridden to `markAllAsTouched()` + `updateValueAndValidity()` on the current `stepControl` first, so linear steppers surface validation errors before denying the transition.
|
|
139
|
+
|
|
140
|
+
### `HlmStep` — selector `hlm-step` (component, extends `CdkStep`)
|
|
141
|
+
|
|
142
|
+
| Input | Type | Default | Description |
|
|
143
|
+
| ------------- | ---------------- | ------- | -------------------------------------------------------- |
|
|
144
|
+
| `label` | forwarded to `CdkStep` | — | Step title (string, or rich template via `hlmStepLabel`). |
|
|
145
|
+
| `icon` | `string \| null` | `null` | Icon name rendered when `indicatorMode="icon"`. |
|
|
146
|
+
| `stepControl` | forwarded to `CdkStep` | — | Form group driving `linear` validation. |
|
|
147
|
+
| `optional` | forwarded to `CdkStep` | — | Shows an "Optional" caption in the header. |
|
|
148
|
+
| `errorMessage`| forwarded to `CdkStep` | — | Header error caption when the step state is `error`. |
|
|
149
|
+
| `hasError` | forwarded to `CdkStep` | — | Forces the error state (pair with `STEPPER_GLOBAL_OPTIONS` `showError`). |
|
|
150
|
+
|
|
151
|
+
### `HlmStepHeader` — selector `hlm-step-header` (component, extends `CdkStepHeader`)
|
|
152
|
+
|
|
153
|
+
Usually rendered by `HlmStepper` itself; use directly only for custom layouts.
|
|
154
|
+
|
|
155
|
+
| Input | Type | Default | Description |
|
|
156
|
+
| --------------- | ------------------------------------------- | ------- | -------------------------------------------- |
|
|
157
|
+
| `index` | `number` | `0` | Step index (drives the number indicator). |
|
|
158
|
+
| `state` | `StepState` | `'number'` | CDK step state (`number`, `edit`, `done`, `error`, …). |
|
|
159
|
+
| `label` | `HlmStepLabel \| string \| null` | `null` | Resolved label content. |
|
|
160
|
+
| `selected` / `reached` / `active` | `boolean` | `false` | Visual state flags. |
|
|
161
|
+
| `optional` / `disabled` | `boolean` | `false` | Optional caption / disabled styling. |
|
|
162
|
+
| `icon` | `string \| null` | `null` | Icon name for `indicatorMode="icon"`. |
|
|
163
|
+
| `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering. |
|
|
164
|
+
| `labelPosition` | `'end' \| 'bottom'` | `'end'` | Label beside vs. below the indicator. |
|
|
165
|
+
| `errorMessage` | `string` | `''` | Error caption shown when `state === 'error'`.|
|
|
166
|
+
|
|
167
|
+
Indicator modes: `number` always renders `1, 2, 3`; `state` (default) renders a check icon for selected/reached steps and an alert icon for errors; `icon` renders the per-step `icon` name (provide icons via `@ng-icons/core` `provideIcons` in your app).
|
|
168
|
+
|
|
169
|
+
### `HlmStepperNext` / `HlmStepperPrevious` — `button[hlmStepperNext]` / `button[hlmStepperPrevious]`
|
|
170
|
+
|
|
171
|
+
No inputs/outputs. Thin extensions of `CdkStepperNext` / `CdkStepperPrevious` (sets `type` binding + `touch-action: manipulation`). Pair with `hlmBtn` for styling.
|
|
172
|
+
|
|
173
|
+
### Global config
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { provideHlmStepperConfig } from '@egose/shadcn-theme-ng/stepper';
|
|
177
|
+
|
|
178
|
+
@Component({
|
|
179
|
+
// ...
|
|
180
|
+
providers: [provideHlmStepperConfig({ animationEnabled: true, animationDuration: 300, defaultIndicatorMode: 'state' })],
|
|
181
|
+
})
|
|
182
|
+
export class FeatureComponent {}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Per-instance `animationsEnabled`, `animationDuration`, and `indicatorMode` inputs override the global defaults.
|
|
186
|
+
|
|
187
|
+
## Examples
|
|
188
|
+
|
|
189
|
+
### 1. Basic horizontal stepper
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { Component } from '@angular/core';
|
|
193
|
+
import { HlmButtonImports } from '@egose/shadcn-theme-ng/button';
|
|
194
|
+
import { HlmStepperImports } from '@egose/shadcn-theme-ng/stepper';
|
|
195
|
+
|
|
196
|
+
@Component({
|
|
197
|
+
selector: 'demo-basic',
|
|
198
|
+
standalone: true,
|
|
199
|
+
imports: [...HlmStepperImports, ...HlmButtonImports],
|
|
200
|
+
template: `
|
|
201
|
+
<hlm-stepper>
|
|
202
|
+
<hlm-step label="Step One">
|
|
203
|
+
<p>Content 1</p>
|
|
204
|
+
<button hlmBtn hlmStepperNext>Next</button>
|
|
205
|
+
</hlm-step>
|
|
206
|
+
<hlm-step label="Step Two">
|
|
207
|
+
<p>Content 2</p>
|
|
208
|
+
<button hlmBtn variant="outline" hlmStepperPrevious>Back</button>
|
|
209
|
+
<button hlmBtn hlmStepperNext>Next</button>
|
|
210
|
+
</hlm-step>
|
|
211
|
+
<hlm-step label="Step Three">
|
|
212
|
+
<p>Content 3</p>
|
|
213
|
+
<button hlmBtn variant="outline" hlmStepperPrevious>Back</button>
|
|
214
|
+
<button hlmBtn>Finish</button>
|
|
215
|
+
</hlm-step>
|
|
216
|
+
</hlm-stepper>
|
|
217
|
+
`,
|
|
218
|
+
})
|
|
219
|
+
export class DemoBasic {}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 2. Vertical orientation
|
|
223
|
+
|
|
224
|
+
```html
|
|
225
|
+
<hlm-stepper orientation="vertical">
|
|
226
|
+
<hlm-step label="Campaign"><!-- … --></hlm-step>
|
|
227
|
+
<hlm-step label="Audience"><!-- … --></hlm-step>
|
|
228
|
+
<hlm-step label="Review"><!-- … --></hlm-step>
|
|
229
|
+
</hlm-stepper>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Use vertical when content is dense or horizontal space is limited.
|
|
233
|
+
|
|
234
|
+
### 3. Responsive orientation via `BreakpointObserver`
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
238
|
+
import { Component, computed, inject } from '@angular/core';
|
|
239
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
240
|
+
import { map } from 'rxjs/operators';
|
|
241
|
+
|
|
242
|
+
@Component({ /* … */ })
|
|
243
|
+
export class DemoResponsive {
|
|
244
|
+
private readonly _breakpoints = inject(BreakpointObserver);
|
|
245
|
+
private readonly _isSmall = toSignal(
|
|
246
|
+
this._breakpoints.observe('(max-width: 767.98px)').pipe(map((s) => s.matches)),
|
|
247
|
+
{ initialValue: false },
|
|
248
|
+
);
|
|
249
|
+
protected readonly orientation = computed(() => (this._isSmall() ? 'vertical' : 'horizontal'));
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```html
|
|
254
|
+
<hlm-stepper [orientation]="orientation()"><!-- … --></hlm-stepper>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 4. Linear stepper with validation
|
|
258
|
+
|
|
259
|
+
With `linear`, the current step must be valid before advancing — `HlmStepper.next()` marks the step form touched first:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { Component, inject } from '@angular/core';
|
|
263
|
+
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
264
|
+
|
|
265
|
+
@Component({ /* …, imports: [ReactiveFormsModule, ...HlmStepperImports, ...HlmButtonImports] */ })
|
|
266
|
+
export class DemoLinear {
|
|
267
|
+
private readonly _fb = inject(FormBuilder);
|
|
268
|
+
protected readonly identity = this._fb.group({ name: ['', Validators.required] });
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```html
|
|
273
|
+
<hlm-stepper [linear]="true">
|
|
274
|
+
<hlm-step [stepControl]="identity" label="Identity">
|
|
275
|
+
<form [formGroup]="identity">
|
|
276
|
+
<input hlmInput formControlName="name" placeholder="Required before continuing" />
|
|
277
|
+
<button hlmBtn hlmStepperNext>Next</button>
|
|
278
|
+
</form>
|
|
279
|
+
</hlm-step>
|
|
280
|
+
<hlm-step label="Review"><!-- … --></hlm-step>
|
|
281
|
+
</hlm-stepper>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 5. Error states
|
|
285
|
+
|
|
286
|
+
Enable `showError` through `STEPPER_GLOBAL_OPTIONS` to surface a step's error message in its header:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper';
|
|
290
|
+
|
|
291
|
+
@Component({
|
|
292
|
+
providers: [{ provide: STEPPER_GLOBAL_OPTIONS, useValue: { showError: true } }],
|
|
293
|
+
/* … */
|
|
294
|
+
})
|
|
295
|
+
export class DemoError {}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
```html
|
|
299
|
+
<hlm-step
|
|
300
|
+
[stepControl]="contact"
|
|
301
|
+
[hasError]="contact.invalid && contact.touched"
|
|
302
|
+
errorMessage="Enter a valid work email before continuing."
|
|
303
|
+
label="Contact"
|
|
304
|
+
>
|
|
305
|
+
<!-- … -->
|
|
306
|
+
</hlm-step>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### 6. Layout controls
|
|
310
|
+
|
|
311
|
+
```html
|
|
312
|
+
<hlm-stepper labelPosition="bottom" headerPosition="bottom"><!-- … --></hlm-stepper>
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`labelPosition` moves horizontal labels below the indicator; `headerPosition="bottom"` moves the header row below the content panel.
|
|
316
|
+
|
|
317
|
+
### 7. Animation controls
|
|
318
|
+
|
|
319
|
+
```html
|
|
320
|
+
<hlm-stepper [animationsEnabled]="true" [animationDuration]="300"><!-- … --></hlm-stepper>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Or set global defaults with `provideHlmStepperConfig` and override per instance.
|
|
324
|
+
|
|
325
|
+
### 8. Indicator modes
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<hlm-stepper indicatorMode="number"><!-- always 1, 2, 3 --></hlm-stepper>
|
|
329
|
+
<hlm-stepper indicatorMode="state"><!-- default: check / alert icons --></hlm-stepper>
|
|
330
|
+
<hlm-stepper indicatorMode="icon">
|
|
331
|
+
<hlm-step label="Profile" icon="lucideUser"><!-- … --></hlm-step>
|
|
332
|
+
</hlm-stepper>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Per-step `icon` names resolve through `@ng-icons/core` — register them with `provideIcons` in your app.
|
|
336
|
+
|
|
337
|
+
## Accessibility notes
|
|
338
|
+
|
|
339
|
+
- Horizontal headers get `role="tab"` semantics inside a `role="tablist"` (labelled `Progress` by default — override with `stepperAriaLabel` / `stepperAriaLabelledby`); panels get `role="tabpanel"` wired via `aria-labelledby` / `aria-controls`.
|
|
340
|
+
- Vertical headers use `role="button"` with `aria-expanded` / `aria-current="step"`.
|
|
341
|
+
- Keyboard: `HlmStepper` inherits CDK arrow-key navigation (`_onKeydown`). Headers are focusable via roving `tabIndex`.
|
|
342
|
+
- Disabled steps (`linear` + not navigable) get `aria-disabled` and `data-disabled` styling — explain in the label why a step is unavailable.
|
|
343
|
+
- In `linear` mode, failed validation marks fields touched so screen readers announce the errors.
|
|
344
|
+
|
|
345
|
+
## Theming / CSS variables
|
|
346
|
+
|
|
347
|
+
Headers follow `buttonVariants({ size: 'icon-sm' })` (`default` when selected/reached, `outline` otherwise, `destructive` on error) with `data-disabled` opacity handling. Connectors use `bg-primary` once reached, `bg-border` otherwise. Follows your shadcn theme automatically; extend via `class` (merged through `classes()` on headers) or the layout/indicator inputs.
|
|
348
|
+
|
|
349
|
+
## Related subpaths
|
|
350
|
+
|
|
351
|
+
- `@egose/shadcn-theme-ng/button` — `buttonVariants` powers the step indicators; `hlmBtn` styles the nav buttons.
|
|
352
|
+
- `@egose/shadcn-theme-ng/input` / `@egose/shadcn-theme-ng/label` / `@egose/shadcn-theme-ng/field` — form controls inside linear steps.
|
|
353
|
+
- `@egose/shadcn-theme-ng/card` — rich content panels.
|