From 2718ab90194b3c7be600d1a2dadc4250207db1c0 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Tue, 11 Mar 2025 14:56:43 -0700 Subject: [PATCH] feat: flex component, document higher-order layout components --- docs/layout-components.md | 62 +++++++++++++++++++++++++++++++ docs/layout.md | 4 +- quartz.layout.ts | 11 +++++- quartz/components/DesktopOnly.tsx | 26 ++++++------- quartz/components/Flex.tsx | 55 +++++++++++++++++++++++++++ quartz/components/MobileOnly.tsx | 26 ++++++------- quartz/components/index.ts | 2 + 7 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 docs/layout-components.md create mode 100644 quartz/components/Flex.tsx 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 @@ These correspond to following parts of the page: 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 @@ export const defaultContentPageLayout: PageLayout = { 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 - } - - 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 } -}) satisfies QuartzComponentConstructor + + DesktopOnly.displayName = component.displayName + DesktopOnly.afterDOMLoaded = component?.afterDOMLoaded + DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded + DesktopOnly.css = component?.css + return DesktopOnly +}) satisfies QuartzComponentConstructor 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 ( +
+ {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 ( +
+ +
+ ) + })} +
+ ) + } + + 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 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 - } - - 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 } -}) satisfies QuartzComponentConstructor + + MobileOnly.displayName = component.displayName + MobileOnly.afterDOMLoaded = component?.afterDOMLoaded + MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded + MobileOnly.css = component?.css + return MobileOnly +}) satisfies QuartzComponentConstructor 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 MobileOnly from "./MobileOnly" import RecentNotes from "./RecentNotes" import Breadcrumbs from "./Breadcrumbs" import Comments from "./Comments" +import Flex from "./Flex" export { ArticleTitle, @@ -44,4 +45,5 @@ export { NotFound, Breadcrumbs, Comments, + Flex, }