We use Stencil.js, a web-component compiler that lets us write custom elements and manage them with TypeScript. Unless you're planning on authoring your own components, this section probably won't be all that interesting to you -- but if you are, great! You've come to the right place.
They're right here. There isn't much, and a brisk read through them will help you get oriented quickly.
If you're familiar with React or Angular, Stencil components operate similarly: values are passed into components as HTML attributes (which Stencil calls "props"), and events are handled using native DOM events and propagation. Encapsulation, composition, use the platform, etc.
We also use Stencil's built-in support for Redux for managing state, both in-page and between pageviews by serializing to local storage. Together, these two tools let us manage the DOM more declaratively, treating the Redux container as the single source of truth. Install the devtools extension for additional fun and occasional profit.
Please do not simply copy and paste an existing component to create a new one -- that's what generators are for. Stencil has a lovely component generator you can invoke simply by running:
yarn run generate
... and following the prompts. For the tag name, please use the convention
pulumi-somenoun
, and Stencil.js will take care of the rest. By default, you'll be
offered the option of creating a stylesheet and both unit and end-to-end tests. Accept
all, then change the file extension of the CSS file to .scss
.
Stencil ships with a development server that runs on port 3333. (It runs independently of the Hugo development server.) To use it, from the project root, run:
make serve_components
... and browse to http://localhost:3333. There, you'll see a single HTML page (whose
source you'll find at /components/src/index.html
) that you can use as a sandbox for
developing and testing your components in isolation. Any changes you make to that page
will be reflected immediately, and changes to the components themselves will trigger a
recompile and be copied into Hugo site at /static/js/
. So if you're running make serve
in one shell and make serve_components
in another, saving a component will trigger a
reload of the Hugo site as well. (I chose to keep these as separate targets simply because
most users who run make serve
won't need be able to work on components concurrently.)
Stencil supports both Sass and the Shadow
DOM.
However, because most of our styling is handled with
TailwindCSS and customizations, we opt out of Shadow DOM and
simply style the components externally, using the Sass defined in
/assets/sass/components
. Other than behavior fundamental to your component (e.g.,
visibility), most of the styling you apply -- color, type, padding, and so on -- should
happen there, and not be expressed as raw CSS alongside your component definition.
This component creates a set of tabs for selecting content, like a language, OS, or cloud
provider. It can operate either as a self-closing component with no children or as a
container with one or more pulumi-choosable
components as its children. In both cases,
by default, the options
you provide will be set globally and persist between pageviews.
Available chooser types are:
cloud
, with supported optionsaws
,azure
, andgcp
os
, with supported optionsmacos
,linux
andwindows
language
, with supported optionsjavascript
,typescript
,python
,go
,csharp
,fsharp
, andvisualbasic
.k8s-language
, with supported optionstypescript
,typescript-kx
, andyaml
Options will be ordered automatically by the component; variable sort order is not exposed in the component API.
If you want to present a chooser that works independently of what's globally stored, use
the mode="local"
attribute, as indicated in the example below.
When a chooser's options don't line up with what's stored globally -- e.g., if a user's
default language is C#, but the chooser doesn't have a C# option -- the chooser switches
into local mode and displays the first available tab and pulumi-choosable
, in order to
ensure something always appears on the page.
This chooser sets the selected os
to persist between pages.
<pulumi-chooser type="os" options="macos,windows,linux" />
This chooser operates in local mode, meaning it ignores whatever happens to be set on the global store. Useful if you want to present content that may be deliberately different from content more relevant to the user's general preferences.
<pulumi-chooser type="os" options="macos,windows,linux" mode="local">
<pulumi-choosable type="os" value="macos">Some Mac Stuff</pulumi-choosable>
<pulumi-choosable type="os" value="windows">Some Windows Stuff</pulumi-choosable>
<pulumi-choosable type="os" value="linux">Some Linux Stuff</pulumi-choosable>
</pulumi-chooser>
This chooser groups a set of options, but hides its controls by providing an
option-style
(which is tabbed
by default):
<pulumi-chooser type="os" options="macos,windows,linux" option-style="none">
<pulumi-choosable type="os" value="macos">Some Mac Stuff</pulumi-choosable>
<pulumi-choosable type="os" value="windows">Some Windows Stuff</pulumi-choosable>
<pulumi-choosable type="os" value="linux">Some Linux Stuff</pulumi-choosable>
</pulumi-chooser>
This is useful if you have a set of related items you want to toggle based on a value on the global store (e.g., language-specific types, filenames and the like).
This component can be supplied either as a child of a chooser, as indicated above, or on its own, in which case it'll simply honor whatever's in the global store.
<body>
<div>
<pulumi-choosable type="os" value="macos">
I'll only appear when the user's preferred OS is Mac. Otherwise, you'll never see me.
</pulumi-choosable>
...
In Markdown files, you can express these choosers either as HTML alone or with Hugo shortcodes. For example:
{{< chooser language "typescript,go,csharp" >}}
.. will render:
<div>
<pulumi-chooser type="language" options="typescript,go,csharp"></pulumi-chooser>
</div>
Similarly:
{{< chooser cloud "aws,azure,gcp" >}}
{{% choosable cloud aws %}}
Some AWS stuff.
{{% /choosable %}}
{{% choosable cloud azure %}}
Some Azure stuff.
{{% /choosable %}}
{{% choosable cloud gcp %}}
Some GCP stuff.
{{% /choosable %}}
{{< /chooser >}}
.. will render:
<div>
<pulumi-chooser type="cloud" options="aws,azure,gcp">
<div>
<pulumi-choosable type="cloud" values="aws" mode="">Some AWS stuff.</pulumi-choosable>
</div>
<div>
<pulumi-choosable type="cloud" values="azure" mode="">Some Azure stuff.</pulumi-choosable>
</div>
<div>
<pulumi-choosable type="cloud" values="gcp" mode="">Some GCP stuff.</pulumi-choosable>
</div>
</pulumi-chooser>
</div>
A few things to note:
-
The
choosable
shortcode renders Markdown automatically. You don't need use themd
shortcode to do that anymore. (So please don't, and thank you!) -
The containing
div
s emitted by thechooser
andchoosable
shortcodes are intentional and in most cases required, because the template renderers we're using today don't know how to handle custom elements like these, so wrapping them in a standard HTML tag is necessary. (It's not necessary in HTML layouts files, though -- only in Markdown files.) -
By default, active choosables will inherit
display: block;
, but you can override this behavior by passing the TailwindCSS utility classinline
in the chooser (typically in conjunction withoption-style="none"
:
<p>
It looks like you're on a
<pulumi-chooser type="os" options="macos,windows,linux" option-style="none" class="inline">
<pulumi-choosable type="os" value="macos">Mac</pulumi-choosable>
<pulumi-choosable type="os" value="windows">Windows</pulumi-choosable>
<pulumi-choosable type="os" value="linux">Linux</pulumi-choosable>
</pulumi-chooser>
computer. Nice!
</p>
This component shows a tooltip bubble over its children on mouseover (or touch). The
content of the bubble is specified using the content
slot.
The component also exposes show()
and hide()
methods for programmatic control over the
bubble's visibility.
<pulumi-tooltip>
<i class="fas fa-question-circle"></i>
<span slot="content">
You hovered over the icon!
</span>
</pulumi-tooltip>
<pulumi-tooltip id="my-tooltip">
<i class="fas fa-question-circle"></i>
<span slot="content">
You called the show() method!
</span>
</pulumi-tooltip>
<script>
var tooltip = document.querySelector("#my-tooltip");
tooltip
.show()
.then(function() { console.log("The tooltip is visible."); });
tooltip
.hide()
.then(function() { console.log("The tooltip is not visible."); });
</script>
Blame Christian. Also, he's happy to help.