-
Notifications
You must be signed in to change notification settings - Fork 83
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add vaadin-dashboard-layout (#7653)
- Loading branch information
1 parent
2134205
commit 8864042
Showing
14 changed files
with
409 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* @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 | ||
*/ | ||
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: 25rem; | ||
--_vaadin-dashboard-default-col-max-width: 1fr; | ||
/* 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)) | ||
); | ||
} | ||
`; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
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; | ||
const remValue = parseFloat(getComputedStyle(document.documentElement).fontSize); | ||
|
||
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([remValue * 25]); | ||
}); | ||
|
||
it('should have one overflown column if narrowed below minimum column width', () => { | ||
// 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); | ||
expect(getColumnWidths(dashboard)).to.eql([columnWidth, columnWidth]); | ||
// Widen the component to have the width of 2.5 columns | ||
dashboard.style.width = `${columnWidth * 2.5}px`; | ||
expect(getColumnWidths(dashboard)).to.eql([columnWidth * 1.25, columnWidth * 1.25]); | ||
// Widen the component to have the width of 3 columns | ||
dashboard.style.width = `${columnWidth * 3}px`; | ||
expect(getColumnWidths(dashboard)).to.eql([columnWidth, columnWidth, columnWidth]); | ||
// Shrink the component to have the width of 1.5 columns | ||
dashboard.style.width = `${columnWidth * 1.5}px`; | ||
expect(getColumnWidths(dashboard)).to.eql([columnWidth * 1.5]); | ||
}); | ||
|
||
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], | ||
]); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.