diff --git a/README.md b/README.md
index ec850886a1..343c9830e6 100644
--- a/README.md
+++ b/README.md
@@ -103,5 +103,5 @@ NOTE: To update the component status:
| Text Field - Single | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/842889a5-67ba-4350-91c1-55eee48f4fa2/) | | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/docs/text-field--text-field) | :white_check_mark: | :white_check_mark: |
| Toggle Icon Button | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/d022d8af-22f4-4bf2-981c-1dc0c61afece/) | | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/story/toggle-button--icon-button) | :white_check_mark: | :white_check_mark: |
| Toolbar | | [Issue](https://github.com/ni/nimble/issues/411) | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/story/toolbar--toolbar) | :white_check_mark: | :white_check_mark: |
-| Tooltip | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/044414d7-1714-40f2-9679-2ce2c8202d1c/) | [Issue](https://github.com/ni/nimble/issues/309) | :o: | :o: | :o: |
+| Tooltip | [XD](https://xd.adobe.com/view/33ffad4a-eb2c-4241-b8c5-ebfff1faf6f6-66ac/screen/044414d7-1714-40f2-9679-2ce2c8202d1c/) | [Issue](https://github.com/ni/nimble/issues/309) | [:arrows_counterclockwise: - SB](https://ni.github.io/nimble/storybook/?path=/docs/tooltip--tooltip) | :white_check_mark: | :o: |
| Tree View | | | [:white_check_mark: - SB](https://ni.github.io/nimble/storybook/?path=/docs/tree-view--tree-view) | :white_check_mark: | :white_check_mark: |
diff --git a/angular-workspace/projects/example-client-app/src/app/app.module.ts b/angular-workspace/projects/example-client-app/src/app/app.module.ts
index 11585c3b1d..ac178a47f1 100644
--- a/angular-workspace/projects/example-client-app/src/app/app.module.ts
+++ b/angular-workspace/projects/example-client-app/src/app/app.module.ts
@@ -6,7 +6,7 @@ import { NimbleTextAreaModule, NimbleTextFieldModule, NimbleNumberFieldModule, N
NimbleButtonModule, NimbleTreeViewModule, NimbleTreeItemModule, NimbleDrawerModule, NimbleThemeProviderModule,
NimbleTabModule, NimbleTabPanelModule, NimbleTabsModule, NimbleTabsToolbarModule, NimbleMenuModule,
NimbleMenuItemModule, NimbleCheckboxModule, NimbleToggleButtonModule, NimbleBreadcrumbModule, NimbleBreadcrumbItemModule,
- NimbleIconAddModule, NimbleSwitchModule, NimbleToolbarModule, NimbleMenuButtonModule, NimbleComboboxModule, NimbleCardButtonModule } from '@ni/nimble-angular';
+ NimbleIconAddModule, NimbleSwitchModule, NimbleToolbarModule, NimbleMenuButtonModule, NimbleComboboxModule, NimbleTooltipModule, NimbleCardButtonModule } from '@ni/nimble-angular';
import { AppComponent } from './app.component';
import { CustomAppComponent } from './customapp/customapp.component';
import { LoginComponent } from './login/login.component';
@@ -50,6 +50,7 @@ import { NavDrawerComponent } from './nav-drawer/nav-drawer.component';
NimbleToolbarModule,
NimbleComboboxModule,
NimbleMenuButtonModule,
+ NimbleTooltipModule,
NimbleCardButtonModule,
RouterModule.forRoot([
{ path: '', redirectTo: '/login', pathMatch: 'full' },
diff --git a/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.html b/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.html
index 21946720e1..0d9be8005a 100644
--- a/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.html
+++ b/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.html
@@ -162,6 +162,19 @@
Last button
+
+
Tooltip
+
Default
+
Tooltip label
+
Fail
+
Tooltip label
+
Information
+
Tooltip label
+
Fail Icon
+
Tooltip label
+
Information Icon
+
Tooltip label
+
Tree View
diff --git a/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/nimble-tooltip.directive.ts b/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/nimble-tooltip.directive.ts
new file mode 100644
index 0000000000..06e65189a7
--- /dev/null
+++ b/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/nimble-tooltip.directive.ts
@@ -0,0 +1,33 @@
+import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
+import type { Tooltip } from '@ni/nimble-components/dist/esm/tooltip';
+import { TooltipStatus } from '@ni/nimble-components/dist/esm/tooltip/types';
+import { NumberValueOrAttribute, toNumberProperty } from '../utilities/template-value-helpers';
+
+export type { Tooltip };
+export { TooltipStatus };
+
+/**
+ * Directive to provide Angular integration for the tooltip.
+ */
+@Directive({
+ selector: 'nimble-tooltip'
+})
+export class NimbleTooltipDirective {
+ public get anchor(): string {
+ return this.elementRef.nativeElement.anchor;
+ }
+
+ @Input() public set anchor(value: string) {
+ this.renderer.setProperty(this.elementRef.nativeElement, 'anchor', value);
+ }
+
+ public get delay(): number {
+ return this.elementRef.nativeElement.delay;
+ }
+
+ @Input() public set delay(value: NumberValueOrAttribute) {
+ this.renderer.setProperty(this.elementRef.nativeElement, 'delay', toNumberProperty(value));
+ }
+
+ public constructor(private readonly renderer: Renderer2, private readonly elementRef: ElementRef) {}
+}
\ No newline at end of file
diff --git a/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/nimble-tooltip.module.ts b/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/nimble-tooltip.module.ts
new file mode 100644
index 0000000000..40763db322
--- /dev/null
+++ b/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/nimble-tooltip.module.ts
@@ -0,0 +1,12 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { NimbleTooltipDirective } from './nimble-tooltip.directive';
+
+import '@ni/nimble-components/dist/esm/tooltip';
+
+@NgModule({
+ declarations: [NimbleTooltipDirective],
+ imports: [CommonModule],
+ exports: [NimbleTooltipDirective]
+})
+export class NimbleTooltipModule { }
\ No newline at end of file
diff --git a/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/tests/nimble-tooltip.directive.spec.ts b/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/tests/nimble-tooltip.directive.spec.ts
new file mode 100644
index 0000000000..f51f0a6e11
--- /dev/null
+++ b/angular-workspace/projects/ni/nimble-angular/src/directives/tooltip/tests/nimble-tooltip.directive.spec.ts
@@ -0,0 +1,210 @@
+import { Component, ElementRef, ViewChild } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import type { NumberValueOrAttribute } from 'dist/ni/nimble-angular/directives/utilities/template-value-helpers';
+import { Tooltip, NimbleTooltipDirective, TooltipStatus } from '../nimble-tooltip.directive';
+import { NimbleTooltipModule } from '../nimble-tooltip.module';
+
+describe('Nimble tooltip', () => {
+ describe('module', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [NimbleTooltipModule]
+ });
+ });
+
+ it('defines custom element', () => {
+ expect(customElements.get('nimble-tooltip')).not.toBeUndefined();
+ });
+ });
+
+ describe('TooltipStatus', () => {
+ it('can use TooltipStatus values', () => {
+ // Ensure TooltipStatus is exported correctly so that it can be used
+ // as more than a type.
+ expect(TooltipStatus.information).toEqual(TooltipStatus.information);
+ });
+ });
+
+ describe('with no values in template', () => {
+ @Component({
+ template: `
+
+ `
+ })
+ class TestHostComponent {
+ @ViewChild('tooltip', { read: NimbleTooltipDirective }) public directive: NimbleTooltipDirective;
+ @ViewChild('tooltip', { read: ElementRef }) public elementRef: ElementRef;
+ }
+
+ let fixture: ComponentFixture;
+ let directive: NimbleTooltipDirective;
+ let nativeElement: Tooltip;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [TestHostComponent],
+ imports: [NimbleTooltipModule]
+ });
+ fixture = TestBed.createComponent(TestHostComponent);
+ fixture.detectChanges();
+ directive = fixture.componentInstance.directive;
+ nativeElement = fixture.componentInstance.elementRef.nativeElement;
+ });
+
+ it('has expected defaults for anchor', () => {
+ expect(directive.anchor).toBe('');
+ expect(nativeElement.anchor).toBe('');
+ });
+
+ it('has expected defaults for delay', () => {
+ expect(directive.delay).toBe(300);
+ expect(nativeElement.delay).toBe(300);
+ });
+ });
+
+ describe('with template string values', () => {
+ @Component({
+ template: `
+
+ `
+ })
+ class TestHostComponent {
+ @ViewChild('tooltip', { read: NimbleTooltipDirective }) public directive: NimbleTooltipDirective;
+ @ViewChild('tooltip', { read: ElementRef }) public elementRef: ElementRef;
+ }
+
+ let fixture: ComponentFixture;
+ let directive: NimbleTooltipDirective;
+ let nativeElement: Tooltip;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [TestHostComponent],
+ imports: [NimbleTooltipModule]
+ });
+ fixture = TestBed.createComponent(TestHostComponent);
+ fixture.detectChanges();
+ directive = fixture.componentInstance.directive;
+ nativeElement = fixture.componentInstance.elementRef.nativeElement;
+ });
+
+ it('will use template string values for anchor', () => {
+ expect(directive.anchor).toBe('anchor');
+ expect(nativeElement.anchor).toBe('anchor');
+ });
+
+ it('will use template string values for delay', () => {
+ expect(directive.delay).toBe(300);
+ expect(nativeElement.delay).toBe(300);
+ });
+ });
+
+ describe('with property bound values', () => {
+ @Component({
+ template: `
+
+
+ `
+ })
+ class TestHostComponent {
+ @ViewChild('tooltip', { read: NimbleTooltipDirective }) public directive: NimbleTooltipDirective;
+ @ViewChild('tooltip', { read: ElementRef }) public elementRef: ElementRef;
+ public anchor = 'anchor';
+ public delay = 300;
+ }
+
+ let fixture: ComponentFixture;
+ let directive: NimbleTooltipDirective;
+ let nativeElement: Tooltip;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [TestHostComponent],
+ imports: [NimbleTooltipModule]
+ });
+ fixture = TestBed.createComponent(TestHostComponent);
+ fixture.detectChanges();
+ directive = fixture.componentInstance.directive;
+ nativeElement = fixture.componentInstance.elementRef.nativeElement;
+ });
+
+ it('can be configured with property binding for anchor', () => {
+ expect(directive.anchor).toBe('anchor');
+ expect(nativeElement.anchor).toBe('anchor');
+
+ fixture.componentInstance.anchor = 'anchor2';
+ fixture.detectChanges();
+
+ expect(directive.anchor).toBe('anchor2');
+ expect(nativeElement.anchor).toBe('anchor2');
+ });
+ it('can be configured with property binding for delay', () => {
+ expect(directive.delay).toBe(300);
+ expect(nativeElement.delay).toBe(300);
+
+ fixture.componentInstance.delay = 400;
+ fixture.detectChanges();
+
+ expect(directive.delay).toBe(400);
+ expect(nativeElement.delay).toBe(400);
+ });
+ });
+
+ describe('with attribute bound values', () => {
+ @Component({
+ template: `
+
+
+ `
+ })
+ class TestHostComponent {
+ @ViewChild('tooltip', { read: NimbleTooltipDirective }) public directive: NimbleTooltipDirective;
+ @ViewChild('tooltip', { read: ElementRef }) public elementRef: ElementRef;
+ public anchor = 'anchor';
+ public delay: NumberValueOrAttribute = 300;
+ }
+
+ let fixture: ComponentFixture;
+ let directive: NimbleTooltipDirective;
+ let nativeElement: Tooltip;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [TestHostComponent],
+ imports: [NimbleTooltipModule]
+ });
+ fixture = TestBed.createComponent(TestHostComponent);
+ fixture.detectChanges();
+ directive = fixture.componentInstance.directive;
+ nativeElement = fixture.componentInstance.elementRef.nativeElement;
+ });
+
+ it('can be configured with attribute binding for anchor', () => {
+ expect(directive.anchor).toBe('anchor');
+ expect(nativeElement.anchor).toBe('anchor');
+
+ fixture.componentInstance.anchor = 'anchor2';
+ fixture.detectChanges();
+
+ expect(directive.anchor).toBe('anchor2');
+ expect(nativeElement.anchor).toBe('anchor2');
+ });
+ // Test is disabled because of [FAST bug](https://github.com/microsoft/fast/issues/6257)
+ xit('can be configured with attribute binding for delay', () => {
+ expect(directive.delay).toBe(300);
+ expect(nativeElement.delay).toBe(300);
+
+ fixture.componentInstance.delay = 400;
+ fixture.detectChanges();
+
+ expect(directive.delay).toBe(400);
+ expect(nativeElement.delay).toBe(400);
+ });
+ });
+});
diff --git a/angular-workspace/projects/ni/nimble-angular/src/public-api.ts b/angular-workspace/projects/ni/nimble-angular/src/public-api.ts
index 32740b2dec..30bdb8dc71 100644
--- a/angular-workspace/projects/ni/nimble-angular/src/public-api.ts
+++ b/angular-workspace/projects/ni/nimble-angular/src/public-api.ts
@@ -64,6 +64,8 @@ export * from './directives/tree-item/nimble-tree-item.directive';
export * from './directives/tree-item/nimble-tree-item.module';
export * from './directives/tree-view/nimble-tree-view.directive';
export * from './directives/tree-view/nimble-tree-view.module';
+export * from './directives/tooltip/nimble-tooltip.directive';
+export * from './directives/tooltip/nimble-tooltip.module';
export * from './testing/async-helpers';
// Export enums that are used by multiple components here to avoid exporting them multiple times.
diff --git a/change/@ni-nimble-angular-95820796-03e5-42c0-aec1-3dbd10c45311.json b/change/@ni-nimble-angular-95820796-03e5-42c0-aec1-3dbd10c45311.json
new file mode 100644
index 0000000000..9e7adb57e4
--- /dev/null
+++ b/change/@ni-nimble-angular-95820796-03e5-42c0-aec1-3dbd10c45311.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "Tooltip Angular Integration (#309)",
+ "packageName": "@ni/nimble-angular",
+ "email": "103057880+aidendk@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}