diff --git a/.xo-config.cjs b/.xo-config.cjs index c328175b1f0..14ae97efa56 100644 --- a/.xo-config.cjs +++ b/.xo-config.cjs @@ -43,6 +43,13 @@ module.exports = { '@typescript-eslint/no-unsafe-return': 0, // valid for app 'import/no-extraneous-dependencies': 0 // foundation and component.css are inside this repo } + }, + { + files: ['./**/*.spec.ts'], + rules: { + // Playwright tests are async we shall use loops there + 'no-await-in-loop': 0 + } } ], rules: { diff --git a/packages/components/scripts/post-build/angular.ts b/packages/components/scripts/post-build/angular.ts index df4f11af32f..53a7abfc996 100644 --- a/packages/components/scripts/post-build/angular.ts +++ b/packages/components/scripts/post-build/angular.ts @@ -20,6 +20,14 @@ const changeFile = (input: string) => { return line.replace(': ElementRef', ': ElementRef | undefined'); } + // We need to remove "nativeElement" in template part, because it only exists in ts + if ( + line.includes('ref.nativeElement') && + (line.includes('{{') || line.includes('}}')) + ) { + return line.replace('.nativeElement', ''); + } + if (line.includes('.nativeElement') && !line.includes('=')) { return line.replace('.nativeElement', '?.nativeElement'); } diff --git a/packages/components/scripts/post-build/components.ts b/packages/components/scripts/post-build/components.ts index df373e85966..3cff22ed719 100644 --- a/packages/components/scripts/post-build/components.ts +++ b/packages/components/scripts/post-build/components.ts @@ -63,6 +63,10 @@ export const getComponents = (): Component[] => [ { from: 'scrollContainer = null;', to: 'scrollContainer: Element | null = null;' + }, + { + from: '& > .db-tab-panel', + to: '& > dbtabpanel > .db-tab-panel, & > db-tab-panel > .db-tab-panel' } ] } @@ -149,6 +153,13 @@ export const getComponents = (): Component[] => [ { name: 'select', + overwrites: { + react: [ + // React not allowing selected for options + { from: 'selected={option.selected}', to: '' }, + { from: 'selected={optgroupOption.selected}', to: '' } + ] + }, config: { vue: { vModel: [{ modelValue: 'value', binding: ':value' }] diff --git a/packages/components/scripts/post-build/react.ts b/packages/components/scripts/post-build/react.ts index 272a9fc891b..aa7d6bfecfb 100644 --- a/packages/components/scripts/post-build/react.ts +++ b/packages/components/scripts/post-build/react.ts @@ -98,6 +98,12 @@ export default (tmp?: boolean) => { `{...filterPassingProps(props,${JSON.stringify( component?.config?.react?.propsPassingFilter ?? [] )})}` + }, + /* We need to overwrite the internal state._value property just for react to have controlled components. + * It works for Angular & Vue, so we overwrite it only for React. */ + { + from: 'props.value ?? _value', + to: 'props.value' } ]; diff --git a/packages/components/scripts/post-build/vue.ts b/packages/components/scripts/post-build/vue.ts index 61ea32b85b8..3e3194a7708 100644 --- a/packages/components/scripts/post-build/vue.ts +++ b/packages/components/scripts/post-build/vue.ts @@ -63,9 +63,30 @@ export default (tmp?: boolean) => { { from: /immediate: true,/g, to: 'immediate: true,\nflush: "post"' + }, + /* `this` can be undefined for ssr (nuxt) we need to add */ + { + from: /this.\$refs.ref\?.validationMessage/g, + to: 'this?.$refs.ref?.validationMessage' } ]; + /* This is a workaround for valid/invalid Messages. + * If a valid/invalid message appears it will use the old this._value, + * so we need to overwrite this._value with the current event.target.value. */ + [ + 'HTMLSelectElement', + 'HTMLInputElement', + 'HTMLTextAreaElement' + ].forEach((element) => { + replacements.push({ + from: `handleInput(event: InputEvent<${element}>) {`, + to: + `handleInput(event: InputEvent<${element}>) {\n` + + 'this._value = (event.target as any).value;' + }); + }); + replaceInFileSync({ files: vueFile, processor(input) { diff --git a/packages/components/src/components/checkbox/checkbox.lite.tsx b/packages/components/src/components/checkbox/checkbox.lite.tsx index 87965973672..2990ddb59db 100644 --- a/packages/components/src/components/checkbox/checkbox.lite.tsx +++ b/packages/components/src/components/checkbox/checkbox.lite.tsx @@ -9,7 +9,6 @@ import { import { DBCheckboxProps, DBCheckboxState } from './model'; import { cls, uuid } from '../../utils'; import { - DEFAULT_ID, DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_MESSAGE_ID_SUFFIX, @@ -29,12 +28,11 @@ export default function DBCheckbox(props: DBCheckboxProps) { // jscpd:ignore-start const state = useStore({ initialized: false, - _id: DEFAULT_ID, - _messageId: DEFAULT_ID + DEFAULT_MESSAGE_ID_SUFFIX, - _validMessageId: DEFAULT_ID + DEFAULT_VALID_MESSAGE_ID_SUFFIX, - _invalidMessageId: DEFAULT_ID + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, + _id: 'checkbox-' + uuid(), + _messageId: this._id + DEFAULT_MESSAGE_ID_SUFFIX, + _validMessageId: this._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX, + _invalidMessageId: this._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, _descByIds: '', - handleChange: (event: ChangeEvent) => { if (props.onChange) { props.onChange(event); @@ -44,6 +42,20 @@ export default function DBCheckbox(props: DBCheckboxProps) { props.change(event); } handleFrameworkEvent(this, event, 'checked'); + + /* For a11y reasons we need to map the correct message with the checkbox */ + if (!ref?.validity.valid || props.customValidity === 'invalid') { + state._descByIds = state._invalidMessageId; + } else if ( + props.customValidity === 'valid' || + (ref?.validity.valid && props.required) + ) { + state._descByIds = state._validMessageId; + } else if (props.message) { + state._descByIds = state._messageId; + } else { + state._descByIds = ''; + } }, handleBlur: (event: InteractionEvent) => { if (props.onBlur) { @@ -62,48 +74,30 @@ export default function DBCheckbox(props: DBCheckboxProps) { if (props.focus) { props.focus(event); } - }, - getValidMessage: () => { - return props.validMessage || DEFAULT_VALID_MESSAGE; - }, - getInvalidMessage: () => { - return ( - props.invalidMessage || - ref?.validationMessage || - DEFAULT_INVALID_MESSAGE - ); } }); onMount(() => { state.initialized = true; - state._id = props.id || 'checkbox-' + uuid(); + state._id = props.id || state._id; }); onUpdate(() => { - if (state.initialized && state._id) { - state._messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; - state._validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; - state._invalidMessageId = + if (state._id) { + const messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; + const validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; + const invalidMessageId = state._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX; - } - }, [state._id, state.initialized]); + state._messageId = messageId; + state._validMessageId = validMessageId; + state._invalidMessageId = invalidMessageId; - onUpdate(() => { - const descByIds = [state._validMessageId, state._invalidMessageId]; - if (props.message) { - descByIds.push(state._messageId); + if (props.message) { + state._descByIds = messageId; + } } - state._descByIds = descByIds.join(' '); - }, [ - props.message, - state._messageId, - state._validMessageId, - state._invalidMessageId - ]); - // jscpd:ignore-end + }, [state._id]); - // TODO we have to check how to update on every change.. onUpdate(() => { if (state.initialized && document && state._id) { const checkboxElement = document?.getElementById( @@ -120,9 +114,12 @@ export default function DBCheckbox(props: DBCheckboxProps) { // It has no accessibility or UX implications. (https://mui.com/material-ui/react-checkbox/) checkboxElement.indeterminate = props.indeterminate; } + + state.initialized = false; } } }, [state.initialized, props.indeterminate, props.checked]); + // jscpd:ignore-end return (
- {state.getValidMessage()} + {props.validMessage ?? DEFAULT_VALID_MESSAGE} - {state.getInvalidMessage()} + {props.invalidMessage ?? + ref?.validationMessage ?? + DEFAULT_INVALID_MESSAGE}
); diff --git a/packages/components/src/components/checkbox/model.ts b/packages/components/src/components/checkbox/model.ts index 7a6f324ffac..27d2af17afe 100644 --- a/packages/components/src/components/checkbox/model.ts +++ b/packages/components/src/components/checkbox/model.ts @@ -1,16 +1,15 @@ import { + ChangeEventProps, + ChangeEventState, FocusEventProps, FocusEventState, - ChangeEventState, - ChangeEventProps, - GlobalProps, - GlobalState, + FormCheckProps, + FormMessageProps, FormProps, FormState, - FormCheckProps, + GlobalProps, + GlobalState, InitializedState, - FormMessageProps, - FormMessageState, SizeProps } from '../../shared/model'; @@ -37,5 +36,4 @@ export type DBCheckboxState = DBCheckboxDefaultState & ChangeEventState & FocusEventState & FormState & - InitializedState & - FormMessageState; + InitializedState; diff --git a/packages/components/src/components/input/input.lite.tsx b/packages/components/src/components/input/input.lite.tsx index 4d6533297c0..4b80b61ca81 100644 --- a/packages/components/src/components/input/input.lite.tsx +++ b/packages/components/src/components/input/input.lite.tsx @@ -10,7 +10,7 @@ import { import { cls, uuid } from '../../utils'; import { DBInputProps, DBInputState } from './model'; import { - DEFAULT_ID, + DEFAULT_DATALIST_ID_SUFFIX, DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_LABEL, @@ -35,12 +35,13 @@ export default function DBInput(props: DBInputProps) { const ref = useRef(null); // jscpd:ignore-start const state = useStore({ - _id: DEFAULT_ID, - _messageId: DEFAULT_ID + DEFAULT_MESSAGE_ID_SUFFIX, - _validMessageId: DEFAULT_ID + DEFAULT_VALID_MESSAGE_ID_SUFFIX, - _invalidMessageId: DEFAULT_ID + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, - _descByIds: '', - _dataListId: DEFAULT_ID, + _id: 'input-' + uuid(), + _messageId: this._id + DEFAULT_MESSAGE_ID_SUFFIX, + _validMessageId: this._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX, + _invalidMessageId: this._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, + _dataListId: this._id + DEFAULT_DATALIST_ID_SUFFIX, + _descByIds: 'none', + _value: '', defaultValues: { label: DEFAULT_LABEL, placeholder: ' ' @@ -64,6 +65,24 @@ export default function DBInput(props: DBInputProps) { } handleFrameworkEvent(this, event); + + /* For a11y reasons we need to map the correct message with the input */ + if (!ref?.validity.valid || props.customValidity === 'invalid') { + state._descByIds = state._invalidMessageId; + } else if ( + props.customValidity === 'valid' || + (ref?.validity.valid && + (props.required || + props.minLength || + props.maxLength || + props.pattern)) + ) { + state._descByIds = state._validMessageId; + } else if (props.message) { + state._descByIds = state._messageId; + } else { + state._descByIds = 'none'; + } }, handleBlur: (event: InteractionEvent) => { if (props.onBlur) { @@ -82,45 +101,33 @@ export default function DBInput(props: DBInputProps) { if (props.focus) { props.focus(event); } - }, - getValidMessage: () => { - return props.validMessage || DEFAULT_VALID_MESSAGE; - }, - getInvalidMessage: () => { - return ( - props.invalidMessage || - ref?.validationMessage || - DEFAULT_INVALID_MESSAGE - ); } }); onMount(() => { - state._id = props.id || 'input-' + uuid(); - state._dataListId = props.dataListId || `datalist-${uuid()}`; + state._id = props.id ?? state._id; + state._dataListId = props.dataListId ?? state._dataListId; }); onUpdate(() => { if (state._id) { - state._messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; - state._validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; - state._invalidMessageId = + const messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; + const validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; + const invalidMessageId = state._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX; + state._messageId = messageId; + state._validMessageId = validMessageId; + state._invalidMessageId = invalidMessageId; + + if (props.message) { + state._descByIds = messageId; + } } }, [state._id]); onUpdate(() => { - const descByIds = [state._validMessageId, state._invalidMessageId]; - if (props.message) { - descByIds.push(state._messageId); - } - state._descByIds = descByIds.join(' '); - }, [ - props.message, - state._messageId, - state._validMessageId, - state._invalidMessageId - ]); + state._value = props.value; + }, [props.value]); return (
- {state.getValidMessage()} + {props.validMessage ?? DEFAULT_VALID_MESSAGE} - {state.getInvalidMessage()} + {props.invalidMessage ?? + ref?.validationMessage ?? + DEFAULT_INVALID_MESSAGE}
); diff --git a/packages/components/src/components/input/model.ts b/packages/components/src/components/input/model.ts index 8aae70a247f..6c077c92152 100644 --- a/packages/components/src/components/input/model.ts +++ b/packages/components/src/components/input/model.ts @@ -4,7 +4,6 @@ import { FocusEventProps, FocusEventState, FormMessageProps, - FormMessageState, FormProps, FormState, FormTextProps, @@ -80,5 +79,4 @@ export type DBInputState = DBInputDefaultState & InputEventState & ChangeEventState & FocusEventState & - FormState & - FormMessageState; + FormState; diff --git a/packages/components/src/components/radio/radio.lite.tsx b/packages/components/src/components/radio/radio.lite.tsx index df02f167ce3..ae794761994 100644 --- a/packages/components/src/components/radio/radio.lite.tsx +++ b/packages/components/src/components/radio/radio.lite.tsx @@ -7,7 +7,6 @@ import { useStore } from '@builder.io/mitosis'; import { DBRadioProps, DBRadioState } from './model'; -import { DEFAULT_ID } from '../../shared/constants'; import { cls, uuid } from '../../utils'; import { ChangeEvent, InteractionEvent } from '../../shared/model'; import { handleFrameworkEvent } from '../../utils/form-components'; @@ -21,7 +20,7 @@ export default function DBRadio(props: DBRadioProps) { // jscpd:ignore-start const state = useStore({ initialized: false, - _id: DEFAULT_ID, + _id: 'radio-' + uuid(), handleChange: (event: ChangeEvent) => { if (props.onChange) { props.onChange(event); @@ -55,7 +54,7 @@ export default function DBRadio(props: DBRadioProps) { onMount(() => { state.initialized = true; - state._id = props.id || 'radio-' + uuid(); + state._id = props.id ?? state._id; }); // jscpd:ignore-end diff --git a/packages/components/src/components/select/model.ts b/packages/components/src/components/select/model.ts index dc60d3dcc5d..6af02821be7 100644 --- a/packages/components/src/components/select/model.ts +++ b/packages/components/src/components/select/model.ts @@ -6,12 +6,14 @@ import { FocusEventProps, FocusEventState, FormMessageProps, - FormMessageState, FormProps, FormState, GlobalProps, GlobalState, - IconProps + IconProps, + InitializedState, + InputEventProps, + InputEventState } from '../../shared/model'; export interface DBSelectDefaultProps { @@ -35,9 +37,13 @@ export type DBSelectOptionType = { /** * Disables this option */ - // Disables this option disabled?: boolean; + /** + * Selects this option + */ + selected?: boolean; + /** * If the value is different from the label you want to show to the user. */ @@ -59,6 +65,7 @@ export type DBSelectProps = DBSelectDefaultProps & ClickEventProps & ChangeEventProps & FocusEventProps & + InputEventProps & FormProps & IconProps & FormMessageProps; @@ -73,5 +80,6 @@ export type DBSelectState = DBSelectDefaultState & ClickEventState & ChangeEventState & FocusEventState & + InputEventState & FormState & - FormMessageState; + InitializedState; diff --git a/packages/components/src/components/select/select.lite.tsx b/packages/components/src/components/select/select.lite.tsx index d9947f50db0..d86de808a9b 100644 --- a/packages/components/src/components/select/select.lite.tsx +++ b/packages/components/src/components/select/select.lite.tsx @@ -10,7 +10,6 @@ import { import { DBSelectOptionType, DBSelectProps, DBSelectState } from './model'; import { cls, uuid } from '../../utils'; import { - DEFAULT_ID, DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_LABEL, @@ -20,28 +19,48 @@ import { DEFAULT_VALID_MESSAGE_ID_SUFFIX } from '../../shared/constants'; import { DBInfotext } from '../infotext'; -import { ChangeEvent, ClickEvent, InteractionEvent } from '../../shared/model'; +import { + ChangeEvent, + ClickEvent, + InputEvent, + InteractionEvent +} from '../../shared/model'; import { handleFrameworkEvent } from '../../utils/form-components'; useMetadata({ - isAttachedToShadowDom: true + isAttachedToShadowDom: true, + angular: { + nativeAttributes: ['value'] + } }); export default function DBSelect(props: DBSelectProps) { const ref = useRef(null); // jscpd:ignore-start const state = useStore({ - _id: DEFAULT_ID, - _messageId: DEFAULT_ID + DEFAULT_MESSAGE_ID_SUFFIX, - _validMessageId: DEFAULT_ID + DEFAULT_VALID_MESSAGE_ID_SUFFIX, - _invalidMessageId: DEFAULT_ID + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, - _descByIds: '', - _placeholderId: DEFAULT_ID + DEFAULT_PLACEHOLDER_ID_SUFFIX, + _id: 'select-' + uuid(), + _messageId: this._id + DEFAULT_MESSAGE_ID_SUFFIX, + _validMessageId: this._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX, + _invalidMessageId: this._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, + _placeholderId: this._id + DEFAULT_PLACEHOLDER_ID_SUFFIX, + // Workaround for Vue output: TS for Vue would think that it could be a function, and by this we clarify that it's a string + _descByIds: 'none', + _value: '', + initialized: false, handleClick: (event: ClickEvent) => { if (props.onClick) { props.onClick(event); } }, + handleInput: (event: InputEvent) => { + if (props.onInput) { + props.onInput(event); + } + + if (props.input) { + props.input(event); + } + }, handleChange: (event: ChangeEvent) => { if (props.onChange) { props.onChange(event); @@ -52,6 +71,20 @@ export default function DBSelect(props: DBSelectProps) { } handleFrameworkEvent(this, event); + + /* For a11y reasons we need to map the correct message with the select */ + if (!ref?.validity.valid || props.customValidity === 'invalid') { + state._descByIds = state._invalidMessageId; + } else if ( + props.customValidity === 'valid' || + (ref?.validity.valid && props.required) + ) { + state._descByIds = state._validMessageId; + } else if (props.message) { + state._descByIds = state._messageId; + } else { + state._descByIds = state._placeholderId; + } }, handleBlur: (event: InteractionEvent) => { if (props.onBlur) { @@ -73,45 +106,39 @@ export default function DBSelect(props: DBSelectProps) { }, getOptionLabel: (option: DBSelectOptionType) => { return option.label ?? option.value.toString(); - }, - getValidMessage: () => { - return props.validMessage || DEFAULT_VALID_MESSAGE; - }, - getInvalidMessage: () => { - return ( - props.invalidMessage || - ref?.validationMessage || - DEFAULT_INVALID_MESSAGE - ); } }); onMount(() => { - state._id = props.id || 'select-' + uuid(); + state._id = props.id || state._id; + state.initialized = true; }); onUpdate(() => { - if (state._id) { - state._placeholderId = state._id + DEFAULT_PLACEHOLDER_ID_SUFFIX; - state._messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; - state._validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; - state._invalidMessageId = + if (state._id && state.initialized) { + const messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; + const validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; + const invalidMessageId = state._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX; + const placeholderId = state._id + DEFAULT_PLACEHOLDER_ID_SUFFIX; + state._messageId = messageId; + state._validMessageId = validMessageId; + state._invalidMessageId = invalidMessageId; + state._placeholderId = placeholderId; + + if (props.message) { + state._descByIds = messageId; + } else { + state._descByIds = placeholderId; + } + + state.initialized = false; } - }, [state._id]); + }, [state._id, state.initialized]); onUpdate(() => { - const descByIds = [state._validMessageId, state._invalidMessageId]; - if (props.message) { - descByIds.push(state._messageId); - } - state._descByIds = descByIds.join(' '); - }, [ - props.message, - state._messageId, - state._validMessageId, - state._invalidMessageId - ]); + state._value = props.value; + }, [props.value]); return (
) => + state.handleInput(event) + } onClick={(event: ClickEvent) => state.handleClick(event) } @@ -141,9 +171,7 @@ export default function DBSelect(props: DBSelectProps) { onFocus={(event: InteractionEvent) => state.handleFocus(event) } - aria-describedby={ - (props.message && state._messageId) || state._placeholderId - }> + aria-describedby={state._descByIds}> {/* Empty option for floating label */} @@ -160,6 +188,9 @@ export default function DBSelect(props: DBSelectProps) {
); diff --git a/packages/components/src/components/switch/switch.lite.tsx b/packages/components/src/components/switch/switch.lite.tsx index f4df30ce26b..20e42425fb5 100644 --- a/packages/components/src/components/switch/switch.lite.tsx +++ b/packages/components/src/components/switch/switch.lite.tsx @@ -1,7 +1,6 @@ import { onMount, useMetadata, useRef, useStore } from '@builder.io/mitosis'; import { DBSwitchProps, DBSwitchState } from './model'; import { cls, uuid } from '../../utils'; -import { DEFAULT_ID } from '../../shared/constants'; import { ChangeEvent, InteractionEvent } from '../../shared/model'; import { handleFrameworkEvent } from '../../utils/form-components'; @@ -14,7 +13,7 @@ export default function DBSwitch(props: DBSwitchProps) { const ref = useRef(null); // jscpd:ignore-start const state = useStore({ - _id: DEFAULT_ID, + _id: 'switch-' + uuid(), initialized: false, handleChange: (event: ChangeEvent) => { if (props.onChange) { @@ -47,7 +46,7 @@ export default function DBSwitch(props: DBSwitchProps) { }); onMount(() => { - state._id = props.id || 'switch-' + uuid(); + state._id = props.id || state._id; }); // jscpd:ignore-end diff --git a/packages/components/src/components/tabs/tabs.lite.tsx b/packages/components/src/components/tabs/tabs.lite.tsx index 1a2b52281f6..a2079a24a86 100644 --- a/packages/components/src/components/tabs/tabs.lite.tsx +++ b/packages/components/src/components/tabs/tabs.lite.tsx @@ -120,7 +120,7 @@ export default function DBTabs(props: DBTabsProps) { } const tabPanels = Array.from( - ref.querySelectorAll('.db-tab-panel') + ref.querySelectorAll('& > .db-tab-panel') ); for (const panel of tabPanels) { if (panel.id) continue; diff --git a/packages/components/src/components/textarea/model.ts b/packages/components/src/components/textarea/model.ts index 40df658db9f..9f4cde44898 100644 --- a/packages/components/src/components/textarea/model.ts +++ b/packages/components/src/components/textarea/model.ts @@ -4,12 +4,11 @@ import { FocusEventProps, FocusEventState, FormMessageProps, - FormMessageState, FormProps, FormState, + FormTextProps, GlobalProps, GlobalState, - FormTextProps, InputEventProps, InputEventState } from '../../shared/model'; @@ -65,5 +64,4 @@ export type DBTextareaState = DBTextareaDefaultState & InputEventState & FocusEventState & FormState & - GlobalState & - FormMessageState; + GlobalState; diff --git a/packages/components/src/components/textarea/textarea.lite.tsx b/packages/components/src/components/textarea/textarea.lite.tsx index 6a15c585255..179a8b00c27 100644 --- a/packages/components/src/components/textarea/textarea.lite.tsx +++ b/packages/components/src/components/textarea/textarea.lite.tsx @@ -10,7 +10,6 @@ import { DBTextareaProps, DBTextareaState } from './model'; import { DBInfotext } from '../infotext'; import { cls, uuid } from '../../utils'; import { - DEFAULT_ID, DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_LABEL, @@ -29,11 +28,13 @@ export default function DBTextarea(props: DBTextareaProps) { const ref = useRef(null); // jscpd:ignore-start const state = useStore({ - _id: DEFAULT_ID, - _messageId: DEFAULT_ID + DEFAULT_MESSAGE_ID_SUFFIX, - _validMessageId: DEFAULT_ID + DEFAULT_VALID_MESSAGE_ID_SUFFIX, - _invalidMessageId: DEFAULT_ID + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, - _descByIds: '', + _id: 'textarea-' + uuid(), + _messageId: this._id + DEFAULT_MESSAGE_ID_SUFFIX, + _validMessageId: this._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX, + _invalidMessageId: this._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX, + // Workaround for Vue output: TS for Vue would think that it could be a function, and by this we clarify that it's a string + _descByIds: 'none', + _value: '', defaultValues: { label: DEFAULT_LABEL, placeholder: ' ', @@ -58,6 +59,21 @@ export default function DBTextarea(props: DBTextareaProps) { } handleFrameworkEvent(this, event); + + /* For a11y reasons we need to map the correct message with the textarea */ + if (!ref?.validity.valid || props.customValidity === 'invalid') { + state._descByIds = state._invalidMessageId; + } else if ( + props.customValidity === 'valid' || + (ref?.validity.valid && + (props.required || props.minLength || props.maxLength)) + ) { + state._descByIds = state._validMessageId; + } else if (props.message) { + state._descByIds = state._messageId; + } else { + state._descByIds = 'none'; + } }, handleBlur: (event: InteractionEvent) => { if (props.onBlur) { @@ -76,44 +92,32 @@ export default function DBTextarea(props: DBTextareaProps) { if (props.focus) { props.focus(event); } - }, - getValidMessage: () => { - return props.validMessage || DEFAULT_VALID_MESSAGE; - }, - getInvalidMessage: () => { - return ( - props.invalidMessage || - ref?.validationMessage || - DEFAULT_INVALID_MESSAGE - ); } }); onMount(() => { - state._id = props.id || 'textarea-' + uuid(); + state._id = props.id || state._id; }); onUpdate(() => { if (state._id) { - state._messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; - state._validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; - state._invalidMessageId = + const messageId = state._id + DEFAULT_MESSAGE_ID_SUFFIX; + const validMessageId = state._id + DEFAULT_VALID_MESSAGE_ID_SUFFIX; + const invalidMessageId = state._id + DEFAULT_INVALID_MESSAGE_ID_SUFFIX; + state._messageId = messageId; + state._validMessageId = validMessageId; + state._invalidMessageId = invalidMessageId; + + if (props.message) { + state._descByIds = messageId; + } } }, [state._id]); onUpdate(() => { - const descByIds = [state._validMessageId, state._invalidMessageId]; - if (props.message) { - descByIds.push(state._messageId); - } - state._descByIds = descByIds.join(' '); - }, [ - props.message, - state._messageId, - state._validMessageId, - state._invalidMessageId - ]); + state._value = props.value; + }, [props.value]); return (
) => state.handleFocus(event) } - value={props.value} - aria-describedby={props.message && state._messageId} + value={props.value ?? state._value} + aria-describedby={state._descByIds} placeholder={ props.placeholder ?? state.defaultValues.placeholder } @@ -173,14 +177,16 @@ export default function DBTextarea(props: DBTextareaProps) { id={state._validMessageId} size="small" semantic="successful"> - {state.getValidMessage()} + {props.validMessage ?? DEFAULT_VALID_MESSAGE} - {state.getInvalidMessage()} + {props.invalidMessage ?? + ref?.validationMessage ?? + DEFAULT_INVALID_MESSAGE}
); diff --git a/packages/components/src/shared/constants.ts b/packages/components/src/shared/constants.ts index 682cd0b3e76..a1a519eb710 100644 --- a/packages/components/src/shared/constants.ts +++ b/packages/components/src/shared/constants.ts @@ -5,6 +5,7 @@ export const DEFAULT_MESSAGE_ID_SUFFIX: string = '-message'; export const DEFAULT_VALID_MESSAGE_ID_SUFFIX: string = '-valid-message'; export const DEFAULT_INVALID_MESSAGE_ID_SUFFIX: string = '-invalid-message'; export const DEFAULT_PLACEHOLDER_ID_SUFFIX: string = '-placeholder'; +export const DEFAULT_DATALIST_ID_SUFFIX: string = '-datalist'; export const DEFAULT_VALID_MESSAGE: string = 'TODO: Add a validMessage'; export const DEFAULT_INVALID_MESSAGE: string = 'TODO: Add an invalidMessage'; diff --git a/packages/components/src/shared/model.ts b/packages/components/src/shared/model.ts index 12087be1bc8..4a260c7dd65 100644 --- a/packages/components/src/shared/model.ts +++ b/packages/components/src/shared/model.ts @@ -252,11 +252,6 @@ export type FormCheckProps = { variant?: CheckVariantType; }; -export type FormMessageState = { - getValidMessage: () => string; - getInvalidMessage: () => string; -}; - export const LabelVariantList = ['above', 'floating', 'hidden'] as const; export type LabelVariantType = (typeof LabelVariantList)[number]; export const AutoCompleteList = [ @@ -359,6 +354,7 @@ export type FormState = { _validMessageId?: string; _invalidMessageId?: string; _descByIds?: string; + _value?: string; }; export type InitializedState = { diff --git a/showcases/angular-showcase/src/app/components/form/checkboxes/checkboxes.component.html b/showcases/angular-showcase/src/app/components/form/checkboxes/checkboxes.component.html new file mode 100644 index 00000000000..39c40c91846 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/checkboxes/checkboxes.component.html @@ -0,0 +1,25 @@ + +
+ Different form approaches + + + +
+
diff --git a/showcases/angular-showcase/src/app/components/form/checkboxes/checkboxes.component.ts b/showcases/angular-showcase/src/app/components/form/checkboxes/checkboxes.component.ts new file mode 100644 index 00000000000..6f337e4a897 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/checkboxes/checkboxes.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WrapperComponent } from '../wrapper/wrapper.component'; +import { DBCheckbox } from '../../../../../../../output/angular/src'; + +@Component({ + selector: 'app-checkboxes', + standalone: true, + imports: [WrapperComponent, DBCheckbox, FormsModule, ReactiveFormsModule], + templateUrl: './checkboxes.component.html' +}) +export class CheckboxesComponent { + plain = true; + ngModel = true; + formControl: FormControl = new FormControl(true); +} diff --git a/showcases/angular-showcase/src/app/components/form/form.component.html b/showcases/angular-showcase/src/app/components/form/form.component.html index ee895898299..bad11e317d0 100644 --- a/showcases/angular-showcase/src/app/components/form/form.component.html +++ b/showcases/angular-showcase/src/app/components/form/form.component.html @@ -257,19 +257,19 @@

Output


- - - Airplane Button - Cancel Button - + + + Airplane Button + Cancel Button + Long Button Label with a lot of text - - + + Another Button Label with a lot of text - - - - + + + + It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a @@ -281,10 +281,10 @@

Output

sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). -
- Tab Panel 2 - Tab Panel 3 - + + Tab Panel 2 + Tab Panel 3 + At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint @@ -318,9 +318,9 @@

Output

principle of selection: he rejects pleasures to secure other greater pleasures, or else he endures pains to avoid worse pains. -
- Tab Panel 5 -
+ + Tab Panel 5 + diff --git a/showcases/angular-showcase/src/app/components/form/inputs/inputs.component.html b/showcases/angular-showcase/src/app/components/form/inputs/inputs.component.html new file mode 100644 index 00000000000..47f3f1922dc --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/inputs/inputs.component.html @@ -0,0 +1,34 @@ + +
+ Different form approaches + + + +
+
diff --git a/showcases/angular-showcase/src/app/components/form/inputs/inputs.component.ts b/showcases/angular-showcase/src/app/components/form/inputs/inputs.component.ts new file mode 100644 index 00000000000..92d62365a41 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/inputs/inputs.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WrapperComponent } from '../wrapper/wrapper.component'; +import { DBInput } from '../../../../../../../output/angular/src'; + +@Component({ + selector: 'app-inputs', + standalone: true, + imports: [WrapperComponent, DBInput, FormsModule, ReactiveFormsModule], + templateUrl: './inputs.component.html' +}) +export class InputsComponent { + plain = 'test1'; + ngModel = 'test2'; + formControl: FormControl = new FormControl('test3'); +} diff --git a/showcases/angular-showcase/src/app/components/form/selects/selects.component.html b/showcases/angular-showcase/src/app/components/form/selects/selects.component.html new file mode 100644 index 00000000000..7b4861bcd79 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/selects/selects.component.html @@ -0,0 +1,28 @@ + +
+ Different form approaches + + + + + + + + + + + + + + + +
+
diff --git a/showcases/angular-showcase/src/app/components/form/selects/selects.component.ts b/showcases/angular-showcase/src/app/components/form/selects/selects.component.ts new file mode 100644 index 00000000000..a7aae117c4d --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/selects/selects.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WrapperComponent } from '../wrapper/wrapper.component'; +import { DBSelect } from '../../../../../../../output/angular/src'; + +@Component({ + selector: 'app-selects', + standalone: true, + imports: [WrapperComponent, DBSelect, FormsModule, ReactiveFormsModule], + templateUrl: './selects.component.html' +}) +export class SelectsComponent { + plain = 'combobox-2'; + ngModel = 'combobox-2'; + formControl: FormControl = new FormControl('combobox-2'); +} diff --git a/showcases/angular-showcase/src/app/components/form/textareas/textareas.component.html b/showcases/angular-showcase/src/app/components/form/textareas/textareas.component.html new file mode 100644 index 00000000000..e71354d59dd --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/textareas/textareas.component.html @@ -0,0 +1,31 @@ + +
+ Different form approaches + + + +
+
diff --git a/showcases/angular-showcase/src/app/components/form/textareas/textareas.component.ts b/showcases/angular-showcase/src/app/components/form/textareas/textareas.component.ts new file mode 100644 index 00000000000..1d60865a2a9 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/textareas/textareas.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WrapperComponent } from '../wrapper/wrapper.component'; +import { DBTextarea } from '../../../../../../../output/angular/src'; + +@Component({ + selector: 'app-textareas', + standalone: true, + imports: [WrapperComponent, DBTextarea, FormsModule, ReactiveFormsModule], + templateUrl: './textareas.component.html' +}) +export class TextareasComponent { + plain = 'test1'; + ngModel = 'test2'; + formControl: FormControl = new FormControl('test3'); +} diff --git a/showcases/angular-showcase/src/app/components/form/wrapper/wrapper.component.html b/showcases/angular-showcase/src/app/components/form/wrapper/wrapper.component.html new file mode 100644 index 00000000000..6984274cf65 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/wrapper/wrapper.component.html @@ -0,0 +1,13 @@ +
+
+
+
+
+
Plain:
+
{{ plain.toString() }}
+
ngModel:
+
{{ model.toString() }}
+
formControl:
+
{{ control.toString() }}
+
+
diff --git a/showcases/angular-showcase/src/app/components/form/wrapper/wrapper.component.ts b/showcases/angular-showcase/src/app/components/form/wrapper/wrapper.component.ts new file mode 100644 index 00000000000..94c86dfe72f --- /dev/null +++ b/showcases/angular-showcase/src/app/components/form/wrapper/wrapper.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'app-form-wrapper', + standalone: true, + imports: [], + templateUrl: './wrapper.component.html' +}) +export class WrapperComponent { + @Input('plain') plain!: string | boolean; + @Input('model') model!: string | boolean; + @Input('control') control!: string | boolean; +} diff --git a/showcases/angular-showcase/src/app/components/home/home.component.html b/showcases/angular-showcase/src/app/components/home/home.component.html new file mode 100644 index 00000000000..1740f8a2ad1 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/home/home.component.html @@ -0,0 +1,24 @@ + + + All + Input + Textarea + Select + Checkbox + + + + + + + + + + + + + + + + + diff --git a/showcases/angular-showcase/src/app/components/home/home.component.ts b/showcases/angular-showcase/src/app/components/home/home.component.ts new file mode 100644 index 00000000000..37bf3a8aa29 --- /dev/null +++ b/showcases/angular-showcase/src/app/components/home/home.component.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; +import { + DBTabItem, + DBTabList, + DBTabPanel, + DBTabs +} from '../../../../../../output/angular/src'; +import { InputsComponent } from '../form/inputs/inputs.component'; +import { FormComponent } from '../form/form.component'; +import { TextareasComponent } from '../form/textareas/textareas.component'; +import { SelectsComponent } from '../form/selects/selects.component'; +import { CheckboxesComponent } from '../form/checkboxes/checkboxes.component'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + imports: [ + DBTabs, + DBTabItem, + DBTabList, + DBTabPanel, + InputsComponent, + FormComponent, + TextareasComponent, + SelectsComponent, + CheckboxesComponent + ], + standalone: true +}) +export class HomeComponent {} diff --git a/showcases/angular-showcase/src/app/utils/navigation-item.ts b/showcases/angular-showcase/src/app/utils/navigation-item.ts index ae211ecc30b..c3b2b4f9c84 100644 --- a/showcases/angular-showcase/src/app/utils/navigation-item.ts +++ b/showcases/angular-showcase/src/app/utils/navigation-item.ts @@ -14,7 +14,6 @@ import { SelectComponent } from '../components/select/select.component'; import { TagComponent } from '../components/tag/tag.component'; import { InputComponent } from '../components/input/input.component'; import { ButtonComponent } from '../components/button/button.component'; -import { FormComponent } from '../components/form/form.component'; import { LinkComponent } from '../components/link/link.component'; import { NotificationComponent } from '../components/notification/notification.component'; import { RadioComponent } from '../components/radio/radio.component'; @@ -27,6 +26,7 @@ import { DrawerComponent } from '../components/drawer/drawer.component'; import { IconComponent } from '../components/icon/icon.component'; import { BrandComponent } from '../components/brand/brand.component'; import { HeaderComponent } from '../components/header/header.component'; +import { HomeComponent } from '../components/home/home.component'; export type NavItem = { path: string; @@ -167,7 +167,7 @@ export const NAVIGATION_ITEMS: NavItem[] = [ } ]) }, - { path: '', label: 'Home', component: FormComponent } + { path: '', label: 'Home', component: HomeComponent } ]; const pushRoute = (routes: Routes, item: NavItem) => { diff --git a/showcases/e2e/home/showcase-home.spec.ts b/showcases/e2e/home/showcase-home.spec.ts index 40a59614336..4b251616f9a 100644 --- a/showcases/e2e/home/showcase-home.spec.ts +++ b/showcases/e2e/home/showcase-home.spec.ts @@ -1,7 +1,60 @@ -import { expect, test } from '@playwright/test'; +import { expect, test, type Page } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; import { waitForDBPage } from '../default'; +const testFormComponents = async ( + page: Page, + testId: string, + role: 'textbox' | 'combobox' | 'checkbox' +) => { + await page.goto('./'); + const tab = page.getByTestId(testId); + await tab.click({ force: true }); + const definition = await page + .getByTestId('data-list') + .getByRole('definition') + .all(); + + const components = await page.getByTestId('tabs').getByRole(role).all(); + for (const component of components) { + const index = components.indexOf(component); + switch (role) { + case 'textbox': { + await component.clear(); + await component.fill(`${role}-${index}`); + + break; + } + + case 'combobox': { + await component.selectOption({ value: `${role}-${index}` }); + + break; + } + + case 'checkbox': { + await component.click({ force: true }); + + break; + } + // No default + } + } + + // Move focus out of last component to reflect changes + await tab.click({ force: true }); + + for (const def of definition) { + const index = definition.indexOf(def); + const text = await def.textContent(); + if (role === 'checkbox') { + expect(text).toEqual('false'); + } else { + expect(text).toEqual(`${role}-${index}`); + } + } +}; + test.describe('Home', () => { test('has title', async ({ page }) => { await page.goto('./'); @@ -31,4 +84,20 @@ test.describe('Home', () => { expect(accessibilityScanResults.violations).toEqual([]); }); + + test('test inputs', async ({ page }) => { + await testFormComponents(page, 'tab-inputs', 'textbox'); + }); + + test('test textareas', async ({ page }) => { + await testFormComponents(page, 'tab-textareas', 'textbox'); + }); + + test('test selects', async ({ page }) => { + await testFormComponents(page, 'tab-selects', 'combobox'); + }); + + test('test checkboxes', async ({ page }) => { + await testFormComponents(page, 'tab-checkboxes', 'checkbox'); + }); }); diff --git a/showcases/react-showcase/src/components/form/checkbox.tsx b/showcases/react-showcase/src/components/form/checkbox.tsx new file mode 100644 index 00000000000..6730a858e24 --- /dev/null +++ b/showcases/react-showcase/src/components/form/checkbox.tsx @@ -0,0 +1,31 @@ +import { DBCheckbox, DBInput } from '@db-ui/react-components/src'; +import { useState } from 'react'; +import FormWrapper from './form-wrapper'; + +const FormCheckboxes = () => { + const [controlled, setControlled] = useState(true); + const [uncontrolled, setUncontrolled] = useState(true); + + return ( + + { + setControlled(event.target.checked); + }}> + Controlled + + { + setUncontrolled(event.target.checked); + }}> + Uncontrolled + + + ); +}; + +export default FormCheckboxes; diff --git a/showcases/react-showcase/src/components/form/form-wrapper.tsx b/showcases/react-showcase/src/components/form/form-wrapper.tsx new file mode 100644 index 00000000000..a2944ca8309 --- /dev/null +++ b/showcases/react-showcase/src/components/form/form-wrapper.tsx @@ -0,0 +1,26 @@ +import { type PropsWithChildren } from 'react'; + +const FormWrapper = ({ + children, + uncontrolled, + controlled +}: PropsWithChildren<{ + controlled: string | boolean; + uncontrolled: string | boolean; +}>) => { + return ( +
+
+
{children}
+
+
+
Controlled:
+
{controlled.toString()}
+
Uncontrolled:
+
{uncontrolled.toString()}
+
+
+ ); +}; + +export default FormWrapper; diff --git a/showcases/react-showcase/src/components/form/input.tsx b/showcases/react-showcase/src/components/form/input.tsx new file mode 100644 index 00000000000..525770b6949 --- /dev/null +++ b/showcases/react-showcase/src/components/form/input.tsx @@ -0,0 +1,37 @@ +import { DBInput } from '@db-ui/react-components/src'; +import { useState } from 'react'; +import FormWrapper from './form-wrapper'; + +const FormInputs = () => { + const [controlled, setControlled] = useState('test1'); + const [uncontrolled, setUncontrolled] = useState('test2'); + + return ( + + { + setControlled(event.target.value); + }} + /> + { + setUncontrolled(event.target.value); + }} + /> + + ); +}; + +export default FormInputs; diff --git a/showcases/react-showcase/src/components/form/select.tsx b/showcases/react-showcase/src/components/form/select.tsx new file mode 100644 index 00000000000..1fb32adfbd0 --- /dev/null +++ b/showcases/react-showcase/src/components/form/select.tsx @@ -0,0 +1,35 @@ +import { DBSelect } from '@db-ui/react-components/src'; +import { useState } from 'react'; +import FormWrapper from './form-wrapper'; + +const FormSelects = () => { + const [controlled, setControlled] = useState('combobox-2'); + const [uncontrolled, setUncontrolled] = useState('combobox-2'); + + return ( + + { + setControlled(event.target.value); + }}> + + + + + { + setUncontrolled(event.target.value); + }}> + + + + + + ); +}; + +export default FormSelects; diff --git a/showcases/react-showcase/src/components/form/textarea.tsx b/showcases/react-showcase/src/components/form/textarea.tsx new file mode 100644 index 00000000000..229663d0adf --- /dev/null +++ b/showcases/react-showcase/src/components/form/textarea.tsx @@ -0,0 +1,29 @@ +import { DBTextarea } from '@db-ui/react-components/src'; +import { useState } from 'react'; +import FormWrapper from './form-wrapper'; + +const FormTextareas = () => { + const [controlled, setControlled] = useState('test1'); + const [uncontrolled, setUncontrolled] = useState('test2'); + + return ( + + { + setControlled(event.target.value); + }} + /> + { + setUncontrolled(event.target.value); + }} + /> + + ); +}; + +export default FormTextareas; diff --git a/showcases/react-showcase/src/components/home/index.tsx b/showcases/react-showcase/src/components/home/index.tsx new file mode 100644 index 00000000000..0565a263831 --- /dev/null +++ b/showcases/react-showcase/src/components/home/index.tsx @@ -0,0 +1,42 @@ +import { + DBTabItem, + DBTabList, + DBTabPanel, + DBTabs +} from '@db-ui/react-components/src'; +import FormInputs from '../form/input'; +import FormComponent from '../form'; +import FormTextareas from '../form/textarea'; +import FormSelects from '../form/select'; +import FormCheckboxes from '../form/checkbox'; + +const Home = () => { + return ( + + + All + Input + Textarea + Select + Checkbox + + + + + + + + + + + + + + + + + + ); +}; + +export default Home; diff --git a/showcases/react-showcase/src/components/select/index.tsx b/showcases/react-showcase/src/components/select/index.tsx index 8510e02434f..d8ffc1e01e7 100644 --- a/showcases/react-showcase/src/components/select/index.tsx +++ b/showcases/react-showcase/src/components/select/index.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { DBSelect } from '../../../../../output/react/src'; import DefaultComponent from '../default-component'; import defaultComponentVariants from '../../../../shared/select.json'; @@ -15,20 +16,26 @@ const getSelect = ({ value, required, variant -}: DBSelectProps) => ( - - {children} - -); +}: DBSelectProps) => { + const [mValue, setValue] = useState(value); + return ( + { + setValue(event.target.value); + }} + message={message} + required={required}> + {children} + + ); +}; const SelectComponent = (props: BaseComponentProps) => { return ( diff --git a/showcases/react-showcase/src/utils/navigation-item.tsx b/showcases/react-showcase/src/utils/navigation-item.tsx index e9d18aa870d..a6c5e6c34e3 100644 --- a/showcases/react-showcase/src/utils/navigation-item.tsx +++ b/showcases/react-showcase/src/utils/navigation-item.tsx @@ -17,7 +17,6 @@ import NotificationComponent from '../components/notification'; import ButtonComponent from '../components/button'; import CardComponent from '../components/card'; import DividerComponent from '../components/divider'; -import FormComponent from '../components/form'; import InfotextComponent from '../components/infotext'; import InputComponent from '../components/input'; import LinkComponent from '../components/link'; @@ -26,6 +25,7 @@ import TextareaComponent from '../components/textarea'; import IconComponent from '../components/icon'; import BrandComponent from '../components/brand'; import HeaderComponent from '../components/header'; +import Home from '../components/home'; export type NavigationItem = { path: string; @@ -170,5 +170,5 @@ export const NAVIGATION_ITEMS: NavigationItem[] = [ } ]) }, - { path: '', label: 'Home', component: } + { path: '', label: 'Home', component: } ]; diff --git a/showcases/shared/select.json b/showcases/shared/select.json index 9218ec97392..97db35611e9 100644 --- a/showcases/shared/select.json +++ b/showcases/shared/select.json @@ -185,7 +185,8 @@ "props": { "options": [ { - "value": "Filled" + "value": "Filled", + "selected": true }, { "value": "Option 2" @@ -244,7 +245,8 @@ "props": { "options": [ { - "value": "Filled" + "value": "Filled", + "selected": true }, { "value": "Option 2" diff --git a/showcases/vue-showcase/src/components/accordion-item/AccordionItem.vue b/showcases/vue-showcase/src/components/accordion-item/AccordionItem.vue index 7ea1b892b0e..4d4944356b5 100644 --- a/showcases/vue-showcase/src/components/accordion-item/AccordionItem.vue +++ b/showcases/vue-showcase/src/components/accordion-item/AccordionItem.vue @@ -2,7 +2,6 @@ import DefaultComponent from "../DefaultComponent.vue"; import defaultComponentVariants from "../../../../shared/accordion-item.json"; import { DBAccordionItem } from "../../../../../output/vue/src"; -import { ref } from "vue";