feat: button approach layout for task page in mobile

This commit is contained in:
mehedi-hasan 2024-04-21 18:39:07 +06:00
parent 32e9451416
commit f9a4afcc06
6 changed files with 430 additions and 134 deletions

View File

@ -1,4 +1,3 @@
import React from "react";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { import {
ResizableHandle, ResizableHandle,
@ -8,16 +7,21 @@ import {
import markdownStyles from "@/styles/markdown-styles.module.css"; import markdownStyles from "@/styles/markdown-styles.module.css";
import { tutorialData } from "@/constants/tutorial-data"; import { tutorialData } from "@/constants/tutorial-data";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { buttonVariants } from "@/components/ui/button"; import { Button, buttonVariants } from "@/components/ui/button";
import Link from "next/link"; import Link from "next/link";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import EditorWindow from "@/components/task/editor-window"; import EditorWindow from "@/components/task/editor-window";
import { LANGUAGE_VERSIONS } from "@/constants/language-options"; import { LANGUAGE_VERSIONS } from "@/constants/language-options";
import { markdownToHtml } from "@/lib/markdown-to-html"; import { markdownToHtml } from "@/lib/markdown-to-html";
import TaskPage from "@/components/task-page";
const languages = Object.keys(LANGUAGE_VERSIONS); const languages = Object.keys(LANGUAGE_VERSIONS);
const Page = async ({ params }: { params: { taskId: string[] } }) => { const Page = async ({ params }: { params: { taskId: string[] } }) => {
// const [activeMobileView, setActiveMobileView] = useState<
// "tutorial" | "code" | "output"
// >("tutorial");
if (!params.taskId[0] || !languages.includes(params.taskId[0])) { if (!params.taskId[0] || !languages.includes(params.taskId[0])) {
notFound(); notFound();
} }
@ -41,113 +45,13 @@ const Page = async ({ params }: { params: { taskId: string[] } }) => {
const content = await markdownToHtml(tutorial?.content || ""); const content = await markdownToHtml(tutorial?.content || "");
return ( return (
<> <>
<div className="md:hidden w-full h-full"> <TaskPage
<ResizablePanelGroup params={params}
direction="vertical" langTutorial={langTutorial}
className="max-w-[1440px] mx-auto h-full" tutorial={tutorial}
> tutorialIndex={tutorialIndex}
<ResizablePanel defaultSize={50}> content={content}
<ScrollArea className="h-[calc(100vh-55px)]">
<div className="px-4 py-6 flex flex-col gap-4 h-full mb-[60vh]">
<div
className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<div className="flex justify-between">
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex - 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1
}`}
className={buttonVariants({
variant: "outline",
})}
>
Prev
</Link>
)}
</div>
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex + 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1
}`}
className={buttonVariants({ variant: "outline" })}
>
Next
</Link>
)}
</div>
</div>
</div>
</ScrollArea>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<EditorWindow
language={params.taskId[0]}
step={Number(params.taskId[1]) || 1}
/> />
</ResizablePanel>
</ResizablePanelGroup>
</div>
<div className="hidden md:block w-full h-full">
<ResizablePanelGroup
direction="horizontal"
className="max-w-[1440px] mx-auto h-full"
>
<ResizablePanel defaultSize={50}>
<ScrollArea className="h-[calc(100vh-55px)]">
<div className="px-4 py-6 flex flex-col gap-4 h-full">
<div
className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<div className="flex justify-between">
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex - 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1
}`}
className={buttonVariants({
variant: "outline",
})}
>
Prev
</Link>
)}
</div>
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex + 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1
}`}
className={buttonVariants({ variant: "outline" })}
>
Next
</Link>
)}
</div>
</div>
</div>
</ScrollArea>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<EditorWindow
language={params.taskId[0]}
step={Number(params.taskId[1]) || 1}
/>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</> </>
); );
}; };

View File

@ -16,7 +16,7 @@ export function MobileSidebar({ className }: SidebarProps) {
return ( return (
<> <>
<Sheet open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild> <SheetTrigger asChild className="cursor-pointer">
<MenuIcon /> <MenuIcon />
</SheetTrigger> </SheetTrigger>
<SheetContent side="left" className="!px-0"> <SheetContent side="left" className="!px-0">

View File

@ -0,0 +1,315 @@
"use client";
import React, { useState } from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
import markdownStyles from "@/styles/markdown-styles.module.css";
import { tutorialData } from "@/constants/tutorial-data";
import { notFound } from "next/navigation";
import { Button, buttonVariants } from "@/components/ui/button";
import Link from "next/link";
import { cn } from "@/lib/utils";
import EditorWindow from "@/components/task/editor-window";
import { LANGUAGE_VERSIONS } from "@/constants/language-options";
import { markdownToHtml } from "@/lib/markdown-to-html";
import Header from "./task/header";
import Image from "next/image";
import ThemeToggle from "./layout/ThemeToggle/theme-toggle";
const languages = Object.keys(LANGUAGE_VERSIONS);
interface LangTutorial {
id: number;
language: string;
tutorial: {
id: number;
content: string;
code: string;
}[];
}
interface Tutorial {
id: number;
content: string;
code: string;
}
const TaskPage = ({
params,
langTutorial,
tutorial,
tutorialIndex,
content,
}: {
params: { taskId: string[] };
langTutorial: LangTutorial;
tutorial: Tutorial;
tutorialIndex: number;
content: string;
}) => {
const [activeMobileView, setActiveMobileView] = useState<
"tutorial" | "code" | "output"
>("tutorial");
// if (!params.taskId[0] || !languages.includes(params.taskId[0])) {
// notFound();
// }
// const langTutorial = tutorialData.find(
// (t) => t.language === params.taskId[0],
// )!;
// const tutorial = langTutorial.tutorial.find(
// (t) => t.id === (Number(params.taskId[1]) || 1),
// );
// const tutorialIndex = langTutorial.tutorial.findIndex(
// (t) => t.id === (Number(params.taskId[1]) || 1),
// );
// if (!tutorial || tutorialIndex === -1) {
// notFound();
// }
// const content = await markdownToHtml(tutorial?.content || "");
return (
<>
<div className="md:hidden flex items-center justify-center gap-4 fixed w-screen bottom-0 left-0 z-50 px-4 py-3 bg-background border-t">
<Button
variant={activeMobileView === "tutorial" ? "default" : "outline"}
onClick={() => setActiveMobileView("tutorial")}
size="sm"
>
Tutorial
</Button>
<Button
variant={activeMobileView === "code" ? "default" : "outline"}
onClick={() => setActiveMobileView("code")}
size="sm"
>
Code
</Button>
<Button
variant={activeMobileView === "output" ? "default" : "outline"}
onClick={() => setActiveMobileView("output")}
size="sm"
>
Output
</Button>
</div>
<div className="md:hidden w-full h-full">
{/* <ResizablePanelGroup
direction="vertical"
className="max-w-[1440px] mx-auto h-full"
> */}
{/* <ResizablePanel defaultSize={50}> */}
<div
className={cn(
"fixed left-0 top-0 h-[calc(100vh-59px)] w-screen transition-transform translate-x-0",
activeMobileView === "tutorial"
? "translate-x-0"
: activeMobileView === "code"
? "-translate-x-full"
: activeMobileView === "output"
? "-translate-x-[200%]"
: "",
)}
>
<div className="border-b px-4 bg-background">
<nav className="flex justify-between items-center max-w-[1440px] mx-auto py-2">
<Link href="/dashboard" className="inline-block w-fit">
<Image
src="/Skilld AI Logos/Skilld Logo-colour on black.png"
alt="Skilld Logo"
width={100}
height={30}
className="hidden dark:block h-[25px] w-[80px] md:w-[100px] md:h-[30px] object-contain"
/>
<Image
src="/Skilld AI Logos/Skilld Logo-colour.png"
alt="Skilld Logo"
width={100}
height={30}
className="dark:hidden h-[25px] w-[80px] md:w-[100px] md:h-[30px] object-contain"
/>
</Link>
<div className="flex items-center gap-8">
{/* <Link href="/">Home</Link> */}
<Link href="/dashboard">Dashboard</Link>
{/* <Link href="/login">Login</Link> */}
<ThemeToggle />
</div>
</nav>
</div>
<ScrollArea className="h-[calc(100vh-calc(53px+59px))]">
<div className="p-4 flex flex-col gap-4 h-full">
<div
className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<div className="flex justify-between">
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex - 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1
}`}
className={buttonVariants({
variant: "outline",
size: "sm",
})}
>
Prev
</Link>
)}
</div>
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex + 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1
}`}
className={buttonVariants({
variant: "outline",
size: "sm",
})}
>
Next
</Link>
)}
</div>
</div>
</div>
</ScrollArea>
</div>
{/* </ResizablePanel> */}
{/* <ResizableHandle withHandle /> */}
{/* <ResizablePanel defaultSize={50}> */}
<EditorWindow
activeMobileView={activeMobileView}
language={params.taskId[0]}
step={Number(params.taskId[1]) || 1}
/>
{/* </ResizablePanel> */}
{/* </ResizablePanelGroup> */}
</div>
{/* <div className="md:hidden w-full h-full">
<ResizablePanelGroup
direction="vertical"
className="max-w-[1440px] mx-auto h-full"
>
<ResizablePanel defaultSize={50}>
<ScrollArea className="h-[calc(100vh-55px)]">
<div className="px-4 py-6 flex flex-col gap-4 h-full mb-[60vh]">
<div
className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<div className="flex justify-between">
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex - 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1
}`}
className={buttonVariants({
variant: "outline",
})}
>
Prev
</Link>
)}
</div>
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex + 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1
}`}
className={buttonVariants({ variant: "outline" })}
>
Next
</Link>
)}
</div>
</div>
</div>
</ScrollArea>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<EditorWindow
language={params.taskId[0]}
step={Number(params.taskId[1]) || 1}
/>
</ResizablePanel>
</ResizablePanelGroup>
</div> */}
<div className="hidden md:block w-full h-full">
<ResizablePanelGroup
direction="horizontal"
className="max-w-[1440px] mx-auto h-full"
>
<ResizablePanel defaultSize={50}>
<ScrollArea className="h-[calc(100vh-55px)]">
<div className="px-4 py-6 flex flex-col gap-4 h-full">
<div
className={cn("grow", markdownStyles["markdown"])}
dangerouslySetInnerHTML={{ __html: content }}
></div>
<div className="flex justify-between">
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex - 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) - 1
}`}
className={buttonVariants({
variant: "outline",
})}
>
Prev
</Link>
)}
</div>
<div className="mb-6">
{langTutorial?.tutorial[tutorialIndex + 1] && (
<Link
href={`/dashboard/task/${params.taskId[0]}/${
Number(params.taskId[1]) + 1
}`}
className={buttonVariants({ variant: "outline" })}
>
Next
</Link>
)}
</div>
</div>
</div>
</ScrollArea>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel defaultSize={50}>
<EditorWindow
activeMobileView={activeMobileView}
language={params.taskId[0]}
step={Number(params.taskId[1]) || 1}
/>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</>
);
};
export default TaskPage;

View File

@ -19,13 +19,14 @@ import { notFound, useRouter } from "next/navigation";
import { defineTheme } from "@/utils/define-theme"; import { defineTheme } from "@/utils/define-theme";
import Preview from "./preview"; import Preview from "./preview";
import { MyComponents } from "./my-components"; import { MyComponents } from "./my-components";
import { cn } from "@/lib/utils";
export default function EditorWindow({ export default function EditorWindow({
activeMobileView: activeMobileView,
language: lang, language: lang,
step, step,
}: { }: {
activeMobileView: "tutorial" | "code" | "output";
language: string; language: string;
step: number; step: number;
}) { }) {
@ -51,17 +52,17 @@ export default function EditorWindow({
}; };
useEffect(() => { useEffect(() => {
defineTheme("oceanic-next").then((_) => setTheme("oceanic-next")); defineTheme("cobalt").then((_) => setTheme("cobalt"));
}, []); }, []);
function handleThemeChange(th: any) { function handleThemeChange(th: string) {
const theme = th; const themee = th;
// console.log("theme...", theme); // console.log("theme...", theme);
if (["light", "vs-dark"].includes(theme)) { if (["light", "vs-dark"].includes(themee)) {
setTheme(theme); setTheme(themee);
} else { } else {
defineTheme(theme).then((_) => setTheme(theme)); defineTheme(themee).then((_) => setTheme(themee));
} }
} }
@ -95,6 +96,82 @@ export default function EditorWindow({
// console.log(tutorial); // console.log(tutorial);
return ( return (
<>
<div className="md:hidden">
{/* <ResizablePanelGroup direction="vertical" className="border-r">
<ResizablePanel defaultSize={50}> */}
<div
className={cn(
"fixed left-0 top-0 h-[calc(100vh-59px)] w-screen translate-x-full transition-transform ",
activeMobileView === "tutorial"
? "translate-x-full"
: activeMobileView === "code"
? "translate-x-0"
: activeMobileView === "output"
? "-translate-x-full"
: "",
)}
>
<div className="p-2 flex gap-4">
<ThemeDropdown
handleThemeChange={handleThemeChange}
theme={theme}
/>
{/* <LanguagesDropdown
handleLanguageChange={handleLanguageChange}
language={lang}
/> */}
</div>
<div className="h-[calc(100vh-calc(57px+53px))]">
<CodeEditor
codeValue={codeValue}
// @ts-ignore
// defaultCode={CODE_SNIPPETS[language]}
defaultCode={tutorial?.code || ""}
onChange={handleCodeChange}
theme={theme}
// language={language}
language={lang}
/>
</div>
</div>
{/* </ResizablePanel> */}
{/* <ResizableHandle withHandle /> */}
{/* <ResizablePanel defaultSize={50}> */}
<ErrorBoundary FallbackComponent={PreviewErrorFallback}>
<div
className={cn(
"bg-white text-black p-4 fixed left-0 h-[calc(100vh-59px)] w-screen transition-transform translate-x-[200%]",
activeMobileView === "tutorial"
? "translate-x-[200%]"
: activeMobileView === "code"
? "translate-x-[100%]"
: activeMobileView === "output"
? "translate-x-0"
: "",
)}
>
{lang === "react" ? (
<Preview
componentName={componentName}
tutorialCode={tutorial?.code || ""}
/>
) : (
<Output
// @ts-ignore
codeValue={codeValue || tutorial?.code || ""}
// codeValue={codeValue || CODE_SNIPPETS[language]}
// language={language}
language={lang}
/>
)}
</div>
</ErrorBoundary>
{/* </ResizablePanel> */}
{/* </ResizablePanelGroup> */}
</div>
<div className="hidden md:block h-[calc(100vh-55px)]">
<ResizablePanelGroup direction="vertical" className="border-r"> <ResizablePanelGroup direction="vertical" className="border-r">
<ResizablePanel defaultSize={50}> <ResizablePanel defaultSize={50}>
<div className="h-full "> <div className="h-full ">
@ -103,10 +180,10 @@ export default function EditorWindow({
handleThemeChange={handleThemeChange} handleThemeChange={handleThemeChange}
theme={theme} theme={theme}
/> />
<LanguagesDropdown {/* <LanguagesDropdown
handleLanguageChange={handleLanguageChange} handleLanguageChange={handleLanguageChange}
language={lang} language={lang}
/> /> */}
</div> </div>
<div className="h-full"> <div className="h-full">
<CodeEditor <CodeEditor
@ -144,7 +221,7 @@ export default function EditorWindow({
</ErrorBoundary> </ErrorBoundary>
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
</div>
</>
); );
} }

View File

@ -5,7 +5,7 @@ import React from "react";
const Header = () => { const Header = () => {
return ( return (
<header className="border-b px-4 h-min"> <header className="border-b px-4 hidden md:block">
<nav className="flex justify-between items-center max-w-[1440px] mx-auto py-2"> <nav className="flex justify-between items-center max-w-[1440px] mx-auto py-2">
<Link href="/dashboard" className="inline-block w-fit"> <Link href="/dashboard" className="inline-block w-fit">
<Image <Image

View File

@ -51,5 +51,5 @@
} }
.markdown pre { .markdown pre {
@apply p-4 rounded-md my-4 overflow-x-auto max-w-[320px] xs:max-w-full; @apply p-4 rounded-md my-4 overflow-x-auto max-w-[calc(100vw-32px)] xs:max-w-full;
} }