Skip to content

Commit

Permalink
feat: add helper text API (#174)
Browse files Browse the repository at this point in the history
- Add property and named slot
- Add styles for supported themes
- Add demo for helper text API
- Add unit and visual tests for helper text

Part of #170
  • Loading branch information
DiegoCardoso authored May 28, 2020
1 parent 6ddea58 commit 97105f7
Show file tree
Hide file tree
Showing 37 changed files with 294 additions and 5 deletions.
26 changes: 26 additions & 0 deletions demo/checkbox-basic-demos.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ <h3>Disabled state</h3>
</template>
</vaadin-demo-snippet>

<h3>Helper text</h3>
<p>Use the <code>helper-text</code> attribute or add content to the <code>helper</code> slot to set helper content.</p>
<vaadin-demo-snippet id="basic-demos-helper-text">
<template preserve-content>
<vaadin-checkbox-group label="Label" theme="vertical"
helper-text="Pick an option">
<vaadin-checkbox value="1">Option one</vaadin-checkbox>
<vaadin-checkbox value="2">Option two</vaadin-checkbox>
<vaadin-checkbox value="3">Option three</vaadin-checkbox>
</vaadin-checkbox-group>

<vaadin-checkbox-group label="Label" theme="vertical">
<span slot="helper">Pick an option</span>
<vaadin-checkbox value="1">Option one</vaadin-checkbox>
<vaadin-checkbox value="2">Option two</vaadin-checkbox>
<vaadin-checkbox value="3">Option three</vaadin-checkbox>
</vaadin-checkbox-group>

<style>
vaadin-checkbox-group:first-of-type {
margin-right: 200px;
}
</style>
</template>
</vaadin-demo-snippet>

<h3>Object list</h3>
<vaadin-demo-snippet id="object-list" when-defined="vaadin-checkbox">
<template preserve-content>
Expand Down
19 changes: 19 additions & 0 deletions demo/checkbox-theme-variants-demos.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ <h4>Vertical</h4>
</template>
</vaadin-demo-snippet>

<h3>Helper text position</h3>
<vaadin-demo-snippet id="theme-demos-helper-text-position">
<template preserve-content>
<h4>Below</h4>
<vaadin-checkbox-group label="Label" helper-text="Helper text below">
<vaadin-checkbox value="1" checked>Option one</vaadin-checkbox>
<vaadin-checkbox value="2">Option two</vaadin-checkbox>
<vaadin-checkbox value="3">Option three</vaadin-checkbox>
</vaadin-checkbox-group>

<h4>Above</h4>
<vaadin-checkbox-group label="Label" helper-text="Helper text above" theme="helper-above-field">
<vaadin-checkbox value="1" checked>Option one</vaadin-checkbox>
<vaadin-checkbox value="2">Option two</vaadin-checkbox>
<vaadin-checkbox value="3">Option three</vaadin-checkbox>
</vaadin-checkbox-group>
</template>
</vaadin-demo-snippet>

</template>
<script>
class CheckboxThemeVariantsDemos extends DemoReadyEventEmitter(CheckboxDemo(Polymer.Element)) {
Expand Down
68 changes: 64 additions & 4 deletions src/vaadin-checkbox-group.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
<slot id="slot"></slot>
</div>

<div part="helper-text"
on-click="focus"
aria-live="assertive"
aria-hidden$="[[_getHelperTextAriaHidden(helperText, _hasSlottedHelper)]]">
<slot name="helper">[[helperText]]</slot>
</div>

<div part="error-message"
aria-live="assertive"
aria-hidden$="[[_getErrorMessageAriaHidden(invalid, errorMessage)]]"
Expand Down Expand Up @@ -90,6 +97,8 @@
* `focused` | Set when the checkbox group contains focus | :host
* `has-label` | Set when the element has a label | :host
* `has-value` | Set when the element has a value | :host
* `has-helper` | Set when the element has helper text or slot | :host
* `has-error-message` | Set when the element has an error message, regardless if the field is valid or not | :host
* `required` | Set when the element is required | :host
* `invalid` | Set when the element is invalid | :host
*
Expand Down Expand Up @@ -144,7 +153,18 @@
*/
errorMessage: {
type: String,
value: ''
value: '',
observer: '_errorMessageChanged'
},

/**
* String used for the helper text.
* @type {string | null}
*/
helperText: {
type: String,
value: '',
observer: '_helperTextChanged'
},

/**
Expand All @@ -166,6 +186,9 @@
value: false
},

/** @private */
_hasSlottedHelper: Boolean,

};
}

Expand Down Expand Up @@ -214,6 +237,8 @@
}
});

this._setOrToggleHasHelperAttribute();

const hasValue = checkbox => {
const {value} = checkbox;
return checkbox.hasAttribute('value') || value && value !== 'on';
Expand Down Expand Up @@ -314,10 +339,29 @@

/** @private */
_labelChanged(label) {
if (label) {
this.setAttribute('has-label', '');
this._setOrToggleAttribute('has-label', label === 0 || !!label);
}

/** @private */
_errorMessageChanged(errorMessage) {
this._setOrToggleAttribute('has-error-message', errorMessage === 0 || !!errorMessage);
}

/** @private */
_helperTextChanged(helperText) {
this._setOrToggleAttribute('has-helper', helperText === 0 || !!helperText);
}

/** @private */
_setOrToggleAttribute(name, value) {
if (!name) {
return;
}

if (value) {
this.setAttribute(name, (typeof value === 'boolean') ? '' : value);
} else {
this.removeAttribute('has-label');
this.removeAttribute(name);
}
}

Expand Down Expand Up @@ -348,6 +392,22 @@
this.removeAttribute('focused');
}
}

/** @private */
_setOrToggleHasHelperAttribute() {
const slottedNodes = this.shadowRoot.querySelector(`[name="helper"]`).assignedNodes();
// Only has slotted helper if not a text node
// Text nodes are added by the helperText prop and not the helper slot
// The filter is added due to shady DOM triggering this slotchange event on helperText prop change
this._hasSlottedHelper = slottedNodes.filter(node => node.nodeType !== 3).length > 0;

this._setOrToggleAttribute('has-helper', this._hasSlottedHelper ? 'slotted' : this.helperText === 0 || !!this.helperText);
}

/** @private */
_getHelperTextAriaHidden(helperText, hasSlottedHelper) {
return (!(helperText || hasSlottedHelper)).toString();
}
}

customElements.define(CheckboxGroupElement.is, CheckboxGroupElement);
Expand Down
71 changes: 71 additions & 0 deletions test/vaadin-checkbox-group_test.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
</template>
</test-fixture>

<test-fixture id="default-with-slotted-helper">
<template>
<vaadin-checkbox-group>
<div slot="helper">foo</div>
</vaadin-checkbox-group>
</template>
</test-fixture>

<test-fixture id="ironForm">
<template>
<iron-form>
Expand Down Expand Up @@ -312,6 +320,69 @@
expect(console.warn.callCount).to.equal(0);
console.warn.restore();
});

it('setting errorMessage updates has-error-message attribute', function() {
vaadinCheckboxGroup.errorMessage = 'foo';
expect(vaadinCheckboxGroup.hasAttribute('has-error-message')).to.be.true;
});

it('setting errorMessage to empty string does not update has-error-message attribute', function() {
vaadinCheckboxGroup.errorMessage = '';
expect(vaadinCheckboxGroup.hasAttribute('has-error-message')).to.be.false;
});

it('setting errorMessage to null does not update has-error-message attribute', function() {
vaadinCheckboxGroup.errorMessage = null;
expect(vaadinCheckboxGroup.hasAttribute('has-error-message')).to.be.false;
});

describe('vaadin-radio-group helper text', () => {
it('setting helper updates has-helper attribute', function() {
vaadinCheckboxGroup.helperText = 'foo';
expect(vaadinCheckboxGroup.hasAttribute('has-helper')).to.be.true;
});

it('setting helper to empty string does not update has-helper attribute', function() {
vaadinCheckboxGroup.helperText = '';
expect(vaadinCheckboxGroup.hasAttribute('has-helper')).to.be.false;
});

it('setting helper to null does not update has-helper attribute', function() {
vaadinCheckboxGroup.helperText = null;
expect(vaadinCheckboxGroup.hasAttribute('has-helper')).to.be.false;
});

it('setting number helper updates has-helper attribute', function() {
vaadinCheckboxGroup.helperText = 0;
expect(vaadinCheckboxGroup.hasAttribute('has-helper')).to.be.true;
});

it('field with slotted helper updates has-helper attribute', function() {
const checkboxGroupWithSlottedHelper = fixture('default-with-slotted-helper');
checkboxGroupWithSlottedHelper._observer.flush();
expect(checkboxGroupWithSlottedHelper.hasAttribute('has-helper')).to.be.true;
});

it('setting helper with slot updates has-helper attribute', function() {
const helper = document.createElement('div');
helper.setAttribute('slot', 'helper');
helper.textContent = 'foo';
vaadinCheckboxGroup.appendChild(helper);
vaadinCheckboxGroup._observer.flush();

expect(vaadinCheckboxGroup.hasAttribute('has-helper')).to.be.true;
});

it('removing slotted helper removes has-helper attribute', function() {
const radioButtonGroupWithSlottedHelper = fixture('default-with-slotted-helper');

const helper = radioButtonGroupWithSlottedHelper.querySelector('[slot="helper"]');
radioButtonGroupWithSlottedHelper.removeChild(helper);
radioButtonGroupWithSlottedHelper._observer.flush();

expect(radioButtonGroupWithSlottedHelper.hasAttribute('has-helper')).to.be.false;
});
});
});

describe('vaadin-checkbox-group validation', () => {
Expand Down
23 changes: 23 additions & 0 deletions test/visual/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@
</vaadin-checkbox-group>
</div>

<div id="helper-text-tests">
<vaadin-checkbox-group helper-text="Helper text" label="Choose a number">
<vaadin-checkbox value="1">1</vaadin-checkbox>
<vaadin-checkbox value="2">2</vaadin-checkbox>
</vaadin-checkbox-group>
</div>

<div id="helper-text-above-field-tests">
<vaadin-checkbox-group helper-text="Helper text" label="Choose a number" theme="helper-above-field">
<vaadin-checkbox value="1">1</vaadin-checkbox>
<vaadin-checkbox value="2">2</vaadin-checkbox>
</vaadin-checkbox-group>
</div>

<div id="theme-vertical-group-tests">
<vaadin-checkbox-group label="Choose a number" theme="vertical">
<vaadin-checkbox value="1">1</vaadin-checkbox>
Expand All @@ -62,6 +76,15 @@
</vaadin-checkbox-group>
</div>

<div id="validation-with-helper-tests">
<vaadin-checkbox-group helper-text="Helper text" required
error-message="Please choose a number" label="Choose a number"
id="validation-checkbox-with-helper-group" invalid>
<vaadin-checkbox value="1">1</vaadin-checkbox>
<vaadin-checkbox value="2">2</vaadin-checkbox>
</vaadin-checkbox-group>
</div>

<div id="focus-tests">
<vaadin-checkbox-group label="Focused">
<vaadin-checkbox id="focus-checkbox" value="1">1</vaadin-checkbox>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions test/visual/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,31 @@ gemini.suite('vaadin-checkbox', function(rootSuite) {
.capture('default');
});

gemini.suite(`helper-text-tests-${theme}`, function(suite) {
suite
.setUrl(`default.html?theme=${theme}`)
.setCaptureElements('#helper-text-tests')
.capture('default');
});

if (theme === 'lumo') {
gemini.suite(`helper-text-above-field-tests-${theme}`, function(suite) {
suite
.setUrl(`default.html?theme=${theme}`)
.setCaptureElements('#helper-text-above-field-tests')
.capture('default');
});
}

if (theme === 'material') {
gemini.suite(`validation-with-helper-tests-${theme}`, function(suite) {
suite
.setUrl(`default.html?theme=${theme}`)
.setCaptureElements('#validation-with-helper-tests')
.capture('default');
});
}

gemini.suite(`default-rtl-tests-${theme}`, function(suite) {
suite
.setUrl(`default-rtl.html?theme=${theme}`)
Expand Down
49 changes: 48 additions & 1 deletion theme/lumo/vaadin-checkbox-group-styles.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,57 @@
color: var(--lumo-primary-text-color);
}

:host(:hover:not([disabled]):not([focused])) [part="label"] {
:host(:hover:not([disabled]):not([focused])) [part="label"],
:host(:hover:not([disabled]):not([focused])) [part="helper-text"],
:host(:hover:not([disabled]):not([focused])) [part="helper-text"] ::slotted(*) {
color: var(--lumo-body-text-color);
}

:host([has-helper]) [part="helper-text"]::before {
content: "";
display: block;
height: 0.4em;
}

[part="helper-text"],
[part="helper-text"] ::slotted(*) {
display: block;
color: var(--lumo-secondary-text-color);
font-size: var(--lumo-font-size-xs);
line-height: var(--lumo-line-height-xs);
margin-left: calc(var(--lumo-border-radius-m) / 4);
transition: color 0.2s;
}

/* helper-text position */

:host([has-helper][theme~="helper-above-field"]) [part="helper-text"]::before {
display: none;
}

:host([has-helper][theme~="helper-above-field"]) [part="helper-text"]::after {
content: "";
display: block;
height: 0.4em;
}

:host([has-helper][theme~="helper-above-field"]) [part="label"] {
order: 0;
padding-bottom: 0.4em;
}

:host([has-helper][theme~="helper-above-field"]) [part="helper-text"] {
order: 1;
}

:host([has-helper][theme~="helper-above-field"]) [part="group-field"] {
order: 2;
}

:host([has-helper][theme~="helper-above-field"]) [part="error-message"] {
order: 3;
}

/* Touch device adjustment */
@media (pointer: coarse) {
:host(:hover:not([disabled]):not([focused])) [part="label"] {
Expand Down
Loading

0 comments on commit 97105f7

Please sign in to comment.