Skip to content

Commit daf9a0a

Browse files
feat(design): add aria-controls support to DaffMenuComponent (#4315)
Co-Authored-By: Joanna Lau <[email protected]> Co-Authored-By: Fiona Cai <[email protected]>
1 parent 01e332a commit daf9a0a

16 files changed

Lines changed: 185 additions & 24 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<daff-menu>
2+
<a href="#" daff-menu-item>
3+
<fa-icon [icon]="faUser" [fixedWidth]="true" daffPrefix></fa-icon> My
4+
Account
5+
</a>
6+
<a href="#" daff-menu-item>
7+
<fa-icon [icon]="faInfo" [fixedWidth]="true" daffPrefix></fa-icon> Help
8+
</a>
9+
<button daff-menu-item>
10+
<fa-icon [icon]="faEnvelope" [fixedWidth]="true" daffPrefix></fa-icon>
11+
Contact Us
12+
</button>
13+
</daff-menu>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
} from '@angular/core';
5+
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
6+
import {
7+
faEnvelope,
8+
faInfo,
9+
faUser,
10+
} from '@fortawesome/free-solid-svg-icons';
11+
12+
import { DaffMenuModule } from '@daffodil/design/menu';
13+
14+
@Component({
15+
selector: 'menu-with-id-content',
16+
templateUrl: './menu-content.component.html',
17+
changeDetection: ChangeDetectionStrategy.OnPush,
18+
imports: [
19+
DaffMenuModule,
20+
FaIconComponent,
21+
],
22+
})
23+
export class MenuWithIdContentExampleComponent {
24+
faUser = faUser;
25+
faInfo = faInfo;
26+
faEnvelope = faEnvelope;
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<button
2+
daff-button
3+
color="theme"
4+
[daffMenuActivator]="menu"
5+
id="menu-with-id">
6+
Menu
7+
</button>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
} from '@angular/core';
5+
6+
import { DaffButtonComponent } from '@daffodil/design/button';
7+
import { DaffMenuModule } from '@daffodil/design/menu';
8+
9+
import { MenuWithIdContentExampleComponent } from './menu-content/menu-content.component';
10+
11+
@Component({
12+
selector: 'menu-with-id-example',
13+
templateUrl: './menu-with-id.component.html',
14+
changeDetection: ChangeDetectionStrategy.OnPush,
15+
imports: [
16+
DaffButtonComponent,
17+
DaffMenuModule,
18+
],
19+
})
20+
export class MenuWithIdExampleComponent {
21+
public menu = MenuWithIdContentExampleComponent;
22+
}

libs/design-examples/menu/src/provider.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@ export const provideDaffDesignMenuExamplesContent = () => makeEnvironmentProvide
1111
id: 'menu-with-icon-toggle',
1212
component: () => import('./menu-with-icon-toggle/menu-with-icon-toggle.component').then(c => c.MenuWithIconToggleExampleComponent),
1313
},
14+
{
15+
id: 'menu-with-id',
16+
component: () => import('./menu-with-id/menu-with-id.component').then(c => c.MenuWithIdExampleComponent),
17+
},
1418
));
1519

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { BasicMenuExampleComponent } from './basic-menu/basic-menu.component';
22
export { MenuWithIconToggleExampleComponent } from './menu-with-icon-toggle/menu-with-icon-toggle.component';
3+
export { MenuWithIdExampleComponent } from './menu-with-id/menu-with-id.component';
34
export { provideDaffDesignMenuExamplesContent } from './provider';

libs/design/menu/README.md

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,12 @@ The menu activator provides an `isOpen` property that tracks whether the menu is
105105

106106
<daff-docs-example-viewer example="menu-with-icon-toggle"></daff-docs-example-viewer>
107107

108-
```html
109-
<button [daffMenuActivator]="menuContent" #menu="daffMenuActivator">
110-
Options
111-
<fa-icon [icon]="menu.isOpen() ? faChevronUp : faChevronDown"></fa-icon>
112-
</button>
113108

114-
<ng-template #menuContent>
115-
<daff-menu>
116-
<button daff-menu-item>Menu Item</button>
117-
</daff-menu>
118-
</ng-template>
119-
```
109+
### Setting an ID
110+
111+
The menu activator accepts an optional `id` input. When set, the opened menu's `id` is derived as `{id}-menu`. When no `id` is provided, a unique ID is auto-generated.
112+
113+
<daff-docs-example-viewer example="menu-with-id"></daff-docs-example-viewer>
120114

121115
## Accessibility
122116
Menu follows the [Menu and Menubar WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createConfigInjectionToken } from '@daffodil/core';
2+
3+
/**
4+
* Configuration for a menu instance.
5+
*/
6+
export interface DaffMenuConfig {
7+
/**
8+
* A unique identifier for the menu instance.
9+
*/
10+
menuId?: string;
11+
}
12+
13+
const daffMenuConfigDefault: DaffMenuConfig = {
14+
menuId: '',
15+
};
16+
17+
export const {
18+
/**
19+
* An injection token for the menu configuration.
20+
*/
21+
token: DAFF_MENU_CONFIG,
22+
/**
23+
* Provider function for {@link DAFF_MENU_CONFIG}.
24+
*/
25+
provider: provideDaffMenuConfig,
26+
} = createConfigInjectionToken<DaffMenuConfig>(daffMenuConfigDefault, 'DAFF_MENU_CONFIG');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let daffMenuUniqueId = 0;
2+
3+
/**
4+
* Generates a unique menu ID for each menu instance.
5+
*/
6+
export const daffNextMenuId = (): string =>
7+
`daff-menu-${daffMenuUniqueId++}`;

libs/design/menu/src/menu-activator/menu-activator.component.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {
99
import { By } from '@angular/platform-browser';
1010

1111
import { DaffMenuActivatorDirective } from './menu-activator.component';
12+
import {
13+
DAFF_MENU_CONFIG,
14+
DaffMenuConfig,
15+
} from '../config/menu-config';
1216
import { DaffMenuComponent } from '../menu/menu.component';
1317
import { DaffMenuService } from '../services/menu.service';
1418
import { provideTestMenuService } from '../testing/dummy-service';
@@ -37,6 +41,7 @@ describe('@daffodil/design/menu | DaffMenuActivatorDirective', () => {
3741
],
3842
providers: [
3943
provideTestMenuService(),
44+
{ provide: DAFF_MENU_CONFIG, useValue: <DaffMenuConfig>{ menuId: 'daff-menu-test' }},
4045
],
4146
}).compileComponents();
4247

@@ -55,6 +60,10 @@ describe('@daffodil/design/menu | DaffMenuActivatorDirective', () => {
5560
expect(de.nativeElement.getAttribute('aria-haspopup')).toBe('menu');
5661
});
5762

63+
it('should set aria-controls to the reserved menu id on init', () => {
64+
expect(de.nativeElement.getAttribute('aria-controls')).toMatch(/^daff-menu-\d+$/);
65+
});
66+
5867
it('should open the menu when the button is clicked', () => {
5968
const menuService = TestBed.inject(DaffMenuService);
6069
spyOn(menuService, 'open');

0 commit comments

Comments
 (0)