Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] Basic API reference tables (hacked tooltips) #814

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import withDocsInfra from '@mui/monorepo/docs/nextConfigDocsInfra.js';
import nextMdx from '@next/mdx';
import rehypePrettyCode from 'rehype-pretty-code';
import rehypeExtractToc from '@stefanprobst/rehype-extract-toc';
import remarkGfm from 'remark-gfm';
import { rehypeDemos } from './src/components/demo/rehypeDemos.mjs';
import { highlighter } from './src/syntax-highlighting/index.mjs';
import { rehypeInlineCode } from './src/syntax-highlighting/rehype-inline-code.mjs';
Expand All @@ -19,18 +20,19 @@ const getHighlighter = () => highlighter;

const withMdx = nextMdx({
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeDemos,
rehypeInlineCode,
[
rehypePrettyCode,
{
getHighlighter,
theme: 'base-ui-theme',
bypassInlineCode: true,
grid: false,
theme: 'base-ui',
defaultLang: 'jsx',
},
],
rehypeInlineCode,
rehypeSlug,
rehypeExtractToc,
rehypeQuickNav,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/app/(content)/getMarkdownPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const getMarkdownPage = async (basePath: string, slug: string) => {
rehypePrettyCode,
{
getHighlighter: () => highlighter,
theme: 'base-ui-theme',
theme: 'base-ui',
bypassInlineCode: true,
grid: false,
},
Expand Down
58 changes: 54 additions & 4 deletions docs/src/app/new/(content)/components/dialog/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,60 @@

<Demo path="./UnstyledDialogIntroduction" />

## API reference

Import the component and place its parts the following way.

```jsx title="Anatomy"
import { Dialog } from '@base_ui/react/Dialog';

<Dialog.Root>
<Dialog.Trigger />
<Dialog.Backdrop />
<Dialog.Popup>
<Dialog.Title />
<Dialog.Description />
<Dialog.Close />
</Dialog.Popup>
</Dialog.Root>;
```

### Root

Groups all parts of the dialog. Doesn’t render its own HTML element.

<PropsTable component="Dialog.Root" />

### Trigger

A button that opens the dialog. Renders a `<button>` element.

<PropsTable component="Dialog.Trigger" />

### Backdrop

An overlay displayed beneath the popup. Renders a `<div>` element.

<PropsTable component="Dialog.Backdrop" />

### Popup

A component that groups the dialog contents. Renders a `<div>` element.

<PropsTable component="Dialog.Popup" />

### Title

A heading that labels the dialog. Renders an `<h2>` element.

<PropsTable component="Dialog.Title" />

### Description

A paragraph with additional information about the dialog. Renders a `<p>` element.

<PropsTable component="Dialog.Description" />

## Examples

### State
Expand Down Expand Up @@ -57,7 +111,3 @@ It’s also common to use `onOpenChange` if your app needs to do something when
}}
>
```

### Syntax

The `<Dialog.Trigger>` part renders a native `<button>` element. Don’t take this paragraph too seriously though, I’m just testing the inline syntax highlighting.
4 changes: 4 additions & 0 deletions docs/src/components/Code.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.Code {
margin-left: 0.1em;
margin-right: 0.1em;
}
6 changes: 6 additions & 0 deletions docs/src/components/Code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import clsx from 'clsx';
import * as React from 'react';

export function Code({ className, ...props }: React.ComponentProps<'code'>) {
return <code className={clsx('Code', className)} {...props} />;
}
39 changes: 39 additions & 0 deletions docs/src/components/Popup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@layer components {
.Popup {
max-width: var(--available-width);
max-height: var(--available-height);
border-radius: 0.375rem;
background-color: var(--color-popup);
overflow: hidden;
box-shadow:
0px 154px 62px 0px rgba(35, 39, 52, 0.01),
0px 87px 52px 0px rgba(35, 39, 52, 0.03),
0px 39px 39px 0px rgba(35, 39, 52, 0.04),
0px 10px 21px 0px rgba(35, 39, 52, 0.05);

outline: 1px solid var(--color-popup-border);
@media (prefers-color-scheme: dark) {
outline-offset: -1px;
}

transform-origin: var(--transform-origin);
transition-property: opacity, transform;
transition-timing-function: cubic-bezier(0.3, 1.065, 0.01, 0.975);
transition-duration: 220ms;

&[data-open] {
opacity: 1;
transform: scale(1);
}

&,
&[data-entering] {
opacity: 0;
transform: scale(0.98);
}

&[data-instant] {
transition-duration: 0s;
}
}
}
6 changes: 6 additions & 0 deletions docs/src/components/Popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react';
import clsx from 'clsx';

export function Popup({ className, ...props }: React.ComponentProps<'div'>) {
return <div className={clsx('Popup', className)} {...props} />;
}
77 changes: 77 additions & 0 deletions docs/src/components/PropsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react';
import * as jsxRuntime from 'react/jsx-runtime';
import { evaluate, EvaluateOptions } from '@mdx-js/mdx';
import rehypePrettyCode from 'rehype-pretty-code';
import { highlighter } from 'docs/src/syntax-highlighting';
import { getApiReferenceData } from 'docs/src/app/(content)/components/[slug]/getApiReferenceData';
import { rehypeInlineCode } from 'docs/src/syntax-highlighting/rehype-inline-code.mjs';
import * as Table from './Table';
import { PropsTableTooltip } from './PropsTableTooltip';

const getHighlighter = () => highlighter;

interface PropsTableProps extends React.ComponentProps<typeof Table.Root> {
component: string;
}

export async function PropsTable({ component, ...props }: PropsTableProps) {
const [data] = await getApiReferenceData([component]);

return (
<Table.Root {...props}>
<Table.Head>
<Table.Row>
<Table.HeaderCell className="w-[172px]">Prop</Table.HeaderCell>
<Table.HeaderCell className="w-full">Type</Table.HeaderCell>
<Table.HeaderCell className="w-[172px]">Default</Table.HeaderCell>
<Table.HeaderCell className="w-[36px]" aria-label="Description" />
</Table.Row>
</Table.Head>
<Table.Body>
{data.props.map(async (prop) => {
// TODO this is because rehypePrettyCode can't parse `<code>`
// written verbatim; I plan to figure out how to get the real markdown source in here.
prop.description = prop.description.replace('<code>', '`').replace('</code>', '`');
const { default: Description } = await evaluate(prop.description, {
...jsxRuntime,
rehypePlugins: [
rehypeInlineCode,
[
rehypePrettyCode,
{
getHighlighter,
grid: false,
theme: 'base-ui',
defaultLang: 'jsx',
},
],
],
} as unknown as EvaluateOptions);

return (
<Table.Row key={prop.name}>
<Table.HeaderCell scope="row">
<code>{prop.name}</code>
</Table.HeaderCell>
<Table.Cell>
<code className="text-violet">{prop.type.name}</code>
</Table.Cell>
<Table.Cell>
{prop.defaultValue ? (
<code className="text-blue">{prop.defaultValue}</code>
) : (
<code className="text-pale">undefined</code>
)}
</Table.Cell>
<Table.Cell>
<PropsTableTooltip>
<Description />
</PropsTableTooltip>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table.Root>
);
}
98 changes: 98 additions & 0 deletions docs/src/components/PropsTableTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use client';
import { Tooltip } from '@base_ui/react/Tooltip';
import * as React from 'react';
import { ToolbarButton } from './ToolbarButton';
import { Popup } from './Popup';

export function PropsTableTooltip({ children }: React.PropsWithChildren) {
const [open, setOpen] = React.useState(false);

return (
// TODO: this was more effort than I expected, we need to chat about popovers that open on hover
<Tooltip.Root
delay={0}
closeDelay={50}
open={open}
onOpenChange={(newOpen, event, reason) => {
// Open/close on click like popovers
if (reason === 'reference-press' && open === newOpen) {
setOpen(!newOpen);
return;
}

setOpen(newOpen);
}}
>
<Tooltip.Trigger
render={
// TODO: rework this into an IconButton or a generic ghost button component
<ToolbarButton
role="group"
aria-label={getTextContents(children)}
onKeyDown={(event) => {
// Open/close on enter/spacebar like popovers
if (event.key === 'Enter' || event.key === ' ') {
setOpen((currentOpen) => !currentOpen);
}
}}
>
<span className="-mx-0.5 flex h-4 w-4 items-center justify-center">
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="currentcolor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7 0.5C3.41797 0.5 0.5 3.41797 0.5 7C0.5 10.582 3.41797 13.5 7 13.5C10.582 13.5 13.5 10.582 13.5 7C13.5 3.41797 10.582 0.5 7 0.5ZM7 1.5C10.043 1.5 12.5 3.95703 12.5 7C12.5 10.043 10.043 12.5 7 12.5C3.95703 12.5 1.5 10.043 1.5 7C1.5 3.95703 3.95703 1.5 7 1.5ZM6.5 3.5V4.5H7.5V3.5H6.5ZM6.5 6.5V9.5H5.5V10.5H6.5H7.5H8.5V9.5H7.5V6.5V5.5H6.5H5.5V6.5H6.5Z"
/>
</svg>
</span>
</ToolbarButton>
}
/>
<Tooltip.Positioner
alignment="start"
side="left"
alignmentOffset={4}
sideOffset={4}
collisionPadding={16}
>
<Tooltip.Popup render={<Popup className="p-4 text-sm" />}>
<div className="max-w-[300px]">{children}</div>
</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Root>
);
}

function getTextContents(node?: React.ReactNode): string {
if (hasChildren(node)) {
return getTextContents(node.props?.children);
}

if (Array.isArray(node)) {
return node.map(getTextContents).flat().filter(Boolean).join('');
}

if (typeof node === 'string') {
return node;
}

return '';
}

function hasChildren(
element?: React.ReactNode,
): element is React.ReactElement<React.PropsWithChildren> {
return (
React.isValidElement(element) &&
typeof element.props === 'object' &&
!!element.props &&
'children' in element.props &&
Boolean(element.props.children)
);
}
35 changes: 35 additions & 0 deletions docs/src/components/Table.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@layer components {
.TableRoot {
@apply text-sm;
table-layout: fixed;
width: 100%;
}

.TableHeaderCell {
text-align: initial;
font-weight: normal;

&[scope='col'],
&[scope='colgroup'] {
font-weight: 500;
}
}

.TableCell,
.TableHeaderCell {
padding-top: 0.625rem;
padding-bottom: 0.625rem;
padding-left: 1rem;
padding-right: 1rem;

/* Not `border` because Safari renders transparent borders with glitches */
box-shadow: 0 1px var(--color-border);

&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
}
Loading
Loading