diff --git a/src/components/list/list-item.ts b/src/components/list/list-item.ts
index 9745903e49..befc359e69 100644
--- a/src/components/list/list-item.ts
+++ b/src/components/list/list-item.ts
@@ -24,6 +24,11 @@ export interface ListItem {
*/
iconColor?: string;
+ /**
+ * True if a checkbox of this item should be checked
+ */
+ checked?: boolean;
+
[data: string]: any;
}
diff --git a/src/components/list/list-renderer-config.ts b/src/components/list/list-renderer-config.ts
index f3646bb7d8..590503d9f5 100644
--- a/src/components/list/list-renderer-config.ts
+++ b/src/components/list/list-renderer-config.ts
@@ -3,4 +3,5 @@ export interface ListRendererConfig {
isOpen?: boolean;
selectable?: boolean;
badgeIcons?: boolean;
+ includeCheckboxes?: boolean;
}
diff --git a/src/components/list/list-renderer.tsx b/src/components/list/list-renderer.tsx
index 5380924e6d..ff85f8debf 100644
--- a/src/components/list/list-renderer.tsx
+++ b/src/components/list/list-renderer.tsx
@@ -7,6 +7,7 @@ export class ListRenderer {
isOpen: true,
selectable: false,
badgeIcons: false,
+ includeCheckboxes: false,
};
public render(
@@ -15,7 +16,6 @@ export class ListRenderer {
) {
items = items || [];
config = { ...this.defaultConfig, ...config };
-
const twoLines = items.some(item => {
return 'secondaryText' in item && !!item.secondaryText;
});
@@ -66,12 +66,19 @@ export class ListRenderer {
return (
+ {config.includeCheckboxes
+ ? this.renderCheckboxes(
+ item.checked,
+ item.disabled,
+ item.id
+ )
+ : null}
{item.icon ? this.renderIcon(item) : null}
{this.renderText(item.text, item.secondaryText)}
@@ -88,7 +95,7 @@ export class ListRenderer {
*/
private renderText(text: string, secondaryText?: string) {
if (!secondaryText) {
- return text;
+ return ;
}
return (
@@ -123,4 +130,45 @@ export class ListRenderer {
/>
);
}
+
+ /**
+ *
+ * Render a checkbox for a list item
+ * @param checked
+ * @param disabled
+ * @returns {HTMLElement} the checkbox if includeCheckboxes true
+ */
+
+ private renderCheckboxes(
+ checked: boolean,
+ disabled: boolean,
+ value: number
+ ) {
+ return (
+
+ );
+ }
}
diff --git a/src/components/list/list.e2e.ts b/src/components/list/list.e2e.ts
index dd0a00f016..6c80b5a5c5 100644
--- a/src/components/list/list.e2e.ts
+++ b/src/components/list/list.e2e.ts
@@ -133,8 +133,8 @@ describe('limel-list', async () => {
expect(innerList).toHaveClass('mdc-list--two-line');
});
it('renders items withOUT secondary text as single line', () => {
- expect(innerList.children[0].children).toHaveLength(0);
- expect(innerList.children[0]).toEqualText('item 1');
+ expect(innerList.children[0].children[0].children).toHaveLength(0);
+ expect(innerList.children[0].children[0]).toEqualText('item 1');
});
it('renders items WITH secondary text as two lines', () => {
expect(innerList.children[2].children).toHaveLength(1);
@@ -222,4 +222,42 @@ describe('limel-list', async () => {
});
});
});
+
+ describe('when the attribute `includeCheckboxes`', () => {
+ let items;
+ beforeEach(async () => {
+ items = [{ text: 'item 1' }];
+ await limelList.setProperty('items', items);
+ await page.waitForChanges();
+ });
+ describe('is not set', () => {
+ it('is not selectable and does not contain checkboxes', () => {
+ expect(innerList).not.toHaveClass('selectable');
+ expect(innerList.children[0].children).toHaveLength(1);
+ });
+ it('the property is falsy', async () => {
+ const propValue = await limelList.getProperty(
+ 'includeCheckboxes'
+ );
+ expect(propValue).toBeFalsy();
+ });
+ });
+
+ describe('is set', () => {
+ beforeEach(async () => {
+ await limelList.setProperty('includeCheckboxes', true);
+ await page.waitForChanges();
+ });
+ it('is selectable and contains checkboxes', () => {
+ expect(innerList).toHaveClass('selectable');
+ expect(innerList.children[0].children).toHaveLength(2); // label and checkbox
+ });
+ it('the property is `true`', async () => {
+ const propValue = await limelList.getProperty(
+ 'includeCheckboxes'
+ );
+ expect(propValue).toBe(true);
+ });
+ });
+ });
});
diff --git a/src/components/list/list.mdx b/src/components/list/list.mdx
index 939b60f9ac..eacaab12db 100644
--- a/src/components/list/list.mdx
+++ b/src/components/list/list.mdx
@@ -24,3 +24,6 @@ menu: Components
### List with badge icons
+
+### List with checkboxes
+
diff --git a/src/components/list/list.scss b/src/components/list/list.scss
index 604cdd356f..0c7ae59d9b 100644
--- a/src/components/list/list.scss
+++ b/src/components/list/list.scss
@@ -1,4 +1,5 @@
@import "@lime-material/list/mdc-list";
+@import '@lime-material/checkbox/mdc-checkbox';
:host {
display: block;
diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx
index 93f9f08972..94a7b6234b 100644
--- a/src/components/list/list.tsx
+++ b/src/components/list/list.tsx
@@ -1,5 +1,14 @@
+import { MDCCheckbox } from '@lime-material/checkbox';
+import { MDCFormField } from '@lime-material/form-field';
import { MDCList } from '@lime-material/list';
-import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core';
+import {
+ Component,
+ Element,
+ Event,
+ EventEmitter,
+ Prop,
+ State,
+} from '@stencil/core';
import { ListItem, ListSeparator } from '../../interface';
import { ListRenderer } from './list-renderer';
import { ListRendererConfig } from './list-renderer-config';
@@ -28,6 +37,12 @@ export class List {
@Prop()
public badgeIcons: boolean;
+ /**
+ * True if each list item should be trailed be a checkbox
+ */
+ @Prop()
+ public includeCheckboxes: boolean;
+
@Element()
private element: HTMLElement;
@@ -35,47 +50,64 @@ export class List {
private listRenderer = new ListRenderer();
/**
- * Fired when a new value has been selected from the list. Only fired if selectable is set to true
+ * Fired when a new value has been selected from the list. Only fired if selectable or includeCheckboxes is set to true
*/
@Event()
private change: EventEmitter;
+ @State()
+ private mdcCheckboxes = [];
+
public componentDidLoad() {
this.mdcList = new MDCList(
this.element.shadowRoot.querySelector('.mdc-list')
);
- if (!this.selectable) {
- return;
- }
+ if (this.selectable || this.includeCheckboxes) {
+ this.mdcList.singleSelection = true;
- this.mdcList.singleSelection = true;
+ // This is ugly and not the right way to do it.
+ // A better way would be to implement our own
+ // adapter and foundation classes for MDCList
+ // that works with the shadow DOM
+ this.mdcList.foundation_.adapter_.getFocusedElementIndex = () => {
+ return this.mdcList.listElements.indexOf(
+ this.element.shadowRoot.activeElement
+ );
+ };
+ const setSelectedIndex = this.mdcList.foundation_.setSelectedIndex;
+ const self = this; // tslint:disable-line:no-this-assignment
+ this.mdcList.foundation_.setSelectedIndex = function(...args) {
+ setSelectedIndex.apply(this, args);
+ self.handleSelectItem(args[0]);
+ };
+ if (this.includeCheckboxes) {
+ const elements = Array.from(
+ this.element.shadowRoot.querySelectorAll('.mdc-form-field')
+ );
- // This is ugly and not the right way to do it.
- // A better way would be to implement our own
- // adapter and foundation classes for MDCList
- // that works with the shadow DOM
- this.mdcList.foundation_.adapter_.getFocusedElementIndex = () => {
- return this.mdcList.listElements.indexOf(
- this.element.shadowRoot.activeElement
- );
- };
- const setSelectedIndex = this.mdcList.foundation_.setSelectedIndex;
- const self = this; // tslint:disable-line:no-this-assignment
- this.mdcList.foundation_.setSelectedIndex = function(...args) {
- setSelectedIndex.apply(this, args);
- self.handleSelectItem(args[0]);
- };
+ elements.forEach(element => {
+ const formField = new MDCFormField(element);
+ const checkbox = new MDCCheckbox(element.firstChild);
+ formField.input = checkbox;
+ this.mdcCheckboxes.push(checkbox);
+ });
+ }
+ }
}
public componentDidUnload() {
this.mdcList.destroy();
+ this.mdcCheckboxes.forEach(checkbox => {
+ checkbox.destroy();
+ });
}
public render() {
const config: ListRendererConfig = {
- selectable: this.selectable,
+ selectable: this.selectable || this.includeCheckboxes,
badgeIcons: this.badgeIcons,
+ includeCheckboxes: this.includeCheckboxes,
};
return this.listRenderer.render(this.items, config);
}
@@ -88,16 +120,32 @@ export class List {
* @returns {void}
*/
private handleSelectItem(index: number) {
- const selectedElement = this.element.shadowRoot.querySelector(
- '.mdc-list-item--selected'
- );
- let selectedItem: ListItem = null;
- if (selectedElement) {
- selectedItem = this.items.filter(item => {
- return !('separator' in item);
- })[index] as ListItem;
- }
+ if (this.selectable) {
+ const selectedElement = this.element.shadowRoot.querySelector(
+ '.mdc-list-item--selected'
+ );
+ let selectedItem: ListItem = null;
+ if (selectedElement) {
+ selectedItem = this.items.filter(item => {
+ return !('separator' in item);
+ })[index] as ListItem;
+ }
- this.change.emit(selectedItem);
+ this.change.emit(selectedItem);
+ }
+ if (this.includeCheckboxes) {
+ const checked = this.items.filter((item: ListItem) => {
+ const optionChecked = this.mdcCheckboxes.some(mdcCheckbox => {
+ return (
+ mdcCheckbox.checked &&
+ mdcCheckbox.value === item.id.toString()
+ );
+ });
+ if (optionChecked) {
+ return item;
+ }
+ });
+ this.change.emit(checked);
+ }
}
}
diff --git a/src/examples/list/list-checkboxes.tsx b/src/examples/list/list-checkboxes.tsx
new file mode 100644
index 0000000000..d34269856d
--- /dev/null
+++ b/src/examples/list/list-checkboxes.tsx
@@ -0,0 +1,33 @@
+import { Component } from '@stencil/core';
+import { ListItem } from '../../interface';
+
+@Component({
+ tag: 'limel-example-list-checkboxes',
+ shadow: true,
+})
+export class ListCheckboxesExample {
+ private items: ListItem[] = [
+ { text: 'King of Tokyo', id: 1 },
+ { text: 'Smash Up!', id: 2, checked: true },
+ { text: 'Pandemic', id: 3 },
+ { text: 'Catan', id: 4 },
+ { text: 'Ticket to Ride', id: 5 },
+ ];
+
+ public render() {
+ return [
+ {
+ console.log(event.detail);
+ }}
+ includeCheckboxes={true}
+ items={this.items}
+ />,
+
,
+
+ When importing ListItem, see{' '}
+ Usage
+
,
+ ];
+ }
+}