From 209ff8784d0c68f5495b04e2fd2b503b9b4520ef Mon Sep 17 00:00:00 2001 From: Karim Dehbi Date: Wed, 30 Oct 2024 13:48:39 +0100 Subject: [PATCH] feat: UIG-2910 - vl-table-next - introductie component Storybook verbeterd, cypress uitgebreid & meta-data aangepast. --- .../next/table/vl-table.stories.cy.ts | 37 ++ .../vl-rich-data-table.stories.cy.ts | 8 +- .../data-table/vl-data-table.stories.cy.ts | 6 +- .../vlux-meta-data/vlux-meta-data.json | 9 +- ...024-van-v1-naar-v2-beschikbaar.stories.mdx | 30 + .../stories/vl-search-filter.stories-doc.mdx | 2 +- .../table/stories/vl-table.stories-arg.ts | 90 +++ .../table/stories/vl-table.stories-doc.mdx | 165 +++++ .../next/table/stories/vl-table.stories.ts | 421 ++++++++++++ .../src/next/table/vl-table.component.cy.ts | 626 ++++++++++++++++++ .../src/next/table/vl-table.component.ts | 211 ++++++ .../components/src/next/table/vl-table.css.ts | 498 ++++++++++++++ .../src/next/table/vl-table.defaults.ts | 10 + .../stories/vl-data-table.stories-doc.mdx | 10 +- .../wt-config-build/components.wt-config.ts | 7 + .../web-types-completeness.spec.ts | 2 +- 16 files changed, 2116 insertions(+), 16 deletions(-) create mode 100644 apps/storybook-e2e/src/e2e/components/next/table/vl-table.stories.cy.ts create mode 100644 libs/components/src/next/table/stories/vl-table.stories-arg.ts create mode 100644 libs/components/src/next/table/stories/vl-table.stories-doc.mdx create mode 100644 libs/components/src/next/table/stories/vl-table.stories.ts create mode 100644 libs/components/src/next/table/vl-table.component.cy.ts create mode 100644 libs/components/src/next/table/vl-table.component.ts create mode 100644 libs/components/src/next/table/vl-table.css.ts create mode 100644 libs/components/src/next/table/vl-table.defaults.ts diff --git a/apps/storybook-e2e/src/e2e/components/next/table/vl-table.stories.cy.ts b/apps/storybook-e2e/src/e2e/components/next/table/vl-table.stories.cy.ts new file mode 100644 index 000000000..521d3cbda --- /dev/null +++ b/apps/storybook-e2e/src/e2e/components/next/table/vl-table.stories.cy.ts @@ -0,0 +1,37 @@ +const tableNextDefaultUrl = 'http://localhost:8080/iframe.html?id=components-next-table--table-default&viewMode=story'; +const tableNextJoinedRowTitlesUrl = 'http://localhost:8080/iframe.html?id=components-next-table--table-joined-row-titles&viewMode=story'; +const tableNextExpandableUrl = 'http://localhost:8080/iframe.html?id=components-next-table--table-expandable&viewMode=story'; +const tableNextExpandableCustomToggleDetailsColumnUrl = 'http://localhost:8080/iframe.html?id=components-next-table--table-expandable-custom-toggle-details-column&viewMode=story'; + + +describe('story - vl-table-next - default', () => { + it('should render', () => { + cy.visit(tableNextDefaultUrl); + + cy.get('vl-table-next').find('table.vl-table-next'); + }); +}); + +describe('story - vl-table-next - joined row titles', () => { + it('should render', () => { + cy.visit(tableNextJoinedRowTitlesUrl); + + cy.get('vl-table-next').find('table.vl-table-next'); + }); +}); + +describe('story - vl-table-next - expandable', () => { + it('should render', () => { + cy.visit(tableNextExpandableUrl); + + cy.get('vl-table-next').find('table.vl-table-next'); + }); +}); + +describe('story - vl-table-next - expandable custom toggle details column', () => { + it('should render', () => { + cy.visit(tableNextExpandableCustomToggleDetailsColumnUrl); + + cy.get('vl-table-next').find('table.vl-table-next'); + }); +}); diff --git a/apps/storybook-e2e/src/e2e/components/rich-data-table/vl-rich-data-table.stories.cy.ts b/apps/storybook-e2e/src/e2e/components/rich-data-table/vl-rich-data-table.stories.cy.ts index db9b7cbd3..c834303f5 100644 --- a/apps/storybook-e2e/src/e2e/components/rich-data-table/vl-rich-data-table.stories.cy.ts +++ b/apps/storybook-e2e/src/e2e/components/rich-data-table/vl-rich-data-table.stories.cy.ts @@ -136,7 +136,7 @@ const selectPage = (pageNumber: number) => { cy.get('vl-rich-data-table').find('vl-pager').shadow().find(`[data-vl-pager-page=${pageNumber}]`).click(); }; -describe('story vl-rich-data-table default', () => { +describe('story vl-rich-table default', () => { it('should set data in the table', () => { cy.visit(richDataTableUrl); @@ -151,7 +151,7 @@ describe('story vl-rich-data-table default', () => { }); }); -describe('story vl-rich-data-table - sorting', () => { +describe('story vl-rich-table - sorting', () => { beforeEach(() => cy.visit(`${richDataTableSortingUrl}`)); const { data: rowData } = { @@ -199,7 +199,7 @@ describe('story vl-rich-data-table - sorting', () => { }); }); -describe('story vl-rich-data-table - filter', () => { +describe('story vl-rich-table - filter', () => { beforeEach(() => cy.visit(`${richDataTableFilterUrl}`)); const data = rowsForFiltering; const filterField = 'name'; @@ -225,7 +225,7 @@ describe('story vl-rich-data-table - filter', () => { }); }); -describe('story vl-rich-data-table - paging', () => { +describe('story vl-rich-table - paging', () => { beforeEach(() => cy.visit(`${richDataTablePagingUrl}`)); const data = rowsForPagination; diff --git a/apps/storybook-e2e/src/e2e/elements/data-table/vl-data-table.stories.cy.ts b/apps/storybook-e2e/src/e2e/elements/data-table/vl-data-table.stories.cy.ts index 0a04459a0..b251453cb 100644 --- a/apps/storybook-e2e/src/e2e/elements/data-table/vl-data-table.stories.cy.ts +++ b/apps/storybook-e2e/src/e2e/elements/data-table/vl-data-table.stories.cy.ts @@ -5,7 +5,7 @@ const dataTableExpandableUrl = const dataTableExpandableWithCustomToggleUrl = 'http://localhost:8080/iframe.html?args=&id=elements-data-table--data-table-expandable-custom-toggle-details-column&viewMode=story'; -describe('story - vl-data-table - default', () => { +describe('story - vl-table - default', () => { it('should render', () => { cy.visit(dataTableDefaultUrl); @@ -13,7 +13,7 @@ describe('story - vl-data-table - default', () => { }); }); -describe('story - vl-data-table - expandable', () => { +describe('story - vl-table - expandable', () => { it('should render', () => { cy.visit(dataTableExpandableUrl); @@ -21,7 +21,7 @@ describe('story - vl-data-table - expandable', () => { }); }); -describe('story - vl-data-table - expandable with custom toggle', () => { +describe('story - vl-table - expandable with custom toggle', () => { it('should render', () => { cy.visit(dataTableExpandableWithCustomToggleUrl); diff --git a/apps/storybook/.storybook/vlux-meta-data/vlux-meta-data.json b/apps/storybook/.storybook/vlux-meta-data/vlux-meta-data.json index 56ff5c042..03536dbc2 100644 --- a/apps/storybook/.storybook/vlux-meta-data/vlux-meta-data.json +++ b/apps/storybook/.storybook/vlux-meta-data/vlux-meta-data.json @@ -49,10 +49,17 @@ "planningInfo": "[v1 naar v2 - beschikbaar](/docs/planning-2024-van-v1-naar-v2-beschikbaar--documentatie#checkbox)" }, "elements-data-table": { - "vStatus": "v1-todo", + "vStatus": "v1-replace", "legacyText": "vl-data-table", + "nextText": "[vl-table-next](/docs/components-next-table--documentatie)", "planningInfo": "[v1 naar v2 - planning](/docs/planning-2024-van-v1-naar-v2-planning--documentatie)" }, + "components-next-table": { + "vStatus": "v2-next", + "legacyText": "[vl-data-table](/docs/elements-data-table--documentatie)", + "nextText": "vl-table-next", + "planningInfo": "[v1 naar v2 - beschikbaar](/docs/planning-2024-van-v1-naar-v2-beschikbaar--documentatie#data-table)" + }, "components-datepicker": { "vStatus": "v1-replace", "legacyText": "vl-datepicker", diff --git a/apps/storybook/docs/d_planning/3_2024-van-v1-naar-v2-beschikbaar.stories.mdx b/apps/storybook/docs/d_planning/3_2024-van-v1-naar-v2-beschikbaar.stories.mdx index 93e7e5d28..2bad4bb81 100644 --- a/apps/storybook/docs/d_planning/3_2024-van-v1-naar-v2-beschikbaar.stories.mdx +++ b/apps/storybook/docs/d_planning/3_2024-van-v1-naar-v2-beschikbaar.stories.mdx @@ -137,6 +137,36 @@ De `Button [next]` vervangt de `Button [legacy]`, de `Icon Button [legacy]` en d +### Data Table + + + + + + + + + + + + + + + + + + + + + + `} + > + + +Dan zal er automatisch een `button` toegevoegd worden die de gebruiker toelaat de rij B te zien wanneer op de +desbetreffende knop bij rij A wordt gedrukt. + + + +### Colspan + +We berekenen automatisch de `colspan` van de rij die uitklapt, zodat de rij die uitklapt de volledige breedte van de tabel inneemt. +Dit doen we enkel als de rij die uitklapt een enkele cel bevat. Als de rij die uitklapt meerdere cellen bevat, moet je zelf de `colspan` instellen. + +### Expandable with custom toggle + +Je kan ook de knop die de rij open en dicht klapt zelf kiezen.
+Als je 2 rijen hebt, en je wil rij A altijd zichtbaar zetten en rij B verborgen tot die word opengeklapt:
+Om dit te doen, doe het volgende:
+ +- op de rij die meer details geeft op de voorgaande rij, moet de juiste id worden toegekend + + +
+ + `} +> + +- op rij die meer details heeft, een cel toevoegen die: + - `data-with-expand-details` heeft als attribuut + - een element naar keuze heeft, die `toggleDetails([id])` gaat aanroepen met de juiste id voor de openklapbare rij + bv.: + + + + + `} +> + +**_In dit voorbeeld vind je bij `Show code` broncode in `lit-html`-syntax. Voor code voorbeelden in HTML/JavaScript +verwijzen we naar hierboven._** + + + + + +## Referenties + +### Digitaal Vlaanderen + +We nemen de functionaliteit & styling over van het equivalente component van Digitaal Vlaanderen. + +[Documentatie Digitaal Vlaanderen - Data Table](https://overheid.vlaanderen.be/webuniversum/v3/documentation/components/vl-ui-data-table) diff --git a/libs/components/src/next/table/stories/vl-table.stories.ts b/libs/components/src/next/table/stories/vl-table.stories.ts new file mode 100644 index 000000000..eb5bf93fc --- /dev/null +++ b/libs/components/src/next/table/stories/vl-table.stories.ts @@ -0,0 +1,421 @@ +import { story } from '@domg-wc/common-storybook'; +import { Meta } from '@storybook/web-components'; +import { html } from 'lit-html'; +import { registerWebComponents } from '@domg-wc/common-utilities'; +import { VlTableComponent } from '../vl-table.component'; +import { tableArgTypes, tableArgs } from './vl-table.stories-arg'; +import tableDoc from './vl-table.stories-doc.mdx'; + +registerWebComponents([VlTableComponent]); + +export default { + id: 'components-next-table', + title: 'Components-next/table', + tags: ['autodocs'], + args: tableArgs, + argTypes: tableArgTypes, + + parameters: { + docs: { + page: tableDoc, + }, + }, +} as Meta; + +export const TableDefault = story( + tableArgs, + ({ hover, matrix, grid, zebra, uigZebra, collapsedM, collapsedS, collapsedXS }) => html` + +
NaamToestandv1 - Artifactv1 - Gebruikv2 - Artifactv2 - Gebruik
[Data Table [legacy]](/docs/elements-data-table--documentatie)deprecatedelements{``} + + + + + + + + + + + +
--
[Table [next]](/docs/components-next-table--documentatie)release v1.42.0components/next{``}components{``}
+ + ### Doormat diff --git a/libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx b/libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx index 2495ee764..bd645c9de 100644 --- a/libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx +++ b/libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx @@ -11,7 +11,7 @@ import * as VlSearchFilterStories from './vl-search-filter.stories'; {` In de v2 versie van deze component gebruik je hem via de custom-tag, de interne implementatie is voor de rest gelijk gebleven aan deze van de v1 versie. - In v3 zal deze component grondig herwerkt worden; in de context van een herwerking van de vl-data-table. + In v3 zal deze component grondig herwerkt worden; in de context van een herwerking van de vl-table. `} diff --git a/libs/components/src/next/table/stories/vl-table.stories-arg.ts b/libs/components/src/next/table/stories/vl-table.stories-arg.ts new file mode 100644 index 000000000..a3efe708a --- /dev/null +++ b/libs/components/src/next/table/stories/vl-table.stories-arg.ts @@ -0,0 +1,90 @@ +import { CATEGORIES, TYPES } from '@domg-wc/common-storybook'; +import { ArgTypes } from '@storybook/web-components'; +import { tableDefaults } from '../vl-table.defaults'; + +export const tableArgs = { + ...tableDefaults, +}; + +export const tableArgTypes: ArgTypes = { + hover: { + name: 'hover', + description: + 'Attribuut wordt gebruikt om een rij te highlighten wanneer de gebruiker erover hovert met muiscursor.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.hover }, + }, + }, + matrix: { + name: 'matrix', + description: + 'Attribuut wordt gebruikt om data in 2 dimensies te tonen. Zowel de rijen als de kolommen krijgen een titel. Deze titels worden gescheiden door een dikke lijn.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.matrix }, + }, + }, + grid: { + name: 'grid', + description: 'Variant met een lijn tussen elke rij en kolom.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.grid }, + }, + }, + zebra: { + name: 'zebra', + description: + 'Variant waarin de rijen afwisselend een andere achtergrondkleur krijgen. Dit maakt de tabel makkelijker leesbaar. ' + + 'Deze zebra werkt niet voor tabellen met detail rijen, gebruik hiervoor uig-zebra.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.zebra }, + }, + }, + uigZebra: { + name: 'uig-zebra', + description: + 'Variant waarin de rijen afwisselend een andere achtergrondkleur krijgen. Dit maakt de tabel makkelijker leesbaar. Deze zebra werkt voor tabellen met en zonder detail rijen.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.uigZebra }, + }, + }, + collapsedM: { + name: 'collapsed-m', + description: + 'Vanaf een medium schermgrootte zullen de cellen van elke rij onder elkaar ipv naast elkaar getoond worden.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.collapsedM }, + }, + }, + collapsedS: { + name: 'collapsed-s', + description: + 'Vanaf een small schermgrootte zullen de cellen van elke rij onder elkaar ipv naast elkaar getoond worden.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.collapsedS }, + }, + }, + collapsedXS: { + name: 'collapsed-xs', + description: + 'Vanaf een extra small schermgrootte zullen de cellen van elke rij onder elkaar ipv naast elkaar getoond worden.', + table: { + category: CATEGORIES.ATTRIBUTES, + type: { summary: TYPES.BOOLEAN }, + defaultValue: { summary: tableArgs.collapsedXS }, + }, + }, +}; diff --git a/libs/components/src/next/table/stories/vl-table.stories-doc.mdx b/libs/components/src/next/table/stories/vl-table.stories-doc.mdx new file mode 100644 index 000000000..0b48ef2ae --- /dev/null +++ b/libs/components/src/next/table/stories/vl-table.stories-doc.mdx @@ -0,0 +1,165 @@ +import { ArgTypes, Canvas, Source } from '@storybook/addon-docs'; +import * as VlTableStories from './vl-table.stories'; + +# Table - next + + + +Gebruik de `table` component om op een gestructureerde manier (grote hoeveelheden) relationele data te tonen. + + +{` + In de v2 versie van deze component gebruik je hem via de custom-tag, de interne implementatie is voor de rest gelijk gebleven aan deze van de v1 versie. + In v3 zal deze component grondig herwerkt worden; in de context van een herwerking van de vl-table. +`} + + +## Voorbeeld + +```js +import { VlTableComponent } from '@domg-wc/components/next/table'; +``` + +```html + +
+ + + + + + + + + + + + + + + + + +
+ Table +
Entry Header 1Entry Header 2
Entry line 1Entry line 2
Entry line 1Entry line 2
+ +``` + +## Default + + + +## Configuratie + + + +## Functionaliteit + +Standaard is er geen sorteer, filtering of paginatie functionaliteit beschikbaar voor de `table`. Hiervoor is de +[rich-table](?path=/docs/components-rich-table--rich-table-default) beschikbaar.
+Wat dit component wel méér heeft dan die van Digitaal Vlaanderen zijn expandable/collapsible rows. Zie de specifieke +stories hieronder.
+De visuele mark-up volgt standaard die van Digitaal Vlaanderen, inclusief responsiveness. + + +## Lege cellen + +Het is aan te raden om in lege cellen de waarde ` ` te plaatsen, dit zorgt ervoor dat rijen hun hoogte behouden +indien elke cel van een rij leeg is. + + +## Joined row titles + +Gebruik de matrix-variant om gegevens met 2 dimensies weer te geven. Zowel de rijen als de kolommen krijgen een titel. +De titels zijn gescheiden van de inhoud met een vetgedrukte lijn.
+Om dit toe passen maak je zelf gebruik van native html-attribuut `rowspan`. + + + + +## Expandable + +Om een rijen te laten uitklappen ("expanden") moet je het volgende doen:
+Als je 2 rijen hebt, en je wil rij A altijd zichtbaar zetten en rij B verborgen tot die wordt opengeklapt: + +- maak een nieuwe rij B direct na rij A +- zet je het attribuut `data-details-id` op de rij B +- je kan ook meerdere rijen uitklapbaar maken, zolang je maar dezelfde `data-details-id` hergebruikt + +
+ voorbeeld expandable row + +
1234
details on previous row
Details 1
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Meise Botanic Garden herbarium collections +
NameCollectorCollector numberFamily
Didymium clavusThomas H. & Marie L. Farr110Didymiaceae
Critchfield R.L.715Didymiaceae
Rammeloo J.4572Didymiaceae
Epilobium angustifoliumFranz Heylemans160Onagraceae
Stam A.B.477Onagraceae
Van Hoeck Eddy42Onagraceae
Euphorbia scordifoliaMission O. Olufsen125Euphorbiaceae
Brunel J.F.7603Euphorbiaceae
Bamps P.7549Euphorbiaceae
HemitrichiaMadame F. Meyer198Trichiaceae
Johannesen E.W.50BTrichiaceae
Rammeloo J.9438Trichiaceae
+
Table annotation
+
+ + ` +); +TableDefault.storyName = 'vl-table-next - default'; + +export const TableJoinedRowTitles = story( + tableArgs, + ({ hover, matrix, grid, zebra, uigZebra, collapsedM, collapsedS, collapsedXS }) => html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table Matrix - Joined row titles +
Horizontal title 1Thomas H. & Marie L. Farr110Didymiaceae
Critchfield R.L.715Didymiaceae
Rammeloo J.4572Didymiaceae
Horizontal title 2Franz Heylemans160Onagraceae
Stam A.B.477Onagraceae
Van Hoeck Eddy42Onagraceae
+ ` +); +TableJoinedRowTitles.storyName = 'vl-table-next - joined row titles'; + +export const TableExpandable = story( + tableArgs, + ({ hover, matrix, grid, zebra, uigZebra, collapsedM, collapsedS, collapsedXS }: typeof tableArgs) => { + return html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table with expandable details +
Entry Header 1Entry Header 2Entry Header 3Entry Header 4
Entry line 1Entry line 2Entry line 3Entry line 4
Title 1: generic details
Entry line 1Entry line 2Entry line 3
Title 2: generic details
Entry line 1Entry line 2Entry line 3Entry line 4
Al die willen te kaap'ren varen:***
Janfamilienaamtelefoonadres
Pietfamilienaamtelefoonadres
Jorisfamilienaamtelefoonadres
Korneelfamilienaamtelefoonadres
+
+ `; + } +); +TableExpandable.storyName = 'vl-table-next - expandable'; + +export const TableExpandableCustomToggleDetailsColumn = story( + tableArgs, + ({ hover, matrix, grid, zebra, uigZebra, collapsedM, collapsedS, collapsedXS }) => { + let table: (VlTableComponent & Element) | null; + customElements.whenDefined('vl-data-table').then(() => { + table = document.querySelector('#vl-table-with-custom-expandable-details'); + }); + return html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table +
Entry Header 1Entry line 2Entry Header 3Entry Header 4
Entry line 1Entry line 2Entry line 3Entry line 4 + +
+
+
    +
  • Extra Details 1
  • +
  • Extra Details 1
  • +
  • Extra Details 1
  • +
+
+
Entry line 1Entry line 2Entry line 3
+
+
    +
  • Extra Details 2
  • +
  • Extra Details 2
  • +
  • Extra Details 2
  • +
+
+
Entry line 1Entry line 2Entry line 3Entry line 4
Janfamilienaamtelefoonadres
Pietfamilienaamtelefoonadres
Jorisfamilienaamtelefoonadres
Korneelfamilienaamtelefoonadres
+
+ `; + } +); +TableExpandableCustomToggleDetailsColumn.storyName = 'vl-table-next - expandable custom toggle details column'; +TableExpandableCustomToggleDetailsColumn.parameters = { + docs: { + language: 'html', + source: { + format: true, + type: 'code', + }, + }, +}; diff --git a/libs/components/src/next/table/vl-table.component.cy.ts b/libs/components/src/next/table/vl-table.component.cy.ts new file mode 100644 index 000000000..31caf26ef --- /dev/null +++ b/libs/components/src/next/table/vl-table.component.cy.ts @@ -0,0 +1,626 @@ +import { html } from 'lit'; +import { registerWebComponents } from '@domg-wc/common-utilities'; +import { VlTableComponent } from './vl-table.component'; +import { tableDefaults } from './vl-table.defaults'; + +registerWebComponents([VlTableComponent]); + +describe('component - vl-table-next', () => { + it('should mount', () => { + mountDefault({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + }); + + it('should be accessible', () => { + mountDefault({}); + cy.injectAxe(); + + cy.checkA11y('vl-table-next'); + }); + + it('should contain a table with headers', () => { + mountDefault({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + cy.get('vl-table-next') + .find('table') + .find('thead > tr') + .children() + .each((cell, index) => { + cy.wrap(cell).should('have.text', 'Entry Header ' + (index + 1)); + }); + }); + + it('should contain a table with columns', () => { + mountDefault({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + cy.get('vl-table-next') + .find('table') + .find('tbody') + .children() + .each((row) => { + if (!row.attr('data-details-id')) { + cy.wrap(row) + .children() + .each((cell, cellIndex) => { + if (!cell.children('vl-button-next').length) { + cy.wrap(cell).should('contain.text', 'Entry line ' + (cellIndex + 1)); + } + }); + } + }); + }); + + it('should contain a table with hover styling', () => { + mountDefault({ hover: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--hover'); + }); + + it('should contain a table with a matrix', () => { + mountDefault({ matrix: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--matrix'); + }); + + it('should contain a table with a grid', () => { + mountDefault({ grid: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--grid'); + }); + + it('should contain a table with a zebra grid', () => { + mountDefault({ zebra: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--zebra'); + }); + + it('should contain a table that collapsed on the medium breakpoint', () => { + mountDefault({ collapsedM: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--collapsed-m'); + }); + + it('should contain a table that collapsed on the small breakpoint', () => { + mountDefault({ collapsedS: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--collapsed-s'); + }); + + it('should contain a table that collapsed on the extra small breakpoint', () => { + mountDefault({ collapsedXS: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--collapsed-xs'); + }); +}); + +const mountExpandable = ({ + hover, + matrix, + grid, + zebra, + uigZebra, + collapsedM, + collapsedS, + collapsedXS, +}: Partial) => + cy.mount(html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Data table +
Entry Header 1Entry Header 2Entry Header 3Entry Header 4
Entry line 1Entry line 2Entry line 3Entry line 4
Title 1: generic details
Entry line 1Entry line 2Entry line 3
Title 2: generic details
Entry line 1Entry line 2Entry line 3Entry line 4
Title 3: Zij die ter kaperen varen:***
Janfamilienaamtelefoonadres
Pietfamilienaamtelefoonadres
Jorisfamilienaamtelefoonadres
Korneelfamilienaamtelefoonadres
+
+ `); + +const shouldDetailsRowBeVisible = (isVisible: boolean, detailRowIndex = 0) => { + const haveStyle = !isVisible ? 'have.css' : 'not.have.css'; + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .find('tbody') + .find('[data-details-id]') + .eq(detailRowIndex) + .should(haveStyle, 'display', 'none'); +}; +const toggleDetailsButton = () => { + cy.get('vl-table-next') + .find('table') + .find('tbody > tr') + .first() + .find('td') + .last() + .find('vl-button-next') + .shadow() + .find('button') + .click({ force: true }); +}; + +describe('component - vl-table-next - expandable', () => { + it('should mount', () => { + mountExpandable({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + }); + + it('should be accessible', () => { + mountExpandable({}); + cy.injectAxe(); + + cy.checkA11y('vl-table-next'); + }); + + it('should contain a table with headers', () => { + mountExpandable({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + cy.get('vl-table-next') + .find('table') + .find('thead > tr') + .children() + .each((cell, index) => { + cy.wrap(cell).should('have.text', 'Entry Header ' + (index + 1)); + }); + }); + + it('should contain a table with columns', () => { + mountExpandable({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + cy.get('vl-table-next') + .find('table') + .find('tbody > tr:not([data-details-id])') + .each((row, rowIndex) => { + cy.wrap(row) + .children() + .each((cell, cellIndex) => { + if (!cell.children('vl-button-next').length) { + cy.wrap(cell).should('contain.text', 'Entry line ' + (cellIndex + 1)); + } else { + cy.wrap(cell).find('vl-button-next'); + } + }); + cy.wrap(row) + .next() + .find('td') + .should('contain.text', 'Title ' + (rowIndex + 1)); + }); + }); + + it('should contain a table with hover styling', () => { + mountExpandable({ hover: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--hover'); + }); + + it('should contain a table with a matrix', () => { + mountExpandable({ matrix: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--matrix'); + }); + + it('should contain a table with a grid', () => { + mountExpandable({ grid: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--grid'); + }); + + it('should contain a table with a zebra grid', () => { + mountExpandable({ zebra: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--zebra'); + }); + + it('should set colspan for expandable row with one cell', () => { + mountExpandable({}); + + cy.get('vl-table-next') + .find('table') + .find('tbody > tr') + .first() + .find('td') + .last() + .find('vl-button-next') + .click(); + cy.get('vl-table-next') + .find('table') + .find('tbody > tr') + .first() + .next() + .find('td') + .should('have.attr', 'colspan', '5'); + }); + + it('should not set colspan for expandable row with multiple cells', () => { + mountExpandable({}); + + cy.get('vl-table-next') + .find('table') + .find('tbody > tr#multiple-cells') + .first() + .find('td') + .last() + .find('vl-button-next') + .click(); + cy.get('vl-table-next') + .find('table') + .find('tbody > tr#multiple-cells') + .first() + .next() + .find('td') + .should('not.have.attr', 'colspan'); + }); + + it('should expand all relevant detail rows when clicking the expand button', () => { + mountExpandable({}); + + cy.get('vl-table-next') + .find('table') + .find('tbody > tr[data-details-id="details-row3"]') + .should('have.css', 'display', 'none'); + cy.get('vl-table-next') + .find('table') + .find('tbody > tr#multiple-cells') + .first() + .find('td') + .last() + .find('vl-button-next') + .click(); + cy.get('vl-table-next') + .find('table') + .find('tbody > tr[data-details-id="details-row3"]') + .should('not.have.css', 'display', 'none'); + }); + + it('should contain a table that collapsed on the medium breakpoint', () => { + mountExpandable({ collapsedM: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--collapsed-m'); + }); + + it('should contain a table that collapsed on the small breakpoint', () => { + mountExpandable({ collapsedS: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--collapsed-s'); + }); + + it('should contain a table that collapsed on the extra small breakpoint', () => { + mountExpandable({ collapsedXS: true }); + + cy.get('vl-table-next') + .find('table') + .should('have.class', 'vl-table-next') + .should('have.class', 'vl-table-next--collapsed-xs'); + }); + + it('should toggle expanding a detail row when clicking the button', () => { + mountExpandable({}); + cy.injectAxe(); + + shouldDetailsRowBeVisible(false); + toggleDetailsButton(); + cy.checkA11y('vl-table-next'); + shouldDetailsRowBeVisible(true); + toggleDetailsButton(); + cy.checkA11y('vl-table-next'); + shouldDetailsRowBeVisible(false); + }); +}); + +const mountExpandableCustom = ({ + hover, + matrix, + grid, + zebra, + uigZebra, + collapsedM, + collapsedS, + collapsedXS, +}: Partial) => + cy.mount(html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Data table +
Entry Header 1Entry line 2Entry Header 3Entry Header 4
Entry line 1Entry line 2Entry line 3Entry line 4 + { + const table = document.querySelector( + '#vl-table-with-custom-expandable-details' + ); + table?.toggleDetails('details-row1'); + }} + > + click to toggle details + +
+
+
    +
  • Extra Details 1
  • +
  • Extra Details 1
  • +
  • Extra Details 1
  • +
+
+
Entry line 1Entry line 2Entry line 3
+
+
    +
  • Extra Details 2
  • +
  • Extra Details 2
  • +
  • Extra Details 2
  • +
+
+
Entry line 1Entry line 2Entry line 3Entry line 4
Janfamilienaamtelefoonadres
Pietfamilienaamtelefoonadres
Jorisfamilienaamtelefoonadres
Korneelfamilienaamtelefoonadres
+
+ `); + +describe('component - vl-table-next - expandable with custom button', () => { + it('should mount', () => { + mountExpandableCustom({}); + + cy.get('vl-table-next').find('table').should('have.class', 'vl-table-next'); + }); + + it('should be accessible', () => { + mountExpandableCustom({}); + cy.injectAxe(); + + cy.checkA11y('vl-table-next'); + }); + + it('should toggle expanding a detail row when clicking the button', () => { + mountExpandableCustom({}); + cy.injectAxe(); + + shouldDetailsRowBeVisible(false); + toggleDetailsButton(); + cy.checkA11y('vl-table-next'); + shouldDetailsRowBeVisible(true); + + toggleDetailsButton(); + cy.checkA11y('vl-table-next'); + shouldDetailsRowBeVisible(false); + }); +}); + +const mountDefault = ({ + hover, + matrix, + grid, + zebra, + uigZebra, + collapsedM, + collapsedS, + collapsedXS, +}: Partial) => + cy.mount(html` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Data table +
Entry Header 1Entry Header 2Entry Header 3Entry Header 4
Entry line 1Entry line 2Entry line 3Entry line 4
Entry line 1Entry line 2Entry line 3
Entry line 1Entry line 2Entry line 3Entry line 4
+
+ `); diff --git a/libs/components/src/next/table/vl-table.component.ts b/libs/components/src/next/table/vl-table.component.ts new file mode 100644 index 000000000..f7c2d3f22 --- /dev/null +++ b/libs/components/src/next/table/vl-table.component.ts @@ -0,0 +1,211 @@ +import { registerWebComponents, webComponent } from '@domg-wc/common-utilities'; +import { LitElement, PropertyDeclarations, PropertyValues } from 'lit'; +import { VlButtonComponent } from '../button'; +import { tableStyles } from './vl-table.css'; + +registerWebComponents([VlButtonComponent]); + +@webComponent('vl-table-next') +export class VlTableComponent extends LitElement { + private _observer: MutationObserver | undefined; + + static get properties(): PropertyDeclarations { + return { + hover: { type: Boolean, reflect: true }, + matrix: { type: Boolean, reflect: true }, + grid: { type: Boolean, reflect: true }, + zebra: { type: Boolean, reflect: true }, + uigZebra: { type: Boolean, reflect: true, attribute: 'uig-zebra' }, + collapsedM: { type: Boolean, reflect: true, attribute: 'collapsed-m' }, + collapsedS: { type: Boolean, reflect: true, attribute: 'collapsed-s' }, + collapsedXs: { type: Boolean, reflect: true, attribute: 'collapsed-xs' }, + }; + } + + constructor() { + super(); + this.table?.classList.add('vl-table-next'); + } + + protected createRenderRoot(): HTMLElement | DocumentFragment { + return this; + } + + connectedCallback() { + super.connectedCallback(); + this._processScopeAttributes(); + this._processRowElements(); + this._observer = this._observeHeaderElements(() => this._processScopeAttributes()); + } + + disconnectedCallback() { + super.disconnectedCallback(); + + this._observer?.disconnect(); + } + + protected firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + + // Deze stylesheet moet toegevoegd worden aan de adoptedStyleSheets van het document + // omdat deze styles betrekking hebben op de slotted content en dus niet op de shadow dom + document.adoptedStyleSheets = [...document.adoptedStyleSheets, tableStyles.styleSheet as CSSStyleSheet]; + + this.caption?.classList.add('vl-table-next__caption'); + } + + updated(changedProperties: Map) { + super.updated(changedProperties); + + if (!this.table) return; + + // Map van properties naar class namen + const classMap: { [key: string]: string } = { + hover: 'vl-table-next--hover', + matrix: 'vl-table-next--matrix', + grid: 'vl-table-next--grid', + zebra: 'vl-table-next--zebra', + uigZebra: 'vl-table-next--uig-zebra', + collapsedM: 'vl-table-next--collapsed-m', + collapsedS: 'vl-table-next--collapsed-s', + collapsedXs: 'vl-table-next--collapsed-xs', + }; + + // voeg of verwijder klassen op basis van property waarden + Object.entries(classMap).forEach(([property, className]) => { + if (this[property as keyof this]) { + this.table!.classList.add(className); + } else { + this.table!.classList.remove(className); + } + }); + } + + private get table() { + return this.querySelector('table'); + } + + private get caption() { + return this.querySelector('caption'); + } + + private get _headHeaderElements(): HTMLTableCellElement[] { + return Array.from(this.querySelectorAll('thead tr th')); + } + + private get _bodyHeaderElements(): HTMLTableCellElement[] { + return Array.from(this.querySelectorAll('tbody tr th')); + } + + private get _bodyRowElements(): HTMLTableRowElement[] { + return Array.from(this.querySelectorAll('tbody tr')); + } + + private _detailsToggleButtonElement(id: string): HTMLButtonElement | null { + return this.querySelector(`tbody tr td vl-button-next[id="details-toggle-${id}"]`); + } + + private _detailsTableRowElements(id: string): NodeListOf | null | undefined { + return this.table?.querySelectorAll(`tbody tr[data-details-id="${id}"]`); + } + + private _processScopeAttributes() { + this._headHeaderElements + .filter((header) => !header.hasAttribute('scope')) + .forEach((header) => header.setAttribute('scope', 'col')); + this._bodyHeaderElements + .filter((header) => !header.hasAttribute('scope')) + .forEach((header) => header.setAttribute('scope', 'row')); + } + + private _expandCollapseTemplate(id: string) { + const button = document.createElement('vl-button-next'); + button.id = `details-toggle-${id}`; + button.setAttribute('type', 'button'); + button.setAttribute('narrow', ''); + button.setAttribute('secondary', ''); + button.setAttribute('icon', 'arrow-down-fat'); + button.setAttribute('icon-only', ''); + + button.addEventListener('vl-click', (e: Event) => { + e.preventDefault(); + this.toggleDetails(id); + }); + + return button; + } + + collapseDetails(id: string) { + this._showDetails(id, false); + } + + expandDetails(id: string) { + this._showDetails(id, true); + } + + private _showDetails(id: string, show: boolean) { + const details = this._detailsTableRowElements(id); + const vlButton = this._detailsToggleButtonElement(id); + if (show) { + details?.forEach((detail) => detail.style.removeProperty('display')); + vlButton?.shadowRoot?.querySelector('button')?.setAttribute('aria-expanded', 'true'); + vlButton?.setAttribute('icon', 'arrow-up-fat'); + } else { + details?.forEach((detail) => (detail.style.display = 'none')); + vlButton?.shadowRoot?.querySelector('button')?.setAttribute('aria-expanded', 'false'); + vlButton?.setAttribute('icon', 'arrow-down-fat'); + } + } + + private _processRowElements(): void { + const rows = this._bodyRowElements; + let dataRowIndex = 0; + + rows.forEach((row, i) => { + const isDataRow = (rowValue: HTMLTableRowElement) => !rowValue.hasAttribute('data-details-id'); + if (isDataRow(row)) { + dataRowIndex++; + } else { + const id = row.getAttribute('data-details-id'); + row.style.display = 'none'; + + const dataRow = rows[i - 1]; + if (dataRow.querySelectorAll('td[data-with-expand-details]').length === 0 && id && isDataRow(dataRow)) { + const cell = document.createElement('td'); + const vlButton = this._expandCollapseTemplate(id); + cell.appendChild(vlButton); + dataRow.appendChild(cell); + + vlButton.updateComplete.then(() => { + vlButton.shadowRoot?.querySelector('button')?.setAttribute('aria-expanded', 'false'); + vlButton.shadowRoot + ?.querySelector('button') + ?.setAttribute('aria-label', 'toggle details for ' + id); + }); + } + + const detailsCellCount = row?.querySelectorAll('td')?.length; + if (detailsCellCount === 1) { + const dataCellCount = dataRow.querySelectorAll('td').length; + const detailsCell = row.querySelector('td'); + if (detailsCell) detailsCell.colSpan = dataCellCount; + } + } + + row.classList.add(dataRowIndex % 2 === 0 ? 'even' : 'odd'); + }); + } + + private _observeHeaderElements(callback: MutationCallback): MutationObserver { + const observer = new MutationObserver(callback); + observer.observe(this, { childList: true }); + return observer; + } + + toggleDetails(id: string) { + const details = this._detailsTableRowElements(id); + const detailsVisible = details ? details[0].style.display !== 'none' : false; + details?.forEach((detail) => (detail.style.display = detailsVisible ? 'none' : 'table-row')); + this._showDetails(id, !detailsVisible); + } +} diff --git a/libs/components/src/next/table/vl-table.css.ts b/libs/components/src/next/table/vl-table.css.ts new file mode 100644 index 000000000..0be8ab46c --- /dev/null +++ b/libs/components/src/next/table/vl-table.css.ts @@ -0,0 +1,498 @@ +import { css } from 'lit'; + +export const tableStyles = css` + .vl-table-next caption { + color: #687483; + caption-side: bottom; + text-align: left; + margin: 15px 0; + font-size: 1.6rem; + } + + .vl-table-next--uig-zebra + tbody + tr:not(.vl-table-next__element--warning):not(.vl-table-next__element--error):not( + .vl-table-next__element--success + ).odd { + background-color: #f3f5f6; + } + + .vl-table-next--uig-zebra + tbody + tr:not(.vl-table-next__element--warning):not(.vl-table-next__element--error):not( + .vl-table-next__element--success + ).odd:hover { + background-color: #edf0f2; + } + + .vl-table-next tbody tr.vl-table-next__element--disabled, + .vl-table-next tbody td.vl-table-next__element--disabled { + background: #cbd2d9; + color: var(--vl-theme-fg-color-70); + } + + /* from assets */ + + .vl-table-next { + width: 100%; + max-width: 100%; + } + .vl-table-next caption { + text-align: left; + margin: 15px 0 5px 0; + font-size: 1.8rem; + font-weight: 500; + } + .vl-table-next thead tr { + border-bottom: 0.2rem #cbd2da solid; + } + .vl-table-next tfoot tr { + border-top: 0.2rem #cbd2da solid; + } + .vl-table-next tfoot td { + font-weight: 500; + white-space: nowrap; + } + .vl-table-next tfoot td:first-child { + padding-left: 0; + } + .vl-table-next tfoot td:last-child { + padding-right: 0; + } + .vl-table-next tbody tr.vl-table-next__element--error, + .vl-table-next tbody td.vl-table-next__element--error { + background: #fbebec; + border-bottom: 1px solid #d2373c; + } + .vl-table-next tbody tr.vl-table-next__element--warning, + .vl-table-next tbody td.vl-table-next__element--warning { + background: #fff6e7; + border-bottom: 1px solid #ffa10a; + } + .vl-table-next tbody tr.vl-table-next__element--success, + .vl-table-next tbody td.vl-table-next__element--success { + background: #e6f5ed; + border-bottom: 1px solid #009e47; + } + .vl-table-next tbody tr { + border-bottom: 0.1rem #cbd2da solid; + } + .vl-table-next tbody tr[data-vl-table-selectable] { + cursor: pointer; + transition: background 0.2s ease-in-out; + } + .vl-table-next tbody tr[data-vl-table-selectable]:hover { + background: #f3f5f6; + } + .vl-table-next tbody tr.vl-table-next__grouped-row:not(.vl-table-next__grouped-row--last) { + border-bottom: 0; + } + .vl-table-next tbody tr th:first-child { + border-right: 0.2rem #cbd2da solid; + } + .vl-table-next td, + .vl-table-next th { + font-size: 1.6rem; + line-height: 1.3; + text-align: left; + vertical-align: top; + padding: 1.2rem 1rem; + } + @media screen and (max-width: 767px) { + .vl-table-next td, + .vl-table-next th { + font-size: 1.4rem; + padding: 1rem; + } + } + .vl-table-next td:first-child, + .vl-table-next th:first-child { + border-left: 0; + } + .vl-table-next td.vl-table-next__icon-container, + .vl-table-next th.vl-table-next__icon-container { + background-color: #f3f5f6; + color: #333332; + } + .vl-table-next td.vl-table-next__icon-container .vl-vi, + .vl-table-next th.vl-table-next__icon-container .vl-vi { + color: #4d4d4b; + font-size: 3rem; + } + .vl-vi .vl-table-next td.vl-table-next__icon-container, + .vl-vi .vl-table-next th.vl-table-next__icon-container { + text-align: center; + } + .vl-table-next th { + font-weight: 500; + white-space: nowrap; + } + .vl-table-next th > * { + white-space: normal; + } + .vl-table-next .vl-table-next__grouped-row td { + padding: 0.3rem 1rem 0.3rem 0; + } + @media screen and (max-width: 767px) { + .vl-table-next .vl-table-next__grouped-row td { + padding: 0.3rem 1rem 0.3rem 0; + } + } + .vl-table-next .vl-table-next__grouped-row--first td { + padding-top: 1.2rem; + } + @media screen and (max-width: 767px) { + .vl-table-next .vl-table-next__grouped-row--first td { + padding-top: 1rem; + } + } + .vl-table-next .vl-table-next__grouped-row--last td { + padding-bottom: 1.2rem; + } + @media screen and (max-width: 767px) { + .vl-table-next .vl-table-next__grouped-row--last td { + padding-bottom: 1rem; + } + } + .vl-table-next__header-title--sortable { + text-decoration: none; + } + .vl-table-next__header-title--sortable .vl-table-next__header-title__sort-icon { + opacity: 0; + } + .vl-table-next__header-title--sortable:hover, + .vl-table-next__header-title--sortable:focus { + text-decoration: underline; + } + .vl-table-next__header-title--sortable:hover .vl-table-next__header-title__sort-icon, + .vl-table-next__header-title--sortable:focus .vl-table-next__header-title__sort-icon { + opacity: 0.5; + } + .vl-table-next__header-title--sortable-active .vl-table-next__header-title__sort-icon { + opacity: 1; + } + .vl-table-next__body-title { + max-width: 30rem; + } + .vl-table-next--alt tr th:first-child, + .vl-table-next--alt tr td:first-child { + border-right: 0.1rem #cbd2da solid; + } + .vl-table-next--alt tr th:not(:first-child) { + padding: 0 1.2rem 1.2rem; + } + .vl-table-next--alt tr td:not(:first-child) { + padding: 1.2rem; + } + .vl-table-next--double-spacing tr td, + .vl-table-next--double-spacing tr th { + padding: 1.2rem 3rem; + } + .vl-table-next--no-header tbody tr:first-child { + border-top: 3px #cbd2da solid; + } + .vl-table-next .vl-pill { + vertical-align: middle; + } + @media screen and (max-width: 767px) { + .vl-table-next .vl-pill { + font-size: 1.4rem; + height: 2rem; + line-height: 2rem; + padding: 0 0.5rem; + } + } + + .vl-u-table-overflow { + width: 100%; + overflow-x: auto; + } + .vl-u-table-overflow .vl-table-next { + overflow: auto; + } + + .no-js [data-vl-table-check-all] + span { + display: none !important; + } + + .vl-table-next--hover tbody tr { + transition: background 0.2s ease-in-out; + } + .vl-table-next--hover tbody tr:hover { + background: #f3f5f6; + } + + .vl-table-next--matrix tr th:first-child { + border-right: 0.2rem #cbd2da solid; + } + + .vl-table-next--grid td, + .vl-table-next--grid th { + padding: 1.2rem; + } + .vl-table-next--grid td:not(:first-child), + .vl-table-next--grid th:not(:first-child) { + border-left: 0.1rem solid #cbd2da; + } + + .vl-table-next--zebra + tbody + tr:not(.vl-table-next__element--warning):not(.vl-table-next__element--error):not( + .vl-table-next__element--success + ):nth-child(odd) { + background-color: #f3f5f6; + } + .vl-table-next--zebra + tbody + tr:not(.vl-table-next__element--warning):not(.vl-table-next__element--error):not( + .vl-table-next__element--success + ):nth-child(odd):hover { + background-color: #edf0f2; + } + + .vl-table-next__actions--top { + margin: 0 0 2rem; + } + .vl-table-next__actions--bottom { + margin: 2rem 0 0; + } + @media screen and (max-width: 500px) { + .vl-table-next__actions--bottom .vl-table-next__actions__list { + margin: 0 0 1rem; + } + } + .vl-table-next__actions__list .vl-table-next__action:not(:last-child) { + margin-right: 0.6rem; + } + .vl-table-next__action { + display: inline-block; + vertical-align: middle; + margin-bottom: 0.6rem; + } + .vl-table-next__action__toggle { + display: flex; + align-items: center; + background: none; + font-family: 'Flanders Art Sans', sans-serif; + font-size: 1.6rem; + font-weight: 400; + line-height: 1; + color: #333332; + border: 0.1rem #cbd2da solid; + text-decoration: none; + text-align: left; + cursor: pointer; + padding: 0.9rem 1.5rem 0.8rem; + } + @media screen and (max-width: 767px) { + .vl-table-next__action__toggle { + font-size: 1.5rem; + } + } + [data-vl-disable='true'] .vl-table-next__action__toggle { + color: #687483; + cursor: default; + } + .vl-table-next__action__toggle__icon { + font-size: 1.2rem; + } + .vl-table-next__action__toggle__icon:first-child { + margin-right: 0.5rem; + } + .vl-table-next__action__toggle__icon:last-child { + margin-left: 0.5rem; + } + @media screen and (max-width: 500px) { + .vl-table-next__action__toggle--contract-xs span { + display: none; + } + .vl-table-next__action__toggle--contract-xs .vl-vi::before { + margin: 0; + } + } + + @media screen and (max-width: 500px) { + .vl-table-next--collapsed-xs { + position: relative; + } + .vl-table-next--collapsed-xs table, + .vl-table-next--collapsed-xs thead, + .vl-table-next--collapsed-xs tbody, + .vl-table-next--collapsed-xs th, + .vl-table-next--collapsed-xs td, + .vl-table-next--collapsed-xs tr { + display: block; + } + .vl-table-next--collapsed-xs thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + .vl-table-next--collapsed-xs td { + border-bottom: 1px solid #cbd2da; + position: relative; + padding-left: 40%; + white-space: normal; + text-align: left; + } + .vl-table-next--collapsed-xs td::before, + .vl-table-next--collapsed-xs th::before { + content: attr(data-title); + position: absolute; + top: 10px; + left: 10px; + width: 35%; + padding-right: 10px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + text-align: left; + font-weight: 500; + } + } + @media screen and (max-width: 767px) { + .vl-table-next--collapsed-s { + position: relative; + } + .vl-table-next--collapsed-s table, + .vl-table-next--collapsed-s thead, + .vl-table-next--collapsed-s tbody, + .vl-table-next--collapsed-s th, + .vl-table-next--collapsed-s td, + .vl-table-next--collapsed-s tr { + display: block; + } + .vl-table-next--collapsed-s thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + .vl-table-next--collapsed-s td { + border-bottom: 1px solid #cbd2da; + position: relative; + padding-left: 40%; + white-space: normal; + text-align: left; + } + .vl-table-next--collapsed-s td::before, + .vl-table-next--collapsed-s th::before { + content: attr(data-title); + position: absolute; + top: 10px; + left: 10px; + width: 35%; + padding-right: 10px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + text-align: left; + font-weight: 500; + } + } + @media screen and (max-width: 1023px) { + .vl-table-next--collapsed-m { + position: relative; + } + .vl-table-next--collapsed-m table, + .vl-table-next--collapsed-m thead, + .vl-table-next--collapsed-m tbody, + .vl-table-next--collapsed-m th, + .vl-table-next--collapsed-m td, + .vl-table-next--collapsed-m tr { + display: block; + } + .vl-table-next--collapsed-m thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + .vl-table-next--collapsed-m td { + border-bottom: 1px solid #cbd2da; + position: relative; + padding-left: 40%; + white-space: normal; + text-align: left; + } + .vl-table-next--collapsed-m td::before, + .vl-table-next--collapsed-m th::before { + content: attr(data-title); + position: absolute; + top: 10px; + left: 10px; + width: 35%; + padding-right: 10px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + text-align: left; + font-weight: 500; + } + } + + .vl-table-next--sticky { + table-layout: auto; + } + .vl-table-next--sticky .vl-table-next__cell--sticky { + position: sticky; + left: 0; + background-color: inherit; + border: 0; + z-index: 1; + } + .vl-table-next--sticky thead .vl-table-next__cell--sticky { + top: 0; + z-index: 2; + } + .vl-table-next--sticky thead .vl-table-next__cell--sticky::before { + display: block; + position: absolute; + bottom: -3px; + left: 0; + right: 0; + height: 3px; + background-color: #cbd2da; + content: ''; + } + .vl-table-next--sticky tbody .vl-table-next__cell--sticky::before { + display: block; + position: absolute; + bottom: 0; + top: 0; + right: -1px; + width: 1px; + background-color: #cbd2da; + content: ''; + } + .vl-table-next--sticky th, + .vl-table-next--sticky td { + min-width: 200px; + } + .vl-table-next--sticky tr { + background-color: #fff; + } + + .vl-table-next__sticky-wrapper { + width: 100%; + max-height: 75vh; + overflow-x: auto; + overflow-y: auto; + } + + .vl-u-table-overflow { + background: linear-gradient(to right, white, white, rgba(255, 255, 255, 0) 30px), + radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0)), + linear-gradient(to left, white, white, rgba(255, 255, 255, 0) 30px), + radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.3), rgba(255, 255, 255, 0)) 100%; + background-color: white; + background-repeat: no-repeat; + background-attachment: local, scroll, local, scroll; + background-size: 100% 100%, 15px 100%, 100% 100%, 15px 100%; + } + + .vl-table-next__navigation { + position: relative; + z-index: 1; + display: flex; + justify-content: flex-end; + } +`; diff --git a/libs/components/src/next/table/vl-table.defaults.ts b/libs/components/src/next/table/vl-table.defaults.ts new file mode 100644 index 000000000..4d8b1dfd5 --- /dev/null +++ b/libs/components/src/next/table/vl-table.defaults.ts @@ -0,0 +1,10 @@ +export const tableDefaults = { + hover: false as boolean, + matrix: false as boolean, + grid: false as boolean, + zebra: false as boolean, + uigZebra: false as boolean, + collapsedM: false as boolean, + collapsedS: false as boolean, + collapsedXS: false as boolean, +}; diff --git a/libs/elements/src/data-table/stories/vl-data-table.stories-doc.mdx b/libs/elements/src/data-table/stories/vl-data-table.stories-doc.mdx index 2a36e7fe6..7896b2293 100644 --- a/libs/elements/src/data-table/stories/vl-data-table.stories-doc.mdx +++ b/libs/elements/src/data-table/stories/vl-data-table.stories-doc.mdx @@ -11,7 +11,7 @@ Gebruik de `data-table` component om op een gestructureerde manier (grote hoevee ## Voorbeeld ```js -import { VlDataTable } from '@domg-wc/components'; +import { VlDataTable } from '@domg-wc/elements'; ``` ```html @@ -43,6 +43,9 @@ import { VlDataTable } from '@domg-wc/components'; +## Configuratie + + ## Functionaliteit @@ -145,11 +148,6 @@ verwijzen we naar hierboven._** -## Configuratie - - - - ## Referenties ### Digitaal Vlaanderen diff --git a/resources/generate-web-types/wt-config-build/components.wt-config.ts b/resources/generate-web-types/wt-config-build/components.wt-config.ts index b3d98d5ae..862668324 100644 --- a/resources/generate-web-types/wt-config-build/components.wt-config.ts +++ b/resources/generate-web-types/wt-config-build/components.wt-config.ts @@ -1,6 +1,7 @@ import { buttonArgTypes } from '@domg-wc/components/next/button/stories/vl-button.stories-arg'; import { cascaderItemArgTypes } from '@domg-wc/components/next/cascader/stories/vl-cascader-item.stories-arg'; import { cascaderArgTypes } from '@domg-wc/components/next/cascader/stories/vl-cascader.stories-arg'; +import { tableArgTypes } from '@domg-wc/components/next/table/stories/vl-table.stories-arg'; import { doormatArgTypes } from '@domg-wc/components/next/doormat/stories/vl-doormat.stories-arg'; import { iconArgTypes } from '@domg-wc/components/next/icon/stories/vl-icon.stories-arg'; import { infotextArgTypes } from '@domg-wc/components/next/infotext/stories/vl-infotext.stories-arg'; @@ -174,6 +175,12 @@ export const buildWTConfigComponents: WTConfigArray = [ null, '/docs/components-next-cascader-cascader-item--documentatie' ), + buildWTConfig( + 'vl-table-next', + tableArgTypes, + '../../libs/components/src/next/table/stories/vl-table.stories-doc.mdx', + '/docs/components-next-table--documentatie' + ), buildWTConfig( 'vl-doormat-next', doormatArgTypes, diff --git a/resources/generate-web-types/wt-validate-completeness/web-types-completeness.spec.ts b/resources/generate-web-types/wt-validate-completeness/web-types-completeness.spec.ts index bef990f9b..ef15400af 100644 --- a/resources/generate-web-types/wt-validate-completeness/web-types-completeness.spec.ts +++ b/resources/generate-web-types/wt-validate-completeness/web-types-completeness.spec.ts @@ -31,7 +31,7 @@ describe('valideer de volledigheid van de gegenereerde web-types', () => { expect(elementWTWithoutWC).toStrictEqual([]); }); it('components - valideer de volledigheid van de web-types', () => { - expect(componentWCNameCount).toEqual(92); + expect(componentWCNameCount).toEqual(93); expect(componentWTNameCount).toEqual(73); expect(componentWCWithoutWT).toStrictEqual([]); expect(componentWTWithoutWC).toStrictEqual([]);