diff --git a/resources/js/components/attachments.js b/resources/js/components/attachments.js index b4e400aeb..d8a506270 100644 --- a/resources/js/components/attachments.js +++ b/resources/js/components/attachments.js @@ -45,7 +45,7 @@ export class Attachments extends Component { this.stopEdit(); /** @var {Tabs} */ const tabs = window.$components.firstOnElement(this.mainTabs, 'tabs'); - tabs.show('items'); + tabs.show('attachment-panel-items'); window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => { this.list.innerHTML = resp.data; window.$components.init(this.list); diff --git a/resources/js/components/image-manager.js b/resources/js/components/image-manager.js index a44fffc1b..418b7c98a 100644 --- a/resources/js/components/image-manager.js +++ b/resources/js/components/image-manager.js @@ -140,10 +140,9 @@ export class ImageManager extends Component { } setActiveFilterTab(filterName) { - this.filterTabs.forEach(t => t.classList.remove('selected')); - const activeTab = this.filterTabs.find(t => t.dataset.filter === filterName); - if (activeTab) { - activeTab.classList.add('selected'); + for (const tab of this.filterTabs) { + const selected = tab.dataset.filter === filterName; + tab.setAttribute('aria-selected', selected ? 'true' : 'false'); } } diff --git a/resources/js/components/tabs.js b/resources/js/components/tabs.js index 46063d240..ebab4191c 100644 --- a/resources/js/components/tabs.js +++ b/resources/js/components/tabs.js @@ -1,48 +1,46 @@ -import {onSelect} from "../services/dom"; import {Component} from "./component"; /** * Tabs - * Works by matching 'tabToggle' with 'tabContent' sections. + * Uses accessible attributes to drive its functionality. + * On tab wrapping element: + * - role=tablist + * On tabs (Should be a button): + * - id + * - role=tab + * - aria-selected=true/false + * - aria-controls= + * On panels: + * - id + * - tabindex=0 + * - role=tabpanel + * - aria-labelledby= + * - hidden (If not shown by default). */ export class Tabs extends Component { setup() { - this.tabContentsByName = {}; - this.tabButtonsByName = {}; - this.allContents = []; - this.allButtons = []; + this.container = this.$el; + this.tabs = Array.from(this.container.querySelectorAll('[role="tab"]')); + this.panels = Array.from(this.container.querySelectorAll('[role="tabpanel"]')); - for (const [key, elems] of Object.entries(this.$manyRefs || {})) { - if (key.startsWith('toggle')) { - const cleanKey = key.replace('toggle', '').toLowerCase(); - onSelect(elems, e => this.show(cleanKey)); - this.allButtons.push(...elems); - this.tabButtonsByName[cleanKey] = elems; + this.container.addEventListener('click', event => { + const button = event.target.closest('[role="tab"]'); + if (button) { + this.show(button.getAttribute('aria-controls')); } - if (key.startsWith('content')) { - const cleanKey = key.replace('content', '').toLowerCase(); - this.tabContentsByName[cleanKey] = elems; - this.allContents.push(...elems); - } - } + }); } - show(key) { - this.allContents.forEach(c => { - c.classList.add('hidden'); - c.classList.remove('selected'); - }); - this.allButtons.forEach(b => b.classList.remove('selected')); + show(sectionId) { + for (const panel of this.panels) { + panel.toggleAttribute('hidden', panel.id !== sectionId); + } - const contents = this.tabContentsByName[key] || []; - const buttons = this.tabButtonsByName[key] || []; - if (contents.length > 0) { - contents.forEach(c => { - c.classList.remove('hidden') - c.classList.add('selected') - }); - buttons.forEach(b => b.classList.add('selected')); + for (const tab of this.tabs) { + const tabSection = tab.getAttribute('aria-controls'); + const selected = tabSection === sectionId; + tab.setAttribute('aria-selected', selected ? 'true' : 'false'); } } diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index b902220a7..c8ecd438d 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -607,7 +607,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } -.tab-container .nav-tabs { +.tab-container [role="tablist"] { display: flex; align-items: end; justify-items: start; @@ -617,26 +617,24 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { margin-bottom: $-m; } -.nav-tabs { - text-align: center; - .tab-item { - display: inline-block; - padding: $-s; - @include lightDark(color, rgba(0, 0, 0, .5), rgba(255, 255, 255, .5)); - cursor: pointer; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - &.selected { - color: var(--color-primary) !important; - border-bottom-color: var(--color-primary) !important; - } - &:hover, &:focus { - @include lightDark(color, rgba(0, 0, 0, .8), rgba(255, 255, 255, .8)); - @include lightDark(border-bottom-color, rgba(0, 0, 0, .2), rgba(255, 255, 255, .2)); - } +.tab-container [role="tablist"] button[role="tab"], +.image-manager [role="tablist"] button[role="tab"] { + display: inline-block; + padding: $-s; + @include lightDark(color, rgba(0, 0, 0, .5), rgba(255, 255, 255, .5)); + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + &[aria-selected="true"] { + color: var(--color-primary) !important; + border-bottom-color: var(--color-primary) !important; + } + &:hover, &:focus { + @include lightDark(color, rgba(0, 0, 0, .8), rgba(255, 255, 255, .8)); + @include lightDark(border-bottom-color, rgba(0, 0, 0, .2), rgba(255, 255, 255, .2)); } } -.nav-tabs.controls-card { +.tab-container [role="tablist"].controls-card { margin-bottom: 0; border-bottom: 0; padding: 0 $-xs; diff --git a/resources/views/attachments/manager.blade.php b/resources/views/attachments/manager.blade.php index 724ca9c8e..7d14d00e7 100644 --- a/resources/views/attachments/manager.blade.php +++ b/resources/views/attachments/manager.blade.php @@ -9,25 +9,54 @@
-

{{ trans('entities.attachments_explain') }} {{ trans('entities.attachments_explain_instant_save') }}

+

{{ trans('entities.attachments_explain') }} {{ trans('entities.attachments_explain_instant_save') }}

-