Skip to content

Commit

Permalink
Allow overwriting the a (link) component
Browse files Browse the repository at this point in the history
  • Loading branch information
Xennis committed May 30, 2024
1 parent 326a0ab commit f729c40
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 101 deletions.
35 changes: 35 additions & 0 deletions examples/nextjs/src/app/custom/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Render } from "@react-notion-cms/render"
import { getCachedPageContent } from "@/lib/fetchers"

const demoLocale = "en_US"

const formatDateFn = (date: Date) => {
return date.toLocaleDateString(demoLocale, {
month: "short",
day: "numeric",
year: "numeric",
})
}

export default async function CustomPage() {
const blocks = await getCachedPageContent(process.env.NOTION_BLOCK_ID!)

return (
<Render
blocks={blocks}
options={{
formatDateFn: formatDateFn,
resolveLinkFn: (nId) => ({ href: nId, icon: null }),
htmlComponents: {
a: (props: React.ComponentPropsWithoutRef<"a">) => {
return (
<a className="underline decoration-teal-500 decoration-2 underline-offset-1" {...props}>
{props.children}
</a>
)
},
},
}}
/>
)
}
16 changes: 11 additions & 5 deletions examples/nextjs/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Inter } from "next/font/google"
import "./globals.css"
import { classNames } from "@react-notion-cms/render/dist/util"
import Link from "next/link"

import { ToggleDarkModeButton, ToggleRtlDirectionButton } from "@/components/toggle-button"

import "@react-notion-cms/render/src/thirdparty/react-notion-x/styles.css"
import "./globals.css"

const inter = Inter({ subsets: ["latin"] })

export default function RootLayout({
Expand All @@ -12,21 +15,24 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={classNames(inter.className, "dark:bg-slate-800")} suppressHydrationWarning>
<body className={[inter.className, "dark:bg-slate-800"].join(" ")} suppressHydrationWarning>
<header className="w-full bg-teal-500">
<div className="mx-auto flex max-w-screen-lg justify-between px-3 py-2">
<div className="flex gap-4">
<ToggleDarkModeButton />
<ToggleRtlDirectionButton />
</div>
<nav>
<nav className="flex gap-4">
<Link className="hover:underline" href="/custom">
Custom
</Link>
<a className="hover:underline" href="https://github.com/Xennis/react-notion-render" target="_blank">
GitHub
</a>
</nav>
</div>
</header>
<main>{children}</main>
<main className="mx-auto max-w-screen-lg px-3">{children}</main>
</body>
</html>
)
Expand Down
27 changes: 8 additions & 19 deletions examples/nextjs/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import { Render } from "@react-notion-cms/render"
import { getCachedPageContent } from "@/lib/fetchers"

import "@react-notion-cms/render/src/thirdparty/react-notion-x/styles.css"

const demoLocale = "en_US"

const formatDateFn = (date: Date) => {
return date.toLocaleDateString(demoLocale, {
month: "short",
day: "numeric",
year: "numeric",
})
}

export default async function Home() {
export default async function HomePage() {
const blocks = await getCachedPageContent(process.env.NOTION_BLOCK_ID!)

return (
<main className="mx-auto max-w-screen-lg px-3">
<Render
blocks={blocks}
options={{ formatDateFn: formatDateFn, resolveLinkFn: (nId) => ({ href: nId, icon: null }) }}
/>
</main>
<Render
blocks={blocks}
options={{
resolveLinkFn: (nId) => ({ href: nId, icon: null }),
}}
/>
)
}
11 changes: 2 additions & 9 deletions examples/nextjs/src/components/toggle-button.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
"use client"

import { useState } from "react"
import { classNames } from "@react-notion-cms/render/dist/util"

const Button = ({ className, ...props }: React.ComponentPropsWithoutRef<"button">) => {
const Button = ({ ...props }: React.ComponentPropsWithoutRef<"button">) => {
return (
<button
className={classNames(
"rounded bg-black px-2 py-1 text-sm font-semibold text-white hover:bg-gray-800",
className ?? "",
)}
{...props}
>
<button className="rounded bg-black px-2 py-1 text-sm font-semibold text-white hover:bg-gray-800" {...props}>
{props.children}
</button>
)
Expand Down
38 changes: 26 additions & 12 deletions packages/render/src/block.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import { type JSX } from "react"

import { RichTexts } from "./components/text"
import { classNames, notionColor } from "./util"
import type { BlockObjectResponseWithChildren, RichTextOptions } from "./types"
import { cn, notionColor } from "./util"
import { BlockObjectResponseWithChildren, IconResponse, RenderOptions } from "./types"
import { Heading } from "./components/heading"
import { Icon } from "./components/icon"
import { Checkbox } from "./components/checkbox"
import { Link, PageTitle } from "./components/link"
import { A } from "./components/html/a"
import { Toggle } from "./components/toggle"
import { PageTitle } from "./components/page-title"

const defaultFormatDateFn = (date: Date) => date.toString()

export const Render = ({
blocks,
options,
}: {
blocks: Array<BlockObjectResponseWithChildren>
options: RichTextOptions
options: {
formatDateFn?: (date: Date) => string
resolveLinkFn: (nId: string) => { href: string; icon: IconResponse | null } | null
htmlComponents?: {
a?: (props: React.ComponentPropsWithoutRef<"a">) => JSX.Element
}
}
}) => {
const opts = {
...options,
formatDateFn: options.formatDateFn ?? defaultFormatDateFn,
htmlComponents: {
a: options.htmlComponents?.a ?? A,
},
}
return (
<div className="text-base leading-normal text-[color:var(--fg-color)] caret-[color:var(--fg-color)]">
<RenderBlocks blocks={blocks} options={options} />
<RenderBlocks blocks={blocks} options={opts} />
</div>
)
}
Expand All @@ -28,7 +44,7 @@ const RenderBlocks = ({
options,
}: {
blocks: Array<BlockObjectResponseWithChildren>
options: RichTextOptions
options: RenderOptions
}) => {
const nextBlocksOfSameType = (startIndex: number, type: string) => {
const result: Array<BlockObjectResponseWithChildren> = []
Expand Down Expand Up @@ -89,7 +105,7 @@ const RenderBlocks = ({
return <>{elements}</>
}

const Block = ({ block, options }: { block: BlockObjectResponseWithChildren; options: RichTextOptions }) => {
const Block = ({ block, options }: { block: BlockObjectResponseWithChildren; options: RenderOptions }) => {
switch (block.type) {
case "audio":
break
Expand Down Expand Up @@ -168,9 +184,9 @@ const Block = ({ block, options }: { block: BlockObjectResponseWithChildren; opt
}
return (
<p>
<Link {...childPageResolved}>
<options.htmlComponents.a href={childPageResolved?.href ?? "#"}>
<PageTitle icon={childPageResolved.icon}>{block.child_page.title}</PageTitle>
</Link>
</options.htmlComponents.a>
</p>
)
case "code":
Expand Down Expand Up @@ -389,9 +405,7 @@ const Block = ({ block, options }: { block: BlockObjectResponseWithChildren; opt
<div className="flex min-h-[calc(1.5em_+_3px_+_3px)] w-full items-center ps-0.5">
<Checkbox checked={isChecked} />
{/* ref: .notion-to-do-body, if isChecked: notion-to-do-checked */}
<div
className={classNames("whitespace-pre-wrap break-words", isChecked ? `line-through opacity-[0.375]` : "")}
>
<div className={cn("whitespace-pre-wrap break-words", isChecked ? `line-through opacity-[0.375]` : "")}>
<RichTexts value={block.to_do.rich_text} options={options} />
</div>
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/render/src/components/heading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { type Heading1BlockObjectResponse, type RichTextItemResponse } from "@no
import { LinkIcon } from "../thirdparty/heroicons/link-icon"
import { Fragment, type ReactNode } from "react"

import { classNames, notionColor } from "../util"
import { cn, notionColor } from "../util"
import { RichTexts } from "./text"
import { RichTextOptions } from "../types"
import { RenderOptions } from "../types"
import { Toggle } from "./toggle"

export const Heading = ({
Expand All @@ -19,7 +19,7 @@ export const Heading = ({
rich_text: Array<RichTextItemResponse>
color: Heading1BlockObjectResponse["heading_1"]["color"]
is_toggleable: boolean
options: RichTextOptions
options: RenderOptions
children: ReactNode
}) => {
const id = idFromRichTexts(rich_text)
Expand All @@ -31,7 +31,7 @@ export const Heading = ({
marginTop: as === "h1" ? "1.08em" : as === "h2" ? "1.1em" : "1em",
...notionColor(color),
},
className: classNames(
className: cn(
"relative inline-block font-semibold leading-[1.3] max-w-full whitespace-pre-wrap mb-px px-0.5 py-[3px] break-words group",
as === "h1" ? "text-[1.875em]" : as === "h2" ? "text-[1.5em]" : "text-[1.25em]",
),
Expand Down
17 changes: 17 additions & 0 deletions packages/render/src/components/html/a.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { cn } from "../../util"

export const A = ({ className, ...props }: React.ComponentPropsWithoutRef<"a">) => {
return (
// ref: .notion-link
<a
style={{ transition: "border-color 100ms ease-in,opacity 100ms ease-in" }}
className={cn(
"break-words text-inherit underline decoration-[--fg-color-2] opacity-70 hover:decoration-[--fg-color-6] hover:opacity-100",
className ?? "",
)}
{...props}
>
{props.children}
</a>
)
}
6 changes: 3 additions & 3 deletions packages/render/src/components/icon.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IconResponse } from "../types"
import { classNames } from "../util"
import { cn } from "../util"

export const Icon = ({ icon, width, className }: { icon: IconResponse; width: number; className?: string }) => {
// ref: .notion-page-icon
// note(missing): font-family: "Apple Color Emoji", "Segoe UI Emoji", NotoColorEmoji, "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols;*/
className = classNames("text-[1.1em] fill-[--fg-color-6] text-[--fg-color-icon]", className ?? "")
className = cn("text-[1.1em] fill-[--fg-color-6] text-[--fg-color-icon]", className ?? "")
switch (icon?.type) {
case "emoji":
return (
Expand All @@ -20,7 +20,7 @@ export const Icon = ({ icon, width, className }: { icon: IconResponse; width: nu
width={width}
height={width}
alt="Icon"
className={classNames("block max-h-full max-w-full rounded-[3px] object-fill", className)}
className={cn("block max-h-full max-w-full rounded-[3px] object-fill", className)}
/>
)
case "file":
Expand Down
34 changes: 0 additions & 34 deletions packages/render/src/components/link.tsx

This file was deleted.

11 changes: 11 additions & 0 deletions packages/render/src/components/page-title.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IconResponse } from "../types"
import { Icon } from "./icon"

export const PageTitle = ({ icon, children }: { icon: IconResponse | null; children: React.ReactNode }) => {
return (
<span className="notion-page-title">
{icon && <Icon icon={icon} width={20} className="notion-page-title-icon" />}
<span className="notion-page-title-text">{children}</span>
</span>
)
}
Loading

0 comments on commit f729c40

Please sign in to comment.