diff --git a/change/@ni-nimble-components-793b5a45-5de7-42b6-8659-bea657c748b9.json b/change/@ni-nimble-components-793b5a45-5de7-42b6-8659-bea657c748b9.json new file mode 100644 index 0000000000..860327a360 --- /dev/null +++ b/change/@ni-nimble-components-793b5a45-5de7-42b6-8659-bea657c748b9.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "IxD spec for editable table", + "packageName": "@ni/nimble-components", + "email": "1458528+fredvisser@users.noreply.github.com", + "dependentChangeType": "none" +} diff --git a/package-lock.json b/package-lock.json index e9216c6146..9211aa3a24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7307,23 +7307,6 @@ } } }, - "node_modules/@storybook/addon-mdx-gfm": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@storybook/addon-mdx-gfm/-/addon-mdx-gfm-8.4.6.tgz", - "integrity": "sha512-wagsSBUN6pwcSZSWxp/aOhE16ZKI8ZW4XeRT6QivySmkJaLcbva+HNvQOijdXIM28W8PprKjqtyVa8nu4YQxsw==", - "dev": true, - "dependencies": { - "remark-gfm": "^4.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^8.4.6" - } - }, "node_modules/@storybook/addon-measure": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.4.6.tgz", @@ -28144,7 +28127,6 @@ "@storybook/addon-essentials": "^8.4.3", "@storybook/addon-interactions": "^8.4.3", "@storybook/addon-links": "^8.4.3", - "@storybook/addon-mdx-gfm": "^8.4.3", "@storybook/addon-webpack5-compiler-swc": "^1.0.5", "@storybook/cli": "^8.4.3", "@storybook/csf": "^0.1.11", diff --git a/packages/nimble-components/src/table/specs/editable-table-ixd.md b/packages/nimble-components/src/table/specs/editable-table-ixd.md new file mode 100644 index 0000000000..976c232fe5 --- /dev/null +++ b/packages/nimble-components/src/table/specs/editable-table-ixd.md @@ -0,0 +1,129 @@ +# Nimble Editable Table (IxD) + +## Overview + +Nimble table support for interactive cell editing. + +### Background + +- [Figma worksheet](https://www.figma.com/design/r2yGNQNVFdE7cBO9CyHmQx/Nimble---IxD?node-id=1221-36463) +- [Input table columns (select, text field, etc) #1190](https://github.com/ni/nimble/issues/1190) + +## Usage + +**When to use:** + +- When the table is used to display and edit structured engineering data such as measurements, specifications, or configurations. +- When inline editing can improve the user experience by reducing the need for separate forms or dialogs. +- When data accuracy can be ensured through validation and error handling mechanisms. + +**When not to use:** + +- When the table is used for displaying static or read-only data. +- When the user requires guidance entering or modifying data and would be best served by a form or other UX pattern. +- When the application requires spreadsheet features like pasting or filling data into a range of cells + +## Requirements + +- Inline editing support for table cells +- Validation and error handling for input data +- Initiate cell editing via keyboard or mouse +- Support for strings and numbers (1st priority) +- Support for enumerated values (2nd priority) +- All cells within a particular column share the same edit view and configuration +- Compatibility with assistive technologies (e.g., screen readers) + +### Out of scope + +- Support for complex data types (e.g., dates, times, custom objects) +- Advanced spreadsheet functionalities (e.g., formulas, cell references) +- Real-time collaboration or multi-user editing +- Custom cell styling or formatting + +## Anatomy + +![Editable table anatomy](./spec-images/editable-cell-anatomy.png) + +| Element | Description | +| --------------------- | ------------------------------------------------------------------------ | +| Static cell value | Non-editable display of cell value | +| Action button | Button to open cell context menu. Appears on row hover or keyboard focus | +| Editable cell control | Text or numeric control that supports entering or editing cell data | + +## Behavior + +### States + +![Editable cell states for text](./spec-images/text-editable-cell-states.png) +![Editable cell states for numeric](./spec-images/numeric-editable-cell-states.png) +![Editable cell states for select](./spec-images/select-editable-cell-states.png) +![Editable cell states for checkbox](./spec-images/checkbox-editable-cell-states.png) + +#### Error State + +![Editable cell error states](./spec-images/editable-cell-error-data.png) + +### ARIA Considerations + +- Maintain compatibility with existing keyboard navigation behavior +- Consider resolving [existing ARIA gaps](https://github.com/ni/nimble/issues/2285) +- Consider adding `aria-readonly` for non-editable cells + +### Mouse Interactions + +![Editable cell mouse navigation](./spec-images/editable-cell-mouse-navigation.png) + +- Editable table cells show the same row hover state as non-editable cells on mouse hover. + - I.e. When hovering over a row, the action menu button appears. Depending on the table's selection mode, the row may be highlighted. +- The cursor will change from an *arrow cursor* when hovering over editable cells + - To a *text cursor* when hovering over cells with editable text or numeric controls + - To a *hand cursor* when hovering over select or boolean controls +- Single clicking editable cell shows the *edit focus* state. +- Clicking away from a focused editable cell sets the value and removes *edit focus*. + +### Keyboard Interactions + +![Editable cell key navigation](./spec-images/editable-cell-key-navigation.png) + +- When a cell has keyboard focus (*cell focus* state), pressing `TAB` or `ENTER` transforms the cell into an input control in the *edit focus* state. +- When a cell has *edit focus*, pressing `TAB` moves the focus to the action button, or to the next available focus target. + +![Editable cell enter key](./spec-images/editable-cell-enter-key.png) + +- When a cell has *edit focus*, pressing `ENTER` sets the value and transforms it into the *cell focus* state. + +![Editable cell escape key](./spec-images/editable-cell-escape-key.png) + +- When a cell has *edit focus*, pressing `ESCAPE` reverts any value change and transforms it into the *cell focus* state. + +![Editable cell data error](./spec-images/editable-cell-error-data.png) + +- User edits are validated on entry. If the entered data is invalid, + - the error is shown in the *edit focus + error* state. + - pressing `TAB`, `ENTER` or `ESC` resets the cell to the most recent valid value. +- Other invalid cell data is shown in the *cell error* state. + +### Out of scope + +The client application is responsible for defining and implementing workflows for adding, deleting, and moving rows, as well as saving data. + +Example row editing UX: + +![Table row editing](./spec-images/table-row-editing.png) + +Example save workflow: + +![Table save workflow](./spec-images/table-save-workflow.png) + +#### Touch-Screen Devices + +- The edit workflow should support touch screen devices. + +## Open Issues + +See content marked "**QUESTION**" or "**NOTE**". + +## References + +- [AG Grid](https://www.ag-grid.com/example/) +- [Patternfly](https://www.patternfly.org/components/table/react-deprecated/#editable-rows) diff --git a/packages/nimble-components/src/table/specs/spec-images/checkbox-editable-cell-states.png b/packages/nimble-components/src/table/specs/spec-images/checkbox-editable-cell-states.png new file mode 100644 index 0000000000..6b256500e9 Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/checkbox-editable-cell-states.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-anatomy.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-anatomy.png new file mode 100644 index 0000000000..38ff670a2e Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-anatomy.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-enter-key.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-enter-key.png new file mode 100644 index 0000000000..107da79b6f Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-enter-key.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-error-data.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-error-data.png new file mode 100644 index 0000000000..087619431a Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-error-data.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-escape-key.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-escape-key.png new file mode 100644 index 0000000000..29aea56d94 Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-escape-key.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-key-navigation.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-key-navigation.png new file mode 100644 index 0000000000..0f95b45508 Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-key-navigation.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-mouse-navigation.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-mouse-navigation.png new file mode 100644 index 0000000000..dba75cd088 Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-mouse-navigation.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/editable-cell-states.png b/packages/nimble-components/src/table/specs/spec-images/editable-cell-states.png new file mode 100644 index 0000000000..641cfea444 Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/editable-cell-states.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/numeric-editable-cell-states.png b/packages/nimble-components/src/table/specs/spec-images/numeric-editable-cell-states.png new file mode 100644 index 0000000000..233337ce9a Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/numeric-editable-cell-states.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/select-editable-cell-states.png b/packages/nimble-components/src/table/specs/spec-images/select-editable-cell-states.png new file mode 100644 index 0000000000..91be5e019b Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/select-editable-cell-states.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/table-row-editing.png b/packages/nimble-components/src/table/specs/spec-images/table-row-editing.png new file mode 100644 index 0000000000..ae8b4205a9 Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/table-row-editing.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/table-save-workflow.png b/packages/nimble-components/src/table/specs/spec-images/table-save-workflow.png new file mode 100644 index 0000000000..1d4ce9c76c Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/table-save-workflow.png differ diff --git a/packages/nimble-components/src/table/specs/spec-images/text-editable-cell-states.png b/packages/nimble-components/src/table/specs/spec-images/text-editable-cell-states.png new file mode 100644 index 0000000000..faed74168e Binary files /dev/null and b/packages/nimble-components/src/table/specs/spec-images/text-editable-cell-states.png differ diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index 4cb5852aa9..4c7d545800 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -1,4 +1,3 @@ -import { dirname, join } from 'path'; import remarkGfm from 'remark-gfm'; import CircularDependencyPlugin from 'circular-dependency-plugin'; import TerserPlugin from 'terser-webpack-plugin'; @@ -12,13 +11,13 @@ export const stories = [ ]; export const addons = [ { - name: getAbsolutePath('@storybook/addon-essentials'), + name: '@storybook/addon-essentials', options: { outline: false, docs: false } }, { - name: getAbsolutePath('@storybook/addon-docs'), + name: '@storybook/addon-docs', options: { mdxPluginOptions: { mdxCompileOptions: { @@ -27,12 +26,11 @@ export const addons = [ } } }, - getAbsolutePath('@storybook/addon-a11y'), - getAbsolutePath('@storybook/addon-interactions'), - getAbsolutePath('@chromatic-com/storybook'), - getAbsolutePath('@storybook/addon-webpack5-compiler-swc'), - getAbsolutePath('storybook-addon-pseudo-states'), - getAbsolutePath('@storybook/addon-mdx-gfm') + '@storybook/addon-a11y', + '@storybook/addon-interactions', + '@chromatic-com/storybook', + '@storybook/addon-webpack5-compiler-swc', + 'storybook-addon-pseudo-states' ]; export function webpackFinal(config) { @@ -65,9 +63,5 @@ export function webpackFinal(config) { } export const staticDirs = ['public']; export const framework = { - name: getAbsolutePath('@storybook/html-webpack5') + name: '@storybook/html-webpack5' }; - -function getAbsolutePath(value) { - return dirname(require.resolve(join(value, 'package.json'))); -} diff --git a/packages/storybook/package.json b/packages/storybook/package.json index f791668487..9651a049b4 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -33,7 +33,6 @@ "@storybook/addon-essentials": "^8.4.3", "@storybook/addon-interactions": "^8.4.3", "@storybook/addon-links": "^8.4.3", - "@storybook/addon-mdx-gfm": "^8.4.3", "@storybook/addon-webpack5-compiler-swc": "^1.0.5", "@storybook/cli": "^8.4.3", "@storybook/csf": "^0.1.11",