From 4201c1fe8353e7d293e8e775933bb24c57b2736c Mon Sep 17 00:00:00 2001 From: Christian Hoffmann <112889877+ChristianHoffmannS2@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:29:34 +0100 Subject: [PATCH] Select: html options instead of array (#780) * feat(ui-library): replaced options array with slots in blrSelect component --------- Co-authored-by: David Kennedy Co-authored-by: David Kennedy <127498135+davidken91@users.noreply.github.com> --- packages/js-example-app/src/assets/index.html | 13 + packages/js-example-app/src/index.js | 5 + .../src/components/forms/select/index.css.ts | 4 + .../components/forms/select/index.stories.ts | 230 ++++++++++-------- .../src/components/forms/select/index.test.ts | 23 +- .../src/components/forms/select/index.ts | 54 ++-- .../components/forms/select/renderFunction.ts | 5 +- 7 files changed, 198 insertions(+), 136 deletions(-) diff --git a/packages/js-example-app/src/assets/index.html b/packages/js-example-app/src/assets/index.html index f057c13f3..53e2868fa 100644 --- a/packages/js-example-app/src/assets/index.html +++ b/packages/js-example-app/src/assets/index.html @@ -30,6 +30,19 @@

Vanilla JS Example Application

+
+

Select

+ + + + + + + + + +
+

Checkbox

diff --git a/packages/js-example-app/src/index.js b/packages/js-example-app/src/index.js index 7a758bd0b..99ab1e38d 100644 --- a/packages/js-example-app/src/index.js +++ b/packages/js-example-app/src/index.js @@ -7,6 +7,7 @@ const logsContainer = document.querySelector('#logs'); const blrButton = document.getElementsByTagName('blr-text-button')[0]; const blrCheckbox = document.getElementsByTagName('blr-checkbox')[0]; +const blrSelect = document.getElementsByTagName('blr-select')[0]; const addLog = (log) => { logsContainer.innerHTML = logsContainer.innerHTML + log + '
'; @@ -61,3 +62,7 @@ blrCheckbox.addEventListener('blrFocus', () => { blrCheckbox.addEventListener('blrBlur', () => { addLog('blr-checkbox blurred'); }); + +blrSelect.addEventListener('blrChange', (e) => { + addLog('blr-select changed'); +}); diff --git a/packages/ui-library/src/components/forms/select/index.css.ts b/packages/ui-library/src/components/forms/select/index.css.ts index e9e87b6bf..b863948db 100644 --- a/packages/ui-library/src/components/forms/select/index.css.ts +++ b/packages/ui-library/src/components/forms/select/index.css.ts @@ -15,6 +15,10 @@ export const { tokenizedLight: selectInputLight, tokenizedDark: selectInputDark const { UserInput, SurfaceFill, SM, MD, LG, Input, InputBorderRadius, Placeholder, InputIcon } = semanticTokens.Forms; return typeSafeNestedCss` + slot { + display: none; + } + .blr-input-icon { pointer-events: none; } diff --git a/packages/ui-library/src/components/forms/select/index.stories.ts b/packages/ui-library/src/components/forms/select/index.stories.ts index c9ee7c9c4..3fe5c04ec 100644 --- a/packages/ui-library/src/components/forms/select/index.stories.ts +++ b/packages/ui-library/src/components/forms/select/index.stories.ts @@ -33,23 +33,10 @@ const defaultParams: BlrSelectType = { required: false, hasError: false, errorMessage: ' ', - errorIcon: undefined, + errorMessageIcon: undefined, arialabel: 'Select', selectId: 'selectId', name: 'select', - options: [ - { value: '0', label: 'Option 1', selected: false, disabled: true }, - { - value: '1', - label: - 'To big option Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es.Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles.', - selected: false, - disabled: false, - }, - { value: '2', label: 'Option 3', selected: true, disabled: false }, - { value: '4', label: 'Option 4', selected: false, disabled: false }, - ], - onChange: (event: Event) => console.log(event.type), }; export default { @@ -120,14 +107,8 @@ export default { options: { name: 'children (options)', description: - 'Enter an array containing information about the label, value and disabled prop for all options that are part of the select.', + 'Enter a list of html option elements containing information about the label, value and disabled prop for all options that are part of the select.', control: 'array', - options: [ - { value: '0', label: 'Option 1', selected: false, disabled: true }, - { value: '1', label: 'Option 2', selected: false, disabled: false }, - { value: '2', label: 'Option 3', selected: true, disabled: false }, - { value: '4', label: 'Option 4', selected: false, disabled: false }, - ], table: { category: 'Content / Settings', }, @@ -170,7 +151,7 @@ export default { }, if: { arg: 'hasError', eq: true }, }, - errorIcon: { + errorMessageIcon: { name: 'errorMessageIcon', description: 'Select an icon which is displayed in front of the error message.', table: { @@ -255,7 +236,17 @@ Select presents users with a list of options from which they can make a single s }, }; -export const BlrSelect = (params: BlrSelectType) => BlrSelectRenderFunction(params); +const optionsAsChildren = html` + + + + + + + +`; + +export const BlrSelect = (params: BlrSelectType) => BlrSelectRenderFunction(params, optionsAsChildren); BlrSelect.storyName = 'Select'; BlrSelect.args = defaultParams; @@ -288,7 +279,6 @@ const argTypesToDisable = [ const generateDisabledArgTypes = (argTypes: string[]) => { const disabledArgTypes = {}; argTypes.forEach((argType: string) => { - // @ts-expect-error todo disabledArgTypes[argType] = { table: { disable: true, @@ -307,24 +297,33 @@ export const SizeVariant = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - size: 'sm', - label: 'Select SM', - labelAppendix: '', - })} - ${BlrSelectRenderFunction({ - ...defaultParams, - size: 'md', - label: 'Select MD', - labelAppendix: '', - })} - ${BlrSelectRenderFunction({ - ...defaultParams, - size: 'lg', - label: 'Select LG', - labelAppendix: '', - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + size: 'sm', + label: 'Select SM', + labelAppendix: '', + }, + optionsAsChildren + )} + ${BlrSelectRenderFunction( + { + ...defaultParams, + size: 'md', + label: 'Select MD', + labelAppendix: '', + }, + optionsAsChildren + )} + ${BlrSelectRenderFunction( + { + ...defaultParams, + size: 'lg', + label: 'Select LG', + labelAppendix: '', + }, + optionsAsChildren + )}
`; }; @@ -343,12 +342,15 @@ export const Disabled = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - disabled: true, - label: 'Disabled', - labelAppendix: '', - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + disabled: true, + label: 'Disabled', + labelAppendix: '', + }, + optionsAsChildren + )}
`; }; @@ -367,12 +369,15 @@ export const Required = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - required: true, - label: 'Required', - labelAppendix: '', - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + required: true, + label: 'Required', + labelAppendix: '', + }, + optionsAsChildren + )}
`; }; @@ -388,13 +393,16 @@ export const HasError = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - hasError: true, - errorIcon: undefined, - label: 'Error', - labelAppendix: '', - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + hasError: true, + errorMessageIcon: undefined, + label: 'Error', + labelAppendix: '', + }, + optionsAsChildren + )}
`; }; @@ -411,22 +419,22 @@ export const FormLabel = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - label: 'With Label', - labelAppendix: '(with Appendix)', - })} - ${BlrSelectRenderFunction({ - ...defaultParams, - label: ' ', - labelAppendix: ' ', - options: [ - { value: '0', label: 'Option 1', selected: false, disabled: true }, - { value: '1', label: 'Option 2', selected: false, disabled: false }, - { value: '2', label: 'Without Label', selected: true, disabled: false }, - { value: '4', label: 'Option 4', selected: false, disabled: false }, - ], - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + label: 'With Label', + labelAppendix: '(with Appendix)', + }, + optionsAsChildren + )} + ${BlrSelectRenderFunction( + { + ...defaultParams, + label: ' ', + labelAppendix: ' ', + }, + optionsAsChildren + )}
`; }; @@ -442,18 +450,24 @@ export const Icon = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - icon: 'blrArrowUp', - label: 'With Icon', - labelAppendix: ' ', - })} - ${BlrSelectRenderFunction({ - ...defaultParams, - icon: undefined, - label: 'Default Icon', - labelAppendix: ' ', - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + icon: 'blrArrowUp', + label: 'With Icon', + labelAppendix: ' ', + }, + optionsAsChildren + )} + ${BlrSelectRenderFunction( + { + ...defaultParams, + icon: undefined, + label: 'Default Icon', + labelAppendix: ' ', + }, + optionsAsChildren + )}
`; }; @@ -467,22 +481,28 @@ export const FormCaptionGroup = () => { return html` ${sharedStyles}
- ${BlrSelectRenderFunction({ - ...defaultParams, - hasHint: true, - label: 'Hint message', - labelAppendix: ' ', - })} - ${BlrSelectRenderFunction({ - ...defaultParams, - icon: undefined, - label: 'Hint and error message', - labelAppendix: '', - hasHint: true, - hasError: true, - errorMessage: "OMG it's an error", - errorIcon: 'blrErrorFilled', - })} + ${BlrSelectRenderFunction( + { + ...defaultParams, + hasHint: true, + label: 'Hint message', + labelAppendix: ' ', + }, + optionsAsChildren + )} + ${BlrSelectRenderFunction( + { + ...defaultParams, + icon: undefined, + label: 'Hint and error message', + labelAppendix: '', + hasHint: true, + hasError: true, + errorMessage: "OMG it's an error", + errorMessageIcon: 'blrError', + }, + optionsAsChildren + )}
`; }; diff --git a/packages/ui-library/src/components/forms/select/index.test.ts b/packages/ui-library/src/components/forms/select/index.test.ts index 1ad7dc169..6fe7df7f0 100644 --- a/packages/ui-library/src/components/forms/select/index.test.ts +++ b/packages/ui-library/src/components/forms/select/index.test.ts @@ -5,6 +5,7 @@ import type { BlrSelectType } from '@boiler/ui-library/dist/'; import { fixture, expect } from '@open-wc/testing'; import { querySelectorAllDeep, querySelectorDeep } from 'query-selector-shadow-dom'; +import { html } from 'lit-html'; const sampleParams: BlrSelectType = { name: 'Text Input', @@ -16,19 +17,23 @@ const sampleParams: BlrSelectType = { hintMessage: 'Field is used for hint', hintIcon: 'blrInfo', selectId: 'Peter', - errorIcon: 'blrErrorFilled', - options: [ - { value: 'uschi', label: 'Uschi', disabled: true }, - { value: '1', label: 'Option 1' }, - { value: '2', label: 'Option 2', selected: true }, - { value: 'dieter', label: 'Dieter' }, - ], + errorMessageIcon: 'blrErrorFilled', theme: 'Light', }; +const optionsAsChildren = html` + + + + + + + +`; + describe('blr-select', () => { it('is having a select containing the right className', async () => { - const element = await fixture(BlrSelectRenderFunction(sampleParams)); + const element = await fixture(BlrSelectRenderFunction(sampleParams, optionsAsChildren)); const select = querySelectorDeep('select', element.getRootNode() as HTMLElement); const className = select?.className; @@ -43,7 +48,7 @@ describe('blr-select', () => { hasHint: true, hintIcon: 'blrInfo', hasError: true, - errorIcon: 'blrErrorFilled', + errorMessageIcon: 'blrErrorFilled', }) ); diff --git a/packages/ui-library/src/components/forms/select/index.ts b/packages/ui-library/src/components/forms/select/index.ts index a9b2b9f2f..ea18ce81d 100644 --- a/packages/ui-library/src/components/forms/select/index.ts +++ b/packages/ui-library/src/components/forms/select/index.ts @@ -17,14 +17,6 @@ import { BlrFormCaptionGroupRenderFunction } from '../../internal-components/for import { BlrFormCaptionRenderFunction } from '../../internal-components/form-caption-group/form-caption/renderFunction'; import { BlrFormLabelRenderFunction } from '../../internal-components/form-label/renderFunction'; import { BlrIconRenderFunction } from '../../ui/icon/renderFunction'; - -type Option = { - value: string; - label: string; - selected?: boolean; - disabled?: boolean; -}; - import { TAG_NAME } from './renderFunction'; @customElement(TAG_NAME) @@ -39,22 +31,25 @@ export class BlrSelect extends LitElement { @property() disabled?: boolean; @property() size?: FormSizesType = 'md'; @property() required?: boolean; - @property() onChange?: (event: Event) => void; @property() onBlur?: HTMLElement['blur']; @property() onFocus?: HTMLElement['focus']; - @property({ type: Array }) options: Option[] = []; + @property() hasError?: boolean; @property() errorMessage?: string; @property() hintMessage?: string; @property() hintIcon?: SizelessIconType; - @property() errorIcon?: SizelessIconType; + @property() errorMessageIcon?: SizelessIconType; @property() hasHint?: boolean; @property() icon?: SizelessIconType = 'blrChevronDown'; @property() theme: ThemeType = 'Light'; + @property() blrChange?: () => void; + @state() protected isFocused = false; + protected _optionElements: Element[] | undefined; + protected handleFocus = () => { this.isFocused = true; }; @@ -63,6 +58,23 @@ export class BlrSelect extends LitElement { this.isFocused = false; }; + protected handleSlotChange() { + const slot = this.renderRoot?.querySelector('slot'); + + this._optionElements = slot?.assignedElements({ flatten: false }); + this.requestUpdate(); + } + + protected handleChange(event: Event) { + this.dispatchEvent( + new CustomEvent('blrChange', { + bubbles: true, + composed: true, + detail: { originalEvent: event }, + }) + ); + } + protected renderIcon(classes: DirectiveResult) { if (this.size) { const iconSizeVariant = getComponentConfigToken([ @@ -126,13 +138,13 @@ export class BlrSelect extends LitElement { icon: this.hintIcon, }) : nothing} - ${this.hasError && (this.errorMessage || this.errorIcon) + ${this.hasError && (this.errorMessage || this.errorMessageIcon) ? BlrFormCaptionRenderFunction({ variant: 'error', theme: this.theme, size: this.size, message: this.errorMessage, - icon: this.errorIcon, + icon: this.errorMessageIcon, }) : nothing} `; @@ -141,6 +153,9 @@ export class BlrSelect extends LitElement { + + +
${this.label ? BlrFormLabelRenderFunction({ @@ -161,20 +176,19 @@ export class BlrSelect extends LitElement { name=${this.name || nothing} ?disabled=${this.disabled} ?required=${this.required} - @change=${this.onChange} + @change=${this.handleChange} @focus=${this.handleFocus} @blur=${this.handleBlur} > - ${this.options?.map((opt: Option) => { + ${this._optionElements?.map((opt: Element) => { return html` `; })} diff --git a/packages/ui-library/src/components/forms/select/renderFunction.ts b/packages/ui-library/src/components/forms/select/renderFunction.ts index 57469601e..2aeaf8d7e 100644 --- a/packages/ui-library/src/components/forms/select/renderFunction.ts +++ b/packages/ui-library/src/components/forms/select/renderFunction.ts @@ -1,7 +1,8 @@ +import { TemplateResult } from 'lit-html'; import { BlrSelectType } from '.'; import { genericBlrComponentRenderer } from '../../../utils/typesafe-generic-component-renderer'; export const TAG_NAME = 'blr-select'; -export const BlrSelectRenderFunction = (params: BlrSelectType) => - genericBlrComponentRenderer(TAG_NAME, { ...params }); +export const BlrSelectRenderFunction = (params: BlrSelectType, children?: TemplateResult<1>) => + genericBlrComponentRenderer(TAG_NAME, { ...params }, children);