Templates
Ultimately, a template is a javascript object containing the information needed to populate a core component with inner elements and configure them. The template tells a core component which building block components to use and provides the props the core component needs to pass to each of them.
Below is the template used for the demos in the previous guide:
import type { FullBarElementType } from '@bezda/rhp-core';
// custom bar template
const barTemplate: FullBarElementType[] = [
{
type: "bar-content-container",
elements: [
{
type: "bar-dec-container",
elements: [
{
type: "bar",
order: 0,
CSS: "display: grid;place-items: center;box-sizing: border-box;border-radius: 0 10px 10px 0;overflow: hidden;height: auto; transition-property: flex, border;transition-duration: 1s;transition-timing-function: ease-in-out;& div {display:flex;align-items: center;opacity: 0.5;transition: opacity 0.3s ease-in-out;text-align: center;}& div > span {opacity: 0;color: var(--sl-color-bg);font-weight:500;transition: opacity 0.3s ease-in-out;}",
markup: "<div style='background-color: {{color}};height:100%;width:100%;justify-content: center;'><span>block</span></div>",
},
{
type: "decoration",
order: 1,
useData: true,
CSS: "align-items: center;height: 100%;& > div {opacity: 0.5;transition: opacity 0.3s ease-in-out;transition: border 0.4s ease-in; border-style: none; border-width: 3px;}",
markup: "<div style='border-radius: 10px;border-color: {{color}};color: {{color}};height: 100%;aspect-ratio: 1/1;display: grid;place-items: center;grid-template: 1fr / 1fr;'><div class='block-value' style='transition: opacity 0.2s ease-in-out;grid-column: 1 / 1; grid-row: 1 / 1;'>{{$dataValue}}</div><div class='block-text' style='transition: opacity 0.2s ease-in-out 0.2s;opacity: 0;grid-column: 1 / 1; grid-row: 1 / 1;'>block</div></div>",
}
],
CSS: "padding-top: 12px;padding-bottom: 12px;gap: 24px;background: none;& .bar-decoration > div:hover {opacity: 1;border-style: solid;}& .bar > div:hover {opacity: 1;}& .bar > div:hover span {opacity: 1;}& .bar-decoration > div:hover .block-text {opacity: 1!important;}& .bar-decoration > div:hover .block-value {opacity: 0;}",
decorationWidth: "52px",
order: 0,
}
],
decorationWidth: "10%",
order: 1,
CSS:"overflow: visible!important;",
},
{
type: "decoration",
order: 0,
CSS: "height: 100%;opacity: 0.5;transition: opacity 0.3s ease-in-out;&:hover {opacity: 1;} &:hover .block-text {opacity: 1!important;} &:hover .block-value {opacity: 0;}",
markup: "<div style='width: 100%; height: 100%; border-right: 3px solid {{color}};padding-top: 12px;padding-bottom: 12px;'><div style='border-radius: 10px;border: 3px solid {{color}};height: 100%;aspect-ratio: 1/1;margin-right: 24px;margin-left: 24px;display: grid;place-items: center;grid-template: 1fr / 1fr;'><div class='block-value' style='transition: opacity 0.2s ease-in-out;grid-column: 1 / 1; grid-row: 1 / 1;'>{{bar-label}}</div><div class='block-text' style='transition: opacity 0.2s ease-in-out 0.2s;opacity: 0;grid-column: 1 / 1; grid-row: 1 / 1;'>block</div></div></div>",
}
];
A template is just an array of objects of type: FullBarElementType
. This type is an umbrella type that contains all the types that can be used in a template such as DecorationType
and BarContentContainerType
for starters.
The template above is passed to a <PlotSegment>
core component to produce the following:
For the sake of simplicity, ignore all properties in the template except name
and order
. Note that the template has a nested structure:
- bar-content-container (order: 1)
- bar-dec-container (order: 0)
- bar (order: 0)
- decoration (order: 1)
- bar-dec-container (order: 0)
- decoration (order: 0)
The type
property tells <PlotSegment>
which component to use. For example,
type | → | component |
---|---|---|
“bar-content-container” | → | <BarContentContainer> |
“bar-dec-container” | → | <BarDecContainer> |
“decoration” | → | <Decoration> |
“bar” | → | <Bar> |
The order
property tells PlotSegment the order in which to place the components.
Direct children are placed from left to right in <PlotSegment>
with lower order values being placed first (left). In the example above,
a single <BarContentContainer>
component and a single <Decoration>
component are the direct children of the <PlotSegment>
component where the <BarContentContainer>
has an order of 1
and the <Decoration>
has an order of 0. This means that the <Decoration>
will be placed first (left) and the <BarContentContainer>
will be placed
second (right). Elements inside <BarContentContainer>
are placed from top to bottom with lower order values being placed first (top).
Elements inside <BarDecContainer>
are placed from left to right with lower order values being placed first (left).
This template has a structure that is very versatile. If you want a bar group plot, you can add more
“bar-dec-container” objects to the same array containing the first one. If you want a bi-directional/two-sided
bar chart, you can add another “bar-content-container” object to the same array that the first one is in. Just make
sure the decoration that also is in the same array has an order
value that is in-between the two “bar-content-container”s.
This will place the decoration (which contains the label) in between. Dont forget to flip the newly added “bar-content-container”
using CSS (change bar-dec-container’s flex-direction
to row-reverse
or use CSS transform
property) so that the bars are aligned back-to-back.
CSS
The above template contains all the CSS and styles needed to get the transitions and hover effects. The CSS does not need to be in the template and can instead be provided in external style sheets. However, providing the CSS in the template keeps all styling needed for the plot in one place and makes it easier to share templates or use in other projects. You dont have to hunt for relavant CSS in style sheets and elsewhere. Its all in one place.
In general, there are four ways to add styling to a component, three of which involve the template:
- Add CSS via the
CSS
property of a template object.
- Add style/inline CSS via the
markup
property of a template object. - Add CSS classes (like Tailwind CSS classes if you have tailwind added to your project) via the
class
property of a template object. - Add CSS via external style sheets. Essentially, the same as item 3, except that the rendered HTML elements have default classes so you dont need to provide your own.
One of the things that makes the CSS prop powerful, is that it not takes CSS declarations via key-value pairs, it also accepts full declaration blocks that allow you to target child and sibling elements!
Markup
The markup
property is one of the things that makes templates really special.
It allows you to inject HTML into components.
Container-type building block components dont typically have this feature while non-container types like <Bar>
and <Decoration>
do.
The markup
property accepts a string that contains HTML markup. But thats not all. You can also inject variable data into the markup.
This is done by using the double curly braces syntax: {{variableName}}
.
The variableName
is the (string) key of the vars
data object where the data to be injected can be found.
This feature is the main way of incorporating visual content or even styling that changes from core component to core component and/or changes during runtime.
In the template example above, the bar
and decoration
type objects make use of the color
variable to set background color (of the bar
element) and the border color (for both decoration
elements).
As we will see in more detail in the next guide, variables in the vars
object are key-value pairs where the key is the variable name and the value is an array of numbers or strings - one for each core-component.
Note that you do not have to provide a value for every core component. If the number of values you provide in the array are less than the number of core components, the components that dont have a corresponding value will jump to the beginning of the array and look from there.
To illuminate this further, consider the following. Lets say you have 4 core components and you provide an array of 2 values. What will happen is, the first core component will get the first value, the second core component will get the second value, the third core component will get the first value and the fourth core component will get the second value. Essentially, components loop over the array of values you provide if their component index exceeds the last index of the array. This is useful for creating repeating patterns or for when you want to provide a single value for all core components. To do the latter, you can provide an array with a single value.
An example where you might take advantage of this is when you want an alternating pattern like the popular bar chart style where the background of the first bar is a different color than the next, then back again for the bar after that and so on.