From 903cb23df9d350bfa76ee510eb8e55fc8d89952f Mon Sep 17 00:00:00 2001 From: Wagner Maciel Date: Mon, 17 Nov 2025 23:42:48 -0500 Subject: [PATCH] docs: aria tabs examples (cherry picked from commit 04859b41a2c90cb81cfbec5a0dfa96129a8ba306) --- .../tabs/src/disabled-focusable/app/app.css | 9 -- .../tabs/src/disabled-focusable/app/app.html | 1 - .../aria/tabs/src/disabled/app/app.css | 84 ++++++++++ .../aria/tabs/src/disabled/app/app.html | 21 +++ .../app/app.ts | 2 + .../tabs/src/disabled/material/app/app.css | 103 ++++++++++++ .../tabs/src/disabled/material/app/app.html | 22 +++ .../material}/app/app.ts | 2 + .../aria/tabs/src/disabled/retro/app/app.css | 106 +++++++++++++ .../aria/tabs/src/disabled/retro/app/app.html | 19 +++ .../aria/tabs/src/disabled/retro/app/app.ts | 10 ++ .../tabs/src/explicit-selection/app/app.css | 85 +++++++++- .../tabs/src/explicit-selection/app/app.html | 22 ++- .../tabs/src/explicit-selection/app/app.ts | 2 + .../explicit-selection/material/app/app.css | 97 ++++++++++++ .../explicit-selection/material/app/app.html | 22 +++ .../explicit-selection/material/app/app.ts | 10 ++ .../src/explicit-selection/retro/app/app.css | 101 ++++++++++++ .../src/explicit-selection/retro/app/app.html | 19 +++ .../src/explicit-selection/retro/app/app.ts | 10 ++ .../src/selection-follows-focus/app/app.css | 85 +++++++++- .../src/selection-follows-focus/app/app.html | 22 ++- .../src/selection-follows-focus/app/app.ts | 2 + .../material/app/app.css | 97 ++++++++++++ .../material/app/app.html | 22 +++ .../material/app/app.ts | 10 ++ .../selection-follows-focus/retro/app/app.css | 101 ++++++++++++ .../retro/app/app.html | 19 +++ .../selection-follows-focus/retro/app/app.ts | 10 ++ .../tabs/src/vertical-orientation/app/app.css | 9 -- .../src/vertical-orientation/app/app.html | 1 - .../aria/tabs/src/vertical/app/app.css | 83 ++++++++++ .../aria/tabs/src/vertical/app/app.html | 21 +++ .../aria/tabs/src/vertical/app/app.ts | 10 ++ .../tabs/src/vertical/material/app/app.css | 104 +++++++++++++ .../tabs/src/vertical/material/app/app.html | 22 +++ .../tabs/src/vertical/material/app/app.ts | 10 ++ .../aria/tabs/src/vertical/retro/app/app.css | 103 ++++++++++++ .../aria/tabs/src/vertical/retro/app/app.html | 19 +++ .../aria/tabs/src/vertical/retro/app/app.ts | 10 ++ adev/src/content/guide/aria/tabs.md | 146 +++++++++++++++--- 41 files changed, 1594 insertions(+), 59 deletions(-) delete mode 100644 adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.css delete 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/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/disabled/app/app.html rename adev/src/content/examples/aria/tabs/src/{vertical-orientation => disabled}/app/app.ts (54%) create mode 100644 adev/src/content/examples/aria/tabs/src/disabled/material/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/disabled/material/app/app.html rename adev/src/content/examples/aria/tabs/src/{disabled-focusable => disabled/material}/app/app.ts (54%) create mode 100644 adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.ts delete mode 100644 adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.css delete 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/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/material/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/material/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/material/app/app.ts create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.html create mode 100644 adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.ts 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 deleted file mode 100644 index bc6e9c409ad..00000000000 --- a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.css +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index d39d7de603b..00000000000 --- a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.html +++ /dev/null @@ -1 +0,0 @@ -

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tabs/src/disabled/app/app.css b/adev/src/content/examples/aria/tabs/src/disabled/app/app.css new file mode 100644 index 00000000000..7ef539cc77f --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/app/app.css @@ -0,0 +1,84 @@ +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + border-radius: 0.5rem; + border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTabList] { + padding: 0; + display: flex; + list-style: none; + position: relative; + border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + padding: 0.75rem 0; + cursor: pointer; + text-align: center; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); +} + +[ngTab][aria-disabled='true'] { + cursor: default; + color: color-mix(in srgb, var(--primary-contrast) 30%, transparent); + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +[ngTab][aria-selected='false']:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab][aria-disabled='false'][aria-selected='false']:hover { + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.sliding-window { + width: 300%; + display: flex; + transition: all 0.2s ease-in-out; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateX(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + min-height: 100px; + flex: 1; + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); +} diff --git a/adev/src/content/examples/aria/tabs/src/disabled/app/app.html b/adev/src/content/examples/aria/tabs/src/disabled/app/app.html new file mode 100644 index 00000000000..1f9ceb37583 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/app/app.html @@ -0,0 +1,21 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
diff --git a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts b/adev/src/content/examples/aria/tabs/src/disabled/app/app.ts similarity index 54% rename from adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts rename to adev/src/content/examples/aria/tabs/src/disabled/app/app.ts index 183576f2c24..a7c6e50dcae 100644 --- a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.ts +++ b/adev/src/content/examples/aria/tabs/src/disabled/app/app.ts @@ -1,8 +1,10 @@ import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], }) export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.css b/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.css new file mode 100644 index 00000000000..87d1bd4c032 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.css @@ -0,0 +1,103 @@ +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + border-radius: 0.5rem; + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTabList] { + padding: 0; + display: flex; + list-style: none; + position: relative; + border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem 0; + cursor: pointer; + text-align: center; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); +} + +[ngTab][aria-disabled='true'] { + cursor: default; + color: color-mix(in srgb, var(--primary-contrast) 30%, transparent); + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +[ngTab]:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--bright-blue); +} + +.bottom-border { + position: absolute; + pointer-events: none; + left: 0; + bottom: 0; + height: 3px; + width: calc(100% / 3); + background-color: var(--bright-blue); + transition: all 0.2s ease-in-out; + transform: translateX(0%); + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border { + transform: translateX(0%); +} +[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border { + transform: translateX(100%); +} +[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border { + transform: translateX(200%); +} + +.sliding-window { + width: 300%; + display: flex; + transition: all 0.2s ease-in-out; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateX(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + min-height: 100px; + flex: 1; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); +} diff --git a/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.html b/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.html new file mode 100644 index 00000000000..8607a86a2ef --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.html @@ -0,0 +1,22 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
diff --git a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts b/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.ts similarity index 54% rename from adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts rename to adev/src/content/examples/aria/tabs/src/disabled/material/app/app.ts index 183576f2c24..a7c6e50dcae 100644 --- a/adev/src/content/examples/aria/tabs/src/disabled-focusable/app/app.ts +++ b/adev/src/content/examples/aria/tabs/src/disabled/material/app/app.ts @@ -1,8 +1,10 @@ import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], }) export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css new file mode 100644 index 00000000000..e8cbc813695 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css @@ -0,0 +1,106 @@ +@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-size: 0.8rem; + + font-family: 'Press Start 2P'; + --retro-button-color: var(--bright-blue); + --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); + --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); + --retro-elevated-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700); + --retro-flat-shadow: + 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), + -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + --retro-clickable-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + --retro-pressed-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-dark), + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); +} + +[ngTabs] { + width: 600px; +} + +[ngTabList] { + gap: 1rem; + display: flex; +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem; + cursor: pointer; + text-align: center; + color: #000; + background-color: #fff; + box-shadow: var(--retro-clickable-shadow); + transition: + transform 0.1s, + box-shadow 0.1s; +} + +[ngTab][aria-disabled='true'] { + cursor: default; + color: color-mix(in srgb, #000 30%, transparent); +} + +[ngTab][aria-disabled='false'][aria-selected='false']:focus, +[ngTab][aria-disabled='false'][aria-selected='false']:hover { + transform: translate(1px, 1px); +} + +[ngTab]:focus { + outline-offset: 2px; + outline: 4px dashed var(--bright-blue); +} + +[ngTab][aria-disabled='false'][aria-selected='false']:hover { + background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff); +} + +[ngTab][aria-disabled='false']:active, +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + box-shadow: var(--retro-pressed-shadow); + transform: translate(4px, 4px); +} + +[ngTabPanel] { + flex: 1; + font-size: 1.5rem; + color: #000; + background-color: #fff; + padding: 1rem; + margin-top: 1rem; + min-height: 100px; + display: grid; + place-items: center; + box-shadow: var(--retro-flat-shadow); + background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%); + background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff); + background-size: 100% 4px; +} + +[ngTabPanel]:focus { + outline-offset: 4px; + outline: 4px dashed var(--bright-blue); +} + +[ngTabPanel][inert] { + display: none; +} diff --git a/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.html b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.html new file mode 100644 index 00000000000..157f05ca0c4 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.html @@ -0,0 +1,19 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+ +
+ Line 1 +
+ +
+ Line 2 +
+ +
+ Line 3 +
+
diff --git a/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.ts b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +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 index bc6e9c409ad..dd89d85c34c 100644 --- 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 @@ -1,9 +1,78 @@ -h1 { - background: #1e1e1e; - color: #e0e0e0; - padding: 2rem; - margin: 0; - font-family: system-ui, -apple-system, sans-serif; - font-size: 2rem; - text-align: center; +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + border-radius: 0.5rem; + border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTabList] { + padding: 0; + display: flex; + list-style: none; + position: relative; + border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + padding: 0.75rem 0; + cursor: pointer; + text-align: center; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); +} + +[ngTab][aria-selected='false']:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.sliding-window { + width: 300%; + display: flex; + transition: all 0.2s ease-in-out; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateX(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + min-height: 100px; + flex: 1; + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); } 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 index d39d7de603b..6b86a7bee2e 100644 --- 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 @@ -1 +1,21 @@ -

PLACEHOLDER

+
+
+
Movie
+
Cast
+
Reviews
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
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 index 183576f2c24..a7c6e50dcae 100644 --- 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 @@ -1,8 +1,10 @@ import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], }) export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.css b/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.css new file mode 100644 index 00000000000..eecf7dc05bc --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.css @@ -0,0 +1,97 @@ +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + border-radius: 0.5rem; + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTabList] { + padding: 0; + display: flex; + list-style: none; + position: relative; + border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem 0; + cursor: pointer; + text-align: center; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); +} + +[ngTab]:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--bright-blue); +} + +.bottom-border { + position: absolute; + pointer-events: none; + left: 0; + bottom: 0; + height: 3px; + width: calc(100% / 3); + background-color: var(--bright-blue); + transition: all 0.2s ease-in-out; + transform: translateX(0%); + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border { + transform: translateX(0%); +} +[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border { + transform: translateX(100%); +} +[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border { + transform: translateX(200%); +} + +.sliding-window { + width: 300%; + display: flex; + transition: all 0.2s ease-in-out; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateX(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + min-height: 100px; + flex: 1; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); +} diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.html b/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.html new file mode 100644 index 00000000000..077465eb905 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.html @@ -0,0 +1,22 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.ts b/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/material/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css new file mode 100644 index 00000000000..03d13fdffa5 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css @@ -0,0 +1,101 @@ +@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-size: 0.8rem; + + font-family: 'Press Start 2P'; + --retro-button-color: var(--bright-blue); + --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); + --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); + --retro-elevated-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700); + --retro-flat-shadow: + 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), + -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + --retro-clickable-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + --retro-pressed-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-dark), + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); +} + +[ngTabs] { + width: 600px; +} + +[ngTabList] { + gap: 1rem; + display: flex; +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem; + cursor: pointer; + text-align: center; + color: #000; + background-color: #fff; + box-shadow: var(--retro-clickable-shadow); + transition: + transform 0.1s, + box-shadow 0.1s; +} + +[ngTab]:focus, +[ngTab]:hover { + transform: translate(1px, 1px); +} + +[ngTab]:focus { + outline-offset: 2px; + outline: 4px dashed var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff); +} + +[ngTab]:active, +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + box-shadow: var(--retro-pressed-shadow); + transform: translate(4px, 4px); +} + +[ngTabPanel] { + flex: 1; + font-size: 1.5rem; + color: #000; + background-color: #fff; + padding: 1rem; + margin-top: 1rem; + min-height: 100px; + display: grid; + place-items: center; + box-shadow: var(--retro-flat-shadow); + background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%); + background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff); + background-size: 100% 4px; +} + +[ngTabPanel]:focus { + outline-offset: 4px; + outline: 4px dashed var(--bright-blue); +} + +[ngTabPanel][inert] { + display: none; +} diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.html b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.html new file mode 100644 index 00000000000..acd3b75e7f1 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.html @@ -0,0 +1,19 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+ +
+ Line 1 +
+ +
+ Line 2 +
+ +
+ Line 3 +
+
diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.ts b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +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 index bc6e9c409ad..dd89d85c34c 100644 --- 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 @@ -1,9 +1,78 @@ -h1 { - background: #1e1e1e; - color: #e0e0e0; - padding: 2rem; - margin: 0; - font-family: system-ui, -apple-system, sans-serif; - font-size: 2rem; - text-align: center; +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + border-radius: 0.5rem; + border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTabList] { + padding: 0; + display: flex; + list-style: none; + position: relative; + border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + padding: 0.75rem 0; + cursor: pointer; + text-align: center; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); +} + +[ngTab][aria-selected='false']:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.sliding-window { + width: 300%; + display: flex; + transition: all 0.2s ease-in-out; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateX(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + min-height: 100px; + flex: 1; + border-bottom-right-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); } 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 index d39d7de603b..0cec4a7383c 100644 --- 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 @@ -1 +1,21 @@ -

PLACEHOLDER

+
+
+
Movie
+
Cast
+
Reviews
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
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 index 183576f2c24..a7c6e50dcae 100644 --- 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 @@ -1,8 +1,10 @@ import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], }) export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.css b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.css new file mode 100644 index 00000000000..eecf7dc05bc --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.css @@ -0,0 +1,97 @@ +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + border-radius: 0.5rem; + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTabList] { + padding: 0; + display: flex; + list-style: none; + position: relative; + border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem 0; + cursor: pointer; + text-align: center; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); +} + +[ngTab]:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--bright-blue); +} + +.bottom-border { + position: absolute; + pointer-events: none; + left: 0; + bottom: 0; + height: 3px; + width: calc(100% / 3); + background-color: var(--bright-blue); + transition: all 0.2s ease-in-out; + transform: translateX(0%); + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border { + transform: translateX(0%); +} +[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border { + transform: translateX(100%); +} +[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border { + transform: translateX(200%); +} + +.sliding-window { + width: 300%; + display: flex; + transition: all 0.2s ease-in-out; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateX(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateX(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + min-height: 100px; + flex: 1; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); +} diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.html b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.html new file mode 100644 index 00000000000..cef5cc87c74 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.html @@ -0,0 +1,22 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.ts b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/material/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css new file mode 100644 index 00000000000..03d13fdffa5 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css @@ -0,0 +1,101 @@ +@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-size: 0.8rem; + + font-family: 'Press Start 2P'; + --retro-button-color: var(--bright-blue); + --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); + --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); + --retro-elevated-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700); + --retro-flat-shadow: + 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), + -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + --retro-clickable-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + --retro-pressed-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-dark), + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); +} + +[ngTabs] { + width: 600px; +} + +[ngTabList] { + gap: 1rem; + display: flex; +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem; + cursor: pointer; + text-align: center; + color: #000; + background-color: #fff; + box-shadow: var(--retro-clickable-shadow); + transition: + transform 0.1s, + box-shadow 0.1s; +} + +[ngTab]:focus, +[ngTab]:hover { + transform: translate(1px, 1px); +} + +[ngTab]:focus { + outline-offset: 2px; + outline: 4px dashed var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff); +} + +[ngTab]:active, +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + box-shadow: var(--retro-pressed-shadow); + transform: translate(4px, 4px); +} + +[ngTabPanel] { + flex: 1; + font-size: 1.5rem; + color: #000; + background-color: #fff; + padding: 1rem; + margin-top: 1rem; + min-height: 100px; + display: grid; + place-items: center; + box-shadow: var(--retro-flat-shadow); + background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%); + background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff); + background-size: 100% 4px; +} + +[ngTabPanel]:focus { + outline-offset: 4px; + outline: 4px dashed var(--bright-blue); +} + +[ngTabPanel][inert] { + display: none; +} diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.html b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.html new file mode 100644 index 00000000000..d0ad6469578 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.html @@ -0,0 +1,19 @@ +
+
+
Movie
+
Cast
+
Reviews
+
+ +
+ Line 1 +
+ +
+ Line 2 +
+ +
+ Line 3 +
+
diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.ts b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +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 deleted file mode 100644 index bc6e9c409ad..00000000000 --- a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.css +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index d39d7de603b..00000000000 --- a/adev/src/content/examples/aria/tabs/src/vertical-orientation/app/app.html +++ /dev/null @@ -1 +0,0 @@ -

PLACEHOLDER

diff --git a/adev/src/content/examples/aria/tabs/src/vertical/app/app.css b/adev/src/content/examples/aria/tabs/src/vertical/app/app.css new file mode 100644 index 00000000000..32603c98687 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/app/app.css @@ -0,0 +1,83 @@ +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + font-family: var(--inter-font); + display: flex; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + height: 200px; + border-radius: 0.5rem; + border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); + display: flex; +} + +[ngTabList] { + padding: 0; + display: flex; + flex-direction: column; + list-style: none; + position: relative; + border-right: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + width: 80px; + cursor: pointer; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); + display: grid; + place-items: center; +} + +[ngTab][aria-selected='false']:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; +} + +.sliding-window { + height: 300%; + display: flex; + flex-direction: column; + transition: all 0.2s ease-in-out; + flex: 1; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateY(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateY(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateY(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + flex: 1; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); +} diff --git a/adev/src/content/examples/aria/tabs/src/vertical/app/app.html b/adev/src/content/examples/aria/tabs/src/vertical/app/app.html new file mode 100644 index 00000000000..73279b53bf2 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/app/app.html @@ -0,0 +1,21 @@ +
+
+
videocam
+
theater_comedy
+
reviews
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
diff --git a/adev/src/content/examples/aria/tabs/src/vertical/app/app.ts b/adev/src/content/examples/aria/tabs/src/vertical/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.css b/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.css new file mode 100644 index 00000000000..822039eb777 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.css @@ -0,0 +1,104 @@ +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + font-family: var(--inter-font); + display: flex;; + justify-content: center; +} + +[ngTabs] { + overflow: hidden; + width: 600px; + height: 200px; + border-radius: 0.5rem; + background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent); + display: flex; +} + +[ngTabList] { + padding: 0; + display: flex; + flex-direction: column; + list-style: none; + position: relative; + border-right: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent); +} + +[ngTab] { + flex: 1; + outline: none; + width: 80px; + cursor: pointer; + color: color-mix(in srgb, var(--primary-contrast) 60%, transparent); + display: grid; + place-items: center; +} + +[ngTab]:focus { + outline-offset: -8px; + border-radius: 0.7rem; + outline: 2px solid var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); +} + +[ngTab][aria-selected='true'] { + color: var(--bright-blue); +} + +.bottom-border { + position: absolute; + pointer-events: none; + top: 0; + right: 0; + width: 3px; + height: calc(100% / 3); + background-color: var(--bright-blue); + transition: all 0.2s ease-in-out; + transform: translateY(0%); + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border { + transform: translateY(0%); +} +[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border { + transform: translateY(100%); +} +[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border { + transform: translateY(200%); +} + +.sliding-window { + height: 300%; + display: flex; + flex-direction: column; + transition: all 0.2s ease-in-out; + flex: 1; +} + +[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window { + transform: translateY(0%); +} +[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window { + transform: translateY(-33.333%); +} +[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window { + transform: translateY(-66.666%); +} + +[ngTabPanel] { + display: grid; + place-items: center; + padding: 1rem; + flex: 1; +} + +[ngTabPanel]:focus { + outline-offset: -4px; + border-radius: 0.5rem; + outline: 2px solid var(--bright-blue); +} diff --git a/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.html b/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.html new file mode 100644 index 00000000000..ad65b1779d9 --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.html @@ -0,0 +1,22 @@ +
+
+
videocam
+
theater_comedy
+
reviews
+
+
+ +
+
+ Panel 1 +
+ +
+ Panel 2 +
+ +
+ Panel 3 +
+
+
diff --git a/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.ts b/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/material/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +export class App {} diff --git a/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css new file mode 100644 index 00000000000..7906f6f6e9f --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css @@ -0,0 +1,103 @@ +@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + display: flex; + justify-content: center; + font-size: 0.8rem; + + font-family: 'Press Start 2P'; + --retro-button-color: var(--bright-blue); + --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent); + --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); + --retro-elevated-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700); + --retro-flat-shadow: + 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), + -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + --retro-clickable-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-light), + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + --retro-pressed-shadow: + inset 4px 4px 0px 0px var(--retro-shadow-dark), + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), + 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), + 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); +} + +[ngTabs] { + width: 500px; + display: flex; +} + +[ngTabList] { + gap: 1rem; + display: flex; + flex-direction: column; +} + +[ngTab] { + flex: 1; + outline: none; + padding: 1rem 1.5rem; + cursor: pointer; + text-align: center; + color: #000; + background-color: #fff; + box-shadow: var(--retro-clickable-shadow); + transition: + transform 0.1s, + box-shadow 0.1s; +} + +[ngTab]:focus, +[ngTab]:hover { + transform: translate(1px, 1px); +} + +[ngTab]:focus { + outline-offset: 2px; + outline: 4px dashed var(--bright-blue); +} + +[ngTab]:hover { + background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff); +} + +[ngTab]:active, +[ngTab][aria-selected='true'] { + color: var(--page-background); + background-color: var(--bright-blue); + box-shadow: var(--retro-pressed-shadow); + transform: translate(4px, 4px); +} + +[ngTabPanel] { + flex: 1; + font-size: 1.5rem; + color: #000; + background-color: #fff; + padding: 1rem; + margin-left: 1rem; + min-width: 100px; + display: grid; + place-items: center; + box-shadow: var(--retro-flat-shadow); + background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%); + background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff); + background-size: 100% 4px; +} + +[ngTabPanel]:focus { + outline-offset: 4px; + outline: 4px dashed var(--bright-blue); +} + +[ngTabPanel][inert] { + display: none; +} diff --git a/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.html b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.html new file mode 100644 index 00000000000..30c1bced3cc --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.html @@ -0,0 +1,19 @@ +
+
+
videocam
+
theater_comedy
+
reviews
+
+ +
+ Line 1 +
+ +
+ Line 2 +
+ +
+ Line 3 +
+
diff --git a/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.ts b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.ts new file mode 100644 index 00000000000..a7c6e50dcae --- /dev/null +++ b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.ts @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + styleUrl: './app.css', + imports: [TabList, Tab, Tabs, TabPanel, TabContent], +}) +export class App {} diff --git a/adev/src/content/guide/aria/tabs.md b/adev/src/content/guide/aria/tabs.md index 9722815cfa9..2b09faf62a9 100644 --- a/adev/src/content/guide/aria/tabs.md +++ b/adev/src/content/guide/aria/tabs.md @@ -5,11 +5,31 @@ 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. - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -51,10 +71,31 @@ Tabs work well for organizing related content into distinct sections where users When selection follows focus, tabs activate immediately as you navigate with arrow keys. This provides instant feedback and works well for lightweight content. - - - - + + + + + + + + + + + + + + + + + + + + + + + + + Set `[selectionMode]="'follow'"` on the tab list to enable this behavior. @@ -62,10 +103,31 @@ Set `[selectionMode]="'follow'"` on the tab list to enable this behavior. 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. @@ -73,10 +135,31 @@ Use `[selectionMode]="'explicit'"` for heavy content panels to avoid unnecessary 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. @@ -113,10 +196,31 @@ By default, content remains in the DOM after the panel is hidden. Set `[preserve 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.