diff --git a/change/@ni-nimble-components-e559fa6d-6507-478a-9e4e-889ea5cb3c9d.json b/change/@ni-nimble-components-e559fa6d-6507-478a-9e4e-889ea5cb3c9d.json new file mode 100644 index 0000000000..2efc9774d5 --- /dev/null +++ b/change/@ni-nimble-components-e559fa6d-6507-478a-9e4e-889ea5cb3c9d.json @@ -0,0 +1,7 @@ +{ + "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" +} diff --git a/packages/nimble-components/src/table/specs/table-column-specs/table-column-mapping.md b/packages/nimble-components/src/table/specs/table-column-specs/table-column-mapping.md index c58c1e475e..0a154c616c 100644 --- a/packages/nimble-components/src/table/specs/table-column-specs/table-column-mapping.md +++ b/packages/nimble-components/src/table/specs/table-column-specs/table-column-mapping.md @@ -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-mapping` 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-empty`. ### Background @@ -20,6 +20,8 @@ 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 @@ -27,8 +29,9 @@ The `nimble-table-column-enum-text` is a component that supports rendering speci - 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 text 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 --- @@ -38,29 +41,30 @@ Below is an example of how these elements would be used within a `nimble-table`: ```HTML - + Status - - - - - - + + + + + + Error Code - - - - - + + + + + Archived - - + + + ``` 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`. @@ -70,8 +74,6 @@ 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. - 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. **Alternatives:** @@ -92,42 +94,29 @@ Cons: ### API -#### Icon column element: +#### Enum column element: _Component Name_ -- `nimble-table-column-icon` - -_Props/Attrs_ - -- `field-name`: string -- `key-type`: 'string' | 'number' | 'boolean' - -_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` +- `nimble-table-column-mapping` _Props/Attrs_ - `field-name`: string - `key-type`: 'string' | 'number' | 'boolean' +- `width-mode`: enum - `default` (`undefined`) | `iconSize` (`'icon-size'`) - 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. - `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-empty` 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. + _Component Name_ - `nimble-mapping-icon` @@ -135,12 +124,21 @@ _Component Name_ _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. - `severity`: string - one of the supported enum values. Controls color of the icon. -- `label`: string - localized value used as the accessible name and `title` of the icon. Will also be displayed in the group header. +- `text`: string - localized value associated with the given `key`. +- `text-hidden`: boolean - When set, the mapping's text will not be rendered within a cell. When unset, the text will be rendered in a cell. This does not affect the rendering of group rows; group rows will always display the text associated with the mapping. + +The text will be used in the following places: + +- In the group row for a mapping +- If `text-hidden` is set, as the accessible name and `title` of the icon within a cell +- If `text-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` @@ -148,10 +146,19 @@ _Component Name_ _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. +- `text`: string - localized value associated with the given `key`. +- `text-hidden`: boolean - When set, the mapping's text will not be rendered within a cell. When unset, the text will be rendered in a cell. This does not affect the rendering of group rows; group rows will always display the text associated with the mapping. + +The text will be used in the following places: + +- In the group row for a mapping +- If `text-hidden` is set, as the accessible name and `title` of the spinner within a cell +- If `text-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` @@ -159,39 +166,20 @@ _Component Name_ _Props/Attrs_ - `key`: string | number | boolean | undefined -- `label`: string - display text +- `text`: string - display text -### Anatomy +#### Mapping element (empty): -#### `nimble-table-column-enum-text` +The empty mapping element will display an empty cell. A group row associated with an empty mapping will display the mapping's text. The purpose of the empty 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 empty. -```HTML - -``` - -Cell view: - -The cell view relies on the matched mapping to provide a template to render. - -```HTML -html`${x => x.getMappingToRender().cellViewTemplate}` -``` +_Component Name_ -Group header view: +- `nimble-mapping-empty` -Similarly, the group header view relies on the matched mapping to provide a template to render. +_Props/Attrs_ -```HTML -html`${x => x.getMappingToRender().groupHeaderViewTemplate}` -``` +- `key`: string | number | boolean | undefined +- `text`: string - display text #### `nimble-mapping-*` @@ -205,38 +193,34 @@ html`${x => x.getMappingToRender().groupHeaderView ```HTML <${this.icon} - title="${x => x.label}" - aria-label="${x => x.label}" + title="${x => x.textHidden ? x.text: null}" + role="img" + aria-label="${x => x.textHidden ? x.text: null}" + aria-hidden="${x => x.textHidden ? 'false' : 'true'}" severity="${x => x.severity}"> +${when(() => !x.textHidden, html` + ${x => x.text} +`)} ``` `mapping.groupHeaderViewTemplate`: ```HTML <${this.icon} - title="${x => x.label}" - aria-label="${x => x.label}" + aria-hidden="true" severity="${x => x.severity}"> - (x.isValidContentAndHasOverflow ? x.label : null)}> - ${x => x.label} + + ${x => x.text} `; ``` ### 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 text 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. Text in a grouping header will be ellipsized and gain a tooltip if there is not enough room to display it all. @@ -245,9 +229,9 @@ Text in a grouping header will be ellipsized and gain a tooltip if there is not Sorting will be based on the record value. For boolean and number values, a basic sort (just using basic comparison/equality operators) is the clear choice. For string values, it is less clear. In the case where the strings are enum values, they are likely to be non-localized, English strings. Using a basic (alphabetical) sort is not unreasonable. However, if there is a semantically meaningful sort order (e.g. "NOT_STARTED" < "RUNNING" < "DONE"), it would be nice to sort by that. We can only infer that semantic order from the order in which the mappings are given, e.g.: ```HTML - - - + + + ``` We would need new support for sorting this way. We could define a new sorting option, "enumerated sort", where the column provides an ordered list of values, and the table sorts the column based on that given order. To compare the relative order of two values, we have to search the list, making the sort operation a bit more expensive, but still probably reasonable except in the case of enums with many values. @@ -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-mapping` 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 `width-mode` to `iconSize`. 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. @@ -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 - - - - + + + + ``` ### Visual Appearance @@ -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 text 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 text, the icon/spinner will have `aria-hidden="true"` set for the same reason explained for group rows above. However, if the text is not displayed, the icon/spinner will have a role of `img` and use the text as its `title` and `aria-label`. ### Globalization @@ -330,11 +311,8 @@ 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 + - grouping header for icon column includes text - 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. - Visual Chromatic tests will be created