The Trans Directory
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 248 lines 10 kB view raw view rendered
1--- 2title: Creating your own Quartz components 3--- 4 5> [!warning] 6> This guide assumes you have experience writing JavaScript and are familiar with TypeScript. 7 8Normally on the web, we write layout code using HTML which looks something like the following: 9 10```html 11<article> 12 <h1>An article header</h1> 13 <p>Some content</p> 14</article> 15``` 16 17This 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 combined with CSS to style the page and JavaScript to add interactivity. 18 19However, 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 complaints and invented the concept of Components -- JavaScript functions that return JSX -- to solve the code duplication problem. 20 21In 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.** 22 23## An Example Component 24 25### Constructor 26 27Component 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. 28 29Each component file should have a default export that satisfies the `QuartzComponentConstructor` function signature. It's a function that takes in a single optional parameter `opts` and returns a Quartz Component. The type of the parameters `opts` is defined by the interface `Options` which you as the component creator also decide. 30 31In 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. 32 33```tsx {11-17} 34interface Options { 35 favouriteNumber: number 36} 37 38const defaultOptions: Options = { 39 favouriteNumber: 42, 40} 41 42export default ((userOpts?: Options) => { 43 const opts = { ...userOpts, ...defaultOpts } 44 function YourComponent(props: QuartzComponentProps) { 45 if (opts.favouriteNumber < 0) { 46 return null 47 } 48 49 return <p>My favourite number is {opts.favouriteNumber}</p> 50 } 51 52 return YourComponent 53}) satisfies QuartzComponentConstructor 54``` 55 56### Props 57 58The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called [props](https://react.dev/learn/passing-props-to-a-component)) and returns JSX. 59 60All Quartz components accept the same set of props: 61 62```tsx title="quartz/components/types.ts" 63// simplified for sake of demonstration 64export type QuartzComponentProps = { 65 fileData: QuartzPluginData 66 cfg: GlobalConfiguration 67 tree: Node<QuartzPluginData> 68 allFiles: QuartzPluginData[] 69 displayClass?: "mobile-only" | "desktop-only" 70} 71``` 72 73- `fileData`: Any metadata [[making plugins|plugins]] may have added to the current page. 74 - `fileData.slug`: slug of the current page. 75 - `fileData.frontmatter`: any frontmatter parsed. 76- `cfg`: The `configuration` field in `quartz.config.ts`. 77- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file. This is useful if you'd like to render the content using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) (you can find an example of this in `quartz/components/pages/Content.tsx`). 78- `allFiles`: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure. 79- `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. 80 81### Styling 82 83Quartz 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. 84 85Note that inlined styles **must** be plain vanilla CSS: 86 87```tsx {6-10} title="quartz/components/YourComponent.tsx" 88export default (() => { 89 function YourComponent() { 90 return <p class="red-text">Example Component</p> 91 } 92 93 YourComponent.css = ` 94 p.red-text { 95 color: red; 96 } 97 ` 98 99 return YourComponent 100}) satisfies QuartzComponentConstructor 101``` 102 103Imported styles, however, can be from SCSS files: 104 105```tsx {1-2,9} title="quartz/components/YourComponent.tsx" 106// assuming your stylesheet is in quartz/components/styles/YourComponent.scss 107import styles from "./styles/YourComponent.scss" 108 109export default (() => { 110 function YourComponent() { 111 return <p>Example Component</p> 112 } 113 114 YourComponent.css = styles 115 return YourComponent 116}) satisfies QuartzComponentConstructor 117``` 118 119> [!warning] 120> 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. 121 122### Scripts and Interactivity 123 124What about interactivity? Suppose you want to add an-click handler for example. Like the `.css` property on the component, you can also declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties that are strings that contain the script. 125 126```tsx title="quartz/components/YourComponent.tsx" 127export default (() => { 128 function YourComponent() { 129 return <button id="btn">Click me</button> 130 } 131 132 YourComponent.beforeDOMLoaded = ` 133 console.log("hello from before the page loads!") 134 ` 135 136 YourComponent.afterDOMLoaded = ` 137 document.getElementById('btn').onclick = () => { 138 alert('button clicked!') 139 } 140 ` 141 return YourComponent 142}) satisfies QuartzComponentConstructor 143``` 144 145> [!hint] 146> 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 and other properties that accept functions like `onClick` handlers will not work. Instead, do it using a regular JS script that modifies the DOM element directly. 147 148As the names suggest, the `.beforeDOMLoaded` scripts are executed _before_ the page is done loading so it doesn't have access to any elements on the page. This is mostly used to prefetch any critical data. 149 150The `.afterDOMLoaded` script executes once the page has been completely loaded. This is a good place to setup anything that should last for the duration of a site visit (e.g. getting something saved from local storage). 151 152If you need to create an `afterDOMLoaded` script that depends on _page specific_ elements that may change when navigating to a new page, you can listen for the `"nav"` event that gets fired whenever a page loads (which may happen on navigation if [[SPA Routing]] is enabled). 153 154```ts 155document.addEventListener("nav", () => { 156 // do page specific logic here 157 // e.g. attach event listeners 158 const toggleSwitch = document.querySelector("#switch") as HTMLInputElement 159 toggleSwitch.addEventListener("change", switchTheme) 160 window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme)) 161}) 162``` 163 164You can also add the equivalent of a `beforeunload` event for [[SPA Routing]] via the `prenav` event. 165 166```ts 167document.addEventListener("prenav", () => { 168 // executed after an SPA navigation is triggered but 169 // before the page is replaced 170 // one usage pattern is to store things in sessionStorage 171 // in the prenav and then conditionally load then in the consequent 172 // nav 173}) 174``` 175 176It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks. 177This will get called on page navigation. 178 179#### Importing Code 180 181Of course, it isn't always practical (nor desired!) to write your code as a string literal in the component. 182 183Quartz supports importing component code through `.inline.ts` files. 184 185```tsx title="quartz/components/YourComponent.tsx" 186// @ts-ignore: typescript doesn't know about our inline bundling system 187// so we need to silence the error 188import script from "./scripts/graph.inline" 189 190export default (() => { 191 function YourComponent() { 192 return <button id="btn">Click me</button> 193 } 194 195 YourComponent.afterDOMLoaded = script 196 return YourComponent 197}) satisfies QuartzComponentConstructor 198``` 199 200```ts title="quartz/components/scripts/graph.inline.ts" 201// any imports here are bundled for the browser 202import * as d3 from "d3" 203 204document.getElementById("btn").onclick = () => { 205 alert("button clicked!") 206} 207``` 208 209Additionally, like what is shown in the example above, you can import packages in `.inline.ts` files. This will be bundled by Quartz and included in the actual script. 210 211### Using a Component 212 213After creating your custom component, re-export it in `quartz/components/index.ts`: 214 215```ts title="quartz/components/index.ts" {4,10} 216import ArticleTitle from "./ArticleTitle" 217import Content from "./pages/Content" 218import Darkmode from "./Darkmode" 219import YourComponent from "./YourComponent" 220 221export { ArticleTitle, Content, Darkmode, YourComponent } 222``` 223 224Then, you can use it like any other component in `quartz.layout.ts` via `Component.YourComponent()`. See the [[configuration#Layout|layout]] section for more details. 225 226As Quartz components are just functions that return React components, you can compositionally use them in other Quartz components. 227 228```tsx title="quartz/components/AnotherComponent.tsx" 229import YourComponentConstructor from "./YourComponent" 230 231export default (() => { 232 const YourComponent = YourComponentConstructor() 233 234 function AnotherComponent(props: QuartzComponentProps) { 235 return ( 236 <div> 237 <p>It's nested!</p> 238 <YourComponent {...props} /> 239 </div> 240 ) 241 } 242 243 return AnotherComponent 244}) satisfies QuartzComponentConstructor 245``` 246 247> [!hint] 248> Look in `quartz/components` for more examples of components in Quartz as reference for your own components!