Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
35c9fab24b
|
@ -67,5 +67,5 @@ yarn-error.log*
|
||||||
|
|
||||||
# Ignore .idea files in the Expo monorepo
|
# Ignore .idea files in the Expo monorepo
|
||||||
**/.idea/
|
**/.idea/
|
||||||
|
uploads
|
||||||
uploads
|
packages/mind-elixir-core
|
|
@ -2,7 +2,7 @@ import { useContext, useState } from "react";
|
||||||
import { Input, Layout, Avatar, Button, Dropdown } from "antd";
|
import { Input, Layout, Avatar, Button, Dropdown } from "antd";
|
||||||
import { EditFilled, SearchOutlined, UserOutlined } from "@ant-design/icons";
|
import { EditFilled, SearchOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||||
import { UserMenu } from "./UserMenu/UserMenu";
|
import { UserMenu } from "./UserMenu/UserMenu";
|
||||||
import { NavigationMenu } from "./NavigationMenu";
|
import { NavigationMenu } from "./NavigationMenu";
|
||||||
import { useMainContext } from "./MainProvider";
|
import { useMainContext } from "./MainProvider";
|
||||||
|
@ -10,6 +10,7 @@ const { Header } = Layout;
|
||||||
|
|
||||||
export function MainHeader() {
|
export function MainHeader() {
|
||||||
const { isAuthenticated, user } = useAuth();
|
const { isAuthenticated, user } = useAuth();
|
||||||
|
const { id } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { searchValue, setSearchValue } = useMainContext();
|
const { searchValue, setSearchValue } = useMainContext();
|
||||||
return (
|
return (
|
||||||
|
@ -52,10 +53,15 @@ export function MainHeader() {
|
||||||
{isAuthenticated && (
|
{isAuthenticated && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/course/editor")}
|
onClick={() => {
|
||||||
|
const url = id
|
||||||
|
? `/course/${id}/editor`
|
||||||
|
: "/course/editor";
|
||||||
|
navigate(url);
|
||||||
|
}}
|
||||||
className="flex items-center space-x-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 border-none shadow-md hover:shadow-lg transition-all"
|
className="flex items-center space-x-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 border-none shadow-md hover:shadow-lg transition-all"
|
||||||
icon={<EditFilled />}>
|
icon={<EditFilled />}>
|
||||||
创建课程
|
{id ? "编辑课程" : "创建课程"}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -28,9 +28,15 @@ const CollapsibleContent: React.FC<CollapsibleContentProps> = ({
|
||||||
{/* 包装整个内容区域的容器 */}
|
{/* 包装整个内容区域的容器 */}
|
||||||
<div
|
<div
|
||||||
ref={contentWrapperRef}
|
ref={contentWrapperRef}
|
||||||
|
style={{
|
||||||
|
maxHeight:
|
||||||
|
shouldCollapse && !isExpanded
|
||||||
|
? maxHeight
|
||||||
|
: undefined,
|
||||||
|
}}
|
||||||
className={`duration-300 ${
|
className={`duration-300 ${
|
||||||
shouldCollapse && !isExpanded
|
shouldCollapse && !isExpanded
|
||||||
? `max-h-[${maxHeight}px] overflow-hidden relative`
|
? ` overflow-hidden relative`
|
||||||
: ""
|
: ""
|
||||||
}`}>
|
}`}>
|
||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "@nice/common";
|
} from "@nice/common";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import React, { createContext, ReactNode, useEffect, useState } from "react";
|
import React, { createContext, ReactNode, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
interface CourseDetailContextType {
|
interface CourseDetailContextType {
|
||||||
editId?: string; // 添加 editId
|
editId?: string; // 添加 editId
|
||||||
|
@ -33,6 +33,7 @@ export function CourseDetailProvider({
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { read } = useVisitor();
|
const { read } = useVisitor();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const { lectureId } = useParams();
|
||||||
const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } =
|
const { data: course, isLoading }: { data: CourseDto; isLoading: boolean } =
|
||||||
(api.post as any).findFirst.useQuery(
|
(api.post as any).findFirst.useQuery(
|
||||||
{
|
{
|
||||||
|
@ -47,7 +48,7 @@ export function CourseDetailProvider({
|
||||||
|
|
||||||
const [selectedLectureId, setSelectedLectureId] = useState<
|
const [selectedLectureId, setSelectedLectureId] = useState<
|
||||||
string | undefined
|
string | undefined
|
||||||
>(undefined);
|
>(lectureId || undefined);
|
||||||
const { data: lecture, isLoading: lectureIsLoading } = (
|
const { data: lecture, isLoading: lectureIsLoading } = (
|
||||||
api.post as any
|
api.post as any
|
||||||
).findFirst.useQuery(
|
).findFirst.useQuery(
|
||||||
|
|
|
@ -61,7 +61,6 @@ export const CourseDetailDescription: React.FC = () => {
|
||||||
expandable: true,
|
expandable: true,
|
||||||
symbol: "展开",
|
symbol: "展开",
|
||||||
onExpand: () => console.log("展开"),
|
onExpand: () => console.log("展开"),
|
||||||
// collapseText: "收起",
|
|
||||||
}}>
|
}}>
|
||||||
{course?.content}
|
{course?.content}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
|
@ -51,7 +51,7 @@ export const CourseDetailDisplayArea: React.FC = () => {
|
||||||
<div className="w-full bg-white shadow-md rounded-lg border border-gray-200 p-6 ">
|
<div className="w-full bg-white shadow-md rounded-lg border border-gray-200 p-6 ">
|
||||||
<CollapsibleContent
|
<CollapsibleContent
|
||||||
content={lecture?.content || ""}
|
content={lecture?.content || ""}
|
||||||
maxHeight={150} // Optional, defaults to 150
|
maxHeight={500} // Optional, defaults to 150
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { UserMenu } from "@web/src/app/main/layout/UserMenu/UserMenu";
|
import { UserMenu } from "@web/src/app/main/layout/UserMenu/UserMenu";
|
||||||
import { CourseDetailContext } from "../CourseDetailContext";
|
import { CourseDetailContext } from "../CourseDetailContext";
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ const { Header } = Layout;
|
||||||
|
|
||||||
export function CourseDetailHeader() {
|
export function CourseDetailHeader() {
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const { isAuthenticated, user } = useAuth();
|
const { id } = useParams();
|
||||||
|
const { isAuthenticated, user, hasSomePermissions } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { course } = useContext(CourseDetailContext);
|
const { course } = useContext(CourseDetailContext);
|
||||||
|
|
||||||
|
@ -51,10 +52,15 @@ export function CourseDetailHeader() {
|
||||||
{isAuthenticated && (
|
{isAuthenticated && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate("/course/editor")}
|
onClick={() => {
|
||||||
|
const url = id
|
||||||
|
? `/course/${id}/editor`
|
||||||
|
: "/course/editor";
|
||||||
|
navigate(url);
|
||||||
|
}}
|
||||||
className="flex items-center space-x-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 border-none shadow-md hover:shadow-lg transition-all"
|
className="flex items-center space-x-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 border-none shadow-md hover:shadow-lg transition-all"
|
||||||
icon={<EditFilled />}>
|
icon={<EditFilled />}>
|
||||||
创建课程
|
{"编辑课程"}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// components/CourseSyllabus/LectureItem.tsx
|
// components/CourseSyllabus/LectureItem.tsx
|
||||||
|
|
||||||
import { Lecture, LectureType } from "@nice/common";
|
import { Lecture, LectureType, LessonTypeLabel } from "@nice/common";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
|
@ -19,15 +19,24 @@ export const LectureItem: React.FC<LectureItemProps> = ({
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
const { lectureId } = useParams();
|
const { lectureId } = useParams();
|
||||||
|
const isReading = useMemo(() => {
|
||||||
|
return lecture?.id === lectureId;
|
||||||
|
}, [lectureId, lecture]);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full flex items-center gap-4 p-4 hover:bg-gray-50 text-left transition-colors cursor-pointer"
|
className="w-full flex items-center gap-4 p-4 hover:bg-gray-200 text-left transition-colors cursor-pointer"
|
||||||
onClick={() => onClick(lecture.id)}>
|
onClick={() => onClick(lecture.id)}>
|
||||||
{lecture.type === LectureType.VIDEO && (
|
{lecture?.meta?.type === LectureType.VIDEO && (
|
||||||
<PlayCircleOutlined className="w-5 h-5 text-blue-500 flex-shrink-0" />
|
<div className="text-blue-500 flex items-center">
|
||||||
|
<PlayCircleOutlined className="w-5 h-5 flex-shrink-0" />
|
||||||
|
<span>{LessonTypeLabel[lecture?.meta?.type]}</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{lecture.type === LectureType.ARTICLE && (
|
{lecture?.meta?.type === LectureType.ARTICLE && (
|
||||||
<FileTextOutlined className="w-5 h-5 text-blue-500 flex-shrink-0" /> // 为文章类型添加图标
|
<div className="text-blue-500 flex items-center">
|
||||||
|
<FileTextOutlined className="w-5 h-5 text-blue-500 flex-shrink-0" />{" "}
|
||||||
|
<span>{LessonTypeLabel[lecture?.meta?.type]}</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-grow">
|
<div className="flex-grow">
|
||||||
<h4 className="font-medium text-gray-800">{lecture.title}</h4>
|
<h4 className="font-medium text-gray-800">{lecture.title}</h4>
|
||||||
|
@ -37,10 +46,6 @@ export const LectureItem: React.FC<LectureItemProps> = ({
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="flex items-center gap-1 text-sm text-gray-500">
|
|
||||||
<ClockCircleOutlined className="w-4 h-4" />
|
|
||||||
<span>{lecture.duration}分钟</span>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||||
import { SectionDto } from "@nice/common";
|
import { SectionDto } from "@nice/common";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { LectureItem } from "./LectureItem";
|
import { LectureItem } from "./LectureItem";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
// components/CourseSyllabus/SectionItem.tsx
|
|
||||||
interface SectionItemProps {
|
interface SectionItemProps {
|
||||||
section: SectionDto;
|
section: SectionDto;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
@ -13,57 +12,68 @@ interface SectionItemProps {
|
||||||
onLectureClick: (lectureId: string) => void;
|
onLectureClick: (lectureId: string) => void;
|
||||||
ref: React.RefObject<HTMLDivElement>;
|
ref: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SectionItem = React.forwardRef<HTMLDivElement, SectionItemProps>(
|
export const SectionItem = React.forwardRef<HTMLDivElement, SectionItemProps>(
|
||||||
({ section, index, isExpanded, onToggle, onLectureClick }, ref) => (
|
({ section, index, isExpanded, onToggle, onLectureClick }, ref) => {
|
||||||
<div
|
const { lectureId } = useParams();
|
||||||
ref={ref}
|
const isReading = useMemo(() => {
|
||||||
// initial={{ opacity: 0, y: 20 }}
|
return (section?.lectures || [])
|
||||||
// animate={{ opacity: 1, y: 0 }}
|
?.map((lecture) => lecture?.id)
|
||||||
// transition={{ duration: 0.3 }}
|
.includes(lectureId);
|
||||||
className="border rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow">
|
}, [lectureId, section]);
|
||||||
<button
|
return (
|
||||||
className="w-full flex items-center justify-between p-4 hover:bg-gray-50 transition-colors"
|
<div
|
||||||
onClick={() => onToggle(section.id)}>
|
ref={ref}
|
||||||
<div className="flex items-center gap-4">
|
className="border rounded-lg overflow-hidden bg-white shadow-sm hover:shadow-md transition-shadow">
|
||||||
<span className="text-lg font-medium text-gray-700">
|
<div
|
||||||
第{index}章
|
className="w-full flex items-center justify-between p-4 hover:bg-gray-50 transition-colors"
|
||||||
</span>
|
onClick={() => onToggle(section.id)}>
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex items-center gap-4">
|
||||||
<h3 className="text-left font-medium text-gray-900">
|
<span className="text-lg font-medium text-gray-700">
|
||||||
{section.title}
|
第{index}章
|
||||||
</h3>
|
</span>
|
||||||
<p className="text-sm text-gray-500">
|
<div className="flex flex-col items-start">
|
||||||
{section?.lectures?.length}节课 ·{" "}
|
<h3 className="text-left font-medium text-gray-900">
|
||||||
{/* {Math.floor(section?.totalDuration / 60)}分钟 */}
|
{section.title}
|
||||||
</p>
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{section?.lectures?.length}节课 ·
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" flex justify-end gap-2">
|
||||||
|
{isReading && (
|
||||||
|
<span className="text-primary text-sm">
|
||||||
|
正在学习中
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<motion.div
|
||||||
|
animate={{ rotate: isExpanded ? 180 : 0 }}
|
||||||
|
transition={{ duration: 0.2 }}>
|
||||||
|
<ChevronDownIcon className="w-5 h-5 text-gray-500" />
|
||||||
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<motion.div
|
|
||||||
animate={{ rotate: isExpanded ? 180 : 0 }}
|
|
||||||
transition={{ duration: 0.2 }}>
|
|
||||||
<ChevronDownIcon className="w-5 h-5 text-gray-500" />
|
|
||||||
</motion.div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ height: 0, opacity: 0 }}
|
initial={{ height: 0, opacity: 0 }}
|
||||||
animate={{ height: "auto", opacity: 1 }}
|
animate={{ height: "auto", opacity: 1 }}
|
||||||
exit={{ height: 0, opacity: 0 }}
|
exit={{ height: 0, opacity: 0 }}
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
className="border-t">
|
className="border-t">
|
||||||
{section.lectures.map((lecture) => (
|
{section.lectures.map((lecture) => (
|
||||||
<LectureItem
|
<LectureItem
|
||||||
key={lecture.id}
|
key={lecture.id}
|
||||||
lecture={lecture}
|
lecture={lecture}
|
||||||
onClick={onLectureClick}
|
onClick={onLectureClick}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -237,7 +237,10 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
|
||||||
{isContentVisible &&
|
{isContentVisible &&
|
||||||
!editing && // Conditionally render content based on type
|
!editing && // Conditionally render content based on type
|
||||||
(field?.meta?.type === LectureType.ARTICLE ? (
|
(field?.meta?.type === LectureType.ARTICLE ? (
|
||||||
<CollapsibleContent content={field?.content} />
|
<CollapsibleContent
|
||||||
|
maxHeight={200}
|
||||||
|
content={field?.content}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<VideoPlayer src={field?.meta?.videoUrl} />
|
<VideoPlayer src={field?.meta?.videoUrl} />
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default function CourseEditorLayout() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const handleNavigation = (item: NavItem, index: number) => {
|
const handleNavigation = (item: NavItem, index: number) => {
|
||||||
setSelectedSection(index);
|
setSelectedSection(index);
|
||||||
navigate(item.path, { replace: true });
|
navigate(item.path);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<CourseFormProvider editId={id}>
|
<CourseFormProvider editId={id}>
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface CollapsibleSectionProps {
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
key: string;
|
key: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
blank?: boolean
|
blank?: boolean;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
label: string;
|
label: string;
|
||||||
children?: Array<MenuItem>;
|
children?: Array<MenuItem>;
|
||||||
|
@ -69,7 +69,10 @@ const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
|
||||||
const isChildCollapsed = !expandedSections[item.key];
|
const isChildCollapsed = !expandedSections[item.key];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.key} className="flex flex-col mb-2 select-none" style={{ color: token.colorTextLightSolid }}>
|
<div
|
||||||
|
key={item.key}
|
||||||
|
className="flex flex-col mb-2 select-none"
|
||||||
|
style={{ color: token.colorTextLightSolid }}>
|
||||||
<motion.div
|
<motion.div
|
||||||
className={`flex items-center justify-between px-4 py-2 rounded-full ${hasChildren ? "cursor-pointer" : ""} `}
|
className={`flex items-center justify-between px-4 py-2 rounded-full ${hasChildren ? "cursor-pointer" : ""} `}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -78,7 +81,7 @@ const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
|
||||||
}
|
}
|
||||||
if (item.link) {
|
if (item.link) {
|
||||||
if (!item.blank) {
|
if (!item.blank) {
|
||||||
navigate(item.link, { replace: true });
|
navigate(item.link);
|
||||||
} else {
|
} else {
|
||||||
window.open(item.link, "_blank");
|
window.open(item.link, "_blank");
|
||||||
}
|
}
|
||||||
|
@ -86,12 +89,20 @@ const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
|
||||||
}}
|
}}
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{
|
animate={{
|
||||||
backgroundColor: isActive ? token.colorPrimaryBorder : token.colorPrimary,
|
backgroundColor: isActive
|
||||||
|
? token.colorPrimaryBorder
|
||||||
|
: token.colorPrimary,
|
||||||
}}
|
}}
|
||||||
whileHover={{ backgroundColor: token.colorPrimaryHover }}
|
whileHover={{
|
||||||
transition={{ type: "spring", stiffness: 300, damping: 25, duration: 0.3 }}
|
backgroundColor: token.colorPrimaryHover,
|
||||||
style={{ marginLeft: `${level * 16}px` }}
|
}}
|
||||||
>
|
transition={{
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 25,
|
||||||
|
duration: 0.3,
|
||||||
|
}}
|
||||||
|
style={{ marginLeft: `${level * 16}px` }}>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className=" items-center flex gap-2">
|
<div className=" items-center flex gap-2">
|
||||||
{item.icon && <span>{item.icon}</span>}
|
{item.icon && <span>{item.icon}</span>}
|
||||||
|
@ -100,8 +111,7 @@ const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
<Icon
|
<Icon
|
||||||
name={"caret-right"}
|
name={"caret-right"}
|
||||||
className={`ml-1 transition-transform duration-300 ${!isChildCollapsed ? "rotate-90" : ""}`}
|
className={`ml-1 transition-transform duration-300 ${!isChildCollapsed ? "rotate-90" : ""}`}></Icon>
|
||||||
></Icon>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{item.extra && <div className="ml-4">{item.extra}</div>}
|
{item.extra && <div className="ml-4">{item.extra}</div>}
|
||||||
|
@ -115,11 +125,10 @@ const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
|
||||||
// stiffness: 200,
|
// stiffness: 200,
|
||||||
// damping: 20,
|
// damping: 20,
|
||||||
type: "tween",
|
type: "tween",
|
||||||
duration: 0.2
|
duration: 0.2,
|
||||||
}}
|
}}
|
||||||
style={{ overflow: "hidden" }}
|
style={{ overflow: "hidden" }}
|
||||||
className="mt-1"
|
className="mt-1">
|
||||||
>
|
|
||||||
{renderItems(item.children, level + 1)}
|
{renderItems(item.children, level + 1)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
Loading…
Reference in New Issue