Skip to content

Commit

Permalink
feat: add optional copy button to the code block
Browse files Browse the repository at this point in the history
  • Loading branch information
yanceyy committed Apr 11, 2024
1 parent 7e30fd2 commit 8e9e187
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 8 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ import "./style.css";
/>;
```

## API
| Property | Description | Type | Default |
|-----------------|----------------------------------------------------------|-----------|---------------------------------------------|
| block | Receives render code content from `NotionRenderer` | CodeBlock | - |
| className | Additional class for Code | string | - |
| defaultLanguage | Default programming language if not specified in `block` | string | typescript |
| themes | Themes for rendering code | object | {light: "catppuccin-latte", dark: "dracula"} |
| showCopy | Whether to show the copy button | boolean | true |

## Run the Example

1. Install dependencies `pnpm i`
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-notion-x-code-block",
"version": "0.0.2",
"version": "0.1.0",
"description": "",
"type": "module",
"module": "dist/index.js",
Expand Down Expand Up @@ -45,6 +45,7 @@
"notion-utils": "^6.16.0",
"prettier": "^3.2.5",
"react": "^18.2.0",
"react-icons": "^5.0.1",
"react-notion-x": "^6.16.0",
"rollup": "^4.14.0",
"rollup-plugin-delete": "^2.0.0",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions src/code.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
width: 100%;
font-size: 85%;
margin: 2px;
position: relative;
}

/*
Expand All @@ -23,3 +24,29 @@
line-height: 1.4;
color: var(--fg-color-3);
}

.codeCopyButton {
position: absolute;
display: flex;
right: 10px;
top: 15px;
align-items: center;
justify-content: center;
gap: 2px;
padding: 5px;
border: none;
cursor: pointer;
background-color: var(--bg-color);
color: var(--fg-color);
border-radius: 4px;
opacity: 0;
transition: opacity 0.1s linear;
}

@media (prefers-reduced-motion) {
transition: none;
}

.codeBlock:hover .codeCopyButton {
opacity: 1;
}
39 changes: 32 additions & 7 deletions src/code.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
import { getBlockTitle } from "notion-utils";
import React, { useEffect, useState } from "react";
import { IoMdCopy } from "react-icons/io";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { cs, useNotionContext, Text } from "react-notion-x";
import { codeToHtml } from "shiki";
import { BundledTheme } from "shiki/themes";

import styles from "./code.module.css";

import type { CodeBlock } from "notion-types";
import type { BundledTheme } from "shiki/themes";

export const Code: React.FC<{
block: CodeBlock;
defaultLanguage?: string;
className?: string;
captionClassName?: string;
showCopy?: boolean;
themes?: {
light: BundledTheme;
dark: BundledTheme;
};
}> = ({
block,
className,
defaultLanguage = "typescript",
themes = {
light: "catppuccin-latte",
dark: "dracula"
},
className,
captionClassName
showCopy = true
}) => {
const { recordMap } = useNotionContext();
const [isCopied, setIsCopied] = useState(false);
const content = getBlockTitle(block, recordMap);
const [code, setCode] = useState<string | undefined>(undefined);
const timer = useRef<null | number>(null);

const language = (
block.properties?.language?.[0]?.[0] || defaultLanguage
Expand All @@ -47,15 +49,38 @@ export const Code: React.FC<{
content && renderCodeToHtml();
}, [content, language, themes]);

const clickCopy = useCallback(() => {
navigator.clipboard.writeText(content).then(() => {
setIsCopied(true);
if (timer.current) {
clearTimeout(timer.current);
timer.current = null;
}
timer.current = setTimeout(() => {
setIsCopied(false);
timer.current = null;
}, 1000);
});
}, [content]);

return (
<figure className={cs(styles.codeBlock, className)}>
{showCopy ? (
<button
onClick={clickCopy}
className={styles.codeCopyButton}
>
<IoMdCopy />
{isCopied ? "Copied" : "Copy"}
</button>
) : null}
{code == undefined ? (
<pre>{content}</pre>
) : (
<div dangerouslySetInnerHTML={{ __html: code }} />
)}
{caption && (
<figcaption className={cs(styles.codeCaption, captionClassName)}>
<figcaption className={styles.codeCaption}>
<Text value={caption} block={block} />
</figcaption>
)}
Expand Down

0 comments on commit 8e9e187

Please sign in to comment.