Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
"uuid": "^13.0.0",
"vaul": "^1.1.2",
"zod": "^4.3.6",
"zustand": "^5.0.11"
},
Expand Down
58 changes: 58 additions & 0 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { ReactNode } from "react";

import { cn } from "@/lib/classnames";

interface ButtonProps {
onClick?: () => void;
children: ReactNode;
tooltip: string;
inverted?: boolean; // Optional prop to invert colors
}

export default function Button({
onClick,
children,
tooltip,
inverted,
}: ButtonProps) {
return (
<div className="relative group flex flex-col items-center">
<button
onClick={onClick}
className={cn(
"p-3 rounded-full w-fit aspect-square flex items-center justify-center hover:cursor-pointer",
inverted
? "bg-carpipink/20 text-carpipink hover:text-darkblue hover:bg-carpipink transition-colors"
: "bg-darkblue/20 hover:bg-darkblue hover:text-carpipink transition-colors",
)}
>
Comment thread
mirmirmirr marked this conversation as resolved.
{children}
</button>

{/* Tooltip Container */}
<div
className={cn(
"absolute -bottom-7 z-50 hidden group-hover:flex flex-col items-center ",
inverted ? " text-darkblue" : " text-carpipink",
)}
>
<div
className={cn(
"w-2 h-2 rotate-45",
inverted ? "bg-carpipink" : "bg-darkblue",
)}
/>
<div
className={cn(
"bg-darkblue text-carpipink text-tiny! py-0.5 px-2 -mt-1 rounded-full whitespace-nowrap",
inverted
? "bg-carpipink text-darkblue"
: "bg-darkblue text-carpipink",
)}
>
{tooltip}
</div>
</div>
</div>
);
}
91 changes: 91 additions & 0 deletions src/components/SideDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { CSSProperties } from "react";

import { BiX } from "react-icons/bi";
import { Drawer } from "vaul";

import Button from "@/components/Button";
import Tag from "@/components/Tag";
import { cn } from "@/lib/classnames";
import useIsDesktop from "@/lib/hooks/useIsDesktop";
import { useCourseFilters } from "@/lib/stores/useFilterStore";
import { UserCourse } from "@/lib/types";

interface CourseDetailsDrawerProps {
open: boolean;
onOpenChange: (open: boolean) => void;
course: UserCourse;
}

export function CourseDetailsDrawer({
open,
onOpenChange,
course,
}: CourseDetailsDrawerProps) {
const isDesktop = useIsDesktop();
const { attrFilters, semFilters } = useCourseFilters(course.data);

const direction = isDesktop ? "left" : "bottom";
Comment thread
mirmirmirr marked this conversation as resolved.

return (
<Drawer.Root direction={direction} open={open} onOpenChange={onOpenChange}>
<Drawer.Portal>
<Drawer.Content
className={cn(
"md:left-2 md:top-2 bottom-2 fixed z-50",
"outline-none md:w-[calc(100%-16px)] lg:w-1/3 flex h-2/5 md:h-[calc(100%-16px)]",
)}
style={{ "--initial-transform": "calc(100% + 8px)" } as CSSProperties}
>
<div
className={cn(
" w-full relative grow p-8 flex flex-col rounded-2xl overflow-y-auto shadow-2xl shadow-darkblue",
"bg-darkblue md:border-[color-mix(in_oklab,var(--color-carpipink)_40%,white_60%)] md:border-2",
)}
>
<div className="flex items-start justify-between">
<Drawer.Title
aria-label={course.name}
className="flex flex-col mb-2"
>
<span className="text-sm font-light text-carpipink">
{course.name}
</span>
<span className="text-lg font-bold text-carpipink">
{course.data.title}
</span>
</Drawer.Title>

<Drawer.Close>
<Button
inverted
tooltip="Close Drawer"
onClick={() => onOpenChange(false)}
>
<BiX className="w-5 h-5" />
</Button>
</Drawer.Close>
</div>

<div className="flex flex-wrap gap-2 mb-4">
{attrFilters.map((attr) => {
return <Tag key={`${attr.type}-${attr.code}`} filter={attr} />;
})}
{semFilters?.map((semester) => {
return (
<Tag
key={`${semester.type}-${semester.code}`}
filter={semester}
/>
);
})}
</div>

<Drawer.Description className="mb-4 text-carpipink">
{course.data.desc_text || "No description available."}
</Drawer.Description>
</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}
8 changes: 4 additions & 4 deletions src/features/catalog/Tag.tsx → src/components/Tag.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { IoClose } from "react-icons/io5";

import { useCatalog } from "@/features/catalog/search/context/useCatalog";
import useIsDesktop from "@/lib/hooks/useIsDesktop.ts";
import { FilterData } from "@/lib/types";

Expand Down Expand Up @@ -51,6 +50,7 @@ interface TagProps {
isSelected?: boolean;
showCode?: boolean;
isRemovable?: boolean;
onToggle?: (filter: FilterData) => void;
}

export default function Tag({
Expand All @@ -59,8 +59,8 @@ export default function Tag({
isSelectable = false,
isSelected = false,
isRemovable = false,
onToggle,
}: TagProps) {
const { toggleFilter } = useCatalog();
const isDesktop = useIsDesktop();

// Determine content: value or code
Expand All @@ -78,8 +78,8 @@ export default function Tag({
: `border-darkblue text-darkblue ${accentColor.hover_bg}`;

const handleClick = () => {
if (isSelectable || isRemovable) {
toggleFilter(filter);
if ((isSelectable || isRemovable) && onToggle) {
onToggle(filter);
}
};

Expand Down
8 changes: 8 additions & 0 deletions src/components/course/Course.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { MdOutlineMoreHoriz, MdDragIndicator } from "react-icons/md";

import CourseBadge from "@/components/course/CourseBadge";
import CourseLabel from "@/components/course/CourseLabel";
import { CourseDetailsDrawer } from "@/components/SideDrawer";
import { SortableItemProps } from "@/features/dnd/props";
import CourseMenuContent from "@/features/planner/components/course/CourseMenuContent";
import CreditSelector from "@/features/planner/components/CreditSelector";
Expand Down Expand Up @@ -92,9 +93,11 @@ function PlannerCourseView({
course,
semesterId,
}: ViewProps) {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const menuOptions = usePlannerCourse({
course,
semesterId: semesterId ?? null,
onOpenDetails: () => setIsDrawerOpen(true),
});

const isTouch = useIsTouch();
Expand Down Expand Up @@ -176,6 +179,11 @@ function PlannerCourseView({
</Popover.Content>
</Popover.Root>
</div>
<CourseDetailsDrawer
open={isDrawerOpen}
onOpenChange={setIsDrawerOpen}
course={course}
/>
</div>
</ContextMenu.Trigger>

Expand Down
51 changes: 19 additions & 32 deletions src/components/header/ButtonTray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
BiDotsVerticalRounded,
} from "react-icons/bi";

import Button from "@/components/Button";
import Dialog from "@/components/Dialog";
import HeaderButton from "@/components/header/HeaderButton";
import { useCourseWorkspace } from "@/core/workspace/useCourseWorkspace";
import {
SaveFile,
Expand All @@ -29,6 +29,16 @@ export default function ButtonTray() {
const { exportPlan, importPlan } = useInputOutput();
const fileInputRef = useRef<HTMLInputElement>(null);

const handleResetClick = () => {
setActiveDialog("reset");
setIsOpen(false);
};

const handleExportClick = () => {
exportPlan();
setIsOpen(false);
};

const handleImportClick = () => {
fileInputRef.current?.click();
setIsOpen(false);
Expand Down Expand Up @@ -78,35 +88,15 @@ export default function ButtonTray() {

const buttonList = (
<>
<HeaderButton
onClick={() => {
setActiveDialog("reset");
setIsOpen(false);
}}
tooltip="Reset Workspace"
>
<Button onClick={handleResetClick} tooltip="Reset Workspace">
<BiReset className="w-5 h-5" />
</HeaderButton>

<HeaderButton
onClick={() => {
exportPlan();
setIsOpen(false);
}}
tooltip="Export Plan"
>
</Button>
Comment thread
mirmirmirr marked this conversation as resolved.
<Button onClick={handleExportClick} tooltip="Export Plan">
<BiExport className="w-5 h-5" />
</HeaderButton>

<HeaderButton
onClick={() => {
handleImportClick();
setIsOpen(false);
}}
tooltip="Import Plan"
>
</Button>
<Button onClick={handleImportClick} tooltip="Import Plan">
<BiImport className="w-5 h-5" />
</HeaderButton>
</Button>
Comment thread
mirmirmirr marked this conversation as resolved.
</>
);

Expand All @@ -123,12 +113,9 @@ export default function ButtonTray() {
<div className="absolute right-5 top-0 h-full flex items-center z-50">
<div className="hidden md:flex gap-2">{buttonList}</div>
<div className="md:hidden relative flex flex-col items-center justify-center">
<HeaderButton
onClick={() => setIsOpen(!isOpen)}
tooltip="More Options"
>
<Button onClick={() => setIsOpen(!isOpen)} tooltip="More Options">
<BiDotsVerticalRounded className="w-5 h-5" />
</HeaderButton>
</Button>

<AnimatePresence>
{isOpen && (
Expand Down
36 changes: 0 additions & 36 deletions src/components/header/HeaderButton.tsx

This file was deleted.

Loading
Loading