Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow icon column to be configured as fixed width #1907

Merged
merged 52 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6ee6e22
prototype
mollykreis Feb 28, 2024
8685685
more changes
mollykreis Feb 29, 2024
d8d1b79
Merge branch 'main' into fixed-icon-column-width
mollykreis Mar 4, 2024
32a7f65
add some tests
mollykreis Mar 4, 2024
7bfa75d
fix bug
mollykreis Mar 4, 2024
a962a67
Update table-column-sizing.spec.ts
mollykreis Mar 5, 2024
88bb26e
Update index.ts
mollykreis Mar 5, 2024
712ed9b
format
mollykreis Mar 5, 2024
9eb87c9
Merge branch 'main' into fixed-icon-column-width
mollykreis Mar 5, 2024
52d8257
Remove fractional-width and min-pixel-width from icon column in Angul…
mollykreis Mar 5, 2024
165e955
Change files
mollykreis Mar 5, 2024
49c8be9
Update NimbleTableColumnIconTests.cs
mollykreis Mar 5, 2024
daf62ea
more updates
mollykreis Mar 5, 2024
21b40c9
update storybook
mollykreis Mar 5, 2024
c66d762
Merge branch 'main' into fixed-icon-column-width
mollykreis Mar 6, 2024
2ee0f3c
Use icons for header of icon columns
mollykreis Mar 6, 2024
3ca2b16
Merge branch 'main' into fixed-icon-column-width
mollykreis Mar 6, 2024
fdf342f
column header updates in examples
mollykreis Mar 6, 2024
7dca3ce
Update customapp.component.html
mollykreis Mar 6, 2024
baf8ee0
Update customapp.component.html
mollykreis Mar 6, 2024
38b1ce9
Export relevant sizes from table-base types
mollykreis Mar 7, 2024
08e675b
Merge branch 'main' into fixed-icon-column-width
mollykreis Mar 7, 2024
21c98a2
Update packages/nimble-components/src/table/tests/table-column-sizing…
mollykreis Mar 7, 2024
a79f942
format
mollykreis Mar 7, 2024
65b73ea
update from main
mollykreis Apr 19, 2024
b4bb765
revert changes no longer needed
mollykreis Apr 19, 2024
8ba65a6
Update NimbleTableColumnIcon.razor.cs
mollykreis Apr 19, 2024
eae9538
add width mode
mollykreis Apr 19, 2024
15c7bab
delete unneeded change files
mollykreis Apr 19, 2024
5b3f7a6
use container query instead of indicators-hidden
mollykreis Apr 19, 2024
5e81f25
Update template.ts
mollykreis Apr 19, 2024
e370b47
format
mollykreis Apr 22, 2024
4c8db64
Merge branch 'main' into fixed-icon-column-width
mollykreis Apr 22, 2024
dc8bcd3
update min-pixel-width behavior
mollykreis Apr 22, 2024
1d5564d
a few more updates
mollykreis Apr 22, 2024
fd64c39
try this instead
mollykreis Apr 22, 2024
618ade0
Revert "try this instead"
mollykreis Apr 22, 2024
d397037
Update styles.ts
mollykreis Apr 22, 2024
a09a491
Don't put iconSize column as last column
mollykreis Apr 23, 2024
45277ee
Merge branch 'main' into fixed-icon-column-width
mollykreis Apr 23, 2024
ee95114
remove container query and add hideHeaderIndicators to columnInternals
mollykreis Apr 24, 2024
064052a
format
mollykreis Apr 24, 2024
7d75bbe
a few more updates
mollykreis Apr 24, 2024
e42b242
Merge branch 'main' into fixed-icon-column-width
mollykreis Apr 24, 2024
9a96d57
Merge branch 'main' into fixed-icon-column-width
mollykreis Apr 29, 2024
b150370
Update packages/nimble-components/src/table-column/icon/tests/table-c…
mollykreis Apr 29, 2024
a647298
Merge branch 'fixed-icon-column-width' of https://github.com/ni/nimbl…
mollykreis Apr 29, 2024
9c9c907
Move sizing constants
mollykreis Apr 29, 2024
e6e2176
don't cache minPixelWidth or resizingDisabled in initialColumnWidths …
mollykreis Apr 30, 2024
1b69350
format
mollykreis Apr 30, 2024
38b3d29
Merge branch 'main' into fixed-icon-column-width
mollykreis Apr 30, 2024
1c19049
Merge branch 'main' into fixed-icon-column-width
rajsite Apr 30, 2024
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 @@
{
jattasNI marked this conversation as resolved.
Show resolved Hide resolved
"type": "minor",
"comment": "Add configuration to make the icon column fixed width",
"packageName": "@ni/nimble-components",
"email": "20542556+mollykreis@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ export class ColumnInternals<
@observable
public minPixelWidth = defaultMinPixelWidth;

/**
* Whether or not resizing the column has been disabled.
*/
@observable
public resizingDisabled = false;

/**
* Whether or not the grouping and sorting indicators should be hidden in the column header
* when the column is grouped or sorted.
*/
@observable
public hideHeaderIndicators = false;

/**
* @internal Do not write to this value directly. It is used by the Table in order to store
* the resolved value of the fractionalWidth after updates programmatic or interactive updates.
Expand Down
25 changes: 14 additions & 11 deletions packages/nimble-components/src/table-column/base/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,22 @@ export type TableColumnSortOperation =
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface TableColumnValidity extends ValidityObject {}

const groupIconSize = 16;
const sortIconSize = 16;
const spacing = 8;
const menuDropdownSize = 24;
const columnIconSize = 16; // `iconSize` token
const columnSpacing = 8; // `mediumPadding` token
const menuDropdownSize = 24; // `controlSlimHeight` token
const oneCharPlusEllipsisSize = 21;
export const defaultMinPixelWidth = spacing
export const defaultMinPixelWidth = columnSpacing // left cell padding
+ oneCharPlusEllipsisSize
+ spacing
+ sortIconSize
+ spacing
+ groupIconSize
+ spacing
+ columnSpacing
+ columnIconSize // sort icon
+ columnSpacing
+ columnIconSize // group icon
+ columnSpacing
+ menuDropdownSize
+ spacing;
+ columnSpacing; // right cell padding

export const singleIconColumnWidth = columnSpacing // left cell padding
+ columnIconSize
+ columnSpacing; // right cell padding

export const defaultFractionalWidth = 1;
38 changes: 37 additions & 1 deletion packages/nimble-components/src/table-column/icon/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { DesignSystem } from '@microsoft/fast-foundation';
import { attr } from '@microsoft/fast-element';
import {
MappingConfigs,
TableColumnEnumBase,
TableColumnEnumColumnConfig
} from '../enum-base';
import { styles } from '../enum-base/styles';
import { template } from '../enum-base/template';
import { TableColumnSortOperation } from '../base/types';
import {
TableColumnSortOperation,
singleIconColumnWidth,
defaultMinPixelWidth
} from '../base/types';
import { mixinGroupableColumnAPI } from '../mixins/groupable-column';
import { mixinFractionalWidthColumnAPI } from '../mixins/fractional-width-column';
import { MappingSpinner } from '../../mapping/spinner';
Expand All @@ -21,6 +26,7 @@ import { MappingIconConfig } from '../enum-base/models/mapping-icon-config';
import { MappingSpinnerConfig } from '../enum-base/models/mapping-spinner-config';
import { MappingText } from '../../mapping/text';
import { MappingTextConfig } from '../enum-base/models/mapping-text-config';
import { TableColumnMappingWidthMode } from './types';

declare global {
interface HTMLElementTagNameMap {
Expand All @@ -39,6 +45,15 @@ export class TableColumnIcon extends mixinGroupableColumnAPI(
>
)
) {
@attr({ attribute: 'width-mode' })
public widthMode: TableColumnMappingWidthMode;

public override minPixelWidthChanged(): void {
if (this.widthMode !== TableColumnMappingWidthMode.iconSize) {
this.columnInternals.minPixelWidth = this.getConfiguredMinPixelWidth();
}
}

protected override getColumnInternalsOptions(): ColumnInternalsOptions<TableColumnIconValidator> {
return {
cellRecordFieldNames: ['value'],
Expand Down Expand Up @@ -77,6 +92,27 @@ export class TableColumnIcon extends mixinGroupableColumnAPI(
// this function from running when there is an unsupported mapping.
throw new Error('Unsupported mapping');
}

private widthModeChanged(): void {
if (this.widthMode === TableColumnMappingWidthMode.iconSize) {
this.columnInternals.resizingDisabled = true;
this.columnInternals.hideHeaderIndicators = true;
this.columnInternals.pixelWidth = singleIconColumnWidth;
this.columnInternals.minPixelWidth = singleIconColumnWidth;
} else {
this.columnInternals.resizingDisabled = false;
this.columnInternals.hideHeaderIndicators = false;
this.columnInternals.pixelWidth = undefined;
this.columnInternals.minPixelWidth = this.getConfiguredMinPixelWidth();
}
}

private getConfiguredMinPixelWidth(): number {
if (typeof this.minPixelWidth === 'number') {
return this.minPixelWidth;
}
return defaultMinPixelWidth;
}
}

const nimbleTableColumnIcon = TableColumnIcon.compose({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { mappingSpinnerTag } from '../../../mapping/spinner';
import { isChromatic } from '../../../utilities/tests/isChromatic';
import { iconXmarkTag } from '../../../icons/xmark';
import { mappingTextTag } from '../../../mapping/text';
import { TableColumnMappingWidthMode } from '../types';
import { iconQuestionTag } from '../../../icons/question';

const data = [
{
Expand Down Expand Up @@ -80,6 +82,19 @@ const component = (): ViewTemplate => html`
<${mappingIconTag} key="1" text="One" icon="${iconCheckTag}" severity="warning"></${mappingIconTag}>
<${mappingIconTag} key="2" text="Two" icon="${iconCheckTag}" severity="error"></${mappingIconTag}>
</${tableColumnIconTag}>
<${tableColumnIconTag}
field-name="code"
key-type="number"
width-mode="${TableColumnMappingWidthMode.iconSize}"
>
<${iconQuestionTag} title="Icon-only column"></${iconQuestionTag}>
<${mappingIconTag} key="-1" text="Unknown value"></${mappingIconTag}>
<${mappingIconTag} key="0" text="Zero" icon="${iconCheckTag}" severity="success" text-hidden></${mappingIconTag}>
<${mappingIconTag} key="1" text="One" icon="${iconCheckTag}" severity="warning" text-hidden></${mappingIconTag}>
<${mappingIconTag} key="2" text="Two" icon="${iconCheckTag}" severity="error" text-hidden></${mappingIconTag}>
<${mappingIconTag} key="3" text="Three" icon="${iconCheckTag}" severity="information" text-hidden></${mappingIconTag}>
<${mappingIconTag} key="4" text="Four" icon="${iconCheckTag}" text-hidden></${mappingIconTag}>
</${tableColumnIconTag}>
<${tableColumnIconTag}
field-name="code"
key-type="number"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { spinnerTag } from '../../../spinner';
import { themeProviderTag } from '../../../theme-provider';
import { TableColumnIconPageObject } from '../testing/table-column-icon.pageobject';
import { mappingUserTag } from '../../../mapping/user';
import { TableColumnMappingWidthMode } from '../types';
import { defaultMinPixelWidth } from '../../base/types';

interface SimpleTableRecord extends TableRecord {
field1?: MappingKey | null;
Expand Down Expand Up @@ -888,4 +890,99 @@ describe('TableColumnIcon', () => {
});
});
});

describe('width-mode', () => {
beforeEach(async () => {
({ connect, disconnect, model } = await setup({
keyType: MappingKeyType.string
}));
});

it('defaults to `default`', () => {
expect(model.col1.widthMode).toBe(
TableColumnMappingWidthMode.default
);
expect(model.col1.columnInternals.resizingDisabled).toBeFalse();
});

it('column configuration is updated when set to `iconSize`', async () => {
model.col1.widthMode = TableColumnMappingWidthMode.iconSize;
await waitForUpdatesAsync();

expect(model.col1.columnInternals.resizingDisabled).toBeTrue();
expect(model.col1.columnInternals.pixelWidth).toBe(32);
expect(model.col1.columnInternals.minPixelWidth).toBe(32);
});

it('column changes back to fractionally sized when changing from `iconSize` to `default`', async () => {
model.col1.widthMode = TableColumnMappingWidthMode.iconSize;
await waitForUpdatesAsync();
model.col1.widthMode = TableColumnMappingWidthMode.default;
await waitForUpdatesAsync();

expect(model.col1.columnInternals.resizingDisabled).toBeFalse();
expect(model.col1.columnInternals.pixelWidth).toBe(undefined);
expect(model.col1.columnInternals.minPixelWidth).toBe(
defaultMinPixelWidth
);
});

it('changing min-pixel-width with mode of `iconSize` does not change minimum width of column', async () => {
model.col1.widthMode = TableColumnMappingWidthMode.iconSize;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(32);

model.col1.minPixelWidth = 500;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(32);
});

it('previously configured min-pixel-width is retained when switching from `default` to `iconSize` and back to `default`', async () => {
model.col1.widthMode = TableColumnMappingWidthMode.default;
model.col1.minPixelWidth = 500;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(500);

model.col1.widthMode = TableColumnMappingWidthMode.iconSize;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(32);

model.col1.widthMode = TableColumnMappingWidthMode.default;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(500);
});

it('min-pixel-width applied with mode of `iconSize` is used when width-mode changes to `default`', async () => {
model.col1.widthMode = TableColumnMappingWidthMode.iconSize;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(32);

model.col1.minPixelWidth = 500;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(32);

model.col1.widthMode = TableColumnMappingWidthMode.default;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(500);
});

it('clearing min-pixel-width while in `iconSize` mode resets the minimum width to default', async () => {
model.col1.widthMode = TableColumnMappingWidthMode.default;
model.col1.minPixelWidth = 500;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(500);

model.col1.widthMode = TableColumnMappingWidthMode.iconSize;
await waitForUpdatesAsync();
model.col1.minPixelWidth = undefined;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(32);

model.col1.widthMode = TableColumnMappingWidthMode.default;
await waitForUpdatesAsync();
expect(model.col1.columnInternals.minPixelWidth).toBe(
defaultMinPixelWidth
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { mappingSpinnerTag } from '../../../mapping/spinner';
import { sharedMappingValidityDescription } from '../../enum-base/tests/shared-storybook-docs';
import { isChromatic } from '../../../utilities/tests/isChromatic';
import { mappingTextTag } from '../../../mapping/text';
import { TableColumnMappingWidthMode } from '../types';
import { iconChartDiagramChildFocusTag } from '../../../icons/chart-diagram-child-focus';

const simpleData = [
{
Expand Down Expand Up @@ -65,10 +67,16 @@ export default metadata;
interface IconColumnTableArgs extends SharedTableArgs {
fieldName: string;
keyType: string;
widthMode: keyof typeof TableColumnMappingWidthMode;
checkValidity: () => void;
validity: () => void;
}

const widthModeDescription = `When set to \`iconSize\`, the column will have a fixed width that makes the column the appropriate width to render only a single icon in the cell.
This should only be set when the header contains a single icon (no text) and none of the child mapping elements will result in text being rendered in a cell. When unset or set
to \`default\`, the column will be resizable and be sized based on its fractional-width and min-pixel-width values. A column with its \`width-mode\` set to \`iconSize\` should
should not be the right-most column in the table.`;

const validityDescription = `${sharedMappingValidityDescription}
- \`invalidIconName\`: \`true\` when a mapping's \`icon\` value is not the tag name of a valid, loaded Nimble icon (e.g. \`nimble-icon-check\`)
`;
Expand All @@ -91,10 +99,11 @@ export const iconColumn: StoryObj<IconColumnTableArgs> = {
<${mappingSpinnerTag} key="calculating" text="Calculating" text-hidden></${mappingSpinnerTag}>
<${mappingIconTag} key="unknown" text="Unknown" text-hidden></${mappingIconTag}>
</${tableColumnIconTag}>
<${tableColumnIconTag} field-name="isChild" key-type="boolean">
Is Child
<${mappingIconTag} key="false" icon="${iconXmarkTag}" severity="error" text="Not a child"></${mappingIconTag}>
<${mappingIconTag} key="true" icon="${iconCheckLargeTag}" severity="success" text="Is a child"></${mappingIconTag}>
<${tableColumnIconTag} field-name="isChild" key-type="boolean" width-mode="${x => TableColumnMappingWidthMode[x.widthMode]}">
<${iconChartDiagramChildFocusTag} title="Is child"></${iconChartDiagramChildFocusTag}>

<${mappingIconTag} key="false" icon="${iconXmarkTag}" severity="error" text="Not a child" text-hidden></${mappingIconTag}>
<${mappingIconTag} key="true" icon="${iconCheckLargeTag}" severity="success" text="Is a child" text-hidden></${mappingIconTag}>
</${tableColumnIconTag}>
<${tableColumnIconTag} field-name="gender" key-type="string">
Gender
Expand Down Expand Up @@ -123,6 +132,12 @@ export const iconColumn: StoryObj<IconColumnTableArgs> = {
description:
'The data type of the key values used for this column. Must be one of `"string"`, `"number"`, or `"boolean"`. Defaults to `"string"` if unspecified.'
},
widthMode: {
name: 'width-mode',
options: Object.keys(TableColumnMappingWidthMode),
control: { type: 'radio' },
description: widthModeDescription
},
checkValidity: {
name: 'checkValidity()',
description:
Expand All @@ -136,6 +151,7 @@ export const iconColumn: StoryObj<IconColumnTableArgs> = {
...sharedTableArgs(simpleData),
fieldName: 'firstName',
keyType: 'string',
widthMode: 'iconSize',
checkValidity: () => {},
validity: () => {}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { TableColumnMappingWidthMode } from '../types';

describe('Icon column type', () => {
it('TableColumnMappingWidthMode fails compile if assigning arbitrary string values', () => {
// @ts-expect-error This expect will fail if the enum-like type is missing "as const"
const widthMode: TableColumnMappingWidthMode = 'hello';
expect(widthMode!).toEqual('hello');
});
});
9 changes: 9 additions & 0 deletions packages/nimble-components/src/table-column/icon/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Width mode for the icon column
*/
export const TableColumnMappingWidthMode = {
default: undefined,
iconSize: 'icon-size'
} as const;
export type TableColumnMappingWidthMode =
(typeof TableColumnMappingWidthMode)[keyof typeof TableColumnMappingWidthMode];
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export class TableHeader extends FoundationElement {
@attr({ attribute: 'first-sorted-column', mode: 'boolean' })
public firstSortedColumn = false;

@attr({ attribute: 'indicators-hidden', mode: 'boolean' })
public indicatorsHidden = false;

@observable
public isGrouped = false;

Expand Down
Loading
Loading