-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(stepper): Create MAT_STEPPER_GLOBAL_OPTIONS InjectionToken #11457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
cc/ @mmalerba, this is part one of the PR from our conversation on lifecycle hooks and state. |
I think we need a design doc on this first, specifically focusing on how we make these changes in a way that causes as few breaking changes as possible |
Hi @arodr967! This PR has merge conflicts due to recent upstream merges. |
2 similar comments
Hi @arodr967! This PR has merge conflicts due to recent upstream merges. |
Hi @arodr967! This PR has merge conflicts due to recent upstream merges. |
@mmalerba can you take a peek at this when you get a chance. |
@mmalerba Any update on this? Is there anything that I need to do? Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arodr967 sorry! I wrote some comments and forgot to send them
e2e/components/stepper-e2e.spec.ts
Outdated
@@ -73,7 +73,7 @@ describe('stepper', () => { | |||
nextButton.get(0).click(); | |||
|
|||
expect(await element(by.css('mat-step-header[aria-selected="true"]')).getText()) | |||
.toBe('1\nFill out your name'); | |||
.toBe('create\nFill out your name'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't these changes indicative of a breaking change of some kind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
The
completed
andeditable
inputs have no breaking change, they will still work as before. (I need to addcompleted
back as an Input). -
hasError
would create a breaking change; therefore, I will be adding ashowError
input which will allow devs to have the option to display the error or not and by default it will be set tofalse
, instead of doing it automatically as I am now. So this would be an enhancement. -
The changes in functionality are now in regards to the done and edit icons. This is because before it would always show the edit icon, regardless of the step being completed. And now, instead of showing the edit icon when it's not the current step, it's only showing the edit icon when it is the current step (which is why the tests were changed). If you think that we should also keep the way it was working before then I could add some type of Input which would allow the developer to either use the new functionality or the old one (old one would be set to the default).
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than add @Input()
s for these, another option is to create an injectable MAT_STEPPER_GLOBAL_OPTIONS
. We have this for a few other components (e.g. mat-label: https://github.com/angular/material2/blob/d6fec3573cbc854d5bf67d920c5b0f8a6eef0c86/src/lib/core/label/label-options.ts). This way people could enable it for all steppers in their application simultaneously
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I am going to try that, thanks!
src/cdk/stepper/stepper.ts
Outdated
@@ -126,6 +147,19 @@ export class CdkStep implements OnChanges { | |||
return this.stepControl ? this.stepControl.valid && this.interacted : this.interacted; | |||
} | |||
|
|||
/** Whether step has error. */ | |||
get hasError(): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be an @Input
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I will also be creating a showError
input as mentioned in my other comment.
src/cdk/stepper/stepper.ts
Outdated
} | ||
private _customError: boolean | null = null; | ||
|
||
private get _defaultError() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this a method called _getDefaultError
, we don't like to use getter/setters for private properties since it generates more code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was following the pattern made for the completed attributes, where it says private get _defaultCompleted()
. What's the difference between that and private get _defaultError()
? Should they both be changed? Please advise!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other one should probably not be a getter either
src/cdk/stepper/stepper.ts
Outdated
@@ -87,6 +98,9 @@ export class CdkStep implements OnChanges { | |||
/** Plain text label of the step. */ | |||
@Input() label: string; | |||
|
|||
/** Alert message when there's an error. */ | |||
@Input() alertMessage: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
call this errorMessage
to keep the terminology consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
CLAs look good, thanks! |
src/cdk/stepper/stepper.ts
Outdated
@@ -112,6 +126,22 @@ export class CdkStep implements OnChanges { | |||
} | |||
private _optional = false; | |||
|
|||
/** State of the step. */ | |||
@Input() | |||
get state(): StepState | string | null { return this._state; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should not be any need to have an internal private version of state
if the getter and setter of state
just set and get _state
.
You should be able to just have
@Input() state: StepState | string | null = null;
Same for showError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! It is now a simple Input()
.
src/cdk/stepper/stepper.ts
Outdated
@@ -112,6 +126,22 @@ export class CdkStep implements OnChanges { | |||
} | |||
private _optional = false; | |||
|
|||
/** State of the step. */ | |||
@Input() | |||
get state(): StepState | string | null { return this._state; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a bit confused on the typings for state
, wouldn't it be best to capture all of the appropriate states in StepState
instead of allowing arbitrary string
s? Additionally, should null
be possible, is one of the StepState
s a reasonable default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense! So, I've added string
as part of the StepState
type. And correct, it should probably never be null
; therefore, I've added STEP_STATE.NUMBER
as the default. Thanks!
src/cdk/stepper/stepper.ts
Outdated
/** Whether step has error. */ | ||
@Input() | ||
get hasError(): boolean { | ||
return this._customError == null ? this._getDefaultError : this._customError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
===
is preferred to ==
If you just want to do a truthyness check in this case, you could use:
return this._customError || this._getDefaultError;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thanks!
src/cdk/stepper/stepper.ts
Outdated
} | ||
private _customError: boolean | null = null; | ||
|
||
private get _getDefaultError() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getters end up creating alot of extra boilerplate code. For something like this, can you instead have a private method?
private _getDefaultError() {
return this.stepControl && this.stepControl.invalid;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have refactored this method as well as the _getDefaultCompleted
method. Thanks!
d4d0f18
to
dd8036a
Compare
@mmalerba Hey! Please let me know if the changes that I've made so far are good so that I can write some tests and hopefully get this PR merged. Thanks! |
feat: added error state in order to display the correct icon and a default state to display any custom state/icon refactor: add StepState type refactor: refactor getIndicatorType to become more readable feat: add stepper example using a custom state feat: add error step theme feat: update getIndicatorType logic to handle a custom state feat: add alertMessage input for the step and conditionally apply error css feat: add icon override demo example and allow any icon to be overridden fix: issue with icon colors when selected or not test: update/add unit tests for the stepper changes test: update e2e tests for the stepper changes docs(stepper): update the readme to include new functionality refactor: add missing comment refactor: use mixins for stepper theme error
…dd completed and hasError as an Input, rename alertMessage to errorMessage
…e, add spacing in scss, and use .first function in tests
29bb9ca
to
ed4e121
Compare
src/lib/stepper/step-header.html
Outdated
@@ -1,6 +1,10 @@ | |||
<div class="mat-step-header-ripple" mat-ripple [matRippleTrigger]="_getHostElement()"></div> | |||
<div [class.mat-step-icon]="state !== 'number' || selected" | |||
<!-- @breaking-change 8.0.0 remove `mat-step-icon-not-touched` --> | |||
<div class="mat-step-icon" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arodr967 Hmm ok, it looks like maybe the touched class wasn't the issue after all. I'm still seeing screenshot diffs where the pencil is changed from blue to gray. Looking a little deeper I think the issue may be related to the mat-step-icon
class. It looks like you changed it from conditionally applied to always applied and also changed the styles targeted at it to no longer set the background color. I'm assuming that's where these diffs are coming from. How do you feel about changing the classes as follows:
<div class="mat-step-icon mat-step-icon-state-{{state}}"
[class.mat-step-icon-selected]="selected"
[ngSwitch]="state">
...
To me this seems nicer anyways, because each state will get its own class that users can target. If making the pencil gray by default was intentional, I can just add CSS in google to target the -state-edit
class and make it blue again. And we can probably just remove the -not-touched
class again since it seems it wasn't the cause of the problem. I can use the -state-
classes and the -selected
class to target the same elements anyways if needed.
Sorry for all the back and forth on this, sorting out the issues in google3 turned out to be more difficult than I anticipated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries!
So I am not sure mat-step-icon-state-{{state}}
would work for the custom defined states... And no, making the pencil gray by default was not intentional.
I'll get started on the changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would it not work for custom states? As long as they provide a string it should just insert it into the class name right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but I wouldn't be able to predefine the classes since there could be many different types. Or would that be something that the developer could do on their side? Define the class? 🤔
Answering my own question -- yes!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to target styles at all of them, you might want to for the predefined states like -error
but we can still automatically add the class for other states so that users can write styles for .mat-step-state-mystate
and it will just work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple way to fix the problem you're describing with the gray pencil is by just adding additional logic to mat-step-icon-selected
and mat-step-icon-not-selected
classes like so:
<div class="mat-step-icon"
[class.mat-step-icon-selected]="selected || state == 'done' || state == 'edit'"
[class.mat-step-icon-not-selected]="!selected && state != 'done' && state != 'edit'"
[class.mat-step-icon-error]="state == 'error'"
[ngSwitch]="state">
Let me know what you think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather change the classes to be like I suggested. It also makes the meaning of the -selected
class more clear. I would expect the class to reflect the selected
property, not all of these other conditions. I also think its useful for the user if we expose classes for their custom states
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll see what I can do in that case, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry took a while to get this done, been really busy! I have pushed the changes. Let me know what you think.
I had to make this !important
:
.mat-step-icon-selected,
.mat-step-icon-state-done,
.mat-step-icon-state-edit {
background-color: mat-color($primary) !important;
color: mat-color($primary, default-contrast);
}
Otherwise, the styles wouldn't get applied correctly!
src/lib/stepper/step-header.html
Outdated
@@ -1,6 +1,5 @@ | |||
<div class="mat-step-header-ripple" mat-ripple [matRippleTrigger]="_getHostElement()"></div> | |||
<div [class.mat-step-icon]="state !== 'number' || selected" | |||
[class.mat-step-icon-not-touched]="state == 'number' && !selected" | |||
<div class="mat-step-icon-{{selected ? 'selected' : 'not-selected'}} mat-step-icon-state-{{state}} mat-step-icon" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we just use .mat-icon:not(.mat-step-icon-selected)
instead of having both classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
src/lib/stepper/step-header.html
Outdated
@@ -1,5 +1,6 @@ | |||
<div class="mat-step-header-ripple" mat-ripple [matRippleTrigger]="_getHostElement()"></div> | |||
<div class="mat-step-icon-{{selected ? 'selected' : 'not-selected'}} mat-step-icon-state-{{state}} mat-step-icon" | |||
<div [ngClass]="{'mat-step-icon-selected': selected}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[class.mat-step-icon-selected]="selected"
src/lib/stepper/_stepper-theme.scss
Outdated
background-color: mat-color($foreground, disabled-text); | ||
color: mat-color($primary, default-contrast); | ||
} | ||
|
||
.mat-step-icon-state-error { | ||
background-color: transparent !important; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't have !important
anywhere in our CSS. I assume this is because you need greater specificity due to the .mat-step-icon:not(.mat-step-icon-selected)
selector? Maybe instead we can just order them so that the right one takes precedence:
// default that will apply to every icon
.mat-step-icon {
background-color: mat-color($foreground, disabled-text);
color: mat-color($primary, default-contrast);
}
// override background for icons that are selected or are in the edit/done state
.mat-step-icon-selected,
.mat-step-icon-state-done,
.mat-step-icon-state-edit {
background-color: mat-color($primary);
}
// override background and foreground for error state (will also take precedence over selected style)
.mat-step-icon-state-error {
background-color: transparent;
color: mat-color($warn);
}
I think the above is the correct order, but you should confirm. See https://www.w3schools.com/css/css_specificity.asp for more about CSS selector specificity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I was able to remove the !important
by using css specificity but it would not work with the not()
selector so I had to use the -not-selected
class again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I pushed code that I think should be equivalent to what you had but doesn't rely on the class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect!
@mmalerba Thanks for the help! |
🎉 |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Changes
Major
MAT_STEPPER_GLOBAL_OPTIONS
InjectionToken to easily configure the stepper to eithershowError
for the steps and/oruseGuidelines
to either use the existing logic or new logic to display the icons.state
input for the step so that the developer can set their own state if necessary._getIndicatorType
logic to adhere to Material UI Guidelines ifuseGuidelines
is set to true.### Breaking- Removedcompleted
as an input. However, the developer should still be able to override this attribute through the controller.This PR will resolve the following opened issues:
Acknowledgments: