diff --git a/website/app/styles/app.scss b/website/app/styles/app.scss index 5aa25adb4a1..28b06805471 100644 --- a/website/app/styles/app.scss +++ b/website/app/styles/app.scss @@ -30,6 +30,7 @@ @use "pages/foundations/icon" as foundations-icon; @use "pages/foundations/typography" as foundations-typography; @use "pages/components/accordion" as components-accordion; +@use "pages/components/advanced-table" as components-advanced-table; @use "pages/components/alert" as components-alert; @use "pages/components/app-footer" as components-app-footer; @use "pages/components/application-state" as components-application-state; diff --git a/website/app/styles/pages/components/advanced-table.scss b/website/app/styles/pages/components/advanced-table.scss new file mode 100644 index 00000000000..c0b93eb1a9f --- /dev/null +++ b/website/app/styles/pages/components/advanced-table.scss @@ -0,0 +1,23 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +// COMPONENTS > TABLE > ADVANCEDTABLE + +#show-content-components-table-advanced-table { + .doc-advanced-table-scrollable-wrapper { + overflow-x: auto; + } + + .doc-advanced-table-vertical-scrollable-wrapper { + max-height: 500px; + overflow-y: auto; + } + + .doc-advanced-table-multiselect-with-pagination-demo { + .hds-advanced-table + .hds-pagination { + margin-top: 16px; + } + } +} diff --git a/website/app/styles/pages/components/table.scss b/website/app/styles/pages/components/table.scss index 4c9b8a9bc68..5ab1b91c26d 100644 --- a/website/app/styles/pages/components/table.scss +++ b/website/app/styles/pages/components/table.scss @@ -3,9 +3,9 @@ * SPDX-License-Identifier: MPL-2.0 */ -// COMPONENTS > TABLE +// COMPONENTS > TABLE > TABLE -#show-content-components-table { +#show-content-components-table-table { .doc-table-valign-demo { display: flex; gap: 5px; diff --git a/website/docs/components/copy/snippet/partials/guidelines/guidelines.md b/website/docs/components/copy/snippet/partials/guidelines/guidelines.md index aa03424a76d..e4f32eb3d1d 100644 --- a/website/docs/components/copy/snippet/partials/guidelines/guidelines.md +++ b/website/docs/components/copy/snippet/partials/guidelines/guidelines.md @@ -28,7 +28,7 @@ The Copy Snippet comes in two colors: `primary` and `secondary` !!! Do -When multiple Copy Snippets are needed in a single page, such as in a [Table](/components/table), consider using the `secondary` color to reduce the prominence of each Copy Snippet. +When multiple Copy Snippets are needed in a single page, such as in a [Table](/components/table/table), consider using the `secondary` color to reduce the prominence of each Copy Snippet. ![Example of the Copy Snippet component in a table](/assets/components/copy/copy-snippet-table-do.png) diff --git a/website/docs/components/dropdown/partials/code/how-to-use.md b/website/docs/components/dropdown/partials/code/how-to-use.md index 3546525dc4b..a000c6bf200 100644 --- a/website/docs/components/dropdown/partials/code/how-to-use.md +++ b/website/docs/components/dropdown/partials/code/how-to-use.md @@ -51,7 +51,7 @@ Alternatively, pass `secondary` to `@color` to display a secondary button with a #### ToggleIcon as overflow menu -Overflow menus are often found in the last column of a [Tables](/components/table). This is the only use case where it is acceptable to use +Overflow menus are often found in the last column of a [Table](/components/table/table). This is the only use case where it is acceptable to use `@hasChevron={{false}}`. `@text` is still required, because it supplies the `aria-label` for ToggleIcon. ```handlebars diff --git a/website/docs/components/dropdown/partials/guidelines/guidelines.md b/website/docs/components/dropdown/partials/guidelines/guidelines.md index d16bc76162f..22838ff89ff 100644 --- a/website/docs/components/dropdown/partials/guidelines/guidelines.md +++ b/website/docs/components/dropdown/partials/guidelines/guidelines.md @@ -76,7 +76,7 @@ ToggleIcons come in two sizes: **small** and **medium**. !!! Info -While we provide a small size variant, we recommend only using this for the Overflow menu within [Tables](/components/table) because the icons and images start to become unrecognizable in smaller sizes. +While we provide a small size variant, we recommend only using this for the Overflow menu within [Tables](/components/table/table) because the icons and images start to become unrecognizable in smaller sizes. !!! @@ -104,7 +104,7 @@ ToggleButtons require a visible chevron to indicate interactivity and provide di ![Example of open and closed dropdowns](/assets/components/dropdown/dropdown-button-chevrons.png =286x*) -We strongly recommend providing visible chevrons on most instances of ToggleIcons to indicate interactivity. That said, it’s common to see ToggleIcons that use the `more-horizontal` icon without chevrons. Their placement, usually in the last column of a [Table](/components/table), is typically indicative of this type of interaction. +We strongly recommend providing visible chevrons on most instances of ToggleIcons to indicate interactivity. That said, it’s common to see ToggleIcons that use the `more-horizontal` icon without chevrons. Their placement, usually in the last column of a [Table](/components/table/table), is typically indicative of this type of interaction. ![Example of open and closed dropdowns](/assets/components/dropdown/dropdown-icon-chevrons.png =750x*) diff --git a/website/docs/components/pagination/index.md b/website/docs/components/pagination/index.md index 64ac835b297..f287a3d0348 100644 --- a/website/docs/components/pagination/index.md +++ b/website/docs/components/pagination/index.md @@ -10,7 +10,8 @@ links: github: >- https://github.com/hashicorp/design-system/tree/main/packages/components/src/components/hds/pagination related: - - components/table + - components/table/table + - components/table/advanced-table - patterns/filter-patterns - patterns/table-multi-select previewImage: assets/illustrations/components/pagination.jpg diff --git a/website/docs/components/pagination/partials/code/how-to-use.md b/website/docs/components/pagination/partials/code/how-to-use.md index 37d67994373..585c84242e9 100644 --- a/website/docs/components/pagination/partials/code/how-to-use.md +++ b/website/docs/components/pagination/partials/code/how-to-use.md @@ -148,7 +148,7 @@ The reason for using a consumer-side function to determine the `query` argument Even if the Pagination is based on routing, the `onPageChange/onPageSizeChange` callbacks are still available and can be used to respond to the users’ actions (eg. for logging, tracking, etc.). -Below you can find an example of an integration between the sortable [`Table`](/components/table) component and the `Pagination::Numbered` component that uses query parameters in the URL to preserve the UI state: +Below you can find an example of an integration between the sortable [`Table`](/components/table/table) component and the `Pagination::Numbered` component that uses query parameters in the URL to preserve the UI state: ```handlebars
@@ -271,7 +271,7 @@ The reason for using a consumer-side function to determine the `query` argument Even if the Pagination is based on routing, the `onPageChange/onPageSizeChange` callbacks are still available and can be used to respond to the users' actions (eg. for logging, tracking, etc.). -Below you can find an example of an integration between the [`Table`](/components/table) component and the `Pagination::Compact` component that uses query parameters in the URL to preserve the UI state: +Below you can find an example of an integration between the [`Table`](/components/table/table) component and the `Pagination::Compact` component that uses query parameters in the URL to preserve the UI state: ```handlebars
diff --git a/website/docs/components/table/advanced-table/index.js b/website/docs/components/table/advanced-table/index.js new file mode 100644 index 00000000000..094dac7dc37 --- /dev/null +++ b/website/docs/components/table/advanced-table/index.js @@ -0,0 +1,495 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +export default class Index extends Component { + @tracked demoCurrentPage = 1; + @tracked demoCurrentPageSize = 2; + @tracked demoSourceData = [ + { + id: '1', + type: 'folk', + artist: 'Nick Drake', + album: 'Pink Moon', + year: '1972', + quote: + "The song is very special. It's an old song by a guy named Nick Drake. It's called 'Pink Moon' and is actually a very good introduction to Nick Drake if you're not familiar with him. It's very transporting. And to us seemed very fitting for a beautiful drive in the country on a very special night.", + 'vinyl-cost': '29.27', + icon: 'boundary-color', + 'badge-type': 'filled', + 'badge-color': 'neutral', + }, + { + id: '2', + type: 'folk', + artist: 'The Beatles', + album: 'Abbey Road', + year: '1969', + quote: + "it was the Beatles' last love letter to the world...lush, rich, smooth, epic, emotional and utterly gorgeous", + 'vinyl-cost': '25.99', + icon: 'consul-color', + 'badge-type': 'outlined', + 'badge-color': 'neutral', + }, + { + id: '3', + type: 'folk', + artist: 'Melanie', + album: 'Candles in the Rain', + year: '1971', + quote: + 'Candles in the Rain matched material and interpretation with greater skill than she had in the past, and it ranks with her finest work', + 'vinyl-cost': '46.49', + icon: 'terraform-color', + 'badge-type': 'filled', + 'badge-color': 'highlight', + }, + { + id: '4', + type: 'folk', + artist: 'Bob Dylan', + album: 'Bringing It All Back Home', + year: '1965', + quote: + 'By fusing the Chuck Berry beat of the Rolling Stones and the Beatles with the leftist, folk tradition of the folk revival, Dylan really had brought it back home, creating a new kind of rock & roll that made every type of artistic tradition available to rock.', + 'vinyl-cost': '29.00', + icon: 'nomad-color', + 'badge-type': 'outlined', + 'badge-color': 'success', + }, + { + id: '5', + type: 'folk', + artist: 'James Taylor', + album: 'Sweet Baby James', + year: '1970', + quote: + '(It) struck a chord with music fans, especially because of its attractive mixture of folk, country, gospel, and blues elements, all of them carefully understated and distanced.', + 'vinyl-cost': '16.00', + icon: 'waypoint-color', + 'badge-type': 'filled', + 'badge-color': 'warning', + }, + { + id: '6', + type: 'folk', + artist: 'Simon and Garfunkel', + album: 'Bridge Over Troubled Waters', + year: '1970', + quote: + 'Perhaps the most delicately textured album to close out the 1960s from any major rock act.', + 'vinyl-cost': '20.49', + icon: 'vagrant-color', + 'badge-type': 'outlined', + 'badge-color': 'critical', + }, + ]; + @tracked demoSortBySelectedData = [...this.demoSourceData].map( + (row, index) => ({ + ...row, + isSelected: index % 2 === 0, + }) + ); + @tracked demoSortBySelectedControlledSortBy = 'isSelected'; + @tracked demoSortBySelectedControlledSortOrder = 'asc'; + + get model() { + return { myDemoData: this.demoSourceData }; + } + + get myDataItems() { + return [ + { + product: 'Terraform', + brandColor: 'purple', + usesHelios: true, + }, + { + product: 'Nomad', + brandColor: 'green', + usesHelios: true, + }, + { + product: 'Vault', + brandColor: 'yellow', + usesHelios: true, + }, + ]; + } + + get demoDataWithLargeNumberOfColumns() { + return [ + { + first_name: 'Judith', + last_name: 'Maxene', + age: '43', + email: 'j.maxene@randatmail.com', + phone: '697-0732-81', + education: 'Upper secondary school', + occupation: 'Astronomer', + bio: 'Analyst. Gamer. Friendly explorer. Incurable TV lover. Social media scholar. Amateur web geek. Proud zombie guru.', + }, + { + first_name: 'Elmira', + last_name: 'Aishah', + age: '28', + email: 'e.aishah@randatmail.com', + phone: '155-6076-27', + education: 'Master in Physics', + occupation: 'Actress', + bio: 'Total coffee guru. Food enthusiast. Social media expert. TV aficionada. Extreme music advocate. Zombie fan.', + }, + { + first_name: 'Chinwendu', + last_name: 'Henderson', + age: '62', + email: 'c.henderson@randatmail.com', + phone: '155-0155-09', + education: 'Bachelor in Modern History', + occupation: 'Historian', + bio: 'Creator. Internet maven. Coffee practitioner. Troublemaker. Alcohol specialist.', + }, + ]; + } + + get demoDataWithNestedRows() { + return [ + { + id: 1, + name: 'Policy set 1', + status: 'PASS', + description: '', + children: [ + { + id: 11, + name: 'test-advisory-pass.sentinel', + status: 'PASS', + description: 'Sample description for this thing.', + }, + { + id: 12, + name: 'test-hard-mandatory-pass.sentinel', + status: 'PASS', + description: 'Sample description for this thing.', + }, + ], + }, + { + id: 2, + name: 'Policy set 2', + status: 'FAIL', + description: '', + children: [ + { + id: 21, + name: 'test-advisory-pass.sentinel', + status: 'PASS', + description: 'Sample description for this thing.', + }, + { + id: 22, + name: 'test-hard-mandatory-pass.sentinel', + status: 'FAIL', + description: 'Sample description for this thing.', + }, + ], + }, + ]; + } + + get demoDataWithLargeNumberOfRows() { + return [ + { + id: 1, + name: 'Burnaby Kuscha', + email: '1_bkuscha0@tiny.cc', + role: 'Owner', + }, + { + id: 2, + name: 'Barton Penley', + email: '2_bpenley1@miibeian.gov.cn', + role: 'Admin', + }, + { + id: 3, + name: 'Norina Emanulsson', + email: '3_nemanulsson2@walmart.com', + role: 'Contributor', + }, + { + id: 4, + name: 'Orbadiah Smales', + email: '4_osmales3@amazon.co.jp', + role: 'Contributor', + }, + { + id: 5, + name: 'Dido Titchener', + email: '5_dtitchener4@blogs.com', + role: 'Contributor', + }, + { + id: 6, + name: 'Trish Horsburgh', + email: '6_thorsburgh5@samsung.com', + role: 'Contributor', + }, + { + id: 7, + name: 'Orion Laverack', + email: '7_olaverack6@techcrunch.com', + role: 'Contributor', + }, + { + id: 8, + name: 'Delly Moulsdale', + email: '8_dmoulsdale7@sciencedirect.com', + role: 'Contributor', + }, + { + id: 9, + name: 'Gil Carlyle', + email: '9_gcarlyle8@canalblog.com', + role: 'Contributor', + }, + { + id: 10, + name: 'Marinna Corbin', + email: '10_mcorbin9@google.ca', + role: 'Contributor', + }, + { + id: 11, + name: 'Yardley Entwhistle', + email: '11_yentwhistlea@tumblr.com', + role: 'Contributor', + }, + { + id: 12, + name: 'Brinn Clack', + email: '12_bclackb@blogger.com', + role: 'Contributor', + }, + { + id: 13, + name: 'Charleen Millen', + email: '13_cmillenc@mtv.com', + role: 'Contributor', + }, + { + id: 14, + name: 'Kalie Piers', + email: '14_kpiersd@businessweek.com', + role: 'Contributor', + }, + { + id: 15, + name: 'Laure Boxer', + email: '15_lboxere@elegantthemes.com', + role: 'Contributor', + }, + { + id: 16, + name: 'Libby Bonallack', + email: '16_lbonallackf@disqus.com', + role: 'Contributor', + }, + { + id: 17, + name: 'Zebedee Gofton', + email: '17_zgoftong@bbc.co.uk', + role: 'Contributor', + }, + { + id: 18, + name: 'Sari Eckford', + email: '18_seckfordh@cloudflare.com', + role: 'Contributor', + }, + { + id: 19, + name: 'Carlos Byrth', + email: '19_cbyrthi@prlog.org', + role: 'Contributor', + }, + { + id: 20, + name: 'Avery Allmark', + email: '20_aallmarkj@webnode.com', + role: 'Contributor', + }, + { + id: 21, + name: 'Ninnette McSpirron', + email: '21_nmcspirronk@amazon.com', + role: 'Contributor', + }, + { + id: 22, + name: 'Sharlene Ewestace', + email: '22_sewestacel@twitpic.com', + role: 'Contributor', + }, + { + id: 23, + name: 'Jessamine Kembry', + email: '23_jkembrym@hatena.ne.jp', + role: 'Contributor', + }, + { + id: 24, + name: 'Homerus Dixcee', + email: '24_hdixceen@deviantart.com', + role: 'Contributor', + }, + { + id: 25, + name: 'Clevie Clear', + email: '25_cclearo@tmall.com', + role: 'Contributor', + }, + { + id: 26, + name: 'Mohammed Hubatsch', + email: '26_mhubatschp@salon.com', + role: 'Contributor', + }, + { + id: 27, + name: 'Gigi Hovard', + email: '27_ghovardq@cbslocal.com', + role: 'Contributor', + }, + { + id: 28, + name: 'Dorey Tinker', + email: '28_dtinkerr@google.co.uk', + role: 'Contributor', + }, + { + id: 29, + name: 'Arel Mullarkey', + email: '29_amullarkeys@blogs.com', + role: 'Contributor', + }, + { + id: 30, + name: 'Veronike Ventura', + email: '30_vventurat@google.fr', + role: 'Contributor', + }, + { + id: 31, + name: 'Gerti Dranfield', + email: '31_gdranfieldu@vistaprint.com', + role: 'Contributor', + }, + ]; + } + + get demoPaginatedData() { + const start = (this.demoCurrentPage - 1) * this.demoCurrentPageSize; + const end = this.demoCurrentPage * this.demoCurrentPageSize; + return this.demoSourceData.slice(start, end); + } + + get demoTotalItems() { + return this.demoSourceData.length; + } + + get demoSortBySelectedControlledSortedData() { + const clonedData = Array.from(this.demoSortBySelectedData); + + clonedData.sort((s1, s2) => { + const v1 = s1[this.demoSortBySelectedControlledSortBy]; + const v2 = s2[this.demoSortBySelectedControlledSortBy]; + + if (v1 < v2) { + return this.demoSortBySelectedControlledSortOrder === 'asc' ? -1 : 1; + } + + if (v1 > v2) { + return this.demoSortBySelectedControlledSortOrder === 'asc' ? 1 : -1; + } + + return 0; + }); + + return clonedData; + } + + @action + demoOnPageChange(page, pageSize) { + this.demoCurrentPage = page; + this.demoCurrentPageSize = pageSize; + } + + @action + demoOnPageSizeChange(pageSize) { + // we agreed to reset the pagination to the first element (any alternative would result in an unpredictable UX) + this.demoCurrentPage = 1; + this.demoCurrentPageSize = pageSize; + } + + @action + demoOnSelectionChange() { + console.log('demoOnSelectionChange called with arguments:', ...arguments); + } + + @action + demoOnSelectionChangeWithPagination({ selectableRowsStates }) { + selectableRowsStates.forEach((row) => { + const recordToUpdate = this.demoSourceData.find( + (modelRow) => modelRow.id === row.selectionKey + ); + if (recordToUpdate) { + recordToUpdate.isSelected = row.isSelected; + } + }); + } + + @action + demoOnSelectionChangeSortBySelected({ selectableRowsStates }) { + selectableRowsStates.forEach((row) => { + const recordToUpdate = this.demoSortBySelectedData.find( + (modelRow) => modelRow.id === row.selectionKey + ); + if (recordToUpdate) { + recordToUpdate.isSelected = row.isSelected; + } + }); + } + + @action + demoSortBySelectedControlledOnSelectionChange({ + selectionKey, + selectionCheckboxElement, + }) { + if (selectionKey === 'all') { + this.demoSortBySelectedData.forEach((modelRow) => { + modelRow.isSelected = selectionCheckboxElement.checked; + }); + } else { + const recordToUpdate = this.demoSortBySelectedData.find( + (modelRow) => modelRow.id === selectionKey + ); + + if (recordToUpdate) { + recordToUpdate.isSelected = selectionCheckboxElement.checked; + } + } + } + + @action + demoSortBySelectedControlledOnSort(sortBy, sortOrder) { + this.demoSortBySelectedControlledSortBy = sortBy; + this.demoSortBySelectedControlledSortOrder = sortOrder; + } +} diff --git a/website/docs/components/table/advanced-table/index.md b/website/docs/components/table/advanced-table/index.md new file mode 100644 index 00000000000..df8719466c0 --- /dev/null +++ b/website/docs/components/table/advanced-table/index.md @@ -0,0 +1,44 @@ +--- +title: Advanced Table +description: 'Used to display complex, structured tabular data with advanced features.' +caption: 'Used to display complex, structured tabular data with advanced features.' +links: + figma: >- + https://www.figma.com/design/iweq3r2Pi8xiJfD9e6lOhF/HDS-Components-v2.0?node-id=67216-35163&t=w8xQlWxzH7bwXLe2-1 + github: >- + https://github.com/hashicorp/design-system/tree/main/packages/components/src/components/hds/advanced-table +related: + - components/table/table + - components/pagination + - patterns/filter-patterns + - patterns/table-multi-select +previewImage: assets/illustrations/components/advanced-table.jpg +navigation: + keywords: + - data table + - data grid + - datagrid + - grid + - list +status: + added: 4.16.0 +--- + +
+ @include "partials/guidelines/overview.md" + @include "partials/guidelines/guidelines.md" +
+ +
+ @include "partials/code/how-to-use.md" + @include "partials/code/component-api.md" +
+ +
+ @include "partials/specifications/anatomy.md" + @include "partials/specifications/states.md" +
+ +
+ @include "partials/accessibility/accessibility.md" +
diff --git a/website/docs/components/table/advanced-table/partials/accessibility/accessibility.md b/website/docs/components/table/advanced-table/partials/accessibility/accessibility.md new file mode 100644 index 00000000000..33ff6b03832 --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/accessibility/accessibility.md @@ -0,0 +1,29 @@ +## Conformance rating + +Conformant + +When used as recommended, there should not be any WCAG conformance issues with this component. + +## Best practices + +### Interactive rows + +The table row element cannot receive interactions, meaning actions cannot be attached directly to a table row. If you need an interactive element, place it within a table cell element in that row (i.e., `
Some link
`). + +### Focus in Advanced Tables + +Unlike the Table component, each cell receives focus in the Advanced Table to support users navigating through the table efficiently with a keyboard. For any other interactions, you must use interactive elements (buttons, links, etc.) within the cells. + +### Row selection + +You should clearly communicate to the user how many rows are selected and how many rows there are total outside of the Advanced Table. For additional considerations, read the [Multi-select usability and accessibility considerations](/components/table/advanced-table?tab=code#usability-and-accessibility-considerations). + +## Applicable WCAG Success Criteria + +This section is for reference only. This component intends to conform to the following WCAG Success Criteria: + + + +--- + + diff --git a/website/docs/components/table/advanced-table/partials/code/component-api.md b/website/docs/components/table/advanced-table/partials/code/component-api.md new file mode 100644 index 00000000000..b01a0fc134d --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/code/component-api.md @@ -0,0 +1,215 @@ +## Component API + +The Advanced Table component itself is where most of the options will be applied. However, the APIs for the child components are also documented here in case a custom implementation is desired. + +### AdvancedTable + + + + This is a named block where the content for the Advanced Table body is rendered. + + + The value of the index associated with the `@each` loop. Returns a number when there are no nested rows. Returns a string in the form `${parentIndex}.${childIndex}` when there are nested rows. + + + The value of the internal `sortBy` tracked variable. + + + The value of the internal `sortOrder` tracked variable. + + + Returns the value of the internal `isExpanded` tracked variable from the row if it has nested rows; otherwise returns `undefined`. + + + + + The data model to be used by the Advanced Table. The model can have any shape, but for nested rows there are two expected keys. + + + If there are nested rows, the Advanced Table will use the `children` key in the model to render the child content. The key can be changed by setting `childrenKey` argument on the `Hds::AdvancedTable`. + + + If there are nested rows, the default state of the toggle can be set by adding `isExpanded` to the row in the model. + + + + + Array `hash` that defines each column with key-value properties that describe each column. Options: + + + The column’s label. + + + The column’s key (one of the keys in the model’s records); required if the column is sortable. + + + If set to `true`, indicates that a column should be sortable.

+ **Important**: Advanced Table does **not** support setting `isSelectable` to true when there are nested rows. +
+ + Determines the horizontal content alignment (sometimes referred to as text alignment) for the column header. + + + If set, determines the column’s width. + + + If set to `true`, it visually hides the column’s text content (it will still be available to screen readers for accessibility). Only available for non-sortable columns. + + + Callback function to provide support for custom sorting logic. It should implement a typical bubble-sorting algorithm using two elements and comparing them. For more details, see the example of custom sorting in the [How To Use section](#sortable-advanced-table). + + + Text string which will appear in [`Tooltip`](/components/tooltip). May contain basic HTML tags for formatting text such as `strong` and `em` tags. Not intended for multi-paragraph text or other more complex content. May not contain interactive content such as links or buttons. The `placement` and `offset` are automatically set and can’t be overwritten. + +
+
+ + If defined, the value should be set to the key of the column that should be pre-sorted. + + + Use in conjunction with `sortBy`. If defined, indicates which direction the column should be pre-sorted in. If not defined, `asc` is applied by default. + + + If set to `true`, creates a “multi-select” table which renders checkboxes in the table header and on the table rows enabling bulk interaction. Use in conjunction with `onSelectionChange` on the `Table` and `selectionKey` on each `Table::Tr`.

+ **Important**: Advanced Table does **not** support having `isSelectable` true when there are nested rows. +
+ + Use in conjunction with `isSelectable` to pass a callback function to know the selection state. Must be used in conjunction with setting a `selectionKey` on each `Table::Tr`. +

+ When called, this function receives an object as argument, with different keys corresponding to different information: +
    +
  • `selectionKey`: the value of the `@selectionKey` argument associated with the row selected/deselected by the user or `all` if the “select all” checkbox has been toggled
  • +
  • `selectionCheckboxElement`: the checkbox (DOM element) that has been toggled by the user.
  • +
  • `selectedRowsKeys`: an array containing all the `@selectionKey`s of the selected rows in the table (an empty array is returned if no row is selected).
  • +
  • `selectableRowsStates`: an array of objects corresponding to all the rows displayed in the table when the user changed a selection; each object contains the `@selectionKey` value for the associated row and its `isSelected` boolean state (if the checkbox is checked or not).

    + **Important**: the order of the rows in the array doesn’t necessarily follow the order of the rows in the table/DOM.
  • +
+
+ + Determines if even-numbered rows will have a different background color from odd-numbered rows.

+ **Important**: Advanced Table does **not** support setting `isStriped` to true when there are nested rows. +
+ + Determines if the Advanced Table has a sticky header. + + + If set, determines the density (height) of the body’s rows. + + + Determines the vertical alignment for content in a table. Does not apply to table headers (`th`). See [MDN reference on vertical-align](https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align) for more details. + + + If set, this key determines which `@model` item property is used to sort items by selection state. If this argument is not provided, the option to sort by selection state will not be available. + + + Adds a (non-visible) caption for users with assistive technology. If set on a sortable table, the provided caption is paired with the automatically generated sorted message text. + + + Option to [specify a custom key](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/each?anchor=each#:~:text=%3C/ul%3E-,Specifying%20Keys,-In%20order%20to) to the `each` iterator. If `identityKey="none"`, this is interpreted as an `undefined` value for the `@identity` key option. + + + Customizable text added to `caption` element when a sort is performed. + + + If set, this key determines which `@model` item property is used to render nested rows. If this argument is not provided, the default will be used. + + + Callback function that is invoked when one of the sortable headers is clicked (or has a keyboard interaction performed). The function receives the values of `sortBy` and `sortOrder` as arguments. + + + This component supports use of [`...attributes`](https://guides.emberjs.com/release/in-depth-topics/patterns-for-components/#toc_attribute-ordering). + +
+ +### AdvancedTable::Tr + +!!! Info + +Note: This component is not eligible to receive interactions (e.g., it cannot have an `onClick` event handler attached directly to it). Instead, an interactive element should be placed _inside_ of the `AdvancedTable::Th`, `AdvancedTable::Td` components. + +!!! + +This component can contain `Hds::AdvancedTable::Th` or `Hds::AdvancedTable::Td` components. + + + + Elements passed as children are yielded as inner content of a `
` HTML element. + + + Sets the initial selection state for the row (used in conjunction with setting `isSelectable` on the Advanced Table). + + + Required value to associate an unique identifier to each table row (used in conjunction with setting `isSelectable` on the Advanced Table and returned in the `onSelectionChange` callback arguments). It’s required if `isSelectable={{true}}`. + + + Descriptive `aria-label` attribute applied to the checkbox used to select the row (used in conjunction with setting `isSelectable` on the `AdvancedTable`). The component automatically prepends “Select/Deselect” to the string, depending on the selection status. It’s required if `isSelectable={{true}}`. + + + This component supports use of [`...attributes`](https://guides.emberjs.com/release/in-depth-topics/patterns-for-components/#toc_attribute-ordering). + + + +### AdvancedTable::Th + +!!! Info + +Note: This component is not eligible to receive interactions (e.g., it cannot have an `onClick` event handler attached directly to it). Instead, an interactive element should be placed _inside_ of the `AdvancedTable::Th` component. + +!!! + +If the `Th` component is passed as the first cell of a body row, `role="rowheader"` is automatically applied for accessibility purposes. + + + + Determines the horizontal content alignment (sometimes referred to as text alignment) for the column header. + + + If used as the first item in a table body’s row, `scope` should be set to `row` for accessibility purposes. Note: you only need to manually set this if you’re creating a custom table using the child components; if you use the standard invocation for the table, this scope is already provided for you. + + + If set, determines the column’s width. + + + Text string which will appear in the [`Tooltip`](/components/tooltip). May contain basic HTML tags for formatting text such as `strong` and `em` tags. Not intended for multi-paragraph text or other more complex content. May not contain interactive content such as links or buttons. The `placement` and `offset` are automatically set and can’t be overwritten. + + + If set to `true`, it visually hides the column’s text content (it will still be available to screen readers for accessibility). + + + The number of columns the cell spans. Used to apply the correct grid styles and the aria-rowspan attribute for accessibility. + + + The number of rows the cell spans. Used to apply the correct grid styles and the aria-rowspan attribute for accessibility. + + + Elements passed as children are yielded as inner content of a `
` or `
` HTML element. + + + This component supports use of [`...attributes`](https://guides.emberjs.com/release/in-depth-topics/patterns-for-components/#toc_attribute-ordering). + + + +### AdvancedTable::Td +!!! Info + +Note: This component is not eligible to receive interactions (e.g., it cannot have an `onClick` event handler attached directly to it). Instead, an interactive element should be placed _inside_ of the `AdvancedTable::Td` component. + +!!! + + + + Determines the horizontal content alignment (sometimes referred to as text alignment) for the cell (make sure it is also set for the column header). + + + The number of columns the cell spans. Used to apply the correct grid styles and the aria-rowspan attribute for accessibility. + + + The number of rows the cell spans. Used to apply the correct grid styles and the aria-rowspan attribute for accessibility. + + + Elements passed as children are yielded as inner content of a `
` HTML element. + + + This component supports use of [`...attributes`](https://guides.emberjs.com/release/in-depth-topics/patterns-for-components/#toc_attribute-ordering). + + diff --git a/website/docs/components/table/advanced-table/partials/code/how-to-use.md b/website/docs/components/table/advanced-table/partials/code/how-to-use.md new file mode 100644 index 00000000000..3e544813e90 --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/code/how-to-use.md @@ -0,0 +1,717 @@ +## How to use this component + +The Advanced Table is a component meant to display tabular data to overcome limitations with the HTML `` elements and increase the accessibility for complex features, like [nested rows](#nested-rows) and [a sticky header](#vertical-scrolling). + +Instead of using the `
` elements, the Advanced Table uses `
`s with explicitly set roles (for example, instead of `
`, it uses `
`). This allows the Advanced Table to use [CSS Grid](https://developer.mozilla.org/en-US/docs/Web/CSS/grid) for styling. + +### Basic Advanced Table + +To use an Advanced Table, first define the data model in your route or model: + +```javascript +import Route from '@ember/routing/route'; + +export default class ComponentsAdvancedTableRoute extends Route { + async model() { + // example of data retrieved: + //[ + // { + // id: '1', + // attributes: { + // artist: 'Nick Drake', + // album: 'Pink Moon', + // year: '1972' + // }, + // }, + // { + // id: '2', + // attributes: { + // artist: 'The Beatles', + // album: 'Abbey Road', + // year: '1969' + // }, + // }, + // ... + let response = await fetch('/api/demo.json'); + let { data } = await response.json(); + return { myDemoData: data }; + } +} +``` + +For documentation purposes, we’re imitating fetching data from an API and working with that as data model. Depending on your context and needs, you may want to manipulate and adapt the structure of your data to better suit your needs in the template code. + +You can insert your own content into the `:body` block and the component will take care of looping over the `@model` provided: + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + +``` + +!!! Info + +**Important** + +For clarity, there are a couple of important points to note here: + +- provide a `@columns` argument (see [Component API](#component-api) for details about its shape) +- use the `.data` key to access the `@model` record content (it’s yielded as `data`) + +!!! + +### Nested rows +For complex data sets where there is a parent row with several children, you can render them as nested rows. By default, the Advanced Table uses the `children` key on the `@model` argument to render the nested rows. To change the key used, set the `@childrenKey` argument on the Advanced Table. + +To ensure the Advanced Table is accessible, the columns in the nested rows **must** match the columns of the parent rows. Otherwise the relationship between the parent and nested rows will not be clear to users. + +```javascript + // example of data retrieved for the model: + [ + { + id: '1', + name: 'Policy set 1', + status: 'PASS', + children: [ + { + name: 'test-advisory-pass.sentinel', + status: 'PASS', + description: 'Sample description for this thing.' + }, + { + name: 'test-hard-mandatory-pass.sentinel', + status: 'PASS', + description: 'Sample description for this thing.' + } + ] + }, + { + id: '2', + name: 'Policy set 2', + status: 'FAIL', + children: [ + { + name: 'test-advisory-pass.sentinel', + status: 'PASS', + description: 'Sample description for this thing.' + }, + // ... + ] + }, + ] +``` + +!!! Warning + +It is not currently supported to have `@isStriped`, multi-select, or sortable columns with nested rows. If your use case requires any of these features, please [contact the Design Systems Team](/about/support). + +!!! + +Similar to the basic Advanced Table, you can insert your own content into the `:body` block and the component will take care of looping over the `@model` provided for the parent and nested rows. The component adds the expand/collapse button to the `[B].Th` component in each row that has children. + + +```handlebars + + <:body as |B|> + + {{B.data.name}} + + {{#if (eq B.data.status "FAIL")}} + + {{else}} + + {{/if}} + + {{B.data.description}} + + + +``` + + +### Sortable Advanced Table + +!!! Info + +This component takes advantage of the `sort-by` helper provided by [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers). + +!!! + +Add `isSortable=true` to the hash for each column that should be sortable. + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + +``` + +!!! Warning + +At this time, the Advanced Table does not support sortable nested rows. If this is a use case you require, please [contact the Design Systems Team](/about/support). + +!!! + +#### Pre-sorting columns + +To indicate that a specific column should be pre-sorted, add `@sortBy`, where the value is the column’s key. + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + +``` + +##### Pre-sorting direction + +By default, the sort order is set to ascending. To indicate that the column defined in `@sortBy` should be pre-sorted in descending order, pass in `@sortOrder="desc"`. + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + +``` + +#### Custom sort callback + +To implement a custom sort callback on a column: + +1. add a custom function as the value for `sortingFunction` in the column hash. +2. include a custom `onSort` action in your Table invocation to track the sorting order and use it in the custom sorting function. + +This is useful for cases where the key might not be A-Z or 0-9 sortable by default, e.g., status, and you’re otherwise unable to influence the shape of the data in the model. + +_The code has been truncated for clarity._ + +```handlebars{data-execute=false} + + + +``` + +Here’s an example of what a custom sort function could look like. In this example, we are indicating that we want to sort on a status, which takes its order based on the position in the array: + +```javascript +// we use an array to declare the custom sorting order for the "status" column +const customSortingCriteriaArray = [ + 'failing', + 'active', + 'establishing', + 'pending', +]; + +// we track the sorting order, so it can be used in the custom sorting function +@tracked customSortOrderForStatus = 'asc'; + +// we define a "getter" that returns a custom sorting function ("s1" and "s2" are data records) +get customSortingMethodForStatus() { + return (s1, s2) => { + const index1 = customSortingCriteriaArray.indexOf(s1['status']); + const index2 = customSortingCriteriaArray.indexOf(s2['status']); + if (index1 < index2) { + return this.customSortOrderForStatus === 'asc' ? -1 : 1; + } else if (index1 > index2) { + return this.customSortOrderForStatus === 'asc' ? 1 : -1; + } else { + return 0; + } + }; +} + +// we define a callback function that listens to the `onSort` event in the table, +// and updates the tracked sort order values accordingly +@action +customOnSort(_sortBy, sortOrder) { + this.customSortOrderForStatus = sortOrder; +} +``` + +### Density + +To create a condensed or spacious Advanced Table, add `@density` to the Advanced Table’s invocation. Note that it only affects the table body, not the table header. + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + +``` + +#### Horizontal alignment + +To create a column that has right-aligned content, set `@align` to `right` on both the column’s header and cell (the cell’s horizontal content alignment should be the same as the column’s horizontal content alignment). + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + + + + Create + Read + Update + + Delete + + + + + +``` + +### Tooltip + +[Header cells](/components/table/advanced-table#headers) should be clear, concise, and straightforward whenever possible. However, there could be cases where the label is insufficient by itself and extra information is required. In this case, it’s possible to show a tooltip next to the label in the header: + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.vinyl-cost}} + + + +``` +### Scrollable table + +Consuming a large amount of data in a tabular format can lead to an intense cognitive load for the user. As a general principle, care should be taken to simplify the information within a table as much as possible. + +We recommend using functionalities like [pagination](/components/pagination), [sorting](/components/table/advanced-table?tab=code#sortable-advanced-table), and [filtering](/patterns/filter-patterns) to reduce this load. + +#### Vertical scrolling + +For situations where the default number of rows visible may be high, it can be difficult for users to track which column is which once they scroll. In this case, the `hasStickyHeader` argument can be used to make the column headers persist as the user scrolls. + +```handlebars + +
+ + <:body as |B|> + + {{B.data.id}} + {{B.data.name}} + {{B.data.email}} + {{B.data.role}} + + + +
+``` + +#### Horizontal scrolling + +There may be cases when it’s necessary to show an Advanced Table with a large number of columns and allow the user to scroll horizontally. In this case the consumer can place the Advanced Table inside a container with `overflow: auto`. + +```handlebars + +
+ + <:body as |B|> + + {{B.data.first_name}} + {{B.data.last_name}} + {{B.data.age}} + {{B.data.email}} + {{B.data.phone}} + {{B.data.bio}} + {{B.data.education}} + {{B.data.occupation}} + + + +
+``` + +### Multi-select Advanced Table + +A multi-select Advanced Table includes checkboxes enabling users to select multiple rows for purposes of performing bulk operations. Checking or unchecking the checkbox in the Advanced Table header either selects or deselects the checkboxes on each row in the body. Individual checkboxes in the rows can also be selected or deselected. + +Add `isSelectable=true` to create a multi-select Advanced Table. The `onSelectionChange` argument can be used to pass a callback function to receive selection keys when the selected rows change. You must also pass a `selectionKey` to each row which gets passed back through the `onSelectionChange` callback which maps the row selection on the Advanced Table to an item in your data model. + +!!! Warning + +At this time, the Advanced Table does not support multi-select nested rows. If this is a use case you require, please [contact the Design Systems Team](/about/support). + +!!! + +#### Simple multi-select + +This is a simple example of an Advanced Table with multi-selection. Notice the `@selectionKey` argument provided to the rows, used by the `@onSelectionChange` callback to provide the list of selected/deselected rows as argument(s) for the invoked function: + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + +``` + +!!! Warning + +**Important** + +To make the Advanced Table accessible, each checkbox used for the selection needs to have a distinct `aria-label`. For this reason, you need to provide a `@selectionAriaLabelSuffix` value (possibly unique) to the rows in the Advanced Table's body. + +!!! + +Here’s an example of what a `@onSelectionChange` callback function could look like. + +```javascript +@action +demoOnSelectionChange({ + selectionKey, // the `selectionKey` value for the selected row or "all" if the "select all" has been toggled + selectionCheckboxElement, // the checkbox DOM element toggled by the user + selectableRowsStates, // an array of objects describing each displayed "row" state (its `selectionKey` value and its `isSelected` state) + selectedRowsKeys // an array of all the `selectionKey` values of the currently selected rows +}) { + // here we use the `selectedRowsKeys` to execute some action on each of the data records associated (via the `@selectionKey` argument) to the selected rows + selectedRowsKeys.forEach((rowSelectionKey) => { + // do something using the row’s `selectionKey` value + // ... + // ... + // ... + }); +} +``` + +For details about the arguments provided to the `@onSelectionChange` callback function, refer to the [Component API](#component-api) section. + +#### Multi-select with sorting by selection state + +To enable sorting by selected rows in an Advanced Table, you need to set `@selectableColumnKey` to the key in each row that tracks its selection state. This allows you to sort based on whether rows are selected or not. + +In the demo below, we set up a multi-select Advanced Table that can be sorted based on the selection state of its rows. + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + {{if B.data.isSelected "Yes" "No"}} + + + +``` + +#### Multi-select with pagination and persisted selection status + +This is a more complex example, where an Advanced Table with multi-selection is associated with a [Pagination](/components/pagination) element (a similar use case would apply if a [filter](/patterns/filter-patterns) is applied to the data used to populate the Advanced Table). In this case, a **subset of rows** is displayed on screen. + +When a user selects a row, if the displayed rows are replaced with other ones (e.g., when the user clicks on the “next” button or on a different page number) there’s the question of what happens to the previous selection: is it persisted in the data/model underlying the table? Or is it lost? + +In the demo below, we are persisting the selection in the data/model, so that when navigating to different pages, the row selections persist across table re-renderings. + +```handlebars +
+ + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + + +
+``` + +Depending on the expected behavior, you will need to implement the consumer-side logic that handles the persistence (or not) using the `@onSelectionChange` callback function. For the example above, something like this: + +```javascript +@action +demoOnSelectionChangeWithPagination({ selectableRowsStates }) { + // we loop over all the displayed table rows (a subset of the dataset) + selectableRowsStates.forEach((row) => { + // we find the record in the dataset corresponding to the current row + const recordToUpdate = this.demoSourceData.find( + (modelRow) => modelRow.id === row.selectionKey + ); + if (recordToUpdate) { + // we update the record `isSelected` state based on the row (checkbox) state + recordToUpdate.isSelected = row.isSelected; + } + }); +} + +``` + +For details about the arguments provided to the `@onSelectionChange` callback function, refer to the [Component API](#component-api) section. + + +#### Usability and accessibility considerations + +Since the “selected” state of a row is communicated with the checkbox selection, there are some important considerations to keep in mind when implementing a multi-select Advanced Table. + +If the selection status of the rows is persisted even when a row is not displayed in the UI, consider what the expectations of the user might be: how are they made aware that the action they are going to perform may involve rows that were previously selected but not displayed in the current view? + +Even more complex is the case of the “Select all” checkbox in the Advanced Table header. While the expected behavior might seem straightforward when all rows are displayed, it may not be obvious what the expected behavior is when the rows are paginated or have been filtered. + +Consider the experience of a user intending to select all or a subset of all possible rows: + +If a user interacts with a “Select all” function or button, is the expectation that only displayed rows are selected (what happens in the example above), or that all of the rows in the data set/model are selected, even if not displayed in the current view? + +In the first scenario, the “Select all” state changes depending on what rows are in view and can be confusing. + +In the second scenario it might not be obvious that all of the rows have been selected and may result in the user unintentionally performing a destructive action under the assumption that they have only selected the rows in the current view. + +Whatever functionality you decide to implement, be mindful of all these possible subtleties and complexities. + +At a bare minimum we recommend clearly communicating to the user if they have selected rows outside of their current view and how many out of the total data set are selected. We're working to document these scenarios as they arise, in the meantime [contact the Design Systems Team](/about/support) for assistance. + +### More examples + +#### Visually hidden headers + +Labels within the header cells are intended to provide contextual information about the column’s content to the end user. There may be special cases in which that label is redundant from a visual perspective, because the kind of content can be inferred by looking at it (eg. a contextual dropdown). + +In this example we’re visually hiding the label in the last column by passing `isVisuallyHidden=true` to it: + +```handlebars + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + + Delete + + + + + +``` + +_Notice: only non-sortable headers can be visually hidden._ + +#### Internationalized column headers, overflow menu dropdown + +Here’s an Advanced Table implementation that uses an array hash with localized strings for the column headers. It indicates which columns should be sortable, and adds an overflow menu. + +```handlebars{data-execute=false} + + <:body as |B|> + + {{B.data.artist}} + {{B.data.album}} + {{B.data.year}} + + + + Create + Read + Update + + Delete + + + + + +``` + diff --git a/website/docs/components/table/advanced-table/partials/guidelines/guidelines.md b/website/docs/components/table/advanced-table/partials/guidelines/guidelines.md new file mode 100644 index 00000000000..9d4fab56ce9 --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/guidelines/guidelines.md @@ -0,0 +1,232 @@ +## Usage + +### When to use + +- When large datasets benefit from being viewed in a scrollable container instead of with pagination. +- When an expandable table is needed for hierarchical data. +- When users would benefit from more efficient keyboard navigation, such as when there are many rows or columns. + +### When not to use + +- If your dataset requires only basic interactions, such as simple sorting or pagination, and does not require features like nested rows, advanced keyboard navigation, or sticky headers, the standard [Table](/components/table/table) is a more suitable choice. +- When visual representations like charts or graphs better convey the data. +- As a layout mechanism for structuring content that isn’t tabular data. +- To replicate spreadsheet-like functionality with extensive in-cell editing or calculations. + +## Columns + +### Sorting + +![A group of 4 Advanced Table header cells, with each variant of sort button: no sort button, the default unsorted, sorted ascending, and sorted descending.](/assets/components/table/advanced-table/table-sorting.png) + +- Sorting is not relevant for all content, so thoughtfully consider when to apply sorting. +- An Advanced Table allows end-users to sort by one column at a time. While multiple columns may offer sorting options, users can only apply sorting to one column at any given moment. + + +### Tooltips + +Labels within the Advanced Table column should be clear, concise, and straightforward. In the case that more context or details are necessary, a [Tooltip](/components/tooltip) can be used in conjunction with the label but should be used sparingly and as a last resort. + +![](/assets/components/table/advanced-table/table-tooltip-example.png) + +Some examples where it may be useful to include additional context in a tooltip include: + +- When the label contains a product or HashiCorp-specific term. +- When the label refers to a setting that can be changed elsewhere in the application. + + +### Width + +Column width is determined by manually resizing the header column and cells within Figma. As a best practice, column width should be adjusted to fit the longest data type within the cell. + +### Placement + +!!! Info + +The column placement property is only relevant within Figma and doesn’t exist as a property in code. + +!!! + +Column placement determines the visual styling based on where the column is placed relative to other columns in the Advanced Table. + +![For header columns, start placement adds a border radius to the top left corner and a border on the left and right, middle placement has squared corners and a border on the right, end placement has a border radius on the top right corner and a border on the right. For cells, start placement has a border on the left and right, middle and end placement have a border on the right.](/assets/components/table/advanced-table/table-col-placement.png) + +### Alignment + +The alignment of text and content within an Advanced Table impacts the readability and speed at which users can effectively parse the information. The proper alignment method depends on the content within the cell, and relative position within the advanced table. + +!!! Do + +Use consistent alignment between the header label and the cell content. + +![An Advanced Table with two columns, the first column is left aligned and the second is right aligned.](/assets/components/table/advanced-table/table-alignment-do.png) + +!!! + +!!! Dont + +Avoid misaligned header labels and content. + +![An Advanced Table with two columns, the header of the first column is left aligned and the cell below is right aligned. The header of the second column is right aligned and the cell below is left aligned.](/assets/components/table/advanced-table/table-alignment-dont.png) + +!!! + +#### Left alignment + +Align content to the left of the cell by default. This ensures readability across different content types, consistency in content of varying lengths, and alignment between the column header label and the content within the cell. + +Use left alignment for: + +- String and text-based content (unique identifiers or IDs, names and naming conventions, etc). +- Numerical values that do not contain decimals or floating point numbers. +- Numerical values that contain periods or other delimiter characters (IP addresses). +- Nested components that display a string or text value, e.g., a [Badge](/components/badge). + +![](/assets/components/table/advanced-table/start-alignment-example.png) + +#### Right alignment + +Right alignment can be used when expressing numerical values with decimals as this aligns the decimal places vertically. + +Common examples of right alignment include: + +- Financial information and currency amounts. +- Fractional and floating point values represented with decimals. + +![](/assets/components/table/advanced-table/end-alignment-example.png) + +Right alignment can also be used in the last column of an advanced table to: + +- Highlight a "more options" function. +- As a means to visually "bookend" the row with content that is of a similar length, e.g., timestamps, TTL (time-to-live) values, dates. + +![](/assets/components/table/advanced-table/end-alignment-example-02.png) + +!!! Dont + +Don’t right align content that is variable in length. This can make the content more difficult to read by forcing an unnatural [reading pattern](/patterns/button-organization?tab=research#layout-and-reading-patterns). + +![Column with badges that have different length labels end aligned. The badge labels are "Successful", "Needs confirmation", and "Error".](/assets/components/table/advanced-table/end-alignment-variable-length.png) + +!!! + +#### Other alignment methods + +We don’t recommend center or justified alignment of content within Advanced Table cells. These alignment methods can result in the content being difficult to read, especially if it is variable in length. + +!!! Dont + +Don’t center header labels or cell content within a table. + +![](/assets/components/table/advanced-table/center-justified-alignment.png) + +!!! + +## Column and row span + +- Supports combining multiple columns or rows into a single cell. +- Apply column and row spans carefully to maintain alignment, accessibility, and smooth table interactions. +- Multi-span cells should use the same alignment for readability. + +![](/assets/components/table/advanced-table/colspan-table-example.png) + +## Rows + +### Headers + +- Labels in headers should be clear, concise, and straightforward. +- The label should clearly indicate what type of content is contained within the cell (string, number, status, etc). +- Labels should use sentence-case capitalization, not all-caps. + +#### Sticky headers + +Sticky headers keep column labels visible while scrolling, aiding navigation in large data sets or nested rows. + +![](/assets/components/table/advanced-table/advanced-table-sticky-header.png) + +### Expandable rows + +Expandable rows let users show or hide more content without navigating away from the table. The expanded content should align with the header labels, even if the parent row includes minimal data. + +![Advanced Table expandable rows. The parent rows display a summary of a Hashicorp product and the total price, the children rows show a breakdown of each billing item from that product and their individual cost.](/assets/components/table/advanced-table/expandable-rows.png) + +!!! Dont + +Avoid using expandable rows when data is not structured in parent-child relationships. + +![Advanced Table where the parent row has cells for name and email, but children rows have cells containing order date and total.](/assets/components/table/advanced-table/advanced-table-dont-parent-nested.png) + +!!! + + +!!! Dont + +Avoid using different density settings for parent and child rows. + +![Advanced Table with default height parent rows and short density nested rows.](/assets/components/table/advanced-table/advanced-table-density-mix.png) + +!!! + + +### Striping + +!!! Info + +Ensure that content within striped rows continue to maintain adequate color contrast with the striped background. + +!!! + +![Advanced Tables with row striping have rows that alternate between white and light grey background color.](/assets/components/table/advanced-table/advanced-table-striping.png) + +Striping enhances readability by alternating row colors, making it easier to scan tabular data. + +- Non-Nested Advanced Tables: Striping starts with the second row, distinguishing it from the header. +- Nested Advanced Tables: Child rows are automatically striped, while parent rows remain unstriped to visually reinforce hierarchy. This behavior cannot be disabled. + +### Placement + +!!! Info + +The row placement property is only relevant within Figma and doesn’t exist as a property within the code. + +!!! + +Row placement determines the visual styling based on where the row is placed relative to other rows within the Advanced Table. Only cells with a column placement that is either start or end use the row placement property; column position middle does not use this property. + +![The cell with column placement end and row placement end has a border radius set on the bottom right corner.](/assets/components/table/advanced-table/table-row-placement.png) + +## Cells + +### Density + +- Use medium density by default for balanced readability and display. +- Choose short density for text-heavy tables to fit more rows on a page. +- Dense content can make tables harder to read and scan, so use it thoughtfully. + + +## Horizontal scrolling + +Use horizontal scrolling when the number of columns expands beyond the viewport or container. + +![](/assets/components/table/advanced-table/horizontal-scrolling.png) + + +## Multi-Select + +Multi-select allows users to select multiple rows to perform bulk actions, such as deleting or exporting data. The Advanced Table maintains selection states across pagination and filtering, ensuring consistency when interacting with large datasets. For more details, check out the [Multi-Select Table Pattern.](https://helios.hashicorp.design/patterns/table-multi-select) + +!!! Info + +Multi-select and sorting are not supported for nested rows at this time. + +!!! + +A multi-select pattern consists of: + +1. A select all in the table's header row. This acts as the parent checkbox, allowing the simultaneous selection or deselection of all child rows in a single table. + +![](/assets/components/table/advanced-table/table-multi-select-header.png) + +2. Row level select allowing for the selection of an individual row. + +![](/assets/components/table/advanced-table/table-multi-select-cells.png) \ No newline at end of file diff --git a/website/docs/components/table/advanced-table/partials/guidelines/overview.md b/website/docs/components/table/advanced-table/partials/guidelines/overview.md new file mode 100644 index 00000000000..666b50754ae --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/guidelines/overview.md @@ -0,0 +1 @@ +The Advanced Table is for complex datasets with features like sticky headers, keyboard navigation, and expandable rows. Don't mix Table and Advanced Table components; they aren't interchangeable. \ No newline at end of file diff --git a/website/docs/components/table/advanced-table/partials/specifications/anatomy.md b/website/docs/components/table/advanced-table/partials/specifications/anatomy.md new file mode 100644 index 00000000000..fa3895547ed --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/specifications/anatomy.md @@ -0,0 +1,50 @@ +## Anatomy + +### Advanced Table headers + +![](/assets/components/table/advanced-table/advanced-table-header-select-anatomy.png) + +| Element | Usage | +|------------------|-------------------------------------------------| +| Checkbox | Optional, but required when cells yield a checkbox | +| Label | Required | +| Tooltip button | Optional | +| Sort button | Optional, Options: none, ascending, descending | +| Container | Required | + +### Advanced Table cells + + +#### Expandable cell + +![](/assets/components/table/advanced-table/advanced-table-cell-parent-anatomy.png) + +| Element | Usage | +|--------------|----------| +| Expand | Optional | +| Cell content | Required | +| Icon | Optional | +| Container | Required | + + +#### Nested cell + +![](/assets/components/table/advanced-table/advanced-table-cell-nested-anatomy.png) + +| Element | Usage | +|--------------|----------| +| Nested | Required | +| Cell content | Required | +| Icon | Optional | +| Container | Required | + +#### Selection cell + +![](/assets/components/table/advanced-table/advanced-table-cell-select-anatomy.png) + +| Element | Usage | +|--------------|----------| +| Checkbox | Optional, but required when the header yields a checkbox| +| Cell content | Required | +| Icon | Optional | +| Container | Required | \ No newline at end of file diff --git a/website/docs/components/table/advanced-table/partials/specifications/states.md b/website/docs/components/table/advanced-table/partials/specifications/states.md new file mode 100644 index 00000000000..a022457b6e6 --- /dev/null +++ b/website/docs/components/table/advanced-table/partials/specifications/states.md @@ -0,0 +1,11 @@ +## States + +### Sort button +Sortable header columns have interactive state variants, indicating their interactivity. Non-sortable header columns are static and do not include state variants. + +![Header column state example](/assets/components/table/advanced-table/Advanced-table-sort-button-states.png) + +### Advanced Table cells +Cells have a default and focused state. The focused state provides visual feedback for keyboard navigation. + +![Header column state example](/assets/components/table/advanced-table/advanced-table-focus-states.png) diff --git a/website/docs/components/table/index.js b/website/docs/components/table/table/index.js similarity index 100% rename from website/docs/components/table/index.js rename to website/docs/components/table/table/index.js diff --git a/website/docs/components/table/index.md b/website/docs/components/table/table/index.md similarity index 97% rename from website/docs/components/table/index.md rename to website/docs/components/table/table/index.md index dbf5e234864..53b3b1b8955 100644 --- a/website/docs/components/table/index.md +++ b/website/docs/components/table/table/index.md @@ -9,6 +9,7 @@ links: https://github.com/hashicorp/design-system/tree/main/packages/components/src/components/hds/table related: - components/pagination + - components/table/advanced-table - patterns/filter-patterns - patterns/table-multi-select previewImage: assets/illustrations/components/table.jpg diff --git a/website/docs/components/table/partials/accessibility/accessibility.md b/website/docs/components/table/table/partials/accessibility/accessibility.md similarity index 100% rename from website/docs/components/table/partials/accessibility/accessibility.md rename to website/docs/components/table/table/partials/accessibility/accessibility.md diff --git a/website/docs/components/table/partials/code/component-api.md b/website/docs/components/table/table/partials/code/component-api.md similarity index 100% rename from website/docs/components/table/partials/code/component-api.md rename to website/docs/components/table/table/partials/code/component-api.md diff --git a/website/docs/components/table/partials/code/how-to-use.md b/website/docs/components/table/table/partials/code/how-to-use.md similarity index 98% rename from website/docs/components/table/partials/code/how-to-use.md rename to website/docs/components/table/table/partials/code/how-to-use.md index e4afb639c60..660a007cef9 100644 --- a/website/docs/components/table/partials/code/how-to-use.md +++ b/website/docs/components/table/table/partials/code/how-to-use.md @@ -456,7 +456,7 @@ To create a column that has right-aligned content, set `@align` to `right` on bo ### Tooltip -[Table headers](/components/table#headers) should be clear, concise, and straightforward whenever possible. However, there could be cases where the label is insufficient by itself and extra information is required. In this case, it’s possible to show a tooltip next to the label in the header: +[Table headers](/components/table/table#headers) should be clear, concise, and straightforward whenever possible. However, there could be cases where the label is insufficient by itself and extra information is required. In this case, it’s possible to show a tooltip next to the label in the header: ```handlebars diff --git a/website/docs/patterns/filter-patterns/partials/guidelines/overview.md b/website/docs/patterns/filter-patterns/partials/guidelines/overview.md index b59c65f5fe1..30c93009836 100644 --- a/website/docs/patterns/filter-patterns/partials/guidelines/overview.md +++ b/website/docs/patterns/filter-patterns/partials/guidelines/overview.md @@ -1 +1 @@ -Filtering is used to limit the objects in a data set based on one or more parameters. It is commonly used in tandem with a [Table](/components/table), but the core concepts have a wide range of relevant use cases depending on the type of data set and the context within the application. +Filtering is used to limit the objects in a data set based on one or more parameters. It is commonly used in tandem with a [Table](/components/table/table) or [Advanced Table](/components/table/advanced-table), but the core concepts have a wide range of relevant use cases depending on the type of data set and the context within the application. diff --git a/website/docs/patterns/filter-patterns/partials/specifications/specifications.md b/website/docs/patterns/filter-patterns/partials/specifications/specifications.md index ebef35da55d..1917d314fef 100644 --- a/website/docs/patterns/filter-patterns/partials/specifications/specifications.md +++ b/website/docs/patterns/filter-patterns/partials/specifications/specifications.md @@ -21,7 +21,7 @@ Orient filter patterns in either a horizontal filter bar, or a vertical filter s ![Filter bar orientation](/assets/patterns/filter-patterns/layout-filter-bar.png =559x*) -The most common method of organizing filters is in a filter bar which orients the filters horizontally on top of the data set. This orientation is flexible and is commonly used in tandem when a data set is presented in a [Table](/components/table). +The most common method of organizing filters is in a filter bar which orients the filters horizontally on top of the data set. This orientation is flexible and is commonly used in tandem when a data set is presented in a [Table](/components/table/table) or [Advanced Table](/components/table/advanced-table). ### Filter sidebar orientation diff --git a/website/docs/patterns/table-multi-select/index.md b/website/docs/patterns/table-multi-select/index.md index f6d7e42e3a9..3a7de3efc3e 100644 --- a/website/docs/patterns/table-multi-select/index.md +++ b/website/docs/patterns/table-multi-select/index.md @@ -4,7 +4,7 @@ description: Guidelines, compositions, and interaction patterns for selecting an caption: Guidelines, compositions, and interaction patterns for selecting and transforming results in a Table. links: figma: https://www.figma.com/design/5Pv32j4QiOOD8lkFTD1dxC/HDS-Patterns-v2.0?node-id=24-12938&t=cpJowBg8aXp4qAG1-1 -related: ['components/table', 'patterns/filter-patterns', 'components/pagination'] +related: ['components/table/table', 'components/table/advanced-table', 'patterns/filter-patterns', 'components/pagination'] previewImage: assets/illustrations/patterns/table-multi-select.jpg navigation: keywords: ['selection', 'bulk'] diff --git a/website/docs/patterns/table-multi-select/partials/guidelines/guidelines.md b/website/docs/patterns/table-multi-select/partials/guidelines/guidelines.md index 4f6eeae9604..61bb877b2ed 100644 --- a/website/docs/patterns/table-multi-select/partials/guidelines/guidelines.md +++ b/website/docs/patterns/table-multi-select/partials/guidelines/guidelines.md @@ -1,4 +1,4 @@ -The Helios Table [multi-select functionality](/components/table#multi-select) supports performing bulk actions and selection of one or more rows within a Table. +The Helios Table [multi-select functionality](/components/table/table#multi-select) supports performing bulk actions and selection of one or more rows within a Table. ![Example of Table multi-select in context](/assets/patterns/table-multi-select/multi-select-preview-image.png) diff --git a/website/public/assets/components/table/advanced-table/Advanced-table-sort-button-states.png b/website/public/assets/components/table/advanced-table/Advanced-table-sort-button-states.png new file mode 100644 index 00000000000..dbbe5c9691a Binary files /dev/null and b/website/public/assets/components/table/advanced-table/Advanced-table-sort-button-states.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-cell-nested-anatomy.png b/website/public/assets/components/table/advanced-table/advanced-table-cell-nested-anatomy.png new file mode 100644 index 00000000000..de5fffa9a9c Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-cell-nested-anatomy.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-cell-parent-anatomy.png b/website/public/assets/components/table/advanced-table/advanced-table-cell-parent-anatomy.png new file mode 100644 index 00000000000..79a585c9f1a Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-cell-parent-anatomy.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-cell-select-anatomy.png b/website/public/assets/components/table/advanced-table/advanced-table-cell-select-anatomy.png new file mode 100644 index 00000000000..f16d455d5cb Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-cell-select-anatomy.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-density-mix.png b/website/public/assets/components/table/advanced-table/advanced-table-density-mix.png new file mode 100644 index 00000000000..b9134640814 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-density-mix.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-dont-parent-nested.png b/website/public/assets/components/table/advanced-table/advanced-table-dont-parent-nested.png new file mode 100644 index 00000000000..60ed8f62368 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-dont-parent-nested.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-focus-states.png b/website/public/assets/components/table/advanced-table/advanced-table-focus-states.png new file mode 100644 index 00000000000..ad016ebf5ac Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-focus-states.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-header-select-anatomy.png b/website/public/assets/components/table/advanced-table/advanced-table-header-select-anatomy.png new file mode 100644 index 00000000000..f3a1e8df2bd Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-header-select-anatomy.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-sticky-header.png b/website/public/assets/components/table/advanced-table/advanced-table-sticky-header.png new file mode 100644 index 00000000000..66c178b992b Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-sticky-header.png differ diff --git a/website/public/assets/components/table/advanced-table/advanced-table-striping.png b/website/public/assets/components/table/advanced-table/advanced-table-striping.png new file mode 100644 index 00000000000..eaa337c3fe1 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/advanced-table-striping.png differ diff --git a/website/public/assets/components/table/advanced-table/center-justified-alignment.png b/website/public/assets/components/table/advanced-table/center-justified-alignment.png new file mode 100644 index 00000000000..d99811851f5 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/center-justified-alignment.png differ diff --git a/website/public/assets/components/table/advanced-table/colspan-table-example.png b/website/public/assets/components/table/advanced-table/colspan-table-example.png new file mode 100644 index 00000000000..cbb8ad94c23 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/colspan-table-example.png differ diff --git a/website/public/assets/components/table/advanced-table/end-alignment-example-02.png b/website/public/assets/components/table/advanced-table/end-alignment-example-02.png new file mode 100644 index 00000000000..cbbf54baa45 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/end-alignment-example-02.png differ diff --git a/website/public/assets/components/table/advanced-table/end-alignment-example.png b/website/public/assets/components/table/advanced-table/end-alignment-example.png new file mode 100644 index 00000000000..53f879bf332 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/end-alignment-example.png differ diff --git a/website/public/assets/components/table/advanced-table/end-alignment-variable-length.png b/website/public/assets/components/table/advanced-table/end-alignment-variable-length.png new file mode 100644 index 00000000000..c0e0d42456f Binary files /dev/null and b/website/public/assets/components/table/advanced-table/end-alignment-variable-length.png differ diff --git a/website/public/assets/components/table/advanced-table/expandable-rows.png b/website/public/assets/components/table/advanced-table/expandable-rows.png new file mode 100644 index 00000000000..f63a74e5c90 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/expandable-rows.png differ diff --git a/website/public/assets/components/table/advanced-table/horizontal-scrolling.png b/website/public/assets/components/table/advanced-table/horizontal-scrolling.png new file mode 100644 index 00000000000..3e502d34897 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/horizontal-scrolling.png differ diff --git a/website/public/assets/components/table/advanced-table/start-alignment-example.png b/website/public/assets/components/table/advanced-table/start-alignment-example.png new file mode 100644 index 00000000000..b8a25c99beb Binary files /dev/null and b/website/public/assets/components/table/advanced-table/start-alignment-example.png differ diff --git a/website/public/assets/components/table/advanced-table/table-alignment-do.png b/website/public/assets/components/table/advanced-table/table-alignment-do.png new file mode 100644 index 00000000000..3b003c80345 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-alignment-do.png differ diff --git a/website/public/assets/components/table/advanced-table/table-alignment-dont.png b/website/public/assets/components/table/advanced-table/table-alignment-dont.png new file mode 100644 index 00000000000..5c19c040c85 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-alignment-dont.png differ diff --git a/website/public/assets/components/table/advanced-table/table-col-placement.png b/website/public/assets/components/table/advanced-table/table-col-placement.png new file mode 100644 index 00000000000..ad0ab4f62b0 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-col-placement.png differ diff --git a/website/public/assets/components/table/advanced-table/table-multi-select-cells.png b/website/public/assets/components/table/advanced-table/table-multi-select-cells.png new file mode 100644 index 00000000000..e13cca7330a Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-multi-select-cells.png differ diff --git a/website/public/assets/components/table/advanced-table/table-multi-select-header.png b/website/public/assets/components/table/advanced-table/table-multi-select-header.png new file mode 100644 index 00000000000..cb60771ecf2 Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-multi-select-header.png differ diff --git a/website/public/assets/components/table/advanced-table/table-row-placement.png b/website/public/assets/components/table/advanced-table/table-row-placement.png new file mode 100644 index 00000000000..4a76db76cac Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-row-placement.png differ diff --git a/website/public/assets/components/table/advanced-table/table-sorting.png b/website/public/assets/components/table/advanced-table/table-sorting.png new file mode 100644 index 00000000000..d084521b5df Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-sorting.png differ diff --git a/website/public/assets/components/table/advanced-table/table-tooltip-example.png b/website/public/assets/components/table/advanced-table/table-tooltip-example.png new file mode 100644 index 00000000000..b38ab1eeefa Binary files /dev/null and b/website/public/assets/components/table/advanced-table/table-tooltip-example.png differ diff --git a/website/public/assets/components/table/table-density.png b/website/public/assets/components/table/table-density.png index f63cfd58738..7d3114ae4ae 100644 Binary files a/website/public/assets/components/table/table-density.png and b/website/public/assets/components/table/table-density.png differ diff --git a/website/public/assets/illustrations/components/advanced-table.jpg b/website/public/assets/illustrations/components/advanced-table.jpg new file mode 100644 index 00000000000..644ead602ad Binary files /dev/null and b/website/public/assets/illustrations/components/advanced-table.jpg differ diff --git a/website/tests/acceptance/components/table-test.js b/website/tests/acceptance/components/table-test.js index 948e5818881..6bdafa41ea6 100644 --- a/website/tests/acceptance/components/table-test.js +++ b/website/tests/acceptance/components/table-test.js @@ -11,14 +11,14 @@ import { a11yAudit } from 'ember-a11y-testing/test-support'; module('Acceptance | components/table', function (hooks) { setupApplicationTest(hooks); - test('visiting /components/table', async function (assert) { - await visit('/components/table'); + test('visiting /components/table/table', async function (assert) { + await visit('/components/table/table'); - assert.strictEqual(currentURL(), '/components/table'); + assert.strictEqual(currentURL(), '/components/table/table'); }); - test('components/table passes a11y automated checks', async function (assert) { - await visit('/components/table'); + test('components/table/table passes a11y automated checks', async function (assert) { + await visit('/components/table/table'); await a11yAudit(); assert.ok(true, 'a11y automation audit passed'); }); diff --git a/website/tests/acceptance/sidebar-test.js b/website/tests/acceptance/sidebar-test.js index 861f6659525..ad9da1e9abc 100644 --- a/website/tests/acceptance/sidebar-test.js +++ b/website/tests/acceptance/sidebar-test.js @@ -85,8 +85,8 @@ module('Acceptance | Sidebar filter', function (hooks) { test('should expand subsection when click a parent container', async function (assert) { await visit('/components'); - assert.dom('.doc-table-of-contents__folder').exists({ count: 3 }); - assert.dom('.doc-table-of-contents__button').exists({ count: 3 }); + assert.dom('.doc-table-of-contents__folder').exists({ count: 4 }); + assert.dom('.doc-table-of-contents__button').exists({ count: 4 }); assert .dom('.doc-table-of-contents__button') .hasAttribute('aria-expanded', 'false');