diff --git a/Build/Sources/Sass/backend.scss b/Build/Sources/Sass/backend.scss index 8944345aa0f1b9ff49482da1d62064a09a163049..47bae0d66666da578d5edd90d5f14061d35fafac 100644 --- a/Build/Sources/Sass/backend.scss +++ b/Build/Sources/Sass/backend.scss @@ -24,11 +24,6 @@ // @import "libs/sortable"; -// -// tablesort.js -// -@import "libs/tablesort"; - // // NProgress // diff --git a/Build/Sources/Sass/component/_root.scss b/Build/Sources/Sass/component/_root.scss index 15f0a061ff24f6e8ab82d5c39b3c86f3d98a0e3a..9007c0765e40162b60160665d361772de2ed4ae5 100644 --- a/Build/Sources/Sass/component/_root.scss +++ b/Build/Sources/Sass/component/_root.scss @@ -14,6 +14,7 @@ // Light --typo3-light-color: #{$body-color}; + --typo3-light-primary-color: #{$primary}; --typo3-light-secondary-color: #{tint-color($body-color, 40%)}; --typo3-light-bg: #{$white}; --typo3-light-border-color: #{$gray-300}; @@ -34,6 +35,7 @@ // Dark --typo3-dark-color: #{$white}; + --typo3-dark-primary-color: #{tint-color($primary, 40%)}; --typo3-dark-secondary-color: #{shade-color($white, 40%)}; --typo3-dark-bg: #{$gray-900}; --typo3-dark-border-color: #{$gray-800}; @@ -54,6 +56,7 @@ // Component --typo3-component-color: var(--typo3-light-color); + --typo3-component-primary-color: var(--typo3-light-primary-color); --typo3-component-secondary-color: var(--typo3-light-secondary-color); --typo3-component-bg: var(--typo3-light-bg); --typo3-component-link-color: var(--typo3-light-link-color); @@ -118,6 +121,7 @@ @mixin lightmode { --typo3-component-color: var(--typo3-light-color); + --typo3-component-primary-color: var(--typo3-light-primary-color); --typo3-component-secondary-color: var(--typo3-light-secondary-color); --typo3-component-bg: var(--typo3-light-bg); --typo3-component-border-color: var(--typo3-light-border-color); @@ -139,6 +143,7 @@ @mixin darkmode { --typo3-component-color: var(--typo3-dark-color); + --typo3-component-primary-color: var(--typo3-dark-primary-color); --typo3-component-secondary-color: var(--typo3-dark-secondary-color); --typo3-component-bg: var(--typo3-dark-bg); --typo3-component-border-color: var(--typo3-dark-border-color); diff --git a/Build/Sources/Sass/component/_table.scss b/Build/Sources/Sass/component/_table.scss index 4bc706d3e9d7532fb5a686e05196cccc95d8ba1f..d33dbabb4f89bcf81188ee54dab63ae473a687fb 100644 --- a/Build/Sources/Sass/component/_table.scss +++ b/Build/Sources/Sass/component/_table.scss @@ -277,3 +277,28 @@ td.selected { width: auto; } } + +/** + * Sorting + */ +.table-sorting-button { + padding: 0; + border: 0; + font-weight: inherit; + font-size: inherit; + background: transparent; + display: inline-flex; + align-items: center; + gap: .25em; +} + +.table-sorting-icon { + display: flex; + align-items: center; + justify-content: center; + color: var(--typo3-light-secondary-color); + + .table-sorting-button-active & { + color: var(--typo3-light-primary-color); + } +} diff --git a/Build/Sources/Sass/libs/_tablesort.scss b/Build/Sources/Sass/libs/_tablesort.scss deleted file mode 100644 index de5c47d623efc8d1f5f3c49f6bcd0c655952dd3a..0000000000000000000000000000000000000000 --- a/Build/Sources/Sass/libs/_tablesort.scss +++ /dev/null @@ -1,32 +0,0 @@ -// CSS based on https://github.com/tristen/tablesort/blob/gh-pages/tablesort.css -th[role=columnheader]:not([data-sort-method="none"]) { - cursor: pointer; - position: relative; - background-clip: padding-box; - - &:after { - content: ''; - position: absolute; - margin: 7px 0 0 3px; - border-width: 4px 4px 0; - border-style: solid; - border-color: #404040 transparent; - visibility: hidden; - opacity: 0; - user-select: none; - } - - &:hover:after { - visibility: visible; - opacity: 1; - } -} - -th[aria-sort="descending"]:not([data-sort-method="none"]):after { - border-width: 0 4px 4px; -} - -th[aria-sort]:not([data-sort-method="none"]):after { - visibility: visible; - opacity: .4; -} diff --git a/Build/Sources/TypeScript/backend/sortable-table.ts b/Build/Sources/TypeScript/backend/sortable-table.ts index 1baaa8850d0c9c7caa5d38cc99a1ba134ec2bb26..7e377786c95872b2f32fd2d50089b7f476434fa2 100644 --- a/Build/Sources/TypeScript/backend/sortable-table.ts +++ b/Build/Sources/TypeScript/backend/sortable-table.ts @@ -11,12 +11,114 @@ * The TYPO3 project - inspiring people to share! */ -import Tablesort from 'tablesort'; +import Tablesort, { TablesortOptions } from 'tablesort'; import 'tablesort.dotsep'; import 'tablesort.number'; +import { IconElement } from './element/icon-element'; +import { Sizes } from './enum/icon-types'; + +class TablesortWithButtons extends Tablesort { + public init(el: HTMLTableElement, options: TablesortOptions) { + let firstRow: HTMLTableRowElement, defaultSort; + + this.table = el; + this.thead = false; + this.options = options; + + if (el.rows && el.rows.length > 0) { + if (el.tHead && el.tHead.rows.length > 0) { + for (let i = 0; i < el.tHead.rows.length; i++) { + if (el.tHead.rows[i].getAttribute('data-sort-method') === 'thead') { + firstRow = el.tHead.rows[i]; + break; + } + } + if (!firstRow) { + firstRow = el.tHead.rows[el.tHead.rows.length - 1]; + } + this.thead = true; + } else { + firstRow = el.rows[0]; + } + } + + if (!firstRow) { + return; + } + + const onClick = (event: PointerEvent): void => { + event.preventDefault(); + event.stopImmediatePropagation(); + const target = (event.currentTarget as HTMLButtonElement).parentNode as HTMLTableCellElement; + if (this.current && this.current !== target) { + this.current.removeAttribute('aria-sort'); + } + + this.current = target; + this.sortTable(target); + }; + + // Assume first row is the header and attach a click handler to each. + for (let i = 0; i < firstRow.cells.length; i++) { + const cell = firstRow.cells[i] as HTMLTableCellElement; + cell.setAttribute('role', 'columnheader'); + if (cell.getAttribute('data-sort-method') !== 'none') { + + const button = document.createElement('button'); + button.classList.add('table-sorting-button'); + + const labelWrap = document.createElement('span'); + labelWrap.classList.add('table-sorting-label'); + labelWrap.textContent = cell.textContent; + button.appendChild(labelWrap); + + const iconWrap = document.createElement('span'); + iconWrap.classList.add('table-sorting-icon'); + const icon = new IconElement(); + icon.identifier = 'actions-sort-amount'; + icon.size = Sizes.small; + iconWrap.appendChild(icon); + button.appendChild(iconWrap); + + button.addEventListener('click', onClick, false); + cell.replaceChildren(button); + + if (cell.getAttribute('data-sort-default') !== null) { + defaultSort = cell; + } + } + } + + if (defaultSort) { + this.current = defaultSort; + this.sortTable(defaultSort); + } + } +} export default class SortableTable { constructor(table: HTMLTableElement) { - new Tablesort(table); + new TablesortWithButtons(table); + + table.addEventListener('afterSort', (event: CustomEvent) => { + const table = (event.target as HTMLTableElement); + const buttons = table.tHead.querySelectorAll('.table-sorting-button'); + buttons.forEach((button) => { + button.classList.remove('table-sorting-button-active'); + const icon = button.querySelector('typo3-backend-icon'); + icon.identifier = 'actions-sort-amount'; + }); + + const sortingCell = table.tHead.querySelector('th[aria-sort]') as HTMLTableCellElement; + const sortingButton = sortingCell.querySelector('.table-sorting-button'); + sortingButton.classList.add('table-sorting-button-active'); + + const sortingIcon = sortingButton.querySelector('typo3-backend-icon'); + if (sortingCell.ariaSort === 'ascending') { + sortingIcon.identifier = 'actions-sort-amount-down'; + } else { + sortingIcon.identifier = 'actions-sort-amount-up'; + } + }); } } diff --git a/Build/types/tablesort/index.d.ts b/Build/types/tablesort/index.d.ts index 81687fbeb8389809fc3d0147d6a94e195e30f58d..e150384dacd89c4eca69c7ba0fe71590056b2043 100644 --- a/Build/types/tablesort/index.d.ts +++ b/Build/types/tablesort/index.d.ts @@ -1,18 +1,14 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface Tablesort { -} - -type TablesortOptions = { +export type TablesortOptions = { descending: boolean; sortAttribute: string; }; -declare const Tablesort: { - new(table: Element, options?: {[key: string]: TablesortOptions}): Tablesort; - // eslint-disable-next-line @typescript-eslint/ban-types - extend(name: string, pattern: Function, sort: Function): void; -} +export default class Tablesort { + protected table: HTMLTableElement; + protected thead: boolean; + protected options: TablesortOptions; + protected current: HTMLTableCellElement; -declare module 'tablesort' { - export default Tablesort; + constructor(table: HTMLElement, options?: {[key: string]: TablesortOptions}); + protected sortTable(table: HTMLTableCellElement): void; } diff --git a/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php index 0d83a8ff65b7ff400101bfbce545dbc1f3d689b0..5d5285ccd952451ee3a6ae4d9e7691e1bb872395 100644 --- a/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php +++ b/typo3/sysext/backend/Classes/RecordList/DatabaseRecordList.php @@ -1987,31 +1987,42 @@ class DatabaseRecordList * It will automatically detect if sorting should be ascending or descending depending on $this->sortRev. * Also some fields will not be possible to sort (including if single-table-view is disabled). * - * @param string $code The string to link (text) + * @param string $label The string to link (text) * @param string $field The fieldname represented by the title ($code) * @param string $table Table name * @return string Linked $code variable */ - public function addSortLink($code, $field, $table) + public function addSortLink($label, $field, $table): string { // Certain circumstances just return string right away (no links): if ($this->disableSingleTableView || in_array($field, ['_SELECTOR', '_CONTROL_', '_LOCALIZATION_', '_REF_'], true) ) { - return $code; + return $label; } + // If "_PATH_" (showing record path) is selected, force sorting by pid field (will at least group the records!) if ($field === '_PATH_') { $field = 'pid'; } - // Create the sort link: - $sortUrl = $this->listURL('', $table, 'sortField,sortRev,table,pointer') + + // Create the sort link: + $url = $this->listURL('', $table, 'sortField,sortRev,table,pointer') . '&sortField=' . $field . '&sortRev=' . ($this->sortRev || $this->sortField != $field ? 0 : 1); - $sortArrow = $this->sortField === $field - ? $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render() - : ''; + $icon = $this->sortField === $field + ? $this->iconFactory->getIcon('actions-sort-amount-' . ($this->sortRev ? 'down' : 'up'), Icon::SIZE_SMALL)->render() + : $this->iconFactory->getIcon('actions-sort-amount', Icon::SIZE_SMALL)->render(); + // Return linked field: - return '<a href="' . htmlspecialchars($sortUrl) . '">' . $code . $sortArrow . '</a>'; + $attributes = [ + 'class' => 'table-sorting-button ' . ($this->sortField === $field ? 'table-sorting-button-active' : ''), + 'href' => $url, + ]; + + return '<a ' . GeneralUtility::implodeAttributes($attributes, true) . '> + <span class="table-sorting-label">' . $label . '</span> + <span class="table-sorting-icon">' . $icon . '</span> + </a>'; } /** diff --git a/typo3/sysext/backend/Resources/Public/Css/backend.css b/typo3/sysext/backend/Resources/Public/Css/backend.css index a24d0c424f0093eacba46c046735958e18978d51..2763446645b4ddffa5f2059a2c213daf8bf28179 100644 --- a/typo3/sysext/backend/Resources/Public/Css/backend.css +++ b/typo3/sysext/backend/Resources/Public/Css/backend.css @@ -2817,9 +2817,9 @@ button[aria-expanded=true]:not(:disabled) .modulemenu-indicator:after{transform: .scaffold-modulemenu-expanded .modulemenu-action{margin-left:0;width:100%} .scaffold-modulemenu-expanded .modulemenu-indicator{display:block!important} .scaffold-modulemenu-expanded .modulemenu-name{position:static;margin:0 0 0 1em;width:auto;height:auto} -:root{--typo3-font-family-sans-serif:Verdana,Arial,Helvetica,sans-serif;--typo3-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--typo3-font-family:var(--typo3-font-family-sans-serif);--typo3-font-family-code:var(--typo3-font-family-monospace);--typo3-spacing:1rem;--typo3-header-font-family:"Source Sans 3",sans-serif;--typo3-light-color:#000;--typo3-light-secondary-color:#666666;--typo3-light-bg:#fff;--typo3-light-border-color:rgb(215, 215, 215);--typo3-light-link-color:#0078e6;--typo3-light-link-hover-color:#1a86e9;--typo3-light-hover-color:var(--typo3-light-color);--typo3-light-hover-bg:#f2f8fe;--typo3-light-hover-border-color:#d9ebfb;--typo3-light-focus-color:var(--typo3-light-color);--typo3-light-focus-bg:#f2f8fe;--typo3-light-focus-border-color:#3393eb;--typo3-light-active-color:#fff;--typo3-light-active-bg:#3393eb;--typo3-light-active-border-color:#3393eb;--typo3-light-disabled-color:rgb(115, 115, 115);--typo3-light-disabled-bg:transparent;--typo3-light-disabled-border-color:transparent;--typo3-dark-color:#fff;--typo3-dark-secondary-color:#999999;--typo3-dark-bg:rgb(30, 30, 30);--typo3-dark-border-color:rgb(51, 51, 51);--typo3-dark-link-color:#66aef0;--typo3-dark-link-hover-color:#80bcf3;--typo3-dark-hover-color:var(--typo3-dark-color);--typo3-dark-hover-bg:rgb(51, 51, 51);--typo3-dark-hover-border-color:rgb(90, 90, 90);--typo3-dark-focus-color:var(--typo3-dark-color);--typo3-dark-focus-bg:#003c73;--typo3-dark-focus-border-color:#00488a;--typo3-dark-active-color:#fff;--typo3-dark-active-bg:#0066c4;--typo3-dark-active-border-color:#0060b8;--typo3-dark-disabled-color:rgb(115, 115, 115);--typo3-dark-disabled-bg:transparent;--typo3-dark-disabled-border-color:transparent;--typo3-component-color:var(--typo3-light-color);--typo3-component-secondary-color:var(--typo3-light-secondary-color);--typo3-component-bg:var(--typo3-light-bg);--typo3-component-link-color:var(--typo3-light-link-color);--typo3-component-link-hover-color:var(--typo3-light-link-hover-color);--typo3-component-font-size:12px;--typo3-component-line-height:1.5;--typo3-component-border-radius:4px;--typo3-component-border-width:1px;--typo3-component-border-color:var(--typo3-light-border-color);--typo3-component-padding-y:.75rem;--typo3-component-padding-x:1rem;--typo3-component-box-shadow:0 1px 2px rgba(0, 0, 0, .25);--typo3-component-hover-color:var(--typo3-light-hover-color);--typo3-component-hover-bg:var(--typo3-light-hover-bg);--typo3-component-hover-border-color:var(--typo3-light-hover-border-color);--typo3-component-focus-color:var(--typo3-light-focus-color);--typo3-component-focus-bg:var(--typo3-light-focus-bg);--typo3-component-focus-border-color:var(--typo3-light-focus-border-color);--typo3-component-active-color:var(--typo3-light-active-color);--typo3-component-active-bg:var(--typo3-light-active-bg);--typo3-component-active-border-color:var(--typo3-light-active-border-color);--typo3-component-disabled-color:var(--typo3-light-disabled-color);--typo3-component-disabled-bg:var(--typo3-light-disabled-bg);--typo3-component-disabled-border-color:var(--typo3-light-disabled-border-color);--typo3-component-spacing:2rem;--typo3-list-item-padding-y:.5rem;--typo3-list-item-padding-x:.75rem;--typo3-list-item-hover-color:var(--typo3-component-hover-color);--typo3-list-item-hover-bg:var(--typo3-component-hover-bg);--typo3-list-item-hover-border-color:var(--typo3-component-hover-border-color);--typo3-list-item-focus-color:var(--typo3-component-focus-color);--typo3-list-item-focus-bg:var(--typo3-component-focus-bg);--typo3-list-item-focus-border-color:var(--typo3-component-focus-border-color);--typo3-list-item-active-color:var(--typo3-list-item-focus-color);--typo3-list-item-active-bg:var(--typo3-list-item-focus-bg);--typo3-list-item-active-border-color:var(--typo3-list-item-focus-border-color);--typo3-list-item-disabled-color:var(--typo3-component-disabled-color);--typo3-list-item-disabled-bg:var(--typo3-component-disabled-bg);--typo3-list-item-disabled-border-color:var(--typo3-component-disabled-border-color);--typo3-legend-font-weight:600;--typo3-input-color:#333;--typo3-input-color-placeholder:rgb(187, 187, 187);--typo3-input-bg:#fefefe;--typo3-input-border-width:var(--bs-border-width);--typo3-input-border-color:rgb(187, 187, 187);--typo3-input-border-radius:0.125rem;--typo3-input-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.075);--typo3-input-focus-color:#333;--typo3-input-focus-bg:#fefefe;--typo3-input-focus-border-color:#80bcf3;--typo3-input-focus-box-shadow:0 0 0 0.25rem rgba(0, 120, 230, 0.25);--typo3-input-disabled-bg:var(--bs-secondary-bg)} +:root{--typo3-font-family-sans-serif:Verdana,Arial,Helvetica,sans-serif;--typo3-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;--typo3-font-family:var(--typo3-font-family-sans-serif);--typo3-font-family-code:var(--typo3-font-family-monospace);--typo3-spacing:1rem;--typo3-header-font-family:"Source Sans 3",sans-serif;--typo3-light-color:#000;--typo3-light-primary-color:#0078e6;--typo3-light-secondary-color:#666666;--typo3-light-bg:#fff;--typo3-light-border-color:rgb(215, 215, 215);--typo3-light-link-color:#0078e6;--typo3-light-link-hover-color:#1a86e9;--typo3-light-hover-color:var(--typo3-light-color);--typo3-light-hover-bg:#f2f8fe;--typo3-light-hover-border-color:#d9ebfb;--typo3-light-focus-color:var(--typo3-light-color);--typo3-light-focus-bg:#f2f8fe;--typo3-light-focus-border-color:#3393eb;--typo3-light-active-color:#fff;--typo3-light-active-bg:#3393eb;--typo3-light-active-border-color:#3393eb;--typo3-light-disabled-color:rgb(115, 115, 115);--typo3-light-disabled-bg:transparent;--typo3-light-disabled-border-color:transparent;--typo3-dark-color:#fff;--typo3-dark-primary-color:#66aef0;--typo3-dark-secondary-color:#999999;--typo3-dark-bg:rgb(30, 30, 30);--typo3-dark-border-color:rgb(51, 51, 51);--typo3-dark-link-color:#66aef0;--typo3-dark-link-hover-color:#80bcf3;--typo3-dark-hover-color:var(--typo3-dark-color);--typo3-dark-hover-bg:rgb(51, 51, 51);--typo3-dark-hover-border-color:rgb(90, 90, 90);--typo3-dark-focus-color:var(--typo3-dark-color);--typo3-dark-focus-bg:#003c73;--typo3-dark-focus-border-color:#00488a;--typo3-dark-active-color:#fff;--typo3-dark-active-bg:#0066c4;--typo3-dark-active-border-color:#0060b8;--typo3-dark-disabled-color:rgb(115, 115, 115);--typo3-dark-disabled-bg:transparent;--typo3-dark-disabled-border-color:transparent;--typo3-component-color:var(--typo3-light-color);--typo3-component-primary-color:var(--typo3-light-primary-color);--typo3-component-secondary-color:var(--typo3-light-secondary-color);--typo3-component-bg:var(--typo3-light-bg);--typo3-component-link-color:var(--typo3-light-link-color);--typo3-component-link-hover-color:var(--typo3-light-link-hover-color);--typo3-component-font-size:12px;--typo3-component-line-height:1.5;--typo3-component-border-radius:4px;--typo3-component-border-width:1px;--typo3-component-border-color:var(--typo3-light-border-color);--typo3-component-padding-y:.75rem;--typo3-component-padding-x:1rem;--typo3-component-box-shadow:0 1px 2px rgba(0, 0, 0, .25);--typo3-component-hover-color:var(--typo3-light-hover-color);--typo3-component-hover-bg:var(--typo3-light-hover-bg);--typo3-component-hover-border-color:var(--typo3-light-hover-border-color);--typo3-component-focus-color:var(--typo3-light-focus-color);--typo3-component-focus-bg:var(--typo3-light-focus-bg);--typo3-component-focus-border-color:var(--typo3-light-focus-border-color);--typo3-component-active-color:var(--typo3-light-active-color);--typo3-component-active-bg:var(--typo3-light-active-bg);--typo3-component-active-border-color:var(--typo3-light-active-border-color);--typo3-component-disabled-color:var(--typo3-light-disabled-color);--typo3-component-disabled-bg:var(--typo3-light-disabled-bg);--typo3-component-disabled-border-color:var(--typo3-light-disabled-border-color);--typo3-component-spacing:2rem;--typo3-list-item-padding-y:.5rem;--typo3-list-item-padding-x:.75rem;--typo3-list-item-hover-color:var(--typo3-component-hover-color);--typo3-list-item-hover-bg:var(--typo3-component-hover-bg);--typo3-list-item-hover-border-color:var(--typo3-component-hover-border-color);--typo3-list-item-focus-color:var(--typo3-component-focus-color);--typo3-list-item-focus-bg:var(--typo3-component-focus-bg);--typo3-list-item-focus-border-color:var(--typo3-component-focus-border-color);--typo3-list-item-active-color:var(--typo3-list-item-focus-color);--typo3-list-item-active-bg:var(--typo3-list-item-focus-bg);--typo3-list-item-active-border-color:var(--typo3-list-item-focus-border-color);--typo3-list-item-disabled-color:var(--typo3-component-disabled-color);--typo3-list-item-disabled-bg:var(--typo3-component-disabled-bg);--typo3-list-item-disabled-border-color:var(--typo3-component-disabled-border-color);--typo3-legend-font-weight:600;--typo3-input-color:#333;--typo3-input-color-placeholder:rgb(187, 187, 187);--typo3-input-bg:#fefefe;--typo3-input-border-width:var(--bs-border-width);--typo3-input-border-color:rgb(187, 187, 187);--typo3-input-border-radius:0.125rem;--typo3-input-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.075);--typo3-input-focus-color:#333;--typo3-input-focus-bg:#fefefe;--typo3-input-focus-border-color:#80bcf3;--typo3-input-focus-box-shadow:0 0 0 0.25rem rgba(0, 120, 230, 0.25);--typo3-input-disabled-bg:var(--bs-secondary-bg)} @media (prefers-color-scheme:dark){ -:root{--typo3-component-color:var(--typo3-dark-color);--typo3-component-secondary-color:var(--typo3-dark-secondary-color);--typo3-component-bg:var(--typo3-dark-bg);--typo3-component-border-color:var(--typo3-dark-border-color);--typo3-component-link-color:var(--typo3-dark-link-color);--typo3-component-link-hover-color:var(--typo3-dark-link-hover-color);--typo3-component-hover-color:var(--typo3-dark-hover-color);--typo3-component-hover-bg:var(--typo3-dark-hover-bg);--typo3-component-hover-border-color:var(--typo3-dark-hover-border-color);--typo3-component-focus-color:var(--typo3-dark-focus-color);--typo3-component-focus-bg:var(--typo3-dark-focus-bg);--typo3-component-focus-border-color:var(--typo3-dark-focus-border-color);--typo3-component-active-color:var(--typo3-dark-active-color);--typo3-component-active-bg:var(--typo3-dark-active-bg);--typo3-component-active-border-color:var(--typo3-dark-active-border-color);--typo3-component-disabled-color:var(--typo3-dark-disabled-color);--typo3-component-disabled-bg:var(--typo3-dark-disabled-bg);--typo3-component-disabled-border-color:var(--typo3-dark-disabled-border-color)} +:root{--typo3-component-color:var(--typo3-dark-color);--typo3-component-primary-color:var(--typo3-dark-primary-color);--typo3-component-secondary-color:var(--typo3-dark-secondary-color);--typo3-component-bg:var(--typo3-dark-bg);--typo3-component-border-color:var(--typo3-dark-border-color);--typo3-component-link-color:var(--typo3-dark-link-color);--typo3-component-link-hover-color:var(--typo3-dark-link-hover-color);--typo3-component-hover-color:var(--typo3-dark-hover-color);--typo3-component-hover-bg:var(--typo3-dark-hover-bg);--typo3-component-hover-border-color:var(--typo3-dark-hover-border-color);--typo3-component-focus-color:var(--typo3-dark-focus-color);--typo3-component-focus-bg:var(--typo3-dark-focus-bg);--typo3-component-focus-border-color:var(--typo3-dark-focus-border-color);--typo3-component-active-color:var(--typo3-dark-active-color);--typo3-component-active-bg:var(--typo3-dark-active-bg);--typo3-component-active-border-color:var(--typo3-dark-active-border-color);--typo3-component-disabled-color:var(--typo3-dark-disabled-color);--typo3-component-disabled-bg:var(--typo3-dark-disabled-bg);--typo3-component-disabled-border-color:var(--typo3-dark-disabled-border-color)} } .dropdown-menu{--bs-secondary-color:var(--typo3-component-secondary-color)} .h1,h1,typo3-backend-editable-page-title{font-family:var(--typo3-header-font-family);font-variant:normal;font-weight:400} @@ -3248,11 +3248,6 @@ a.dropdown-toggle{text-decoration:none} .cropper-crop{cursor:crosshair} .cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed} .sortable-ghost{opacity:.4!important} -th[role=columnheader]:not([data-sort-method=none]){cursor:pointer;position:relative;background-clip:padding-box} -th[role=columnheader]:not([data-sort-method=none]):after{content:"";position:absolute;margin:7px 0 0 3px;border-width:4px 4px 0;border-style:solid;border-color:#404040 transparent;visibility:hidden;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none} -th[role=columnheader]:not([data-sort-method=none]):hover:after{visibility:visible;opacity:1} -th[aria-sort=descending]:not([data-sort-method=none]):after{border-width:0 4px 4px} -th[aria-sort]:not([data-sort-method=none]):after{visibility:visible;opacity:.4} #nprogress{pointer-events:none} #nprogress .bar{background:#ff8700;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px} #nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;padding:0;box-shadow:0 0 10px #ff8700,0 0 5px #ff8700;opacity:1;transform:rotate(3deg) translate(0,-4px)} @@ -3546,6 +3541,9 @@ to{opacity:1;transform:translate3d(0,0,0)} .table-fit-wrap td,.table-fit-wrap th{white-space:normal} .table-fit-inline-block{max-width:100%;width:auto;display:inline-block;margin:0} .table-fit-inline-block>.table{width:auto} +.table-sorting-button{padding:0;border:0;font-weight:inherit;font-size:inherit;background:0 0;display:inline-flex;align-items:center;gap:.25em} +.table-sorting-icon{display:flex;align-items:center;justify-content:center;color:var(--typo3-light-secondary-color)} +.table-sorting-button-active .table-sorting-icon{color:var(--typo3-light-primary-color)} .media{display:grid;grid-template-columns:2rem auto;grid-gap:1rem} .media .media-body{grid-column:1/3;overflow:hidden} .media .media-left+.media-body{grid-column:2/3;align-self:center} @@ -3868,7 +3866,7 @@ typo3-backend-live-search-result-item-action>* .livesearch-result-item-title .sm .ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px} .ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px} typo3-backend-editable-page-title{display:block;white-space:nowrap;text-overflow:ellipsis} -typo3-backend-new-content-element-wizard{--typo3-component-color:var(--typo3-light-color);--typo3-component-secondary-color:var(--typo3-light-secondary-color);--typo3-component-bg:var(--typo3-light-bg);--typo3-component-border-color:var(--typo3-light-border-color);--typo3-component-link-color:var(--typo3-light-link-color);--typo3-component-link-hover-color:var(--typo3-light-link-hover-color);--typo3-component-hover-color:var(--typo3-light-hover-color);--typo3-component-hover-bg:var(--typo3-light-hover-bg);--typo3-component-hover-border-color:var(--typo3-light-hover-border-color);--typo3-component-focus-color:var(--typo3-light-focus-color);--typo3-component-focus-bg:var(--typo3-light-focus-bg);--typo3-component-focus-border-color:var(--typo3-light-focus-border-color);--typo3-component-active-color:var(--typo3-light-active-color);--typo3-component-active-bg:var(--typo3-light-active-bg);--typo3-component-active-border-color:var(--typo3-light-active-border-color);--typo3-component-disabled-color:var(--typo3-light-disabled-color);--typo3-component-disabled-bg:var(--typo3-light-disabled-bg);--typo3-component-disabled-border-color:var(--typo3-light-disabled-border-color)} +typo3-backend-new-content-element-wizard{--typo3-component-color:var(--typo3-light-color);--typo3-component-primary-color:var(--typo3-light-primary-color);--typo3-component-secondary-color:var(--typo3-light-secondary-color);--typo3-component-bg:var(--typo3-light-bg);--typo3-component-border-color:var(--typo3-light-border-color);--typo3-component-link-color:var(--typo3-light-link-color);--typo3-component-link-hover-color:var(--typo3-light-link-hover-color);--typo3-component-hover-color:var(--typo3-light-hover-color);--typo3-component-hover-bg:var(--typo3-light-hover-bg);--typo3-component-hover-border-color:var(--typo3-light-hover-border-color);--typo3-component-focus-color:var(--typo3-light-focus-color);--typo3-component-focus-bg:var(--typo3-light-focus-bg);--typo3-component-focus-border-color:var(--typo3-light-focus-border-color);--typo3-component-active-color:var(--typo3-light-active-color);--typo3-component-active-bg:var(--typo3-light-active-bg);--typo3-component-active-border-color:var(--typo3-light-active-border-color);--typo3-component-disabled-color:var(--typo3-light-disabled-color);--typo3-component-disabled-bg:var(--typo3-light-disabled-bg);--typo3-component-disabled-border-color:var(--typo3-light-disabled-border-color)} #alert-container{width:400px;position:fixed;right:5px;top:45px;z-index:10000} #alert-container typo3-notification-message{margin-top:5px} typo3-notification-message{display:block} diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/sortable-table.js b/typo3/sysext/backend/Resources/Public/JavaScript/sortable-table.js index 3ca188e2bae04fee65db32a543ab4b52f3669eac..8ef3ee92b20c57654923d1c5ffaa3cabbbed7813 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/sortable-table.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/sortable-table.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import Tablesort from"tablesort";import"tablesort.dotsep.js";import"tablesort.number.js";export default class SortableTable{constructor(t){new Tablesort(t)}} \ No newline at end of file +import Tablesort from"tablesort";import"tablesort.dotsep.js";import"tablesort.number.js";import{IconElement}from"@typo3/backend/element/icon-element.js";import{Sizes}from"@typo3/backend/enum/icon-types.js";class TablesortWithButtons extends Tablesort{init(t,e){let o,r;if(this.table=t,this.thead=!1,this.options=e,t.rows&&t.rows.length>0)if(t.tHead&&t.tHead.rows.length>0){for(let e=0;e<t.tHead.rows.length;e++)if("thead"===t.tHead.rows[e].getAttribute("data-sort-method")){o=t.tHead.rows[e];break}o||(o=t.tHead.rows[t.tHead.rows.length-1]),this.thead=!0}else o=t.rows[0];if(!o)return;const n=t=>{t.preventDefault(),t.stopImmediatePropagation();const e=t.currentTarget.parentNode;this.current&&this.current!==e&&this.current.removeAttribute("aria-sort"),this.current=e,this.sortTable(e)};for(let t=0;t<o.cells.length;t++){const e=o.cells[t];if(e.setAttribute("role","columnheader"),"none"!==e.getAttribute("data-sort-method")){const t=document.createElement("button");t.classList.add("table-sorting-button");const o=document.createElement("span");o.classList.add("table-sorting-label"),o.textContent=e.textContent,t.appendChild(o);const s=document.createElement("span");s.classList.add("table-sorting-icon");const a=new IconElement;a.identifier="actions-sort-amount",a.size=Sizes.small,s.appendChild(a),t.appendChild(s),t.addEventListener("click",n,!1),e.replaceChildren(t),null!==e.getAttribute("data-sort-default")&&(r=e)}}r&&(this.current=r,this.sortTable(r))}}export default class SortableTable{constructor(t){new TablesortWithButtons(t),t.addEventListener("afterSort",(t=>{const e=t.target;e.tHead.querySelectorAll(".table-sorting-button").forEach((t=>{t.classList.remove("table-sorting-button-active");t.querySelector("typo3-backend-icon").identifier="actions-sort-amount"}));const o=e.tHead.querySelector("th[aria-sort]"),r=o.querySelector(".table-sorting-button");r.classList.add("table-sorting-button-active");const n=r.querySelector("typo3-backend-icon");"ascending"===o.ariaSort?n.identifier="actions-sort-amount-down":n.identifier="actions-sort-amount-up"}))}} \ No newline at end of file diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php index b136ce71aaa6b02072b4f65236d013406b97f766..69ee8cfcbed1880fca32861b1b685f2319b3ae72 100644 --- a/typo3/sysext/filelist/Classes/FileList.php +++ b/typo3/sysext/filelist/Classes/FileList.php @@ -449,14 +449,24 @@ class FileList if ($this->sort === $field) { // Check reverse sorting $params['reverse'] = ($this->sortRev ? '0' : '1'); - $sortArrow = $this->iconFactory->getIcon('status-status-sorting-' . ($this->sortRev ? 'desc' : 'asc'), Icon::SIZE_SMALL)->render(); } else { $params['reverse'] = 0; - $sortArrow = ''; } - $href = $this->createModuleUri($params); - return '<a href="' . htmlspecialchars($href) . '">' . htmlspecialchars($label) . ' ' . $sortArrow . '</a>'; + $icon = $this->sort === $field + ? $this->iconFactory->getIcon('actions-sort-amount-' . ($this->sortRev ? 'down' : 'up'), Icon::SIZE_SMALL)->render() + : $this->iconFactory->getIcon('actions-sort-amount', Icon::SIZE_SMALL)->render(); + + $attributes = [ + 'class' => 'table-sorting-button ' . ($this->sort === $field ? 'table-sorting-button-active' : ''), + 'href' => $this->createModuleUri($params), + ]; + + return '<a ' . GeneralUtility::implodeAttributes($attributes, true) . '> + <span class="table-sorting-label">' . htmlspecialchars($label) . '</span> + <span class="table-sorting-icon">' . $icon . '</span> + </a>'; + } /**