Apply differentiation between selected and pressed to WP table view

This commit is contained in:
Henriette Darge
2026-06-03 11:48:05 +02:00
parent 549bf69156
commit 287b8c6e4a
8 changed files with 95 additions and 7 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

@@ -18,6 +18,9 @@
display: flex
align-items: center
:has(.op-ian-item_selected:first-child)
border-top-color: var(--box-list-item-pressed-border-color)
&--viewport
height: 100%
// Avoid infinite horizontal scrolling on mobile iOS (#39849)
@@ -7,7 +7,9 @@ $ian-bg-read-color: var(--bgColor-muted)
$ian-bg-read-hover-color: hsl(from var(--bgColor-muted) h s calc(l - 3.5))
$ian-bg-selected-color: var(--box-list-item-selected-bg-color)
$ian-bg-selected-hover-color: var(--box-list-item-selected-bg-hover-color)
$ian-selected-border-color: var(--box-list-item-selected-border-color)
// Notification center is special as it does not allow multi-select. So the selected element is always the pressed one.
// We can therefore direclty set the pressed border color on the selected element
$ian-active-border-color: var(--box-list-item-pressed-border-color)
// This needs to be set in the itemSize of
// the virtual scroller
$ian-height: 100px
@@ -33,14 +35,17 @@ $subject-font-size: 14px
&_selected
background: $ian-bg-selected-color
border-color: $ian-selected-border-color
border-color: $ian-active-border-color
&:hover
background: $ian-bg-selected-hover-color
// Since the ian only have a top border, we need to highlight the next sibling as well to
// create the impression that the selected ian has a top and bottom border
& + .op-ian-item
border-color: $ian-selected-border-color
border-top-color: $ian-active-border-color
&:last-of-type
border-bottom-color: $ian-active-border-color
&_read
background: $ian-bg-read-color
@@ -13,7 +13,8 @@ import {
} from 'core-app/features/work-packages/components/wp-fast-table/builders/internal-sort-columns';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { debugLog } from 'core-app/shared/helpers/debug_output';
import { checkedClassName } from '../ui-state-link-builder';
import { checkedClassName, pressedClassName } from '../ui-state-link-builder';
import { WorkPackageViewFocusService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-focus.service';
import { RelationCellbuilder } from '../relation-cell-builder';
import {
CellBuilder,
@@ -37,6 +38,8 @@ export class SingleRowBuilder {
// Injections
@InjectField() wpTableSelection:WorkPackageViewSelectionService;
@InjectField() wpTableFocus:WorkPackageViewFocusService;
@InjectField() wpTableColumns:WorkPackageViewColumnsService;
@InjectField() wpTableBaseline:WorkPackageViewBaselineService;
@@ -251,6 +254,11 @@ export class SingleRowBuilder {
row.classList.add(checkedClassName);
}
// Mark the currently focused (details-panel) row as pressed
if (this.wpTableFocus.isFocused(workPackage.id!)) {
row.classList.add(pressedClassName);
}
return [row, false];
}
}
@@ -6,6 +6,7 @@ import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decora
export const uiStateLinkClass = '__ui-state-link';
export const checkedClassName = '-checked';
export const pressedClassName = '-pressed';
export class UiStateLinkBuilder {
constructor(
@@ -9,7 +9,7 @@ import {
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-selection.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import { tableRowClassName } from '../../builders/rows/single-row-builder';
import { checkedClassName } from '../../builders/ui-state-link-builder';
import { checkedClassName, pressedClassName } from '../../builders/ui-state-link-builder';
import { locateTableRow, scrollTableRowIntoView } from '../../helpers/wp-table-row-helpers';
import { WorkPackageTable } from '../../wp-fast-table';
@@ -48,6 +48,15 @@ export class SelectionTransformer {
this.renderSelectionState(state);
});
// Update pressed state when the focused (details-panel) WP changes
this.wpTableFocus.whenChanged()
.pipe(
takeUntil(this.querySpace.stopAllSubscriptions),
)
.subscribe((focusedId:string) => {
this.renderPressedState(focusedId);
});
this.wpTableSelection.registerSelectAllListener(() => table.renderedRows);
this.wpTableSelection.registerDeselectAllListener();
}
@@ -66,4 +75,19 @@ export class SelectionTransformer {
});
});
}
/**
* Update the pressed state to reflect the currently focused (details-panel) row.
*/
private renderPressedState(focusedId:string|null) {
const context = this.table.tableAndTimelineContainer;
context.querySelectorAll(`.${tableRowClassName}.${pressedClassName}`).forEach((el) => el.classList.remove(pressedClassName));
if (focusedId) {
context.querySelectorAll(`.${tableRowClassName}[data-work-package-id="${focusedId}"]`).forEach((el) => {
el.classList.add(pressedClassName);
});
}
}
}
@@ -106,6 +106,7 @@ th.hidden
tr.context-menu-selection,
tr.-checked
background-color: var(--box-list-item-selected-bg-color) !important
border-bottom: 1px solid var(--box-list-item-selected-border-color) !important
&[class*=__hl_background]
outline: var(--bgColor-accent-emphasis) solid 2px
@@ -113,12 +114,19 @@ tr.-checked
background-color: var(--box-list-item-selected-bg-hover-color) !important
td
border-top: 1px solid var(--box-list-item-selected-border-color) !important
border-bottom: 1px solid var(--box-list-item-selected-border-color) !important
color: var(--body-font-color) !important
a
color: var(--body-font-color) !important
tr.-pressed
border-bottom: 1px solid var(--box-list-item-pressed-border-color) !important
tr:not(.-pressed):has(+ tr.-checked)
border-bottom: 1px solid var(--box-list-item-selected-border-color) !important
tr:not(.-pressed):has(+ tr.-pressed)
border-bottom: 1px solid var(--box-list-item-pressed-border-color) !important
#custom-options-table
.custom-option-value
display: inline-block
@@ -0,0 +1,39 @@
List items can have three visual states. The states are expressed via CSS classes on the row element (`<tr>` or equivalent) and are driven by CSS custom properties defined in `_variable_defaults.scss`.
![Box list item color states overview](<%= image_path("lookbook/box_list_color_states.png") %>)
## States
### Default
The item uses the base background (`--box-list-item-bg-color`) and a neutral border (`--box-list-item-border-color`).
### Selected
Applied when an item is part of the active **(multi-)selection** - e.g. for bulk actions or drag & drop. The background is blue (`--box-list-item-selected-bg-color`). A subtle blue border is added at the top and the bottom (`--box-list-item-selected-border-color`).
### Pressed
Applied to the item that is currently **open in the split-screen**. A stronger blue border is added at the top and bottom (`--box-list-item-pressed-border-color`) to indicate the active detail context. The background colour remains unchanged.
An item can carry both states simultaneously when it is selected *and* currently shown in the detail panel.
## CSS custom properties
| Property | Used by |
|---|---|
| `--box-list-item-bg-color` | default background |
| `--box-list-item-bg-hover-color` | hover background |
| `--box-list-item-border-color` | default border |
| `--box-list-item-selected-bg-color` | `selected` background |
| `--box-list-item-selected-bg-hover-color` | `selected` hover background |
| `--box-list-item-selected-border-color` | `selected` border |
| `--box-list-item-pressed-border-color` | `pressed` border |
## Usages
This pattern is currently used in:
* WorkPackage list
* Notification center
* Backlogs