Skip to content

Commit

Permalink
Implement anchor links for custom markdown page headings (#13)
Browse files Browse the repository at this point in the history
* Init base components

* Start reusing base components between Markdown and MdxContent

* Reuse code blocks between renderers

* Add P to base components

* Fully unify the styles of both renderers

* Fix anchor positioning

* Make sure markdown page headings have anchor links
  • Loading branch information
kafkas authored Jul 31, 2023
1 parent fd39a2e commit 95bae6d
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export declare namespace EndpointParameter {

export const EndpointParameter: React.FC<EndpointParameter.Props> = ({ name, description, anchor, type }) => {
return (
<div id={anchor} className="group relative flex flex-col gap-2 py-3">
<div id={anchor} className="group/anchor-container relative flex flex-col gap-2 py-3">
{anchor != null && <AbsolutelyPositionedAnchor verticalPosition="default" anchor={anchor} />}
<div className="flex items-baseline gap-1">
<MonospaceText>{name}</MonospaceText>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/endpoints/EndpointSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export declare namespace EndpointSection {
export const EndpointSection: React.FC<EndpointSection.Props> = ({ title, description, anchor, children }) => {
return (
<div id={anchor} className="flex flex-col">
<div className="group relative mb-3 flex items-center">
<div className="group/anchor-container relative mb-3 flex items-center">
<AbsolutelyPositionedAnchor anchor={anchor} verticalPosition="center" />
<div className="text-lg font-medium">{title}</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const ObjectProperty: React.FC<ObjectProperty.Props> = ({ anchor, propert
return (
<div
id={anchor}
className={classNames("flex relative flex-col py-3 group", {
className={classNames("flex relative flex-col py-3 group/anchor-container", {
"px-3": !contextValue.isRootTypeDefinition,
})}
>
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/commons/AbsolutelyPositionedAnchor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ export const AbsolutelyPositionedAnchor: React.FC<AbsolutelyPositionedAnchor.Pro
return (
<div
className={classNames(
"absolute -left-[calc(0.875rem+0.375rem*2)] flex items-center justify-center px-1.5 py-1 opacity-0 hover:opacity-100 group-hover:opacity-100",
"absolute -left-[calc(0.875rem+0.375rem*2)] flex items-center justify-center px-1.5 py-1 opacity-0 hover:opacity-100 group-hover/anchor-container:opacity-100",
{
"top-2.5": verticalPosition === "default",
"top-auto bottom-auto": verticalPosition === "center",
"top-0 bottom-0": verticalPosition === "center",
}
)}
>
Expand Down
100 changes: 93 additions & 7 deletions packages/ui/app/src/mdx/base-components.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import classNames from "classnames";
import { HTMLAttributes } from "react";
import React, { HTMLAttributes } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { AbsolutelyPositionedAnchor } from "../commons/AbsolutelyPositionedAnchor";

export const CodeBlock: React.FC<HTMLAttributes<HTMLElement>> = ({ children }) => {
if (children == null || typeof children !== "object") {
Expand Down Expand Up @@ -86,28 +87,113 @@ export const Td: React.FC<HTMLAttributes<HTMLTableCellElement>> = ({ className,
);
};

/**
* @see https://github.com/remarkjs/react-markdown/issues/404#issuecomment-604019030
*/
const flatten = (
text: string,
child: string | number | React.ReactElement | React.ReactFragment | React.ReactPortal
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): any => {
return typeof child === "string"
? text + child
: React.Children.toArray((child as React.ReactElement).props.children).reduce(flatten, text);
};

export const H1: React.FC<HTMLAttributes<HTMLHeadingElement>> = ({ className, ...rest }) => {
return <h1 {...rest} className={classNames(className, "text-2xl font-semibold mt-10 mb-3")} />;
const children = React.Children.toArray(rest.children);
const text = children.reduce(flatten, "");
const slug = text.toLowerCase().replace(/\W/g, "-");
return (
<h1
id={slug}
className={classNames(className, "relative group/anchor-container text-2xl font-semibold mt-10 mb-3")}
{...rest}
>
<AbsolutelyPositionedAnchor anchor={slug} verticalPosition="center" />
<span>{children}</span>
</h1>
);
};

export const H2: React.FC<HTMLAttributes<HTMLHeadingElement>> = ({ className, ...rest }) => {
return <h2 {...rest} className={classNames(className, "text-xl font-semibold mt-10 mb-3")} />;
const children = React.Children.toArray(rest.children);
const text = children.reduce(flatten, "");
const slug = text.toLowerCase().replace(/\W/g, "-");
return (
<h2
id={slug}
className={classNames(className, "relative group/anchor-container text-xl font-semibold mt-10 mb-3")}
{...rest}
>
<AbsolutelyPositionedAnchor anchor={slug} verticalPosition="center" />
{children}
</h2>
);
};

export const H3: React.FC<HTMLAttributes<HTMLHeadingElement>> = ({ className, ...rest }) => {
return <h3 {...rest} className={classNames(className, "text-lg font-semibold mt-10 mb-3")} />;
const children = React.Children.toArray(rest.children);
const text = children.reduce(flatten, "");
const slug = text.toLowerCase().replace(/\W/g, "-");
return (
<h3
id={slug}
className={classNames(className, "relative group/anchor-container text-lg font-semibold mt-10 mb-3")}
{...rest}
>
<AbsolutelyPositionedAnchor anchor={slug} verticalPosition="center" />
{children}
</h3>
);
};

export const H4: React.FC<HTMLAttributes<HTMLHeadingElement>> = ({ className, ...rest }) => {
return <h4 {...rest} className={classNames(className, "text-lg font-semibold mt-10 mb-3")} />;
const children = React.Children.toArray(rest.children);
const text = children.reduce(flatten, "");
const slug = text.toLowerCase().replace(/\W/g, "-");
return (
<h4
id={slug}
className={classNames(className, "relative group/anchor-container text-lg font-semibold mt-10 mb-3")}
{...rest}
>
<AbsolutelyPositionedAnchor anchor={slug} verticalPosition="center" />
{children}
</h4>
);
};

export const H5: React.FC<HTMLAttributes<HTMLHeadingElement>> = ({ className, ...rest }) => {
return <h5 {...rest} className={classNames(className, "text-lg font-semibold mt-10 mb-3")} />;
const children = React.Children.toArray(rest.children);
const text = children.reduce(flatten, "");
const slug = text.toLowerCase().replace(/\W/g, "-");
return (
<h5
id={slug}
className={classNames(className, "relative group/anchor-container text-lg font-semibold mt-10 mb-3")}
{...rest}
>
<AbsolutelyPositionedAnchor anchor={slug} verticalPosition="center" />
{children}
</h5>
);
};

export const H6: React.FC<HTMLAttributes<HTMLHeadingElement>> = ({ className, ...rest }) => {
return <h6 {...rest} className={classNames(className, "text-lg font-semibold mt-10 mb-3")} />;
const children = React.Children.toArray(rest.children);
const text = children.reduce(flatten, "");
const slug = text.toLowerCase().replace(/\W/g, "-");
return (
<h6
id={slug}
className={classNames(className, "relative group/anchor-container text-lg font-semibold mt-10 mb-3")}
{...rest}
>
<AbsolutelyPositionedAnchor anchor={slug} verticalPosition="center" />
{children}
</h6>
);
};

export const P: React.FC<{ variant: "sm" | "md" } & HTMLAttributes<HTMLParagraphElement>> = ({
Expand Down

0 comments on commit 95bae6d

Please sign in to comment.