From 2718ab90194b3c7be600d1a2dadc4250207db1c0 Mon Sep 17 00:00:00 2001
From: Jacky Zhao <j.zhao2k19@gmail.com>
Date: Tue, 11 Mar 2025 21:56:43 +0000
Subject: [PATCH] feat: flex component, document higher-order layout components
---
quartz/components/MobileOnly.tsx | 26 ++---
quartz/components/index.ts | 2
quartz/components/Flex.tsx | 55 +++++++++++++
docs/layout.md | 4
docs/layout-components.md | 62 +++++++++++++++
quartz/components/DesktopOnly.tsx | 26 ++---
quartz.layout.ts | 11 ++
7 files changed, 153 insertions(+), 33 deletions(-)
diff --git a/docs/layout-components.md b/docs/layout-components.md
new file mode 100644
index 0000000..0c148a3
--- /dev/null
+++ b/docs/layout-components.md
@@ -0,0 +1,62 @@
+---
+title: Higher-Order Layout Components
+---
+
+Quartz provides several higher-order components that help with layout composition and responsive design. These components wrap other components to add additional functionality or modify their behavior.
+
+## `Flex` Component
+
+The `Flex` component creates a [flexible box layout](https://developer.mozilla.org/en-US/docs/Web/CSS/flex) that can arrange child components in various ways. It's particularly useful for creating responsive layouts and organizing components in rows or columns.
+
+```typescript
+type FlexConfig = {
+ components: {
+ Component: QuartzComponent
+ grow?: boolean // whether component should grow to fill space
+ shrink?: boolean // whether component should shrink if needed
+ basis?: string // initial main size of the component
+ order?: number // order in flex container
+ align?: "start" | "end" | "center" | "stretch" // cross-axis alignment
+ justify?: "start" | "end" | "center" | "between" | "around" // main-axis alignment
+ }[]
+ direction?: "row" | "row-reverse" | "column" | "column-reverse"
+ wrap?: "nowrap" | "wrap" | "wrap-reverse"
+ gap?: string
+}
+```
+
+### Example Usage
+
+```typescript
+Component.Flex({
+ components: [
+ {
+ Component: Component.Search(),
+ grow: true, // Search will grow to fill available space
+ },
+ { Component: Component.Darkmode() }, // Darkmode keeps its natural size
+ ],
+ direction: "row",
+ gap: "1rem",
+})
+```
+
+## `MobileOnly` Component
+
+The `MobileOnly` component is a wrapper that makes its child component only visible on mobile devices. This is useful for creating responsive layouts where certain components should only appear on smaller screens.
+
+### Example Usage
+
+```typescript
+Component.MobileOnly(Component.Spacer())
+```
+
+## `DesktopOnly` Component
+
+The `DesktopOnly` component is the counterpart to `MobileOnly`. It makes its child component only visible on desktop devices. This helps create responsive layouts where certain components should only appear on larger screens.
+
+### Example Usage
+
+```typescript
+Component.DesktopOnly(Component.TableOfContents())
+```
diff --git a/docs/layout.md b/docs/layout.md
index d8427c4..3f73753 100644
--- a/docs/layout.md
+++ b/docs/layout.md
@@ -35,7 +35,9 @@
Quartz **components**, like plugins, can take in additional properties as configuration options. If you're familiar with React terminology, you can think of them as Higher-order Components.
-See [a list of all the components](component.md) for all available components along with their configuration options. You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
+See [a list of all the components](component.md) for all available components along with their configuration options. Additionally, Quartz provides several built-in higher-order components for layout composition - see [[layout-components]] for more details.
+
+You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
### Layout breakpoints
diff --git a/quartz.layout.ts b/quartz.layout.ts
index f45da0c..3a39d0b 100644
--- a/quartz.layout.ts
+++ b/quartz.layout.ts
@@ -25,8 +25,15 @@
left: [
Component.PageTitle(),
Component.MobileOnly(Component.Spacer()),
- Component.Search(),
- Component.Darkmode(),
+ Component.Flex({
+ components: [
+ {
+ Component: Component.Search(),
+ grow: true,
+ },
+ { Component: Component.Darkmode() },
+ ],
+ }),
Component.Explorer(),
],
right: [
diff --git a/quartz/components/DesktopOnly.tsx b/quartz/components/DesktopOnly.tsx
index fe2a27f..ee80137 100644
--- a/quartz/components/DesktopOnly.tsx
+++ b/quartz/components/DesktopOnly.tsx
@@ -1,18 +1,14 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
-export default ((component?: QuartzComponent) => {
- if (component) {
- const Component = component
- const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => {
- return <Component displayClass="desktop-only" {...props} />
- }
-
- DesktopOnly.displayName = component.displayName
- DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded
- DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded
- DesktopOnly.css = component?.css
- return DesktopOnly
- } else {
- return () => <></>
+export default ((component: QuartzComponent) => {
+ const Component = component
+ const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => {
+ return <Component displayClass="desktop-only" {...props} />
}
-}) satisfies QuartzComponentConstructor
+
+ DesktopOnly.displayName = component.displayName
+ DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded
+ DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded
+ DesktopOnly.css = component?.css
+ return DesktopOnly
+}) satisfies QuartzComponentConstructor<QuartzComponent>
diff --git a/quartz/components/Flex.tsx b/quartz/components/Flex.tsx
new file mode 100644
index 0000000..1cf151e
--- /dev/null
+++ b/quartz/components/Flex.tsx
@@ -0,0 +1,55 @@
+import { concatenateResources } from "../util/resources"
+import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
+
+type FlexConfig = {
+ components: {
+ Component: QuartzComponent
+ grow?: boolean
+ shrink?: boolean
+ basis?: string
+ order?: number
+ align?: "start" | "end" | "center" | "stretch"
+ justify?: "start" | "end" | "center" | "between" | "around"
+ }[]
+ direction?: "row" | "row-reverse" | "column" | "column-reverse"
+ wrap?: "nowrap" | "wrap" | "wrap-reverse"
+ gap?: string
+}
+
+export default ((config: FlexConfig) => {
+ const Flex: QuartzComponent = (props: QuartzComponentProps) => {
+ const direction = config.direction ?? "row"
+ const wrap = config.wrap ?? "nowrap"
+ const gap = config.gap ?? "1rem"
+
+ return (
+ <div style={`display: flex; flex-direction: ${direction}; flex-wrap: ${wrap}; gap: ${gap};`}>
+ {config.components.map((c) => {
+ const grow = c.grow ? 1 : 0
+ const shrink = (c.shrink ?? true) ? 1 : 0
+ const basis = c.basis ?? "auto"
+ const order = c.order ?? 0
+ const align = c.align ?? "center"
+ const justify = c.justify ?? "center"
+
+ return (
+ <div
+ style={`flex-grow: ${grow}; flex-shrink: ${shrink}; flex-basis: ${basis}; order: ${order}; align-self: ${align}; justify-self: ${justify};`}
+ >
+ <c.Component {...props} />
+ </div>
+ )
+ })}
+ </div>
+ )
+ }
+
+ Flex.afterDOMLoaded = concatenateResources(
+ ...config.components.map((c) => c.Component.afterDOMLoaded),
+ )
+ Flex.beforeDOMLoaded = concatenateResources(
+ ...config.components.map((c) => c.Component.beforeDOMLoaded),
+ )
+ Flex.css = concatenateResources(...config.components.map((c) => c.Component.css))
+ return Flex
+}) satisfies QuartzComponentConstructor<FlexConfig>
diff --git a/quartz/components/MobileOnly.tsx b/quartz/components/MobileOnly.tsx
index 7d2108d..29958cf 100644
--- a/quartz/components/MobileOnly.tsx
+++ b/quartz/components/MobileOnly.tsx
@@ -1,18 +1,14 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
-export default ((component?: QuartzComponent) => {
- if (component) {
- const Component = component
- const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => {
- return <Component displayClass="mobile-only" {...props} />
- }
-
- MobileOnly.displayName = component.displayName
- MobileOnly.afterDOMLoaded = component?.afterDOMLoaded
- MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded
- MobileOnly.css = component?.css
- return MobileOnly
- } else {
- return () => <></>
+export default ((component: QuartzComponent) => {
+ const Component = component
+ const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => {
+ return <Component displayClass="mobile-only" {...props} />
}
-}) satisfies QuartzComponentConstructor
+
+ MobileOnly.displayName = component.displayName
+ MobileOnly.afterDOMLoaded = component?.afterDOMLoaded
+ MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded
+ MobileOnly.css = component?.css
+ return MobileOnly
+}) satisfies QuartzComponentConstructor<QuartzComponent>
diff --git a/quartz/components/index.ts b/quartz/components/index.ts
index 5b19794..49a3cb6 100644
--- a/quartz/components/index.ts
+++ b/quartz/components/index.ts
@@ -20,6 +20,7 @@
import RecentNotes from "./RecentNotes"
import Breadcrumbs from "./Breadcrumbs"
import Comments from "./Comments"
+import Flex from "./Flex"
export {
ArticleTitle,
@@ -44,4 +45,5 @@
NotFound,
Breadcrumbs,
Comments,
+ Flex,
}
--
Gitblit v1.10.0