diff --git a/packages/web-components/src/components/filter-panel/__stories__/filter-panel.stories.ts b/packages/web-components/src/components/filter-panel/__stories__/filter-panel.stories.ts
index b992f89db6a..dc2929a5ca7 100644
--- a/packages/web-components/src/components/filter-panel/__stories__/filter-panel.stories.ts
+++ b/packages/web-components/src/components/filter-panel/__stories__/filter-panel.stories.ts
@@ -1,25 +1,30 @@
/**
* @license
*
- * Copyright IBM Corp. 2020, 2021
+ * Copyright IBM Corp. 2020, 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { html } from 'lit-element';
-import { text, select } from '@storybook/addon-knobs';
+import { text, select, number } from '@storybook/addon-knobs';
import '../index';
import readme from './README.stories.mdx';
export const Default = ({ parameters }) => {
- const { heading, gridKnobs } = parameters?.props?.FilterPanel ?? {};
+ const { heading, filterCutoff, maxFilters, viewAllText, gridKnobs } = parameters?.props?.FilterPanel ?? {};
return html`
${heading}
-
+
API
Application
Data Set
@@ -109,6 +114,9 @@ export default {
knobs: {
FilterPanel: ({ groupId }) => ({
heading: text('heading', 'Filter', groupId),
+ filterCutoff: number('Filter cutoff', 5, {}, groupId),
+ maxFilters: number('Max filters', 7, {}, groupId),
+ viewAllText: text('View all text', 'View all', groupId),
gridKnobs: select('Grid alignment', ['3 columns', '4 columns'], '4 columns', groupId),
}),
},
@@ -116,6 +124,9 @@ export default {
default: {
FilterPanel: {
heading: 'Filter',
+ filterCutoff: 5,
+ maxFilters: 7,
+ viewAllText: 'View all',
gridKnobs: '4 columns',
},
},
diff --git a/packages/web-components/src/components/filter-panel/filter-group-item.ts b/packages/web-components/src/components/filter-panel/filter-group-item.ts
index 8c8901a7010..2913f341320 100644
--- a/packages/web-components/src/components/filter-panel/filter-group-item.ts
+++ b/packages/web-components/src/components/filter-panel/filter-group-item.ts
@@ -1,19 +1,27 @@
/**
* @license
*
- * Copyright IBM Corp. 2020, 2021
+ * Copyright IBM Corp. 2020, 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
-import { customElement } from 'lit-element';
+import { customElement, property, query, state } from 'lit-element';
+import settings from 'carbon-components/es/globals/js/settings';
import ddsSettings from '@carbon/ibmdotcom-utilities/es/utilities/settings/settings.js';
import BXAccordionItem from 'carbon-web-components/es/components/accordion/accordion-item';
import styles from './filter-panel.scss';
import StableSelectorMixin from '../../globals/mixins/stable-selector';
+import DDSFilterPanelComposite from './filter-panel-composite';
+import DDSFilterPanelCheckbox from './filter-panel-checkbox';
+import DDSFilterPanelInputSelectItem from './filter-panel-input-select-item';
+import DDSFilterPanelInputSelect from './filter-panel-input-select';
const { stablePrefix: ddsPrefix } = ddsSettings;
+const { prefix } = settings;
+
+const viewAllClassName = `${ddsPrefix}-filter-group-item__view-all`;
/**
* DDSFilterGroupItem renders each individual accordion
@@ -30,6 +38,207 @@ class DDSFilterGroupItem extends StableSelectorMixin(BXAccordionItem) {
}
static styles = styles; // `styles` here is a `CSSResult` generated by custom WebPack loader
+
+ static get viewAllSelector(): string {
+ return `button.${viewAllClassName}`;
+ }
+
+ /**
+ * The element containing the default slot.
+ */
+ @query(`.${prefix}--accordion__content`)
+ accordionContent: any;
+
+ /**
+ * The text for the button that reveals all filters in the group.
+ */
+ @property({ type: String, attribute: 'view-all-text' })
+ viewAllText: string = 'View all';
+
+ /**
+ * The number of filters that can be shown without needing to hide any.
+ */
+ @property({ type: Number, attribute: 'max-filters' })
+ maxFilters: number = 7;
+
+ /**
+ * The number of filters to show when not all filters are visible.
+ */
+ @property({ type: Number, attribute: 'filter-cutoff' })
+ filterCutoff: number = 5;
+
+ /**
+ * Whether or not any hidden filters have been revealed.
+ */
+ @property({ type: Boolean })
+ allRevealed = false;
+
+ /**
+ * An element to set focus to on reveal.
+ */
+ @state()
+ _focusedElement: HTMLElement | null = null;
+
+ /**
+ * Whether or not to add view all button functionality.
+ */
+ protected _needsViewAll(): boolean {
+ return this.children.length > this.maxFilters;
+ }
+
+ /**
+ * Checks if any filters beyond the cutoff point have been selected.
+ */
+ protected _hasHiddenActiveFilter(): boolean {
+ const { children, filterCutoff } = this;
+ let result: boolean = false;
+
+ [...children].slice(filterCutoff, children.length).forEach(elem => {
+ if (elem instanceof DDSFilterPanelCheckbox) {
+ if (elem.checked) result = true;
+ }
+ if (elem instanceof DDSFilterPanelInputSelectItem || elem instanceof DDSFilterPanelInputSelect) {
+ if (elem.selected) result = true;
+ }
+ });
+
+ return result;
+ }
+
+ /**
+ * Hides or reveals excess filters.
+ */
+ protected _handleAllRevealed(revealed: boolean): void {
+ const { children, filterCutoff, accordionContent } = this;
+ const hasHiddenActiveFilter = this._hasHiddenActiveFilter();
+
+ [...children].slice(filterCutoff, children.length).forEach(elem => {
+ (elem as HTMLElement).style.display = revealed || hasHiddenActiveFilter ? '' : 'none';
+ });
+
+ if (!revealed && !hasHiddenActiveFilter) {
+ accordionContent.appendChild(this._renderViewAll());
+ }
+
+ this._dispatchViewAllEvent(revealed);
+ }
+
+ /**
+ * Generates a view all button.
+ */
+ protected _renderViewAll(): HTMLButtonElement {
+ const { children, filterCutoff } = this;
+
+ const viewAll = document.createElement('button');
+ viewAll.classList.add(viewAllClassName, `${prefix}--btn--ghost`);
+ viewAll.type = 'button';
+ viewAll.innerText = this.viewAllText;
+
+ viewAll.addEventListener(
+ 'click',
+ (e): void => {
+ this.allRevealed = true;
+ if (e.target instanceof HTMLElement) e.target.remove();
+
+ const firstHidden = children[filterCutoff];
+ if (firstHidden instanceof HTMLElement) {
+ this._focusedElement = firstHidden;
+ }
+ },
+ { passive: true, once: true }
+ );
+
+ return viewAll;
+ }
+
+ /**
+ * Dispatches a custom event that notifies listeners whether or not this
+ * filter group has all options revealed.
+ */
+ protected _dispatchViewAllEvent(removed: boolean): void {
+ const { eventViewAll } = this.constructor as typeof DDSFilterGroupItem;
+ this.dispatchEvent(
+ new CustomEvent(eventViewAll, {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ detail: {
+ id: this.titleText,
+ value: removed,
+ },
+ })
+ );
+ }
+
+ /**
+ * Retrieves view all state stored in the filter panel composite. Returns
+ * internal value if no cache is found.
+ */
+ protected _getCachedViewAllValue(): boolean {
+ const { allRevealed, titleText } = this;
+ let result: boolean = allRevealed;
+
+ const filterPanel = this.closest('dds-filter-panel');
+ if (filterPanel !== null) {
+ // Indicates this is composite's duplicated content.
+ let parentHost: Element | undefined;
+ const parent = filterPanel.parentNode;
+ if (parent instanceof ShadowRoot) {
+ parentHost = parent.host;
+ }
+ if (parentHost instanceof DDSFilterPanelComposite) {
+ const match = parentHost._filterGroupsAllRevealed.find(entry => {
+ return entry.id === titleText;
+ });
+ if (match !== undefined) {
+ result = match.value;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ protected firstUpdated(): void {
+ if (this._needsViewAll()) {
+ this.allRevealed = this._getCachedViewAllValue();
+ }
+ }
+
+ protected updated(_changedProperties: Map): void {
+ const { allRevealed, _focusedElement } = this;
+ if (this._needsViewAll()) {
+ const prevOpen = _changedProperties.get('open');
+ const hasAllRevealed = _changedProperties.has('allRevealed');
+ const prevAllRevealed = _changedProperties.get('allRevealed');
+
+ // Reset `allRevealed` on accordion close.
+ if (prevOpen) {
+ this.allRevealed = this._hasHiddenActiveFilter() || false;
+ }
+
+ // Respect `allRevealed` attribute.
+ if (hasAllRevealed) {
+ if (prevAllRevealed === undefined) {
+ this._handleAllRevealed(this._getCachedViewAllValue());
+ } else {
+ this._handleAllRevealed(allRevealed);
+
+ if (allRevealed && _focusedElement instanceof HTMLElement) {
+ _focusedElement.focus();
+ this._focusedElement = null;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The name of the event that fires when the view all button is toggled.
+ */
+ static get eventViewAll() {
+ return `${ddsPrefix}-filter-group-view-all-toggle`;
+ }
}
/* @__GENERATE_REACT_CUSTOM_ELEMENT_TYPE__ */
diff --git a/packages/web-components/src/components/filter-panel/filter-panel-composite.ts b/packages/web-components/src/components/filter-panel/filter-panel-composite.ts
index b2abf025aeb..7e4dae02ac3 100644
--- a/packages/web-components/src/components/filter-panel/filter-panel-composite.ts
+++ b/packages/web-components/src/components/filter-panel/filter-panel-composite.ts
@@ -7,7 +7,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import { customElement, html, LitElement, property } from 'lit-element';
+import { customElement, html, LitElement, property, TemplateResult } from 'lit-element';
import ddsSettings from '@carbon/ibmdotcom-utilities/es/utilities/settings/settings.js';
import settings from 'carbon-components/es/globals/js/settings';
import Filter from 'carbon-web-components/es/icons/filter/16';
@@ -15,7 +15,6 @@ import HostListenerMixin from 'carbon-web-components/es/globals/mixins/host-list
import './filter-group';
import './filter-panel';
import './filter-panel-modal';
-import { baseFontSize, breakpoints } from '@carbon/layout';
import { unsafeHTML } from 'lit-html/directives/unsafe-html';
import HostListener from 'carbon-web-components/es/globals/decorators/host-listener';
import StableSelectorMixin from '../../globals/mixins/stable-selector';
@@ -25,7 +24,6 @@ import DDSFilterGroupItem from './filter-group-item';
const { prefix } = settings;
const { stablePrefix: ddsPrefix } = ddsSettings;
-const gridBreakpoint = parseFloat(breakpoints.md.width) * baseFontSize;
/**
* Filter panel composite
@@ -118,6 +116,19 @@ class DDSFilterPanelComposite extends HostListenerMixin(StableSelectorMixin(LitE
this.renderStatus();
};
+ @HostListener('document:eventFilterGroupViewAllToggle')
+ protected _handleFilterGroupViewAllToggle = (event: CustomEvent) => {
+ const match = this._filterGroupsAllRevealed.findIndex(entry => {
+ return entry.id === event.detail.id;
+ });
+
+ if (match !== -1) {
+ this._filterGroupsAllRevealed[match].value = event.detail.value;
+ } else {
+ this._filterGroupsAllRevealed.push(event.detail);
+ }
+ };
+
/**
* handles modal close event
*/
@@ -271,6 +282,12 @@ class DDSFilterPanelComposite extends HostListenerMixin(StableSelectorMixin(LitE
@property()
_filterButtonTitle: string = '';
+ /**
+ * stores which filter groups have revealed filters
+ */
+ @property({ type: Array })
+ _filterGroupsAllRevealed: { id: string; value: boolean }[] = [];
+
/**
* Handles `slotchange` event.
*
@@ -300,31 +317,41 @@ class DDSFilterPanelComposite extends HostListenerMixin(StableSelectorMixin(LitE
this._filterButtonTitle = this._title[0].innerText;
}
- protected _renderButton = gridBreakpoint < document.body.clientHeight;
+ /**
+ * Renders original content into the modal and listens for changes to this
+ * content to then be stored in `this._content`.
+ */
+ protected _renderModal = (): TemplateResult => html`
+
+
+
+
+ `;
+
+ /**
+ * Renders copies of slotted elements into the desktop presentation.
+ */
+ protected _renderDesktop = (): TemplateResult => html`
+
+ ${this._title.map(e => {
+ return html`
+ ${unsafeHTML((e as HTMLElement).outerHTML)}
+ `;
+ })}
+ ${this._contents.map(e => {
+ return html`
+ ${unsafeHTML((e as HTMLElement).outerHTML)}
+ `;
+ })}
+
+ `;
render() {
return html`
-
-
-
-
-
-
-
- ${this._title.map(e => {
- return html`
- ${unsafeHTML((e as HTMLElement).outerHTML)}
- `;
- })}
- ${this._contents.map(e => {
- return html`
- ${unsafeHTML((e as HTMLElement).outerHTML)}
- `;
- })}
-
+ ${this._renderModal()} ${this._renderDesktop()}
`;
}
@@ -344,6 +371,15 @@ class DDSFilterPanelComposite extends HostListenerMixin(StableSelectorMixin(LitE
return `${ddsPrefix}-filter-panel-input-select`;
}
+ /**
+ * The name of the custom event captured upon activating "view all" button in
+ * a filter group item
+ */
+
+ static get eventFilterGroupViewAllToggle() {
+ return `${ddsPrefix}-filter-group-view-all-toggle`;
+ }
+
/**
* The name of the custom event captured upon closing the modal
*/
diff --git a/packages/web-components/src/components/filter-panel/filter-panel-input-select.ts b/packages/web-components/src/components/filter-panel/filter-panel-input-select.ts
index 78d74fad31c..21d73d01c06 100644
--- a/packages/web-components/src/components/filter-panel/filter-panel-input-select.ts
+++ b/packages/web-components/src/components/filter-panel/filter-panel-input-select.ts
@@ -1,7 +1,7 @@
/**
* @license
*
- * Copyright IBM Corp. 2020, 2021
+ * Copyright IBM Corp. 2020, 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
@@ -11,6 +11,7 @@ import { customElement, html, property, LitElement } from 'lit-element';
import ddsSettings from '@carbon/ibmdotcom-utilities/es/utilities/settings/settings.js';
import settings from 'carbon-components/es/globals/js/settings';
import Close from 'carbon-web-components/es/icons/close/16';
+import FocusMixin from 'carbon-web-components/es/globals/mixins/focus.js';
import StableSelectorMixin from '../../globals/mixins/stable-selector';
import styles from './filter-panel.scss';
import DDSFilterPanelInputSelectItem from './filter-panel-input-select-item';
@@ -24,7 +25,7 @@ const { stablePrefix: ddsPrefix } = ddsSettings;
* @element dds-filter-panel-input-select
*/
@customElement(`${ddsPrefix}-filter-panel-input-select`)
-class DDSFilterPanelInputSelect extends StableSelectorMixin(LitElement) {
+class DDSFilterPanelInputSelect extends FocusMixin(StableSelectorMixin(LitElement)) {
@property()
ariaLabel = '';
diff --git a/packages/web-components/src/components/filter-panel/filter-panel.scss b/packages/web-components/src/components/filter-panel/filter-panel.scss
index 7d8e257fb74..e0dbb4a61c4 100644
--- a/packages/web-components/src/components/filter-panel/filter-panel.scss
+++ b/packages/web-components/src/components/filter-panel/filter-panel.scss
@@ -1,7 +1,7 @@
/**
* @license
*
- * Copyright IBM Corp. 2020, 2021
+ * Copyright IBM Corp. 2020, 2022
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
@@ -50,6 +50,21 @@
}
}
+:host(#{$dds-prefix}-filter-group-item) {
+ .#{$dds-prefix}-filter-group-item__view-all {
+ width: 100%;
+ padding: $spacing-03 $spacing-05;
+ text-align: left;
+ cursor: pointer;
+
+ &:focus-visible {
+ outline: none;
+ border-color: $focus;
+ box-shadow: inset 0 0 0 $button-outline-width $focus, inset 0 0 0 $button-border-width $ui-background;
+ }
+ }
+}
+
:host(#{$dds-prefix}-filter-modal-footer-button) {
@extend :host(#{$prefix}-modal-footer-button);
diff --git a/packages/web-components/tests/e2e-storybook/cypress/integration/filter-panel/filter-panel.e2e.js b/packages/web-components/tests/e2e-storybook/cypress/integration/filter-panel/filter-panel.e2e.js
index 765bd1b8fb6..5e494482ec4 100644
--- a/packages/web-components/tests/e2e-storybook/cypress/integration/filter-panel/filter-panel.e2e.js
+++ b/packages/web-components/tests/e2e-storybook/cypress/integration/filter-panel/filter-panel.e2e.js
@@ -103,6 +103,101 @@ describe('dds-filter-panel | (desktop)', () => {
.should('have.attr', 'selected');
cy.screenshot(_screenshotOptions);
});
+
+ it('should only add view all button when enough filters are present', () => {
+ let filterCount;
+
+ cy.visit(`${_path}&knob-Filter%20cutoff_FilterPanel=1&knob-Max%20filters_FilterPanel=1`)
+ .get(_selector)
+ .shadow()
+ .find('dds-filter-group-item')
+ .first()
+ .as('filterGroupItem')
+ .click()
+ .find('.dds-filter-group-item__view-all')
+ .should('have.length', 1)
+ .click()
+ .get('@filterGroupItem')
+ .find('dds-filter-panel-checkbox')
+ .then(checkboxes => (filterCount = checkboxes.length));
+ cy.visit(`${_path}&knob-Max%20filters_FilterPanel=${filterCount}`)
+ .get(_selector)
+ .shadow()
+ .find('dds-filter-group-item')
+ .first()
+ .click()
+ .find('.dds-filter-group-item__view-all')
+ .should('have.length', 0);
+ });
+
+ it('should support custom view all button text', () => {
+ let customText = 'Foo button';
+
+ cy.visit(`${_path}&knob-View%20all%20text_FilterPanel=${customText}`)
+ .get(_selector)
+ .shadow()
+ .find('dds-filter-group-item')
+ .first()
+ .click()
+ .find('.dds-filter-group-item__view-all')
+ .should('have.text', customText);
+ cy.screenshot(_screenshotOptions);
+ });
+
+ it('should re-hide excess elements when filter groups are closed and reopened', () => {
+ cy.visit(_path)
+ .get(_selector)
+ .shadow()
+ .find('dds-filter-group-item')
+ .first()
+ .as('filterGroupItem')
+ .shadow()
+ .find('.bx--accordion__heading')
+ .as('toggle')
+ .click()
+ .get('@filterGroupItem')
+ .find('.dds-filter-group-item__view-all')
+ .click()
+ .get('@filterGroupItem')
+ .find('dds-filter-panel-checkbox')
+ .last()
+ .as('lastCheckbox')
+ .get('@toggle')
+ .click()
+ .click()
+ .get('@lastCheckbox')
+ .should('not.be.visible');
+ cy.screenshot(_screenshotOptions);
+ });
+
+ it('should not re-hide elements when an element that would be hidden has been selected', () => {
+ cy.visit(_path)
+ .get(_selector)
+ .shadow()
+ .find('dds-filter-group-item')
+ .first()
+ .as('filterGroupItem')
+ .shadow()
+ .find('.bx--accordion__heading')
+ .as('toggle')
+ .click()
+ .get('@filterGroupItem')
+ .find('.dds-filter-group-item__view-all')
+ .click()
+ .get('@filterGroupItem')
+ .find('dds-filter-panel-checkbox')
+ .last()
+ .as('lastCheckbox')
+ .shadow()
+ .find('input[type="checkbox')
+ .check(_checkOptions)
+ .get('@toggle')
+ .click()
+ .click()
+ .get('@lastCheckbox')
+ .should('be.visible');
+ cy.screenshot(_screenshotOptions);
+ });
});
describe('dds-filter-panel | (mobile)', () => {
@@ -111,13 +206,12 @@ describe('dds-filter-panel | (mobile)', () => {
});
it('checkboxes should maintain state when transitioning to desktop', () => {
- // Visit on mobile and open modal
+ // Check box on mobile
cy.visit(_path)
.get(_selector)
.find('.bx--filter-button')
- .click();
- // Check box on mobile
- cy.get(_selector)
+ .click()
+ .get(_selector)
.find('dds-filter-group-item')
.first()
.click()
@@ -143,13 +237,12 @@ describe('dds-filter-panel | (mobile)', () => {
});
it('select lists should maintain state when transitioning to desktop', () => {
- // Visit on mobile and open modal
+ // Check box on mobile
cy.visit(_path)
.get(_selector)
.find('.bx--filter-button')
- .click();
- // Check box on mobile
- cy.get(_selector)
+ .click()
+ .get(_selector)
.find('dds-filter-group-item')
.eq(1)
.click()
@@ -169,4 +262,109 @@ describe('dds-filter-panel | (mobile)', () => {
.should('have.attr', 'selected');
cy.screenshot(_screenshotOptions);
});
+
+ it('should only add view all button when enough filters are present', () => {
+ let filterCount;
+
+ cy.visit(`${_path}&knob-Filter%20cutoff_FilterPanel=1&knob-Max%20filters_FilterPanel=1`)
+ .get(_selector)
+ .find('.bx--filter-button')
+ .click()
+ .get(_selector)
+ .find('dds-filter-group-item')
+ .first()
+ .as('filterGroupItem')
+ .click()
+ .find('.dds-filter-group-item__view-all')
+ .should('have.length', 1)
+ .click()
+ .get('@filterGroupItem')
+ .find('dds-filter-panel-checkbox')
+ .then(checkboxes => (filterCount = checkboxes.length));
+ cy.visit(`${_path}&knob-Max%20filters_FilterPanel=${filterCount}`)
+ .get(_selector)
+ .find('.bx--filter-button')
+ .click()
+ .get(_selector)
+ .find('dds-filter-group-item')
+ .first()
+ .click()
+ .find('.dds-filter-group-item__view-all')
+ .should('have.length', 0);
+ });
+
+ it('should support custom view all button text', () => {
+ let customText = 'Foo button';
+
+ cy.visit(`${_path}&knob-View%20all%20text_FilterPanel=${customText}`)
+ .get(_selector)
+ .find('.bx--filter-button')
+ .click()
+ .get(_selector)
+ .find('dds-filter-group-item')
+ .first()
+ .click()
+ .find('.dds-filter-group-item__view-all')
+ .should('have.text', customText);
+ cy.screenshot(_screenshotOptions);
+ });
+
+ it('should re-hide excess elements when filter groups are closed and reopened', () => {
+ cy.visit(_path)
+ .get(_selector)
+ .find('.bx--filter-button')
+ .click()
+ .get(_selector)
+ .find('dds-filter-group-item')
+ .first()
+ .as('filterGroupItem')
+ .shadow()
+ .find('.bx--accordion__heading')
+ .as('toggle')
+ .click()
+ .get('@filterGroupItem')
+ .find('.dds-filter-group-item__view-all')
+ .click()
+ .get('@filterGroupItem')
+ .find('dds-filter-panel-checkbox')
+ .last()
+ .as('lastCheckbox')
+ .get('@toggle')
+ .click()
+ .click()
+ .get('@lastCheckbox')
+ .should('not.be.visible');
+ cy.screenshot(_screenshotOptions);
+ });
+
+ it('should not re-hide elements when an element that would be hidden has been selected', () => {
+ cy.visit(_path)
+ .get(_selector)
+ .find('.bx--filter-button')
+ .click()
+ .get(_selector)
+ .find('dds-filter-group-item')
+ .first()
+ .as('filterGroupItem')
+ .shadow()
+ .find('.bx--accordion__heading')
+ .as('toggle')
+ .click()
+ .get('@filterGroupItem')
+ .find('.dds-filter-group-item__view-all')
+ .click()
+ .get('@filterGroupItem')
+ .find('dds-filter-panel-checkbox')
+ .last()
+ .as('lastCheckbox')
+ .shadow()
+ .find('input[type="checkbox"]')
+ .check(_checkOptions)
+ .get('@toggle')
+ .click()
+ .click()
+ .get('@lastCheckbox')
+ .should('be.visible');
+ cy.screenshot(_screenshotOptions);
+ });
});