diff --git a/app/components/CodeEditor/CodeEditor.tsx b/app/components/CodeEditor/CodeEditor.tsx index 32870c3..6b26c1e 100644 --- a/app/components/CodeEditor/CodeEditor.tsx +++ b/app/components/CodeEditor/CodeEditor.tsx @@ -170,14 +170,14 @@ export default function CodeEditor({ chapterIndex: number; outputResult: OutputResult; }) { - const { colorMode } = useColorMode(); - const [monaco, setMonaco] = useState(null); - const [isValidating, setIsValidating] = useState(false); - const editorStore = useEditorStore(); - const editorRef = useRef(null); - - // Apply custom hooks - useEditorTheme(monaco, colorMode); + const { colorMode } = useColorMode(); + const [monaco, setMonaco] = useState(null); + const [isValidating, setIsValidating] = useState(false); + const editorStore = useEditorStore(); + const editorRef = useRef(null); + + // Apply custom hooks + useEditorTheme(monaco, colorMode); const handleValidate = () => { setIsValidating(true); diff --git a/app/components/ContentViewer/ContentViewer.module.css b/app/components/ContentViewer/ContentViewer.module.css index 8e98663..02af120 100644 --- a/app/components/ContentViewer/ContentViewer.module.css +++ b/app/components/ContentViewer/ContentViewer.module.css @@ -7,6 +7,7 @@ padding-right: 14px; padding-bottom: 12px; padding-left: 14px; + margin-top: 50px; } .contentWrapper { border-right: 1px solid hsl(var(--border-color)); diff --git a/app/components/EditorNOutput/EditorNOutput.tsx b/app/components/EditorNOutput/EditorNOutput.tsx index 05957f7..50c8054 100644 --- a/app/components/EditorNOutput/EditorNOutput.tsx +++ b/app/components/EditorNOutput/EditorNOutput.tsx @@ -7,6 +7,7 @@ import { Box } from "@chakra-ui/react"; import Output from "../Output"; import { CodeFile } from "@/lib/types"; import { outputReducer } from "@/lib/reducers"; +import { useUserSolutionStore } from "@/lib/stores"; export default function EditorNOutput({ codeFile, @@ -19,9 +20,7 @@ export default function EditorNOutput({ stepIndex: number; chapterIndex: number; }) { - const [codeString, setCodeString] = useState( - JSON.stringify(codeFile.code, null, 2), - ); + const { setCodeString, codeString } = useUserSolutionStore(); const showSolution = () => { setCodeString(JSON.stringify(codeFile.solution, null, 2)); @@ -64,6 +63,9 @@ export default function EditorNOutput({ }; useEffect(() => { + // Load the initial code + setCodeString(JSON.stringify(codeFile.code, null, 2)); + const topHeight = localStorage.getItem("verticalTopHeight"); if (topHeight) { setTopWidth(Number(topHeight)); diff --git a/app/components/MyBtn/MyBtn.tsx b/app/components/MyBtn/MyBtn.tsx index f8d135c..daab3ec 100644 --- a/app/components/MyBtn/MyBtn.tsx +++ b/app/components/MyBtn/MyBtn.tsx @@ -8,6 +8,7 @@ export default function MyBtn({ isDisabled, tooltip, size = "xs", + position = "right", }: { children: React.ReactNode; variant: "success" | "error" | "default"; @@ -15,6 +16,7 @@ export default function MyBtn({ isDisabled?: boolean; tooltip?: string; size?: "xs" | "sm" | "md" | "lg"; + position?: "left" | "right"; }) { return ( @@ -31,6 +33,7 @@ export default function MyBtn({ textTransform={"uppercase"} isDisabled={isDisabled} fontWeight={"bold"} + style={{float: position}} > {children} diff --git a/app/components/SolutionTab/SolutionTab.module.css b/app/components/SolutionTab/SolutionTab.module.css new file mode 100644 index 0000000..9948716 --- /dev/null +++ b/app/components/SolutionTab/SolutionTab.module.css @@ -0,0 +1,10 @@ +.container { + height: 70vh; + width: 100%; + border: 1px solid #ccc; + margin-bottom: 16px; + padding: 16px 10px; + box-sizing: border-box; + +} + diff --git a/app/components/SolutionTab/SolutionTab.tsx b/app/components/SolutionTab/SolutionTab.tsx new file mode 100644 index 0000000..bff945c --- /dev/null +++ b/app/components/SolutionTab/SolutionTab.tsx @@ -0,0 +1,66 @@ +"use client"; + +import Editor, { Monaco } from "@monaco-editor/react"; +import { useColorMode } from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import { useUserSolutionStore } from "@/lib/stores"; +import styles from "./SolutionTab.module.css"; +import MyBtn from "../MyBtn/MyBtn"; + +type SolutionTabProps = { + solution: string; +}; + +const useEditorTheme = (monaco: Monaco, colorMode: "dark" | "light") => { + useEffect(() => { + if (monaco) { + monaco.editor.defineTheme("my-theme", { + base: "vs-dark", + inherit: true, + rules: [], + colors: { + "editor.background": "#1f1f1f", + }, + }); + monaco.editor.setTheme(colorMode === "light" ? "light" : "my-theme"); + } + }, [monaco, colorMode]); +}; + +const SolutionTab = ({ solution }: SolutionTabProps) => { + const { colorMode } = useColorMode(); + const [monaco, setMonaco] = useState(null); + + const { setCodeString } = useUserSolutionStore(); + + function useSolution() { + setCodeString(solution); + } + + // Apply custom hooks + useEditorTheme(monaco, colorMode); + return ( +
+ { + setMonaco(monacoInstance); + editor.updateOptions({ readOnly: true }); + }} + /> + + Use this solution + +
+ ); +}; + +export default SolutionTab; diff --git a/app/components/SolutionTab/index.tsx b/app/components/SolutionTab/index.tsx new file mode 100644 index 0000000..ccfc666 --- /dev/null +++ b/app/components/SolutionTab/index.tsx @@ -0,0 +1 @@ +export { default as default } from "./SolutionTab"; diff --git a/app/components/TabHeader/TabHeader.module.css b/app/components/TabHeader/TabHeader.module.css new file mode 100644 index 0000000..42aa36b --- /dev/null +++ b/app/components/TabHeader/TabHeader.module.css @@ -0,0 +1,40 @@ +.wrapper { + width: 100%; + padding: 12px 16px; + position: fixed; + top: 40px; + margin: 12px 0; +} + +.tabContainer { + display: flex; + gap: 1.5rem; + font-size: 0.875rem; + font-weight: 500; + position: relative; + color: hsl(var(--text)); +} + +.tabButton { + position: relative; + padding-bottom: 8px; + background: none; + font-size: 16px; + border: none; + cursor: pointer; + font-family: Verdana, Geneva, Tahoma, sans-serif; + color: hsl(var(--text)); +} + +.activeTab { + color: hsl(var(--text)); +} + +.underline { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background-color:hsl(var(--text)); +} diff --git a/app/components/TabHeader/TabHeader.tsx b/app/components/TabHeader/TabHeader.tsx new file mode 100644 index 0000000..3bea330 --- /dev/null +++ b/app/components/TabHeader/TabHeader.tsx @@ -0,0 +1,48 @@ +"use client"; +import styles from "./TabHeader.module.css"; + +type TabHeaderProps = { + currentSelectedTab: "description" | "solution"; + setCurrentSelectedTab: (tab: "description" | "solution") => void; +}; + +const TabHeader = ({ + currentSelectedTab, + setCurrentSelectedTab, +}: TabHeaderProps & { + currentSelectedTab: "description" | "solution"; + setCurrentSelectedTab: (tab: "description" | "solution") => void; +}) => { + + return ( +
+
+ + + +
+
+ ); +}; + +export default TabHeader; diff --git a/app/components/TabHeader/index.tsx b/app/components/TabHeader/index.tsx new file mode 100644 index 0000000..9dc32fb --- /dev/null +++ b/app/components/TabHeader/index.tsx @@ -0,0 +1 @@ +export { default as default } from "./TabHeader"; diff --git a/app/components/Tabs/Tabs.module.css b/app/components/Tabs/Tabs.module.css new file mode 100644 index 0000000..7303f2a --- /dev/null +++ b/app/components/Tabs/Tabs.module.css @@ -0,0 +1,4 @@ +.container { + height: 100%; + width: 100%; +} diff --git a/app/components/Tabs/Tabs.tsx b/app/components/Tabs/Tabs.tsx new file mode 100644 index 0000000..e8769b4 --- /dev/null +++ b/app/components/Tabs/Tabs.tsx @@ -0,0 +1,31 @@ +"use client"; +import ContentViewer from "../ContentViewer/ContentViewer"; +import { ReactElement, useState } from "react"; +import SolutionTab from "../SolutionTab/SolutionTab"; +import TabHeader from "../TabHeader/TabHeader"; +import { TabType } from "@/lib/types"; +import styles from "./Tabs.module.css"; + +type ContentTabProps = { + Page?: ReactElement; + solution: string +}; + +function Tabs({ Page,solution}: ContentTabProps) { + const [currentSelectedTab,setCurrentSelectedTab] = useState("description"); + return ( +
+ + + + { + currentSelectedTab=="description"? + Page: + + } + +
+ ); +} + +export default Tabs; diff --git a/app/components/Tabs/index.tsx b/app/components/Tabs/index.tsx new file mode 100644 index 0000000..24e48f0 --- /dev/null +++ b/app/components/Tabs/index.tsx @@ -0,0 +1 @@ +export { default as default } from "./Tabs"; diff --git a/app/content/[...markdownPath]/page.module.css b/app/content/[...markdownPath]/page.module.css index 7669d38..94f2325 100644 --- a/app/content/[...markdownPath]/page.module.css +++ b/app/content/[...markdownPath]/page.module.css @@ -5,3 +5,9 @@ height: 100%; overflow-y: auto; } +.tabContainer { + display: flex; + flex-direction: column; + width: 50%; + position: relative; +} diff --git a/app/content/[...markdownPath]/page.tsx b/app/content/[...markdownPath]/page.tsx index c2b94a4..7626e65 100644 --- a/app/content/[...markdownPath]/page.tsx +++ b/app/content/[...markdownPath]/page.tsx @@ -2,8 +2,8 @@ import { contentManager } from "@/lib/contentManager"; import styles from "./page.module.css"; import React from "react"; import { parseLessonFolder } from "@/lib/server-functions"; -import ContentViewer from "@/app/components/ContentViewer"; import EditorNOutput from "@/app/components/EditorNOutput"; +import Tabs from "@/app/components/Tabs/Tabs"; export function generateMetadata({ params, @@ -37,12 +37,14 @@ export default async function Content({ const { mdPath, nextStepPath, stepIndex, codePath, chapterIndex } = contentManager.getPageMeta(urlPath); const { Page, metadata, codeFile } = parseLessonFolder(mdPath, codePath); - return (
- - - +
+ } + solution={JSON.stringify(codeFile.solution, null, 2)} + /> +
string | null; clearAllCode: () => void; + codeString: string; + setCodeString: (code: string) => void; }; export const useUserSolutionStore = create()((set, get) => ({ @@ -66,4 +69,9 @@ export const useUserSolutionStore = create()((set, get) => ({ localStorage.removeItem("codeData"); set({ userSolutionsByLesson: {} }); }, + + codeString: "", + setCodeString: (code: string) => { + set({ codeString: code }); + }, })); diff --git a/lib/types.ts b/lib/types.ts index 3d3b031..e9e84ee 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,5 +1,7 @@ import { InvalidSchemaError } from "@hyperjump/json-schema/draft-2020-12"; +export type TabType = "description" | "solution"; + export type ChapterStep = { title: string; fileName: string; @@ -59,4 +61,5 @@ export type OutputResult = { testCaseResults?: TestCaseResult[]; totalTestCases?: number; errors?: InvalidSchemaError | string; + selectedTab?: TabType; };