quartz-research-note/content/advanced/creating components.md
2023-08-07 23:57:24 -07:00

5.7 KiB

title
Creating your own Quartz components

Warning

This guide assumes you have experience writing JavaScript and are familiar with TypeScript.

Normally on the web, we write layout code using HTML which looks something like the following:

<article>
  <h1>An article header</h1>
  <p>Some content</p>
</article>

This piece of HTML represents an article with a leading header that says "An article header" and a paragraph that contains the text "Some content". This is normally combined with CSS to style the page and JavaScript to add interactivity.

However, HTML doesn't let you create reusable templates. If you wanted to create a new page, you would need to copy and paste the above snippet and edit the header and content yourself. This isn't great if we have a lot of content on our site that shares a lot of similar layout. The smart people who created React also had similar thoughts, inventing the concept of JSX Components to solve the code duplication problem.

In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.

[!hint] For those coming from React, Quartz components are different from React components in that it only uses JSX for templating and layout. Hooks like useEffect, useState, etc. are not rendered.

An Example Component

Constructor

Component files are written in .tsx files that live in the quartz/components folder. These are re-exported in quartz/components/index.ts so you can use them in layouts and other components more easily.

Each component file should have a default export that satisfies the QuartzComponentConstructor function signature. It is a function that takes in a single optional parameter opts and returns a Quartz Component. The type of the parameters ops is defined by the interface Options which you as the component creator also decide.

In your component, you can use the values from the configuration option to change the rendering behaviour inside of your component. For example, the component in the code snippet below will not render if the favouriteNumber option is below 0.

interface Options {
  favouriteNumber: number
}

const defaultOptions: Options = {
  favouriteNumber: 42,
}

export default ((userOpts?: Options) => {
  const opts = { ...userOpts, ...defaultOpts }
  function YourComponent(props: QuartzComponentProps) {
    if (opts.favouriteNumber < 0) {
      return null
    }

    return <p>My favourite number is {opts.favouriteNumber}</p>
  }

  return YourComponent
}) satisfies QuartzComponentConstructor

Props

The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called props) and returns JSX.

All Quartz components accept the same set of props which are defined in QuartzComponentProps:

// simplified for sake of demonstration
export type QuartzComponentProps = {
  fileData: QuartzPluginData
  cfg: GlobalConfiguration
  tree: Node<QuartzPluginData>
  allFiles: QuartzPluginData[]
  displayClass?: "mobile-only" | "desktop-only"
}
  • fileData: Any metadata making plugins may have added to the current page.
    • fileData.slug: slug of the current page.
    • fileData.frontmatter: any frontmatter parsed.
  • cfg: The configuration field in quartz.config.ts.
  • tree: the resulting HTML AST after processing and transforming the file. This is useful if you'd like to render the content using hast-util-to-jsx-runtime (you can find an example of this in quartz/components/pages/Content.tsx).
  • allFiles: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure.
  • displayClass: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting. Helpful if you want to conditionally hide a component on mobile or desktop.

Styling

Quartz components can also define a .css property on the actual function component which will get picked up by Quartz. This is expected to be a CSS string which can either be inlined or imported from a .scss file.

Note that inlined styles must be plain vanilla CSS.

export default (() => {
  function YourComponent() {
    return <p>Example Component</p>
  }

  YourComponent.css = `
  p {
    color: red;
  }
  `

  return YourComponent
}) satisfies QuartzComponentConstructor

Imported styles, however, can be from SCSS files.

// assuming your stylesheet is in quartz/components/styles/YourComponent.scss
import styles from "./styles/YourComponent.scss"

export default (() => {
  function YourComponent() {
    return <p>Example Component</p>
  }

  YourComponent.css = styles
  return YourComponent
}) satisfies QuartzComponentConstructor

Warning

Quartz does not use CSS modules so any styles you declare here apply globally. If you only want it to apply to your component, make sure you use specific class names and selectors.

Scripts and Interactivity

  • listening for the nav event
    • best practice: anything here should unmount any existing event handlers to prevent memory leaks

Using a Component

In a layout

In the configuration

[!hint] Look in quartz/components for more examples of components in Quartz as reference for your own components!