diff --git a/lib/public/app.css b/lib/public/app.css index 4fe92017c9..9fe5a6a7f1 100644 --- a/lib/public/app.css +++ b/lib/public/app.css @@ -992,6 +992,87 @@ label { } } +/* + * The filters buttons and panel, and the header of the overview pages containing them, use a smaller font, as the pagination does. + * The header is the row directly containing the filters buttons, or the header-container used by most of the overview pages + */ +.filters-toolbar, +.filters-panel, +.header-container, +.flex-row:has(> .filters-toolbar) { + font-size: 0.875rem; +} + +.filters-panel { + width: var(--ui-component-large); +} + +/* + * Filters panel on tablets and phones: the panel is displayed below the navbar on the whole width of the screen + * (the popover position computed in javascript is overridden) and each filter label is displayed above its input + */ +@media screen and (max-width: 1023px) { + /* + * A table wider than the screen scrolls inside its own box instead of widening the whole page: otherwise the layout viewport of the + * phone gets wider than the screen, and the filters panel, positioned against it, is partly displayed out of the screen. + * Tables having their own scroll wrapper (the logs one) are left untouched + */ + table.table:not(.logs-table) { + display: block; + max-width: 100%; + overflow-x: auto; + } + + .popover.filters-popover { + position: fixed !important; + top: 56px !important; + left: 8px !important; + right: 8px !important; + width: auto !important; + max-width: none !important; + max-height: 75vh; + } + + .filters-panel { + width: 100%; + box-sizing: border-box; + } +} + +@media screen and (max-width: 767px) { + /* The filters button, the active filters indicator and the other buttons and quick filters wrap on several lines instead of being squeezed */ + .header-container, + .flex-row:has(> .filters-toolbar) { + max-height: none; + flex-wrap: wrap; + row-gap: 8px; + } + + .header-container .flex-row, + .flex-row:has(> .filters-toolbar) .flex-row { + flex-wrap: wrap; + row-gap: 8px; + } + + .header-container .active-filters-indicator, + .header-container label, + .flex-row:has(> .filters-toolbar) .active-filters-indicator, + .flex-row:has(> .filters-toolbar) label { + white-space: nowrap; + } + + .filters-panel .filter-row { + flex-direction: column; + align-items: stretch; + row-gap: 4px; + } + + .filters-panel .filter-label, + .filters-panel .filter-input { + width: 100%; + } +} + /* xl */ @media screen and (min-width: 1920px) { .columns-3-xl { diff --git a/lib/public/components/Filters/common/filtersPanelPopover.js b/lib/public/components/Filters/common/filtersPanelPopover.js index 404841bbe0..c9cb0d4ea1 100644 --- a/lib/public/components/Filters/common/filtersPanelPopover.js +++ b/lib/public/components/Filters/common/filtersPanelPopover.js @@ -90,12 +90,12 @@ export const filtersSection = (filteringModel, filtersConfiguration, { profile: .map(([columnKey, { name, filterTooltip, filter }]) => name ? [ - h(`.flex-row.items-baseline.${columnKey}-filter`, [ - h('.w-30.f5.flex-row.items-center.g2', [ + h(`.flex-row.items-baseline.filter-row.${columnKey}-filter`, [ + h('.w-30.f5.flex-row.items-center.g2.filter-label', [ name, filterTooltip ? tooltip(info(), filterTooltip) : null, ]), - h('.w-70', typeof filter === 'function' ? filter(filteringModel) : filter), + h('.w-70.filter-input', typeof filter === 'function' ? filter(filteringModel) : filter), ]), ] : typeof filter === 'function' ? filter(filteringModel) : filter), @@ -114,7 +114,7 @@ const filtersToggleContent = ( filteringModel, filtersConfiguration, configuration = {}, -) => h('.w-l.flex-column.p3.g3', [ +) => h('.filters-panel.flex-column.p3.g3', [ filtersToggleContentHeader(filteringModel), filtersSection(filteringModel, filtersConfiguration, configuration), ]); @@ -134,6 +134,7 @@ const filtersPanelButton = (filteringModel, filtersConfiguration, configuration) { ...PopoverTriggerPreConfiguration.click, anchor: PopoverAnchors.RIGHT_START, + popoverClass: ['filters-popover'], }, ); @@ -209,7 +210,7 @@ export const filtersPanelPopover = (filteringModel, filtersConfiguration, config const hasActiveFilters = filteringModel.isAnyFilterActive(); return h( - '.flex-row.items-center.btn-group', + '.flex-row.items-center.btn-group.filters-toolbar', [ filtersPanelButton(filteringModel, filtersConfiguration, configuration), DropdownComponent( diff --git a/test/public/envs/overview.test.js b/test/public/envs/overview.test.js index fe5671206e..fccf684d4e 100644 --- a/test/public/envs/overview.test.js +++ b/test/public/envs/overview.test.js @@ -220,7 +220,9 @@ module.exports = () => { // Not running env envId = 'EIDO13i3D'; - await pressElement(page, `tr[id='row${envId}'] .popover-trigger`); + // The popover of the previous environment is still opened: with a real mouse click, depending on the layout, the click could land on + // one of its links (opening an external page in a new tab, which stops the page from rendering and hangs the next tests) + await pressElement(page, `tr[id='row${envId}'] .popover-trigger`, true); popover = await getPopoverSelector(await page.waitForSelector(`tr[id='row${envId}'] .popover-trigger`)); await expectLink(page, `${popover} :nth-child(1 of .external-link)`, { diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index a735e21dbc..bc61769581 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -205,6 +205,47 @@ module.exports = () => { } }); + it('should display the filters panel on the whole width of small screens', async () => { + const getFiltersPanelLayout = () => page.$eval('.filters-popover', (popover) => { + const { left, right } = popover.getBoundingClientRect(); + const filterRow = popover.querySelector('.filter-row'); + return { + fitsInViewport: left >= 0 && right <= document.documentElement.clientWidth, + labelIsAboveInput: window.getComputedStyle(filterRow).flexDirection === 'column', + }; + }); + + try { + await page.setViewport({ width: 375, height: 812, deviceScaleFactor: 1 }); + await goToPage(page, 'log-overview'); + await pressElement(page, '#openFilterToggle'); + await page.waitForSelector('.filters-popover .filter-row'); + expect(await getFiltersPanelLayout()).to.eql({ fitsInViewport: true, labelIsAboveInput: true }); + + await page.setViewport({ width: 800, height: 1024, deviceScaleFactor: 1 }); + expect(await getFiltersPanelLayout()).to.eql({ fitsInViewport: true, labelIsAboveInput: false }); + } finally { + await page.setViewport({ width: 1400, height: 940, deviceScaleFactor: 1 }); + } + }); + + it('should let the header of the page wrap instead of being squeezed on small screens', async () => { + try { + await page.setViewport({ width: 375, height: 812, deviceScaleFactor: 1 }); + await goToPage(page, 'log-overview'); + await page.waitForSelector('#main-action-bar'); + + const { overflows, maxHeight, flexWrap } = await page.$eval('#main-action-bar', (header) => ({ + overflows: header.scrollWidth > header.clientWidth, + maxHeight: window.getComputedStyle(header).maxHeight, + flexWrap: window.getComputedStyle(header).flexWrap, + })); + expect({ overflows, maxHeight, flexWrap }).to.eql({ overflows: false, maxHeight: 'none', flexWrap: 'wrap' }); + } finally { + await page.setViewport({ width: 1400, height: 940, deviceScaleFactor: 1 }); + } + }); + it('can sort by columns in ascending and descending manners', async () => { // Close the filter panel await waitForFirstRowToHaveId(page, 'row119');