From b5bd559e357d83cb7911f93669ad7e384b7dead4 Mon Sep 17 00:00:00 2001 From: Ayo Ayco Date: Wed, 26 Mar 2025 22:43:07 +0100 Subject: [PATCH] feat(docs): create pages for significant readme section --- docs/astro.config.mjs | 25 +++-- docs/src/content/docs/guides/Index.md | 11 --- docs/src/content/docs/guides/examples.md | 59 ++++++++++++ docs/src/content/docs/guides/exports.md | 42 +++++++++ .../content/docs/guides/getting-started.md | 42 +++++++++ docs/src/content/docs/guides/just-parts.md | 24 +++++ docs/src/content/docs/guides/library-size.md | 8 ++ .../content/docs/guides/life-cycle-hooks.md | 92 +++++++++++++++++++ docs/src/content/docs/guides/prop-access.md | 44 +++++++++ docs/src/content/docs/guides/shadow-dom.md | 16 ++++ docs/src/content/docs/guides/styling.md | 45 +++++++++ .../content/docs/guides/template-vs-render.md | 12 +++ docs/src/content/docs/guides/usage.md | 43 +++++++++ docs/src/content/docs/index.mdx | 6 +- 14 files changed, 449 insertions(+), 20 deletions(-) delete mode 100644 docs/src/content/docs/guides/Index.md create mode 100644 docs/src/content/docs/guides/examples.md create mode 100644 docs/src/content/docs/guides/exports.md create mode 100644 docs/src/content/docs/guides/getting-started.md create mode 100644 docs/src/content/docs/guides/just-parts.md create mode 100644 docs/src/content/docs/guides/library-size.md create mode 100644 docs/src/content/docs/guides/life-cycle-hooks.md create mode 100644 docs/src/content/docs/guides/prop-access.md create mode 100644 docs/src/content/docs/guides/shadow-dom.md create mode 100644 docs/src/content/docs/guides/styling.md create mode 100644 docs/src/content/docs/guides/template-vs-render.md create mode 100644 docs/src/content/docs/guides/usage.md diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 5aeba1d..b5247ff 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -4,6 +4,9 @@ import starlight from '@astrojs/starlight' // https://astro.build/config export default defineConfig({ + redirects: { + '/guides/': '/guides/why', + }, integrations: [ starlight({ title: 'WCB', @@ -16,14 +19,24 @@ export default defineConfig({ label: 'Guides', items: [ // Each item here is one entry in the navigation menu. - { label: 'Getting Started', slug: 'guides' }, - { label: 'Why?', slug: 'why' }, + 'why', + 'getting-started', + 'exports', + 'usage', + 'examples', + 'template-vs-render', + 'prop-access', + 'styling', + 'shadow-dom', + 'just-parts', + 'life-cycle-hooks', + 'library-size', ], }, - { - label: 'Reference', - autogenerate: { directory: 'reference' }, - }, + // { + // label: 'Reference', + // autogenerate: { directory: 'reference' }, + // }, ], }), ], diff --git a/docs/src/content/docs/guides/Index.md b/docs/src/content/docs/guides/Index.md deleted file mode 100644 index e292bd7..0000000 --- a/docs/src/content/docs/guides/Index.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Getting Started -description: A guide in my new Starlight docs site. ---- - -Guides lead a user through a specific task they want to accomplish, often with a sequence of steps. -Writing a good guide requires thinking about what your users are trying to do. - -## Further reading - -- Read [about how-to guides](https://diataxis.fr/how-to-guides/) in the Diátaxis framework diff --git a/docs/src/content/docs/guides/examples.md b/docs/src/content/docs/guides/examples.md new file mode 100644 index 0000000..be167c1 --- /dev/null +++ b/docs/src/content/docs/guides/examples.md @@ -0,0 +1,59 @@ +--- +title: Examples +slug: examples +--- + +### 1. To-Do App + +A simple app that allows adding / completing tasks: +[View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/GRegyVe?editors=1010) + +![To-Do App screen recording](https://raw.githubusercontent.com/ayoayco/web-component-base/main/assets/todo-app.gif) + +### 2. Single HTML file Example + +Here is an example of using a custom element in a single .html file. + +```html + + + + WC Base Test + + + + + + + +``` + +### 3. Feature Demos + +Some feature-specific demos: + +1. [Context-Aware Post-Apocalyptic Human](https://codepen.io/ayoayco-the-styleful/pen/WNqJMNG?editors=1010) +1. [Simple reactive property](https://codepen.io/ayoayco-the-styleful/pen/ZEwoNOz?editors=1010) +1. [Counter & Toggle](https://codepen.io/ayoayco-the-styleful/pen/PoVegBK?editors=1010) +1. [Using custom templating (lit-html)](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010) +1. [Using dynamic style objects](https://codepen.io/ayoayco-the-styleful/pen/bGzXjwQ?editors=1010) +1. [Using the Shadow DOM](https://codepen.io/ayoayco-the-styleful/pen/VwRYVPv?editors=1010) +1. [Using tagged templates in your vanilla custom element](https://codepen.io/ayoayco-the-styleful/pen/bGzJQJg?editors=1010) diff --git a/docs/src/content/docs/guides/exports.md b/docs/src/content/docs/guides/exports.md new file mode 100644 index 0000000..e535194 --- /dev/null +++ b/docs/src/content/docs/guides/exports.md @@ -0,0 +1,42 @@ +--- +title: Exports +slug: exports +--- + +You can import everything separately, or in a single file each for the main exports and utilities. + +### Main Exports + +```js +// all in a single file + +import { WebComponent, html } from 'web-component-base' + +// in separate files + +import { WebComponent } from 'web-component-base/WebComponent.js' + +import { html } from 'web-component-base/html.js' +``` + +### Utilities + +```js +// in a single file + +import { + serialize, + deserialize, + getCamelCase, + getKebabCase, + createElement, +} from 'web-component-base/utils' + +// or separate files + +import { serialize } from 'web-component-base/utils/serialize.js' + +import { createElement } from 'web-component-base/utils/create-element.js' + +// etc... +``` diff --git a/docs/src/content/docs/guides/getting-started.md b/docs/src/content/docs/guides/getting-started.md new file mode 100644 index 0000000..a74fb5a --- /dev/null +++ b/docs/src/content/docs/guides/getting-started.md @@ -0,0 +1,42 @@ +--- +title: Getting Started +slug: getting-started +--- + +**Web Component Base (WCB)** is a zero-dependency, tiny JS base class for creating reactive [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_Components/Using_custom_elements) easily. + +When you extend the WebComponent class for your component, you only have to define the template and properties. Any change in any property value will automatically cause just the component UI to render. + +The result is a reactive UI on property changes. + +## Project Status + +It is ready for many simple cases we see people use custom elements for. If you have a cool project built on **WebComponent.io** we'd love to know! :) + +For building advanced interactions, we have an in-progress work on smart diffing to prevent component children being wiped on interaction. + +In the mean time, if you have some complex needs, we recommend using the `WebComponent` base class with a more mature rendering approach like `lit-html`... and here's a demo for that: [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010). + +...or you can even [use just parts](#just-the-templating) of it for your own base class. + +## Installation + +The library is distributed as complete ECMAScript Modules (ESM) and published on [NPM](https://ayco.io/n/web-component-base). Please file an issue in our [issue tracker](https://ayco.io/gh/web-component-base/issues) for problems or requests regarding our distribution. + +### Import via CDN + +It is possible to import directly using a CDN like [esm.sh](https://esm.sh/web-component-base) or [unpkg](https://unpkg.com/web-component-base) in your vanilla JS component or HTML files. In all examples in this document, we use `unpkg` but you can find on CodePen examples that `esm.sh` also works well. + +Additionally, we use `@latest` in the rest of our [usage examples](#usage) here for simplicity, but take note that this incurs additional resolution steps for CDNs to find the actual latest published version. You may replace the `@latest` in the URL with specific versions as shown in our CodePen examples, and this will typically be better for performance. + +```js +import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js' +``` + +### Installation via npm + +Usable for projects with bundlers or using import maps pointing to the specific files downloaded in `node_modules/web-component-base`. + +```bash +npm i web-component-base +``` diff --git a/docs/src/content/docs/guides/just-parts.md b/docs/src/content/docs/guides/just-parts.md new file mode 100644 index 0000000..b2b8397 --- /dev/null +++ b/docs/src/content/docs/guides/just-parts.md @@ -0,0 +1,24 @@ +--- +title: Using Just Some Parts +slug: 'just-parts' +--- + +You don't have to extend the whole base class just to use some features. All internals are exposed and usable separately so you can practically build the behavior on your own classes. + +Here's an example of using the `html` tag template on a class that extends from vanilla `HTMLElement`... also [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzJQJg?editors=1010). + +```js +import { html } from 'https://unpkg.com/web-component-base/html' +import { createElement } from 'https://unpkg.com/web-component-base/utils' + +class MyQuote extends HTMLElement { + connectedCallback() { + const el = createElement( + html` ` + ) + this.appendChild(el) + } +} + +customElements.define('my-quote', MyQuote) +``` diff --git a/docs/src/content/docs/guides/library-size.md b/docs/src/content/docs/guides/library-size.md new file mode 100644 index 0000000..e63f4dc --- /dev/null +++ b/docs/src/content/docs/guides/library-size.md @@ -0,0 +1,8 @@ +--- +title: Library Size +slug: library-size +--- + +All the functions and the base class in the library are minimalist by design and only contains what is needed for their purpose. + +The main export (with `WebComponent` + `html`) is **1.7 kB** (min + gzip) according to [bundlephobia.com](https://bundlephobia.com/package/web-component-base@latest), and the `WebComponent` base class is just **1.08 kB** (min + brotli) according to [size-limit](http://github.com/ai/size-limit). diff --git a/docs/src/content/docs/guides/life-cycle-hooks.md b/docs/src/content/docs/guides/life-cycle-hooks.md new file mode 100644 index 0000000..bf95ebb --- /dev/null +++ b/docs/src/content/docs/guides/life-cycle-hooks.md @@ -0,0 +1,92 @@ +--- +title: Life-Cycle Hooks +slug: life-cycle-hooks +--- + +Define behavior when certain events in the component's life cycle is triggered by providing hook methods + +### onInit() + +- Triggered when the component is connected to the DOM +- Best for setting up the component + +```js +import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js' + +class ClickableText extends WebComponent { + // gets called when the component is used in an HTML document + onInit() { + this.onclick = () => console.log('>>> click!') + } + + get template() { + return `Click me!` + } +} +``` + +### afterViewInit() + +- Triggered after the view is first initialized + +```js +class ClickableText extends WebComponent { + // gets called when the component's innerHTML is first filled + afterViewInit() { + const footer = this.querySelector('footer') + // do stuff to footer after view is initialized + } + + get template() { + return `` + } +} +``` + +### onDestroy() + +- Triggered when the component is disconnected from the DOM +- best for undoing any setup done in `onInit()` + +```js +import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js' + +class ClickableText extends WebComponent { + clickCallback() { + console.log('>>> click!') + } + + onInit() { + this.onclick = this.clickCallback + } + + onDestroy() { + console.log('>>> removing event listener') + this.removeEventListener('click', this.clickCallback) + } + + get template() { + return `Click me!` + } +} +``` + +### onChanges() + +- Triggered when an attribute value changed + +```js +import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js' + +class ClickableText extends WebComponent { + // gets called when an attribute value changes + onChanges(changes) { + const { property, previousValue, currentValue } = changes + console.log('>>> ', { property, previousValue, currentValue }) + } + + get template() { + return `Click me!` + } +} +``` diff --git a/docs/src/content/docs/guides/prop-access.md b/docs/src/content/docs/guides/prop-access.md new file mode 100644 index 0000000..cee5adb --- /dev/null +++ b/docs/src/content/docs/guides/prop-access.md @@ -0,0 +1,44 @@ +--- +title: Prop Access +slug: prop-access +--- + +The `props` property of the `WebComponent` interface is provided for easy read/write access to a camelCase counterpart of _any_ observed attribute. + +```js +class HelloWorld extends WebComponent { + static props = { + myProp: 'World', + } + get template() { + return html`

Hello ${this.props.myProp}

` + } +} +``` + +Assigning a value to the `props.camelCase` counterpart of an observed attribute will trigger an "attribute change" hook. + +For example, assigning a value like so: + +``` +this.props.myName = 'hello' +``` + +...is like calling the following: + +``` +this.setAttribute('my-name','hello'); +``` + +Therefore, this will tell the browser that the UI needs a render if the attribute is one of the component's observed attributes we explicitly provided with `static props`; + +> [!NOTE] +> The `props` property of `WebComponent` works like `HTMLElement.dataset`, except `dataset` is only for attributes prefixed with `data-`. A camelCase counterpart using `props` will give read/write access to any attribute, with or without the `data-` prefix. +> Another advantage over `HTMLElement.dataset` is that `WebComponent.props` can hold primitive types 'number', 'boolean', 'object' and 'string'. + +### Alternatives + +The current alternatives are using what `HTMLElement` provides out-of-the-box, which are: + +1. `HTMLElement.dataset` for attributes prefixed with `data-*`. Read more about this [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset). +1. Methods for reading/writing attribute values: `setAttribute(...)` and `getAttribute(...)`; note that managing the attribute names as strings can be difficult as the code grows. diff --git a/docs/src/content/docs/guides/shadow-dom.md b/docs/src/content/docs/guides/shadow-dom.md new file mode 100644 index 0000000..ff2ae32 --- /dev/null +++ b/docs/src/content/docs/guides/shadow-dom.md @@ -0,0 +1,16 @@ +--- +title: Using the Shadow DOM +slug: shadow-dom +--- + +Add a static property `shadowRootInit` with object value of type `ShadowRootInit` (see [options on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options)) to opt-in to using shadow dom for the whole component. + +Try it now [on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/VwRYVPv?editors=1010) + +Example: + +```js + static shadowRootInit = { + mode: "closed", + }; +``` diff --git a/docs/src/content/docs/guides/styling.md b/docs/src/content/docs/guides/styling.md new file mode 100644 index 0000000..2582c3c --- /dev/null +++ b/docs/src/content/docs/guides/styling.md @@ -0,0 +1,45 @@ +--- +title: Styling +slug: styling +--- + +When using the built-in `html` function for tagged templates, a style object of type `Partial` can be passed to any element's `style` attribute. This allows for calculated and conditional styles. Read more on style objects [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration). + +Try it now with this [example on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzXjwQ?editors=1010) + +```js +import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js' + +class StyledElements extends WebComponent { + static props = { + emphasize: false, + type: 'warn', + } + + #typeStyles = { + warn: { + backgroundColor: 'yellow', + border: '1px solid orange', + }, + error: { + backgroundColor: 'orange', + border: '1px solid red', + }, + } + + get template() { + return html` +
+

Wow!

+
+ ` + } +} + +customElements.define('styled-elements', StyledElements) +``` diff --git a/docs/src/content/docs/guides/template-vs-render.md b/docs/src/content/docs/guides/template-vs-render.md new file mode 100644 index 0000000..e94c445 --- /dev/null +++ b/docs/src/content/docs/guides/template-vs-render.md @@ -0,0 +1,12 @@ +--- +title: template vs render() +slug: template-vs-render +--- + +This mental model attempts to reduce the cognitive complexity of authoring components: + +1. The `template` is a read-only property (initialized with a `get` keyword) that represents _how_ the component view is rendered. +1. There is a `render()` method that triggers a view render. +1. This `render()` method is _automatically_ called under the hood every time an attribute value changed. +1. You can _optionally_ call this `render()` method at any point to trigger a render if you need (eg, if you have private unobserved properties that need to manually trigger a render) +1. Overriding the `render()` function for handling a custom `template` is also possible. Here's an example of using `lit-html`: [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010) diff --git a/docs/src/content/docs/guides/usage.md b/docs/src/content/docs/guides/usage.md new file mode 100644 index 0000000..1fac1f4 --- /dev/null +++ b/docs/src/content/docs/guides/usage.md @@ -0,0 +1,43 @@ +--- +title: Usage +slug: usage +--- + +In your component class: + +```js +// HelloWorld.mjs +import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js' + +class HelloWorld extends WebComponent { + static props = { + myName: 'World', + emotion: 'sad', + } + get template() { + return ` +

Hello ${this.props.myName}${this.props.emotion === 'sad' ? '. 😭' : '! 🙌'}

+ ` + } +} + +customElements.define('hello-world', HelloWorld) +``` + +In your HTML page: + +```html + + + + + + + +``` diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index a42cbd8..b6343bc 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -8,7 +8,7 @@ hero: # file: ../../assets/houston.webp actions: - text: Get Started - link: /guides/ + link: /why icon: right-arrow - text: Play on CodePen link: https://codepen.io/ayoayco-the-styleful/pen/PoVegBK?editors=1010 @@ -25,10 +25,10 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ~1 kB base class (minified, compressed) with versatile utilities - + Sensible life-cycle hooks that you understand and remember - + Use the built-in JSX-like syntax or bring your own custom templating