From 1dfd7f18cf7aef5f133c7b0bfae31e2a9faec4fe Mon Sep 17 00:00:00 2001 From: kpdve Date: Wed, 26 Mar 2025 17:23:48 +0100 Subject: [PATCH 1/2] feat(app): TabCode in markdown --- src/components/markdown.tsx | 88 +++++++++++++++++-- .../creator/components/creator-toolbox.tsx | 18 +++- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/src/components/markdown.tsx b/src/components/markdown.tsx index 029b5d454..24c85456b 100644 --- a/src/components/markdown.tsx +++ b/src/components/markdown.tsx @@ -14,21 +14,99 @@ import ReactMarkdown, { type Options } from 'react-markdown'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; +import { Tabs } from 'design-system/tabs'; + +const useTab = (children: ReactNode) => { + const [activeTab, setActiveTab] = React.useState(0); + + const content = React.useMemo(() => children?.toString() || ``, [children]); + const hasTabs = content.includes(`@@@`); + + const parseTabContent = React.useCallback((content: string): string[] => { + const panels = content.split(`@@@`); + return panels[0].trim() === `` ? panels.slice(1) : panels; + }, []); + + const tabs = React.useMemo( + () => (hasTabs ? parseTabContent(content) : []), + [hasTabs, content, parseTabContent], + ); + + return { + activeTab, + setActiveTab, + hasTabs, + content, + tabs, + }; +}; + +type TabCodeProps = { + tabs: string[]; + activeTab: number; + setActiveTab: React.Dispatch>; +}; + +const TabCode = ({ tabs, activeTab, setActiveTab }: TabCodeProps) => { + return ( + <> + + {tabs.map((_, index) => ( + setActiveTab(index)} + className={c( + `px-3 py-2 text-sm whitespace-nowrap hover:cursor-pointer font-medium`, + activeTab === index + ? `bg-green-700 text-white` + : `bg-gray-300 dark:bg-slate-800 text-black dark:text-white hover:bg-gray-400/70 dark:hover:bg-slate-800/70`, + )} + > + {`Tab ${index + 1}`} + + ))} + + {tabs.map((tab, index) => ( + { + if (index === activeTab && el) { + highlightElement(el); + } + }} + className={c(`language-javascript`, index !== activeTab && `hidden`)} + > + {tab} + + ))} + + ); +}; const Code = ({ children, }: DetailedHTMLProps, HTMLElement>) => { const ref = React.useRef(null); + const { hasTabs, tabs, activeTab, setActiveTab } = useTab(children); React.useLayoutEffect(() => { const container = ref.current; - container && highlightElement(container); - }, [children]); + if (container && !hasTabs) { + highlightElement(container); + } + }, [children, hasTabs]); + + if (!hasTabs) { + return ( + + {children} + + ); + } return ( - - {children} - + ); }; diff --git a/src/features/creator/components/creator-toolbox.tsx b/src/features/creator/components/creator-toolbox.tsx index a5c2005a3..8f0811cef 100644 --- a/src/features/creator/components/creator-toolbox.tsx +++ b/src/features/creator/components/creator-toolbox.tsx @@ -13,6 +13,7 @@ import { BiMath, BiSolidQuoteAltLeft, BiStrikethrough, + BiWindow, } from 'react-icons/bi'; type CreatorToolboxSyntaxKey = @@ -26,7 +27,8 @@ type CreatorToolboxSyntaxKey = | `link` | `ol` | `ul` - | `todo`; + | `todo` + | `tab`; type CreatorToolboxProps = { creator: HTMLTextAreaElement | null; @@ -72,6 +74,11 @@ const CreatorToolbox = ({ creator, onClick }: CreatorToolboxProps) => { todo: () => { onClick(`- [x] a\n- [ ] b\n- [x] c`); }, + tab: () => { + onClick( + `\`\`\`javascript\n@@@\n// Tab name: Tab 1\n\n@@@\n// Tab name: Empty Tab\n\n@@@\n// Tab name: Tab 3\nconsole.log("Hello world");\n\`\`\``, + ); + }, }; operationsMap[syntaxKey](); @@ -133,6 +140,15 @@ const CreatorToolbox = ({ creator, onClick }: CreatorToolboxProps) => { > +