Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/components-examples/aria/select/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load("//tools:defaults.bzl", "ng_project")

package(default_visibility = ["//visibility:public"])

ng_project(
name = "select",
srcs = glob(["**/*.ts"]),
assets = glob([
"**/*.html",
"**/*.css",
]),
deps = [
"//:node_modules/@angular/core",
"//src/aria/combobox",
"//src/aria/listbox",
"//src/cdk/overlay",
],
)

filegroup(
name = "source-files",
srcs = glob([
"**/*.html",
"**/*.css",
"**/*.ts",
]),
)
3 changes: 3 additions & 0 deletions src/components-examples/aria/select/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {SelectDisabledExample} from './select-disabled/select-disabled-example';
export {SelectMultiExample} from './select-multi/select-multi-example';
export {SelectExample} from './select/select-example';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<div ngCombobox readonly disabled>
<div #origin class="example-select">
<div class="example-select-value">
<span class="example-select-label">Select an option</span>
</div>
<input aria-label="Select an option" ngComboboxInput />
<span class="example-arrow material-symbols-outlined">arrow_drop_down</span>
</div>

<ng-template
[cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
[cdkConnectedOverlayOpen]="true"
>
<ng-template ngComboboxPopupContainer>
<div class="example-popup">
<div ngListbox>
@for (item of items; track item) {
<div ngOption [value]="item" [label]="item">
<span aria-hidden="true" class="example-option-icon material-symbols-outlined">{{item}}</span>
<span class="example-option-text">{{item}}</span>
<span aria-hidden="true" class="example-option-check material-symbols-outlined">check</span>
</div>
}
</div>
</div>
</ng-template>
</ng-template>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {
Combobox,
ComboboxInput,
ComboboxPopup,
ComboboxPopupContainer,
} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {OverlayModule} from '@angular/cdk/overlay';

/** @title Aria select disabled example. */
@Component({
selector: 'select-disabled-example',
templateUrl: 'select-disabled-example.html',
styleUrl: '../select.css',
imports: [
Combobox,
ComboboxInput,
ComboboxPopup,
ComboboxPopupContainer,
Listbox,
Option,
OverlayModule,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectDisabledExample {
/** The items available for selection. */
items = ['Option 1', 'Option 2', 'Option 3'];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<div ngCombobox readonly>
<div #origin class="example-select">
<div class="example-select-value">
<span class="example-select-label">{{ displayValue() }}</span>
</div>
<input aria-label="Select a day of the week" ngComboboxInput />
<span class="example-arrow material-symbols-outlined">arrow_drop_down</span>
</div>

<ng-template
[cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
[cdkConnectedOverlayOpen]="true"
>
<ng-template ngComboboxPopupContainer>
<div class="example-popup">
<div ngListbox multi>
@for (item of items; track item) {
<div ngOption [value]="item" [label]="item">
<span class="example-option-text">{{item}}</span>
<span aria-hidden="true" class="example-option-check material-symbols-outlined">check</span>
</div>
}
</div>
</div>
</ng-template>
</ng-template>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {
Combobox,
ComboboxInput,
ComboboxPopup,
ComboboxPopupContainer,
} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {
afterRenderEffect,
ChangeDetectionStrategy,
Component,
computed,
viewChild,
viewChildren,
} from '@angular/core';
import {OverlayModule} from '@angular/cdk/overlay';

/** @title Aria multiselect example. */
@Component({
selector: 'select-multi-example',
templateUrl: 'select-multi-example.html',
styleUrl: '../select.css',
imports: [
Combobox,
ComboboxInput,
ComboboxPopup,
ComboboxPopupContainer,
Listbox,
Option,
OverlayModule,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectMultiExample {
/** The options available in the listbox. */
options = viewChildren<Option<string>>(Option);

/** The combobox listbox popup. */
listbox = viewChild<Listbox<string>>(Listbox);

/** The visible label displayed to the user. */
displayValue = computed(() => {
const values = this.listbox()?.values();

if (!values?.length) {
return 'Select a day';
}

if (values.length <= 2) {
return values.join(', ');
}

return `${values[0]} + ${values.length - 1} more`;
});

/** The items available for selection. */
items = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

constructor() {
// Scrolls to the active item when the active option changes.
afterRenderEffect(() => {
const option = this.options().find(opt => opt.active());
option?.element.scrollIntoView({block: 'nearest'});
});
}
}
137 changes: 137 additions & 0 deletions src/components-examples/aria/select/select.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
.example-select {
position: relative;
display: flex;
align-items: center;
border: 1px solid var(--mat-sys-outline);
border-radius: var(--mat-sys-corner-extra-small);

/* stylelint-disable-next-line material/no-prefixes -- Valid in all remotely recent browsers. */
width: fit-content;
}

.example-select:has([ngComboboxInput][aria-disabled='true']) {
opacity: 0.7;
background-color: var(--mat-sys-surface-dim);
}

.example-select:focus-within {
outline-offset: -2px;
outline: 2px solid var(--mat-sys-primary);
}

.example-arrow,
.example-select-value {
position: absolute;
pointer-events: none;
}

.example-select-value {
display: flex;
gap: 1rem;
left: 1rem;
width: calc(100% - 4rem);
}

.example-select-label {
text-overflow: ellipsis;
text-wrap-mode: nowrap;
overflow: hidden;
}

.example-arrow,
.example-select-icon {
font-size: 1.25rem;
opacity: 0.875;
}

.example-arrow {
right: 1rem;
transition: transform 0.2s ease-in-out;
}

[ngComboboxInput] {
cursor: pointer;
padding: 0.7rem 3rem;
opacity: 0;
}

[ngComboboxInput][aria-disabled='true'] {
cursor: default;
}

[ngComboboxInput][aria-expanded='true'] + .example-arrow {
transform: rotate(180deg);
}

[ngCombobox]:has([aria-expanded='false']) .example-popup {
display: none;
}

.example-popup {
width: 100%;
margin-top: 2px;
padding: 0.1rem;
max-height: 11rem;
border-radius: var(--mat-sys-corner-extra-small);
background-color: var(--mat-sys-surface);
border: 1px solid var(--mat-sys-outline);
}

.example-no-results {
padding: 1rem;
}

[ngListbox] {
gap: 2px;
width: 100%;
height: 100%;
display: flex;
overflow: auto;
flex-direction: column;
}

[ngOption] {
display: flex;
cursor: pointer;
align-items: center;
margin: 1px;
gap: 1rem;
padding: 0.7rem 1rem;
border-radius: var(--mat-sys-corner-extra-small);
}

[ngOption][aria-disabled='true'] {
cursor: default;
opacity: 0.5;
background-color: var(--mat-sys-surface-dim);
}

[ngOption]:hover {
background-color: color-mix(in srgb, var(--mat-sys-outline) 15%, transparent);
}

[ngOption][data-active='true'] {
outline-offset: -2px;
outline: 2px solid var(--mat-sys-primary);
}

[ngOption][aria-selected='true'] {
color: var(--mat-sys-primary);
background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
display: none;
}

.example-option-text {
flex: 1;
}

.example-option-icon {
font-size: 1.25rem;
}

.example-option-check {
font-size: 1rem;
}
35 changes: 35 additions & 0 deletions src/components-examples/aria/select/select/select-example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div ngCombobox readonly>
<div #origin class="example-select">
<div class="example-select-value">
<span class="example-select-icon material-symbols-outlined">{{ value().icon }}</span>
<span class="example-select-label">{{ value().label }}</span>
</div>
<input aria-label="Select a theme" ngComboboxInput [value]="value().label" />
<span class="example-arrow material-symbols-outlined">arrow_drop_down</span>
</div>

<ng-template
[cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
[cdkConnectedOverlayOpen]="true"
>
<ng-template ngComboboxPopupContainer>
<div class="example-popup">
<div ngListbox [values]="[value()]">
@for (item of items; track item.label) {
<div ngOption [value]="item" [label]="item.label">
<span
aria-hidden="true"
class="example-option-icon material-symbols-outlined"
>{{item.icon}}</span
>
<span class="example-option-text">{{item.label}}</span>
<span aria-hidden="true" class="example-option-check material-symbols-outlined"
>check</span
>
</div>
}
</div>
</div>
</ng-template>
</ng-template>
</div>
Loading
Loading