From f62e2b599fcb48511cf77ecde41ceb3cfa63d2e5 Mon Sep 17 00:00:00 2001 From: Ben Hong Date: Mon, 17 Nov 2025 10:07:32 -0500 Subject: [PATCH] docs: add draft aria component guides (pt 2) (cherry picked from commit c61a195391168fe2c06bcf1615883dc347cead9a) --- adev/src/app/routing/sub-navigation-data.ts | 21 +- .../src/disabled-focusable/app/app.css | 9 + .../src/disabled-focusable/app/app.html | 1 + .../src/disabled-focusable/app/app.ts | 8 + .../accordion/src/multi-expansion/app/app.css | 9 + .../src/multi-expansion/app/app.html | 1 + .../accordion/src/multi-expansion/app/app.ts | 8 + .../src/single-expansion/app/app.css | 9 + .../src/single-expansion/app/app.html | 1 + .../accordion/src/single-expansion/app/app.ts | 8 + .../aria/grid/src/grid-calendar/app/app.css | 58 ++++ .../aria/grid/src/grid-calendar/app/app.html | 109 ++++++++ .../aria/grid/src/grid-calendar/app/app.ts | 10 + .../aria/grid/src/grid-pill-list/app/app.css | 53 ++++ .../aria/grid/src/grid-pill-list/app/app.html | 108 ++++++++ .../aria/grid/src/grid-pill-list/app/app.ts | 10 + .../aria/grid/src/grid-table/app/app.css | 53 ++++ .../aria/grid/src/grid-table/app/app.html | 108 ++++++++ .../aria/grid/src/grid-table/app/app.ts | 11 + .../aria/menu/src/menu-bar/app/app.css | 53 ++++ .../aria/menu/src/menu-bar/app/app.html | 108 ++++++++ .../aria/menu/src/menu-bar/app/app.ts | 11 + .../aria/menu/src/menu-context/app/app.css | 53 ++++ .../aria/menu/src/menu-context/app/app.html | 108 ++++++++ .../aria/menu/src/menu-context/app/app.ts | 10 + .../aria/menu/src/menu-standalone/app/app.css | 53 ++++ .../menu/src/menu-standalone/app/app.html | 108 ++++++++ .../aria/menu/src/menu-standalone/app/app.ts | 10 + .../src/menu-trigger-disabled/app/app.css | 53 ++++ .../src/menu-trigger-disabled/app/app.html | 108 ++++++++ .../menu/src/menu-trigger-disabled/app/app.ts | 10 + .../aria/menu/src/menu-trigger/app/app.css | 58 ++++ .../aria/menu/src/menu-trigger/app/app.html | 109 ++++++++ .../aria/menu/src/menu-trigger/app/app.ts | 10 + .../tabs/src/disabled-focusable/app/app.css | 9 + .../tabs/src/disabled-focusable/app/app.html | 1 + .../tabs/src/disabled-focusable/app/app.ts | 8 + .../tabs/src/explicit-selection/app/app.css | 9 + .../tabs/src/explicit-selection/app/app.html | 1 + .../tabs/src/explicit-selection/app/app.ts | 8 + .../src/selection-follows-focus/app/app.css | 9 + .../src/selection-follows-focus/app/app.html | 1 + .../src/selection-follows-focus/app/app.ts | 8 + .../tabs/src/vertical-orientation/app/app.css | 9 + .../src/vertical-orientation/app/app.html | 1 + .../tabs/src/vertical-orientation/app/app.ts | 8 + .../src/tree-disabled-focusable/app/app.css | 9 + .../src/tree-disabled-focusable/app/app.html | 1 + .../src/tree-disabled-focusable/app/app.ts | 8 + .../tree/src/tree-multi-select/app/app.css | 9 + .../tree/src/tree-multi-select/app/app.html | 1 + .../tree/src/tree-multi-select/app/app.ts | 8 + .../aria/tree/src/tree-nav/app/app.css | 9 + .../aria/tree/src/tree-nav/app/app.html | 1 + .../aria/tree/src/tree-nav/app/app.ts | 8 + .../app/app.css | 9 + .../app/app.html | 1 + .../app/app.ts | 8 + .../tree/src/tree-single-select/app/app.css | 9 + .../tree/src/tree-single-select/app/app.html | 1 + .../tree/src/tree-single-select/app/app.ts | 8 + adev/src/content/guide/aria/accordion.md | 244 ++++++++++++++++- adev/src/content/guide/aria/grid.md | 217 ++++++++++++++- adev/src/content/guide/aria/menu.md | 253 +++++++++++++++++- adev/src/content/guide/aria/tabs.md | 250 ++++++++++++++++- adev/src/content/guide/aria/tree.md | 245 ++++++++++++++++- 66 files changed, 2769 insertions(+), 59 deletions(-) create mode 100644 adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.css create mode 100644 adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.html create mode 100644 adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.ts create mode 100644 adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.css create mode 100644 adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.html create mode 100644 adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.ts create mode 100644 adev/src/content/examples/aria/accordion/src/single-expansion/app/app.css create mode 100644 adev/src/content/examples/aria/accordion/src/single-expansion/app/app.html create mode 100644 adev/src/content/examples/aria/accordion/src/single-expansion/app/app.ts create mode 100644 adev/src/content/examples/aria/grid/src/grid-calendar/app/app.css create mode 100644 adev/src/content/examples/aria/grid/src/grid-calendar/app/app.html create mode 100644 adev/src/content/examples/aria/grid/src/grid-calendar/app/app.ts create mode 100644 adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.css create mode 100644 adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.html create mode 100644 adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.ts create mode 100644 adev/src/content/examples/aria/grid/src/grid-table/app/app.css create mode 100644 adev/src/content/examples/aria/grid/src/grid-table/app/app.html create mode 100644 adev/src/content/examples/aria/grid/src/grid-table/app/app.ts create mode 100644 adev/src/content/examples/aria/menu/src/menu-bar/app/app.css create mode 100644 adev/src/content/examples/aria/menu/src/menu-bar/app/app.html create mode 100644 adev/src/content/examples/aria/menu/src/menu-bar/app/app.ts create mode 100644 adev/src/content/examples/aria/menu/src/menu-context/app/app.css create mode 100644 adev/src/content/examples/aria/menu/src/menu-context/app/app.html create mode 100644 adev/src/content/examples/aria/menu/src/menu-context/app/app.ts create mode 100644 adev/src/content/examples/aria/menu/src/menu-standalone/app/app.css create mode 100644 adev/src/content/examples/aria/menu/src/menu-standalone/app/app.html create mode 100644 adev/src/content/examples/aria/menu/src/menu-standalone/app/app.ts create mode 100644 adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.css create mode 100644 adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.html create mode 100644 adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.ts create mode 100644 adev/src/content/examples/aria/menu/src/menu-trigger/app/app.css create mode 100644 adev/src/content/examples/aria/menu/src/menu-trigger/app/app.html create mode 100644 adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts create mode 100644 adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.css create mode 100644 adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.html create mode 100644 adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.ts create mode 100644 adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.css create mode 100644 adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.html create mode 100644 adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.ts create mode 100644 adev/src/content/examples/aria/tree/src/tree-nav/app/app.css create mode 100644 adev/src/content/examples/aria/tree/src/tree-nav/app/app.html create mode 100644 adev/src/content/examples/aria/tree/src/tree-nav/app/app.ts create mode 100644 adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.css create mode 100644 adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.html create mode 100644 adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.ts create mode 100644 adev/src/content/examples/aria/tree/src/tree-single-select/app/app.css create mode 100644 adev/src/content/examples/aria/tree/src/tree-single-select/app/app.html create mode 100644 adev/src/content/examples/aria/tree/src/tree-single-select/app/app.ts diff --git a/adev/src/app/routing/sub-navigation-data.ts b/adev/src/app/routing/sub-navigation-data.ts index 82739edb66d..6ac57c2d16d 100644 --- a/adev/src/app/routing/sub-navigation-data.ts +++ b/adev/src/app/routing/sub-navigation-data.ts @@ -718,9 +718,9 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ contentPath: 'guide/aria/overview', }, { - label: 'Autocomplete', - path: 'guide/aria/autocomplete', - contentPath: 'guide/aria/autocomplete', + label: 'Accordion', + path: 'guide/aria/accordion', + contentPath: 'guide/aria/accordion', }, { label: 'Autocomplete', @@ -732,11 +732,21 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ path: 'guide/aria/combobox', contentPath: 'guide/aria/combobox', }, + { + label: 'Grid', + path: 'guide/aria/grid', + contentPath: 'guide/aria/grid', + }, { label: 'Listbox', path: 'guide/aria/listbox', contentPath: 'guide/aria/listbox', }, + { + label: 'Menu', + path: 'guide/aria/menu', + contentPath: 'guide/aria/menu', + }, { label: 'Multiselect', path: 'guide/aria/multiselect', @@ -757,6 +767,11 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [ path: 'guide/aria/toolbar', contentPath: 'guide/aria/toolbar', }, + { + label: 'Tree', + path: 'guide/aria/tree', + contentPath: 'guide/aria/tree', + }, ], }, ] diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.css b/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.html b/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.ts b/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.css b/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.html b/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.ts b/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.css b/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.html b/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.ts b/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.css b/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.css new file mode 100644 index 00000000000..88ce9748857 --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.css @@ -0,0 +1,58 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} + +[ngToolbarWidget][aria-disabled="true"] { + cursor: default; + opacity: 0.45; +} diff --git a/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.html b/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.html new file mode 100644 index 00000000000..17ec24848fc --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.html @@ -0,0 +1,109 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.ts b/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.ts new file mode 100644 index 00000000000..b53e249a983 --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-calendar/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.css b/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.css new file mode 100644 index 00000000000..c1a83e5b23d --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.css @@ -0,0 +1,53 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} diff --git a/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.html b/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.html new file mode 100644 index 00000000000..a4cb741fa58 --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.html @@ -0,0 +1,108 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.ts b/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.ts new file mode 100644 index 00000000000..b53e249a983 --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-pill-list/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/grid/src/grid-table/app/app.css b/adev/src/content/examples/aria/grid/src/grid-table/app/app.css new file mode 100644 index 00000000000..c1a83e5b23d --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-table/app/app.css @@ -0,0 +1,53 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} diff --git a/adev/src/content/examples/aria/grid/src/grid-table/app/app.html b/adev/src/content/examples/aria/grid/src/grid-table/app/app.html new file mode 100644 index 00000000000..029d7911e3a --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-table/app/app.html @@ -0,0 +1,108 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/grid/src/grid-table/app/app.ts b/adev/src/content/examples/aria/grid/src/grid-table/app/app.ts new file mode 100644 index 00000000000..8f4d0f2f230 --- /dev/null +++ b/adev/src/content/examples/aria/grid/src/grid-table/app/app.ts @@ -0,0 +1,11 @@ +import {Dir} from '@angular/cdk/bidi'; +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Dir, Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/menu/src/menu-bar/app/app.css b/adev/src/content/examples/aria/menu/src/menu-bar/app/app.css new file mode 100644 index 00000000000..c1a83e5b23d --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-bar/app/app.css @@ -0,0 +1,53 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} diff --git a/adev/src/content/examples/aria/menu/src/menu-bar/app/app.html b/adev/src/content/examples/aria/menu/src/menu-bar/app/app.html new file mode 100644 index 00000000000..029d7911e3a --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-bar/app/app.html @@ -0,0 +1,108 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/menu/src/menu-bar/app/app.ts b/adev/src/content/examples/aria/menu/src/menu-bar/app/app.ts new file mode 100644 index 00000000000..8f4d0f2f230 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-bar/app/app.ts @@ -0,0 +1,11 @@ +import {Dir} from '@angular/cdk/bidi'; +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Dir, Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/menu/src/menu-context/app/app.css b/adev/src/content/examples/aria/menu/src/menu-context/app/app.css new file mode 100644 index 00000000000..c1a83e5b23d --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-context/app/app.css @@ -0,0 +1,53 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} diff --git a/adev/src/content/examples/aria/menu/src/menu-context/app/app.html b/adev/src/content/examples/aria/menu/src/menu-context/app/app.html new file mode 100644 index 00000000000..a4cb741fa58 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-context/app/app.html @@ -0,0 +1,108 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/menu/src/menu-context/app/app.ts b/adev/src/content/examples/aria/menu/src/menu-context/app/app.ts new file mode 100644 index 00000000000..b53e249a983 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-context/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.css b/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.css new file mode 100644 index 00000000000..c1a83e5b23d --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.css @@ -0,0 +1,53 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} diff --git a/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.html b/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.html new file mode 100644 index 00000000000..a4cb741fa58 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.html @@ -0,0 +1,108 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.ts b/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.ts new file mode 100644 index 00000000000..b53e249a983 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-standalone/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.css b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.css new file mode 100644 index 00000000000..c1a83e5b23d --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.css @@ -0,0 +1,53 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.html b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.html new file mode 100644 index 00000000000..a4cb741fa58 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.html @@ -0,0 +1,108 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.ts b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.ts new file mode 100644 index 00000000000..b53e249a983 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.css b/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.css new file mode 100644 index 00000000000..88ce9748857 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.css @@ -0,0 +1,58 @@ +:host { + display: flex; + justify-content: center; +} + +[ngToolbar] { + gap: 1.5rem; + display: flex; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background-color: var(--septenary-contrast); +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + align-self: center; + height: calc(100% - 1rem); + background-color: var(--quinary-contrast); +} + +[ngToolbarWidget] { + border: none; + cursor: pointer; + padding: 0.5rem; + border-radius: 4px; + font-size: 1.25rem; + background-color: transparent; + color: var(--primary-contrast); +} + +[ngToolbarWidget]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngToolbarWidget]:active { + background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent); +} + +[ngToolbarWidget]:focus { + outline-offset: -1px; + outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent); +} + +[ngToolbarWidget][aria-pressed="true"], +[ngToolbarWidget][aria-checked="true"] { + color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast)); + background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent); +} + +[ngToolbarWidget][aria-disabled="true"] { + cursor: default; + opacity: 0.45; +} diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.html b/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.html new file mode 100644 index 00000000000..17ec24848fc --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.html @@ -0,0 +1,109 @@ + + +
+
+ + + +
+ + + +
+ + + + + +
+ + + +
+ + + + + +
+
diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts b/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts new file mode 100644 index 00000000000..b53e249a983 --- /dev/null +++ b/adev/src/content/examples/aria/menu/src/menu-trigger/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; + +@Component({ + selector: 'app-root', + templateUrl: 'app.html', + styleUrl: 'app.css', + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.css b/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.html b/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts b/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.css b/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.html b/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.ts b/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.css b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.html b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.ts b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.css b/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.html b/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts b/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.css b/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.html b/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.ts b/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-disabled-focusable/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.css b/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.html b/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.ts b/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-multi-select/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tree/src/tree-nav/app/app.css b/adev/src/content/examples/aria/tree/src/tree-nav/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-nav/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tree/src/tree-nav/app/app.html b/adev/src/content/examples/aria/tree/src/tree-nav/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-nav/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tree/src/tree-nav/app/app.ts b/adev/src/content/examples/aria/tree/src/tree-nav/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-nav/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.css b/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.html b/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.ts b/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-single-select-follow-focus/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.css b/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.css new file mode 100644 index 00000000000..bc6e9c409ad --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.css @@ -0,0 +1,9 @@ +h1 { + background: #1e1e1e; + color: #e0e0e0; + padding: 2rem; + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + font-size: 2rem; + text-align: center; +} diff --git a/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.html b/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.html new file mode 100644 index 00000000000..d39d7de603b --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.html @@ -0,0 +1 @@ +

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.ts b/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.ts new file mode 100644 index 00000000000..183576f2c24 --- /dev/null +++ b/adev/src/content/examples/aria/tree/src/tree-single-select/app/app.ts @@ -0,0 +1,8 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', +}) +export class App {} diff --git a/adev/src/content/guide/aria/accordion.md b/adev/src/content/guide/aria/accordion.md index b2fa21c8bac..42dd071fdc5 100644 --- a/adev/src/content/guide/aria/accordion.md +++ b/adev/src/content/guide/aria/accordion.md @@ -1,22 +1,246 @@ - +## Overview + +An accordion organizes related content into expandable and collapsible sections, reducing page scrolling and helping users focus on relevant information. Each section has a trigger button and a content panel. Clicking a trigger toggles the visibility of its associated panel. + + + + + + + - + - +This mode works well for FAQs or situations where you want users to focus on one answer at a time. -### Example with TailwindCSS +### Multiple expansion mode - +Set `[multiExpandable]="true"` to allow multiple panels to be open simultaneously. Users can expand as many panels as needed without closing others. + + + + + + +This mode is useful for form sections or when users need to compare content across multiple panels. + +NOTE: The `multiExpandable` input defaults to `true`. Set it to `false` explicitly if you want single expansion behavior. + +### Disabled accordion items + +Disable specific triggers using the `disabled` input. Control how disabled items behave during keyboard navigation using the `softDisabled` input on the accordion group. + + + + + + +When `[softDisabled]="true"` (the default), disabled items can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled items are skipped entirely during keyboard navigation. + +### Lazy content rendering + +Use the `ngAccordionContent` directive on an `ng-template` to defer rendering content until the panel first expands. This improves performance for accordions with heavy content like images, charts, or complex components. + +```angular-html +
+
+ +
+ + + Description + + +
+
+
+``` + +By default, content remains in the DOM after the panel collapses. Set `[preserveContent]="false"` to remove the content from the DOM when the panel closes. + +## Showcase + +TBD + +## APIs + +### AccordionGroup + +The container directive that manages keyboard navigation and expansion behavior for a group of accordion items. + +#### Inputs + +| Property | Type | Default | Description | +| ----------------- | --------- | ------- | ------------------------------------------------------------------------- | +| `disabled` | `boolean` | `false` | Disables all triggers in the group | +| `multiExpandable` | `boolean` | `true` | Whether multiple panels can be expanded simultaneously | +| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable. When `false`, they are skipped | +| `wrap` | `boolean` | `false` | Whether keyboard navigation wraps from last to first item and vice versa | + +#### Methods + +| Method | Parameters | Description | +| ------------- | ---------- | ---------------------------------------------------------------- | +| `expandAll` | none | Expands all panels (only works when `multiExpandable` is `true`) | +| `collapseAll` | none | Collapses all panels | + +### AccordionTrigger + +The directive applied to the button element that toggles panel visibility. + +#### Inputs + +| Property | Type | Default | Description | +| ---------- | --------- | ------- | -------------------------------------------------------------- | +| `id` | `string` | auto | Unique identifier for the trigger | +| `panelId` | `string` | — | **Required.** Must match the `panelId` of the associated panel | +| `disabled` | `boolean` | `false` | Disables this trigger | +| `expanded` | `boolean` | `false` | Whether the panel is expanded (supports two-way binding) | + +#### Signals + +| Property | Type | Description | +| -------- | ----------------- | --------------------------------------- | +| `active` | `Signal` | Whether the trigger currently has focus | + +#### Methods + +| Method | Parameters | Description | +| ---------- | ---------- | --------------------------------- | +| `expand` | none | Expands the associated panel | +| `collapse` | none | Collapses the associated panel | +| `toggle` | none | Toggles the panel expansion state | + +### AccordionPanel + +The directive applied to the element containing the collapsible content. + +#### Inputs + +| Property | Type | Default | Description | +| ----------------- | --------- | ------- | ---------------------------------------------------------------- | +| `id` | `string` | auto | Unique identifier for the panel | +| `panelId` | `string` | — | **Required.** Must match the `panelId` of the associated trigger | +| `preserveContent` | `boolean` | `true` | Whether to keep content in DOM after panel collapses | + +#### Signals + +| Property | Type | Description | +| --------- | ----------------- | --------------------------------------- | +| `visible` | `Signal` | Whether the panel is currently expanded | + +#### Methods + +| Method | Parameters | Description | +| ---------- | ---------- | --------------------------- | +| `expand` | none | Expands this panel | +| `collapse` | none | Collapses this panel | +| `toggle` | none | Toggles the expansion state | + +### AccordionContent + +The structural directive applied to an `ng-template` inside an accordion panel to enable lazy rendering. + +This directive has no inputs, outputs, or methods. Apply it to an `ng-template` element: + +```angular-html +
+ + + +
+``` + +## Styling + +Angular automatically applies attributes to accordion elements that you can use in your CSS selectors. + +The accordion group receives the `ng-accordion-group` attribute: + +```css +[ng-accordion-group] { + display: flex; + flex-direction: column; + gap: 0.5rem; +} +``` + +Triggers receive the `ng-accordion-trigger` attribute and a `data-active` attribute when focused: + +```css +[ng-accordion-trigger] { + cursor: pointer; + background: transparent; + border: none; + padding: 1rem; + width: 100%; + text-align: left; + font-weight: 500; +} + +[ng-accordion-trigger][data-active] { + background: var(--focus-background); + outline: 2px solid var(--focus-color); +} + +[ng-accordion-trigger][aria-expanded="true"] { + font-weight: 600; +} +``` + +Panels receive the `ng-accordion-panel` attribute and an `inert` attribute when collapsed: + +```css +[ng-accordion-panel]:not([inert]) { + padding: 1rem; + border-top: 1px solid var(--border-color); +} +``` + +TIP: Use the `:not([inert])` selector to style expanded panels, as Angular automatically adds the `inert` attribute to collapsed panels to hide them from assistive technologies. diff --git a/adev/src/content/guide/aria/grid.md b/adev/src/content/guide/aria/grid.md index 8f597b050cd..b21baaf9bdd 100644 --- a/adev/src/content/guide/aria/grid.md +++ b/adev/src/content/guide/aria/grid.md @@ -1,22 +1,219 @@ - +## Overview + +A grid enables users to navigate two-dimensional data or interactive elements using directional arrow keys, Home, End, and Page Up/Down. Grids work for data tables, calendars, spreadsheets, and layout patterns that group related interactive elements. + + + + + + + - + - +Apply the `ngGrid` directive to the table element, `ngGridRow` to each row, and `ngGridCell` to each cell. -### Example with TailwindCSS +### Calendar grid - +Calendars are a common use case for grids. This example shows a month view where users navigate dates using arrow keys. + + + + + + +Users can activate a date by pressing Enter or Space when focused on a cell. + +### Layout grid + +Use a layout grid to group interactive elements and reduce tab stops. This example shows a grid of pill buttons. + + + + + + +Instead of tabbing through each button, users navigate with arrow keys and only one button receives tab focus. + +### Selection and focus modes + +Enable selection with `[enableSelection]="true"` and configure how focus and selection interact. + +```angular-html + + + + + +
Cell 1Cell 2
+``` + +**Selection modes:** + +- `follow`: Focused cell is automatically selected +- `explicit`: Users select cells with Space or click + +**Focus modes:** + +- `roving`: Focus moves to cells using `tabindex` (better for simple grids) +- `activedescendant`: Focus stays on grid container, `aria-activedescendant` indicates active cell (better for virtual scrolling) + +## Showcase + +TBD + +## APIs + +### Grid + +The container directive that provides keyboard navigation and focus management for rows and cells. + +#### Inputs + +| Property | Type | Default | Description | +| ---------------------- | ------------------------------------ | ---------- | ------------------------------------------------------------- | +| `enableSelection` | `boolean` | `false` | Whether selection is enabled for the grid | +| `disabled` | `boolean` | `false` | Disables the entire grid | +| `softDisabled` | `boolean` | `true` | When `true`, disabled cells are focusable but not interactive | +| `focusMode` | `'roving' \| 'activedescendant'` | `'roving'` | Focus strategy used by the grid | +| `rowWrap` | `'continuous' \| 'loop' \| 'nowrap'` | `'loop'` | Navigation wrapping behavior along rows | +| `colWrap` | `'continuous' \| 'loop' \| 'nowrap'` | `'loop'` | Navigation wrapping behavior along columns | +| `multi` | `boolean` | `false` | Whether multiple cells can be selected | +| `selectionMode` | `'follow' \| 'explicit'` | `'follow'` | Whether selection follows focus or requires explicit action | +| `enableRangeSelection` | `boolean` | `false` | Enable range selections with modifier keys or dragging | + +### GridRow + +Represents a row within a grid and serves as a container for grid cells. + +#### Inputs + +| Property | Type | Default | Description | +| ---------- | -------- | ------- | ------------------------------------- | +| `rowIndex` | `number` | auto | The index of this row within the grid | + +### GridCell + +Represents an individual cell within a grid row. + +#### Inputs + +| Property | Type | Default | Description | +| ------------- | ---------------------------- | -------------- | ------------------------------------------------------- | +| `id` | `string` | auto | Unique identifier for the cell | +| `role` | `string` | `'gridcell'` | Cell role: `gridcell`, `columnheader`, or `rowheader` | +| `disabled` | `boolean` | `false` | Disables this cell | +| `selected` | `boolean` | `false` | Whether the cell is selected (supports two-way binding) | +| `selectable` | `boolean` | `true` | Whether the cell can be selected | +| `rowSpan` | `number` | — | Number of rows the cell spans | +| `colSpan` | `number` | — | Number of columns the cell spans | +| `rowIndex` | `number` | — | Row index of the cell | +| `colIndex` | `number` | — | Column index of the cell | +| `orientation` | `'vertical' \| 'horizontal'` | `'horizontal'` | Orientation for widgets within the cell | +| `wrap` | `boolean` | `true` | Whether widget navigation wraps within the cell | + +#### Signals + +| Property | Type | Description | +| -------- | ----------------- | ------------------------------------ | +| `active` | `Signal` | Whether the cell currently has focus | + +## Styling + +Angular automatically applies attributes to grid elements that you can use in your CSS selectors. + +The grid receives the `ng-grid` attribute: + +```css +[ng-grid] { + border-collapse: collapse; + width: 100%; +} +``` + +Rows receive the `ng-grid-row` attribute: + +```css +[ng-grid-row] { + border-bottom: 1px solid #ddd; +} +``` + +Cells receive the `ng-grid-cell` attribute and a `data-active` attribute when focused: + +```css +[ng-grid-cell] { + padding: 0.75rem; + border: 1px solid #ddd; +} + +[ng-grid-cell][data-active] { + outline: 2px solid var(--focus-color); + outline-offset: -2px; +} + +[ng-grid-cell][aria-selected="true"] { + background: var(--selected-background); +} +``` + +Style column and row headers based on their role: + +```css +[ng-grid-cell][role="columnheader"], +[ng-grid-cell][role="rowheader"] { + font-weight: 600; + background: var(--header-background); +} +``` + +TIP: Use the `[data-active]` selector to style focused cells, as Angular automatically adds this attribute when a cell has focus. diff --git a/adev/src/content/guide/aria/menu.md b/adev/src/content/guide/aria/menu.md index 8cc500b036b..5532aff5a29 100644 --- a/adev/src/content/guide/aria/menu.md +++ b/adev/src/content/guide/aria/menu.md @@ -1,22 +1,249 @@ - - - - - +## Overview - + + + + -### Example with TailwindCSS +## Usage - +Menus work well for presenting lists of actions or commands that users can choose from. + +**Use menus when:** + +- Building application command menus (File, Edit, View) +- Creating context menus (right-click actions) +- Showing dropdown action lists +- Implementing toolbar dropdowns +- Organizing settings or options + +**Avoid menus when:** + +- Building site navigation (use navigation landmarks instead) +- Creating form selects (use the [Select](guide/aria/select) component) +- Switching between content panels (use [Tabs](guide/aria/tabs)) +- Showing collapsible content (use [Accordion](guide/aria/accordion)) + +## Features + +- **Keyboard navigation** - Arrow keys, Home/End, and character search for efficient navigation +- **Submenus** - Nested menu support with automatic positioning +- **Menu types** - Standalone menus, triggered menus, and menubars +- **Checkboxes and radios** - Toggle and selection menu items +- **Disabled items** - Soft or hard disabled states with focus management +- **Auto-close behavior** - Configurable close on selection +- **RTL support** - Right-to-left language navigation + +## Examples + +### Menu with trigger + +Create a dropdown menu by pairing a trigger button with a menu. The trigger opens and closes the menu. + + + + + + +The menu automatically closes when a user selects an item or presses Escape. + +### Menubar + +A menubar provides persistent access to multiple menus, commonly used in application toolbars. + + + + + + +Users navigate between top-level menus with arrow keys and open menus with Enter or by hovering. + +### Context menu + +Context menus appear at the cursor position when users right-click an element. + + + + + + +Position the menu using the `contextmenu` event coordinates. + +### Standalone menu + +A standalone menu doesn't require a trigger and remains visible in the interface. + + + + + + +Standalone menus work well for always-visible action lists or navigation. + +### Disabled menu items + +Disable specific menu items using the `disabled` input. Control focus behavior with `softDisabled`. + + + + + + +When `[softDisabled]="true"`, disabled items can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation. + +## Showcase + +TBD + +## APIs + +### Menu + +The container directive for menu items. + +#### Inputs + +| Property | Type | Default | Description | +| -------------- | --------- | ------- | ------------------------------------------------------------- | +| `disabled` | `boolean` | `false` | Disables all items in the menu | +| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps at edges | +| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | + +#### Methods + +| Method | Parameters | Description | +| ---------------- | ---------- | ---------------------------------- | +| `close` | none | Closes the menu | +| `focusFirstItem` | none | Moves focus to the first menu item | + +### MenuBar + +A horizontal container for multiple menus. + +#### Inputs + +| Property | Type | Default | Description | +| -------------- | --------- | ------- | ------------------------------------------------------------- | +| `disabled` | `boolean` | `false` | Disables the entire menubar | +| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps at edges | +| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | + +### MenuItem + +An individual item within a menu. + +#### Inputs + +| Property | Type | Default | Description | +| ------------ | --------- | ------- | ---------------------------------------------------- | +| `value` | `any` | — | **Required.** Value for this item | +| `disabled` | `boolean` | `false` | Disables this menu item | +| `submenu` | `Menu` | — | Reference to a submenu | +| `searchTerm` | `string` | `''` | Search term for typeahead (supports two-way binding) | + +#### Signals + +| Property | Type | Description | +| ---------- | ----------------- | ------------------------------------------ | +| `active` | `Signal` | Whether the item currently has focus | +| `expanded` | `Signal` | Whether the submenu is expanded | +| `hasPopup` | `Signal` | Whether the item has an associated submenu | + +NOTE: MenuItem does not expose public methods. Use the `submenu` input to associate submenus with menu items. + +### MenuTrigger + +A button or element that opens a menu. + +#### Inputs + +| Property | Type | Default | Description | +| -------------- | --------- | ------- | ------------------------------------------ | +| `menu` | `Menu` | — | **Required.** The menu to trigger | +| `disabled` | `boolean` | `false` | Disables the trigger | +| `softDisabled` | `boolean` | `true` | When `true`, disabled trigger is focusable | + +#### Signals + +| Property | Type | Description | +| ---------- | ----------------- | ------------------------------------------ | +| `expanded` | `Signal` | Whether the menu is currently open | +| `hasPopup` | `Signal` | Whether the trigger has an associated menu | + +#### Methods + +| Method | Parameters | Description | +| -------- | ---------- | ---------------------------- | +| `open` | none | Opens the menu | +| `close` | none | Closes the menu | +| `toggle` | none | Toggles the menu open/closed | + +## Styling + +Angular automatically applies attributes to menu elements that you can use in your CSS selectors. + +The menu container receives the `ng-menu` attribute: + +```css +[ng-menu] { + background: white; + border: 1px solid #ccc; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + padding: 0.5rem 0; + min-width: 200px; +} +``` + +Menu items receive the `ng-menu-item` attribute and a `data-active` attribute when focused: + +```css +[ng-menu-item] { + padding: 0.5rem 1rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; +} + +[ng-menu-item][data-active] { + background: var(--hover-background); +} + +[ng-menu-item][aria-disabled="true"] { + opacity: 0.5; + cursor: not-allowed; +} +``` + +Style menu items with submenus using the `aria-haspopup` attribute: + +```css +[ng-menu-item][aria-haspopup="menu"]::after { + content: '▶'; + margin-left: auto; +} +``` + +Menubars receive the `ng-menu-bar` attribute: + +```css +[ng-menu-bar] { + display: flex; + gap: 0.5rem; + background: var(--toolbar-background); + padding: 0.5rem; +} +``` + +TIP: Use `[data-active]` to style focused menu items and `[aria-expanded="true"]` to style open menu triggers. diff --git a/adev/src/content/guide/aria/tabs.md b/adev/src/content/guide/aria/tabs.md index 1919556952a..9722815cfa9 100644 --- a/adev/src/content/guide/aria/tabs.md +++ b/adev/src/content/guide/aria/tabs.md @@ -1,22 +1,252 @@ - +## Overview + +Tabs display layered content sections where only one panel is visible at a time. Users switch between panels by clicking tab buttons or using arrow keys to navigate the tab list. + + + + + + + - + - +Set `[selectionMode]="'follow'"` on the tab list to enable this behavior. -### Example with TailwindCSS +### Manual activation - +With manual activation, arrow keys move focus between tabs without changing the selected tab. Users press Space or Enter to activate the focused tab. + + + + + + +Use `[selectionMode]="'explicit'"` for heavy content panels to avoid unnecessary rendering. + +### Vertical tabs + +Arrange tabs vertically for interfaces like settings panels or navigation sidebars. + + + + + + +Set `[orientation]="'vertical'"` on the tab list. Navigation changes to Up/Down arrow keys. + +### Lazy content rendering + +Use the `ngTabContent` directive on an `ng-template` to defer rendering tab panels until they're first shown. + +```angular-html +
+
    +
  • Tab 1
  • +
  • Tab 2
  • +
+ +
+ + + + +
+ +
+ + + + +
+
+``` + +By default, content remains in the DOM after the panel is hidden. Set `[preserveContent]="false"` to remove content when the panel is deactivated. + +### Disabled tabs + +Disable specific tabs to prevent user interaction. Control whether disabled tabs can receive keyboard focus. + + + + + + +When `[softDisabled]="true"` on the tab list, disabled tabs can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled tabs are skipped during keyboard navigation. + +## Showcase + +TBD + +## APIs + +### Tabs + +The container directive that coordinates tab lists and panels. + +This directive has no inputs or outputs. It serves as the root container for `ngTabList`, `ngTab`, and `ngTabPanel` directives. + +### TabList + +The container for tab buttons that manages selection and keyboard navigation. + +#### Inputs + +| Property | Type | Default | Description | +| --------------- | ---------------------------- | -------------- | ------------------------------------------------------------------ | +| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Tab list layout direction | +| `wrap` | `boolean` | `false` | Whether keyboard navigation wraps from last to first tab | +| `softDisabled` | `boolean` | `true` | When `true`, disabled tabs are focusable but not activatable | +| `selectionMode` | `'follow' \| 'explicit'` | `'follow'` | Whether tabs activate on focus or require explicit activation | +| `selectedTab` | `any` | — | The value of the currently selected tab (supports two-way binding) | + +### Tab + +An individual tab button. + +#### Inputs + +| Property | Type | Default | Description | +| ---------- | --------- | ------- | --------------------------------------- | +| `value` | `any` | — | **Required.** Unique value for this tab | +| `disabled` | `boolean` | `false` | Disables this tab | + +#### Signals + +| Property | Type | Description | +| ---------- | ----------------- | ------------------------------------- | +| `selected` | `Signal` | Whether the tab is currently selected | +| `active` | `Signal` | Whether the tab currently has focus | + +### TabPanel + +The content panel associated with a tab. + +#### Inputs + +| Property | Type | Default | Description | +| ----------------- | --------- | ------- | ---------------------------------------------------------- | +| `value` | `any` | — | **Required.** Must match the `value` of the associated tab | +| `preserveContent` | `boolean` | `true` | Whether to keep panel content in DOM after deactivation | + +#### Signals + +| Property | Type | Description | +| --------- | ----------------- | -------------------------------------- | +| `visible` | `Signal` | Whether the panel is currently visible | + +### TabContent + +A structural directive for lazy rendering tab panel content. + +This directive has no inputs, outputs, or methods. Apply it to an `ng-template` element inside a tab panel: + +```angular-html +
+ + + +
+``` + +## Styling + +Angular automatically applies attributes to tab elements that you can use in your CSS selectors. + +The tab list receives the `ng-tab-list` attribute: + +```css +[ng-tab-list] { + display: flex; + gap: 0.5rem; + border-bottom: 2px solid #ddd; +} + +[ng-tab-list][aria-orientation="vertical"] { + flex-direction: column; + border-bottom: none; + border-right: 2px solid #ddd; +} +``` + +Tabs receive the `ng-tab` attribute with `data-active` when focused and `aria-selected` when selected: + +```css +[ng-tab] { + padding: 0.75rem 1.5rem; + cursor: pointer; + border: 1px solid transparent; + background: transparent; +} + +[ng-tab][data-active] { + outline: 2px solid var(--focus-color); +} + +[ng-tab][aria-selected="true"] { + border-color: var(--primary-color); + border-bottom-color: white; + background: white; + font-weight: 600; +} +``` + +Tab panels receive the `ng-tab-panel` attribute: + +```css +[ng-tab-panel] { + padding: 1.5rem; +} + +[ng-tab-panel][hidden] { + display: none; +} +``` + +TIP: Use `[data-active]` to style the focused tab and `[aria-selected="true"]` to style the selected tab. These are often the same tab but can differ in manual activation mode. diff --git a/adev/src/content/guide/aria/tree.md b/adev/src/content/guide/aria/tree.md index ca5d76542c2..60253d4cd57 100644 --- a/adev/src/content/guide/aria/tree.md +++ b/adev/src/content/guide/aria/tree.md @@ -1,22 +1,241 @@ - - - - - +## Overview - + + + + -### Example with TailwindCSS +## Usage - +Trees work well for displaying hierarchical data where users need to navigate through nested structures. + +**Use trees when:** + +- Building file system navigation +- Showing folder and document hierarchies +- Creating nested menu structures +- Displaying organization charts +- Browsing hierarchical data +- Implementing site navigation with nested sections + +**Avoid trees when:** + +- Displaying flat lists (use [Listbox](guide/aria/listbox) instead) +- Showing data tables (use [Grid](guide/aria/grid) instead) +- Creating simple dropdowns (use [Select](guide/aria/select) instead) +- Building breadcrumb navigation (use breadcrumb patterns) + +## Features + +- **Hierarchical navigation** - Nested tree structure with expand and collapse functionality +- **Selection modes** - Single or multi-selection with explicit or follow-focus behavior +- **Selection follows focus** - Optional automatic selection when focus changes +- **Keyboard navigation** - Arrow keys, Home, End, and type-ahead search +- **Expand/collapse** - Right/Left arrows or Enter to toggle parent nodes +- **Disabled items** - Disable specific nodes with focus management +- **Focus modes** - Roving tabindex or activedescendant focus strategies +- **RTL support** - Right-to-left language navigation + +## Examples + +### Navigation tree + +Use a tree for navigation where clicking items triggers actions rather than selecting them. + + + + + + +Set `[nav]="true"` to enable navigation mode. This uses `aria-current` to indicate the current page instead of selection. + +### Single selection + +Enable single selection for scenarios where users choose one item from the tree. + + + + + + +Leave `[multi]="false"` (the default) for single selection. Users press Space to select the focused item. + +### Multi-selection + +Allow users to select multiple items from the tree. + + + + + + +Set `[multi]="true"` on the tree. Users select items individually with Space or select ranges with Shift+Arrow keys. + +### Selection follows focus + +When selection follows focus, the focused item is automatically selected. This simplifies interaction for navigation scenarios. + + + + + + +Set `[selectionMode]="'follow'"` on the tree. Selection automatically updates as users navigate with arrow keys. + +### Disabled tree items + +Disable specific tree nodes to prevent interaction. Control whether disabled items can receive focus. + + + + + + +When `[softDisabled]="true"` on the tree, disabled items can receive focus but cannot be activated or selected. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation. + +## Showcase + +TBD + +## APIs + +### Tree + +The container directive that manages hierarchical navigation and selection. + +#### Inputs + +| Property | Type | Default | Description | +| --------------- | -------------------------------- | ------------ | ------------------------------------------------------------- | +| `disabled` | `boolean` | `false` | Disables the entire tree | +| `softDisabled` | `boolean` | `true` | When `true`, disabled items are focusable but not interactive | +| `multi` | `boolean` | `false` | Whether multiple items can be selected | +| `selectionMode` | `'explicit' \| 'follow'` | `'explicit'` | Whether selection requires explicit action or follows focus | +| `nav` | `boolean` | `false` | Whether the tree is in navigation mode (uses `aria-current`) | +| `wrap` | `boolean` | `true` | Whether keyboard navigation wraps from last to first item | +| `focusMode` | `'roving' \| 'activedescendant'` | `'roving'` | Focus strategy used by the tree | +| `values` | `any[]` | `[]` | Selected item values (supports two-way binding) | + +#### Methods + +| Method | Parameters | Description | +| ---------------- | ---------- | --------------------------------------------- | +| `expandAll` | none | Expands all tree nodes | +| `collapseAll` | none | Collapses all tree nodes | +| `selectAll` | none | Selects all items (only in multi-select mode) | +| `clearSelection` | none | Clears all selection | + +### TreeItem + +An individual node in the tree that can contain child nodes. + +#### Inputs + +| Property | Type | Default | Description | +| ---------- | --------- | ------- | ------------------------------------------------------- | +| `value` | `any` | — | **Required.** Unique value for this tree item | +| `disabled` | `boolean` | `false` | Disables this item | +| `expanded` | `boolean` | `false` | Whether the node is expanded (supports two-way binding) | + +#### Signals + +| Property | Type | Description | +| ------------- | ----------------- | ------------------------------------ | +| `selected` | `Signal` | Whether the item is selected | +| `active` | `Signal` | Whether the item currently has focus | +| `hasChildren` | `Signal` | Whether the item has child nodes | + +#### Methods + +| Method | Parameters | Description | +| ---------- | ---------- | --------------------------- | +| `expand` | none | Expands this node | +| `collapse` | none | Collapses this node | +| `toggle` | none | Toggles the expansion state | + +### TreeGroup + +A container for child tree items. + +This directive has no inputs, outputs, or methods. It serves as a container to organize child `ngTreeItem` elements: + +```angular-html +
  • + Parent Item +
      +
    • Child 1
    • +
    • Child 2
    • +
    +
  • +``` + +## Styling + +Angular automatically applies attributes to tree elements that you can use in your CSS selectors. + +The tree receives the `ng-tree` attribute: + +```css +[ng-tree] { + list-style: none; + padding: 0; + margin: 0; +} +``` + +Tree items receive the `ng-tree-item` attribute with `data-active` when focused and `aria-selected` when selected: + +```css +[ng-tree-item] { + padding: 0.5rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; +} + +[ng-tree-item][data-active] { + outline: 2px solid var(--focus-color); +} + +[ng-tree-item][aria-selected="true"] { + background: var(--selected-background); + font-weight: 600; +} +``` + +Tree groups receive the `ng-tree-group` attribute for indenting child items: + +```css +[ng-tree-group] { + padding-left: 1.5rem; + list-style: none; +} +``` + +Style expandable items using the `aria-expanded` attribute: + +```css +/* Expand/collapse indicator */ +[ng-tree-item][aria-expanded]::before { + content: '▶'; + transition: transform 0.2s; +} + +[ng-tree-item][aria-expanded="true"]::before { + transform: rotate(90deg); +} +``` + +TIP: Use `[aria-expanded]` to determine if an item is expandable and style its expand/collapse indicator accordingly.