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 vaadin-dashboard-layout #7653

Merged
merged 11 commits into from
Aug 16, 2024
42 changes: 42 additions & 0 deletions dev/dashboard-layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dashboard layout</title>
<script type="module" src="./common.js"></script>

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

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

vaadin-dashboard-layout {
--vaadin-dashboard-col-min-width: 300px;
--vaadin-dashboard-col-max-width: 500px;
}
</style>
</head>

<body>
<vaadin-dashboard-layout>
<div>Item 0</div>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
</vaadin-dashboard-layout>
</body>
</html>
20 changes: 20 additions & 0 deletions packages/dashboard/src/vaadin-dashboard-layout-mixin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @license
* Copyright (c) 2000 - 2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
*
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import type { Constructor } from '@open-wc/dedupe-mixin';

/**
* A mixin to enable the dashboard layout functionality.
*/
export declare function DashboardLayoutMixin<T extends Constructor<HTMLElement>>(
base: T,
): Constructor<DashboardLayoutMixinClass> & T;

export declare class DashboardLayoutMixinClass {}
45 changes: 45 additions & 0 deletions packages/dashboard/src/vaadin-dashboard-layout-mixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @license
* Copyright (c) 2000 - 2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
*
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import { css } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';

/**
* A mixin to enable the dashboard layout functionality
*
* @polymerMixin
* @mixes ResizeMixin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no ResizeMixin used here, so this line should be probably removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, removed for now. ResizeMixin is to be added in a separate PR.

*/
export const DashboardLayoutMixin = (superClass) =>
class DashboardLayoutMixinClass extends superClass {
static get styles() {
return css`
:host {
display: grid;
/* Default min and max column widths */
--_vaadin-dashboard-default-col-min-width: 200px;
--_vaadin-dashboard-default-col-max-width: 400px;
/* Effective min and max column widths */
--_vaadin-dashboard-col-min-width: var(
--vaadin-dashboard-col-min-width,
var(--_vaadin-dashboard-default-col-min-width)
);
--_vaadin-dashboard-col-max-width: var(
--vaadin-dashboard-col-max-width,
var(--_vaadin-dashboard-default-col-max-width)
);

grid-template-columns: repeat(
auto-fill,
minmax(var(--_vaadin-dashboard-col-min-width), var(--_vaadin-dashboard-col-max-width))
);
}
`;
}
};
25 changes: 25 additions & 0 deletions packages/dashboard/src/vaadin-dashboard-layout.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license
* Copyright (c) 2000 - 2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
*
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';

/**
* A responsive, grid-based dashboard layout component
*/
declare class DashboardLayout extends ElementMixin(ThemableMixin(HTMLElement)) {}

declare global {
interface HTMLElementTagNameMap {
'vaadin-dashboard-layout': DashboardLayout;
}
}

export { DashboardLayout };
40 changes: 40 additions & 0 deletions packages/dashboard/src/vaadin-dashboard-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @license
* Copyright (c) 2000 - 2024 Vaadin Ltd.
*
* This program is available under Vaadin Commercial License and Service Terms.
*
*
* See https://vaadin.com/commercial-license-and-service-terms for the full
* license.
*/
import { html, LitElement } from 'lit';
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 { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { DashboardLayoutMixin } from './vaadin-dashboard-layout-mixin.js';

/**
* A responsive, grid-based dashboard layout component
*
* @customElement
* @extends HTMLElement
* @mixes DashboardLayoutMixin
* @mixes ElementMixin
* @mixes ThemableMixin
*/
class DashboardLayout extends DashboardLayoutMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) {
static get is() {
return 'vaadin-dashboard-layout';
}

/** @protected */
render() {
return html`<slot></slot>`;
}
}

defineCustomElement(DashboardLayout);

export { DashboardLayout };
3 changes: 2 additions & 1 deletion packages/dashboard/src/vaadin-dashboard.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
* license.
*/
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { DashboardLayoutMixin } from './vaadin-dashboard-layout-mixin.js';

/**
* A responsive, grid-based dashboard layout component
*/
declare class Dashboard extends ElementMixin(HTMLElement) {}
declare class Dashboard extends DashboardLayoutMixin(ElementMixin(HTMLElement)) {}

declare global {
interface HTMLElementTagNameMap {
Expand Down
4 changes: 3 additions & 1 deletion packages/dashboard/src/vaadin-dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ import { html, LitElement } from 'lit';
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 { DashboardLayoutMixin } from './vaadin-dashboard-layout-mixin.js';

/**
* A responsive, grid-based dashboard layout component
*
* @customElement
* @extends HTMLElement
* @mixes ElementMixin
* @mixes DashboardLayoutMixin
*/
class Dashboard extends ElementMixin(PolylitMixin(LitElement)) {
class Dashboard extends DashboardLayoutMixin(ElementMixin(PolylitMixin(LitElement))) {
static get is() {
return 'vaadin-dashboard';
}
Expand Down
148 changes: 148 additions & 0 deletions packages/dashboard/test/dashboard-layout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { expect } from '@vaadin/chai-plugins';
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
import '../vaadin-dashboard-layout.js';
import type { DashboardLayout } from '../vaadin-dashboard-layout.js';
import {
getColumnWidths,
getElementFromCell,
setGap,
setMaximumColumnWidth,
setMinimumColumnWidth,
} from './helpers.js';

/**
* Validates the given grid layout.
*
* This function iterates through a number matrix representing the IDs of
* the items in the layout, and checks if the elements in the corresponding
* cells of the grid match the expected IDs.
*
* For example, the following layout would expect a grid with two columns
* and three rows, where the first row has one element with ID "item-0" spanning
* two columns, and the second row has two elements with IDs "item-1" and "item-2"
* where the first one spans two rows, and the last cell in the third row has
* an element with ID "item-3":
*
* ```
* [
* [0, 0],
* [1, 2],
* [1, 3]
* ]
* ```
*/
function expectLayout(dashboard: DashboardLayout, layout: number[][]) {
layout.forEach((row, rowIndex) => {
row.forEach((itemId, columnIndex) => {
const element = getElementFromCell(dashboard, rowIndex, columnIndex);
if (!element) {
expect(itemId).to.be.undefined;
} else {
expect(element.id).to.equal(`item-${itemId}`);
}
});
});
}

describe('dashboard layout', () => {
let dashboard: DashboardLayout;
const columnWidth = 100;

beforeEach(async () => {
dashboard = fixtureSync(`
<vaadin-dashboard-layout>
<div id="item-0">Item 0</div>
<div id="item-1">Item 1</div>
</vaadin-dashboard-layout>
`);
// Disable gap between items in these tests
setGap(dashboard, 0);
// Set the column width to a fixed value
setMinimumColumnWidth(dashboard, columnWidth);
setMaximumColumnWidth(dashboard, columnWidth);
// Make the dashboard wide enough to fit all items on a single row
dashboard.style.width = `${columnWidth * dashboard.childElementCount}px`;

await nextFrame();

expect(getColumnWidths(dashboard)).to.eql([columnWidth, columnWidth]);
/* prettier-ignore */
expectLayout(dashboard, [
[0, 1],
]);
});

it('should be responsive', () => {
// Narrow down the component to fit one column
dashboard.style.width = `${columnWidth}px`;

/* prettier-ignore */
expectLayout(dashboard, [
[0],
[1],
]);
});

describe('minimum column width', () => {
it('should have a default minimum column width', () => {
// Clear the minimum column width used in the tests
setMinimumColumnWidth(dashboard, undefined);
// Narrow down the component to have the width of 0
dashboard.style.width = '0';
// Expect the column width to equal the default minimum column width
expect(getColumnWidths(dashboard)).to.eql([200]);
});

it('should have one overflown column if narrowed below miminum column width', () => {
tomivirkki marked this conversation as resolved.
Show resolved Hide resolved
// Narrow down the component to have the width of half a column
dashboard.style.width = `${columnWidth / 2}px`;
// Expect the column width to still be the same (overflown)
expect(getColumnWidths(dashboard)).to.eql([columnWidth]);
});

it('should not overflow if narrowed to the minimum column width', () => {
// Set the min column width to half of the column width
setMinimumColumnWidth(dashboard, columnWidth / 2);
// Narrow down the component to have the width of half a column
dashboard.style.width = `${columnWidth / 2}px`;
// Expect the column width to equal the min column width
expect(getColumnWidths(dashboard)).to.eql([columnWidth / 2]);
});

it('should have one wide column with large minimum column width', () => {
setMaximumColumnWidth(dashboard, columnWidth * 2);
// Set the min column width to be twice as wide
setMinimumColumnWidth(dashboard, columnWidth * 2);
// Expect there to only be one column with twice the width
expect(getColumnWidths(dashboard)).to.eql([columnWidth * 2]);
/* prettier-ignore */
expectLayout(dashboard, [
[0],
[1],
]);
});
});

describe('maximum column width', () => {
it('should have a default maximum column width', () => {
// Clear the maximum column width used in the tests
setMaximumColumnWidth(dashboard, undefined);
// Widen the component
dashboard.style.width = '1000px';
// Expect the column width to equal the default maximum column width
expect(getColumnWidths(dashboard)[0]).to.eql(400);
});

it('should have one wide column with large maximum column width', () => {
// Allow the column to be twice as wide
setMaximumColumnWidth(dashboard, columnWidth * 2);
// Expect there to only be one column with twice the width
expect(getColumnWidths(dashboard)).to.eql([columnWidth * 2]);
/* prettier-ignore */
expectLayout(dashboard, [
[0],
[1],
]);
});
});
});
Loading
Loading