I'm starting out on a project that will standardise some increasingly complex webcomponents. In a final version the goal is to support multiple different design systems (as the components might be used for different brands, which require different color schemes, border radiuses, margins, etc.). This is my attempt at creating a template that can easily be adapted for other (and more complex) webcomponents. For now, it's only a button with different styles / sizes and limited functionality. Colors are only placeholders for now and will most likely be refactored to variables. Once I'm confident in the approach I'll extend functionality and start on the next components.
I expect there to be a lot (!) of room improvement, as this implementation is just what seemed the most intuitive to me when starting to work with webcomponents.
component-button.js
class ComponentButton extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.defineProperties(props);
this.render();
}
/**
* Define properties (including getters and setters) for componentButton
* @param props Array of property objects {name, validValues, defaultValue}
*/
defineProperties(props) {
const props = [
{ name: "variant", validValues: new Set(["primary", "secondary", "text"]), defaultValue: "primary" },
{ name: "size", validValues: new Set(["s", "m", "l"]), defaultValue: "m" },
{ name: "disabled", validValues: new Set(["true", "false"]), defaultValue: "false" }
];
for (let { name, validValues, defaultValue } of props) {
const attribute = this.getAttribute(name);
const initialValue = validValues.has(attribute) ? attribute : defaultValue;
Object.defineProperty(this, name, { value: initialValue });
}
}
/**
* Getter for all relevant css-classes from relevant properties
* @returns {string}
*/
get classes() {
let clss = [
`my-button`,
`my-button_${this.size}`,
`my-button_${this.variant}`
];
if (eval(this.disabled)) {
clss.push(`my-button_disabled`)
}
return clss.join(" ");
}
render() {
this.shadowRoot.innerHTML = `
${this.styles}
<button class="${this.classes}">
<slot class="my-button__slot"> Click </slot>
</button>
`;
}
/**
* Getter for all relevant css-styles
* @returns {string}
*/
get styles() {
return `
<style>
.my-button {
position: relative;
overflow: hidden;
display: flex;
margin: 5px;
justify-content: center;
padding: 18px 30px;
text-align: center;
text-decoration: none;
cursor: pointer;
font-weight: 300;
font-family: inherit;
transition: all .3s cubic-bezier(.75, .02, .5, 1);
}
.my-button_s {
font-size: 16px;
min-width: 144px;
}
.my-button_m {
font-size: 18px;
min-width: 162px;
}
.my-button_l {
font-size: 20px;
min-width: 180px;
}
.my-button_primary {
border: 1px solid darkblue;
background-color: darkblue;
color: #FFF;
}
.my-button_primary:hover {
border: 1px solid blue;
background-color: blue;
color: #FFF;
}
.my-button_primary.my-button_disabled {
border: 1px solid #333;
background-color: #333;
color: #FFF;
cursor: not-allowed;
}
.my-button_secondary {
border: 1px solid darkblue;
background-color: transparent;
color: darkblue;
}
.my-button_secondary:hover {
border: 1px solid blue;
background-color: transparent;
color: blue;
}
.my-button_secondary.my-button_disabled {
border: 1px solid #4c4c4c;
background-color: transparent;
color: #4c4c4c;
cursor: not-allowed;
}
.my-button_text {
border: none;
background-color: transparent;
color: darkblue;
}
.my-button_text.my-button_s, .my-button_text.my-button_m, .my-button_text.my-button_l {
min-width: auto;
}
.my-button_text .my-button__slot {
display: block;
position: relative;
}
.my-button_text:hover {
color: blue;
}
.my-button_disabled {
color: #4c4c4c;
cursor: not-allowed;
}
</style>
`;
}
}
window.customElements.define('component-button', componentButton)
index.html
For visual testing
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Button Visual Test</title>
<script src="./component-button.js"></script>
</head>
<body>
<div style="display: flex; flex-direction: column">
<component-button> Full Default Button </component-button>
<component-button variant="secondary"> Default Secondary Button </component-button>
<component-button variant="text"> Default Text Button </component-button>
<component-button variant="primary" size="s" disabled="true"> Primary Size_S disabled </component-button>
<component-button variant="secondary" size="m" disabled="true"> Secondary Size_M disabled </component-button>
<component-button variant="text" size="l" disabled="true"> Text Size_L disabled </component-button>
</div>
</body>
</html>