feat: flex component, document higher-order layout components

This commit is contained in:
Jacky Zhao 2025-03-11 14:56:43 -07:00
parent 87b803790c
commit 2718ab9019
7 changed files with 153 additions and 33 deletions

62
docs/layout-components.md Normal file
View file

@ -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())
```

View file

@ -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. 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 ### Layout breakpoints

View file

@ -25,8 +25,15 @@ export const defaultContentPageLayout: PageLayout = {
left: [ left: [
Component.PageTitle(), Component.PageTitle(),
Component.MobileOnly(Component.Spacer()), Component.MobileOnly(Component.Spacer()),
Component.Search(), Component.Flex({
Component.Darkmode(), components: [
{
Component: Component.Search(),
grow: true,
},
{ Component: Component.Darkmode() },
],
}),
Component.Explorer(), Component.Explorer(),
], ],
right: [ right: [

View file

@ -1,7 +1,6 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default ((component?: QuartzComponent) => { export default ((component: QuartzComponent) => {
if (component) {
const Component = component const Component = component
const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => { const DesktopOnly: QuartzComponent = (props: QuartzComponentProps) => {
return <Component displayClass="desktop-only" {...props} /> return <Component displayClass="desktop-only" {...props} />
@ -12,7 +11,4 @@ export default ((component?: QuartzComponent) => {
DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded DesktopOnly.beforeDOMLoaded = component?.beforeDOMLoaded
DesktopOnly.css = component?.css DesktopOnly.css = component?.css
return DesktopOnly return DesktopOnly
} else { }) satisfies QuartzComponentConstructor<QuartzComponent>
return () => <></>
}
}) satisfies QuartzComponentConstructor

View file

@ -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>

View file

@ -1,7 +1,6 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
export default ((component?: QuartzComponent) => { export default ((component: QuartzComponent) => {
if (component) {
const Component = component const Component = component
const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => { const MobileOnly: QuartzComponent = (props: QuartzComponentProps) => {
return <Component displayClass="mobile-only" {...props} /> return <Component displayClass="mobile-only" {...props} />
@ -12,7 +11,4 @@ export default ((component?: QuartzComponent) => {
MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded MobileOnly.beforeDOMLoaded = component?.beforeDOMLoaded
MobileOnly.css = component?.css MobileOnly.css = component?.css
return MobileOnly return MobileOnly
} else { }) satisfies QuartzComponentConstructor<QuartzComponent>
return () => <></>
}
}) satisfies QuartzComponentConstructor

View file

@ -20,6 +20,7 @@ import MobileOnly from "./MobileOnly"
import RecentNotes from "./RecentNotes" import RecentNotes from "./RecentNotes"
import Breadcrumbs from "./Breadcrumbs" import Breadcrumbs from "./Breadcrumbs"
import Comments from "./Comments" import Comments from "./Comments"
import Flex from "./Flex"
export { export {
ArticleTitle, ArticleTitle,
@ -44,4 +45,5 @@ export {
NotFound, NotFound,
Breadcrumbs, Breadcrumbs,
Comments, Comments,
Flex,
} }