mirror of
https://github.com/massbug/judge4c.git
synced 2025-07-03 23:30:50 +00:00
feat(panel-layout): add scrollable content support with isScroll prop
- Add ScrollArea and ScrollBar components from ui/scroll-area - Introduce optional isScroll prop (defaults to true) to control scrolling - Maintain backward compatibility with existing usage
This commit is contained in:
parent
d437485b22
commit
b34acf37b9
@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { CoreEditor } from "@/components/core-editor";
|
import { CoreEditor } from "@/components/core-editor";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
|
import { updateProblemTemplate } from "@/components/creater/problem-maintain";
|
||||||
@ -70,7 +69,6 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<CardHeader className="px-6 py-4">
|
<CardHeader className="px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -112,8 +110,6 @@ export default function EditCodePanel({ problemId }: EditCodePanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import { CoreEditor } from "@/components/core-editor";
|
|||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { VideoEmbed } from "@/components/content/video-embed";
|
import { VideoEmbed } from "@/components/content/video-embed";
|
||||||
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
@ -105,7 +104,6 @@ export default function EditDescriptionPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>题目描述</CardTitle>
|
<CardTitle>题目描述</CardTitle>
|
||||||
@ -211,8 +209,6 @@ export default function EditDescriptionPanel({
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Difficulty } from "@/generated/client";
|
import { Difficulty } from "@/generated/client";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { updateProblemDetail } from "@/components/creater/problem-maintain";
|
import { updateProblemDetail } from "@/components/creater/problem-maintain";
|
||||||
@ -80,7 +79,6 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<CardHeader className="px-6 py-4">
|
<CardHeader className="px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -163,8 +161,6 @@ export default function EditDetailPanel({ problemId }: { problemId: string }) {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { CoreEditor } from "@/components/core-editor";
|
|||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { VideoEmbed } from "@/components/content/video-embed";
|
import { VideoEmbed } from "@/components/content/video-embed";
|
||||||
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
import { getProblemLocales } from "@/app/actions/getProblemLocales";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
import { updateProblemSolution } from "@/components/creater/problem-maintain";
|
import { updateProblemSolution } from "@/components/creater/problem-maintain";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
@ -96,7 +95,6 @@ export default function EditSolutionPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>题目解析</CardTitle>
|
<CardTitle>题目解析</CardTitle>
|
||||||
@ -204,8 +202,6 @@ export default function EditSolutionPanel({
|
|||||||
</Button>
|
</Button>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import { Button } from "@/components/ui/button";
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { getProblemData } from "@/app/actions/getProblem";
|
import { getProblemData } from "@/app/actions/getProblem";
|
||||||
import { generateAITestcase } from "@/app/actions/ai-testcase";
|
import { generateAITestcase } from "@/app/actions/ai-testcase";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
import { PanelLayout } from "@/features/problems/layouts/panel-layout";
|
||||||
|
|
||||||
@ -185,7 +184,6 @@ export default function EditTestcasePanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout>
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<Card className="w-full rounded-none border-none bg-background">
|
<Card className="w-full rounded-none border-none bg-background">
|
||||||
<CardHeader className="px-6 py-4">
|
<CardHeader className="px-6 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -226,9 +224,7 @@ export default function EditTestcasePanel({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<Label>输入参数</Label>
|
<Label>输入参数</Label>
|
||||||
<Button onClick={() => handleAddInput(idx)}>
|
<Button onClick={() => handleAddInput(idx)}>添加输入</Button>
|
||||||
添加输入
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{tc.inputs.map((inp, iIdx) => (
|
{tc.inputs.map((inp, iIdx) => (
|
||||||
<div key={iIdx} className="grid grid-cols-2 gap-4">
|
<div key={iIdx} className="grid grid-cols-2 gap-4">
|
||||||
@ -247,12 +243,7 @@ export default function EditTestcasePanel({
|
|||||||
<Input
|
<Input
|
||||||
value={inp.value}
|
value={inp.value}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleInputChange(
|
handleInputChange(idx, iIdx, "value", e.target.value)
|
||||||
idx,
|
|
||||||
iIdx,
|
|
||||||
"value",
|
|
||||||
e.target.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
placeholder="参数值"
|
placeholder="参数值"
|
||||||
/>
|
/>
|
||||||
@ -272,8 +263,6 @@ export default function EditTestcasePanel({
|
|||||||
))}
|
))}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ interface BotPanelProps {
|
|||||||
|
|
||||||
export const BotPanel = ({ problemId }: BotPanelProps) => {
|
export const BotPanel = ({ problemId }: BotPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout isScroll={false}>
|
||||||
<Suspense fallback={<BotContentSkeleton />}>
|
<Suspense fallback={<BotContentSkeleton />}>
|
||||||
<BotContent problemId={problemId} />
|
<BotContent problemId={problemId} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
@ -13,7 +13,7 @@ interface CodePanelProps {
|
|||||||
|
|
||||||
export const CodePanel = ({ problemId }: CodePanelProps) => {
|
export const CodePanel = ({ problemId }: CodePanelProps) => {
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout isScroll={false}>
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<CodeToolbar className="border-b" />
|
<CodeToolbar className="border-b" />
|
||||||
<Suspense fallback={<CodeContentSkeleton />}>
|
<Suspense fallback={<CodeContentSkeleton />}>
|
||||||
|
@ -2,7 +2,6 @@ import prisma from "@/lib/prisma";
|
|||||||
import { getLocale } from "next-intl/server";
|
import { getLocale } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import type { Locale, ProblemLocalization } from "@/generated/client";
|
import type { Locale, ProblemLocalization } from "@/generated/client";
|
||||||
|
|
||||||
const getLocalizedDescription = (
|
const getLocalizedDescription = (
|
||||||
@ -40,12 +39,7 @@ export const DescriptionContent = async ({
|
|||||||
|
|
||||||
const description = getLocalizedDescription(descriptions, locale as Locale);
|
const description = getLocalizedDescription(descriptions, locale as Locale);
|
||||||
|
|
||||||
return (
|
return <MdxRenderer source={description} className="p-4 md:p-6" />;
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<MdxRenderer source={description} className="p-4 md:p-6" />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DescriptionContentSkeleton = () => {
|
export const DescriptionContentSkeleton = () => {
|
||||||
|
@ -16,7 +16,7 @@ export const DetailPanel = ({ submissionId }: DetailPanelProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout>
|
<PanelLayout isScroll={false}>
|
||||||
<DetailHeader />
|
<DetailHeader />
|
||||||
<Suspense fallback={<DetailContentSkeleton />}>
|
<Suspense fallback={<DetailContentSkeleton />}>
|
||||||
<DetailContent submissionId={submissionId} />
|
<DetailContent submissionId={submissionId} />
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
interface PanelLayoutProps {
|
interface PanelLayoutProps {
|
||||||
|
isScroll?: boolean;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PanelLayout = ({ children }: PanelLayoutProps) => {
|
export const PanelLayout = ({
|
||||||
|
isScroll = true,
|
||||||
|
children,
|
||||||
|
}: PanelLayoutProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-lg bg-background overflow-hidden">
|
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-lg bg-background overflow-hidden">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
<div className="absolute h-full w-full">{children}</div>
|
<div className="absolute h-full w-full">
|
||||||
|
{isScroll ? (
|
||||||
|
<ScrollArea className="h-full">
|
||||||
|
{children}
|
||||||
|
<ScrollBar orientation="horizontal" />
|
||||||
|
</ScrollArea>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,6 @@ import prisma from "@/lib/prisma";
|
|||||||
import { getLocale } from "next-intl/server";
|
import { getLocale } from "next-intl/server";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
import { MdxRenderer } from "@/components/content/mdx-renderer";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import type { Locale, ProblemLocalization } from "@/generated/client";
|
import type { Locale, ProblemLocalization } from "@/generated/client";
|
||||||
|
|
||||||
const getLocalizedSolution = (
|
const getLocalizedSolution = (
|
||||||
@ -38,12 +37,7 @@ export const SolutionContent = async ({ problemId }: SolutionContentProps) => {
|
|||||||
|
|
||||||
const solution = getLocalizedSolution(solutions, locale as Locale);
|
const solution = getLocalizedSolution(solutions, locale as Locale);
|
||||||
|
|
||||||
return (
|
return <MdxRenderer source={solution} className="p-4 md:p-6" />;
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<MdxRenderer source={solution} className="p-4 md:p-6" />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SolutionContentSkeleton = () => {
|
export const SolutionContentSkeleton = () => {
|
||||||
|
@ -3,7 +3,6 @@ import { CodeXmlIcon } from "lucide-react";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
import { SubmissionTable } from "@/features/problems/submission/components/table";
|
import { SubmissionTable } from "@/features/problems/submission/components/table";
|
||||||
|
|
||||||
interface SubmissionContentProps {
|
interface SubmissionContentProps {
|
||||||
@ -45,11 +44,12 @@ export const SubmissionContent = async ({
|
|||||||
const session = await auth();
|
const session = await auth();
|
||||||
const userId = session?.user?.id;
|
const userId = session?.user?.id;
|
||||||
|
|
||||||
return (
|
return userId ? (
|
||||||
<ScrollArea className="h-full px-3">
|
<div className="px-3">
|
||||||
{userId ? <SubmissionTable problemId={problemId} /> : <LoginPromptCard />}
|
<SubmissionTable problemId={problemId} />
|
||||||
<ScrollBar orientation="horizontal" />
|
</div>
|
||||||
</ScrollArea>
|
) : (
|
||||||
|
<LoginPromptCard />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { TestcaseTable } from "@/features/problems/testcase/table";
|
import { TestcaseTable } from "@/features/problems/testcase/table";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
|
||||||
|
|
||||||
interface TestcaseContentProps {
|
interface TestcaseContentProps {
|
||||||
problemId: string;
|
problemId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TestcaseContent = ({ problemId }: TestcaseContentProps) => {
|
export const TestcaseContent = ({ problemId }: TestcaseContentProps) => {
|
||||||
return (
|
return <TestcaseTable problemId={problemId} />;
|
||||||
<ScrollArea className="h-full">
|
|
||||||
<TestcaseTable problemId={problemId} />
|
|
||||||
<ScrollBar orientation="horizontal" />
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TestcaseContentSkeleton = () => {
|
export const TestcaseContentSkeleton = () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user