diff --git a/src/combobox/example/index.ts b/src/combobox/example/index.ts index e34047f883..d9b77f4957 100644 --- a/src/combobox/example/index.ts +++ b/src/combobox/example/index.ts @@ -65,7 +65,10 @@ export default class App extends WidgetBase { private _value7 = ''; private _value8 = ''; private _value9 = ''; - private _invalid = false; + private _valid: { valid: boolean | undefined; message: string } = { + valid: true, + message: 'Please enter value of state' + }; onChange(value: string, key?: string) { if (!key) { @@ -89,7 +92,6 @@ export default class App extends WidgetBase { render(): DNode { const { onChange, onRequestResults } = this; - return v( 'div', { @@ -180,16 +182,21 @@ export default class App extends WidgetBase { required: true, onChange: (value: string) => { this._value9 = value; - this._invalid = value.trim().length === 0; + this._valid.valid = value.trim().length !== 0; this.invalidate(); }, getResultLabel: (result: any) => result.value, onRequestResults, results: this._results, value: this._value9, - invalid: this._invalid, + valid: this._valid, + helperText: 'helper text', inputProperties: { placeholder: 'Enter a value' + }, + onValidate: (valid: boolean | undefined) => { + this._valid.valid = valid; + this.invalidate(); } }) ] diff --git a/src/combobox/index.ts b/src/combobox/index.ts index a924f5bd9f..5609d55b2b 100644 --- a/src/combobox/index.ts +++ b/src/combobox/index.ts @@ -20,6 +20,7 @@ import { CommonMessages, LabeledProperties } from '../common/interfaces'; import * as css from '../theme/combobox.m.css'; import * as baseCss from '../common/styles/base.m.css'; import { customElement } from '@dojo/framework/widget-core/decorators/customElement'; +import HelperText from '../helper-text/index'; /** * @type ComboBoxProperties @@ -31,6 +32,7 @@ import { customElement } from '@dojo/framework/widget-core/decorators/customElem * @property getResultLabel Can be used to get the text label of a result based on the underlying result object * @property getResultSelected Can be used to highlight the selected result. Defaults to checking the result label * @property getResultValue Can be used to define a value returned by onChange when a given result is selected. Defaults to getResultLabel + * @property helpertext Displays text at bottom of widget * @property widgetId Optional id string for the combobox, set on the text input * @property inputProperties TextInput properties to set on the underlying input * @property invalid Determines if this input is valid @@ -54,9 +56,10 @@ export interface ComboBoxProperties extends ThemedProperties, LabeledProperties, getResultLabel?(result: any): DNode; getResultSelected?(result: any): boolean; getResultValue?(result: any): string; + helperText?: string; widgetId?: string; inputProperties?: TextInputProperties; - invalid?: boolean; + valid?: { valid?: boolean; message?: string } | boolean; isResultDisabled?(result: any): boolean; onBlur?(value: string, key?: string | number): void; onChange?(value: string, key?: string | number): void; @@ -64,6 +67,7 @@ export interface ComboBoxProperties extends ThemedProperties, LabeledProperties, onMenuChange?(open: boolean, key?: string | number): void; onRequestResults?(key?: string | number): void; onResultSelect?(result: any, key?: string | number): void; + onValidate?: (valid: boolean | undefined, message: string) => void; openOnFocus?: boolean; readOnly?: boolean; required?: boolean; @@ -85,14 +89,13 @@ export enum Operation { 'theme', 'classes', 'extraClasses', - 'labelAfter', 'labelHidden', 'clearable', 'disabled', 'inputProperties', - 'invalid', + 'valid', 'isResultDisabled', - 'labelAfter', + 'helperText', 'labelHidden', 'openOnFocus', 'readOnly', @@ -100,7 +103,15 @@ export enum Operation { 'results' ], attributes: ['widgetId', 'label', 'value'], - events: ['onBlur', 'onChange', 'onFocus', 'onMenuChange', 'onRequestResults', 'onResultSelect'] + events: [ + 'onBlur', + 'onChange', + 'onFocus', + 'onMenuChange', + 'onRequestResults', + 'onResultSelect', + 'onValidate' + ] }) export class ComboBox extends I18nMixin(ThemedMixin(FocusMixin(WidgetBase))) { private _activeIndex = 0; @@ -289,18 +300,17 @@ export class ComboBox extends I18nMixin(ThemedMixin(FocusMixin(WidgetBase))) w(ComboBox, { onChange })); h.trigger(`.${css.trigger}`, 'onclick', stubEvent); - h.expect(() => getExpectedVdom(false, true, false, {})); + h.expect(() => getExpectedVdom(false, false, false, {})); h.trigger('@textinput', 'onKeyDown', Keys.Down, preventDefault); h.trigger('@textinput', 'onKeyDown', Keys.Enter, preventDefault); @@ -607,7 +632,8 @@ registerSuite('ComboBox', { onBlur: noop, onFocus: noop, onInput: noop, - onKeyDown: noop + onKeyDown: noop, + onValidate: undefined }) ); }, @@ -628,12 +654,10 @@ registerSuite('ComboBox', { }, 'widget states render correctly'() { - let invalid = true; const h = harness(() => w(ComboBox, { ...testProperties, disabled: true, - invalid, readOnly: true, required: true }) @@ -648,7 +672,7 @@ registerSuite('ComboBox', { owns: '' }, widgetId: 'foo', - valid: false, + valid: undefined, focus: noop, disabled: true, readOnly: true, @@ -659,7 +683,8 @@ registerSuite('ComboBox', { onBlur: noop, onFocus: noop, onInput: noop, - onKeyDown: noop + onKeyDown: noop, + onValidate: undefined }) ); @@ -720,8 +745,6 @@ registerSuite('ComboBox', { ) ); - invalid = false; - h.expectPartial('@textinput', () => w(TextInput, { key: 'textinput', @@ -732,7 +755,7 @@ registerSuite('ComboBox', { owns: '' }, widgetId: 'foo', - valid: true, + valid: undefined, focus: noop, disabled: true, readOnly: true, @@ -742,7 +765,8 @@ registerSuite('ComboBox', { onBlur: noop, onFocus: noop, onInput: noop, - onKeyDown: noop + onKeyDown: noop, + onValidate: undefined }) ); }, @@ -811,6 +835,97 @@ registerSuite('ComboBox', { h.expectPartial('@dropdown', () => getExpectedMenu(true, true, { visualFocus: true })); h.trigger('@dropdown', 'onmouseover', stubEvent); h.expectPartial('@dropdown', () => getExpectedMenu(true, true)); + }, + + 'renders helpertext'() { + const h = harness(() => + w(ComboBox, { + ...testProperties, + helperText: helperText + }) + ); + h.expect(() => + getExpectedVdom( + true, + false, + true, + {}, + false, + { valid: undefined, message: undefined }, + helperText + ) + ); + }, + + 'renders validity correctly'() { + let h = harness(() => + w(ComboBox, { + ...testProperties, + valid: undefined + }) + ); + h.expect(() => + getExpectedVdom(true, false, true, {}, false, { + valid: undefined, + message: undefined + }) + ); + + h = harness(() => + w(ComboBox, { + ...testProperties, + valid: true + }) + ); + h.expect(() => + getExpectedVdom(true, false, true, {}, false, { valid: true, message: undefined }) + ); + + h = harness(() => + w(ComboBox, { + ...testProperties, + valid: false + }) + ); + h.expect(() => + getExpectedVdom(true, false, true, {}, false, { valid: false, message: undefined }) + ); + + h = harness(() => + w(ComboBox, { + ...testProperties, + valid: { valid: true, message: invalidMessage } + }) + ); + h.expect(() => + getExpectedVdom(true, false, true, {}, false, { + valid: true, + message: invalidMessage + }) + ); + + h = harness(() => + w(ComboBox, { + ...testProperties, + valid: { valid: false, message: invalidMessage } + }) + ); + h.expect(() => + getExpectedVdom(true, false, true, {}, false, { + valid: false, + message: invalidMessage + }) + ); + + h = harness(() => + w(ComboBox, { + ...testProperties, + valid: { valid: false, message: undefined } + }) + ); + h.expect(() => + getExpectedVdom(true, false, true, {}, false, { valid: false, message: undefined }) + ); } } }); diff --git a/src/text-input/index.ts b/src/text-input/index.ts index ad3120dfff..3212065dd6 100644 --- a/src/text-input/index.ts +++ b/src/text-input/index.ts @@ -39,7 +39,7 @@ interface TextInputInternalState { * @property maxLength Maximum number of characters allowed in the input * @property minLength Minimum number of characters allowed in the input * @property placeholder Placeholder text - * @property value The current value + * @property value The current value * @property leading Renderer for leading icon content * @property trailing Renderer for trailing icon content */ diff --git a/src/theme/combobox.m.css b/src/theme/combobox.m.css index 49a33e284b..787e46e6fe 100644 --- a/src/theme/combobox.m.css +++ b/src/theme/combobox.m.css @@ -29,7 +29,7 @@ cursor: pointer; border: none; background: none; - top: 50%; + top: .5rem; transform: translateY(-50%); padding: 0; } diff --git a/src/time-picker/index.ts b/src/time-picker/index.ts index 0100143941..b76579cea6 100644 --- a/src/time-picker/index.ts +++ b/src/time-picker/index.ts @@ -315,7 +315,7 @@ export class TimePicker extends ThemedMixin(FocusMixin(WidgetBase))