Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I left some API design feedback in the linked isue: #848 (comment)

Some of it requires changes to the approach in the PR, hopefully not too big though. The main thing is changing the attribute from a boolean to a string enum.

"type": "minor",
"comment": "add support for pinning table columns",
"packageName": "@ni/nimble-angular",
"email": "5265744+hellovolcano@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "add support for pinning table columns",
"packageName": "@ni/nimble-blazor",
"email": "5265744+hellovolcano@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there are still some styling opacity issues with group rows on hover when there is a pinned column. (Note the arrow button lighter grey on the hovered row)

Image

"type": "minor",
"comment": "add support for pinning table columns",
"packageName": "@ni/nimble-components",
"email": "5265744+hellovolcano@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import type { BooleanValueOrAttribute } from '@ni/nimble-angular/internal-utilities';
import { NimbleTableModule } from '../../../table/nimble-table.module';
import { NimbleTableColumnMappingModule } from '../nimble-table-column-mapping.module';
import { NimbleTableColumnMappingDirective, type TableColumnMapping, TableColumnMappingWidthMode } from '../nimble-table-column-mapping.directive';
Expand Down Expand Up @@ -30,6 +31,7 @@ describe('NimbleTableColumnMapping', () => {
action-menu-slot="my-slot"
action-menu-label="my menu"
column-hidden="true"
pinned
fractional-width="2"
min-pixel-width="40"
sort-direction="${TableColumnSortDirection.ascending}"
Expand Down Expand Up @@ -93,6 +95,11 @@ describe('NimbleTableColumnMapping', () => {
expect(nativeElement.columnHidden).toBe(true);
});

it('will use template string value for pinned', () => {
expect(directive.pinned).toBeTrue();
expect(nativeElement.pinned).toBeTrue();
});

it('will use template string values for sortDirection', () => {
expect(directive.sortDirection).toBe(TableColumnSortDirection.ascending);
expect(nativeElement.sortDirection).toBe(TableColumnSortDirection.ascending);
Expand Down Expand Up @@ -146,6 +153,7 @@ describe('NimbleTableColumnMapping', () => {
[actionMenuSlot]="actionMenuSlot"
[actionMenuLabel]="actionMenuLabel"
[column-hidden]="columnHidden"
[pinned]="pinned"
[fractional-width]="fractionalWidth"
[min-pixel-width]="minPixelWidth"
[sort-direction]="sortDirection"
Expand All @@ -170,6 +178,7 @@ describe('NimbleTableColumnMapping', () => {
public minPixelWidth: number | null = 40;
public columnId = 'my-column';
public columnHidden = true;
public pinned: BooleanValueOrAttribute = null;
public sortDirection: TableColumnSortDirection = TableColumnSortDirection.ascending;
public sortIndex: number | null = 0;
public sortingDisabled = false;
Expand Down Expand Up @@ -259,6 +268,17 @@ describe('NimbleTableColumnMapping', () => {
expect(nativeElement.columnHidden).toBe(false);
});

it('can be configured with property binding for pinned', () => {
expect(directive.pinned).toBeFalse();
expect(nativeElement.pinned).toBeFalse();

fixture.componentInstance.pinned = true;
fixture.detectChanges();

expect(directive.pinned).toBeTrue();
expect(nativeElement.pinned).toBeTrue();
});

it('can be configured with property binding for sortDirection', () => {
expect(directive.sortDirection).toBe(TableColumnSortDirection.ascending);
expect(nativeElement.sortDirection).toBe(TableColumnSortDirection.ascending);
Expand Down Expand Up @@ -404,6 +424,7 @@ describe('NimbleTableColumnMapping', () => {
[attr.action-menu-slot]="actionMenuSlot"
[attr.action-menu-label]="actionMenuLabel"
[attr.column-hidden]="columnHidden"
[attr.pinned]="pinned"
[attr.fractional-width]="fractionalWidth"
[attr.min-pixel-width]="minPixelWidth"
[attr.sort-direction]="sortDirection"
Expand All @@ -428,6 +449,7 @@ describe('NimbleTableColumnMapping', () => {
public minPixelWidth: number | null = 40;
public columnId = 'my-column';
public columnHidden = true;
public pinned: BooleanValueOrAttribute = null;
public sortDirection: TableColumnSortDirection = TableColumnSortDirection.ascending;
public sortIndex: number | null = 0;
public sortingDisabled = false;
Expand Down Expand Up @@ -517,6 +539,17 @@ describe('NimbleTableColumnMapping', () => {
expect(nativeElement.columnHidden).toBe(false);
});

it('can be configured with attribute binding for pinned', () => {
expect(directive.pinned).toBeFalse();
expect(nativeElement.pinned).toBeFalse();

fixture.componentInstance.pinned = '';
fixture.detectChanges();

expect(directive.pinned).toBeTrue();
expect(nativeElement.pinned).toBeTrue();
});

it('can be configured with attribute binding for sortDirection', () => {
expect(directive.sortDirection).toBe(TableColumnSortDirection.ascending);
expect(nativeElement.sortDirection).toBe(TableColumnSortDirection.ascending);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export class NimbleTableColumnBaseDirective<T extends TableColumn> {
this.renderer.setProperty(this.elementRef.nativeElement, 'columnHidden', toBooleanProperty(value));
}

public get pinned(): BooleanValueOrAttribute {
return this.elementRef.nativeElement.pinned;
}

@Input('pinned') public set pinned(value: BooleanValueOrAttribute) {
this.renderer.setProperty(this.elementRef.nativeElement, 'pinned', toBooleanProperty(value));
}

public constructor(protected readonly renderer: Renderer2, protected readonly elementRef: ElementRef<T>) {}

public checkValidity(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ describe('Nimble table', () => {
duplicateGroupIndex: false,
idFieldNameNotConfigured: false,
invalidColumnConfiguration: false,
invalidParentIdConfiguration: false
invalidParentIdConfiguration: false,
invalidPinnedColumnConfiguration: false
};
expect(directive.validity).toEqual(expectedValidity);
expect(nativeElement.validity).toEqual(expectedValidity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
column-hidden="@ColumnHidden"
fractional-width="@FractionalWidthAsString"
min-pixel-width="@MinPixelWidthAsString"
pinned="@Pinned"
sort-direction="@SortDirection.ToAttributeValue()"
sort-index="@SortIndex"
sorting-disabled="@SortingDisabled"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ public partial class NimbleTableColumnMapping<TKey> : NimbleTableColumnEnumBase<
[Parameter]
public double? MinPixelWidth { get; set; }

/// <summary>
/// Indicates whether the column is pinned.
/// </summary>
[Parameter]
public bool Pinned { get; set; }

/// <summary>
/// Sets the width mode on the column.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions packages/nimble-components/src/table-column/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export abstract class TableColumn<
@attr({ attribute: 'column-hidden', mode: 'boolean' })
public columnHidden = false;

@attr({ attribute: 'pinned', mode: 'boolean' })
public pinned = false;

/** @internal */
@observable
public hasOverflow = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ describe('TableColumn', () => {
expect(element.columnInternals.currentPixelWidth).toBe(200);
});

it('has expected defaults for pinned', async () => {
await connect();

expect(element.pinned).toBeFalse();
expect(element.hasAttribute('pinned')).toBeFalse();
});

describe('with a custom constructor', () => {
// Seems subject to change how errors are handled during custom
// element construction: https://github.com/WICG/webcomponents/issues/635
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export class TableGroupRow extends FoundationElement {
@observable
public nestingLevel = 0;

@observable
public pinnedColumnOffset = 0;

/**
* Row index in the flattened set of all regular and group header rows.
* Represents the index in table.tableData (TableRowState[]).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import {
controlHeight,
fillHoverColor,
mediumPadding,
standardPadding
standardPadding,
tableRowBorderColor
} from '../../../theme-provider/design-tokens';
import { Theme } from '../../../theme-provider/types';
import { hexToRgbaCssColor } from '../../../utilities/style/colors';
import { themeBehavior } from '../../../utilities/style/theme';
import { userSelectNone } from '../../../utilities/style/user-select';
import { styles as expandCollapseStyles } from '../../../patterns/expand-collapse/styles';
import { focusVisible } from '../../../utilities/style/focus';
import { ZIndexLevels } from '../../../utilities/style/types';

export const styles = css`
${display('grid')}
Expand All @@ -26,6 +28,7 @@ export const styles = css`
height: calc(${controlHeight} + 2 * ${borderWidth});
border-top: calc(2 * ${borderWidth}) solid ${applicationBackgroundColor};
grid-template-columns:
calc(var(--ni-private-table-group-row-pinned-column-offset))
calc(
${controlHeight} *
(var(--ni-private-table-group-row-indent-level) + 1)
Expand All @@ -35,6 +38,7 @@ export const styles = css`

:host([selectable]) {
grid-template-columns:
calc(var(--ni-private-table-group-row-pinned-column-offset))
${controlHeight}
calc(
${controlHeight} *
Expand All @@ -56,11 +60,31 @@ export const styles = css`
background-color: ${fillHoverColor};
}

:host([allow-hover]:hover) .pinned-column-spacer.has-pinned-columns,
:host([allow-hover]:hover) .checkbox-container.has-pinned-columns {
background: linear-gradient(${fillHoverColor}, ${fillHoverColor}),
${tableRowBorderColor};
}

:host(${focusVisible}) {
outline: calc(2 * ${borderWidth}) solid ${borderHoverColor};
outline-offset: calc(-2 * ${borderWidth});
}

.pinned-column-spacer {
height: 100%;
}

.pinned-column-spacer.has-pinned-columns {
position: sticky;
left: 0;
background: ${tableRowBorderColor};
z-index: ${ZIndexLevels.zIndex1000};
}

.expand-collapse-button-container.selectable {
}

.expand-collapse-button {
margin-left: calc(
${mediumPadding} + ${standardPadding} * 2 *
Expand Down Expand Up @@ -90,6 +114,13 @@ export const styles = css`
display: flex;
}

.checkbox-container.has-pinned-columns {
position: sticky;
left: var(--ni-private-table-group-row-pinned-column-offset);
z-index: ${ZIndexLevels.zIndex1000};
background: ${tableRowBorderColor};
}

.selection-checkbox {
margin-left: ${standardPadding};
}
Expand All @@ -104,6 +135,28 @@ export const styles = css`
:host([allow-hover]:hover)::before {
background-color: ${hexToRgbaCssColor(White, 0.05)};
}

:host([allow-hover]:hover) .pinned-column-spacer.has-pinned-columns,
:host([allow-hover]:hover) .checkbox-container.has-pinned-columns {
background: linear-gradient(
${hexToRgbaCssColor(White, 0.05)},
${hexToRgbaCssColor(White, 0.05)}
),
linear-gradient(
${hexToRgbaCssColor(White, 0.1)},
${hexToRgbaCssColor(White, 0.1)}
),
${tableRowBorderColor};
}

.pinned-column-spacer.has-pinned-columns,
.checkbox-container.has-pinned-columns {
background: linear-gradient(
${hexToRgbaCssColor(White, 0.1)},
${hexToRgbaCssColor(White, 0.1)}
),
${tableRowBorderColor};
}
`
),
themeBehavior(
Expand All @@ -112,6 +165,15 @@ export const styles = css`
:host([allow-hover]:hover)::before {
background-color: ${hexToRgbaCssColor(White, 0.1)};
}

:host([allow-hover]:hover) .pinned-column-spacer.has-pinned-columns,
:host([allow-hover]:hover) .checkbox-container.has-pinned-columns {
background: linear-gradient(
${hexToRgbaCssColor(White, 0.1)},
${hexToRgbaCssColor(White, 0.1)}
),
${tableRowBorderColor};
}
`
)
);
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ export const template = html<TableGroupRow>`
role="row"
@click=${x => x.onGroupExpandToggle()}
aria-expanded=${x => x.expanded}
style="--ni-private-table-group-row-indent-level: ${x => x.nestingLevel};"
style="
--ni-private-table-group-row-indent-level: ${x => x.nestingLevel};
--ni-private-table-group-row-pinned-column-offset: ${x => x.pinnedColumnOffset}px;
"
>
<span class="pinned-column-spacer ${x => (x.pinnedColumnOffset > 0 ? 'has-pinned-columns' : '')}"></span>

${when(x => x.selectable, html<TableGroupRow>`
<span role="gridcell" class="checkbox-container">
<span role="gridcell" class="checkbox-container ${x => (x.pinnedColumnOffset > 0 ? 'has-pinned-columns' : '')}">
<${checkboxTag}
${ref('selectionCheckbox')}
class="selection-checkbox"
Expand All @@ -31,7 +36,7 @@ export const template = html<TableGroupRow>`
</span>
`)}

<span role="gridcell">
<span role="gridcell" class="expand-collapse-button-container ${x => (x.selectable ? 'selectable' : '')}">
<${buttonTag}
appearance="${ButtonAppearance.ghost}"
content-hidden
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,9 @@ export class TableRow<
}

private updateCellIndentLevels(): void {
const firstNonPinnedIndex = this.columns.findIndex(col => !col.pinned);
this.cellIndentLevels = this.columns.map((_, i) => {
return i === 0 ? this.nestingLevel : 0;
return i === firstNonPinnedIndex ? this.nestingLevel : 0;
});
}

Expand Down
Loading