Skip to content

Commit a01c404

Browse files
committed
docs: add aria select page (#32508)
* docs: add aria select page * docs: add basic aria select example * docs: add aria multiselect example * docs: add aria select disabled example * docs: aria select demo layout * fixup! docs: aria select demo layout * fixup! docs: aria select demo layout (cherry picked from commit 450641c)
1 parent 4256fcb commit a01c404

File tree

16 files changed

+502
-0
lines changed

16 files changed

+502
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("//tools:defaults.bzl", "ng_project")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ng_project(
6+
name = "select",
7+
srcs = glob(["**/*.ts"]),
8+
assets = glob([
9+
"**/*.html",
10+
"**/*.css",
11+
]),
12+
deps = [
13+
"//:node_modules/@angular/core",
14+
"//src/aria/combobox",
15+
"//src/aria/listbox",
16+
"//src/cdk/overlay",
17+
],
18+
)
19+
20+
filegroup(
21+
name = "source-files",
22+
srcs = glob([
23+
"**/*.html",
24+
"**/*.css",
25+
"**/*.ts",
26+
]),
27+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export {SelectDisabledExample} from './select-disabled/select-disabled-example';
2+
export {SelectMultiExample} from './select-multi/select-multi-example';
3+
export {SelectExample} from './select/select-example';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<div ngCombobox readonly disabled>
2+
<div #origin class="example-select">
3+
<div class="example-select-value">
4+
<span class="example-select-label">Select an option</span>
5+
</div>
6+
<input aria-label="Select an option" ngComboboxInput />
7+
<span class="example-arrow material-symbols-outlined">arrow_drop_down</span>
8+
</div>
9+
10+
<ng-template
11+
[cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
12+
[cdkConnectedOverlayOpen]="true"
13+
>
14+
<ng-template ngComboboxPopupContainer>
15+
<div class="example-popup">
16+
<div ngListbox>
17+
@for (item of items; track item) {
18+
<div ngOption [value]="item" [label]="item">
19+
<span aria-hidden="true" class="example-option-icon material-symbols-outlined">{{item}}</span>
20+
<span class="example-option-text">{{item}}</span>
21+
<span aria-hidden="true" class="example-option-check material-symbols-outlined">check</span>
22+
</div>
23+
}
24+
</div>
25+
</div>
26+
</ng-template>
27+
</ng-template>
28+
</div>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {
10+
Combobox,
11+
ComboboxInput,
12+
ComboboxPopup,
13+
ComboboxPopupContainer,
14+
} from '@angular/aria/combobox';
15+
import {Listbox, Option} from '@angular/aria/listbox';
16+
import {ChangeDetectionStrategy, Component} from '@angular/core';
17+
import {OverlayModule} from '@angular/cdk/overlay';
18+
19+
/** @title Aria select disabled example. */
20+
@Component({
21+
selector: 'select-disabled-example',
22+
templateUrl: 'select-disabled-example.html',
23+
styleUrl: '../select.css',
24+
imports: [
25+
Combobox,
26+
ComboboxInput,
27+
ComboboxPopup,
28+
ComboboxPopupContainer,
29+
Listbox,
30+
Option,
31+
OverlayModule,
32+
],
33+
changeDetection: ChangeDetectionStrategy.OnPush,
34+
})
35+
export class SelectDisabledExample {
36+
/** The items available for selection. */
37+
items = ['Option 1', 'Option 2', 'Option 3'];
38+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<div ngCombobox readonly>
2+
<div #origin class="example-select">
3+
<div class="example-select-value">
4+
<span class="example-select-label">{{ displayValue() }}</span>
5+
</div>
6+
<input aria-label="Select a day of the week" ngComboboxInput />
7+
<span class="example-arrow material-symbols-outlined">arrow_drop_down</span>
8+
</div>
9+
10+
<ng-template
11+
[cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
12+
[cdkConnectedOverlayOpen]="true"
13+
>
14+
<ng-template ngComboboxPopupContainer>
15+
<div class="example-popup">
16+
<div ngListbox multi>
17+
@for (item of items; track item) {
18+
<div ngOption [value]="item" [label]="item">
19+
<span class="example-option-text">{{item}}</span>
20+
<span aria-hidden="true" class="example-option-check material-symbols-outlined">check</span>
21+
</div>
22+
}
23+
</div>
24+
</div>
25+
</ng-template>
26+
</ng-template>
27+
</div>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import {
10+
Combobox,
11+
ComboboxInput,
12+
ComboboxPopup,
13+
ComboboxPopupContainer,
14+
} from '@angular/aria/combobox';
15+
import {Listbox, Option} from '@angular/aria/listbox';
16+
import {
17+
afterRenderEffect,
18+
ChangeDetectionStrategy,
19+
Component,
20+
computed,
21+
viewChild,
22+
viewChildren,
23+
} from '@angular/core';
24+
import {OverlayModule} from '@angular/cdk/overlay';
25+
26+
/** @title Aria multiselect example. */
27+
@Component({
28+
selector: 'select-multi-example',
29+
templateUrl: 'select-multi-example.html',
30+
styleUrl: '../select.css',
31+
imports: [
32+
Combobox,
33+
ComboboxInput,
34+
ComboboxPopup,
35+
ComboboxPopupContainer,
36+
Listbox,
37+
Option,
38+
OverlayModule,
39+
],
40+
changeDetection: ChangeDetectionStrategy.OnPush,
41+
})
42+
export class SelectMultiExample {
43+
/** The options available in the listbox. */
44+
options = viewChildren<Option<string>>(Option);
45+
46+
/** The combobox listbox popup. */
47+
listbox = viewChild<Listbox<string>>(Listbox);
48+
49+
/** The visible label displayed to the user. */
50+
displayValue = computed(() => {
51+
const values = this.listbox()?.values();
52+
53+
if (!values?.length) {
54+
return 'Select a day';
55+
}
56+
57+
if (values.length <= 2) {
58+
return values.join(', ');
59+
}
60+
61+
return `${values[0]} + ${values.length - 1} more`;
62+
});
63+
64+
/** The items available for selection. */
65+
items = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
66+
67+
constructor() {
68+
// Scrolls to the active item when the active option changes.
69+
afterRenderEffect(() => {
70+
const option = this.options().find(opt => opt.active());
71+
option?.element.scrollIntoView({block: 'nearest'});
72+
});
73+
}
74+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
.example-select {
2+
position: relative;
3+
display: flex;
4+
align-items: center;
5+
border: 1px solid var(--mat-sys-outline);
6+
border-radius: var(--mat-sys-corner-extra-small);
7+
8+
/* stylelint-disable-next-line material/no-prefixes -- Valid in all remotely recent browsers. */
9+
width: fit-content;
10+
}
11+
12+
.example-select:has([ngComboboxInput][aria-disabled='true']) {
13+
opacity: 0.7;
14+
background-color: var(--mat-sys-surface-dim);
15+
}
16+
17+
.example-select:focus-within {
18+
outline-offset: -2px;
19+
outline: 2px solid var(--mat-sys-primary);
20+
}
21+
22+
.example-arrow,
23+
.example-select-value {
24+
position: absolute;
25+
pointer-events: none;
26+
}
27+
28+
.example-select-value {
29+
display: flex;
30+
gap: 1rem;
31+
left: 1rem;
32+
width: calc(100% - 4rem);
33+
}
34+
35+
.example-select-label {
36+
text-overflow: ellipsis;
37+
text-wrap-mode: nowrap;
38+
overflow: hidden;
39+
}
40+
41+
.example-arrow,
42+
.example-select-icon {
43+
font-size: 1.25rem;
44+
opacity: 0.875;
45+
}
46+
47+
.example-arrow {
48+
right: 1rem;
49+
transition: transform 0.2s ease-in-out;
50+
}
51+
52+
[ngComboboxInput] {
53+
cursor: pointer;
54+
padding: 0.7rem 3rem;
55+
opacity: 0;
56+
}
57+
58+
[ngComboboxInput][aria-disabled='true'] {
59+
cursor: default;
60+
}
61+
62+
[ngComboboxInput][aria-expanded='true'] + .example-arrow {
63+
transform: rotate(180deg);
64+
}
65+
66+
[ngCombobox]:has([aria-expanded='false']) .example-popup {
67+
display: none;
68+
}
69+
70+
.example-popup {
71+
width: 100%;
72+
margin-top: 2px;
73+
padding: 0.1rem;
74+
max-height: 11rem;
75+
border-radius: var(--mat-sys-corner-extra-small);
76+
background-color: var(--mat-sys-surface);
77+
border: 1px solid var(--mat-sys-outline);
78+
}
79+
80+
.example-no-results {
81+
padding: 1rem;
82+
}
83+
84+
[ngListbox] {
85+
gap: 2px;
86+
width: 100%;
87+
height: 100%;
88+
display: flex;
89+
overflow: auto;
90+
flex-direction: column;
91+
}
92+
93+
[ngOption] {
94+
display: flex;
95+
cursor: pointer;
96+
align-items: center;
97+
margin: 1px;
98+
gap: 1rem;
99+
padding: 0.7rem 1rem;
100+
border-radius: var(--mat-sys-corner-extra-small);
101+
}
102+
103+
[ngOption][aria-disabled='true'] {
104+
cursor: default;
105+
opacity: 0.5;
106+
background-color: var(--mat-sys-surface-dim);
107+
}
108+
109+
[ngOption]:hover {
110+
background-color: color-mix(in srgb, var(--mat-sys-outline) 15%, transparent);
111+
}
112+
113+
[ngOption][data-active='true'] {
114+
outline-offset: -2px;
115+
outline: 2px solid var(--mat-sys-primary);
116+
}
117+
118+
[ngOption][aria-selected='true'] {
119+
color: var(--mat-sys-primary);
120+
background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent);
121+
}
122+
123+
[ngOption]:not([aria-selected='true']) .example-option-check {
124+
display: none;
125+
}
126+
127+
.example-option-text {
128+
flex: 1;
129+
}
130+
131+
.example-option-icon {
132+
font-size: 1.25rem;
133+
}
134+
135+
.example-option-check {
136+
font-size: 1rem;
137+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<div ngCombobox readonly>
2+
<div #origin class="example-select">
3+
<div class="example-select-value">
4+
<span class="example-select-icon material-symbols-outlined">{{ value().icon }}</span>
5+
<span class="example-select-label">{{ value().label }}</span>
6+
</div>
7+
<input aria-label="Select a theme" ngComboboxInput [value]="value().label" />
8+
<span class="example-arrow material-symbols-outlined">arrow_drop_down</span>
9+
</div>
10+
11+
<ng-template
12+
[cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
13+
[cdkConnectedOverlayOpen]="true"
14+
>
15+
<ng-template ngComboboxPopupContainer>
16+
<div class="example-popup">
17+
<div ngListbox [values]="[value()]">
18+
@for (item of items; track item.label) {
19+
<div ngOption [value]="item" [label]="item.label">
20+
<span
21+
aria-hidden="true"
22+
class="example-option-icon material-symbols-outlined"
23+
>{{item.icon}}</span
24+
>
25+
<span class="example-option-text">{{item.label}}</span>
26+
<span aria-hidden="true" class="example-option-check material-symbols-outlined"
27+
>check</span
28+
>
29+
</div>
30+
}
31+
</div>
32+
</div>
33+
</ng-template>
34+
</ng-template>
35+
</div>

0 commit comments

Comments
 (0)