Skip to content

feature(mat-experimental/mat-feature-highlight): Port Feature Highlight #18170

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

Closed
wants to merge 1 commit into from
Closed

feature(mat-experimental/mat-feature-highlight): Port Feature Highlight #18170

wants to merge 1 commit into from

Conversation

jermowery
Copy link
Contributor

This PR adds the feature highlight component that Google Domains uses to angular/components.
This is a straight port with minimal changes made so that the current usages can be more easily migrated.

Initial design for this component was approved by @jelbourn internally.

Once this is submitted and synced I will migrate Google Domains usages to this component.

to open source Angular from Google Domains code.
@jermowery jermowery requested review from jelbourn and a team as code owners January 14, 2020 17:01
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Jan 14, 2020
@jermowery
Copy link
Contributor Author

@jelbourn there seems to be some kind of lint check failing for a code owner for this. Who should I assign as the code owner?

@jelbourn jelbourn added feature This issue represents a new feature or feature request rather than a bug or bug fix G This is is related to a Google internal issue target: minor This PR is targeted for the next minor release labels Jan 14, 2020
@jelbourn jelbourn added this to the 9.1.0 milestone Jan 14, 2020
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass of comments

For the code owner, feel free to put yourself if you want and me

* refer to left and right in an LTR context and vice versa in an RTL context.
*/
export type FeatureHighlightCalloutPosition =
'top_start'|'top_end'|'bottom_start'|'bottom_end';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of Angular Material uses hyphens for word separators in string literal types, so we should be consistent here

(here and for other string literal types below)

import {BasePortalOutlet, CdkPortalOutlet, ComponentPortal, TemplatePortal} from '@angular/cdk/portal';
import {Component, ComponentRef, EmbeddedViewRef, OnDestroy, ViewChild} from '@angular/core';

/** Container for the callout component of feature highlight. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you expand this description a bit to explain how the component is used?

@@ -0,0 +1 @@
<ng-template cdkPortalOutlet></ng-template>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: could inline this since it's just one line


private _assertNotAttached() {
if (this.portalOutlet.hasAttached()) {
throw new Error(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we omit the new on Error since it does the same thing without it (which is different from the internal style guide)

(here and elsewhere)

* Left value of the callout, relative to the target element. Used only when
* isOuterCircleBounded is not true.
*/
readonly calloutLeft?: string|number;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than left/right, we try to use start/end so that the name applies in both LTR and RTL. Would it make sense for this to be something like calloutStartCoordinate?

* feature highlight elements.
*/
@Directive({
selector: `[feature-highlight-target],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selectors and class name here should start with Mat, as well as any associated classes that apply to this class.

I also debated with myself about omitting "target" from the selector so it's more like [matTooltip], but then the class name would need to match, and then it would conflict with the service name. Another alternative would be something like [matHighlightedFeature], but I'm not sure if it's too similar and would be confusing. @angular/angular-components what do you all think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to get a better feel for how this component and related directives worked before providing feedback on this, but I couldn't find any examples in this PR.

@Directive({
selector: `[feature-highlight-target],
[featureHighlightTarget],
feature-highlight-target`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific case for the element selector, or could we exclusively use the attribute selector?

},
})
export class FeatureHighlightTarget {
@Input() featureHighlightEnabled = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do directive inputs with alises so that the class member stays short, but the template name is unambiguous:

@Input('matFeatureHighlightTargetDisabled')
disabled: boolean;

},
})
export class FeatureHighlightTarget {
@Input() featureHighlightEnabled = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer using disabled that defaults to false rather than enabled that defaults to true. For boolean inputs, we also always use a setter so we can use coerceBooleanProperty from cdk/coercion. This lets users apply the value like <button matFeatureHighlightTargetDisabled> without a value, the same as the native disabled, required, etc.

*/
get zIndex(): string|null {
return this.featureHighlightEnabled ? '1001' : null;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer using methods rather than getters for anything that doesn't need to be a bindable property since the getter generates a bit more es5 code.

Copy link
Contributor

@Splaktar Splaktar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a link to an example where this component is used?
If not, is it possible to add an example and include it in the src/dev-app?

Examples normally go in src/components-examples/material-experimental and are later moved to src/components-examples/material when the component moves out of experimental.

Some kind of basic explanation in a .md file would be helpful as well since GitHub doesn't really organize these files in any kind of way to put the most helpful comments at the top. 😀

* feature highlight elements.
*/
@Directive({
selector: `[feature-highlight-target],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to get a better feel for how this component and related directives worked before providing feedback on this, but I couldn't find any examples in this PR.

}

/**
* Open a feature highlight with callout as a component.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not 100% clear to me what a "callout" is. Perhaps this wouldn't be an issue if there was a feature-highlight.md file with some details about what the component does and how the different parts (and vocab) fit together.

});

TestBed.compileComponents();
fixture = TestBed.createComponent(CalloutHostComponent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you switch the tests so each individual one creates the component itself? Creating it like this in a beforeEach makes it difficult to add new tests that use a different template. You can see an example in drag.spec.ts.

`,
})
class CalloutHostComponent {
@ViewChild('calloutContainer', {static: true})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid adding new code that depends on static queries. You can avoid it in this case by calling detectChanges after creating the test component.

/** Container for the callout component of feature highlight. */
@Component({
selector: 'feature-highlight-callout-container',
templateUrl: './feature-highlight-callout-container.ng.html',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a lint rule for this, but I think that it that previous failures meant that the CI never got far enough to log it.

* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mat- prefix should be removed from the directory name.

/** Container for the callout component of feature highlight. */
@Component({
selector: 'feature-highlight-callout-container',
templateUrl: './feature-highlight-callout-container.ng.html',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the .ng from the file name.

@Output() afterDismissed = new EventEmitter<void>();

@ViewChild('rootDiv', {static: true}) rootDiv!: ElementRef<HTMLElement>;
@ViewChild('innerCircle', {static: true})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid adding new code with static queries.

* is equal to the diagonal length of the box, should the diameter be auto
* determined.
*/
private _positionOuterCircle() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is somewhat long. Can it be broken up into a couple of helpers?

private _changeAnimationState(newState: State) {
requestAnimationFrame(() => {
this.state = newState;
this._changeDetectorRef.markForCheck();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is redundant since requestAnimationFrame triggers change detection.


// TODO(b/138400499) - Ideally we should emit afterOpened event when the
// transitionend is triggered. However it doesn't look like catalyst flush()
// triggers the transitionend event so there is no way to test the behavior.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior can be tested with the CDK. You can check out some tests in drag.spec.ts that flush transitionend.

FeatureHighlightCalloutContainer,
],
})
export class FeatureHighlightModule {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name should be prefixed with Mat.

@josephperrott josephperrott removed the request for review from a team April 29, 2021 15:59
@devversion devversion removed their request for review August 18, 2021 13:01
@andrewseguin andrewseguin removed the cla: yes PR author has agreed to Google's Contributor License Agreement label Dec 28, 2021
@andrewseguin
Copy link
Contributor

Closing due to inactivity after review was provided. If you'd like to work on landing this, feel free to re-open a PR with the comments addressed

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Apr 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature This issue represents a new feature or feature request rather than a bug or bug fix G This is is related to a Google internal issue target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants