diff --git a/graylog2-web-interface/src/components/common/EntityDataTable/CSSVariables.ts b/graylog2-web-interface/src/components/common/EntityDataTable/CSSVariables.ts index 9dbca1852d..4594cc8bb8 100644 --- a/graylog2-web-interface/src/components/common/EntityDataTable/CSSVariables.ts +++ b/graylog2-web-interface/src/components/common/EntityDataTable/CSSVariables.ts @@ -21,3 +21,4 @@ export const columnOpacityVar = (colId: string) => `--col-${colId}-opacity`; export const columnTransition = () => `--col-transition`; export const actionsHeaderWidthVar = `--actions-header-width`; export const displayScrollRightIndicatorVar = `--display-scroll-right-indicator`; +export const scrollContainerWidthVar = `--scroll-container-width`; diff --git a/graylog2-web-interface/src/components/common/EntityDataTable/EntityDataTable.tsx b/graylog2-web-interface/src/components/common/EntityDataTable/EntityDataTable.tsx index d49f80627a..62d2fbf085 100644 --- a/graylog2-web-interface/src/components/common/EntityDataTable/EntityDataTable.tsx +++ b/graylog2-web-interface/src/components/common/EntityDataTable/EntityDataTable.tsx @@ -37,6 +37,7 @@ import { columnTransformVar, columnWidthVar, displayScrollRightIndicatorVar, + scrollContainerWidthVar, } from 'components/common/EntityDataTable/CSSVariables'; import useHeaderMinWidths from 'components/common/EntityDataTable/hooks/useHeaderMinWidths'; import useColumnDefinitions from 'components/common/EntityDataTable/hooks/useColumnDefinitions'; @@ -60,20 +61,30 @@ const ScrollContainer = styled.div<{ $columnTransform: { [_attributeId: string]: string }; $actionsHeaderWidth: number; $canScrollRight: boolean; + $scrollContainerWidth: number; }>( - ({ $columnWidths, $activeColId, $columnTransform, $actionsHeaderWidth, $canScrollRight }) => css` + ({ + $columnWidths, + $activeColId, + $columnTransform, + $actionsHeaderWidth, + $canScrollRight, + $scrollContainerWidth, + }) => css` width: 100%; overflow-x: auto; ${Object.entries($columnWidths).map(([id, width]) => cssVariable(columnWidthVar(id), `${width}px`))} ${Object.entries($columnTransform).map(([id, transform]) => cssVariable(columnTransformVar(id), transform))} - ${$actionsHeaderWidth && cssVariable(actionsHeaderWidthVar, `${$actionsHeaderWidth}px`)} - ${$canScrollRight && cssVariable(displayScrollRightIndicatorVar, 'block')} - ${$activeColId && - css` - ${cssVariable(columnOpacityVar($activeColId), 0.4)} - ${cssVariable(columnTransition(), 'transform 0.2s ease-in-out')} - `} + ${$actionsHeaderWidth ? cssVariable(actionsHeaderWidthVar, `${$actionsHeaderWidth}px`) : ''} + ${$canScrollRight ? cssVariable(displayScrollRightIndicatorVar, 'block') : ''} + ${$scrollContainerWidth ? cssVariable(scrollContainerWidthVar, `${$scrollContainerWidth}px`) : ''} + ${$activeColId + ? css` + ${cssVariable(columnOpacityVar($activeColId), 0.4)} + ${cssVariable(columnTransition(), 'transform 0.2s ease-in-out')} + ` + : ''} `, ); @@ -229,20 +240,18 @@ const EntityDataTable = ({ displayBulkSelectCol, ); - const { columnWidths, handleActionsWidthChange, tableIsCompressed, actionsColMinWidth } = useElementWidths< - Entity, - Meta - >({ - columnRenderersByAttribute, - columnSchemas: authorizedColumnSchemas, - columnWidthPreferences: internalColumnWidthPreferences, - displayBulkSelectCol, - entities, - hasRowActions, - headerMinWidths, - scrollContainerRef, - visibleColumns: columnOrder, - }); + const { columnWidths, handleActionsWidthChange, tableIsCompressed, actionsColMinWidth, scrollContainerWidth } = + useElementWidths({ + columnRenderersByAttribute, + columnSchemas: authorizedColumnSchemas, + columnWidthPreferences: internalColumnWidthPreferences, + displayBulkSelectCol, + entities, + hasRowActions, + headerMinWidths, + scrollContainerRef, + visibleColumns: columnOrder, + }); const columnDefinitions = useColumnDefinitions({ actionsColMinWidth, @@ -324,7 +333,8 @@ const EntityDataTable = ({ $activeColId={activeColId} $columnTransform={columnTransform} $columnWidths={columnWidths} - $canScrollRight={scrolledToRight && tableIsCompressed}> + $canScrollRight={scrolledToRight && tableIsCompressed} + $scrollContainerWidth={scrollContainerWidth}> expandedSectionRenderers={expandedSectionRenderers} diff --git a/graylog2-web-interface/src/components/common/EntityDataTable/ExpandedSections.tsx b/graylog2-web-interface/src/components/common/EntityDataTable/ExpandedSections.tsx index a652aa3df8..5f215db553 100644 --- a/graylog2-web-interface/src/components/common/EntityDataTable/ExpandedSections.tsx +++ b/graylog2-web-interface/src/components/common/EntityDataTable/ExpandedSections.tsx @@ -20,6 +20,8 @@ import styled, { css } from 'styled-components'; import IconButton from 'components/common/IconButton'; import { ButtonToolbar } from 'components/bootstrap'; +import { CELL_PADDING } from 'components/common/EntityDataTable/Constants'; +import { scrollContainerWidthVar } from 'components/common/EntityDataTable/CSSVariables'; import type { EntityBase, ExpandedSectionRenderers } from './types'; import ExpandedEntitiesSectionsContext from './contexts/ExpandedSectionsContext'; @@ -32,6 +34,20 @@ const Container = styled.tr( `, ); +const ContentCell = styled.td` + && { + padding: 0; + } +`; + +const Content = styled.div` + position: sticky; + left: 0; + width: 100%; + max-width: var(${scrollContainerWidthVar}, 100%); + padding: ${CELL_PADDING}px; +`; + const Header = styled.div` display: flex; justify-content: space-between; @@ -63,29 +79,31 @@ const ExpandedSections = ({ return ( - - {Object.entries(expandedSectionRenderers ?? {}) - .filter(([sectionName]) => expandedEntitySections.includes(sectionName)) - .map(([sectionName, section]) => { - const hideSection = () => toggleSection(entity.id, sectionName); - const actions = section.actions?.(entity); + + + {Object.entries(expandedSectionRenderers ?? {}) + .filter(([sectionName]) => expandedEntitySections.includes(sectionName)) + .map(([sectionName, section]) => { + const hideSection = () => toggleSection(entity.id, sectionName); + const actions = section.actions?.(entity); - return ( -
- {section.disableHeader !== true ? ( -
-

{section.title}

- - {actions} - - -
- ) : null} - {section.content(entity)} -
- ); - })} - + return ( +
+ {section.disableHeader !== true ? ( +
+

{section.title}

+ + {actions} + + +
+ ) : null} + {section.content(entity)} +
+ ); + })} +
+
); }; diff --git a/graylog2-web-interface/src/components/common/EntityDataTable/hooks/useElementWidths.ts b/graylog2-web-interface/src/components/common/EntityDataTable/hooks/useElementWidths.ts index e27eff5594..3dfb6f3813 100644 --- a/graylog2-web-interface/src/components/common/EntityDataTable/hooks/useElementWidths.ts +++ b/graylog2-web-interface/src/components/common/EntityDataTable/hooks/useElementWidths.ts @@ -69,6 +69,7 @@ const useElementWidths = ({ columnWidths, tableIsCompressed: actionsColMinWidth === columnWidths[ACTIONS_COL_ID], actionsColMinWidth, + scrollContainerWidth, }; }; diff --git a/graylog2-web-interface/src/components/common/EntityFilters/ActiveFilters.tsx b/graylog2-web-interface/src/components/common/EntityFilters/ActiveFilters.tsx index 557e40f77a..06c0cec651 100644 --- a/graylog2-web-interface/src/components/common/EntityFilters/ActiveFilters.tsx +++ b/graylog2-web-interface/src/components/common/EntityFilters/ActiveFilters.tsx @@ -15,7 +15,7 @@ * . */ import React from 'react'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import type { Filters, Filter } from 'components/common/EntityFilters/types'; import type { Attributes } from 'stores/PaginationTypes'; @@ -23,20 +23,27 @@ import ActiveFilter from 'components/common/EntityFilters/ActiveFilter'; import HoverForHelp from 'components/common/HoverForHelp'; import { ROW_MIN_HEIGHT } from 'components/common/EntityFilters/Constants'; -const FilterGroup = styled.div` - display: inline-flex; - align-items: center; - min-height: ${ROW_MIN_HEIGHT}px; - gap: 3px; - flex-wrap: wrap; -`; +const FilterGroup = styled.div( + ({ theme }) => css` + display: inline-flex; + align-items: center; + min-height: ${ROW_MIN_HEIGHT}px; + gap: ${theme.spacings.xxs}; + flex-wrap: wrap; -const FilterGroupTitle = styled.div` - display: inline-flex; - align-items: center; - gap: 3px; - margin-right: 3px; -`; + &:not(:last-child) { + margin-right: ${theme.spacings.xs}; + } + `, +); + +const FilterGroupTitle = styled.div( + ({ theme }) => css` + display: inline-flex; + align-items: center; + gap: ${theme.spacings.xxs}; + `, +); const SLICE_FILTER_CONFLICT_HELP = 'This filter is ignored because a slice is active for this attribute. Clear the slice to apply the filter.';