So I'm working on a very small web component to feature as part of a much larger design system.
I'm a bit new to use of Web Components, but I know this particular web-component could be used many, many times in a single layout.
This web component controls how much vertical space to put around any child components that are passed into it.
The anatomy of the component is pretty simple:
import { LitElement, html, css, unsafeCSS, property, customElement } from 'lit-element';
export const spacingAmounts = {
'x-small': css`4px`,
'small': css`8px`,
'medium': css`12px`,
'large': css`16px`,
'x-large': css`20px`,
'2x-large': css`30px`,
'3x-large': css`40px`,
'4x-large': css`60px`,
'5x-large': css`90px`,
'6x-large': css`120px`,
};
const createSpacingStyleRules = (direction: 'top' | 'bottom') => {
return Object.keys(spacingAmounts).map(s => {
const amount = spacingAmounts[s];
return css`
:host([${unsafeCSS(direction)}="${unsafeCSS(s)}"]) {
margin-${unsafeCSS(direction)}: ${unsafeCSS(amount)};
}
`;
});
};
@customElement('gu-vertical-space')
export class GuVerticalSpace extends LitElement {
@property() top: string;
@property() bottom: string;
static get styles() {
const styles = [
css`
:host {
display: block;
}
`,
// ----------------------------------------------------------
// @TODO:
// test if it's better performance wise to either:
//
// 1 - generate a verbose list of static styles for
// each instance of gu-vertical-space
// or
// 2 - generate a tiny amount of styles on the fly,
// based on property inputs...
// ----------------------------------------------------------
// ...createSpacingStyleRules('top'),
// ...createSpacingStyleRules('bottom'),
];
return styles;
}
render() {
const styles = [];
if (this.top) {
styles.push(css`
:host([top="${unsafeCSS(this.top)}"]) {
margin-top: ${spacingAmounts[this.top]}
}
`);
}
if (this.bottom) {
styles.push(css`
:host([bottom="${unsafeCSS(this.bottom)}"]) {
margin-bottom: ${spacingAmounts[this.bottom]}
}
`);
}
return html`
<style>${styles}</style>
<slot></slot>
`;
}
}
There are 2 approaches here for mapping pre-defined margin amounts to either the top or bottom of the web component's host.
The currently active approach is simply to dynamically generate a <style>
block inside the render function, which contains any margin amounts, as decided by the "top" or "bottom" input properties.
The other approach that I'm contemplating (is commented out atm though) - is to staticly generate a massive list of style rules - thus avoiding the need to generate any dynamic style stuff inside the render() function, which could be an anti-pattern - if i'm understanding the lit-element docs correctly.
Perhaps there's a more elegant approach that I'm missing? I'm leaning toward the current approach, just because it feels simpler to understand - but curious as to what others think!
As suggested by Alan, a much simpler solution to this issue is to use css-variables.
Basically, just map the input margin amounts as a css-variable at the connect() life-cycle event (or render if you imagine the props will ever change after the initial render) - and call it a day!
static get styles() {
return css`
:host {
display: block;
margin-top: var(--margin-top);
margin-bottom: var(--margin-bottom);
}
`;
}
connectedCallback() {
super.connectedCallback();
this.style.setProperty(
'--margin-top',
this.top
? spacingAmounts[this.top]
: 'unset'
);
this.style.setProperty(
'--margin-bottom',
this.bottom
? spacingAmounts[this.bottom]
: 'unset'
);
}