>();
+ const { toggle, open } = useAccordionAnimation(innerRef, () =>
+ semester.courses.length === 0 ? '140px' : '170px',
+ );
const {
handleSelectCourses,
@@ -96,8 +103,8 @@ export const MemoizedSemesterTile = React.memo(
// QUESTION: isValid color?
return (
@@ -110,7 +117,7 @@ export const MemoizedSemesterTile = React.memo(
open ? '-rotate-90' : 'rotate-90'
} ml-auto h-3 w-3 transform cursor-pointer text-neutral-500 transition-all duration-500`}
fontSize="inherit"
- onClick={() => setOpen(!open)}
+ onClick={toggle}
/>
@@ -153,8 +160,8 @@ export const MemoizedSemesterTile = React.memo(
/>
)}
{semester.courses.map((course) => (
diff --git a/src/components/planner/TransferBank.tsx b/src/components/planner/TransferBank.tsx
index ad1c67844..03be8cd68 100644
--- a/src/components/planner/TransferBank.tsx
+++ b/src/components/planner/TransferBank.tsx
@@ -1,16 +1,21 @@
-import { FC, useState } from 'react';
+import { FC, useRef } from 'react';
import ChevronIcon from '@/icons/ChevronIcon';
+import useAccordionAnimation from '@/shared/useAccordionAnimation';
interface TransferBankProps {
transferCredits: string[];
}
const TransferBank: FC = ({ transferCredits }) => {
- const [open, setOpen] = useState(true);
+ const bankRef = useRef(null);
+ const { toggle, open } = useAccordionAnimation(bankRef, () => '50px');
return (
-
+
Transfer Credits
= ({ transferCredits }) => {
open ? '-rotate-90' : 'rotate-90'
} h-3 w-3 transform cursor-pointer text-neutral-500 transition-all duration-500`}
fontSize="inherit"
- onClick={() => setOpen(!open)}
+ onClick={toggle}
/>
{transferCredits.map((credit, i) => (
- void }) {
}
}
}
+
// TODO: Consider whether credit was earned or not before adding to credits list
const credits: Credit[] = [];
let isTransfer = true;
@@ -158,6 +159,7 @@ export default function CustomPlan({ onDismiss }: { onDismiss: () => void }) {
}
}
}
+
const dedupedCredits = credits.reduce((acc, curr) => {
if (!acc.some((i) => i.courseCode === curr.courseCode)) {
acc.push(curr);
diff --git a/src/shared/useAccordionAnimation.tsx b/src/shared/useAccordionAnimation.tsx
new file mode 100644
index 000000000..ee06a8b7d
--- /dev/null
+++ b/src/shared/useAccordionAnimation.tsx
@@ -0,0 +1,40 @@
+import { useState, useCallback } from 'react';
+
+const useAccordionAnimation = (
+ ref: React.RefObject | null,
+ getClosedHeight: () => string,
+) => {
+ const [open, setOpen] = useState(true);
+ function collapseSection(element: HTMLElement, closedHeight: string) {
+ const sectionHeight = element.scrollHeight;
+ element.style.height = sectionHeight + 'px';
+ setTimeout(() => {
+ element.style.height = closedHeight;
+ }, 100);
+ }
+ function expandSection(element: HTMLElement) {
+ const zero = performance.now();
+ function animate() {
+ const value = (performance.now() - zero) / 700;
+ if (value < 1) {
+ element.style.height = element.scrollHeight + 8 + 'px';
+ requestAnimationFrame(animate);
+ }
+ }
+ requestAnimationFrame(animate);
+ }
+
+ const toggle = useCallback(() => {
+ if (!ref || !ref.current) return;
+ setOpen((prev) => !prev);
+ if (open) {
+ collapseSection(ref.current, getClosedHeight());
+ } else {
+ expandSection(ref.current);
+ }
+ }, [open, ref, getClosedHeight]);
+
+ return { toggle, open };
+};
+
+export default useAccordionAnimation;