diff --git a/examples/full/components/NotionPage.tsx b/examples/full/components/NotionPage.tsx index e8531e71e..8b51ebb82 100644 --- a/examples/full/components/NotionPage.tsx +++ b/examples/full/components/NotionPage.tsx @@ -112,7 +112,14 @@ export const NotionPage = ({ g.recordMap = recordMap g.block = block } - + const codeMenuActions = { + copy: (content: string, language: string, block: any) => { + console.log('copy', content, language, block) + }, + menu: (content: string, language: string, block: any) => { + console.log('menu', content, language, block) + } + } const socialDescription = 'React Notion X Demo' const socialImage = 'https://react-notion-x-demo.transitivebullsh.it/social.jpg' @@ -152,6 +159,7 @@ export const NotionPage = ({ rootDomain={rootDomain} rootPageId={rootPageId} previewImages={previewImagesEnabled} + customFunctions={{ codeMenuActions: codeMenuActions.menu }} components={{ // NOTE (transitive-bullshit 3/12/2023): I'm disabling next/image for this repo for now because the amount of traffic started costing me hundreds of dollars a month in Vercel image optimization costs. I'll probably re-enable it in the future if I can find a better solution. // nextImage: Image, diff --git a/packages/react-notion-x/src/block.tsx b/packages/react-notion-x/src/block.tsx index 6b3e0084d..212949f1d 100644 --- a/packages/react-notion-x/src/block.tsx +++ b/packages/react-notion-x/src/block.tsx @@ -525,7 +525,7 @@ export const Block: React.FC = (props) => { ) case 'code': - return + return case 'column_list': return
{children}
diff --git a/packages/react-notion-x/src/context.tsx b/packages/react-notion-x/src/context.tsx index 63140538d..271b5e050 100644 --- a/packages/react-notion-x/src/context.tsx +++ b/packages/react-notion-x/src/context.tsx @@ -40,7 +40,7 @@ export interface NotionContext { defaultPageIcon?: string defaultPageCover?: string defaultPageCoverPosition?: number - + customFunctions?: any zoom: any } @@ -71,7 +71,7 @@ export interface PartialNotionContext { defaultPageIcon?: string defaultPageCover?: string defaultPageCoverPosition?: number - + customFunctions?: any zoom?: any } diff --git a/packages/react-notion-x/src/icons/menu-vertical-svgrepo-com.svg b/packages/react-notion-x/src/icons/menu-vertical-svgrepo-com.svg new file mode 100644 index 000000000..8d16502de --- /dev/null +++ b/packages/react-notion-x/src/icons/menu-vertical-svgrepo-com.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/react-notion-x/src/icons/menu.tsx b/packages/react-notion-x/src/icons/menu.tsx new file mode 100644 index 000000000..7c4c1916c --- /dev/null +++ b/packages/react-notion-x/src/icons/menu.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' + +function MenuIcon(props: React.SVGProps) { + return ( + + + + ) +} + +export default MenuIcon diff --git a/packages/react-notion-x/src/renderer.tsx b/packages/react-notion-x/src/renderer.tsx index 4f063fba0..2404f926a 100644 --- a/packages/react-notion-x/src/renderer.tsx +++ b/packages/react-notion-x/src/renderer.tsx @@ -58,6 +58,7 @@ export const NotionRenderer: React.FC<{ blockId?: string hideBlockId?: boolean disableHeader?: boolean + customFunctions?: any }> = ({ components, recordMap, @@ -81,6 +82,7 @@ export const NotionRenderer: React.FC<{ defaultPageIcon, defaultPageCover, defaultPageCoverPosition, + customFunctions, ...rest }) => { const zoom = React.useMemo( @@ -118,6 +120,7 @@ export const NotionRenderer: React.FC<{ defaultPageCover={defaultPageCover} defaultPageCoverPosition={defaultPageCoverPosition} zoom={isImageZoomable ? zoom : null} + customFunctions={customFunctions} > diff --git a/packages/react-notion-x/src/styles.css b/packages/react-notion-x/src/styles.css index 184e8b3f0..c88cefa7e 100644 --- a/packages/react-notion-x/src/styles.css +++ b/packages/react-notion-x/src/styles.css @@ -929,7 +929,7 @@ svg.notion-page-icon { .notion-code-copy { position: absolute; top: 1em; - right: 1em; + right: 5em; user-select: none; z-index: 9; transition: opacity 0.2s cubic-bezier(0.3, 0, 0.5, 1); @@ -997,6 +997,63 @@ svg.notion-page-icon { color: #fff; border-radius: 6px; } +.notion-code-menu { + position: absolute; + top: 1em; + right: 1em; + user-select: none; + z-index: 9; + transition: opacity 0.2s cubic-bezier(0.3, 0, 0.5, 1); + opacity: 0; /* Start with hidden menu to reduce UI clutter */ +} + +.notion-code:hover .notion-code-menu { + opacity: 1; /* Show the menu on hover */ +} + +.notion-code-menu-button { + display: inline-block; + padding: 0.6em; + font-size: 1.25em; + line-height: 1em; + cursor: pointer; + transition: background-color 0.2s cubic-bezier(0.3, 0, 0.5, 1), color 0.2s cubic-bezier(0.3, 0, 0.5, 1), border-color 0.2s cubic-bezier(0.3, 0, 0.5, 1); + box-shadow: 0 1px 0 rgba(27, 31, 36, 0.04), inset 0 1px 0 rgba(255, 255, 255, 0.25); + background-color: #f6f8fa; + color: #24292f; + border: 1px solid rgba(27, 31, 36, 0.15); + border-radius: 6px; +} + +.notion-code-menu-button:hover { + background-color: #f3f4f6; + border-color: rgba(27, 31, 36, 0.15); +} + +.notion-code-menu-button:active { + background: hsla(220, 14%, 93%, 1); + border-color: rgba(27, 31, 36, 0.15); + transition: none; +} + +.notion-code-menu-tooltip { + display: none; /* Keep tooltip hidden initially */ + position: absolute; + bottom: -38px; /* Position the tooltip below the button */ + left: 50%; + transform: translateX(-50%); /* Center the tooltip */ + z-index: 99; + font-size: 14px; + white-space: nowrap; /* Ensure the tooltip text is on a single line */ + background-color: #333; /* Background color for tooltip */ + color: white; /* Text color for tooltip */ + padding: 5px 8px; /* Padding around the tooltip text */ + border-radius: 4px; /* Rounded corners for the tooltip */ +} + +.notion-code .notion-code-menu-button:hover + .notion-code-menu-tooltip { + display: block; /* Show the tooltip when hovering over the menu button */ +} .notion-column { display: flex; diff --git a/packages/react-notion-x/src/third-party/code.tsx b/packages/react-notion-x/src/third-party/code.tsx index baec68cb6..ef1ca6ea1 100644 --- a/packages/react-notion-x/src/third-party/code.tsx +++ b/packages/react-notion-x/src/third-party/code.tsx @@ -15,24 +15,24 @@ import 'prismjs/components/prism-tsx.min.js' import 'prismjs/components/prism-typescript.min.js' import { Text } from '../components/text' -import { useNotionContext } from '../context' import CopyIcon from '../icons/copy' +import MenuIcon from '../icons/menu' import { cs } from '../utils' export const Code: React.FC<{ block: CodeBlock defaultLanguage?: string className?: string -}> = ({ block, defaultLanguage = 'typescript', className }) => { + ctx: any +}> = ({ block, defaultLanguage = 'typescript', className, ctx }) => { const [isCopied, setIsCopied] = React.useState(false) const copyTimeout = React.useRef() - const { recordMap } = useNotionContext() + const { recordMap, customFunctions } = ctx const content = getBlockTitle(block, recordMap) const language = ( block.properties?.language?.[0]?.[0] || defaultLanguage ).toLowerCase() const caption = block.properties.caption - const codeRef = React.useRef() React.useEffect(() => { if (codeRef.current) { @@ -64,12 +64,26 @@ export const Code: React.FC<{ ) + const handleCodeMenu = () => { + setIsMenuOpen(!isMenuOpen) + if (customFunctions.codeMenuActions) { + customFunctions.codeMenuActions(content, language, block) + } + setIsMenuOpen(false) + } + + const MenuButton = ( +
+ +
+ ) + const [isMenuOpen, setIsMenuOpen] = React.useState(false) + return ( <>
         
{copyButton} - {isCopied && (
{isCopied ? 'Copied' : 'Copy'}
@@ -77,6 +91,13 @@ export const Code: React.FC<{ )}
+
+ {MenuButton} +
+
{isMenuOpen ? 'Menu Open' : 'Menu'}
+
+
+ {content}