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

Update mapping column HLD #1992

Merged
merged 22 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
"type": "none",
"comment": "Update mapping column HLD with new plan of supporting icon + text in the same cell",
"packageName": "@ni/nimble-components",
"email": "20542556+mollykreis@users.noreply.github.com",
"dependentChangeType": "none"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

The `nimble-table-column-enum-text` is a component that supports rendering specific number, boolean, or string values as mapped text. `nimble-table-column-icon` instead maps values to icons and/or spinners. The actual mappings are defined by child elements `nimble-mapping-icon`, `nimble-mapping-spinner`, and `nimble-mapping-text`.
The `nimble-table-column-enum` is a component that supports rendering specific number, boolean, or string values as an icon, a spinner, text, or an icon/spinner with text. The mappings are defined by child elements of `nimble-mapping-icon`, `nimble-mapping-spinner`, `nimble-mapping-text`, or `nimble-mapping-placeholder`.

### Background

Expand All @@ -20,15 +20,18 @@ The `nimble-table-column-enum-text` is a component that supports rendering speci
- Text
- Icon
- Spinner
- Icon with text
- Spinner with text
- (empty)

### Non-goals

- Non-Nimble icon support
- Arbitrary icon colors
- Hyperlink icons
- Mixed text and icons
- Non-icon, non-spinner Nimble components
- Specifying different text for an icon/spinner's label than the overall label of the mapping
- Detecting that only icons/spinners can be rendered in the cells and column header and automatically making the column non-resizable and 32px

---

Expand All @@ -38,29 +41,30 @@ Below is an example of how these elements would be used within a `nimble-table`:

```HTML
<nimble-table>
<nimble-table-column-icon field-name="status" key-type="string">
<nimble-table-column-enum field-name="status" key-type="string">
Status
<nimble-mapping-icon key="fail" icon="nimble-icon-xmark" severity="error" label="Failed"></nimble-mapping-icon>
<nimble-mapping-icon key="error" icon="nimble-icon-xmark" severity="error" label="Errored"></nimble-mapping-icon>
<nimble-mapping-icon key="pass" icon="nimble-icon-check" severity="success" label="Passed"></nimble-mapping-icon>
<nimble-mapping-spinner key="running" label="Running"></nimble-mapping-spinner>
</nimble-table-column-icon>
<nimble-table-column-enum-text field-name="errorCode" key-type="number">
</nimble-table-column-enum>
<nimble-table-column-enum field-name="errorCode" key-type="number">
Error Code
<nimble-mapping-text key="1" label="A bad thing happened"></nimble-mapping-text>
<nimble-mapping-text key="2" label="A worse thing happened"></nimble-mapping-text>
<nimble-mapping-text key="3" label="A terrible thing happened"></nimble-mapping-text>
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
</nimble-table-column-enum-text>
<nimble-table-column-icon field-name="archived" key-type="boolean">
</nimble-table-column-enum>
<nimble-table-column-enum field-name="archived" key-type="boolean">
Archived
<nimble-mapping-icon key="true" icon="nimble-icon-database" label="Archived"></nimble-mapping-icon>
</nimble-table-column-icon>
<nimble-mapping-icon key="true" icon="nimble-icon-database" label="Archived" label-hidden></nimble-mapping-icon>
<nimble-mapping-placeholder key="false" label="Not archived"></nimble-mapping-placeholder>
</nimble-table-column-enum>
</nimble-table>
```

Each column contains mapping elements that define what to render when the cell's value matches the given `key` value.

When none of the given mappings match the record value for a cell, that cell will be empty.
When none of the given mappings match the record value for a cell, that cell will be empty. While the table will not enter an error state, this is considered invalid data from the table's perspective and should be fixed within the client application.

The column will translate its contained mapping elements into a map that is stored in the `columnConfig`.

Expand All @@ -70,7 +74,7 @@ If multiple mappings in a column have the same key, an error flag will be set on

If an invalid `icon` value is passed to `nimble-mapping-icon`, an error flag will be set on the column's validity object. An invalid `icon` value is any element that cannot be resolved or that does not derive from `Icon`.

`nimble-table-column-icon` supports only `nimble-mapping-icon` and `nimble-mapping-spinner` as mapping elements. `nimble-table-column-enum-text` supports only `nimble-mapping-text`. Unsupported mappings will result in an error flag being set on the column's validity object.
`nimble-table-column-enum` supports `nimble-mapping-icon`, `nimble-mapping-spinner`, `nimble-mapping-text`, and `nimble-mapping-placeholder` as mapping elements. Unsupported mappings will result in an error flag being set on the column's validity object.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved

Text in a grouping header or in the cell will be ellipsized and gain a tooltip when the full text is too long to display.

Expand All @@ -92,66 +96,69 @@ Cons:

### API

#### Icon column element:
#### Enum column element:

_Component Name_

- `nimble-table-column-icon`
- `nimble-table-column-enum`
mollykreis marked this conversation as resolved.
Show resolved Hide resolved

_Props/Attrs_

- `field-name`: string
- `key-type`: 'string' | 'number' | 'boolean'
- `fixed-to-icon-width`: boolean - When set, 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.

_Content_

- column title (icon)
- 1 or more `nimble-mapping-icon` or `nimble-mapping-spinner` elements

#### General mapping column element:

_Component Name_

- `nimble-table-column-enum-text`

_Props/Attrs_

- `field-name`: string
- `key-type`: 'string' | 'number' | 'boolean'
- `fractional-width`: number (defaults to 1)
- `min-pixel-width`: number (defaults to minimum supported by table)

_Content_

- column title (text or icon)
- 1 or more `nimble-mapping-text` elements
- column title (icon and/or text)
- 1 or more `nimble-mapping-icon`, `nimble-mapping-spinner`, `nimble-mapping-text`, or `nimble-mapping-placeholder` elements

#### Mapping element (icon):

The icon mapping element will support displaying an icon, icon with text, or only text in a cell. If only text is displayed in a cell, space will be reserved for an icon so that the text associated with all icon mapping elements is aligned. A group row associated with an icon mapping element will always display the icon, if specified, and text.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved

_Component Name_

- `nimble-mapping-icon`

_Props/Attrs_

- `key`: string | number | boolean | undefined
- `icon`: string - name of the Nimble icon element
- `icon`: string | undefined - name of the Nimble icon element. If `undefined`, no icon will be associated with the given `key`, but space will be reserved in the cell for an icon.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
- `severity`: string - one of the supported enum values. Controls color of the icon.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
- `label`: string - localized value used as the accessible name and `title` of the icon. Will also be displayed in the group header.
- `label`: string - localized value associated with the given `key`.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
- `label-hidden`: boolean - When set, the label will not be rendered within a cell. When unset, the label will be rendered with a cell. This does not affect the rendering of group rows; group rows will always display the label associated with the mapping.

The label will be used in the following places:

- In the group row for a mapping
- If `label-hidden` is set, as the accessible name and `title` of the icon within a cell
- If `label-hidden` is not set, as text rendered next to the icon within a cell

#### Mapping element (spinner):

The spinner mapping element will support displaying a spinner or spinner with text in a cell. A group row associated with a spinner mapping element will always display the spinner and text.

_Component Name_

- `nimble-mapping-spinner`

_Props/Attrs_

- `key`: string | number | boolean | undefined
- `label`: string - localized value used as the accessible name and `title` of the spinner. Will also be displayed in the group header.
- `label`: string - localized value associated with the given `key`.
- `label-hidden`: boolean - When set, the label will not be rendered within a cell. When unset, the label will be rendered with a cell. This does not affect the rendering of group rows; group rows will always display the label associated with the mapping.

The label will be used in the following places:

- In the group row for a mapping
- If `label-hidden` is set, as the accessible name and `title` of the spinner within a cell
- If `label-hidden` is not set, as text rendered next to the spinner within a cell

#### Mapping element (text):

The text mapping element will support displaying text in a cell. A group row associated with a text mapping will also display the text associated with the mapping.

_Component Name_

- `nimble-mapping-text`
Expand All @@ -161,37 +168,18 @@ _Props/Attrs_
- `key`: string | number | boolean | undefined
- `label`: string - display text

### Anatomy

#### `nimble-table-column-enum-text`
#### Mapping element (placeholder):

```HTML
<template slot="${x => x.columnInternals.uniqueId}">
<slot
${slotted('mappings')}
name="mapping">
</slot>
<span class="header-content">
<slot></slot>
</span>
</template>
```
The placeholder mapping element will display an empty cell. A group row associated with a placeholder mapping will display the mapping's text. The purpose of the placeholder mapping element is to allow clients to avoid cluttering their table with information that isn't particularly helpful to a user (e.g. that the state of a system is 'idle') while still having a good grouping experience that ensures group rows are not blank.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved

Cell view:

The cell view relies on the matched mapping to provide a template to render.

```HTML
html<TableColumnMappingCellView>`${x => x.getMappingToRender().cellViewTemplate}`
```
_Component Name_

Group header view:
- `nimble-mapping-placeholder`

Similarly, the group header view relies on the matched mapping to provide a template to render.
_Props/Attrs_

```HTML
html<TableColumnMappingHeaderView>`${x => x.getMappingToRender().groupHeaderViewTemplate}`
```
- `key`: string | number | boolean | undefined
- `label`: string - display text

#### `nimble-mapping-*`

Expand All @@ -205,38 +193,34 @@ html<TableColumnMappingHeaderView>`${x => x.getMappingToRender().groupHeaderView

```HTML
<${this.icon}
title="${x => x.label}"
aria-label="${x => x.label}"
title="${x => x.labelHidden ? x.label: null}"
role="img"
aria-label="${x => x.labelHidden ? x.label: null}"
aria-hidden="${x => x.labelHidden ? 'false' : 'true'}"
severity="${x => x.severity}">
</${this.icon}>
${when(() => !x.labelHidden, html`
<span>${x => x.label}</span>
`)}
```

`mapping.groupHeaderViewTemplate`:

```HTML
<${this.icon}
title="${x => x.label}"
aria-label="${x => x.label}"
aria-hidden="true"
severity="${x => x.severity}">
</${this.icon}>
<span
${ref('span')}
@mouseover="${x => {
x.isValidContentAndHasOverflow = !!x.label && x.span!.offsetWidth < x.span!.scrollWidth;
}}"
@mouseout="${x => {
x.isValidContentAndHasOverflow = false;
}}"
title=${x => (x.isValidContentAndHasOverflow ? x.label : null)}>
<span>
mollykreis marked this conversation as resolved.
Show resolved Hide resolved
${x => x.label}
</span>`;
```

### Grouping

Grouping will be based on the record value. The grouping header will display the rendered icon/spinner/text. In the case of an icon/spinner, it will also be followed by the `label` text. This will disambiguate cases where multiple record values map to the same icon (assuming the labels are different).
Grouping will be based on the record value. The grouping header will display the rendered label along with the icon/spinner, if configured.

For values that do not match any mapping, we will display the raw data value. While this introduces inconsistency, it seems preferable to the alternative, which is having multiple, separate groupings with a blank header (well, with just the item count in parens). Even in the case where there is a default mapping, we would still end up with separate groups with the identical default mapped icon and/or text, which is just as bad.
For values that do not match any mapping, the group row will display only the count. This is considered invalid data from the table's perspective and should be fixed within the client application.
mollykreis marked this conversation as resolved.
Show resolved Hide resolved

Text in a grouping header will be ellipsized and gain a tooltip if there is not enough room to display it all.

Expand All @@ -258,25 +242,20 @@ For icons, if multiple values map to the same icon, it is possible that sorting

### Sizing

`nimble-table-column-icon` will be a fixed pixel size (32px) and will not be resizable. The 32px fixed size allows room from a single icon along with left and right cell padding of 8px each.
By default, the `nimble-table-column-enum` will be a fractional width column with a fractional width of 1. However, it can be configured to be a fixed pixel size (32px) and not be resizable by setting `fixed-to-icon-width` on the column. The 32px fixed size allows room from a single icon or spinner along with left and right cell padding of 8px each.

This has the following implications:
When the column is a fixed 32px:

- The grouping indicator and sorting indicator will always be hidden on the icon column.
- The grouping indicator and sorting indicator will be hidden in the column header.
- A client is expected to only place an icon as the header content of an icon column.
- A user cannot resize an icon column.
- There will be no public API exposed on the icon column related to sizing. Unlike other columns today, the icon column will not have attributes for `min-pixel-width` or `fractional-width`.
- Column sizing configuration on the column, such as `fractional-width`, will be ignored.

This will be accomplished through the following configuration on the column:

- The icon column will not use the `mixinFractionalWidthColumnAPI` mixin because it will not expose a sizing API.
- The icon column will set `columnInternals.resizingDisabled` to `true`.
- The icon column will set both `columnInternals.pixelWidth` and `columnInternals.minPixelWidth` to `32`, which is equal to the icon size plus left and right paddings of 8px

In the future, we can consider adding an API to allow the icon column to have its size configured by a user and/or client, but that is currently out of scope.

`nimble-table-column-enum-text` will support fixed or fractional widths. If `pixel-width` is set, the column will have a fixed width, otherwise it defaults to a fractional width of 1. The client may configure `fractional-width` and/or `min-pixel-width`.

### Angular integration

Angular directives will be created for the column components and the mapping components. No component has form association, so a `ControlValueAccessor` will not be created.
Expand All @@ -286,10 +265,10 @@ Angular directives will be created for the column components and the mapping com
Blazor wrappers will be created for the components. Columns will be generic in the type of the key, and will cascade that type parameter to contained mapping elements (see [`CascadingTypeParameter`](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/generic-type-support?view=aspnetcore-7.0#cascaded-generic-type-support)):

```HTML
<NimbleTableColumnEnumText TKey=int Field="NumberData">
<NimbleTableColumnEnum TKey=int Field="NumberData">
<NimbleMappingText Key="1" Label="foo"></NimbleMappingText>
<NimbleMappingText Key="2" Label="bar"></NimbleMappingText>
</NimbleTableColumnEnumText>
</NimbleTableColumnEnum>
```

### Visual Appearance
Expand All @@ -306,7 +285,9 @@ N/A

### Accessibility

Text, icons, and spinner are not interactive and cannot receive keyboard focus. These items already have proper accessibility roles, and we will set accessible names (`aria-label`) based on the `label` value provided by the client.
- Text, icons, and spinner are not interactive and cannot receive keyboard focus.
- In group rows, the rendered icon/spinner will have `aria-hidden="true"` set because the label is displayed directly next to the icon. The icon/spinner is purely decorative, and it does not contain any additional information that needs to be available with a screen reader.
- In table cells, if an icon/spinner is displayed with the label, the icon/spinner will have `aria-hidden="true"` set for the same reason explained for group rows above. However, if the label is not displayed, the icon/spinner will have a role of `img` and use the label as its `title` and `aria-label`.

### Globalization

Expand All @@ -330,10 +311,7 @@ None
- renders mapping matching the cell value (string, number, and boolean)
- nothing rendered when value matches no mappings
- validation error when non-unique mapping keys exist
- validation error when multiple mappings marked as default
- validation error when mapping key is null and not marked default
- validation error when invalid icon name given
- validation error when icon column has a `nimble-mapping-text` element
- grouping header for icon column includes label
- Verify manually that the column content appears in the accessibility tree and can be read by a screen reader.
- Verify manually that several mapping columns with thousands of elements scrolls performantly.
Expand Down
Loading