diff --git a/examples/minimal/components/NotionPage.tsx b/examples/minimal/components/NotionPage.tsx
index c39f9653..da28f3b1 100644
--- a/examples/minimal/components/NotionPage.tsx
+++ b/examples/minimal/components/NotionPage.tsx
@@ -1,9 +1,9 @@
import * as React from 'react'
import Head from 'next/head'
+import { NotionRenderer } from 'areyes-react-notion-x'
import { ExtendedRecordMap } from 'notion-types'
import { getPageTitle } from 'notion-utils'
-import { NotionRenderer } from 'react-notion-x'
export const NotionPage = ({
recordMap,
diff --git a/examples/minimal/pages/_app.tsx b/examples/minimal/pages/_app.tsx
index eac6a0d7..ae03b4ae 100644
--- a/examples/minimal/pages/_app.tsx
+++ b/examples/minimal/pages/_app.tsx
@@ -1,7 +1,7 @@
import * as React from 'react'
// core styles shared by all of react-notion-x (required)
-import 'react-notion-x/src/styles.css'
+import 'areyes-react-notion-x/src/styles.css'
import '../styles/globals.css'
diff --git a/packages/react-notion-x/src/block-components/aframe.tsx b/packages/react-notion-x/src/block-components/aframe.tsx
new file mode 100644
index 00000000..e676abab
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/aframe.tsx
@@ -0,0 +1,177 @@
+import * as React from 'react'
+
+import { BaseContentBlock } from 'notion-types'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+
+export const AFrame: React.FC<{
+ block: BaseContentBlock
+}> = ({ block }) => {
+ const { recordMap } = useNotionContext()
+
+ if (!block) {
+ return null
+ }
+
+ const style: React.CSSProperties = {
+ position: 'relative',
+ display: 'flex',
+ justifyContent: 'center',
+ alignSelf: 'center',
+ width: '100%',
+ maxWidth: '100%',
+ flexDirection: 'column'
+ }
+
+ const assetStyle: React.CSSProperties = {}
+ // console.log('asset', block)
+
+ if (block.format) {
+ const {
+ block_aspect_ratio,
+ block_height,
+ block_width,
+ block_full_width,
+ block_page_width,
+ block_preserve_scale
+ } = block.format
+
+ if (block_full_width || block_page_width) {
+ if (block_full_width) {
+ style.width = '100vw'
+ } else {
+ style.width = '100%'
+ }
+
+ if (block.type === 'video') {
+ if (block_height) {
+ style.height = block_height
+ } else if (block_aspect_ratio) {
+ style.paddingBottom = `${block_aspect_ratio * 100}%`
+ } else if (block_preserve_scale) {
+ style.objectFit = 'contain'
+ }
+ } else if (block_aspect_ratio && block.type !== 'image') {
+ style.paddingBottom = `${block_aspect_ratio * 100}%`
+ } else if (block_height) {
+ style.height = block_height
+ } else if (block_preserve_scale) {
+ if (block.type === 'image') {
+ style.height = '100%'
+ } else {
+ // TODO: this is just a guess
+ style.paddingBottom = '75%'
+ style.minHeight = 100
+ }
+ }
+ } else {
+ switch (block.format?.block_alignment) {
+ case 'center': {
+ style.alignSelf = 'center'
+ break
+ }
+ case 'left': {
+ style.alignSelf = 'start'
+ break
+ }
+ case 'right': {
+ style.alignSelf = 'end'
+ break
+ }
+ }
+
+ if (block_width) {
+ style.width = block_width
+ }
+
+ if (block_preserve_scale && block.type !== 'image') {
+ style.paddingBottom = '50%'
+ style.minHeight = 100
+ } else {
+ if (block_height && block.type !== 'image') {
+ style.height = block_height
+ }
+ }
+ }
+
+ if (block.type === 'image') {
+ assetStyle.objectFit = 'cover'
+ } else if (block_preserve_scale) {
+ assetStyle.objectFit = 'contain'
+ }
+ }
+
+ const source =
+ recordMap.signed_urls?.[block.id] || block.properties?.source?.[0]?.[0]
+ let content = null
+
+ if (!source) {
+ return null
+ }
+
+ let src = block.format?.display_source || source
+
+ if (src) {
+ if (block.type === 'gist') {
+ if (!src.endsWith('.pibb')) {
+ src = `${src}.pibb`
+ }
+
+ assetStyle.width = '100%'
+ style.paddingBottom = '50%'
+
+ // TODO: GitHub gists do not resize their height properly
+ content = (
+
+ )
+ } else {
+ src += block.type === 'typeform' ? '&disable-auto-focus=true' : ''
+
+ content = (
+
+ )
+ }
+ }
+
+ const children = (
+ <>
+ {block?.properties?.caption && (
+
+
+
+ )}
+ >
+ )
+
+ return (
+ <>
+
diff --git a/packages/react-notion-x/src/block-components/header.tsx b/packages/react-notion-x/src/block-components/header.tsx
new file mode 100644
index 00000000..7d75a39d
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/header.tsx
@@ -0,0 +1,117 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+import {
+ getBlockParentPage,
+ getPageTableOfContents,
+ getTextContent,
+ uuidToId
+} from 'notion-utils'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+import { LinkIcon } from '../icons/link-icon'
+import { cs } from '../utils'
+
+// TODO: use react state instead of a global for this
+const tocIndentLevelCache: {
+ [blockId: string]: number
+} = {}
+
+export const TextHeader: React.FC<{
+ blockId: string
+ block: Block
+ children?: React.ReactNode
+}> = ({ blockId, block, children }) => {
+ const ctx = useNotionContext()
+ const { recordMap } = ctx
+
+ if (!block.properties) return null
+
+ const blockColor = block.format?.block_color
+ const id = uuidToId(block.id)
+ const title = getTextContent(block.properties.title) || `Notion Header ${id}`
+
+ // we use a cache here because constructing the ToC is non-trivial
+ let indentLevel = tocIndentLevelCache[block.id]
+ let indentLevelClass: string
+
+ if (indentLevel === undefined) {
+ const page = getBlockParentPage(block, recordMap)
+
+ if (page) {
+ const toc = getPageTableOfContents(page, recordMap)
+ const tocItem = toc.find((tocItem) => tocItem.id === block.id)
+
+ if (tocItem) {
+ indentLevel = tocItem.indentLevel
+ tocIndentLevelCache[block.id] = indentLevel
+ }
+ }
+ }
+
+ if (indentLevel !== undefined) {
+ indentLevelClass = `notion-h-indent-${indentLevel}`
+ }
+
+ const isH1 = block.type === 'header'
+ const isH2 = block.type === 'sub_header'
+ const isH3 = block.type === 'sub_sub_header'
+
+ const classNameStr = cs(
+ isH1 && 'notion-h notion-h1',
+ isH2 && 'notion-h notion-h2',
+ isH3 && 'notion-h notion-h3',
+ blockColor && `notion-${blockColor}`,
+ indentLevelClass,
+ blockId
+ )
+
+ const innerHeader = (
+
+
+ {!block.format?.toggleable && (
+
+
+
+ )}
+
+
+
+
+
+ )
+ let headerBlock = null
+
+ //page title takes the h1 so all header blocks are greater
+ if (isH1) {
+ headerBlock = (
+
+ {innerHeader}
+
+ )
+ } else if (isH2) {
+ headerBlock = (
+
+ {innerHeader}
+
+ )
+ } else {
+ headerBlock = (
+
+ {innerHeader}
+
+ )
+ }
+
+ if (block.format?.toggleable) {
+ return (
+
+ {headerBlock}
+ {children}
+
+ )
+ } else {
+ return headerBlock
+ }
+}
diff --git a/packages/react-notion-x/src/block-components/image.tsx b/packages/react-notion-x/src/block-components/image.tsx
new file mode 100644
index 00000000..c5d678af
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/image.tsx
@@ -0,0 +1,182 @@
+import * as React from 'react'
+
+import { BaseContentBlock, Block } from 'notion-types'
+import { getTextContent, parsePageId } from 'notion-utils'
+
+import { LazyImage } from '../components/lazy-image'
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+
+const urlStyle = { width: '100%' }
+
+export const Image: React.FC<{
+ block: BaseContentBlock
+ zoomable?: boolean
+}> = ({ block, zoomable = true }) => {
+ const { recordMap, mapImageUrl, rootDomain, components, mapPageUrl } =
+ useNotionContext()
+
+ if (!block) {
+ return null
+ }
+
+ let isURL = false
+ const _caption: string = block?.properties?.caption?.[0]?.[0]
+
+ if (_caption) {
+ const id = parsePageId(_caption, { uuid: true })
+
+ const isPage = _caption.charAt(0) === '/' && id
+ if (isPage || isValidURL(_caption)) {
+ isURL = true
+ }
+ }
+
+ const style: React.CSSProperties = {
+ position: 'relative',
+ display: 'flex',
+ justifyContent: 'center',
+ alignSelf: 'center',
+ width: '100%',
+ maxWidth: '100%',
+ flexDirection: 'column'
+ }
+
+ const assetStyle: React.CSSProperties = {}
+
+ if (block.format) {
+ const {
+ block_height,
+ block_width,
+ block_full_width,
+ block_page_width,
+ block_preserve_scale
+ } = block.format
+
+ if (block_full_width || block_page_width) {
+ if (block_full_width) {
+ style.width = '100vw'
+ } else {
+ style.width = '100%'
+ }
+
+ if (block_height) {
+ style.height = block_height
+ } else if (block_preserve_scale) {
+ style.height = '100%'
+ }
+ } else {
+ switch (block.format?.block_alignment) {
+ case 'center': {
+ style.alignSelf = 'center'
+ break
+ }
+ case 'left': {
+ style.alignSelf = 'start'
+ break
+ }
+ case 'right': {
+ style.alignSelf = 'end'
+ break
+ }
+ }
+
+ if (block_width) {
+ style.width = block_width
+ }
+ }
+
+ assetStyle.objectFit = 'cover'
+ }
+
+ let source =
+ recordMap.signed_urls?.[block.id] || block.properties?.source?.[0]?.[0]
+ let content = null
+
+ if (!source) {
+ return null
+ }
+
+ // console.log('image', block)
+ // kind of a hack for now. New file.notion.so images aren't signed correctly
+ if (source.includes('file.notion.so')) {
+ source = block.properties?.source?.[0]?.[0]
+ }
+ const src = mapImageUrl(source, block as Block)
+ const caption = getTextContent(block.properties?.caption)
+ const alt = caption || 'notion image'
+
+ content = (
+
+ )
+
+ let children = (
+ <>
+ {block?.properties?.caption && (
+
+
+
+ )}
+ >
+ )
+
+ if (isURL) {
+ const caption: string = block?.properties?.caption[0][0]
+ const id = parsePageId(caption, { uuid: true })
+ const isPage = caption.charAt(0) === '/' && id
+ const captionHostname = extractHostname(caption)
+
+ children = (
+
+ {children}
+
+ )
+ }
+ const figure = (
+
+ {content}
+ {children}
+
+ )
+
+ return figure
+}
+
+function isValidURL(str: string) {
+ // TODO: replace this with a more well-tested package
+ const pattern = new RegExp(
+ '^(https?:\\/\\/)?' + // protocol
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
+ '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
+ '(\\#[-a-z\\d_]*)?$',
+ 'i'
+ )
+ return !!pattern.test(str)
+}
+
+function extractHostname(url: string) {
+ try {
+ const hostname = new URL(url).hostname
+ return hostname
+ } catch (err) {
+ return ''
+ }
+}
diff --git a/packages/react-notion-x/src/block-components/list.tsx b/packages/react-notion-x/src/block-components/list.tsx
new file mode 100644
index 00000000..d2517939
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/list.tsx
@@ -0,0 +1,57 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+import { cs, getListNumber } from '../utils'
+
+export const List: React.FC<{
+ blockId: string
+ block: Block
+ children?: React.ReactNode
+}> = ({ blockId, block, children }) => {
+ const ctx = useNotionContext()
+ const { recordMap } = ctx
+
+ const wrapList = (content: React.ReactNode, start?: number) =>
+ block.type === 'bulleted_list' ? (
+
+ ) : (
+
+ {content}
+
+ )
+
+ let output: JSX.Element | null = null
+
+ if (block.content) {
+ output = (
+ <>
+ {block.properties && (
+
+
+
+ )}
+ {wrapList(children)}
+ >
+ )
+ } else {
+ output = block.properties ? (
+
+
+
+ ) : null
+ }
+
+ const isTopLevel =
+ block.type !== recordMap.block[block.parent_id]?.value?.type
+ const start = getListNumber(block.id, recordMap.block)
+
+ return isTopLevel ? wrapList(output, start) : output
+}
diff --git a/packages/react-notion-x/src/block-components/page.tsx b/packages/react-notion-x/src/block-components/page.tsx
new file mode 100644
index 00000000..dcb23208
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/page.tsx
@@ -0,0 +1,264 @@
+import * as React from 'react'
+
+import * as types from 'notion-types'
+import {
+ getBlockCollectionId,
+ getBlockIcon,
+ getPageTableOfContents,
+ getTextContent
+} from 'notion-utils'
+
+import { LazyImage } from '../components/lazy-image'
+import { PageAside } from '../components/page-aside'
+import { PageIcon } from '../components/page-icon'
+import { PageTitle } from '../components/page-title'
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+import { cs, isUrl } from '../utils'
+
+const pageCoverStyleCache: Record
= {}
+
+export const Page: React.FC<{
+ block: types.Block
+ blockId: string
+ level: number
+
+ className?: string
+ bodyClassName?: string
+
+ header?: React.ReactNode
+ footer?: React.ReactNode
+ pageHeader?: React.ReactNode
+ pageFooter?: React.ReactNode
+ pageTitle?: React.ReactNode
+ pageAside?: React.ReactNode
+ pageCover?: React.ReactNode
+
+ hideBlockId?: boolean
+ disableHeader?: boolean
+
+ children?: React.ReactNode
+}> = (props) => {
+ const {
+ block,
+ blockId,
+ children,
+ level,
+ className,
+ bodyClassName,
+ header,
+ footer,
+ pageHeader,
+ pageFooter,
+ pageTitle,
+ pageAside,
+ pageCover,
+ disableHeader
+ } = props
+
+ const [activeSection, setActiveSection] = React.useState(null)
+
+ const ctx = useNotionContext()
+ const {
+ components,
+ fullPage,
+ darkMode,
+ recordMap,
+ mapPageUrl,
+ mapImageUrl,
+ showTableOfContents,
+ minTableOfContentsItems,
+ defaultPageIcon,
+ defaultPageCover,
+ defaultPageCoverPosition
+ } = ctx
+
+ if (!block.properties) return null
+
+ if (level === 0) {
+ const {
+ page_icon = defaultPageIcon,
+ page_cover = defaultPageCover,
+ page_cover_position = defaultPageCoverPosition,
+ page_full_width,
+ page_small_text
+ } = block.format || {}
+
+ if (fullPage) {
+ const properties =
+ block.type === 'page'
+ ? block.properties
+ : {
+ title:
+ recordMap.collection[getBlockCollectionId(block, recordMap)]
+ ?.value?.name
+ }
+
+ const coverPosition = (1 - (page_cover_position || 0.5)) * 100
+ const pageCoverObjectPosition = `center ${coverPosition}%`
+ let pageCoverStyle = pageCoverStyleCache[pageCoverObjectPosition]
+ if (!pageCoverStyle) {
+ pageCoverStyle = pageCoverStyleCache[pageCoverObjectPosition] = {
+ objectPosition: pageCoverObjectPosition
+ }
+ }
+
+ const pageIcon = getBlockIcon(block, recordMap) ?? defaultPageIcon
+ const isPageIconUrl = pageIcon && isUrl(pageIcon)
+
+ const toc = getPageTableOfContents(block as types.PageBlock, recordMap)
+
+ const hasToc =
+ showTableOfContents && toc.length >= minTableOfContentsItems
+ const hasAside = (hasToc || pageAside) && !page_full_width
+ const hasPageCover = pageCover || page_cover
+
+ return (
+
+
+
+
+ {!disableHeader &&
}
+ {header}
+
+
+ {hasPageCover &&
+ (pageCover ? (
+ pageCover
+ ) : (
+
+
+
+ ))}
+
+
+ {page_icon && (
+
+ )}
+
+ {pageHeader}
+
+
+ {pageTitle ?? (
+
+ )}
+
+
+ {(block.type === 'collection_view_page' ||
+ (block.type === 'page' &&
+ block.parent_table === 'collection')) && (
+
+ )}
+
+ {block.type !== 'collection_view_page' && (
+
+
+ {children}
+
+
+ {hasAside && (
+
+ )}
+
+ )}
+
+ {pageFooter}
+
+
+ {footer}
+
+
+
+ )
+ } else {
+ return (
+
+
+
+ {pageHeader}
+
+ {(block.type === 'collection_view_page' ||
+ (block.type === 'page' && block.parent_table === 'collection')) && (
+
+ )}
+
+ {block.type !== 'collection_view_page' && children}
+
+ {pageFooter}
+
+ )
+ }
+ } else {
+ const blockColor = block.format?.block_color
+
+ return (
+
+
+
+ )
+ }
+}
diff --git a/packages/react-notion-x/src/block-components/pdf.tsx b/packages/react-notion-x/src/block-components/pdf.tsx
new file mode 100644
index 00000000..5510c63c
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/pdf.tsx
@@ -0,0 +1,127 @@
+import * as React from 'react'
+
+import { BaseContentBlock } from 'notion-types'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+
+const isServer = typeof window === 'undefined'
+
+export const PDF: React.FC<{
+ block: BaseContentBlock
+}> = ({ block }) => {
+ const { recordMap, components } = useNotionContext()
+
+ if (!block) {
+ return null
+ }
+
+ const style: React.CSSProperties = {
+ position: 'relative',
+ display: 'flex',
+ justifyContent: 'center',
+ alignSelf: 'center',
+ width: '100%',
+ maxWidth: '100%',
+ flexDirection: 'column'
+ }
+
+ const assetStyle: React.CSSProperties = {}
+ // console.log('asset', block)
+
+ if (block.format) {
+ const {
+ block_aspect_ratio,
+ block_height,
+ block_width,
+ block_full_width,
+ block_page_width,
+ block_preserve_scale
+ } = block.format
+
+ if (block_full_width || block_page_width) {
+ if (block_full_width) {
+ style.width = '100vw'
+ } else {
+ style.width = '100%'
+ }
+
+ if (block_aspect_ratio) {
+ style.paddingBottom = `${block_aspect_ratio * 100}%`
+ } else if (block_height) {
+ style.height = block_height
+ } else if (block_preserve_scale) {
+ // TODO: this is just a guess
+ style.paddingBottom = '75%'
+ style.minHeight = 100
+ }
+ } else {
+ switch (block.format?.block_alignment) {
+ case 'center': {
+ style.alignSelf = 'center'
+ break
+ }
+ case 'left': {
+ style.alignSelf = 'start'
+ break
+ }
+ case 'right': {
+ style.alignSelf = 'end'
+ break
+ }
+ }
+
+ if (block_width) {
+ style.width = block_width
+ }
+
+ if (block_preserve_scale) {
+ style.paddingBottom = '50%'
+ style.minHeight = 100
+ } else {
+ style.height = block_height
+ }
+ }
+
+ assetStyle.objectFit = 'contain'
+ }
+
+ const source =
+ recordMap.signed_urls?.[block.id] || block.properties?.source?.[0]?.[0]
+ let content = null
+
+ if (!source) {
+ return null
+ }
+
+ style.overflow = 'auto'
+ style.background = 'rgb(226, 226, 226)'
+ style.display = 'block'
+
+ if (!style.padding) {
+ style.padding = '8px 16px'
+ }
+
+ if (!isServer) {
+ // console.log('pdf', block, signedUrl)
+ content =
+ }
+
+ const children = (
+ <>
+ {block?.properties?.caption && (
+
+
+
+ )}
+ >
+ )
+
+ return (
+ <>
+ {content}
+
+ {children}
+ >
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/quote.tsx b/packages/react-notion-x/src/block-components/quote.tsx
new file mode 100644
index 00000000..56ea377a
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/quote.tsx
@@ -0,0 +1,31 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+
+import { Text } from '../components/text'
+import { cs } from '../utils'
+
+export const Quote: React.FC<{
+ blockId: string
+ block: Block
+ children?: React.ReactNode
+}> = ({ blockId, block, children }) => {
+ if (!block.properties) return null
+
+ const blockColor = block.format?.block_color
+
+ return (
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/table-of-contents.tsx b/packages/react-notion-x/src/block-components/table-of-contents.tsx
new file mode 100644
index 00000000..62d5ec6d
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/table-of-contents.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+import {
+ getBlockParentPage,
+ getPageTableOfContents,
+ uuidToId
+} from 'notion-utils'
+
+import { useNotionContext } from '../context'
+import { cs } from '../utils'
+
+export const TableOfContents: React.FC<{
+ blockId: string
+ block: Block
+}> = ({ blockId, block }) => {
+ const ctx = useNotionContext()
+ const { recordMap } = ctx
+
+ const page = getBlockParentPage(block, recordMap)
+ if (!page) return null
+
+ const toc = getPageTableOfContents(page, recordMap)
+ const blockColor = block.format?.block_color
+
+ return (
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/table-row.tsx b/packages/react-notion-x/src/block-components/table-row.tsx
new file mode 100644
index 00000000..9aa6a68e
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/table-row.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react'
+
+import * as types from 'notion-types'
+import { Block } from 'notion-types'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+import { cs } from '../utils'
+
+export const TableRow: React.FC<{
+ blockId: string
+ block: Block
+}> = ({ blockId, block }) => {
+ const ctx = useNotionContext()
+ const { recordMap } = ctx
+
+ const tableBlock = recordMap.block[block.parent_id]?.value as types.TableBlock
+ const order = tableBlock.format?.table_block_column_order
+ const formatMap = tableBlock.format?.table_block_column_format
+ const backgroundColor = block.format?.block_color
+
+ if (!tableBlock || !order) {
+ return null
+ }
+
+ return (
+
+ {order.map((column) => {
+ const color = formatMap?.[column]?.color
+
+ return (
+ |
+
+
+
+ |
+ )
+ })}
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/table.tsx b/packages/react-notion-x/src/block-components/table.tsx
new file mode 100644
index 00000000..c5ba6b96
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/table.tsx
@@ -0,0 +1,17 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+
+import { cs } from '../utils'
+
+export const Table: React.FC<{
+ blockId: string
+ block: Block
+ children: React.ReactNode
+}> = ({ blockId, children }) => {
+ return (
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/text.tsx b/packages/react-notion-x/src/block-components/text.tsx
new file mode 100644
index 00000000..3cc4641a
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/text.tsx
@@ -0,0 +1,34 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+
+import { Text } from '../components/text'
+import { cs } from '../utils'
+
+export const TextBlock: React.FC<{
+ blockId: string
+ block: Block
+ children?: React.ReactNode
+}> = ({ blockId, block, children }) => {
+ if (!block.properties && !block.content?.length) {
+ return
+ }
+
+ const blockColor = block.format?.block_color
+
+ return (
+
+ {block.properties?.title && (
+
+ )}
+
+ {children &&
{children}
}
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/to-do.tsx b/packages/react-notion-x/src/block-components/to-do.tsx
new file mode 100644
index 00000000..95113b3f
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/to-do.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+import { cs } from '../utils'
+
+export const ToDo: React.FC<{
+ blockId: string
+ block: Block
+ children: React.ReactNode
+}> = ({ blockId, block, children }) => {
+ const ctx = useNotionContext()
+
+ if (!block.properties) return null
+
+ const { components } = ctx
+
+ const isChecked = block.properties?.checked?.[0]?.[0] === 'Yes'
+
+ return (
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/toggle.tsx b/packages/react-notion-x/src/block-components/toggle.tsx
new file mode 100644
index 00000000..8e65f71d
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/toggle.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react'
+
+import { Block } from 'notion-types'
+
+import { Text } from '../components/text'
+import { cs } from '../utils'
+
+export const Toggle: React.FC<{
+ blockId: string
+ block: Block
+ children?: React.ReactNode
+}> = ({ blockId, block, children }) => {
+ return (
+
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/tweet.tsx b/packages/react-notion-x/src/block-components/tweet.tsx
new file mode 100644
index 00000000..11a2702a
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/tweet.tsx
@@ -0,0 +1,134 @@
+import * as React from 'react'
+
+import { BaseContentBlock } from 'notion-types'
+
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+
+export const Tweet: React.FC<{
+ block: BaseContentBlock
+}> = ({ block }) => {
+ const { recordMap, components } = useNotionContext()
+
+ if (!block) {
+ return null
+ }
+
+ const style: React.CSSProperties = {
+ position: 'relative',
+ display: 'flex',
+ justifyContent: 'center',
+ alignSelf: 'center',
+ width: '100%',
+ maxWidth: '100%',
+ flexDirection: 'column'
+ }
+
+ const assetStyle: React.CSSProperties = {}
+ // console.log('asset', block)
+
+ if (block.format) {
+ const {
+ block_aspect_ratio,
+ block_height,
+ block_width,
+ block_full_width,
+ block_page_width,
+ block_preserve_scale
+ } = block.format
+
+ if (block_full_width || block_page_width) {
+ if (block_full_width) {
+ style.width = '100vw'
+ } else {
+ style.width = '100%'
+ }
+
+ if (block_aspect_ratio) {
+ style.paddingBottom = `${block_aspect_ratio * 100}%`
+ } else if (block_height) {
+ style.height = block_height
+ } else if (block_preserve_scale) {
+ // TODO: this is just a guess
+ style.paddingBottom = '75%'
+ style.minHeight = 100
+ }
+ } else {
+ switch (block.format?.block_alignment) {
+ case 'center': {
+ style.alignSelf = 'center'
+ break
+ }
+ case 'left': {
+ style.alignSelf = 'start'
+ break
+ }
+ case 'right': {
+ style.alignSelf = 'end'
+ break
+ }
+ }
+
+ if (block_width) {
+ style.width = block_width
+ }
+
+ if (block_preserve_scale) {
+ style.paddingBottom = '50%'
+ style.minHeight = 100
+ } else {
+ if (block_height) {
+ style.height = block_height
+ }
+ }
+ }
+
+ assetStyle.objectFit = 'contain'
+ }
+
+ const source =
+ recordMap.signed_urls?.[block.id] || block.properties?.source?.[0]?.[0]
+ let content = null
+
+ if (!source) {
+ return null
+ }
+
+ const src = source
+ if (!src) return null
+
+ const id = src.split('?')[0].split('/').pop()
+ if (!id) return null
+
+ content = (
+
+
+
+ )
+
+ const children = (
+ <>
+ {block?.properties?.caption && (
+
+
+
+ )}
+ >
+ )
+
+ return (
+ <>
+ {content}
+
+ {children}
+ >
+ )
+}
diff --git a/packages/react-notion-x/src/block-components/video.tsx b/packages/react-notion-x/src/block-components/video.tsx
new file mode 100644
index 00000000..e16359a7
--- /dev/null
+++ b/packages/react-notion-x/src/block-components/video.tsx
@@ -0,0 +1,166 @@
+import * as React from 'react'
+
+import { BaseContentBlock } from 'notion-types'
+
+import { LiteYouTubeEmbed } from '../components/lite-youtube-embed'
+import { Text } from '../components/text'
+import { useNotionContext } from '../context'
+import { getYoutubeId } from '../utils'
+
+export const Video: React.FC<{
+ block: BaseContentBlock
+}> = ({ block }) => {
+ const { recordMap } = useNotionContext()
+
+ if (!block) {
+ return null
+ }
+
+ const style: React.CSSProperties = {
+ position: 'relative',
+ display: 'flex',
+ justifyContent: 'center',
+ alignSelf: 'center',
+ width: '100%',
+ maxWidth: '100%',
+ flexDirection: 'column'
+ }
+
+ const assetStyle: React.CSSProperties = {}
+
+ if (block.format) {
+ const {
+ block_aspect_ratio,
+ block_height,
+ block_width,
+ block_full_width,
+ block_page_width,
+ block_preserve_scale
+ } = block.format
+
+ if (block_full_width || block_page_width) {
+ if (block_full_width) {
+ style.width = '100vw'
+ } else {
+ style.width = '100%'
+ }
+
+ if (block_height) {
+ style.height = block_height
+ } else if (block_aspect_ratio) {
+ style.paddingBottom = `${block_aspect_ratio * 100}%`
+ } else if (block_preserve_scale) {
+ style.objectFit = 'contain'
+ }
+ } else {
+ switch (block.format?.block_alignment) {
+ case 'center': {
+ style.alignSelf = 'center'
+ break
+ }
+ case 'left': {
+ style.alignSelf = 'start'
+ break
+ }
+ case 'right': {
+ style.alignSelf = 'end'
+ break
+ }
+ }
+
+ if (block_width) {
+ style.width = block_width
+ }
+
+ style.height = block_height
+ }
+
+ assetStyle.objectFit = 'contain'
+ }
+
+ const source =
+ recordMap.signed_urls?.[block.id] || block.properties?.source?.[0]?.[0]
+ let content = null
+
+ if (!source) {
+ return null
+ }
+
+ if (
+ source &&
+ source.indexOf('youtube') < 0 &&
+ source.indexOf('youtu.be') < 0 &&
+ source.indexOf('vimeo') < 0 &&
+ source.indexOf('wistia') < 0 &&
+ source.indexOf('loom') < 0 &&
+ source.indexOf('videoask') < 0 &&
+ source.indexOf('getcloudapp') < 0
+ ) {
+ style.paddingBottom = undefined
+
+ content = (
+
+ )
+ } else {
+ const src = block.format?.display_source || source
+ const youtubeVideoId: string | null = getYoutubeId(src)
+
+ if (src && youtubeVideoId) {
+ content = (
+
+ )
+ } else {
+ // Loom handle
+ let src = block.format?.display_source || source
+
+ if (src) {
+ src += block.type === 'typeform' ? '&disable-auto-focus=true' : ''
+
+ content = (
+
+ )
+ }
+ }
+ }
+
+ const children = (
+ <>
+ {block?.properties?.caption && (
+
+
+
+ )}
+ >
+ )
+
+ return (
+ <>
+ {content}
+
+ {children}
+ >
+ )
+}
diff --git a/packages/react-notion-x/src/block.tsx b/packages/react-notion-x/src/block.tsx
index 6b3e0084..57e4dffd 100644
--- a/packages/react-notion-x/src/block.tsx
+++ b/packages/react-notion-x/src/block.tsx
@@ -1,29 +1,11 @@
import * as React from 'react'
import * as types from 'notion-types'
-import {
- getBlockCollectionId,
- getBlockIcon,
- getBlockParentPage,
- getPageTableOfContents,
- getTextContent,
- uuidToId
-} from 'notion-utils'
-
-import { AssetWrapper } from './components/asset-wrapper'
-import { Audio } from './components/audio'
-import { EOI } from './components/eoi'
-import { File } from './components/file'
-import { GoogleDrive } from './components/google-drive'
-import { LazyImage } from './components/lazy-image'
-import { PageAside } from './components/page-aside'
-import { PageIcon } from './components/page-icon'
-import { PageTitle } from './components/page-title'
+import { uuidToId } from 'notion-utils'
+
import { SyncPointerBlock } from './components/sync-pointer-block'
-import { Text } from './components/text'
import { useNotionContext } from './context'
-import { LinkIcon } from './icons/link-icon'
-import { cs, getListNumber, isUrl } from './utils'
+import { cs } from './utils'
interface BlockProps {
block: types.Block
@@ -46,47 +28,12 @@ interface BlockProps {
children?: React.ReactNode
}
-// TODO: use react state instead of a global for this
-const tocIndentLevelCache: {
- [blockId: string]: number
-} = {}
-
-const pageCoverStyleCache: Record = {}
-
export const Block: React.FC = (props) => {
const ctx = useNotionContext()
- const {
- components,
- fullPage,
- darkMode,
- recordMap,
- mapPageUrl,
- mapImageUrl,
- showTableOfContents,
- minTableOfContentsItems,
- defaultPageIcon,
- defaultPageCover,
- defaultPageCoverPosition
- } = ctx
-
- const [activeSection, setActiveSection] = React.useState(null)
-
- const {
- block,
- children,
- level,
- className,
- bodyClassName,
- header,
- footer,
- pageHeader,
- pageFooter,
- pageTitle,
- pageAside,
- pageCover,
- hideBlockId,
- disableHeader
- } = props
+
+ const components = ctx.components
+
+ const { block, children, level, hideBlockId } = props
if (!block) {
return null
@@ -106,377 +53,47 @@ export const Block: React.FC = (props) => {
case 'collection_view_page':
// fallthrough
case 'page':
- if (level === 0) {
- const {
- page_icon = defaultPageIcon,
- page_cover = defaultPageCover,
- page_cover_position = defaultPageCoverPosition,
- page_full_width,
- page_small_text
- } = block.format || {}
-
- if (fullPage) {
- const properties =
- block.type === 'page'
- ? block.properties
- : {
- title:
- recordMap.collection[getBlockCollectionId(block, recordMap)]
- ?.value?.name
- }
-
- const coverPosition = (1 - (page_cover_position || 0.5)) * 100
- const pageCoverObjectPosition = `center ${coverPosition}%`
- let pageCoverStyle = pageCoverStyleCache[pageCoverObjectPosition]
- if (!pageCoverStyle) {
- pageCoverStyle = pageCoverStyleCache[pageCoverObjectPosition] = {
- objectPosition: pageCoverObjectPosition
- }
- }
-
- const pageIcon = getBlockIcon(block, recordMap) ?? defaultPageIcon
- const isPageIconUrl = pageIcon && isUrl(pageIcon)
-
- const toc = getPageTableOfContents(
- block as types.PageBlock,
- recordMap
- )
-
- const hasToc =
- showTableOfContents && toc.length >= minTableOfContentsItems
- const hasAside = (hasToc || pageAside) && !page_full_width
- const hasPageCover = pageCover || page_cover
-
- return (
-
-
-
-
- {!disableHeader &&
}
- {header}
-
-
- {hasPageCover &&
- (pageCover ? (
- pageCover
- ) : (
-
-
-
- ))}
-
-
- {page_icon && (
-
- )}
-
- {pageHeader}
-
-
- {pageTitle ?? (
-
- )}
-
-
- {(block.type === 'collection_view_page' ||
- (block.type === 'page' &&
- block.parent_table === 'collection')) && (
-
- )}
-
- {block.type !== 'collection_view_page' && (
-
-
- {children}
-
-
- {hasAside && (
-
- )}
-
- )}
-
- {pageFooter}
-
-
- {footer}
-
-
-
- )
- } else {
- return (
-
-
-
- {pageHeader}
-
- {(block.type === 'collection_view_page' ||
- (block.type === 'page' &&
- block.parent_table === 'collection')) && (
-
- )}
-
- {block.type !== 'collection_view_page' && children}
-
- {pageFooter}
-
- )
- }
- } else {
- const blockColor = block.format?.block_color
-
- return (
-
-
-
- )
- }
+ return
case 'header':
// fallthrough
case 'sub_header':
// fallthrough
case 'sub_sub_header': {
- if (!block.properties) return null
-
- const blockColor = block.format?.block_color
- const id = uuidToId(block.id)
- const title =
- getTextContent(block.properties.title) || `Notion Header ${id}`
-
- // we use a cache here because constructing the ToC is non-trivial
- let indentLevel = tocIndentLevelCache[block.id]
- let indentLevelClass: string
-
- if (indentLevel === undefined) {
- const page = getBlockParentPage(block, recordMap)
-
- if (page) {
- const toc = getPageTableOfContents(page, recordMap)
- const tocItem = toc.find((tocItem) => tocItem.id === block.id)
-
- if (tocItem) {
- indentLevel = tocItem.indentLevel
- tocIndentLevelCache[block.id] = indentLevel
- }
- }
- }
-
- if (indentLevel !== undefined) {
- indentLevelClass = `notion-h-indent-${indentLevel}`
- }
-
- const isH1 = block.type === 'header'
- const isH2 = block.type === 'sub_header'
- const isH3 = block.type === 'sub_sub_header'
-
- const classNameStr = cs(
- isH1 && 'notion-h notion-h1',
- isH2 && 'notion-h notion-h2',
- isH3 && 'notion-h notion-h3',
- blockColor && `notion-${blockColor}`,
- indentLevelClass,
- blockId
- )
-
- const innerHeader = (
-
-
- {!block.format?.toggleable && (
-
-
-
- )}
-
-
-
-
-
+ return (
+
+ {children}
+
)
- let headerBlock = null
-
- //page title takes the h1 so all header blocks are greater
- if (isH1) {
- headerBlock = (
-
- {innerHeader}
-
- )
- } else if (isH2) {
- headerBlock = (
-
- {innerHeader}
-
- )
- } else {
- headerBlock = (
-
- {innerHeader}
-
- )
- }
-
- if (block.format?.toggleable) {
- return (
-
- {headerBlock}
- {children}
-
- )
- } else {
- return headerBlock
- }
}
case 'divider':
- return
+ return
case 'text': {
- if (!block.properties && !block.content?.length) {
- return
- }
-
- const blockColor = block.format?.block_color
-
return (
-
- {block.properties?.title && (
-
- )}
-
- {children &&
{children}
}
-
+
+ {children}
+
)
}
case 'bulleted_list':
// fallthrough
case 'numbered_list': {
- const wrapList = (content: React.ReactNode, start?: number) =>
- block.type === 'bulleted_list' ? (
-
- ) : (
-
- {content}
-
- )
-
- let output: JSX.Element | null = null
-
- if (block.content) {
- output = (
- <>
- {block.properties && (
-
-
-
- )}
- {wrapList(children)}
- >
- )
- } else {
- output = block.properties ? (
-
-
-
- ) : null
- }
-
- const isTopLevel =
- block.type !== recordMap.block[block.parent_id]?.value?.type
- const start = getListNumber(block.id, recordMap.block)
-
- return isTopLevel ? wrapList(output, start) : output
+ return (
+
+ {children}
+
+ )
}
case 'embed':
- return
- case 'replit':
// fallthrough
- case 'tweet':
+ case 'replit':
// fallthrough
case 'maps':
// fallthrough
- case 'pdf':
- // fallthrough
case 'figma':
// fallthrough
case 'typeform':
@@ -485,35 +102,58 @@ export const Block: React.FC = (props) => {
// fallthrough
case 'excalidraw':
// fallthrough
- case 'image':
- // fallthrough
case 'gist':
- // fallthrough
+ // fallthrough
+ return (
+
+
+
+ )
+
+ case 'pdf':
+ return (
+
+
+
+ )
+
case 'video':
- return
+ return (
+
+
+
+ )
+
+ case 'image': // for test
+ return (
+
+
+
+ )
case 'drive': {
- const properties = block.format?.drive_properties
- if (!properties) {
- //check if this drive actually needs to be embeded ex. google sheets.
- if (block.format?.display_source) {
- return
- }
- }
+ return
+ }
+
+ case 'tweet':
+ return (
+
+
+
+ )
+ case 'audio':
return (
-
)
- }
-
- case 'audio':
- return
case 'file':
- return
+ return (
+
+ )
case 'equation':
return (
@@ -528,50 +168,24 @@ export const Block: React.FC = (props) => {
return
case 'column_list':
- return {children}
-
- case 'column': {
- // note: notion uses 46px
- const spacerWidth = `min(32px, 4vw)`
- const ratio = block.format?.column_ratio || 0.5
- const parent = recordMap.block[block.parent_id]?.value
- const columns =
- parent?.content?.length || Math.max(2, Math.ceil(1.0 / ratio))
-
- const width = `calc((100% - (${
- columns - 1
- } * ${spacerWidth})) * ${ratio})`
- const style = { width }
-
return (
- <>
-
- {children}
-
+
+ {children}
+
+ )
-
- >
+ case 'column':
+ return (
+
+ {children}
+
)
- }
case 'quote': {
- if (!block.properties) return null
-
- const blockColor = block.format?.block_color
-
return (
-
-
-
-
+
{children}
-
+
)
}
@@ -581,174 +195,36 @@ export const Block: React.FC = (props) => {
)
case 'callout':
- if (components.Callout) {
- return
- } else {
- return (
-
- )
- }
+ return (
+
+ {children}
+
+ )
case 'bookmark': {
- if (!block.properties) return null
-
- const link = block.properties.link
- if (!link || !link[0]?.[0]) return null
-
- let title = getTextContent(block.properties.title)
- if (!title) {
- title = getTextContent(link)
- }
-
- if (title) {
- if (title.startsWith('http')) {
- try {
- const url = new URL(title)
- title = url.hostname
- } catch (err) {
- // ignore invalid links
- }
- }
- }
-
return (
-
-
-
- {title && (
-
-
-
- )}
-
- {block.properties?.description && (
-
-
-
- )}
-
-
- {block.format?.bookmark_icon && (
-
-
-
- )}
-
-
-
-
-
-
-
- {block.format?.bookmark_cover && (
-
-
-
- )}
-
-
+
)
}
case 'toggle':
return (
-
-
-
-
-
- {children}
-
+
+ {children}
+
)
- case 'table_of_contents': {
- const page = getBlockParentPage(block, recordMap)
- if (!page) return null
-
- const toc = getPageTableOfContents(page, recordMap)
- const blockColor = block.format?.block_color
-
- return (
-
- )
- }
+ case 'table_of_contents':
+ return
case 'to_do': {
- const isChecked = block.properties?.checked?.[0]?.[0] === 'Yes'
-
return (
-
+
+ {children}
+
)
}
@@ -758,76 +234,28 @@ export const Block: React.FC = (props) => {
case 'transclusion_reference':
return
- case 'alias': {
- const blockPointerId = block?.format?.alias_pointer?.id
- const linkedBlock = recordMap.block[blockPointerId]?.value
- if (!linkedBlock) {
- console.log('"alias" missing block', blockPointerId)
- return null
- }
-
+ case 'alias':
return (
-
-
-
+
)
- }
case 'table':
return (
-
+
+ {children}
+
)
- case 'table_row': {
- const tableBlock = recordMap.block[block.parent_id]
- ?.value as types.TableBlock
- const order = tableBlock.format?.table_block_column_order
- const formatMap = tableBlock.format?.table_block_column_format
- const backgroundColor = block.format?.block_color
-
- if (!tableBlock || !order) {
- return null
- }
-
+ case 'table_row':
return (
-
- {order.map((column) => {
- const color = formatMap?.[column]?.color
-
- return (
- |
-
-
-
- |
- )
- })}
-
+
)
- }
case 'external_object_instance':
- return
+ return
default:
if (process.env.NODE_ENV !== 'production') {
diff --git a/packages/react-notion-x/src/context.tsx b/packages/react-notion-x/src/context.tsx
index 63140538..4d913b82 100644
--- a/packages/react-notion-x/src/context.tsx
+++ b/packages/react-notion-x/src/context.tsx
@@ -2,8 +2,33 @@ import * as React from 'react'
import { ExtendedRecordMap } from 'notion-types'
-import { AssetWrapper } from './components/asset-wrapper'
+import { AFrame } from './block-components/aframe'
+import { Alias } from './block-components/alias'
+import { AssetWrapper } from './block-components/asset-wrapper'
+import { Audio } from './block-components/audio'
+import { Bookmark } from './block-components/bookmark'
+import { Callout } from './block-components/callout'
+import { Column } from './block-components/column'
+import { ColumnList } from './block-components/column-list'
+import { Divider } from './block-components/divider'
+import { Drive } from './block-components/drive'
+import { File } from './block-components/file'
+import { TextHeader } from './block-components/header'
+import { Image } from './block-components/image'
+import { List } from './block-components/list'
+import { Page } from './block-components/page'
+import { PDF } from './block-components/pdf'
+import { Quote } from './block-components/quote'
+import { Table } from './block-components/table'
+import { TableOfContents } from './block-components/table-of-contents'
+import { TableRow } from './block-components/table-row'
+import { TextBlock } from './block-components/text'
+import { ToDo } from './block-components/to-do'
+import { Toggle } from './block-components/toggle'
+import { Tweet } from './block-components/tweet'
+import { Video } from './block-components/video'
import { Checkbox as DefaultCheckbox } from './components/checkbox'
+import { EOI } from './components/eoi'
import { Header } from './components/header'
import { wrapNextImage, wrapNextLink } from './next'
import {
@@ -82,7 +107,6 @@ const DefaultLinkMemo = React.memo(DefaultLink)
const DefaultPageLink: React.FC = (props) =>
const DefaultPageLinkMemo = React.memo(DefaultPageLink)
-const DefaultEmbed = (props) =>
const DefaultHeader = Header
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -104,11 +128,9 @@ const dummyOverrideFn = (_: any, defaultValueFn: () => React.ReactNode) =>
defaultValueFn()
const defaultComponents: NotionComponents = {
- Image: null, // disable custom images by default
Link: DefaultLinkMemo,
PageLink: DefaultPageLinkMemo,
Checkbox: DefaultCheckbox,
- Callout: undefined, // use the built-in callout rendering by default
Code: dummyComponent('Code'),
Equation: dummyComponent('Equation'),
@@ -137,7 +159,32 @@ const defaultComponents: NotionComponents = {
Modal: dummyComponent('Modal'),
Header: DefaultHeader,
- Embed: DefaultEmbed
+ TextHeader,
+ Divider,
+ TextBlock,
+ Drive,
+ List,
+ ColumnList,
+ Column,
+ Quote,
+ Callout,
+ Bookmark,
+ Toggle,
+ TableOfContents,
+ ToDo,
+ Alias,
+ Table,
+ TableRow,
+ Page,
+ EOI,
+ Audio,
+ File,
+ Image,
+ Video,
+ PDF,
+ TweetAsset: Tweet,
+ AFrame,
+ AssetWrapper
}
const defaultNotionContext: NotionContext = {
diff --git a/packages/react-notion-x/src/index.tsx b/packages/react-notion-x/src/index.tsx
index 44c6541e..b1b2ac4a 100644
--- a/packages/react-notion-x/src/index.tsx
+++ b/packages/react-notion-x/src/index.tsx
@@ -6,3 +6,4 @@ export * from './context'
export * from './components/text'
export * from './components/header'
export * from './components/page-icon'
+export * from './components/lite-youtube-embed'
diff --git a/packages/react-notion-x/src/types.ts b/packages/react-notion-x/src/types.ts
index 9eec16c9..0c6baf6d 100644
--- a/packages/react-notion-x/src/types.ts
+++ b/packages/react-notion-x/src/types.ts
@@ -27,6 +27,30 @@ export interface NotionComponents {
Code: any
Equation: any
Callout?: any
+ TextHeader?: any
+ Divider?: any
+ TextBlock?: any
+ Drive?: any
+ List?: any
+ ColumnList?: any
+ Column?: any
+ Quote?: any
+ Bookmark?: any
+ Toggle?: any
+ TableOfContents?: any
+ ToDo?: any
+ Alias?: any
+ Table?: any
+ TableRow?: any
+ Page?: any
+ EOI?: any
+ Audio?: any
+ File?: any
+ Video?: any
+ PDF?: any
+ TweetAsset?: any
+ AFrame?: any
+ AssetWrapper?: any
// collection
Collection: any
@@ -52,7 +76,6 @@ export interface NotionComponents {
Pdf: any
Tweet: any
Modal: any
- Embed: any
// page navigation
Header: any