mirror of
https://github.com/jackyzha0/quartz.git
synced 2026-03-21 21:45:42 -05:00
feat: add PageFrame system for custom page layouts
This commit is contained in:
parent
5b06bba764
commit
daec1d9b6a
61
quartz/components/frames/DefaultFrame.tsx
Normal file
61
quartz/components/frames/DefaultFrame.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { PageFrame, PageFrameProps } from "./types"
|
||||||
|
import HeaderConstructor from "../Header"
|
||||||
|
|
||||||
|
const Header = HeaderConstructor()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default page frame — three-column layout with left sidebar, center
|
||||||
|
* content (header + body + afterBody), and right sidebar, followed by a footer.
|
||||||
|
*
|
||||||
|
* This is the original Quartz layout, extracted from renderPage.tsx.
|
||||||
|
*/
|
||||||
|
export const DefaultFrame: PageFrame = {
|
||||||
|
name: "default",
|
||||||
|
render({
|
||||||
|
componentData,
|
||||||
|
header,
|
||||||
|
beforeBody,
|
||||||
|
pageBody: Content,
|
||||||
|
afterBody,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
footer: Footer,
|
||||||
|
}: PageFrameProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="left sidebar">
|
||||||
|
{left.map((BodyComponent) => (
|
||||||
|
<BodyComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
<div class="page-header">
|
||||||
|
<Header {...componentData}>
|
||||||
|
{header.map((HeaderComponent) => (
|
||||||
|
<HeaderComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</Header>
|
||||||
|
<div class="popover-hint">
|
||||||
|
{beforeBody.map((BodyComponent) => (
|
||||||
|
<BodyComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Content {...componentData} />
|
||||||
|
<hr />
|
||||||
|
<div class="page-footer">
|
||||||
|
{afterBody.map((BodyComponent) => (
|
||||||
|
<BodyComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right sidebar">
|
||||||
|
{right.map((BodyComponent) => (
|
||||||
|
<BodyComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Footer {...componentData} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
51
quartz/components/frames/FullWidthFrame.tsx
Normal file
51
quartz/components/frames/FullWidthFrame.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { PageFrame, PageFrameProps } from "./types"
|
||||||
|
import HeaderConstructor from "../Header"
|
||||||
|
|
||||||
|
const Header = HeaderConstructor()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full-width page frame — no sidebars. The center content area spans the
|
||||||
|
* full width of the page. Header, beforeBody, body, afterBody, and footer
|
||||||
|
* are all rendered in a single column.
|
||||||
|
*
|
||||||
|
* Useful for page types like Canvas, presentations, or dashboards that
|
||||||
|
* need maximum horizontal space.
|
||||||
|
*/
|
||||||
|
export const FullWidthFrame: PageFrame = {
|
||||||
|
name: "full-width",
|
||||||
|
render({
|
||||||
|
componentData,
|
||||||
|
header,
|
||||||
|
beforeBody,
|
||||||
|
pageBody: Content,
|
||||||
|
afterBody,
|
||||||
|
footer: Footer,
|
||||||
|
}: PageFrameProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="center full-width">
|
||||||
|
<div class="page-header">
|
||||||
|
<Header {...componentData}>
|
||||||
|
{header.map((HeaderComponent) => (
|
||||||
|
<HeaderComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</Header>
|
||||||
|
<div class="popover-hint">
|
||||||
|
{beforeBody.map((BodyComponent) => (
|
||||||
|
<BodyComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Content {...componentData} />
|
||||||
|
<hr />
|
||||||
|
<div class="page-footer">
|
||||||
|
{afterBody.map((BodyComponent) => (
|
||||||
|
<BodyComponent {...componentData} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer {...componentData} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
23
quartz/components/frames/MinimalFrame.tsx
Normal file
23
quartz/components/frames/MinimalFrame.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { PageFrame, PageFrameProps } from "./types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal page frame — no sidebars, no header/footer chrome. Only the
|
||||||
|
* page body is rendered with a thin wrapper, plus the footer for legal/link
|
||||||
|
* obligations.
|
||||||
|
*
|
||||||
|
* Useful for immersive page types like full-screen canvases, kiosks,
|
||||||
|
* or custom landing pages that want complete control of the viewport.
|
||||||
|
*/
|
||||||
|
export const MinimalFrame: PageFrame = {
|
||||||
|
name: "minimal",
|
||||||
|
render({ componentData, pageBody: Content, footer: Footer }: PageFrameProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div class="center minimal">
|
||||||
|
<Content {...componentData} />
|
||||||
|
</div>
|
||||||
|
<Footer {...componentData} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
40
quartz/components/frames/index.ts
Normal file
40
quartz/components/frames/index.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { PageFrame } from "./types"
|
||||||
|
import { DefaultFrame } from "./DefaultFrame"
|
||||||
|
import { FullWidthFrame } from "./FullWidthFrame"
|
||||||
|
import { MinimalFrame } from "./MinimalFrame"
|
||||||
|
|
||||||
|
export type { PageFrame, PageFrameProps } from "./types"
|
||||||
|
export { DefaultFrame } from "./DefaultFrame"
|
||||||
|
export { FullWidthFrame } from "./FullWidthFrame"
|
||||||
|
export { MinimalFrame } from "./MinimalFrame"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry of built-in page frames. Page types can reference these by name
|
||||||
|
* via their `frame` property, and YAML config can override via
|
||||||
|
* `layout.byPageType.<name>.template`.
|
||||||
|
*
|
||||||
|
* The "default" frame reproduces the original three-column Quartz layout.
|
||||||
|
*/
|
||||||
|
const builtinFrames: Record<string, PageFrame> = {
|
||||||
|
default: DefaultFrame,
|
||||||
|
"full-width": FullWidthFrame,
|
||||||
|
minimal: MinimalFrame,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a frame by name. Returns the DefaultFrame if the name is not found,
|
||||||
|
* logging a warning for unknown frame names.
|
||||||
|
*/
|
||||||
|
export function resolveFrame(name: string | undefined): PageFrame {
|
||||||
|
if (!name || name === "default") {
|
||||||
|
return DefaultFrame
|
||||||
|
}
|
||||||
|
const frame = builtinFrames[name]
|
||||||
|
if (!frame) {
|
||||||
|
console.warn(
|
||||||
|
`Unknown page frame "${name}", falling back to "default". Available frames: ${Object.keys(builtinFrames).join(", ")}`,
|
||||||
|
)
|
||||||
|
return DefaultFrame
|
||||||
|
}
|
||||||
|
return frame
|
||||||
|
}
|
||||||
43
quartz/components/frames/types.ts
Normal file
43
quartz/components/frames/types.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { JSX } from "preact"
|
||||||
|
import { QuartzComponent, QuartzComponentProps } from "../types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props passed to a PageFrame's render function.
|
||||||
|
* Contains the resolved layout components and the shared component data.
|
||||||
|
*/
|
||||||
|
export interface PageFrameProps {
|
||||||
|
/** Component data shared across all components on the page */
|
||||||
|
componentData: QuartzComponentProps
|
||||||
|
/** The Head component (rendered in <head>) — NOT used by frames, included for completeness */
|
||||||
|
head: QuartzComponent
|
||||||
|
/** Header slot components (rendered inside <header>) */
|
||||||
|
header: QuartzComponent[]
|
||||||
|
/** Components rendered before the page body */
|
||||||
|
beforeBody: QuartzComponent[]
|
||||||
|
/** The page body component (Content) */
|
||||||
|
pageBody: QuartzComponent
|
||||||
|
/** Components rendered after the page body */
|
||||||
|
afterBody: QuartzComponent[]
|
||||||
|
/** Left sidebar components */
|
||||||
|
left: QuartzComponent[]
|
||||||
|
/** Right sidebar components */
|
||||||
|
right: QuartzComponent[]
|
||||||
|
/** Footer component */
|
||||||
|
footer: QuartzComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PageFrame defines the inner HTML structure of a page inside the
|
||||||
|
* `<div id="quartz-root">` shell. Different frames can produce completely
|
||||||
|
* different layouts (e.g. with/without sidebars, horizontal scroll, etc.)
|
||||||
|
* while the outer shell (html, head, body, quartz-root) remains stable
|
||||||
|
* for SPA navigation.
|
||||||
|
*/
|
||||||
|
export interface PageFrame {
|
||||||
|
/** Unique name for this frame (e.g. "default", "full-width", "minimal") */
|
||||||
|
name: string
|
||||||
|
/** Render the inner page structure. Returns a JSX tree to be placed inside Body > #quartz-body. */
|
||||||
|
render: (props: PageFrameProps) => JSX.Element
|
||||||
|
/** Optional CSS string to include when this frame is active */
|
||||||
|
css?: string
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user