[docs] design shuffle (#2951)

This PR incorporates design tweaks from #2922 without the home page or
content changes.

These are:
- Replacing all `hello@tldraw.com` with `sales@tldraw.com`
- Fix mailto links.
- Showing the first item in a section on direct routes to the section
- Splitting the article page for human-written content from article page
for generated content
- Splitting the layout for the landing page from the rest of the site
(temporarily identical to the regular content)
- Removing headings from left sidebar
- Restoring headings in right sidebar for human-written pages with > 1
heading link
- Styling block quote
- Adjusting section link appearance / layout in header / menu
- Changing the order of search results to preference docs over examples
- Updating copy on events
- Removing copy on user interface menus
- Adding hero as prop to all articles
- Updated icon
- Fixing a few broken links
- Replaces the sandpack code blocks with hljs code blocks, except in
examples.

### Change Type

- [x] `documentation` — Changes to the documentation only[^2]
This commit is contained in:
Steve Ruiz 2024-02-29 16:28:45 +00:00 committed by GitHub
parent 3f5803729d
commit 9a6f4e8c4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 1587 additions and 1299 deletions

View file

@ -138,7 +138,7 @@ Please see our [contributing guide](https://github.com/tldraw/tldraw/blob/main/C
The tldraw source code and its distributions are provided under the [tldraw license](https://github.com/tldraw/tldraw/blob/master/LICENSE.md). This license does not permit commercial use. The tldraw source code and its distributions are provided under the [tldraw license](https://github.com/tldraw/tldraw/blob/master/LICENSE.md). This license does not permit commercial use.
If you wish to use this project in commercial product, you need to purchase a commercial license. matPlease contact us at [hello@tldraw.com](mailto:hello@tldraw.com) for more inforion about obtaining a commercial license. If you wish to use this project in commercial product, you need to purchase a commercial license. matPlease contact us at [sales@tldraw.com](mailto:sales@tldraw.com) for more inforion about obtaining a commercial license.
## Trademarks ## Trademarks
@ -146,4 +146,4 @@ Copyright (c) 2023-present tldraw Inc. The tldraw name and logo are trademarks o
## Contact ## Contact
Find us on Twitter at [@tldraw](https://twitter.com/tldraw) or email [hello@tldraw.com](mailto://hello@tldraw.com). You can also [join our discord](https://discord.gg/rhsyWMUJxd) for quick help and support. Find us on Twitter at [@tldraw](https://twitter.com/tldraw) or email [sales@tldraw.com](mailto://sales@tldraw.com). You can also [join our discord](https://discord.gg/rhsyWMUJxd) for quick help and support.

File diff suppressed because one or more lines are too long

View file

@ -1,12 +1,20 @@
import { ArticleDocsPage } from '@/components/ArticleDocsPage' import { ArticleDocsPage } from '@/components/ArticleDocsPage'
import { ArticleReferenceDocsPage } from '@/components/ArticleReferenceDocsPage'
import { CategoryDocsPage } from '@/components/CategoryDocsPage' import { CategoryDocsPage } from '@/components/CategoryDocsPage'
import { ExampleDocsPage } from '@/components/ExampleDocsPage' import { ExampleDocsPage } from '@/components/ExampleDocsPage'
import { SectionDocsPage } from '@/components/SectionDocsPage' import { SectionDocsPage } from '@/components/SectionDocsPage'
import { Article, Category, Section } from '@/types/content-types'
import { getDb } from '@/utils/ContentDatabase' import { getDb } from '@/utils/ContentDatabase'
import { Metadata } from 'next' import { Metadata } from 'next'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
async function getContentForPath(path: string) { async function getContentForPath(
path: string
): Promise<
| { type: 'section'; section: Section }
| { type: 'category'; category: Category }
| { type: 'article'; article: Article }
> {
const db = await getDb() const db = await getDb()
const section = await db.db.get(`SELECT * FROM sections WHERE sections.path = ?`, path) const section = await db.db.get(`SELECT * FROM sections WHERE sections.path = ?`, path)
@ -105,6 +113,28 @@ export default async function ContentPage({ params }: { params: { id: string | s
switch (content.type) { switch (content.type) {
case 'section': { case 'section': {
const db = await getDb()
let firstArticleInSection: Article | undefined
const categories = await db.getCategoriesForSection(content.section.id)
for (const category of categories) {
const articles = await db.getCategoryArticles(content.section.id, category.id)
const article = articles[0]
if (article) {
firstArticleInSection = article
break
}
}
if (firstArticleInSection) {
const article = await db.getArticle(firstArticleInSection.id)
if (article?.componentCode) {
return <ExampleDocsPage article={article} />
}
return <ArticleDocsPage article={article} />
}
return <SectionDocsPage section={content.section} /> return <SectionDocsPage section={content.section} />
} }
case 'category': { case 'category': {
@ -114,6 +144,11 @@ export default async function ContentPage({ params }: { params: { id: string | s
if (content.article.componentCode) { if (content.article.componentCode) {
return <ExampleDocsPage article={content.article} /> return <ExampleDocsPage article={content.article} />
} }
if (content.article.sectionId === 'reference') {
return <ArticleReferenceDocsPage article={content.article} />
}
return <ArticleDocsPage article={content.article} /> return <ArticleDocsPage article={content.article} />
} }
default: { default: {

View file

@ -10,7 +10,7 @@ export default async function ClaPage() {
<> <>
<Header /> <Header />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className="article"> <main className="main-content article">
<div className="page-header"> <div className="page-header">
<h1>Contributor License Agreement</h1> <h1>Contributor License Agreement</h1>
</div> </div>

View file

@ -0,0 +1,10 @@
import { Footer } from '@/components/Footer'
export default async function ContentLayout({ children }: { children: React.ReactNode }) {
return (
<div className="wrapper">
<div className="layout">{children}</div>
<Footer />
</div>
)
}

View file

@ -0,0 +1,10 @@
import { Footer } from '@/components/Footer'
export default async function ContentLayout({ children }: { children: React.ReactNode }) {
return (
<div className="wrapper">
<div className="layout">{children}</div>
<Footer />
</div>
)
}

View file

@ -1,4 +1,3 @@
import { Footer } from '@/components/Footer'
import { Analytics } from '@vercel/analytics/react' import { Analytics } from '@vercel/analytics/react'
import { Metadata, Viewport } from 'next' import { Metadata, Viewport } from 'next'
import AutoRefresh from '../components/AutoRefresh' import AutoRefresh from '../components/AutoRefresh'
@ -7,9 +6,9 @@ import '../styles/hljs.css'
import '../styles/parameters-table.css' import '../styles/parameters-table.css'
import { Providers } from './providers' import { Providers } from './providers'
const TITLE = 'tldraw docs' const TITLE = 'tldraw SDK'
const DESCRIPTION = const DESCRIPTION =
'Developer documentation for tldraw. Build infinite canvas experiences for the web.' 'Infinite canvas SDK from tldraw. Build whiteboards, design tools, and canvas experiences for the web.'
const TWITTER_HANDLE = '@tldraw' const TWITTER_HANDLE = '@tldraw'
const TWITTER_CARD = 'social-twitter.png' const TWITTER_CARD = 'social-twitter.png'
const FACEBOOK_CARD = 'social-og.png' const FACEBOOK_CARD = 'social-og.png'
@ -70,10 +69,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<html suppressHydrationWarning> <html suppressHydrationWarning>
<body> <body>
<Providers> <Providers>
<div className="wrapper"> {children}
<div className="layout">{children}</div>
<Footer />
</div>
<Analytics /> <Analytics />
</Providers> </Providers>
</body> </body>

View file

@ -7,15 +7,17 @@ export default async function NotFound() {
const sidebar = await db.getSidebarContentList({}) const sidebar = await db.getSidebarContentList({})
return ( return (
<> <div className="wrapper">
<Header /> <div className="layout">
<Sidebar {...sidebar} /> <Header />
<main className="article"> <Sidebar {...sidebar} />
<div className="page-header"> <main className="main-content article">
<h1>Not found.</h1> <div className="page-header">
</div> <h1>Not found.</h1>
<p>There's nothing here. :(</p> </div>
</main> <p>There's nothing here. :(</p>
</> </main>
</div>
</div>
) )
} }

View file

@ -3,5 +3,5 @@
import { ThemeProvider } from 'next-themes' import { ThemeProvider } from 'next-themes'
export function Providers({ children }: { children: any }) { export function Providers({ children }: { children: any }) {
return <ThemeProvider>{children}</ThemeProvider> return <ThemeProvider enableSystem>{children}</ThemeProvider>
} }

View file

@ -1,8 +1,8 @@
import { Article } from '@/types/content-types' import { Article } from '@/types/content-types'
import { getDb } from '@/utils/ContentDatabase' import { getDb } from '@/utils/ContentDatabase'
import { ArticleDetails } from './ArticleDetails' import { ArticleDetails } from './ArticleDetails'
import { ArticleHeadingLinks } from './ArticleHeadingLinks'
import { ArticleNavLinks } from './ArticleNavLinks' import { ArticleNavLinks } from './ArticleNavLinks'
import { Breadcrumb } from './Breadcrumb'
import { Header } from './Header' import { Header } from './Header'
import { Mdx } from './Mdx' import { Mdx } from './Mdx'
import { Sidebar } from './Sidebar' import { Sidebar } from './Sidebar'
@ -20,22 +20,20 @@ export async function ArticleDocsPage({ article }: { article: Article }) {
articleId: article.id, articleId: article.id,
}) })
const isGenerated = article.sectionId === 'reference'
return ( return (
<> <>
<Header sectionId={section.id} /> <Header sectionId={section.id} />
<Sidebar headings={headings} {...sidebar} /> <Sidebar {...sidebar} />
<main className={`article${isGenerated ? ' article__api-docs' : ''}`}> <main className="main-content article">
<div className="page-header"> <div className="page-header">
<Breadcrumb section={section} category={category} />
<h1>{article.title}</h1> <h1>{article.title}</h1>
</div> </div>
{article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />} {article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />}
{article.content && <Mdx content={article.content} />} {article.content && <Mdx content={article.content} />}
{isGenerated ? null : <ArticleDetails article={article} />} <ArticleDetails article={article} />
{links && <ArticleNavLinks links={links} />} {links && <ArticleNavLinks links={links} />}
</main> </main>
<ArticleHeadingLinks article={article} headingLinks={headings} />
</> </>
) )
} }

View file

@ -0,0 +1,46 @@
/* eslint-disable no-useless-escape */
import { Article, ArticleHeading, ArticleHeadings } from '@/types/content-types'
import Link from 'next/link'
export function ArticleHeadingLinks({
headingLinks,
}: {
article: Article
headingLinks: ArticleHeadings
}) {
const linksToShow = headingLinks.filter((heading) => heading.level < 4)
if (linksToShow.length <= 1) return null
return (
<nav className="layout__headings">
<ul className="sidebar__list sidebar__sections__list">
<li className="sidebar__section">
<div className="sidebar__section__title uppercase_title">On this page</div>
<ul className="sidebar__list">
{linksToShow.map((heading) => (
<HeaderLink key={heading.slug} heading={heading} />
))}
</ul>
</li>
</ul>
</nav>
)
}
function HeaderLink({ heading }: { heading: ArticleHeading }) {
return (
<li className="sidebar__article">
<Link href={`#${heading.slug}`} className="sidebar__link">
{heading.level > 2 ? <span className="sidebar__link__indent">{''}</span> : null}
<span className="sidebar__link__title">
{heading.isCode ? (
<code>{heading.title.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')}</code>
) : (
heading.title.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
)}
</span>
</Link>
</li>
)
}

View file

@ -0,0 +1,36 @@
import { Article } from '@/types/content-types'
import { getDb } from '@/utils/ContentDatabase'
import { ArticleNavLinks } from './ArticleNavLinks'
import { Breadcrumb } from './Breadcrumb'
import { Header } from './Header'
import { Mdx } from './Mdx'
import { Sidebar } from './Sidebar'
import { Image } from './mdx-components/generic'
export async function ArticleReferenceDocsPage({ article }: { article: Article }) {
const db = await getDb()
const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId)
const links = await db.getArticleLinks(article)
const sidebar = await db.getSidebarContentList({
sectionId: section.id,
categoryId: category.id,
articleId: article.id,
})
return (
<>
<Header sectionId={section.id} />
<Sidebar {...sidebar} />
<main className="main-content article article__api-docs">
<div className="page-header">
<Breadcrumb section={section} category={category} />
<h1>{article.title}</h1>
</div>
{article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />}
{article.content && <Mdx content={article.content} />}
{links && <ArticleNavLinks links={links} />}
</main>
</>
)
}

View file

@ -23,10 +23,8 @@ export async function CategoryDocsPage({ category }: { category: Category }) {
{articles.length > 0 && ( {articles.length > 0 && (
<ul> <ul>
{articles.map((article) => ( {articles.map((article) => (
<li> <li key={article.id}>
<Link key={article.id} href={`/${section.id}/${category.id}/${article.id}`}> <Link href={`/${section.id}/${category.id}/${article.id}`}>{article.title}</Link>
{article.title}
</Link>
</li> </li>
))} ))}
</ul> </ul>

View file

@ -1,18 +1,15 @@
import { Article } from '@/types/content-types' import { Article } from '@/types/content-types'
import { getDb } from '@/utils/ContentDatabase' import { getDb } from '@/utils/ContentDatabase'
import { ArticleNavLinks } from './ArticleNavLinks' import { ArticleNavLinks } from './ArticleNavLinks'
import { Breadcrumb } from './Breadcrumb'
import ExampleCodeBlock from './ExampleCodeBlock' import ExampleCodeBlock from './ExampleCodeBlock'
import { Header } from './Header' import { Header } from './Header'
import { Mdx } from './Mdx' import { Mdx } from './Mdx'
import { Sidebar } from './Sidebar' import { Sidebar } from './Sidebar'
import { Image } from './mdx-components/generic'
export async function ExampleDocsPage({ article }: { article: Article }) { export async function ExampleDocsPage({ article }: { article: Article }) {
const db = await getDb() const db = await getDb()
const section = await db.getSection(article.sectionId) const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId) const category = await db.getCategory(article.categoryId)
const headings = await db.getArticleHeadings(article.id)
const links = await db.getArticleLinks(article) const links = await db.getArticleLinks(article)
const sidebar = await db.getSidebarContentList({ const sidebar = await db.getSidebarContentList({
sectionId: section.id, sectionId: section.id,
@ -23,25 +20,21 @@ export async function ExampleDocsPage({ article }: { article: Article }) {
return ( return (
<> <>
<Header sectionId={section.id} /> <Header sectionId={section.id} />
<Sidebar headings={headings} {...sidebar} /> <Sidebar {...sidebar} />
<main className={`article article__example`}> <main className={`main-content article article__example`}>
<div className="page-header"> <div className="page-header">
<Breadcrumb section={section} category={category} />
<h1>{article.title}</h1> <h1>{article.title}</h1>
{article.description && <p>{article.description}</p>} {article.description && <p>{article.description}</p>}
</div> </div>
{article.hero && <Image alt="hero" title={article.title} src={`images/${article.hero}`} />}
{article.content && <Mdx content={article.content} />} {article.content && <Mdx content={article.content} />}
{article.componentCode && ( <ExampleCodeBlock
<ExampleCodeBlock articleId={article.id}
articleId={article.id} files={{
files={{ 'App.tsx': article.componentCode,
'App.tsx': article.componentCode, ...(article.componentCodeFiles ? JSON.parse(article.componentCodeFiles) : null),
...(article.componentCodeFiles ? JSON.parse(article.componentCodeFiles) : null), }}
}} activeFile={'App.tsx'}
activeFile={'App.tsx'} />
/>
)}
{links && <ArticleNavLinks links={links} />} {links && <ArticleNavLinks links={links} />}
</main> </main>
</> </>

View file

@ -1,44 +1,43 @@
'use client' 'use client'
import { debounce } from '@/utils/debounce' import { useRef } from 'react'
import { useEffect, useRef, useState } from 'react'
export default function FancyBox() { export default function FancyBox() {
const rContainer = useRef<HTMLDivElement>(null) const rContainer = useRef<HTMLDivElement>(null)
const [items, setItems] = useState<number[]>([]) // const [items, setItems] = useState<number[]>([])
useEffect(() => { // useEffect(() => {
const populate = debounce(() => { // const populate = debounce(() => {
const elm = rContainer.current // const elm = rContainer.current
if (!elm) return // if (!elm) return
const width = elm.clientWidth // const width = elm.clientWidth
const height = elm.clientHeight // const height = elm.clientHeight
const SIZE = 32 // const SIZE = 32
const cols = Math.ceil(width / SIZE) // const cols = Math.ceil(width / SIZE)
const rows = Math.ceil(height / SIZE) // const rows = Math.ceil(height / SIZE)
const items = Array.from(Array(cols * rows)).map((_, i) => i) // const items = Array.from(Array(cols * rows)).map((_, i) => i)
setItems(items) // setItems(items)
}, 100) // }, 100)
populate() // populate()
window.addEventListener('resize', populate) // window.addEventListener('resize', populate)
return () => { // return () => {
window.removeEventListener('resize', populate) // window.removeEventListener('resize', populate)
} // }
}, []) // }, [])
return ( return (
<div className="footer__fancybox" ref={rContainer}> <div className="footer__fancybox" ref={rContainer}>
{items.map((i) => { {/* {items.map((i) => {
const c = 1 + (i % 7) const c = 1 + (i % 7)
return <div key={i} className="footer__fancybox__item" data-c={c} /> return <div key={i} className="footer__fancybox__item" data-c={c} />
})} })} */}
</div> </div>
) )
} }

View file

@ -18,11 +18,7 @@ export function Footer() {
<p>tldraw © {new Date().getFullYear()}</p> <p>tldraw © {new Date().getFullYear()}</p>
</a> </a>
<div className="footer__socials"> <div className="footer__socials">
<a <a href="https://x.com/tldraw" className="sidebar__button icon-button" title="x">
href="https://twitter.com/tldraw"
className="sidebar__button icon-button"
title="twitter"
>
<Icon icon="twitter" /> <Icon icon="twitter" />
</a> </a>
<a <a

View file

@ -10,43 +10,42 @@ export function Header({ sectionId }: { sectionId?: string }) {
<div className="layout__header"> <div className="layout__header">
<div className="layout__header__left"> <div className="layout__header__left">
<Link href="/quick-start"> <Link href="/quick-start">
<div <img className="logo-dark" src="/tldraw_dev_dark.png" />
className="lockup" <img className="logo-light" src="/tldraw_dev_light.png" />
style={{
mask: `url(/lockup.svg) center 100% / 100% no-repeat`,
WebkitMask: `url(/lockup.svg) center 100% / 100% no-repeat`,
}}
/>
</Link> </Link>
</div> </div>
<Search /> <Search />
<div className="layout__header__sections_and_socials"> <div className="layout__header__links">
<SectionLinks sectionId={sectionId} /> <div className="layout__header__sections">
<a <SectionLinks sectionId={sectionId} />
href="https://x.com/tldraw/" </div>
className="sidebar__button icon-button" <div className="layout__header__socials">
title="twitter" <a
target="_blank" href="https://x.com/tldraw/"
> className="sidebar__button icon-button"
<Icon icon="twitter" /> title="twitter"
</a> target="_blank"
<a >
href="https://discord.com/invite/SBBEVCA4PG" <Icon icon="twitter" />
className="sidebar__button icon-button" </a>
title="discord" <a
target="_blank" href="https://discord.com/invite/SBBEVCA4PG"
> className="sidebar__button icon-button"
<Icon icon="discord" /> title="discord"
</a> target="_blank"
<a >
href="https://github.com/tldraw/tldraw" <Icon icon="discord" />
className="sidebar__button icon-button" </a>
title="github" <a
target="_blank" href="https://github.com/tldraw/tldraw"
> className="sidebar__button icon-button"
<Icon icon="github" /> title="github"
</a> target="_blank"
<ThemeSwitcher /> >
<Icon icon="github" />
</a>
<ThemeSwitcher />
</div>
</div> </div>
</div> </div>
) )

View file

@ -13,7 +13,7 @@ export function Chevron({ className }: { className?: string }) {
<path <path
d="M4 6L8 10L12 6" d="M4 6L8 10L12 6"
stroke="currentColor" stroke="currentColor"
strokeWidth="2" strokeWidth="1"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />

View file

@ -1,8 +1,8 @@
import { MDXRemote } from 'next-mdx-remote/rsc' import { MDXRemote } from 'next-mdx-remote/rsc'
import rehypeAutolinkHeadings from 'rehype-autolink-headings' import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeHighlight from 'rehype-highlight'
import rehypeSlug from 'rehype-slug-custom-id' import rehypeSlug from 'rehype-slug-custom-id'
import { components } from './mdx-components' import { components } from './mdx-components'
// import rehypeHighlight from 'rehype-highlight'
interface MdxProps { interface MdxProps {
content: string content: string
@ -17,7 +17,7 @@ export function Mdx({ content }: MdxProps) {
mdxOptions: { mdxOptions: {
// remarkPlugins: [remarkGfm, {}], // remarkPlugins: [remarkGfm, {}],
rehypePlugins: [ rehypePlugins: [
// [rehypeHighlight as any, {}], [rehypeHighlight as any, {}],
[rehypeAutolinkHeadings, {}], [rehypeAutolinkHeadings, {}],
[rehypeSlug, { enableCustomId: true, maintainCase: true, removeAccents: true }], [rehypeSlug, { enableCustomId: true, maintainCase: true, removeAccents: true }],
], ],

View file

@ -102,7 +102,7 @@ export function Search() {
// {searchType === SEARCH_TYPE.NORMAL ? '✨ Search using AI' : '⭐ Search without AI'} // {searchType === SEARCH_TYPE.NORMAL ? '✨ Search using AI' : '⭐ Search without AI'}
// </button> // </button>
// } // }
groups={['examples', 'docs', 'reference']} groups={['docs', 'examples', 'reference']}
groupsToLabel={{ examples: 'Examples', docs: 'Articles', reference: 'Reference' }} groupsToLabel={{ examples: 'Examples', docs: 'Articles', reference: 'Reference' }}
options={searchResults} options={searchResults}
isLoading={isLoading} isLoading={isLoading}

View file

@ -11,7 +11,7 @@ export async function SectionDocsPage({ section }: { section: Section }) {
<> <>
<Header sectionId={section.id} /> <Header sectionId={section.id} />
<Sidebar {...sidebar} /> <Sidebar {...sidebar} />
<main className="article"> <main className="main-content article">
<div className="page-header"> <div className="page-header">
<h1>{section.title}</h1> <h1>{section.title}</h1>
</div> </div>

View file

@ -33,7 +33,7 @@ const linkContext = createContext<{
// it keeps re-rendering. // it keeps re-rendering.
let scrollPosition = 0 let scrollPosition = 0
export function Sidebar({ headings, links, sectionId, categoryId, articleId }: SidebarProps) { export function Sidebar({ links, sectionId, categoryId, articleId }: SidebarProps) {
const activeId = articleId ?? categoryId ?? sectionId const activeId = articleId ?? categoryId ?? sectionId
const sidebarRef = useRef<HTMLDivElement>(null) const sidebarRef = useRef<HTMLDivElement>(null)
@ -72,7 +72,7 @@ export function Sidebar({ headings, links, sectionId, categoryId, articleId }: S
<div className="sidebar__section__links"> <div className="sidebar__section__links">
<SectionLinks sectionId={sectionId} /> <SectionLinks sectionId={sectionId} />
</div> </div>
<SidebarLinks headings={headings} links={links} /> <SidebarLinks links={links} />
<SidebarCloseButton /> <SidebarCloseButton />
</div> </div>
<ToggleMenuButton /> <ToggleMenuButton />
@ -81,62 +81,48 @@ export function Sidebar({ headings, links, sectionId, categoryId, articleId }: S
) )
} }
export function SidebarLinks({ export function SidebarLinks({ links }: { links: SidebarContentLink[] }) {
headings,
links,
}: {
headings?: ArticleHeadings
links: SidebarContentLink[]
}) {
return ( return (
<nav className="sidebar__nav"> <nav className="sidebar__nav">
<ul className="sidebar__list sidebar__sections__list"> <ul className="sidebar__list sidebar__sections__list">
{links.map((link) => ( {links.map((link) => (
<SidebarLink key={link.url} headings={headings} {...link} /> <SidebarLink key={link.url} {...link} />
))} ))}
</ul> </ul>
</nav> </nav>
) )
} }
function SidebarLink({ headings, ...props }: SidebarContentLink & { headings?: ArticleHeadings }) { function SidebarLink(props: SidebarContentLink) {
switch (props.type) { switch (props.type) {
case 'section': { case 'section': {
return <SidebarSection headings={headings} {...props} /> return <SidebarSection {...props} />
} }
case 'article': { case 'article': {
return <SidebarArticle headings={headings} {...props} /> return <SidebarArticle {...props} />
} }
case 'category': { case 'category': {
return <SidebarCategory headings={headings} {...props} /> return <SidebarCategory {...props} />
} }
} }
} }
function SidebarSection({ function SidebarSection({ title, children }: SidebarContentSectionLink) {
title,
children,
headings,
}: SidebarContentSectionLink & { headings?: ArticleHeadings }) {
if (children.length === 0) return null if (children.length === 0) return null
return ( return (
<li className="sidebar__section"> <li className="sidebar__section">
{title && <span className="sidebar__section__title">{title}</span>} {title && <span className="sidebar__section__title uppercase_title">{title}</span>}
<ul className="sidebar__list"> <ul className="sidebar__list">
{children.map((link) => ( {children.map((link) => (
<SidebarLink key={link.url} headings={headings} {...link} /> <SidebarLink key={link.url} {...link} />
))} ))}
</ul> </ul>
</li> </li>
) )
} }
function SidebarCategory({ function SidebarCategory({ title, children }: SidebarContentCategoryLink) {
title,
children,
headings,
}: SidebarContentCategoryLink & { headings?: ArticleHeadings }) {
const linkCtx = useContext(linkContext) const linkCtx = useContext(linkContext)
if (children.length === 0) return null if (children.length === 0) return null
const hasGroups = children.some((child) => !!(child as SidebarContentArticleLink).groupId) const hasGroups = children.some((child) => !!(child as SidebarContentArticleLink).groupId)
@ -171,7 +157,7 @@ function SidebarCategory({
<Accordion.Content> <Accordion.Content>
<ul className="sidebar__list sidebar__group"> <ul className="sidebar__list sidebar__group">
{articles.map((link) => ( {articles.map((link) => (
<SidebarLink key={link.url} headings={headings} {...link} /> <SidebarLink key={link.url} {...link} />
))} ))}
</ul> </ul>
</Accordion.Content> </Accordion.Content>

View file

@ -5,12 +5,7 @@ import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
export const Code = (props: any) => { export const Code = (props: any) => {
if (!props.className) { return <code {...props} />
return <code {...props} />
}
const language = props.className.replace('language-', '')
return <CodeBlock code={{ [`App.${language}`]: props.children.trim() }} />
} }
export function CodeBlock({ code }: { code: SandpackFiles }) { export function CodeBlock({ code }: { code: SandpackFiles }) {

View file

@ -150,7 +150,7 @@ export const Footnotes = (props: any) => {
/* -------------------- API docs -------------------- */ /* -------------------- API docs -------------------- */
export const ApiHeading = (props: any) => { export const ApiHeading = (props: any) => {
return <div {...props} /> return <div className="article__api-heading uppercase_title" {...props} />
} }
export const Embed = (props: any) => { export const Embed = (props: any) => {
@ -161,3 +161,14 @@ export const Embed = (props: any) => {
</div> </div>
) )
} }
/* -------------------- Callouts -------------------- */
export const Callout = ({ icon, children }: any) => {
return (
<div className="article__callout">
<span>{icon}</span>
<p>{children}</p>
</div>
)
}

View file

@ -4,6 +4,7 @@ import {
A, A,
ApiHeading, ApiHeading,
Blockquote, Blockquote,
Callout,
Divider, Divider,
Embed, Embed,
Heading1, Heading1,
@ -56,6 +57,7 @@ export const components = {
Image, Image,
Small: Small, Small: Small,
Video, Video,
Callout,
...customComponents, ...customComponents,
...apiComponents, ...apiComponents,
} }

View file

@ -9,14 +9,14 @@
{ {
"id": "tldraw", "id": "tldraw",
"name": "tldraw", "name": "tldraw",
"email": "hello@tldraw.com", "email": "sales@tldraw.com",
"twitter": "tldraw", "twitter": "tldraw",
"image": "tldraw.jpg" "image": "tldraw.jpg"
}, },
{ {
"id": "api", "id": "api",
"name": "API", "name": "API",
"email": "hello@tldraw.com", "email": "sales@tldraw.com",
"twitter": "tldraw", "twitter": "tldraw",
"image": "api.jpg" "image": "api.jpg"
} }

View file

@ -10,7 +10,7 @@ tldraw uses a dual licensing model to support the development of the project.
The project's source code, libraries, and distributions are provided under the [tldraw licence](https://github.com/tldraw/tldraw/blob/master/LICENSE.md). The project's source code, libraries, and distributions are provided under the [tldraw licence](https://github.com/tldraw/tldraw/blob/master/LICENSE.md).
This license does not permit commercial use. If you wish to use this project in commercial product or enterprise, you need to purchase a commercial license. This license does not permit commercial use. If you wish to use this project in a commercial product or enterprise, you need to purchase a commercial license.
To purchase a commercial license, or for more information, please contact us at [sales@tldraw.com](mailto:sales@tldraw.com). To purchase a commercial license, or for more information, please contact us at [sales@tldraw.com](mailto:sales@tldraw.com).

View file

@ -1,5 +1,5 @@
--- ---
title: User Interface title: User interface
status: published status: published
author: steveruizok author: steveruizok
date: 3/22/2023 date: 3/22/2023
@ -39,6 +39,27 @@ All of our user interface works by controlling the editor via its `Editor` metho
The source for these examples are available in the [tldraw repository](https://github.com/tldraw/tldraw/blob/main/apps/examples/src) or in a [sandbox here](https://stackblitz.com/github/tldraw/tldraw/tree/examples?file=src%2Findex.tsx). The source for these examples are available in the [tldraw repository](https://github.com/tldraw/tldraw/blob/main/apps/examples/src) or in a [sandbox here](https://stackblitz.com/github/tldraw/tldraw/tree/examples?file=src%2Findex.tsx).
## Events
The [Tldraw](?) component has a prop, `onUiEvent`, that the user interface will call when certain events occur.
```tsx
function Example() {
function handleEvent(name, data) {
// do something with the event
}
return <Tldraw onUiEvent={handleEvent} />
}
```
The `onUiEvent` callback is called with the name of the event as a string and an object with information about the event's source (e.g. `menu` or `context-menu`) and possibly other data specific to each event, such as the direction in an `align-shapes` event.
Note that `onUiEvent` is only called when interacting with the user interface. It is not called when running commands manually against the app, e.g. calling [Editor#alignShapes](?) will not call `onUiEvent`.
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to customize tldraw's user interface.
## Overrides ## Overrides
The content of tldraw's menus can be controlled via the `overrides` prop. This prop accepts a [TLUiOverrides](/reference/tldraw/TLUiOverrides) object, which has methods for each part of the user interface, such as the `toolbar` or `keyboardShortcutsMenu`. The content of tldraw's menus can be controlled via the `overrides` prop. This prop accepts a [TLUiOverrides](/reference/tldraw/TLUiOverrides) object, which has methods for each part of the user interface, such as the `toolbar` or `keyboardShortcutsMenu`.
@ -99,70 +120,6 @@ const myOverrides: TLUiOverrides = {
The `tools` object is a map of [TLUiToolItem](/reference/tldraw/TLUiToolItem)s, with each item keyed under its `id`. The `tools` object is a map of [TLUiToolItem](/reference/tldraw/TLUiToolItem)s, with each item keyed under its `id`.
### Toolbar and Menus
The remaining overrides are for toolbar and the various menus: the main menu, actions menu, context menu, help menu, and the keyboard shortcuts menu.
Each of these overrides accepts a method that receives the default menu schema and returns a mutated version of that schema.
```ts
const myOverrides: TLUiOverrides = {
actions(editor, actions) {
// Create a new action or replace an existing one
actions['my-new-action'] = {
id: 'my-new-action',
label: 'My new action',
readonlyOk: true,
kbd: '$u',
onSelect(source: any) {
window.alert('My new action just happened!')
},
}
return actions
},
contextMenu(editor, contextMenu, { actions }) {
const newMenuItem = menuItem(actions['my-new-action'])
const newMenuGroup = menuGroup('my-items', newMenuItem)
contextMenu.unshift(newMenuItem)
return contextMenu
},
menu(editor, menu, { actions }) {
// using the findMenuItem helper
const fileMenu = findMenuItem(menu, ['menu', 'file'])
if (fileMenu.type === 'submenu') {
// add the new item to the file menu's children
const newMenuItem = menuItem(actions['my-new-action'])
fileMenu.children.unshift(newMenuItem)
}
return menu
},
}
```
A menu schema is an array of either [submenus](/reference/tldraw/TLUiSubMenu), [groups](/reference/tldraw/TLUiMenuGroup), [items](/reference/tldraw/TLUiMenuItem), or [custom items](/reference/tldraw/TLUiCustomMenuItem). Each group or submenu may include any of the other types as its children.
The menu schema is stateful. Referencing atomic properties (such as computed values in the editor) will cause the menu to update when those values change. If you wish for a menu item to disappear from the menu, you can return `null` from the menu method. You can also provide additional options for each item, `disabled` or `checked`.
```ts
const myOverrides: TLUiOverrides = {
menu(editor, menu, { actions }) {
const selectedShapes = editor.getSelectedShapeIds().length
const newMenuGroup = menuGroup(
'my-actions',
selectedShapes > 0 ? menuItem(actions['action-a']) : null,
menuItem(actions['action-b'], { disabled: selectedShapes < 3 })
)
menu.unshift(newMenuGroup)
return menu
},
}
```
It's recommmended to explore the [default menu schema](https://github.com/tldraw/tldraw/blob/main/packages/tldraw/src/lib/ui/hooks/useMenuSchema.tsx) in order to understand how menu items work.
### Translations ### Translations
The `translations` method accepts a table of new translations. For example, if you wanted a tool to reference a key `"tools.card"`, then you should at minimum provide an english translation for this key. The `translations` method accepts a table of new translations. For example, if you wanted a tool to reference a key `"tools.card"`, then you should at minimum provide an english translation for this key.
@ -176,23 +133,3 @@ const myOverrides: TLUiOverrides = {
}, },
} }
``` ```
## Events
The [Tldraw](?) component has a prop, `onUiEvent`, that the user interface will call when certain events occur.
```tsx
function Example() {
function handleEvent(name, data) {
// do something with the event
}
return <Tldraw onUiEvent={handleEvent} />
}
```
The `onUiEvent` callback is called with the name of the event as a string and an object with information about the event's source (e.g. `menu` or `context-menu`) and possibly other data specific to each event, such as the direction in an `align-shapes` event.
Note that `onUiEvent` is only called when interacting with the user interface. It is not called when running commands manually against the app, e.g. calling [Editor#alignShapes](?) will not call `onUiEvent`.
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to customize tldraw's user interface.

View file

@ -12,71 +12,64 @@ The tldraw SDK provides a really simple way to craft infinite canvas experiences
By the end of this guide you will have made something that looks like this: By the end of this guide you will have made something that looks like this:
<Embed className="article__embed--quickstart" src="https://vite-template-five.vercel.app/" /> <Embed className="article__embed--quickstart" src="https://examples.tldraw.com/develop" />
### 1. Installation
<hr /> - Set up a React project however you normally do. [We recommend Vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project).
<ol className="ordered-list__quickstart"> - Install the tldraw library using this command:
<li>
### Installation
- Set up a React project however you normally do. [We recommend Vite](https://vitejs.dev/guide/#scaffolding-your-first-vite-project). ```bash
- Install the tldraw library using this command: npm install @tldraw/tldraw@beta
```
```bash
npm install tldraw@beta
```
</li>
<li>
### Import Styles
<br />
To import fonts and CSS for tldraw:
- Create or edit a css file called `index.css` ### 2. Import Styles
- Copy and paste this into the file:
```CSS
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Roboto+Mono:wght@400;700&display=swap");
@import url("tldraw/tldraw.css");
body { To import fonts and CSS for tldraw:
font-family: "Inter";
}
```
</li>
<li>
### Render Tldraw Component
<br />
To render the Tldraw component
- Import the `<Tldraw />` component from `tldraw` - Create or edit a css file called `index.css`
- Import the `index.css` CSS file from earlier - Copy and paste this into the file:
- Wrap the Tldraw component in a `<div>` element with the style attribute set to: `{ position: 'fixed', inset: 0 }`
<p className="">This will render a full screen canvas:</p>
```javascript ```CSS
import { Tldraw } from "tldraw"; @import url("https://fonts.googleapis.com/css2?family=Inter:wght@500;700;&display=swap");
import "./index.css"; @import url("@tldraw/tldraw/tldraw.css");
export default function App() { body {
return ( font-family: "Inter";
<div style={{ position: 'fixed', inset: 0 }}> }
<Tldraw /> ```
</div>
);
}
```
</li>
</ol>
<hr /> ### 3. Render Tldraw Component
### Next Steps To render the Tldraw component
You did it! Now that you have your canvas working, you may be wondering: what next? You can try: - Import the `<Tldraw />` component from `@tldraw/tldraw`
- Import the `index.css` CSS file from earlier
- Wrap the Tldraw component in a `<div>` element with the style attribute set to: `{ position: 'fixed', inset: 0 }`
This will render a full screen canvas:
```javascript
import { Tldraw } from "@tldraw/tldraw";
import "./index.css";
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw />
</div>
);
}
```
## Next Steps
Now that you have your canvas working, you may be wondering: what next?
You can try:
- Giving the editor a makeover by [customizing the UI](/docs/user-interface) - Giving the editor a makeover by [customizing the UI](/docs/user-interface)
- Adding your own [shapes](/docs/shapes) and [tools](/docs/tools) - Adding your own [shapes](/docs/shapes) and [tools](/docs/tools)
- Providing collaboration using [multiplayer](https://github.com/tldraw/tldraw-yjs-example) - Providing collaboration using [multiplayer](https://github.com/tldraw/tldraw-yjs-example)
We provide the above examples and more in our [examples section](/examples/basic/develop). Go build something creative and please do share it with us in our [#show-and-tell](https://discord.com/invite/SBBEVCA4PG) channel on Discord! We provide the above examples and more in our [examples section](/examples). Go build something creative and please do share it with us in our [#show-and-tell](https://discord.com/invite/SBBEVCA4PG) channel on Discord!

View file

@ -9,7 +9,7 @@
{ {
"id": "docs", "id": "docs",
"title": "Learn tldraw", "title": "Learn tldraw",
"description": "Developer documentation for tldraw.", "description": "Learn to use the tldraw SDK.",
"categories": [], "categories": [],
"sidebar_behavior": "show-links" "sidebar_behavior": "show-links"
}, },
@ -58,7 +58,8 @@
"id": "Namespace", "id": "Namespace",
"path": null "path": null
} }
] ],
"hero": null
}, },
{ {
"id": "store", "id": "store",
@ -93,7 +94,8 @@
"id": "Namespace", "id": "Namespace",
"path": null "path": null
} }
] ],
"hero": null
}, },
{ {
"id": "tldraw", "id": "tldraw",
@ -128,7 +130,8 @@
"id": "Namespace", "id": "Namespace",
"path": null "path": null
} }
] ],
"hero": null
}, },
{ {
"id": "tlschema", "id": "tlschema",
@ -163,7 +166,8 @@
"id": "Namespace", "id": "Namespace",
"path": null "path": null
} }
] ],
"hero": null
}, },
{ {
"id": "validate", "id": "validate",
@ -198,9 +202,11 @@
"id": "Namespace", "id": "Namespace",
"path": null "path": null
} }
] ],
"hero": null
} }
], ],
"sidebar_behavior": "reference" "sidebar_behavior": "reference",
"hero": null
} }
] ]

View file

@ -29,7 +29,7 @@
"dev": "concurrently \"NODE_ENV=development next dev --port=3001\" \"tsx ./watcher.ts\" --kill-others", "dev": "concurrently \"NODE_ENV=development next dev --port=3001\" \"tsx ./watcher.ts\" --kill-others",
"next-dev": "next dev", "next-dev": "next dev",
"lint": "yarn run -T tsx ../../scripts/lint.ts", "lint": "yarn run -T tsx ../../scripts/lint.ts",
"build": "yarn create-api-markdown && yarn refresh-content && next build", "build": "yarn create-api-markdown && yarn refresh-content && next build && yarn check-links",
"start": "yarn create-api-markdown && yarn refresh-content && next start", "start": "yarn create-api-markdown && yarn refresh-content && next start",
"fetch-api-source": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/fetch-api-source.ts", "fetch-api-source": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/fetch-api-source.ts",
"fetch-releases": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/fetch-releases.ts", "fetch-releases": "yarn run -T tsx --tsconfig ./tsconfig.content.json ./scripts/fetch-releases.ts",
@ -54,7 +54,6 @@
"@types/sqlite3": "^3.1.9", "@types/sqlite3": "^3.1.9",
"@types/ws": "^8.5.9", "@types/ws": "^8.5.9",
"@vercel/analytics": "^1.1.1", "@vercel/analytics": "^1.1.1",
"broken-link-checker": "^0.7.8",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
@ -87,5 +86,8 @@
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
"vectra": "^0.4.4", "vectra": "^0.4.4",
"ws": "^8.16.0" "ws": "^8.16.0"
},
"devDependencies": {
"linkinator": "^6.0.4"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,29 +1,25 @@
import { nicelog } from '@/utils/nicelog' import { nicelog } from '@/utils/nicelog'
import blc from 'broken-link-checker' import { check } from 'linkinator'
const IGNORED_URLS = ['https://twitter.com/tldraw', 'https://tldraw.com']
export async function checkBrokenLinks() { export async function checkBrokenLinks() {
nicelog('Checking broken links...') const results = await check({
const checked = new Set<string>() path: 'http://localhost:3001',
const checker = new blc.SiteChecker( recurse: true,
{ })
filterLevel: 1,
}, // All good
{ if (results.passed) return
link(result) {
if (IGNORED_URLS.includes(result.url.original)) return // There seems to be a porblem
if (checked.has(result.url.resolved)) return nicelog(
// nicelog('Checking', result.url.resolved.replace('http://localhost:3001', '')) `𐄂 Broken links detected!\n\n` +
if (result.broken) { results.links
nicelog(`BROKEN: ${result.url.resolved} on page ${result.base.resolved}`) .filter((result) => result.state !== 'OK')
} .map(
checked.add(result.url.resolved) (result, i) =>
}, `${i + 1}.\t${result.url}\n\tFrom: ${result.parent}\n\tStatus: ${result.status}`
end() { )
nicelog('done') .join('\n\n') +
}, '\n\n'
}
) )
checker.enqueue('http://localhost:3001/docs/assets', null)
} }

View file

@ -13,6 +13,7 @@ export async function createApiMarkdown() {
description: "Reference for the tldraw package's APIs (generated).", description: "Reference for the tldraw package's APIs (generated).",
categories: [], categories: [],
sidebar_behavior: 'reference', sidebar_behavior: 'reference',
hero: null,
} }
const addedCategories = new Set<string>() const addedCategories = new Set<string>()
@ -55,6 +56,7 @@ export async function createApiMarkdown() {
id: title, id: title,
path: null, path: null,
})), })),
hero: null,
}) })
addedCategories.add(categoryName) addedCategories.add(categoryName)
} }

View file

@ -8,13 +8,14 @@ const section: InputSection = {
title: 'Examples', title: 'Examples',
description: 'Code recipes for bending tldraw to your will.', description: 'Code recipes for bending tldraw to your will.',
categories: [ categories: [
{ id: 'basic', title: 'Getting Started', description: '', groups: [] }, { id: 'basic', title: 'Getting Started', description: '', groups: [], hero: null },
{ id: 'ui', title: 'UI & Theming', description: '', groups: [] }, { id: 'ui', title: 'UI & Theming', description: '', groups: [], hero: null },
{ id: 'shapes/tools', title: 'Shapes & Tools', description: '', groups: [] }, { id: 'shapes/tools', title: 'Shapes & Tools', description: '', groups: [], hero: null },
{ id: 'data/assets', title: 'Data & Assets', description: '', groups: [] }, { id: 'data/assets', title: 'Data & Assets', description: '', groups: [], hero: null },
{ id: 'editor-api', title: 'Editor API', description: '', groups: [] }, { id: 'editor-api', title: 'Editor API', description: '', groups: [], hero: null },
{ id: 'collaboration', title: 'Collaboration', description: '', groups: [] }, { id: 'collaboration', title: 'Collaboration', description: '', groups: [], hero: null },
], ],
hero: null,
sidebar_behavior: 'show-links', sidebar_behavior: 'show-links',
} }

View file

@ -166,6 +166,7 @@ export function generateSection(section: InputSection, articles: Articles, index
groups: [], groups: [],
path: `/${section.id}/ucg`, path: `/${section.id}/ucg`,
content: null, content: null,
hero: null,
}, },
] ]
@ -188,6 +189,7 @@ export function generateSection(section: InputSection, articles: Articles, index
index: i + 1, index: i + 1,
path: `/${section.id}/${inputCategory.id}`, path: `/${section.id}/${inputCategory.id}`,
content: null, content: null,
hero: null,
groups: inputCategory.groups.map(({ id }, i) => ({ groups: inputCategory.groups.map(({ id }, i) => ({
id, id,
title: id, title: id,
@ -210,6 +212,7 @@ export function generateSection(section: InputSection, articles: Articles, index
index, index,
categories, categories,
content: '', content: '',
hero: section.hero ?? null,
path: `/${section.id}`, path: `/${section.id}`,
} }
} }

View file

@ -217,7 +217,7 @@ async function addDocComment(result: Result, member: ApiItem) {
if (exampleBlocks.length) { if (exampleBlocks.length) {
result.markdown += `\n\n` result.markdown += `\n\n`
result.markdown += `##### Example\n\n` result.markdown += `<ApiHeading>Example</ApiHeading>\n\n`
for (const example of exampleBlocks) { for (const example of exampleBlocks) {
result.markdown += await MarkdownWriter.docNodeToMarkdown(member, example.content) result.markdown += await MarkdownWriter.docNodeToMarkdown(member, example.content)
} }
@ -393,7 +393,7 @@ function addTags(result: Result, member: ApiItem) {
tags.push('readonly') tags.push('readonly')
} }
tags.push(member.kind.toLowerCase()) tags.push(member.kind.toLowerCase())
result.markdown += `<Small>${tags.join(' ')}</Small>\n\n` result.markdown += `<Small>${tags.filter((t) => t.toLowerCase() !== 'none').join(' ')}</Small>\n\n`
} }
function addReferences(result: Result, member: ApiItem) { function addReferences(result: Result, member: ApiItem) {

View file

@ -19,13 +19,15 @@
--color-tint-5: rgb(144, 144, 144); --color-tint-5: rgb(144, 144, 144);
--color-tint-6: rgb(81, 81, 81); --color-tint-6: rgb(81, 81, 81);
--color-blockquote: rgb(242, 247, 255);
/* Light theme */ /* Light theme */
--color-text: #1d1d1d; --color-text: #1d1d1d;
--color-text-secondary: #666; --color-text-secondary: #666;
--color-background: #ffffff; --color-background: #ffffff;
--color-contrast: #ffffff; --color-contrast: #ffffff;
--color-accent: #2f80ed; --color-accent: #2f80ed;
--color-footer-background: #212529; --color-footer-background: hsl(240, 5%, 8%);
--color-footer-text: #fafafa; --color-footer-text: #fafafa;
--shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.12), 0px 0px 4px 0px rgba(0, 0, 0, 0.12); --shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.12), 0px 0px 4px 0px rgba(0, 0, 0, 0.12);
@ -47,7 +49,8 @@
--border-radius-menu: 4px; --border-radius-menu: 4px;
/* Sizes */ /* Sizes */
--header-height: 72px; --header-height: 80px;
--header-padding: 24px;
} }
[data-theme='dark'] { [data-theme='dark'] {
@ -57,7 +60,7 @@
--color-background: hsl(240, 5%, 8%); --color-background: hsl(240, 5%, 8%);
--color-contrast: #000; --color-contrast: #000;
--color-accent: #74b0ff; --color-accent: #74b0ff;
--color-footer-background: #0d0d0d; --color-footer-background: hsl(240, 5%, 8%);
--color-footer-text: #ccc; --color-footer-text: #ccc;
--shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.52), 0px 0px 4px 0px rgba(0, 0, 0, 0.62); --shadow-small: 0px 0px 16px -2px rgba(0, 0, 0, 0.52), 0px 0px 4px 0px rgba(0, 0, 0, 0.62);
@ -71,6 +74,8 @@
--color-tint-5: rgb(144, 144, 144); --color-tint-5: rgb(144, 144, 144);
--color-tint-6: rgb(186, 186, 186); --color-tint-6: rgb(186, 186, 186);
--color-blockquote: rgb(34, 47, 55);
/* Code colors */ /* Code colors */
--hl: #c8c5f1; --hl: #c8c5f1;
--hl-0: #5c6370; --hl-0: #5c6370;
@ -93,7 +98,7 @@
html { html {
background-color: #000; background-color: #000;
border-bottom: none; border-bottom: none;
scroll-padding-top: var(--header-height); scroll-padding-top: calc(var(--header-height) + var(--header-padding));
} }
body { body {
@ -137,7 +142,7 @@ body {
z-index: 900; z-index: 900;
top: 0px; top: 0px;
display: grid; display: grid;
padding: 16px; padding: 0px 16px;
grid-template-columns: 250px 1fr auto; grid-template-columns: 250px 1fr auto;
gap: 16px; gap: 16px;
justify-content: center; justify-content: center;
@ -145,24 +150,31 @@ body {
color: var(--color-text); color: var(--color-text);
} }
.layout__header .lockup { .layout__header__left img {
position: relative; width: calc(136px);
width: calc(71px * (30 / 18)); height: auto;
height: calc(18px * (30 / 18));
background: currentColor;
color: var(--color-text);
margin-bottom: 8px;
} }
.layout__header__sections_and_socials { .layout__header__links {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 8px;
}
.layout__header__sections {
display: flex;
align-items: center;
}
.layout__header__socials {
display: flex;
align-items: center;
} }
.layout_header__section { .layout_header__section {
text-decoration: none; text-decoration: none;
padding: 8px 12px; padding: 13px 12px;
font-size: 14px; font-size: 14px;
color: var(--color-text); color: var(--color-text);
position: relative; position: relative;
@ -172,7 +184,7 @@ body {
position: absolute; position: absolute;
display: block; display: block;
content: ''; content: '';
inset: 7px 1px; inset: 7px 2px;
background-color: var(--bg); background-color: var(--bg);
border-radius: var(--border-radius-menu); border-radius: var(--border-radius-menu);
} }
@ -241,11 +253,11 @@ body {
background-color: currentColor; background-color: currentColor;
} }
.article { .main-content {
justify-self: center; justify-self: center;
width: 100%; width: 100%;
min-height: calc(100vh - 64px); min-height: calc(100vh - 64px);
padding: 0px 0px 96px 0px; padding: var(--header-padding) 0px 96px 0px;
font-weight: 400; font-weight: 400;
overflow-x: hidden; overflow-x: hidden;
overflow-y: visible; overflow-y: visible;
@ -354,7 +366,7 @@ body {
} }
.article > h2 { .article > h2 {
margin-top: 16px; margin-top: 48px;
} }
.article > p + h2 { .article > p + h2 {
@ -468,9 +480,10 @@ body {
.article > blockquote { .article > blockquote {
max-width: 100%; max-width: 100%;
margin: 20px 0px; margin: 32px 0px;
padding-left: 16px; padding: 16px;
border-left: 2px solid var(--color-tint-2); border-radius: var(--border-radius-menu);
background-color: var(--color-blockquote);
} }
.article pre { .article pre {
@ -517,8 +530,7 @@ body {
color: var(--color-text); color: var(--color-text);
} }
.article ol h3, .article ol h3 {
.article ol li::marker {
font-size: 1.17em; font-size: 1.17em;
line-height: 28px; line-height: 28px;
} }
@ -551,7 +563,6 @@ body {
.page-header > p { .page-header > p {
margin-top: 1rem; margin-top: 1rem;
} }
.article table { .article table {
@ -624,6 +635,7 @@ body {
.article__embed--quickstart { .article__embed--quickstart {
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
min-height: 405px; min-height: 405px;
margin: 32px 0px;
} }
@media screen and (max-width: 520px) { @media screen and (max-width: 520px) {
@ -649,8 +661,8 @@ body {
.breadcrumb { .breadcrumb {
font-size: 14px; font-size: 14px;
color: var(--color-text); color: var(--color-text);
font-weight: 600; font-weight: 500;
height: 40px; padding-bottom: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
@ -675,7 +687,6 @@ body {
font-size: 12px; font-size: 12px;
gap: 24px; gap: 24px;
color: var(--color-footer-text); color: var(--color-footer-text);
border-top: 1px solid rgba(144, 144, 144, 0.28);
overflow: hidden; overflow: hidden;
} }
@ -781,7 +792,7 @@ body {
align-self: start; align-self: start;
top: var(--header-height); top: var(--header-height);
margin-left: -12px; margin-left: -12px;
padding: 24px 28px 120px 12px; padding: var(--header-padding) 28px 120px 12px;
max-height: calc(100vh); max-height: calc(100vh);
width: 290px; width: 290px;
z-index: 800; z-index: 800;
@ -815,7 +826,7 @@ body {
align-self: start; align-self: start;
top: var(--header-height); top: var(--header-height);
margin-left: -12px; margin-left: -12px;
padding: 0px 12px 120px 28px; padding: var(--header-padding) 12px 120px 28px;
width: calc(100% + 24px); width: calc(100% + 24px);
max-height: calc(100vh); max-height: calc(100vh);
z-index: 800; z-index: 800;
@ -934,18 +945,21 @@ body {
transition-delay: 0s; transition-delay: 0s;
} }
.sidebar__section__title { .uppercase_title {
text-transform: uppercase;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
letter-spacing: 0.5px;
color: var(--color-text-secondary);
}
.sidebar__section__title {
margin-bottom: 4px; margin-bottom: 4px;
position: relative; position: relative;
letter-spacing: 0.5px; padding-bottom: 4px;
height: 40px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
color: var(--color-text-secondary);
text-transform: uppercase;
--bg: transparent; --bg: transparent;
white-space: nowrap; white-space: nowrap;
} }
@ -958,7 +972,6 @@ body {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
color: var(--color-text-secondary);
white-space: nowrap; white-space: nowrap;
background: transparent; background: transparent;
border: 0; border: 0;
@ -971,7 +984,6 @@ body {
.sidebar__sections__list > *:nth-last-of-type(n + 2) > .sidebar__list { .sidebar__sections__list > *:nth-last-of-type(n + 2) > .sidebar__list {
padding-bottom: 12px; padding-bottom: 12px;
margin-bottom: 12px; margin-bottom: 12px;
border-bottom: 1px solid var(--color-tint-2);
} }
@media (hover: hover) { @media (hover: hover) {
@ -1113,8 +1125,7 @@ body {
grid-gap: 40px; grid-gap: 40px;
} }
.layout__header .layout__header__sections_and_socials .layout_header__section, .layout__header .NavigationMenuRoot {
.layout__header .layout__header__sections_and_socials .NavigationMenuRoot {
display: none; display: none;
} }
@ -1122,10 +1133,10 @@ body {
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex; display: flex;
justify-content: space-around; padding: 12px 0px;
padding: 16px 0; border-bottom: 1px solid var(--color-tint-2);
border-top: 1px solid var(--color-accent); margin-bottom: 12px;
border-bottom: 1px solid var(--color-accent); margin-left: -8px;
} }
.NavigationMenuTrigger { .NavigationMenuTrigger {
@ -1142,7 +1153,7 @@ body {
} }
.layout__header { .layout__header {
grid-template-columns: auto auto; grid-template-columns: auto 1fr;
justify-content: space-between; justify-content: space-between;
} }
@ -1227,7 +1238,7 @@ body {
display: none; display: none;
} }
.article { .main-content {
padding: 24px 16px 16px 16px; padding: 24px 16px 16px 16px;
} }
@ -1288,6 +1299,14 @@ body {
display: none; display: none;
} }
.layout__header__links {
justify-content: flex-end;
}
.layout__header__sections {
display: none;
}
.article__links__prev { .article__links__prev {
border: none; border: none;
grid-row: 2; grid-row: 2;
@ -1327,6 +1346,7 @@ html[data-theme='light'] .hero__dark {
} }
.code-example .sandpack { .code-example .sandpack {
margin-top: 20px;
margin-bottom: 32px; margin-bottom: 32px;
} }
@ -1358,12 +1378,12 @@ html[data-theme='light'] .hero__dark {
/* ------------------- Hero images ------------------ */ /* ------------------- Hero images ------------------ */
.hero__images { .hero__images__wrapper {
margin: 32px 0px; padding-bottom: 24px;
} }
.hero_images > a { .hero__images {
margin: 0px; margin: 32px 0px;
} }
.article__image { .article__image {
@ -1496,3 +1516,83 @@ html[data-theme='light'] .hero__dark {
.scroll-light::-webkit-scrollbar-thumb:hover { .scroll-light::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 144, 144); background-color: rgba(144, 144, 144);
} }
/* ------------------ Landing page ------------------ */
.landing {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.landing__inner {
width: fit-content;
max-width: 960px;
height: fit-content;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.landing__logo {
display: flex;
align-items: center;
justify-content: center;
}
.landing__logo img {
width: 200px;
height: auto;
}
[data-theme='dark'] .logo-light {
display: none;
}
[data-theme='light'] .logo-dark {
display: none;
}
.landing__blurb {
font-size: 1em;
font-weight: 500;
text-align: center;
margin: 12px 0 16px 0;
}
.landing__links {
list-style-type: none;
display: flex;
gap: 0px;
}
.landing__links a {
text-decoration: none;
color: var(--color-text);
padding: 16px 16px;
position: relative;
font-size: inherit;
font-family: inherit;
font-weight: 600;
}
.landing__links a::after {
position: absolute;
display: block;
content: '';
top: 8px;
bottom: 8px;
left: 1px;
right: 1px;
background-color: var(--color-tint-1);
border-radius: var(--border-radius-menu);
opacity: 0;
}
@media (hover: hover) {
.landing__links a:hover::after {
opacity: 1;
}
}

View file

@ -1,11 +1,3 @@
.article__api-heading {
text-transform: uppercase;
color: var(--color-tint-5);
letter-spacing: 0.07em;
font-size: 12px;
font-weight: 400;
}
.article__parameters-table { .article__parameters-table {
table-layout: fixed; table-layout: fixed;
} }

View file

@ -3,6 +3,7 @@ export type InputCategory = {
title: string title: string
description: string description: string
groups: InputGroup[] groups: InputGroup[]
hero: string | null
} }
export type InputSection = { export type InputSection = {
@ -10,6 +11,7 @@ export type InputSection = {
title: string title: string
description: string description: string
categories: InputCategory[] categories: InputCategory[]
hero: string | null
sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference' sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference'
} }
@ -56,6 +58,8 @@ export interface Section extends ContentPage {
categories: Category[] categories: Category[]
/** How the section should appear in the sidebar. */ /** How the section should appear in the sidebar. */
sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference' sidebar_behavior: 'show-links' | 'show-title' | 'hidden' | 'reference'
/** The section's hero image (optional). */
hero: string | null
} }
export interface Category extends ContentPage { export interface Category extends ContentPage {
@ -66,6 +70,8 @@ export interface Category extends ContentPage {
index: number index: number
/** The category's groups */ /** The category's groups */
groups: Group[] groups: Group[]
/** The category's hero image (optional). */
hero: string | null
} }
export interface Group extends ContentPage { export interface Group extends ContentPage {
@ -179,7 +185,6 @@ export type SidebarContentLink =
| SidebarContentArticleLink | SidebarContentArticleLink
export type SidebarContentList = { export type SidebarContentList = {
headings?: ArticleHeadings
sectionId: string | null sectionId: string | null
categoryId: string | null categoryId: string | null
articleId: string | null articleId: string | null

View file

@ -1400,7 +1400,136 @@ export const TldrawSelectionBackground: ({ bounds, rotation }: TLSelectionBackgr
export const TldrawSelectionForeground: MemoExoticComponent<({ bounds, rotation, }: TLSelectionForegroundProps) => JSX_2.Element | null>; export const TldrawSelectionForeground: MemoExoticComponent<({ bounds, rotation, }: TLSelectionForegroundProps) => JSX_2.Element | null>;
// @public (undocumented) // @public (undocumented)
export const TldrawUi: React_2.NamedExoticComponent<TldrawUiProps>; export const TldrawUi: React_2.NamedExoticComponent<{
children?: any;
hideUi?: boolean | undefined;
components?: Partial<{
ContextMenu: null | React_2.ComponentType<TLUiContextMenuProps>;
ActionsMenu: null | React_2.ComponentType<TLUiActionsMenuProps>;
HelpMenu: null | React_2.ComponentType<TLUiHelpMenuProps>;
ZoomMenu: null | React_2.ComponentType<TLUiZoomMenuProps>;
MainMenu: null | React_2.ComponentType<TLUiMainMenuProps>;
Minimap: null | React_2.ComponentType;
StylePanel: null | React_2.ComponentType<TLUiStylePanelProps>;
PageMenu: null | React_2.ComponentType;
NavigationPanel: null | React_2.ComponentType;
Toolbar: null | React_2.ComponentType;
KeyboardShortcutsDialog: null | React_2.ComponentType<TLUiKeyboardShortcutsDialogProps>;
QuickActions: null | React_2.ComponentType<TLUiQuickActionsProps>;
HelperButtons: null | React_2.ComponentType<TLUiHelperButtonsProps>;
DebugMenu: null | React_2.ComponentType;
MenuPanel: null | React_2.ComponentType;
TopPanel: null | React_2.ComponentType;
SharePanel: null | React_2.ComponentType;
}> | undefined;
renderDebugMenuItems?: (() => React_2.ReactNode) | undefined;
assetUrls?: (RecursivePartial<TLUiAssetUrls> & RecursivePartial<TLUiAssetUrls>) | undefined;
overrides?: Partial<{
actions: TLUiOverride<TLUiActionsContextType, {
addToast: (toast: Omit<TLUiToast, "id"> & {
id?: string | undefined;
}) => string;
removeToast: (id: string) => string;
clearToasts: () => void;
addDialog: (dialog: Omit<TLUiDialog, "id"> & {
id?: string | undefined;
}) => string;
clearDialogs: () => void;
removeDialog: (id: string) => string;
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
msg: (id?: string | undefined) => string;
isMobile: boolean;
}>;
toolbar: TLUiOverride<TLUiToolbarSchemaContextType, {
tools: TLUiToolsContextType;
} & {
addToast: (toast: Omit<TLUiToast, "id"> & {
id?: string | undefined;
}) => string;
removeToast: (id: string) => string;
clearToasts: () => void;
addDialog: (dialog: Omit<TLUiDialog, "id"> & {
id?: string | undefined;
}) => string;
clearDialogs: () => void;
removeDialog: (id: string) => string;
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
msg: (id?: string | undefined) => string;
isMobile: boolean;
}>;
tools: TLUiOverride<TLUiToolsContextType, {
insertMedia: () => void;
} & {
addToast: (toast: Omit<TLUiToast, "id"> & {
id?: string | undefined;
}) => string;
removeToast: (id: string) => string;
clearToasts: () => void;
addDialog: (dialog: Omit<TLUiDialog, "id"> & {
id?: string | undefined;
}) => string;
clearDialogs: () => void;
removeDialog: (id: string) => string;
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
msg: (id?: string | undefined) => string;
isMobile: boolean;
}>;
translations: Record<string, Record<string, string>> | undefined;
}> | Partial<{
actions: TLUiOverride<TLUiActionsContextType, {
addToast: (toast: Omit<TLUiToast, "id"> & {
id?: string | undefined;
}) => string;
removeToast: (id: string) => string;
clearToasts: () => void;
addDialog: (dialog: Omit<TLUiDialog, "id"> & {
id?: string | undefined;
}) => string;
clearDialogs: () => void;
removeDialog: (id: string) => string;
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
msg: (id?: string | undefined) => string;
isMobile: boolean;
}>;
toolbar: TLUiOverride<TLUiToolbarSchemaContextType, {
tools: TLUiToolsContextType;
} & {
addToast: (toast: Omit<TLUiToast, "id"> & {
id?: string | undefined;
}) => string;
removeToast: (id: string) => string;
clearToasts: () => void;
addDialog: (dialog: Omit<TLUiDialog, "id"> & {
id?: string | undefined;
}) => string;
clearDialogs: () => void;
removeDialog: (id: string) => string;
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
msg: (id?: string | undefined) => string;
isMobile: boolean;
}>;
tools: TLUiOverride<TLUiToolsContextType, {
insertMedia: () => void;
} & {
addToast: (toast: Omit<TLUiToast, "id"> & {
id?: string | undefined;
}) => string;
removeToast: (id: string) => string;
clearToasts: () => void;
addDialog: (dialog: Omit<TLUiDialog, "id"> & {
id?: string | undefined;
}) => string;
clearDialogs: () => void;
removeDialog: (id: string) => string;
updateDialog: (id: string, newDialogData: Partial<TLUiDialog>) => string;
msg: (id?: string | undefined) => string;
isMobile: boolean;
}>;
translations: Record<string, Record<string, string>> | undefined;
}>[] | undefined;
onUiEvent?: TLUiEventHandler | undefined;
forceMobile?: boolean | undefined;
}>;
// @public // @public
export interface TldrawUiBaseProps { export interface TldrawUiBaseProps {
@ -1518,7 +1647,7 @@ export function TldrawUiPopoverContent({ side, children, align, sideOffset, alig
export function TldrawUiPopoverTrigger({ children }: TLUiPopoverTriggerProps): JSX_2.Element; export function TldrawUiPopoverTrigger({ children }: TLUiPopoverTriggerProps): JSX_2.Element;
// @public // @public
export type TldrawUiProps = TldrawUiBaseProps & TldrawUiContextProviderProps; export type TldrawUiProps = Expand<TldrawUiBaseProps & TldrawUiContextProviderProps>;
// @internal (undocumented) // @internal (undocumented)
export const TldrawUiSlider: NamedExoticComponent<TLUiSliderProps>; export const TldrawUiSlider: NamedExoticComponent<TLUiSliderProps>;

View file

@ -16204,16 +16204,808 @@
}, },
{ {
"kind": "Content", "kind": "Content",
"text": "<" "text": "<{\n children?: any;\n hideUi?: boolean | undefined;\n components?: "
}, },
{ {
"kind": "Reference", "kind": "Reference",
"text": "TldrawUiProps", "text": "Partial",
"canonicalReference": "tldraw!TldrawUiProps:type" "canonicalReference": "!Partial:type"
}, },
{ {
"kind": "Content", "kind": "Content",
"text": ">" "text": "<{\n ContextMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiContextMenuProps",
"canonicalReference": "tldraw!TLUiContextMenuProps:interface"
},
{
"kind": "Content",
"text": ">;\n ActionsMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiActionsMenuProps",
"canonicalReference": "tldraw!TLUiActionsMenuProps:type"
},
{
"kind": "Content",
"text": ">;\n HelpMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiHelpMenuProps",
"canonicalReference": "tldraw!TLUiHelpMenuProps:type"
},
{
"kind": "Content",
"text": ">;\n ZoomMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiZoomMenuProps",
"canonicalReference": "tldraw!TLUiZoomMenuProps:type"
},
{
"kind": "Content",
"text": ">;\n MainMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiMainMenuProps",
"canonicalReference": "tldraw!TLUiMainMenuProps:type"
},
{
"kind": "Content",
"text": ">;\n Minimap: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n StylePanel: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiStylePanelProps",
"canonicalReference": "tldraw!TLUiStylePanelProps:interface"
},
{
"kind": "Content",
"text": ">;\n PageMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n NavigationPanel: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n Toolbar: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n KeyboardShortcutsDialog: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiKeyboardShortcutsDialogProps",
"canonicalReference": "tldraw!TLUiKeyboardShortcutsDialogProps:type"
},
{
"kind": "Content",
"text": ">;\n QuickActions: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiQuickActionsProps",
"canonicalReference": "tldraw!TLUiQuickActionsProps:type"
},
{
"kind": "Content",
"text": ">;\n HelperButtons: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": "<import(\"../..\")."
},
{
"kind": "Reference",
"text": "TLUiHelperButtonsProps",
"canonicalReference": "tldraw!TLUiHelperButtonsProps:type"
},
{
"kind": "Content",
"text": ">;\n DebugMenu: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n MenuPanel: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n TopPanel: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n SharePanel: null | "
},
{
"kind": "Reference",
"text": "React.ComponentType",
"canonicalReference": "@types/react!React.ComponentType:type"
},
{
"kind": "Content",
"text": ";\n }> | undefined;\n renderDebugMenuItems?: (() => "
},
{
"kind": "Reference",
"text": "React.ReactNode",
"canonicalReference": "@types/react!React.ReactNode:type"
},
{
"kind": "Content",
"text": ") | undefined;\n assetUrls?: (import(\"@tldraw/editor\")."
},
{
"kind": "Reference",
"text": "RecursivePartial",
"canonicalReference": "@tldraw/utils!RecursivePartial:type"
},
{
"kind": "Content",
"text": "<import(\"./assetUrls\")."
},
{
"kind": "Reference",
"text": "TLUiAssetUrls",
"canonicalReference": "tldraw!~TLUiAssetUrls:type"
},
{
"kind": "Content",
"text": "> & import(\"@tldraw/editor\")."
},
{
"kind": "Reference",
"text": "RecursivePartial",
"canonicalReference": "@tldraw/utils!RecursivePartial:type"
},
{
"kind": "Content",
"text": "<import(\"./assetUrls\")."
},
{
"kind": "Reference",
"text": "TLUiAssetUrls",
"canonicalReference": "tldraw!~TLUiAssetUrls:type"
},
{
"kind": "Content",
"text": ">) | undefined;\n overrides?: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<{\n actions: import(\"./overrides\")."
},
{
"kind": "Reference",
"text": "TLUiOverride",
"canonicalReference": "tldraw!~TLUiOverride:type"
},
{
"kind": "Content",
"text": "<import(\"./context/actions\")."
},
{
"kind": "Reference",
"text": "TLUiActionsContextType",
"canonicalReference": "tldraw!TLUiActionsContextType:type"
},
{
"kind": "Content",
"text": ", {\n addToast: (toast: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/toasts\")."
},
{
"kind": "Reference",
"text": "TLUiToast",
"canonicalReference": "tldraw!TLUiToast:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n removeToast: (id: string) => string;\n clearToasts: () => void;\n addDialog: (dialog: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n clearDialogs: () => void;\n removeDialog: (id: string) => string;\n updateDialog: (id: string, newDialogData: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ">) => string;\n msg: (id?: string | undefined) => string;\n isMobile: boolean;\n }>;\n toolbar: import(\"./overrides\")."
},
{
"kind": "Reference",
"text": "TLUiOverride",
"canonicalReference": "tldraw!~TLUiOverride:type"
},
{
"kind": "Content",
"text": "<import(\"./hooks/useToolbarSchema\")."
},
{
"kind": "Reference",
"text": "TLUiToolbarSchemaContextType",
"canonicalReference": "tldraw!TLUiToolbarSchemaContextType:type"
},
{
"kind": "Content",
"text": ", {\n tools: import(\"./hooks/useTools\")."
},
{
"kind": "Reference",
"text": "TLUiToolsContextType",
"canonicalReference": "tldraw!TLUiToolsContextType:type"
},
{
"kind": "Content",
"text": ";\n } & {\n addToast: (toast: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/toasts\")."
},
{
"kind": "Reference",
"text": "TLUiToast",
"canonicalReference": "tldraw!TLUiToast:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n removeToast: (id: string) => string;\n clearToasts: () => void;\n addDialog: (dialog: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n clearDialogs: () => void;\n removeDialog: (id: string) => string;\n updateDialog: (id: string, newDialogData: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ">) => string;\n msg: (id?: string | undefined) => string;\n isMobile: boolean;\n }>;\n tools: import(\"./overrides\")."
},
{
"kind": "Reference",
"text": "TLUiOverride",
"canonicalReference": "tldraw!~TLUiOverride:type"
},
{
"kind": "Content",
"text": "<import(\"./hooks/useTools\")."
},
{
"kind": "Reference",
"text": "TLUiToolsContextType",
"canonicalReference": "tldraw!TLUiToolsContextType:type"
},
{
"kind": "Content",
"text": ", {\n insertMedia: () => void;\n } & {\n addToast: (toast: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/toasts\")."
},
{
"kind": "Reference",
"text": "TLUiToast",
"canonicalReference": "tldraw!TLUiToast:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n removeToast: (id: string) => string;\n clearToasts: () => void;\n addDialog: (dialog: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n clearDialogs: () => void;\n removeDialog: (id: string) => string;\n updateDialog: (id: string, newDialogData: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ">) => string;\n msg: (id?: string | undefined) => string;\n isMobile: boolean;\n }>;\n translations: "
},
{
"kind": "Reference",
"text": "Record",
"canonicalReference": "!Record:type"
},
{
"kind": "Content",
"text": "<string, "
},
{
"kind": "Reference",
"text": "Record",
"canonicalReference": "!Record:type"
},
{
"kind": "Content",
"text": "<string, string>> | undefined;\n }> | "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<{\n actions: import(\"./overrides\")."
},
{
"kind": "Reference",
"text": "TLUiOverride",
"canonicalReference": "tldraw!~TLUiOverride:type"
},
{
"kind": "Content",
"text": "<import(\"./context/actions\")."
},
{
"kind": "Reference",
"text": "TLUiActionsContextType",
"canonicalReference": "tldraw!TLUiActionsContextType:type"
},
{
"kind": "Content",
"text": ", {\n addToast: (toast: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/toasts\")."
},
{
"kind": "Reference",
"text": "TLUiToast",
"canonicalReference": "tldraw!TLUiToast:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n removeToast: (id: string) => string;\n clearToasts: () => void;\n addDialog: (dialog: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n clearDialogs: () => void;\n removeDialog: (id: string) => string;\n updateDialog: (id: string, newDialogData: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ">) => string;\n msg: (id?: string | undefined) => string;\n isMobile: boolean;\n }>;\n toolbar: import(\"./overrides\")."
},
{
"kind": "Reference",
"text": "TLUiOverride",
"canonicalReference": "tldraw!~TLUiOverride:type"
},
{
"kind": "Content",
"text": "<import(\"./hooks/useToolbarSchema\")."
},
{
"kind": "Reference",
"text": "TLUiToolbarSchemaContextType",
"canonicalReference": "tldraw!TLUiToolbarSchemaContextType:type"
},
{
"kind": "Content",
"text": ", {\n tools: import(\"./hooks/useTools\")."
},
{
"kind": "Reference",
"text": "TLUiToolsContextType",
"canonicalReference": "tldraw!TLUiToolsContextType:type"
},
{
"kind": "Content",
"text": ";\n } & {\n addToast: (toast: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/toasts\")."
},
{
"kind": "Reference",
"text": "TLUiToast",
"canonicalReference": "tldraw!TLUiToast:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n removeToast: (id: string) => string;\n clearToasts: () => void;\n addDialog: (dialog: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n clearDialogs: () => void;\n removeDialog: (id: string) => string;\n updateDialog: (id: string, newDialogData: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ">) => string;\n msg: (id?: string | undefined) => string;\n isMobile: boolean;\n }>;\n tools: import(\"./overrides\")."
},
{
"kind": "Reference",
"text": "TLUiOverride",
"canonicalReference": "tldraw!~TLUiOverride:type"
},
{
"kind": "Content",
"text": "<import(\"./hooks/useTools\")."
},
{
"kind": "Reference",
"text": "TLUiToolsContextType",
"canonicalReference": "tldraw!TLUiToolsContextType:type"
},
{
"kind": "Content",
"text": ", {\n insertMedia: () => void;\n } & {\n addToast: (toast: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/toasts\")."
},
{
"kind": "Reference",
"text": "TLUiToast",
"canonicalReference": "tldraw!TLUiToast:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n removeToast: (id: string) => string;\n clearToasts: () => void;\n addDialog: (dialog: "
},
{
"kind": "Reference",
"text": "Omit",
"canonicalReference": "!Omit:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ", \"id\"> & {\n id?: string | undefined;\n }) => string;\n clearDialogs: () => void;\n removeDialog: (id: string) => string;\n updateDialog: (id: string, newDialogData: "
},
{
"kind": "Reference",
"text": "Partial",
"canonicalReference": "!Partial:type"
},
{
"kind": "Content",
"text": "<import(\"./context/dialogs\")."
},
{
"kind": "Reference",
"text": "TLUiDialog",
"canonicalReference": "tldraw!TLUiDialog:interface"
},
{
"kind": "Content",
"text": ">) => string;\n msg: (id?: string | undefined) => string;\n isMobile: boolean;\n }>;\n translations: "
},
{
"kind": "Reference",
"text": "Record",
"canonicalReference": "!Record:type"
},
{
"kind": "Content",
"text": "<string, "
},
{
"kind": "Reference",
"text": "Record",
"canonicalReference": "!Record:type"
},
{
"kind": "Content",
"text": "<string, string>> | undefined;\n }>[] | undefined;\n onUiEvent?: import(\"./context/events\")."
},
{
"kind": "Reference",
"text": "TLUiEventHandler",
"canonicalReference": "tldraw!TLUiEventHandler:type"
},
{
"kind": "Content",
"text": " | undefined;\n forceMobile?: boolean | undefined;\n}>"
} }
], ],
"fileUrlPath": "packages/tldraw/src/lib/ui/TldrawUi.tsx", "fileUrlPath": "packages/tldraw/src/lib/ui/TldrawUi.tsx",
@ -16222,7 +17014,7 @@
"name": "TldrawUi", "name": "TldrawUi",
"variableTypeTokenRange": { "variableTypeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 5 "endIndex": 181
} }
}, },
{ {
@ -18325,12 +19117,21 @@
{ {
"kind": "TypeAlias", "kind": "TypeAlias",
"canonicalReference": "tldraw!TldrawUiProps:type", "canonicalReference": "tldraw!TldrawUiProps:type",
"docComment": "/**\n * Props for the {@link tldraw#Tldraw} and {@link TldrawUi} components.\n *\n * @public\n */\n", "docComment": "/**\n * Props for the {@link @tldraw/tldraw#Tldraw} and {@link TldrawUi} components.\n *\n * @public\n */\n",
"excerptTokens": [ "excerptTokens": [
{ {
"kind": "Content", "kind": "Content",
"text": "export type TldrawUiProps = " "text": "export type TldrawUiProps = "
}, },
{
"kind": "Reference",
"text": "Expand",
"canonicalReference": "@tldraw/utils!Expand:type"
},
{
"kind": "Content",
"text": "<"
},
{ {
"kind": "Reference", "kind": "Reference",
"text": "TldrawUiBaseProps", "text": "TldrawUiBaseProps",
@ -18345,6 +19146,10 @@
"text": "TldrawUiContextProviderProps", "text": "TldrawUiContextProviderProps",
"canonicalReference": "tldraw!TldrawUiContextProviderProps:interface" "canonicalReference": "tldraw!TldrawUiContextProviderProps:interface"
}, },
{
"kind": "Content",
"text": ">"
},
{ {
"kind": "Content", "kind": "Content",
"text": ";" "text": ";"
@ -18355,7 +19160,7 @@
"name": "TldrawUiProps", "name": "TldrawUiProps",
"typeTokenRange": { "typeTokenRange": {
"startIndex": 1, "startIndex": 1,
"endIndex": 4 "endIndex": 7
} }
}, },
{ {

View file

@ -1,5 +1,5 @@
import { ToastProvider } from '@radix-ui/react-toast' import { ToastProvider } from '@radix-ui/react-toast'
import { useEditor, useValue } from '@tldraw/editor' import { Expand, useEditor, useValue } from '@tldraw/editor'
import classNames from 'classnames' import classNames from 'classnames'
import React, { ReactNode } from 'react' import React, { ReactNode } from 'react'
import { TLUiAssetUrlOverrides } from './assetUrls' import { TLUiAssetUrlOverrides } from './assetUrls'
@ -23,13 +23,6 @@ import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
import { useReadonly } from './hooks/useReadonly' import { useReadonly } from './hooks/useReadonly'
import { useTranslation } from './hooks/useTranslation/useTranslation' import { useTranslation } from './hooks/useTranslation/useTranslation'
/**
* Props for the {@link tldraw#Tldraw} and {@link TldrawUi} components.
*
* @public
*/
export type TldrawUiProps = TldrawUiBaseProps & TldrawUiContextProviderProps
/** /**
* Base props for the {@link tldraw#Tldraw} and {@link TldrawUi} components. * Base props for the {@link tldraw#Tldraw} and {@link TldrawUi} components.
* *
@ -60,6 +53,13 @@ export interface TldrawUiBaseProps {
assetUrls?: TLUiAssetUrlOverrides assetUrls?: TLUiAssetUrlOverrides
} }
/**
* Props for the {@link @tldraw/tldraw#Tldraw} and {@link TldrawUi} components.
*
* @public
*/
export type TldrawUiProps = Expand<TldrawUiBaseProps & TldrawUiContextProviderProps>
/** /**
* @public * @public
*/ */

791
yarn.lock

File diff suppressed because it is too large Load diff