Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add basic dashboard widget structure #7679

Merged
merged 2 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions dev/dashboard-layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@

<script type="module">
import '@vaadin/dashboard/vaadin-dashboard-layout.js';
import '@vaadin/dashboard/vaadin-dashboard-widget.js';
</script>

<style>
vaadin-dashboard-layout div {
vaadin-dashboard-widget {
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1em;
text-align: center;
height: 100px;
padding: 10px;
}

vaadin-dashboard-layout {
Expand All @@ -27,16 +26,49 @@
--vaadin-dashboard-gap: 20px;
--vaadin-dashboard-col-max-count: 3;
}

.kpi-number {
font-size: 80px;
font-weight: bold;
color: #4caf50;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}

.chart {
height: 300px;
background: repeating-linear-gradient(45deg, #e0e0e0, #e0e0e0 10px, #f5f5f5 10px, #f5f5f5 20px);
}
</style>
</head>

<body>
<vaadin-dashboard-layout>
<div>Item 0</div>
<div style="--vaadin-dashboard-item-colspan: 2">Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
<vaadin-dashboard-widget widget-title="Total cost">
<span slot="header">2023-2024</span>
<div class="kpi-number">+203%</div>
</vaadin-dashboard-widget>

<vaadin-dashboard-widget style="--vaadin-dashboard-item-colspan: 2" widget-title="Sales">
<span slot="header">2023-2024</span>
<div class="chart"></div>
</vaadin-dashboard-widget>

<vaadin-dashboard-widget widget-title="Sales closed this month">
<div class="kpi-number">54 000€</div>
</vaadin-dashboard-widget>

<vaadin-dashboard-widget widget-title="Just some number">
<span slot="header">2014-2024</span>
<div class="kpi-number">1234</div>
</vaadin-dashboard-widget>

<vaadin-dashboard-widget>
<h2 slot="title">Activity since 2023</h2>
<div class="chart"></div>
</vaadin-dashboard-widget>
</vaadin-dashboard-layout>
</body>
</html>
21 changes: 21 additions & 0 deletions packages/dashboard/src/title-controller.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright (c) 2019 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { SlotChildObserveController } from '@vaadin/component-base/src/slot-child-observe-controller.js';

/**
* A controller to manage the widget title element.
*/
export class TitleController extends SlotChildObserveController {
/**
* String used for the widget title.
*/
protected widgetTitle: string | null | undefined;

/**
* Set widget title based on corresponding host property.
*/
setWidgetTitle(widgetTitle: string | null | undefined): void;
}
64 changes: 64 additions & 0 deletions packages/dashboard/src/title-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @license
* Copyright (c) 2019 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { SlotChildObserveController } from '@vaadin/component-base/src/slot-child-observe-controller.js';

/**
* A controller to manage the widget title element.
*/
export class TitleController extends SlotChildObserveController {
constructor(host) {
super(host, 'title', null);
}

/**
* Set widget title based on corresponding host property.
*
* @param {string} widgetTitle
*/
setWidgetTitle(widgetTitle) {
this.widgetTitle = widgetTitle;

// Restore the default widgetTitle, if needed.
const widgetTitleNode = this.getSlotChild();
if (!widgetTitleNode) {
this.restoreDefaultNode();
}

// When default widgetTitle is used, update it.
if (this.node === this.defaultNode) {
this.updateDefaultNode(this.node);
}
}

/**
* Override method inherited from `SlotChildObserveController`
* to restore and observe the default widget title element.
*
* @protected
* @override
*/
restoreDefaultNode() {
this.tagName = 'h2';
this.attachDefaultNode();
}

/**
* Override method inherited from `SlotChildObserveController`
* to update the default widgetTitle element text content.
*
* @param {Node | undefined} node
* @protected
* @override
*/
updateDefaultNode(node) {
if (node) {
node.textContent = this.widgetTitle;
}

// Notify the host after update.
super.updateDefaultNode(node);
}
}
8 changes: 7 additions & 1 deletion packages/dashboard/src/vaadin-dashboard-widget.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';

/**
* A Widget component for use with the Dashboard component
*/
declare class DashboardWidget extends ElementMixin(HTMLElement) {}
declare class DashboardWidget extends ControllerMixin(ElementMixin(HTMLElement)) {
/**
* The title of the widget
*/
widgetTitle: string | null | undefined;
}

declare global {
interface HTMLElementTagNameMap {
Expand Down
80 changes: 78 additions & 2 deletions packages/dashboard/src/vaadin-dashboard-widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,101 @@
* license.
*/
import { html, LitElement } from 'lit';
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
import { css } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { TitleController } from './title-controller.js';

/**
* A Widget component for use with the Dashboard component
*
* @customElement
* @extends HTMLElement
* @mixes ElementMixin
* @mixes ControllerMixin
*/
class DashboardWidget extends ElementMixin(PolylitMixin(LitElement)) {
class DashboardWidget extends ControllerMixin(ElementMixin(PolylitMixin(LitElement))) {
static get is() {
return 'vaadin-dashboard-widget';
}

static get styles() {
return css`
:host {
display: flex;
flex-direction: column;
}

:host([hidden]) {
display: none !important;
}

header {
display: flex;
justify-content: space-between;
align-items: center;
}

#content {
flex: 1;
}
`;
}

static get properties() {
return {
/**
* The title of the widget.
*/
widgetTitle: {
type: String,
value: '',
observer: '__onWidgetTitleChanged',
},
};
}

/** @protected */
render() {
return html``;
return html`
<header>
<slot name="title" @slotchange="${this.__onTitleSlotChange}"></slot>
<slot name="header"></slot>
<div id="header-actions"></div>
</header>

<div id="content">
<slot></slot>
</div>
`;
}

constructor() {
super();
this.__titleController = new TitleController(this);
this.__titleController.addEventListener('slot-content-changed', (event) => {
const { node } = event.target;
if (node) {
this.setAttribute('aria-labelledby', node.id);
}
});
}

/** @protected */
ready() {
super.ready();
this.addController(this.__titleController);

if (!this.hasAttribute('role')) {
this.setAttribute('role', 'article');
}
}

/** @private */
__onWidgetTitleChanged(widgetTitle) {
this.__titleController.setWidgetTitle(widgetTitle);
}
}

Expand Down
Loading
Loading