From 7d6f804bd6d5e42cd86cacf663bb6f830cd75f3f Mon Sep 17 00:00:00 2001 From: Karim Dehbi Date: Tue, 15 Oct 2024 10:06:25 +0200 Subject: [PATCH] feat: UIG-2929 - vl-search-filter-next - introductie component De `vl-rich-data` component uitgebreid met vl-search-filter-next integratie. Font-family naamvariabelen verbeterd. Storybook verbeterd & cypress testen uitgebreid. --- .../vl-rich-data-table.stories.cy.ts | 6 +- .../vlux-meta-data/vlux-meta-data.json | 11 +- ...024-van-v1-naar-v2-beschikbaar.stories.mdx | 82 ++++----- libs/common/utilities/src/css/font/fonts.js | 4 +- libs/components/components.web-types.json | 32 ++++ .../src/next/search-filter/index.ts | 1 + .../stories/vl-search-filter.stories-arg.ts | 50 ++++++ .../stories/vl-search-filter.stories-doc.mdx | 41 +++++ .../stories/vl-search-filter.stories.ts | 143 ++++++++++++++++ .../vl-search-filter.component.cy.ts | 161 ++++++++++++++++++ .../vl-search-filter.component.ts | 149 ++++++++++++++++ .../search-filter/vl-search-filter.css.ts | 63 +++++++ .../vl-search-filter.defaults.ts | 6 + .../vl-search-filter.global.css.ts | 87 ++++++++++ .../stories/vl-rich-data-table.stories.ts | 102 ++++++----- .../rich-data/vl-rich-data.component.cy.ts | 2 +- .../src/rich-data/vl-rich-data.component.ts | 83 +++++++-- .../stories/vl-search-filter.stories-arg.ts | 29 ++-- .../stories/vl-search-filter.stories.ts | 3 + resources/generate-web-types/readme.md | 16 +- .../wt-config-build/components.wt-config.ts | 7 + .../web-types-completeness.spec.ts | 4 +- 22 files changed, 934 insertions(+), 148 deletions(-) create mode 100644 libs/components/src/next/search-filter/index.ts create mode 100644 libs/components/src/next/search-filter/stories/vl-search-filter.stories-arg.ts create mode 100644 libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx create mode 100644 libs/components/src/next/search-filter/stories/vl-search-filter.stories.ts create mode 100644 libs/components/src/next/search-filter/vl-search-filter.component.cy.ts create mode 100644 libs/components/src/next/search-filter/vl-search-filter.component.ts create mode 100644 libs/components/src/next/search-filter/vl-search-filter.css.ts create mode 100644 libs/components/src/next/search-filter/vl-search-filter.defaults.ts create mode 100644 libs/components/src/next/search-filter/vl-search-filter.global.css.ts 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 1c34dd370..db9b7cbd3 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 @@ -210,17 +210,17 @@ describe('story vl-rich-data-table - filter', () => { }); it('should be able to filter on a field', () => { - cy.get('vl-rich-data-table').find(`input[name="${filterField}"]`).type(filterValue); + cy.get('vl-rich-data-table').find(`[name="${filterField}"]`).shadow().find('input').type(filterValue); }); it('should be able to find the filtered data in the table', () => { - cy.get('vl-rich-data-table').find(`input[name="${filterField}"]`).type(filterValue); + cy.get('vl-rich-data-table').find(`[name="${filterField}"]`).shadow().find('input').type(filterValue); const filteredData = data.filter((row) => row[filterField].indexOf(filterValue) !== -1); shouldMatchTableData(filteredData); }); it('should not be able to find data not corresponding to search fields', () => { - cy.get('vl-rich-data-table').find(`input[name="${filterField}"]`).type(filterValue); + cy.get('vl-rich-data-table').find(`[name="${filterField}"]`).shadow().find('input').type(filterValue); executeForEveryRow((row, rowIndex) => shouldNotMatchCellWithData(row, rowIndex, data)); }); }); 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 52cf3ba12..56ff5c042 100644 --- a/apps/storybook/.storybook/vlux-meta-data/vlux-meta-data.json +++ b/apps/storybook/.storybook/vlux-meta-data/vlux-meta-data.json @@ -280,9 +280,16 @@ "planningInfo": "[v1 naar v2 - beschikbaar](/docs/planning-2024-van-v1-naar-v2-beschikbaar--documentatie#radio-group--radio)" }, "elements-search-filter": { - "vStatus": "v1-todo", + "vStatus": "v1-replace", "legacyText": "vl-search-filter", - "planningInfo": "[v1 naar v2 - planning](/docs/planning-2024-van-v1-naar-v2-planning--documentatie)" + "nextText": "[vl-search-filter-next](/docs/components-next-search-filter--documentatie)", + "planningInfo": "[v1 naar v2 - beschikbaar](/docs/planning-2024-van-v1-naar-v2-beschikbaar--documentatie#search-filter)" + }, + "components-next-search-filter": { + "vStatus": "v2-next", + "legacyText": "[vl-search-filter](/docs/elements-search-filter--documentatie)", + "nextText": "vl-search-filter-next", + "planningInfo": "[v1 naar v2 - beschikbaar](/docs/planning-2024-van-v1-naar-v2-beschikbaar--documentatie#search-filter)" }, "elements-search-results": { "vStatus": "v1-todo" 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 fb59e9ede..93e7e5d28 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 @@ -9,7 +9,6 @@ import {Meta} from '@storybook/blocks'; - [Aanpak](#aanpak) - [Geïmpacteerde Web Componenten](#geïmpacteerde-web-componenten) - ## Aanpak Op deze pagina vind je een overzicht van de web componenten die geïmpacteerd zijn door de migratie van v1 naar v2 @@ -25,24 +24,6 @@ beschikbaar is (zal zijn), je kan doorklikken op de naam om naar de documentatie - typisch hebben de legacy en de next variant dezelfde naam, maar niet altijd - momenteel zijn er enkel v1 releases, wat is er zal gebeuren in v2 is informatief; er is nog geen v2 beschikbaar - -[//]: # (## Releases) -[//]: # (### tem v1.37.x) -[//]: # () -[//]: # (Sinds een jaar zijn we stelselmatig next componenten aan het introduceren. ) -[//]: # () -[//]: # (Van de volgende web componenten bestaat er een next variant: [Button](#button), [Checkbox](#checkbox),) -[//]: # ([Datepicker](#datepicker), [Doormat](#doormat), [Form Label](#form-label),) -[//]: # ([Form Validation Message / Error Message](#form-validation-message--error-message), [Icon](#icon),) -[//]: # ([Infotext](#infotext), [Input Field](#input-field), [Link](#link), [Pattern](#pattern), [Properties](#properties),) -[//]: # ([Radio Group / Radio](#radio-group--radio), [Select / MultiSelect / Select Rich](#select--multiselect--select-rich),) -[//]: # ([Steps / Step](#steps--step), [Textarea / Textarea Rich](#textarea--textarea-rich), [Title](#title),) -[//]: # ([Upload](#upload)) -[//]: # () -[//]: # (Volgende componenten werden geschrapt, zie hieronder voor de nieuwe aanpak: [Form](#form),) -[//]: # ([Form Annotation](#form-annotation), [Form Validation](#form-validation)) - - ## Geïmpacteerde Web Componenten ### Button @@ -425,38 +406,6 @@ gebruiken dit niet meer. - -[//]: # (### Introduction) - -[//]: # () -[//]: # () -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # ( ) -[//]: # (
NaamToestandv1 - Artifactv1 - Gebruikv2 - Artifactv2 - Gebruik
[Introduction [legacy]](/docs/elements-introduction--documentatie)wordt deprecatedelements{`

`}

--
[Introduction [next]](TODO)release 1.40.0 [te bevestigen]]tokens/next{`

`}

tokens{`

`}

) - - ### Link `Button Link [legacy]` verdwijnt in v2, gebruik een `` of een ``. @@ -557,7 +506,6 @@ gebruiken dit niet meer. - ### Radio Group / Radio @@ -604,6 +552,36 @@ gebruiken dit niet meer.
+### Search Filter + + + + + + + + + + + + + + + + + + + + + + + + + + +
NaamToestandv1 - Artifactv1 - Gebruikv2 - Artifactv2 - Gebruik
[Search Filter [legacy]](/docs/elements-search-filter--documentatie)deprecatedelements{``}--
[Search Filter [next]](/docs/components-next-search-filter--documentatie)release v1.42.0components/next{``}components{``}
+ + ### Select / MultiSelect / Select Rich diff --git a/libs/common/utilities/src/css/font/fonts.js b/libs/common/utilities/src/css/font/fonts.js index 025821d4c..fe0cfd314 100644 --- a/libs/common/utilities/src/css/font/fonts.js +++ b/libs/common/utilities/src/css/font/fonts.js @@ -5,8 +5,8 @@ export const baseFontLocation = 'https://cdn.omgeving.vlaanderen.be/domg/govflanders-font/22.0.2'; // Flanders font -export const fontFamily = 'FlandersArtSans'; -export const serifFontFamily = 'FlandersArtSerif'; +export const fontFamily = 'Flanders Art Sans'; +export const serifFontFamily = 'Flanders Art Serif'; // Icon font export const iconFontFamily = 'vlaanderen-icon-classic'; diff --git a/libs/components/components.web-types.json b/libs/components/components.web-types.json index aa177a818..a3a7cc2b1 100644 --- a/libs/components/components.web-types.json +++ b/libs/components/components.web-types.json @@ -1386,6 +1386,38 @@ ] } }, + { + "name": "vl-search-filter-next", + "description": "[next-component]

\n\n\n{`\n 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.\n In v3 zal deze component grondig herwerkt worden; in de context van een herwerking van de vl-data-table.\n`}\n\n\nGebruik de `search-filter-next` component om een zoek filter te tonen op een pagina.", + "doc-url": "https://milieuinfo.github.io/uigov-builds/release/DOMG-WC-VERSION/storybook/?path=/docs/components-next-search-filter--documentatie", + "attributes": [ + { + "name": "custom-css", + "description": "Custom CSS string.
Wordt toegevoegd aan de adoptedStyleSheets in de shadow DOM van de component.", + "default": "null" + }, + { + "name": "filter-title", + "description": "De titel van deze zoekfilter.", + "default": "" + }, + { + "name": "alt", + "description": "Alternatieve (transparante) achtergrond.", + "default": "false" + }, + { + "name": "mobile-modal", + "description": "Activeert geoptimaliseerde weergave voor mobiele apparaten.
Dit wordt ook geactiveerd als de viewport kleiner is dan 768px.", + "default": "false" + }, + { + "name": "mobile-modal-title", + "description": "Stelt de titel in van deze zoekfilter op mobiele apparaten.
Als die niet gedeclareerd is, wordt de waarde van filter-title gebruikt.", + "default": "" + } + ] + }, { "name": "vl-steps-next", "description": "[next-component]
Gebruik de `steps-next` component om een verticale lijst van stappen af te beelden om de gebruiker door een procedure te begeleiden.
\n\nDeze component is de nieuwe versie van de [vl-steps](/docs/components-steps--documentatie) component, we raden aan deze versie te gebruiken.", diff --git a/libs/components/src/next/search-filter/index.ts b/libs/components/src/next/search-filter/index.ts new file mode 100644 index 000000000..3a6763e36 --- /dev/null +++ b/libs/components/src/next/search-filter/index.ts @@ -0,0 +1 @@ +export { VlSearchFilterComponent } from './vl-search-filter.component'; diff --git a/libs/components/src/next/search-filter/stories/vl-search-filter.stories-arg.ts b/libs/components/src/next/search-filter/stories/vl-search-filter.stories-arg.ts new file mode 100644 index 000000000..e425e9587 --- /dev/null +++ b/libs/components/src/next/search-filter/stories/vl-search-filter.stories-arg.ts @@ -0,0 +1,50 @@ +import { ArgTypes } from '@storybook/web-components'; +import { CATEGORIES, defaultArgs, defaultArgTypes, TYPES } from '@domg-wc/common-storybook'; +import { searchFilterDefaults } from '../vl-search-filter.defaults'; + +export const searchFilterArgs = { + ...defaultArgs, + ...searchFilterDefaults, +}; + +export const searchFilterArgTypes: ArgTypes = { + ...defaultArgTypes(true), + filterTitle: { + name: 'filter-title', + description: 'De titel van deze zoekfilter.', + table: { + type: { summary: TYPES.STRING }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.filterTitle }, + }, + }, + alt: { + name: 'alt', + description: 'Alternatieve (transparante) achtergrond.', + table: { + type: { summary: TYPES.BOOLEAN }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.alt }, + }, + }, + mobileModal: { + name: 'mobile-modal', + description: + 'Activeert geoptimaliseerde weergave voor mobiele apparaten.
Dit wordt ook geactiveerd als de viewport kleiner is dan 768px.', + table: { + type: { summary: TYPES.STRING }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.mobileModal }, + }, + }, + mobileModalTitle: { + name: 'mobile-modal-title', + description: + 'Stelt de titel in van deze zoekfilter op mobiele apparaten.
Als die niet gedeclareerd is, wordt de waarde van filter-title gebruikt.', + table: { + type: { summary: TYPES.STRING }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.mobileModalTitle }, + }, + }, +}; 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 new file mode 100644 index 000000000..2495ee764 --- /dev/null +++ b/libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx @@ -0,0 +1,41 @@ +import {ArgTypes, Canvas} from '@storybook/addon-docs'; + +import * as VlSearchFilterStories from './vl-search-filter.stories'; + +# Search Filter - next + + + +
+ + +{` + 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. +`} + + +Gebruik de `search-filter-next` component om een zoek filter te tonen op een pagina. + + +## Voorbeeld + +```js +import { VlSearchFilterComponent } from '@domg-wc/components/next/search-filter'; +``` + +```html + +``` + + + + +## Configuratie + + + + +## Referenties + +[Documentatie Digitaal Vlaanderen - Search Filter](https://overheid.vlaanderen.be/webuniversum/v3/documentation/components/vl-ui-search-filter) diff --git a/libs/components/src/next/search-filter/stories/vl-search-filter.stories.ts b/libs/components/src/next/search-filter/stories/vl-search-filter.stories.ts new file mode 100644 index 000000000..919109759 --- /dev/null +++ b/libs/components/src/next/search-filter/stories/vl-search-filter.stories.ts @@ -0,0 +1,143 @@ +import { story } from '@domg-wc/common-storybook'; +import { registerWebComponents } from '@domg-wc/common-utilities'; +import { VlFormLabelComponent } from '@domg-wc/form/next/form-label'; +import { VlInputFieldComponent } from '@domg-wc/form/next/input-field'; +import { VlSelectComponent } from '@domg-wc/form/next/select'; +import { Meta } from '@storybook/web-components'; +import { html } from 'lit-html'; +import { VlButtonComponent } from '../../button'; +import searchFilterDoc from './vl-search-filter.stories-doc.mdx'; +import { VlTitleComponent } from '../../title'; +import { VlSearchFilterComponent } from '../vl-search-filter.component'; +import { searchFilterArgs, searchFilterArgTypes } from './vl-search-filter.stories-arg'; + +export default { + id: 'components-next-search-filter', + title: 'Components-next/search-filter', + tags: ['autodocs'], + args: searchFilterArgs, + argTypes: searchFilterArgTypes, + parameters: { + docs: { + page: searchFilterDoc, + }, + }, +} as Meta; + +registerWebComponents([ + VlInputFieldComponent, + VlFormLabelComponent, + VlSelectComponent, + VlButtonComponent, + VlSearchFilterComponent, + VlTitleComponent, +]); + +const searchFilterTemplate = story( + searchFilterArgs, + ({ filterTitle, alt, mobileModal, mobileModalTitle }) => html` + +
+
+
+ Doorzoek projecten +
+ + +
+
+ + +
+ + +
+
+
+ Locatie +
+ + + +
+
+ + + +
+
+
+
+ Zoeken + Reset +
+ +
+ ` +); + +// TODO kspeltin: 'as any' is een vuile fix +export const SearchFilterDefault = searchFilterTemplate.bind({}) as any; +SearchFilterDefault.storyName = 'vl-search-filter - default'; + +export const SearchFilterMobile = searchFilterTemplate.bind({}) as any; +SearchFilterMobile.storyName = 'vl-search-filter - mobile'; +SearchFilterMobile.args = { + mobileModal: true, + mobileModalTitle: 'Mobile title', +}; +SearchFilterMobile.parameters = { + layout: 'fullscreen', + viewport: { + defaultViewport: 'mobile1', + }, +}; diff --git a/libs/components/src/next/search-filter/vl-search-filter.component.cy.ts b/libs/components/src/next/search-filter/vl-search-filter.component.cy.ts new file mode 100644 index 000000000..c33a944ef --- /dev/null +++ b/libs/components/src/next/search-filter/vl-search-filter.component.cy.ts @@ -0,0 +1,161 @@ +import { VlFormLabelComponent } from '@domg-wc/form/next/form-label'; +import { VlInputFieldComponent } from '@domg-wc/form/next/input-field'; +import { VlSelectComponent } from '@domg-wc/form/next/select'; +import { html } from 'lit'; +import { registerWebComponents } from '@domg-wc/common-utilities'; +import { VlButtonComponent } from '../button'; +import { VlTitleComponent } from '../title'; +import { VlSearchFilterComponent } from './vl-search-filter.component'; + +registerWebComponents([ + VlSearchFilterComponent, + VlTitleComponent, + VlInputFieldComponent, + VlFormLabelComponent, + VlSelectComponent, + VlButtonComponent, +]); + +describe('component - vl-search-filter', () => { + it('should mount', () => { + cy.mount(html``); + + cy.get('vl-search-filter-next').shadow().find('.form-container'); + }); + + it('should be accessible', () => { + cy.mount(html``); + cy.injectAxe(); + + cy.checkA11y('vl-search-filter-next'); + }); + + it('should render the title on mobile', () => { + cy.viewport('iphone-6'); + cy.mount(html``); + + cy.get('vl-search-filter-next').shadow().find('.vl-search-filter-next--header-modal').contains('Filter title'); + cy.get('vl-search-filter-next').invoke('attr', 'mobile-modal-title', 'Mobile title'); + cy.get('vl-search-filter-next').shadow().find('.vl-search-filter-next--header-modal').contains('Mobile title'); + }); + + it('should render the filter title on desktop', () => { + cy.viewport('macbook-15'); + cy.mount(html``); + + cy.get('vl-search-filter-next').shadow().find('.vl-search-filter-next--intro').contains('Filter title'); + }); + + it('should render alt style', () => { + mountWithSlottedForm(); + + cy.get('form.vl-search-filter-next--form').shouldHaveComputedStyle({ + style: 'background-color', + value: 'rgb(232, 235, 238)', + }); + cy.get('vl-search-filter-next').invoke('attr', 'alt', 'true'); + cy.get('form.vl-search-filter-next--form').shouldHaveComputedStyle({ + style: 'background-color', + value: 'rgb(255, 255, 255)', + }); + }); + + it('should not submit form when clicking button', () => { + mountWithSlottedForm(); + cy.viewport('iphone-6'); + + cy.get('form.vl-search-filter-next--form') + .find('vl-input-field-next[name="id"]') + .shadow() + .find('input') + .type('123'); + cy.get('form.vl-search-filter-next--form').find('vl-button-next[type="submit"]').click(); + cy.get('form.vl-search-filter-next--form').find('vl-input-field-next[name="id"]').should('have.value', '123'); + }); +}); + +const mountWithSlottedForm = () => { + cy.mount(html` + +
+
+
+ Doorzoek projecten +
+ + +
+
+ + +
+ + +
+
+
+ Locatie +
+ + + +
+
+ + + +
+
+
+
+ Zoeken + Reset +
+ +
+ `); +}; diff --git a/libs/components/src/next/search-filter/vl-search-filter.component.ts b/libs/components/src/next/search-filter/vl-search-filter.component.ts new file mode 100644 index 000000000..73fafec1c --- /dev/null +++ b/libs/components/src/next/search-filter/vl-search-filter.component.ts @@ -0,0 +1,149 @@ +import { BaseLitElement, webComponent } from '@domg-wc/common-utilities'; +import { globalStylesNext } from '@domg-wc/common-utilities/css/global-styles-decorator'; +import { CSSResult, html, nothing, PropertyDeclarations, PropertyValues } from 'lit'; +import { classMap } from 'lit/directives/class-map.js'; +import { searchFilterStyles } from './vl-search-filter.css'; +import { searchFilterDefaults } from './vl-search-filter.defaults'; +import { searchFilterGlobalStyles } from './vl-search-filter.global.css'; + +@globalStylesNext() +@webComponent('vl-search-filter-next') +export class VlSearchFilterComponent extends BaseLitElement { + private filterTitle: string | undefined = searchFilterDefaults.filterTitle; + private alt: string | undefined; + private mobileModal = searchFilterDefaults.mobileModal; + private mobileModalTitle: string | undefined; + private resizeObserver: ResizeObserver | null = null; + + static get styles(): (CSSResult | CSSResult[])[] { + return [searchFilterStyles]; + } + + static get properties(): PropertyDeclarations { + return { + filterTitle: { type: String, attribute: 'filter-title' }, + alt: { type: Boolean }, + mobileModal: { type: Boolean, attribute: 'mobile-modal', reflect: true }, + mobileModalTitle: { type: String, attribute: 'mobile-modal-title' }, + }; + } + + get form(): HTMLFormElement | null { + return this.querySelector('form'); + } + + get submitButton(): HTMLButtonElement | null | undefined { + return this.form?.querySelector('vl-button-next[type="submit"]'); + } + + get buttonContainer(): HTMLElement | null | undefined { + return this.form?.querySelector('*:has(vl-button-next[type="submit"],button[type="submit"])'); + } + + get formData(): FormData | undefined { + return this.form ? new FormData(this.form) : undefined; + } + + protected async firstUpdated(changedProperties: PropertyValues) { + super.firstUpdated(changedProperties); + + this.form?.classList.add('vl-search-filter-next--form'); + this.form?.addEventListener('submit', this.handleSubmitModal); + this.form?.addEventListener('keydown', (event: KeyboardEvent) => { + if (event.key === 'Escape') { + this.setAttribute('hidden', 'true'); + } + }); + const buttonContainer = this.form?.querySelector( + 'div:has(vl-button-next[type="submit"],button[type="submit"])' + ); + buttonContainer?.classList.add('vl-search-filter-next--submit'); + + // 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, + searchFilterGlobalStyles.styleSheet as CSSStyleSheet, + ]; + } + + protected updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + + if (changedProperties.has('alt')) { + if (this.alt) { + this.form?.classList.add('vl-search-filter-next--form__alt'); + } else { + this.form?.classList.remove('vl-search-filter-next--form__alt'); + } + } + if (changedProperties.has('mobileModal')) { + if (this.mobileModal) { + this.buttonContainer?.classList.add('vl-search-filter-next--footer-modal'); + this.form?.classList.add('vl-search-filter-next--form__mobile-modal'); + } else { + this.buttonContainer?.classList.remove('vl-search-filter-next--footer-modal'); + this.form?.classList.remove('vl-search-filter-next--form__mobile-modal'); + } + } + } + + render() { + const classes = { + 'vl-search-filter-next': true, + 'vl-search-filter-next--mobile-modal': this.mobileModal, + }; + return html` +
+ ${!this.mobileModal ? this.renderTitle() : this.renderMobileModal()} +
+ +
+
+ `; + } + + connectedCallback() { + super.connectedCallback(); + this.resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + this.mobileModal = entry.contentRect.width < 767; + } + }); + this.resizeObserver.observe(document.body); + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.form?.removeEventListener('submit', this.handleSubmitModal); + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; + } + } + + private renderTitle() { + return this.filterTitle ? html`

${this.filterTitle}

` : nothing; + } + + private renderMobileModal() { + return html` +
+

${this.mobileModalTitle || this.filterTitle || 'Filter'}

+
+ `; + } + + private handleSubmitModal = (event: Event) => { + event.preventDefault(); + if (this.mobileModal) { + this.setAttribute('hidden', 'true'); + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + 'vl-search-filter-next': VlSearchFilterComponent; + } +} diff --git a/libs/components/src/next/search-filter/vl-search-filter.css.ts b/libs/components/src/next/search-filter/vl-search-filter.css.ts new file mode 100644 index 000000000..7703c56a3 --- /dev/null +++ b/libs/components/src/next/search-filter/vl-search-filter.css.ts @@ -0,0 +1,63 @@ +import { css, CSSResult } from 'lit'; + +export const searchFilterStyles: CSSResult = css` + :host { + --vl-form-label-font-weight: normal; + --vl-title-h2-font-size: 1.6rem; + --vl-title-h2-font-size-small: 1.4rem; + --vl-title-letter-spacing: 0.1rem; + } + + @media screen and (max-width: 767px) { + :host .vl-search-filter-next { + display: flex; + flex-direction: column; + position: fixed; + padding: 0; + top: 0; + right: 0; + bottom: 0; + left: 0; + height: 100vh; + overflow-y: hidden; + .form-container { + flex: 1; + overflow-y: auto; + height: 100vh; + } + } + } + + h2 { + font-size: 3.2rem; + font-family: 'Flanders Art Sans', sans-serif; + font-weight: 500; + margin-bottom: 2rem; + line-height: 1.24; + } + + .vl-search-filter-next--intro { + text-transform: uppercase; + font-weight: 500; + border-bottom: 3px solid #e8ebee; + font-size: 1.8rem; + padding: 0 0 0.7rem; + margin: 0 0 2rem; + } + + @media screen and (max-width: 767px) { + .vl-search-filter-next--header-modal { + padding: 2.5rem 1.5rem 1rem; + border-bottom: 1px solid #8695a8; + display: flex; + align-items: center; + h2 { + font-size: 2.6rem; + margin: 0; + } + } + :host([alt]) .vl-search-filter-next--header-modal { + background-color: #fff; + } + } +`; diff --git a/libs/components/src/next/search-filter/vl-search-filter.defaults.ts b/libs/components/src/next/search-filter/vl-search-filter.defaults.ts new file mode 100644 index 000000000..08e7d844a --- /dev/null +++ b/libs/components/src/next/search-filter/vl-search-filter.defaults.ts @@ -0,0 +1,6 @@ +export const searchFilterDefaults = { + filterTitle: '' as string, + alt: false as boolean, + mobileModal: false as boolean, + mobileModalTitle: '' as string, +} as const; diff --git a/libs/components/src/next/search-filter/vl-search-filter.global.css.ts b/libs/components/src/next/search-filter/vl-search-filter.global.css.ts new file mode 100644 index 000000000..8046cfd8c --- /dev/null +++ b/libs/components/src/next/search-filter/vl-search-filter.global.css.ts @@ -0,0 +1,87 @@ +import { css, CSSResult } from 'lit'; + +export const searchFilterGlobalStyles: CSSResult = css` + form.vl-search-filter-next--form { + padding: 2rem; + background-color: #e8ebee; + section { + padding-bottom: 2rem; + margin-bottom: 2rem; + border-bottom: 1px solid #cbd2da; + h2 { + font-size: 1.5rem; + display: block; + text-transform: uppercase; + margin-bottom: 1.2rem; + letter-spacing: 0.1rem; + font-weight: 500; + } + } + } + + form.vl-search-filter-next--form__alt { + background: transparent; + height: 100%; + } + + @media screen and (max-width: 767px) { + form.vl-search-filter-next--form__alt { + background: #fff; + } + + .vl-search-filter-next--form__mobile-modal:has(* section) { + display: flex; + flex-direction: column; + height: 100%; + padding: 0 !important; + margin: 0 !important; + *:has(section) { + padding: 1.5rem !important; + flex: 1; + overflow-y: auto; + } + } + } + + @media screen and (max-width: 767px) { + .vl-search-filter-next--form__mobile-modal:has(> section) { + display: flex; + flex-direction: column; + padding: 1.5rem !important; + } + } + + @media screen and (max-width: 767px) { + .vl-search-filter-next--form__alt.vl-search-filter-next--form { + padding: 0 2rem 55px; + } + } + + @media screen and (max-width: 767px) { + .vl-search-filter-next--footer-modal { + display: flex; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; + padding: 10px 15px; + width: 100%; + border-top: 1px solid #8695a8; + background-color: #fff; + z-index: 2; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1); + button, + vl-button-next { + width: 100%; + } + } + .vl-search-filter-next--form:has(> section) { + height: calc(120vh + 100px); + .vl-search-filter-next--footer-modal { + position: fixed; + bottom: 0; + left: 0; + right: 0; + } + } + } +`; diff --git a/libs/components/src/rich-data-table/stories/vl-rich-data-table.stories.ts b/libs/components/src/rich-data-table/stories/vl-rich-data-table.stories.ts index 7bc438c92..3ce889b2f 100644 --- a/libs/components/src/rich-data-table/stories/vl-rich-data-table.stories.ts +++ b/libs/components/src/rich-data-table/stories/vl-rich-data-table.stories.ts @@ -2,9 +2,18 @@ import { story } from '@domg-wc/common-storybook'; // -> buiten de monorepo werkt dat niet omdat sideEffects disabled worden voor de root-barrel file in de artifacts import '@domg-wc/elements'; +import { registerWebComponents } from '@domg-wc/common-utilities'; + import { Meta } from '@storybook/web-components'; import { html } from 'lit-html'; import '../vl-rich-data-table.component'; +import { VlRichDataTable } from '../vl-rich-data-table.component'; +import { VlSearchFilterComponent } from '../../next/search-filter'; +import { VlButtonComponent } from '../../next/button'; +import { VlTitleComponent } from '../../next/title'; +import { VlFormLabelComponent } from '@domg-wc/form/next/form-label'; +import { VlInputFieldComponent } from '@domg-wc/form/next/input-field'; +import { VlSelectComponent } from '@domg-wc/form/next/select'; import { filterRichTableImplementation } from './vl-rich-data-table-filter.stories-util'; import richDataFilterPagerData from './vl-rich-data-table-pagination.stories-mock'; import { paginationRichTableImplementation } from './vl-rich-data-table-pagination.stories-util'; @@ -12,6 +21,16 @@ import { sortingRichTableImplementation } from './vl-rich-data-table-sorting.sto import { richDataTableArgs, richDataTableArgTypes } from './vl-rich-data-table.stories-arg'; import richDataTableDoc from './vl-rich-data-table.stories-doc.mdx'; +registerWebComponents([ + VlRichDataTable, + VlSearchFilterComponent, + VlTitleComponent, + VlButtonComponent, + VlFormLabelComponent, + VlInputFieldComponent, + VlSelectComponent, +]); + export default { id: 'components-rich-data-table', title: 'Components/rich-data-table', @@ -120,57 +139,48 @@ const TemplateFilter = story( data-vl-label="Eerste medewerker" data-vl-selector="medewerkers.0.lastName" > -
-
-
-

Doorzoek projecten

-
- - -
-
-
-

Project details

-
- - + +
+ Doorzoek projecten +
+ + +
+
+ + +
+ + -
-
- - + block + autocomplete="family-name" + >
-
-
- -
+
+
+ Zoeken + Reset +
-
- -
-
+ { .then((child) => { expect(child[0]).to.contain('1-5'); }); - cy.get('vl-pager').shadow().find('li[data-vl-pager-page=2]').click(); + cy.get('vl-pager').shadow().find('li[data-vl-pager-page=2]').click({ force: true }); cy.get('vl-pager') .shadow() .find('li[id=bounds]') diff --git a/libs/components/src/rich-data/vl-rich-data.component.ts b/libs/components/src/rich-data/vl-rich-data.component.ts index 3a138cca4..a897b68f1 100644 --- a/libs/components/src/rich-data/vl-rich-data.component.ts +++ b/libs/components/src/rich-data/vl-rich-data.component.ts @@ -1,5 +1,14 @@ import { BaseElementOfType, registerWebComponents, webComponent } from '@domg-wc/common-utilities'; -import { VlButtonElement, VlColumnElement, VlFormLabel, VlGridElement, VlIconElement } from '@domg-wc/elements'; +import { + VlButtonElement, + VlColumnElement, + VlFormLabel, + VlGridElement, + VlIconElement, + VlSearchFilterElement, +} from '@domg-wc/elements'; +import { number } from 'prop-types'; +import { VlSearchFilterComponent } from '../next/search-filter'; import { Pagination, VlPagerComponent } from '../pager/vl-pager.component'; import styles from './vl-rich-data.uig-css'; @@ -126,7 +135,7 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { return this.shadowRoot.querySelector('#content'); } - get __searchFilter(): HTMLFormElement { + get __searchFilter(): (VlSearchFilterElement & HTMLElement) | VlSearchFilterComponent { return this.querySelector('[slot="filter"]'); } @@ -187,7 +196,9 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { } get __searchFilterForm(): HTMLFormElement | null { - return this.__searchFilter ? this.__searchFilter.querySelector('form') : this.__searchFilter; + return this.__searchFilter + ? this.__searchFilter.querySelector('form') + : (this.__searchFilter).form; } get __contentSlot(): HTMLSlotElement { @@ -239,7 +250,7 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { set _filter(filter: any) { if (filter && this.__searchFilter) { - const form = this.__searchFilter.querySelector('form'); + const form = this.__searchFilter.querySelector('form') || this.__searchFilter.form; if (form) { filter.forEach((entry: { value: string; name: number }) => { const formElements = form.elements; @@ -299,6 +310,7 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { } else { this.__hideSearchColumn(); } + this.__handleSearchFilterClosing(); } __observeFilterButtons() { @@ -312,7 +324,11 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { this.setAttribute('data-vl-filter-closed', ''); this._element.appendChild(this.__filterSlot); this.__hideHiddenInModalElements(); - this.__searchFilter.setAttribute('data-vl-mobile-modal', ''); + if (!(this.__searchFilter instanceof VlSearchFilterComponent)) { + this.__searchFilter.setAttribute('data-vl-mobile-modal', ''); + } else { + this.__searchFilter.removeAttribute('hidden'); + } }); } @@ -358,19 +374,37 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { __processSearchFilter(): void { if (this.__searchFilter) { - this.__searchFilter.setAttribute('data-vl-alt', ''); + if (!(this.__searchFilter instanceof VlSearchFilterComponent)) { + this.__searchFilter.setAttribute('data-vl-mobile-modal', ''); + } else { + this.__searchFilter.setAttribute('alt', ''); + } if (!this.hasAttribute('data-vl-filter-closed')) { this.__showSearchColumn(); } this.__showSearchResults(); this.__addSearchFilterEventListeners(); - this.__observeMobileModal(() => this.__processScrollableBody()); + this.__observeMobileModal(() => { + this.__processScrollableBody(); + + this.__handleSearchFilterClosing(); + }); } else { this.__hideSearchColumn(); this.__hideSearchResults(); } } + __handleSearchFilterClosing(): void { + if (this.__searchFilter instanceof VlSearchFilterComponent) { + if (this.hasAttribute('data-vl-filter-closed')) { + this.__searchFilter.setAttribute('hidden', 'true'); + } else { + this.__searchFilter.removeAttribute('hidden'); + } + } + } + __processSorter(): void { if (this.__sorter) { this.__showSorter(); @@ -441,14 +475,20 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { } } + private stopPropagationPreventDefault = (e: any) => { + e.stopPropagation(); + e.preventDefault(); + }; + __addSearchFilterEventListeners() { - this.__searchFilter.addEventListener('change', (e: any) => { - e.stopPropagation(); - e.preventDefault(); - }); - this.__searchFilter.addEventListener('input', (e: any) => { - this.__onFilterFieldChanged(e); - }); + if (!(this.__searchFilter instanceof VlSearchFilterComponent)) { + this.__searchFilter.addEventListener('change', this.stopPropagationPreventDefault); + this.__searchFilter.addEventListener('input', this.__onFilterFieldChanged); + } else { + this.__searchFilter.addEventListener('vl-change', this.stopPropagationPreventDefault); + this.__searchFilter.addEventListener('vl-input', this.__onFilterFieldChanged); + } + if (this.__searchFilterForm) { this.__searchFilterForm.addEventListener('reset', (e: any) => { setTimeout(() => { @@ -458,20 +498,27 @@ export class VlRichData extends BaseElementOfType(HTMLElement) { } } - __onFilterFieldChanged(event: any) { + __onFilterFieldChanged = (event: any) => { event.stopPropagation(); event.preventDefault(); this.__onStateChange(event); - } + }; __observeMobileModal(callback: any) { const observer = new MutationObserver(callback); - observer.observe(this.__searchFilter, { attributeFilter: ['data-vl-mobile-modal'] }); + if (!(this.__searchFilter instanceof VlSearchFilterComponent)) { + observer.observe(this.__searchFilter, { attributeFilter: ['data-vl-mobile-modal'] }); + } else { + observer.observe(this.__searchFilter, { attributeFilter: ['mobile-modal'] }); + } return observer; } __processScrollableBody() { - if (this.__searchFilter.hasAttribute('data-vl-mobile-modal')) { + if ( + this.__searchFilter.hasAttribute('data-vl-mobile-modal') || + this.__searchFilter.hasAttribute('mobile-modal') + ) { this.__disableBodyScroll(); } else { this.__enableBodyScroll(); diff --git a/libs/elements/src/search-filter/stories/vl-search-filter.stories-arg.ts b/libs/elements/src/search-filter/stories/vl-search-filter.stories-arg.ts index 7c7bf731e..d8bac994f 100644 --- a/libs/elements/src/search-filter/stories/vl-search-filter.stories-arg.ts +++ b/libs/elements/src/search-filter/stories/vl-search-filter.stories-arg.ts @@ -1,10 +1,11 @@ import { ArgTypes } from '@storybook/web-components'; +import { CATEGORIES, TYPES } from '@domg-wc/common-storybook'; export const searchFilterArgs = { - title: 'Lorem ipsum', + title: '', alt: false, mobileModal: false, - mobileModalTitle: 'Lorem ipsum dolor set', + mobileModalTitle: '', maxWidth: '800px', }; @@ -13,27 +14,27 @@ export const searchFilterArgTypes: ArgTypes = { name: 'data-vl-title', description: 'The title of this search filter.', table: { - type: { summary: 'string' }, - category: 'Attributes', - defaultValue: { summary: '' }, + type: { summary: TYPES.STRING }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.title }, }, }, alt: { name: 'data-vl-alt', description: 'Alternative (transparent) background.', table: { - type: { summary: 'boolean' }, - category: 'Attributes', - defaultValue: { summary: 'false' }, + type: { summary: TYPES.BOOLEAN }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.alt }, }, }, mobileModal: { name: 'data-vl-mobile-modal', description: 'Activates optimized display for mobile devices.', table: { - type: { summary: 'boolean' }, - category: 'Attributes', - defaultValue: { summary: 'false' }, + type: { summary: TYPES.BOOLEAN }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.mobileModal }, }, }, mobileModalTitle: { @@ -41,9 +42,9 @@ export const searchFilterArgTypes: ArgTypes = { description: 'The title of this search filter on mobile devices. If not declared, the value of data-vl-title will be used.', table: { - type: { summary: 'string' }, - category: 'Attributes', - defaultValue: { summary: '' }, + type: { summary: TYPES.STRING }, + category: CATEGORIES.ATTRIBUTES, + defaultValue: { summary: searchFilterArgs.mobileModalTitle }, }, }, maxWidth: { diff --git a/libs/elements/src/search-filter/stories/vl-search-filter.stories.ts b/libs/elements/src/search-filter/stories/vl-search-filter.stories.ts index 09e5b4599..5c966068e 100644 --- a/libs/elements/src/search-filter/stories/vl-search-filter.stories.ts +++ b/libs/elements/src/search-filter/stories/vl-search-filter.stories.ts @@ -87,6 +87,9 @@ const searchFilterTemplate = ({ title, alt, mobileModal, mobileModalTitle, maxWi // TODO kspeltin: 'as any' is een vuile fix export const searchFilterDefault = searchFilterTemplate.bind({}) as any; searchFilterDefault.storyName = 'vl-search-filter - default'; +searchFilterDefault.args = { + title: 'Lorem ipsum', +}; searchFilterDefault.argTypes = { mobileModal: { control: { diff --git a/resources/generate-web-types/readme.md b/resources/generate-web-types/readme.md index 0ed382e85..92fc236ea 100644 --- a/resources/generate-web-types/readme.md +++ b/resources/generate-web-types/readme.md @@ -2,7 +2,7 @@ ## genereren -npm run generate-web-types + npm run generate-web-types ## testen @@ -13,14 +13,14 @@ Rechter klikken in de IDE en op `web-types.spec.ts` en laten lopen lukt, alleen In plaats van er verder tijd in te steken: de oorzaak is de Nx verwevenheid, die wordt weggewerkt, in die aanpak ook Jest testen toevoegen voor de web-types! -tsx ./tools/web-types-generator/tests/compare-wc-wt-components.ts -tsx ./tools/web-types-generator/tests/compare-wc-wt-elements.ts -tsx ./tools/web-types-generator/tests/compare-wc-wt-form.ts -tsx ./tools/web-types-generator/tests/compare-wc-wt-map.ts -tsx ./tools/web-types-generator/tests/compare-wc-wt-qlik.ts -tsx ./tools/web-types-generator/tests/compare-wc-wt-sections.ts + tsx ./tools/web-types-generator/tests/compare-wc-wt-components.ts + tsx ./tools/web-types-generator/tests/compare-wc-wt-elements.ts + tsx ./tools/web-types-generator/tests/compare-wc-wt-form.ts + tsx ./tools/web-types-generator/tests/compare-wc-wt-map.ts + tsx ./tools/web-types-generator/tests/compare-wc-wt-qlik.ts + tsx ./tools/web-types-generator/tests/compare-wc-wt-sections.ts ## schema validatie -tsx ./tools/web-types-generator/schema/validate-schema.ts + tsx ./tools/web-types-generator/schema/validate-schema.ts 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 77759b50a..b3d98d5ae 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 @@ -6,6 +6,7 @@ import { iconArgTypes } from '@domg-wc/components/next/icon/stories/vl-icon.stor import { infotextArgTypes } from '@domg-wc/components/next/infotext/stories/vl-infotext.stories-arg'; import { linkArgTypes } from '@domg-wc/components/next/link/stories/vl-link.stories-arg'; import { propertiesArgTypes } from '@domg-wc/components/next/properties/stories/vl-properties.stories-arg'; +import { searchFilterArgTypes } from '@domg-wc/components/next/search-filter/stories/vl-search-filter.stories-arg'; import { stepArgTypes } from '@domg-wc/components/next/steps/stories/vl-step.stories-arg'; import { stepsArgTypes } from '@domg-wc/components/next/steps/stories/vl-steps.stories-arg'; import { titleArgTypes } from '@domg-wc/components/next/title/stories/vl-title.stories-arg'; @@ -203,6 +204,12 @@ export const buildWTConfigComponents: WTConfigArray = [ '../../libs/components/src/next/properties/stories/vl-properties.stories-doc.mdx', '/docs/components-next-properties--documentatie' ), + buildWTConfig( + 'vl-search-filter-next', + searchFilterArgTypes, + '../../libs/components/src/next/search-filter/stories/vl-search-filter.stories-doc.mdx', + '/docs/components-next-search-filter--documentatie' + ), buildWTConfig( 'vl-steps-next', stepsArgTypes, 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 0c7a9b9fd..bef990f9b 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,8 +31,8 @@ describe('valideer de volledigheid van de gegenereerde web-types', () => { expect(elementWTWithoutWC).toStrictEqual([]); }); it('components - valideer de volledigheid van de web-types', () => { - expect(componentWCNameCount).toEqual(91); - expect(componentWTNameCount).toEqual(72); + expect(componentWCNameCount).toEqual(92); + expect(componentWTNameCount).toEqual(73); expect(componentWCWithoutWT).toStrictEqual([]); expect(componentWTWithoutWC).toStrictEqual([]); });