diff --git a/package.json b/package.json index 70597f2d8..2a88a95f5 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "jest": "jest", "lerna": "lerna bootstrap -- --no-optional --no-package-lock", "lint": "eslint packages/clay-*/src/*.js packages/clay-*/src/**/*.js && npm run mcritic", - "mcritic": "mcritic packages/ --ignore '**/{browserslist-config-clay-components,generator-metal-clay,clay-dropdown,clay-alert,clay-list,clay-management-toolbar,clay-card,node_modules}/**'", + "mcritic": "mcritic packages/ --ignore '**/{browserslist-config-clay-components,generator-metal-clay,clay-dropdown,clay-alert,clay-list,clay-management-toolbar,clay-card,clay-table,node_modules}/**'", "precommit": "lint-staged", "prettier": "prettier-eslint packages/clay-*/src/*.js packages/clay-*/src/**/*.js", "start": "http-server . -p 4000", diff --git a/packages/clay-table/LICENSE.md b/packages/clay-table/LICENSE.md new file mode 100644 index 000000000..70f93e946 --- /dev/null +++ b/packages/clay-table/LICENSE.md @@ -0,0 +1,29 @@ +# Software License Agreement (BSD License) + +Copyright (c) 2014, Liferay Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* The name of Liferay Inc. may not be used to endorse or promote products + derived from this software without specific prior + written permission of Liferay Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/clay-table/README.md b/packages/clay-table/README.md new file mode 100644 index 000000000..5138b1083 --- /dev/null +++ b/packages/clay-table/README.md @@ -0,0 +1,26 @@ +# clay-table + +Metal Clay Table component + +## Setup + +1. Install NodeJS >= v0.12.0 and NPM >= v3.0.0, if you don't have it yet. You +can find it [here](https://nodejs.org). + +2. Install local dependencies: + + ``` + npm install + ``` + +3. Build the code: + + ``` + npm run build + ``` + +4. Open the demo at demos/index.html on your browser. + +## Contribute + +We'd love to get contributions from you! Please, check our [Contributing Guidelines](CONTRIBUTING.md) to see how you can help us improve. diff --git a/packages/clay-table/demos/image.jpg b/packages/clay-table/demos/image.jpg new file mode 100644 index 000000000..fbaaa2468 Binary files /dev/null and b/packages/clay-table/demos/image.jpg differ diff --git a/packages/clay-table/demos/index.html b/packages/clay-table/demos/index.html new file mode 100644 index 000000000..5fcbf15c7 --- /dev/null +++ b/packages/clay-table/demos/index.html @@ -0,0 +1,812 @@ + + + + + + Demo: ClayTable + + + + + + + + +

+ ClayTable +

+ +
+
+

Simple Table

+
+
+
+ +
+
+

Simple Table with links

+ +
+
+ +
+
+

Simple Table with Label

+
+
+
+ +
+
+

Simple Table with Progress Bar

+
+
+
+ +
+
+

Selectable Table

+
+
+
+ +
+
+

Selectable Table with Sticker

+
+
+
+ +
+
+

Selectable Table with Action Menu

+
+
+
+ +
+
+

Selectable Table with Quick Action Menu

+
+
+
+ +
+
+

Selectable Table with group separator

+
+
+
+ +
+
+

Selectable Table with Selected Items

+
+
+
+ + + + diff --git a/packages/clay-table/package.json b/packages/clay-table/package.json new file mode 100644 index 000000000..1868c66aa --- /dev/null +++ b/packages/clay-table/package.json @@ -0,0 +1,61 @@ +{ + "name": "clay-table", + "version": "1.0.0-alpha.8", + "description": "Metal ClayTable component", + "license": "BSD", + "repository": "https://github.com/metal/metal-clay-components/tree/master/packages/clay-table", + "engines": { + "node": ">=0.12.0", + "npm": ">=3.0.0" + }, + "main": "lib/ClayTable.js", + "esnext:main": "src/ClayTable.js", + "jsnext:main": "src/ClayTable.js", + "files": [ + "lib", + "src", + "test" + ], + "scripts": { + "build": "npm run soy && webpack", + "compile": "babel -d lib/ src/ -s --ignore src/__tests__", + "prepublish": "npm run soy && npm run compile", + "soy": "metalsoy --soyDeps '../../node_modules/clay-+(dropdown|label|sticker|progress-bar|button|checkbox|icon|link|radio)/src/**/*.soy'" + }, + "keywords": [ + "clay", + "metal" + ], + "dependencies": { + "clay-button": "^1.0.0-alpha.8", + "clay-checkbox": "^1.0.0-alpha.5", + "clay-dropdown": "^1.0.0-alpha.8", + "clay-icon": "^1.0.0-alpha.5", + "clay-label": "^1.0.0-alpha.8", + "clay-link": "^1.0.0-alpha.8", + "clay-progress-bar": "^1.0.0-alpha.5", + "clay-sticker": "^1.0.0-alpha.5", + "metal-component": "^2.14.0", + "metal-dom": "^2.14.0", + "metal-events": "^2.14.0", + "metal-soy": "^2.14.0", + "metal-state": "^2.14.0", + "metal-web-component": "^2.14.0", + "metal": "^2.14.0" + }, + "devDependencies": { + "babel-cli": "^6.24.1", + "babel-core": "^6.25.0", + "babel-loader": "^7.0.0", + "babel-plugin-transform-node-env-inline": "^0.1.1", + "babel-preset-env": "^1.6.0", + "browserslist-config-clay-components": "^1.0.0-alpha.2", + "clay": "^2.0.0-beta.4", + "metal-dom": "^2.13.2", + "metal-tools-soy": "^4.2.1", + "webpack": "^3.0.0" + }, + "browserslist": [ + "extends browserslist-config-clay-components" + ] +} diff --git a/packages/clay-table/src/ClayTable.js b/packages/clay-table/src/ClayTable.js new file mode 100644 index 000000000..7a9185789 --- /dev/null +++ b/packages/clay-table/src/ClayTable.js @@ -0,0 +1,161 @@ +import './ClayTableItem'; +import 'clay-link'; +import {Config} from 'metal-state'; +import {EventHandler} from 'metal-events'; +import Component from 'metal-component'; +import defineWebComponent from 'metal-web-component'; +import dom from 'metal-dom'; +import itemsValidator from './items_validator'; +import Soy from 'metal-soy'; + +import templates from './ClayTable.soy.js'; + +/** + * Metal ClayTable component. + */ +class ClayTable extends Component { + /** + * @inheritDoc + */ + attached() { + this.eventHandler_.add( + dom.delegate( + document, + 'click', + 'body', + this.handleClickDocument_.bind(this) + ), + dom.delegate(this.element, 'focus', 'tr', this.handleFocus_) + ); + } + + /** + * @inheritDoc + */ + created() { + this.eventHandler_ = new EventHandler(); + } + + /** + * @inheritDoc + */ + detached() { + super.detached(); + this.eventHandler_.removeAllListeners(); + } + + /** + * Propagate the change of the checkbox event. + * @param {!Event} event + * @private + */ + handleClickCheckbox_(event) { + this.emit('itemToggled', event); + } + + /** + * Handle click in document for remove the class `table-focus` of tr. + * @private + */ + handleClickDocument_() { + dom.removeClasses(this.element.querySelectorAll('tr'), 'table-focus'); + } + + /** + * Handle focus the tr for add class `table-focus`. + * @param {!Event} event + * @private + */ + handleFocus_(event) { + const getFirstTable = dom.closest(event.target, 'table'); + const getFirstTr = dom.closest(event.target, 'tr'); + + dom.removeClasses(getFirstTable.querySelectorAll('tr'), 'table-focus'); + dom.addClasses(getFirstTr, 'table-focus'); + } + + /** + * Handle button sort in header. + * @param {!Event} event + * @private + */ + handleSortColumn_(event) { + this.emit('sortColumn', event); + } +} + +/** + * State definition. + * @static + * @type {!Object} + */ +ClayTable.STATE = { + /** + * CSS classes to be applied to the element. + * @instance + * @memberof ClayTable + * @type {?string|undefined} + * @default undefined + */ + elementClasses: Config.string(), + + /** + * Used to render the header of a column. + * @instance + * @memberof ClayTable + * @type {?array|undefined} + * @default undefined + */ + header: Config.arrayOf( + Config.shapeOf({ + colSpan: Config.number(), + elementClasses: Config.string(), + label: Config.string(), + sort: Config.bool().value(false), + sortDirection_: Config.oneOf(['down', 'up']).value('down'), + }) + ), + + /** + * Id to be applied to the element. + * @instance + * @memberof ClayTable + * @type {?string|undefined} + * @default undefined + */ + id: Config.string(), + + /** + * List the items of the table. + * @instance + * @memberof ClayTable + * @type {?array|undefined} + * @default undefined + */ + items: itemsValidator, + + /** + * Table responsive sizes. Available `lg`, `md`, `sm` and `xl`. + * @instance + * @memberof ClayTable + * @type {?string|undefined} + * @default undefined + */ + size: Config.oneOf(['lg', 'md', 'sm', 'xl']), + + /** + * The path to the SVG spritemap file containing the icons. + * @instance + * @memberof ClayTable + * @type {!string} + * @default undefined + */ + spritemap: Config.string().required(), +}; + +defineWebComponent('clay-table', ClayTable); + +Soy.register(ClayTable, templates); + +export {ClayTable}; +export default ClayTable; diff --git a/packages/clay-table/src/ClayTable.soy b/packages/clay-table/src/ClayTable.soy new file mode 100644 index 000000000..d3defa41a --- /dev/null +++ b/packages/clay-table/src/ClayTable.soy @@ -0,0 +1,205 @@ +{namespace ClayTable} + +/** + * This renders the component's whole content. + */ +{template .render} + {@param spritemap: string} + {@param? elementClasses: string} + {@param? handleClickCheckbox_: any} + {@param? handleSortColumn_: any} + {@param? header: list<[ + colSpan: int, + elementClasses: string, + label: string, + sort: bool, + sortDirection_: string + ]>} + {@param? id: string} + {@param? items: list<[ + actionColumnElementClasses: string, + actionItems: list<[ + disabled: bool, + href: string, + icon: string, + label: string, + quickAction: bool, + separator: bool + ]>, + columns: list<[ + buttonStyle: string, + elementClasses: string, + href: string, + label: string, + labelStyle: string, + progressBar: [ + maxValue: int, + minValue: int, + status: string, + value: int + ], + type: string, + useEllipse: bool + ]>, + disabled: bool, + group: string, + inputName: string, + inputValue: string, + items: list, + selectable: bool, + selectableElementClasses: string, + selected: bool, + stickerElementClasses: string, + stickerImageAlt: string, + stickerImageSrc: string, + stickerLabel: string, + stickerStyle: string, + stickerShape: string + ]>} + {@param? size: string} + + {let $attributes kind="attributes"} + class="table table-autofit table-hover table-list show-quick-actions-on-hover + {if $elementClasses} + {sp}{$elementClasses} + {/if} + " + + {if $id} + id="{$id}" + {/if} + {/let} + + {let $classes kind="text"} + table-responsive + {if $size} + -{$size} + {/if} + {/let} + +
+ + {if $header} + + + {call .header} + {param handleSortColumn_: $handleSortColumn_ /} + {param header: $header /} + {param spritemap: $spritemap /} + {/call} + + + {/if} + {if $items} + + {call .body} + {param handleClickCheckbox_: $handleClickCheckbox_ /} + {param items: $items /} + {param spritemap: $spritemap /} + {/call} + + {/if} +
+
+{/template} + +/** + * This renders a rows of the table. + */ +{template .body} + {@param items: list} + {@param spritemap: string} + {@param? handleClickCheckbox_: any} + + {foreach $item in $items} + {if $item.group} + + {$item.group} + + {if $item.items} + {foreach $itemToGroup in $item.items} + {call ClayTableItem.render} + {param actionColumnElementClasses: $itemToGroup.actionColumnElementClasses /} + {param actionItems: $itemToGroup.actionItems /} + {param columns: $itemToGroup.columns /} + {param disabled: $itemToGroup.disabled /} + {param events: ['itemToggled': $handleClickCheckbox_] /} + {param inputName: $itemToGroup.inputName /} + {param inputValue: $itemToGroup.inputValue /} + {param selectable: $itemToGroup.selectable /} + {param selectableElementClasses: $itemToGroup.selectableElementClasses /} + {param selected: $itemToGroup.selected /} + {param spritemap: $spritemap /} + {param stickerElementClasses: $itemToGroup.stickerElementClasses /} + {param stickerImageAlt: $itemToGroup.stickerImageAlt /} + {param stickerImageSrc: $itemToGroup.stickerImageSrc /} + {param stickerLabel: $itemToGroup.stickerLabel /} + {param stickerShape: $itemToGroup.stickerShape /} + {param stickerStyle: $itemToGroup.stickerStyle /} + {/call} + {/foreach} + {/if} + {else} + {call ClayTableItem.render} + {param actionColumnElementClasses: $item.actionColumnElementClasses /} + {param actionItems: $item.actionItems /} + {param columns: $item.columns /} + {param disabled: $item.disabled /} + {param events: ['itemToggled': $handleClickCheckbox_] /} + {param inputName: $item.inputName /} + {param inputValue: $item.inputValue /} + {param selectable: $item.selectable /} + {param selectableElementClasses: $item.selectableElementClasses /} + {param selected: $item.selected /} + {param spritemap: $spritemap /} + {param stickerElementClasses: $item.stickerElementClasses /} + {param stickerImageAlt: $item.stickerImageAlt /} + {param stickerImageSrc: $item.stickerImageSrc /} + {param stickerLabel: $item.stickerLabel /} + {param stickerShape: $item.stickerShape /} + {param stickerStyle: $item.stickerStyle /} + {/call} + {/if} + {/foreach} +{/template} + +/** + * This renders columns to the header of the table. + */ +{template .header} + {@param header: list} + {@param spritemap: string} + {@param? handleSortColumn_: any} + + {foreach $item in $header} + {let $thAttributes kind="attributes"} + class=" + {if $item.elementClasses} + {$item.elementClasses} + {/if} + " + + {if $item.colSpan} + colspan="{$item.colSpan}" + {/if} + {/let} + + + {if $item.label and not $item.sort} + {$item.label} + {elseif $item.sort and $item.label} + {let $icon: $item.sortDirection_ ?: 'down' /} + + {call ClayLink.render} + {param events: ['click': $handleSortColumn_] /} + {param href: '#sort' /} + {param icon: 'order-arrow-' + $icon /} + {param iconAlignment: 'right' /} + {param id: '' + index($item) /} + {param label: $item.label /} + {param spritemap: $spritemap /} + {/call} + {/if} + + {/foreach} +{/template} diff --git a/packages/clay-table/src/ClayTableItem.js b/packages/clay-table/src/ClayTableItem.js new file mode 100644 index 000000000..511461cfa --- /dev/null +++ b/packages/clay-table/src/ClayTableItem.js @@ -0,0 +1,209 @@ +import 'clay-button'; +import 'clay-checkbox'; +import 'clay-dropdown'; +import 'clay-icon'; +import 'clay-label'; +import 'clay-link'; +import 'clay-progress-bar'; +import 'clay-sticker'; +import {actionsItemsValidator, columnsValidator} from './items_validator'; +import {Config} from 'metal-state'; +import Component from 'metal-component'; +import defineWebComponent from 'metal-web-component'; +import dom from 'metal-dom'; +import Soy from 'metal-soy'; + +import templates from './ClayTableItem.soy.js'; + +/** + * Metal ClayTableItem component. + */ +class ClayTableItem extends Component { + /** + * Handle input of type `checkbox` for add class `table-active` in tr. + * @param {!Event} event + * @private + */ + handleItemToggled_(event) { + dom.toggleClasses(this.element, 'table-active'); + + this.emit('itemToggled', event); + } +} + +/** + * State definition. + * @static + * @type {!Object} + */ +ClayTableItem.STATE = { + /** + * CSS classes to be applied to the td of the actions menu. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + actionColumnElementClasses: Config.string(), + + /** + * List of items to display in the actions menu. + * @instance + * @memberof ClayTableItem + * @type {?array|undefined} + * @default undefined + */ + actionItems: actionsItemsValidator, + + /** + * List of the columns in the row. + * @instance + * @memberof ClayTableItem + * @type {?array|undefined} + * @default undefined + */ + columns: columnsValidator, + + /** + * Name of the content renderer to use template variants. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + contentRenderer: Config.string(), + + /** + * Flag to indicate if the item is disabled or not. + * @instance + * @memberof ClayTableItem + * @type {?bool} + * @default false + */ + disabled: Config.bool().value(false), + + /** + * Name to be applied to the input element. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + inputName: Config.string(), + + /** + * Value to be applied to the input element. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + inputValue: Config.string(), + + /** + * Flag to indicate if the item is selectable. + * @instance + * @memberof ClayTableItem + * @type {?bool} + * @default false + */ + selectable: Config.bool().value(false), + + /** + * CSS classes to be applied to the td of the selectable. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + selectableElementClasses: Config.string(), + + /** + * Flag to indicate if the item is selected or not. + * @instance + * @memberof ClayTableItem + * @type {?bool} + * @default false + */ + selected: Config.bool().value(false), + + /** + * The path to the SVG spritemap file containing the icons. + * @instance + * @memberof ClayTableItem + * @type {!string} + * @default undefined + */ + spritemap: Config.string().required(), + + /** + * CSS classes to be applied to the td of the sticker. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + stickerElementClasses: Config.string(), + + /** + * Alternate text of the image sticker. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + stickerImageAlt: Config.string(), + + /** + * Source of the image to be rendered inside the sticker. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + stickerImageSrc: Config.string(), + + /** + * Sets the text to be rendered inside sticker. + * @instance + * @memberof ClayTableItem + * @type {?string|undefined} + * @default undefined + */ + stickerLabel: Config.string(), + + /** + * Shape of sticker. Available shapes are `circle`, `rounded`. + * @instance + * @memberof ClayTableItem + * @type {?string} + * @default circle + */ + stickerShape: Config.string().value('circle'), + + /** + * Sticker style. Available sizes are: `danger`, `dark`, `info`, `light`, + * `primary`, `secondary`, `success`, `warning`. + * @instance + * @memberof ClayTableItem + * @type {?string} + * @default primary + */ + stickerStyle: Config.oneOf([ + 'danger', + 'dark', + 'info', + 'light', + 'primary', + 'secondary', + 'success', + 'warning', + ]).value('primary'), +}; + +defineWebComponent('clay-table-item', ClayTableItem); + +Soy.register(ClayTableItem, templates); + +export {ClayTableItem}; +export default ClayTableItem; diff --git a/packages/clay-table/src/ClayTableItem.soy b/packages/clay-table/src/ClayTableItem.soy new file mode 100644 index 000000000..e636c8313 --- /dev/null +++ b/packages/clay-table/src/ClayTableItem.soy @@ -0,0 +1,271 @@ +{namespace ClayTableItem} + +/** + * This renders the component's whole content. + */ +{template .render} + {@param spritemap: string} + {@param? actionColumnElementClasses: string} + {@param? actionItems: list<[ + disabled: bool, + href: string, + icon: string, + label: string, + quickAction: bool, + separator: bool + ]>} + {@param? columns: list<[ + buttonStyle: string, + elementClasses: string, + href: string, + label: string, + labelStyle: string, + progressBar: [ + maxValue: int, + minValue: int, + status: string, + value: int + ], + type: string, + useEllipse: bool + ]>} + {@param? contentRenderer: string} + {@param? disabled: bool} + {@param? handleItemToggled_: any} + {@param? inputName: string} + {@param? inputValue: string} + {@param? selectable: bool} + {@param? selectableElementClasses: string} + {@param? selected: bool} + {@param? stickerElementClasses: string} + {@param? stickerImageAlt: string} + {@param? stickerImageSrc: string} + {@param? stickerLabel: string} + {@param? stickerShape: string} + {@param? stickerStyle: string} + + {let $attributes kind="attributes"} + class=" + {if $selectable and $selected} + table-active + {/if} + " + {/let} + + + {call .content} + {param actionColumnElementClasses: $actionColumnElementClasses /} + {param actionItems: $actionItems /} + {param columns: $columns /} + {param contentRenderer: $contentRenderer /} + {param disabled: $disabled /} + {param handleItemToggled_: $handleItemToggled_ /} + {param inputName: $inputName /} + {param inputValue: $inputValue /} + {param selectable: $selectable /} + {param selectableElementClasses: $selectableElementClasses /} + {param selected: $selected /} + {param spritemap: $spritemap /} + {param stickerElementClasses: $stickerElementClasses /} + {param stickerImageAlt: $stickerImageAlt /} + {param stickerImageSrc: $stickerImageSrc /} + {param stickerLabel: $stickerLabel /} + {param stickerShape: $stickerShape /} + {param stickerStyle: $stickerStyle /} + {/call} + +{/template} + +/** + * This renders the content of the row. + */ +{template .content} + {@param spritemap: string} + {@param? actionColumnElementClasses: string} + {@param? actionItems: list} + {@param? columns: list} + {@param? contentRenderer: string} + {@param? disabled: bool} + {@param? handleItemToggled_: any} + {@param? inputName: string} + {@param? inputValue: string} + {@param? selectable: bool} + {@param? selectableElementClasses: string} + {@param? selected: bool} + {@param? stickerElementClasses: string} + {@param? stickerImageAlt: string} + {@param? stickerImageSrc: string} + {@param? stickerLabel: string} + {@param? stickerShape: string} + {@param? stickerStyle: string} + + {if $selectable} + + {call ClayCheckbox.render} + {param checked: $selected /} + {param disabled: $disabled /} + {param events: ['change': $handleItemToggled_] /} + {param hideLabel: true /} + {param name: $inputName /} + {param value: $inputValue /} + {/call} + + {/if} + + {if $stickerLabel or $stickerImageSrc} + + {if $stickerImageSrc} + {call ClaySticker.render} + {param imageAlt: $stickerImageAlt /} + {param imageSrc: $stickerImageSrc /} + {param shape: $stickerShape ?: 'circle' /} + {/call} + {else} + {call ClaySticker.render} + {param label: $stickerLabel /} + {param shape: $stickerShape ?: 'circle' /} + {param style: $stickerStyle /} + {/call} + {/if} + + {/if} + + {if $columns} + {foreach $column in $columns} + {delcall ClayTableItem.Column variant="$contentRenderer"} + {param buttonStyle: $column.buttonStyle /} + {param elementClasses: $column.elementClasses /} + {param href: $column.href /} + {param label: $column.label /} + {param labelStyle: $column.labelStyle /} + {param progressBar: $column.progressBar /} + {param type: $column.type ?: 'text' /} + {param useEllipse: $column.useEllipse /} + {/delcall} + {/foreach} + {/if} + + {if $actionItems} + + {call .quickMenu} + {param actionItems: $actionItems /} + {param spritemap: $spritemap /} + {/call} + + {call ClayActionsDropdown.render} + {param items: $actionItems /} + {param spritemap: $spritemap /} + {/call} + + {/if} +{/template} + +/** + * This renders a multiples columns of the row. + */ +{deltemplate ClayTableItem.Column} + {@param? buttonStyle: string} + {@param? elementClasses: string} + {@param? href: string} + {@param? label: string} + {@param? labelStyle: string} + {@param? progressBar: [ + maxValue: int, + minValue: int, + status: string, + value: int + ]} + {@param? type: string} + {@param? useEllipse: bool} + + {let $labelContent kind="html"} + {call .label} + {param href: $href /} + {param label: $label /} + {/call} + {/let} + + {let $content kind="html"} + {if $type == 'label'} + {call ClayLabel.render} + {param label: $label /} + {param style: $labelStyle /} + {/call} + {elseif $progressBar} + {call ClayProgressBar.render} + {param maxValue: $progressBar.maxValue /} + {param minValue: $progressBar.minValue /} + {param status: $progressBar.status /} + {param value: $progressBar.value /} + {/call} + {elseif $type == 'button'} + {call ClayButton.render} + {param label: $label /} + {param size: 'sm' /} + {param style: $labelStyle /} + {/call} + {elseif $type == 'text'} + {if $useEllipse} + {$labelContent} + {else} + {$labelContent} + {/if} + {/if} + {/let} + + {let $classes kind="text"} + {if $elementClasses} + {$elementClasses} + {/if} + + {if $useEllipse} + {sp}table-cell-expand + {/if} + {/let} + + + {$content} + +{/deltemplate} + +/** + * This renders a text of the column. + */ +{template .label} + {@param? href: string} + {@param? label: string} + + {if $label and $href} + {call ClayLink.render} + {param href: $href /} + {param label: $label /} + {/call} + {else} + {$label} + {/if} +{/template} + +/** + * This renders the quick actions menu. + */ +{template .quickMenu} + {@param actionItems: list} + {@param spritemap: string} + +
+ {foreach $item in $actionItems} + {if $item.quickAction and $item.icon} + {call ClayLink.render} + {param elementClasses: 'quick-action-item' /} + {param href: $item.href /} + {param label kind="html"} + {call ClayIcon.render} + {param spritemap: $spritemap /} + {param symbol: $item.icon /} + {/call} + {/param} + {/call} + {/if} + {/foreach} +
+{/template} diff --git a/packages/clay-table/src/__tests__/ClayTable.js b/packages/clay-table/src/__tests__/ClayTable.js new file mode 100644 index 000000000..528cf83a3 --- /dev/null +++ b/packages/clay-table/src/__tests__/ClayTable.js @@ -0,0 +1,248 @@ +import ClayTable from '../ClayTable'; + +let component; +let spritemap = 'icons.svg'; +let items = [ + { + columns: [ + { + href: '#1', + label: 'suffing-photo.png', + useEllipse: true, + }, + { + label: 'Juan Anton', + }, + { + label: '264KB', + }, + { + label: 'Approved', + labelStyle: 'success', + type: 'label', + }, + { + label: '15 Minutes Ago', + }, + { + label: '15 Minutes Ago', + }, + ], + }, + { + columns: [ + { + href: '#1', + label: 'suffing-photo.png', + useEllipse: true, + }, + { + label: 'Juan Anton', + }, + { + label: '264KB', + }, + { + label: 'Approved', + labelStyle: 'success', + type: 'label', + }, + { + label: '15 Minutes Ago', + }, + { + label: '15 Minutes Ago', + }, + ], + }, +]; + +describe('ClayTable', function() { + afterEach(() => { + if (component) { + component.dispose(); + } + }); + + it('should render the default markup', () => { + component = new ClayTable({ + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with classes', () => { + component = new ClayTable({ + elementClasses: 'my-custom-class', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with size `lg`', () => { + component = new ClayTable({ + size: 'lg', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with size `md`', () => { + component = new ClayTable({ + size: 'md', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with size `sm`', () => { + component = new ClayTable({ + size: 'sm', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with size `xl`', () => { + component = new ClayTable({ + size: 'xl', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with id', () => { + component = new ClayTable({ + id: 'myId', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with multiples items', () => { + component = new ClayTable({ + items: items, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with multiples columns in header', () => { + component = new ClayTable({ + header: [ + { + label: 'Title', + }, + { + label: 'Author', + }, + { + label: 'Size', + }, + { + label: 'Status', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with colspan in columns header', () => { + component = new ClayTable({ + header: [ + { + colSpan: 2, + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a ClayTable with elementClasses in the columns', () => { + component = new ClayTable({ + header: [ + { + elementClasses: 'custom1-column', + label: 'Foo', + }, + { + elementClasses: 'custom2-column', + label: 'Bar', + }, + ], + items: [ + { + columns: [ + { + elementClasses: 'custom1-column', + label: 'Foo', + }, + { + elementClasses: 'custom2-column', + label: 'Bar', + }, + ], + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTable with group', () => { + component = new ClayTable({ + items: [ + { + items: [ + { + columns: [ + { + label: 'Foo', + }, + ], + }, + ], + group: 'Group 1', + }, + { + items: [ + { + columns: [ + { + label: 'Foo', + }, + ], + }, + ], + group: 'Group 2', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should fail when no spritemap is passed', () => { + expect(() => { + component = new ClayTable(); + }).toThrow(); + }); +}); diff --git a/packages/clay-table/src/__tests__/ClayTableItem.js b/packages/clay-table/src/__tests__/ClayTableItem.js new file mode 100644 index 000000000..6318a3c4c --- /dev/null +++ b/packages/clay-table/src/__tests__/ClayTableItem.js @@ -0,0 +1,452 @@ +import ClayTableItem from '../ClayTableItem'; + +let component; +let spritemap = 'icons.svg'; + +describe('ClayTableItem', function() { + afterEach(() => { + if (component) { + component.dispose(); + } + }); + + it('should render the default markup', () => { + component = new ClayTableItem({ + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with id', () => { + component = new ClayTableItem({ + id: 'myId', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with custom element class', () => { + component = new ClayTableItem({ + elementClasses: 'my-custom-class', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with columns', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + url: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with user sticker in the column', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Name', + useEllipse: true, + }, + ], + spritemap: spritemap, + stickerLabel: 'AA', + stickerStyle: 'primary', + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with custom classes in column of sticker', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Name', + useEllipse: true, + }, + ], + spritemap: spritemap, + stickerElementClasses: 'custom-column', + stickerLabel: 'AA', + stickerStyle: 'primary', + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render ClayTableItem with sticker with image in column', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Name', + useEllipse: true, + }, + ], + spritemap: spritemap, + stickerImageSrc: 'image.png', + stickerImageAlt: 'Image', + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render ClayTableItem with sticker shape `rounded`', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Name', + useEllipse: true, + }, + ], + spritemap: spritemap, + stickerImageSrc: 'image.png', + stickerImageAlt: 'Image', + stickerShape: 'rounded', + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with progress bar in the column', () => { + component = new ClayTableItem({ + columns: [ + { + progressBar: { + value: 40, + }, + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with label in the column', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Approved', + labelStyle: 'success', + type: 'label', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with action menu', () => { + component = new ClayTableItem({ + actionItems: [ + { + label: 'Option 1', + href: '#1', + }, + { + label: 'Option 2', + separator: true, + href: '#2', + }, + { + label: 'Option 3', + href: '#3', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with custom classes in column of `action menu`', () => { + component = new ClayTableItem({ + actionItems: [ + { + label: 'Option 1', + href: '#1', + }, + { + label: 'Option 2', + separator: true, + href: '#2', + }, + { + label: 'Option 3', + href: '#3', + }, + ], + actionColumnElementClasses: 'custom-column', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with quick action menu', () => { + component = new ClayTableItem({ + actionItems: [ + { + icon: 'trash', + label: 'Option 1', + quickAction: true, + href: '#1', + }, + { + icon: 'download', + label: 'Option 2', + quickAction: true, + separator: true, + href: '#2', + }, + { + icon: 'info-circle-open', + label: 'Option 3', + quickAction: true, + href: '#3', + }, + ], + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem selectable', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + selectable: true, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with custom classes in columns of selectable', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + selectable: true, + selectableElementClasses: 'custom-column', + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem selected', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + selectable: true, + selected: true, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem disabled', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + selectable: true, + disabled: true, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with input name', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + inputName: 'name', + selectable: true, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render the ClayTableItem with input value', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + inputValue: 'value', + selectable: true, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should render a selectable ClayTableItem and emit an event on itemToggle', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + }, + ], + selectable: true, + spritemap: spritemap, + }); + + const spy = jest.spyOn(component, 'emit'); + + component.element.querySelector('input[type="checkbox"]').click(); + + expect(spy).toHaveBeenCalled(); + expect(spy).toHaveBeenCalledWith('itemToggled', expect.any(Object)); + }); + + it('should render the ClayTableItem with button', () => { + component = new ClayTableItem({ + columns: [ + { + label: 'Foo', + useEllipse: true, + }, + { + label: 'Bar', + href: '#1', + }, + { + label: 'Foo', + }, + { + label: 'Bar', + buttonStyle: 'primary', + type: 'button', + }, + ], + inputValue: 'value', + selectable: true, + spritemap: spritemap, + }); + + expect(component).toMatchSnapshot(); + }); + + it('should fail when no spritemap is passed', () => { + expect(() => { + component = new ClayTableItem(); + }).toThrow(); + }); +}); diff --git a/packages/clay-table/src/__tests__/__snapshots__/ClayTable.js.snap b/packages/clay-table/src/__tests__/__snapshots__/ClayTable.js.snap new file mode 100644 index 000000000..72c30f3d8 --- /dev/null +++ b/packages/clay-table/src/__tests__/__snapshots__/ClayTable.js.snap @@ -0,0 +1,148 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ClayTable should render a ClayTable with classes 1`] = ` +
+
+
+`; + +exports[`ClayTable should render a ClayTable with colspan in columns header 1`] = ` +
+ + + + + + + +
FooBar
+
+`; + +exports[`ClayTable should render a ClayTable with elementClasses in the columns 1`] = ` +
+ + + + + + + + + + + + + +
FooBar
FooBar
+
+`; + +exports[`ClayTable should render a ClayTable with id 1`] = ` +
+
+
+`; + +exports[`ClayTable should render a ClayTable with multiples columns in header 1`] = ` +
+ + + + + + + + + +
TitleAuthorSizeStatus
+
+`; + +exports[`ClayTable should render a ClayTable with multiples items 1`] = ` +
+ + + + + + + + + + + + + + + + + + + +
+ + suffing-photo.png + + Juan Anton264KB + Approved + 15 Minutes Ago15 Minutes Ago
+ + suffing-photo.png + + Juan Anton264KB + Approved + 15 Minutes Ago15 Minutes Ago
+
+`; + +exports[`ClayTable should render a ClayTable with size \`lg\` 1`] = ` +
+
+
+`; + +exports[`ClayTable should render a ClayTable with size \`md\` 1`] = ` +
+
+
+`; + +exports[`ClayTable should render a ClayTable with size \`sm\` 1`] = ` +
+
+
+`; + +exports[`ClayTable should render a ClayTable with size \`xl\` 1`] = ` +
+
+
+`; + +exports[`ClayTable should render the ClayTable with group 1`] = ` +
+ + + + + + + + + + + + + + + +
Group 1
Foo
Group 2
Foo
+
+`; + +exports[`ClayTable should render the default markup 1`] = ` +
+
+
+`; diff --git a/packages/clay-table/src/__tests__/__snapshots__/ClayTableItem.js.snap b/packages/clay-table/src/__tests__/__snapshots__/ClayTableItem.js.snap new file mode 100644 index 000000000..3fe6c52e8 --- /dev/null +++ b/packages/clay-table/src/__tests__/__snapshots__/ClayTableItem.js.snap @@ -0,0 +1,352 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ClayTableItem should render ClayTableItem with sticker shape \`rounded\` 1`] = ` + + + + Image + + + + Name + + +`; + +exports[`ClayTableItem should render ClayTableItem with sticker with image in column 1`] = ` + + + + Image + + + + Name + + +`; + +exports[`ClayTableItem should render the ClayTableItem disabled 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem selectable 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem selected 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem with action menu 1`] = ` + + +
+ + + +`; + +exports[`ClayTableItem should render the ClayTableItem with button 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + + + + +`; + +exports[`ClayTableItem should render the ClayTableItem with columns 1`] = ` + + + Foo + + Bar + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem with custom classes in column of \`action menu\` 1`] = ` + + +
+ + + +`; + +exports[`ClayTableItem should render the ClayTableItem with custom classes in column of sticker 1`] = ` + + + AA + + + Name + + +`; + +exports[`ClayTableItem should render the ClayTableItem with custom classes in columns of selectable 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem with custom element class 1`] = ``; + +exports[`ClayTableItem should render the ClayTableItem with id 1`] = ``; + +exports[`ClayTableItem should render the ClayTableItem with input name 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem with input value 1`] = ` + + +
+ +
+ + + Foo + + + Bar + + Foo + Bar + +`; + +exports[`ClayTableItem should render the ClayTableItem with label in the column 1`] = ` + + + Approved + + +`; + +exports[`ClayTableItem should render the ClayTableItem with progress bar in the column 1`] = ` + + +
+
+
+
+
40%
+
+ + +`; + +exports[`ClayTableItem should render the ClayTableItem with quick action menu 1`] = ` + + +
+ + + + + + + + + +
+ + + +`; + +exports[`ClayTableItem should render the ClayTableItem with user sticker in the column 1`] = ` + + + AA + + + Name + + +`; + +exports[`ClayTableItem should render the default markup 1`] = ``; diff --git a/packages/clay-table/src/items_validator.js b/packages/clay-table/src/items_validator.js new file mode 100644 index 000000000..1478b49b8 --- /dev/null +++ b/packages/clay-table/src/items_validator.js @@ -0,0 +1,69 @@ +import {Config} from 'metal-state'; + +let actionItems = { + disabled: Config.bool().value(false), + icon: Config.string(), + label: Config.string(), + quickAction: Config.bool().value(false), + separator: Config.bool().value(false), + href: Config.string().required(), +}; + +let columns = { + buttonStyle: Config.oneOf(['link', 'primary', 'secondary']).value( + 'primary' + ), + elementClasses: Config.string(), + href: Config.string(), + label: Config.string(), + labelStyle: Config.oneOf([ + 'danger', + 'info', + 'secondary', + 'success', + 'warning', + ]).value('secondary'), + progressBar: Config.shapeOf({ + maxValue: Config.number(), + minValue: Config.number(), + status: Config.oneOf(['complete', 'warning']), + value: Config.number(), + }), + type: Config.oneOf(['text', 'button', 'label']).value('text'), + useEllipse: Config.bool().value(false), +}; + +let items = { + actionColumnElementClasses: Config.string(), + actionItems: Config.arrayOf(Config.shapeOf(actionItems)), + columns: Config.arrayOf(Config.shapeOf(columns)), + disabled: Config.bool().value(false), + group: Config.string(), + inputName: Config.string(), + inputValue: Config.string(), + selectable: Config.bool().value(false), + selectableElementClasses: Config.string(), + selected: Config.bool().value(false), + stickerElementClasses: Config.string(), + stickerImageAlt: Config.string(), + stickerImageSrc: Config.string(), + stickerLabel: Config.string(), + stickerShape: Config.string().value('circle'), + stickerStyle: Config.oneOf([ + 'danger', + 'dark', + 'info', + 'light', + 'primary', + 'secondary', + 'success', + 'warning', + ]).value('primary'), +}; + +const actionsItemsValidator = Config.arrayOf(Config.shapeOf(actionItems)); +const columnsValidator = Config.arrayOf(Config.shapeOf(columns)); +const itemsValidator = Config.arrayOf(Config.shapeOf(items)); + +export {actionsItemsValidator, columnsValidator, itemsValidator}; +export default itemsValidator; diff --git a/packages/clay-table/webpack.config.js b/packages/clay-table/webpack.config.js new file mode 100644 index 000000000..63267c1da --- /dev/null +++ b/packages/clay-table/webpack.config.js @@ -0,0 +1,31 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + entry: './src/ClayTable.js', + module: { + rules: [ + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + compact: false, + presets: ['babel-preset-env'], + plugins: ['babel-plugin-transform-node-env-inline'], + }, + }, + }, + ], + }, + output: { + library: 'metal', + libraryTarget: 'this', + filename: './build/globals/clay-table.js', + }, + plugins: [new webpack.optimize.ModuleConcatenationPlugin()], + resolve: { + mainFields: ['esnext:main', 'main'], + }, +};