Skip to content

Commit

Permalink
Roadmap Search Redesign (Part 1) (#514)
Browse files Browse the repository at this point in the history
* Make mobile "add course" menu nicer
* Fix visual issues
* Empty Query = Show Course Bag
* minor tweaks
* Recreate basic drag functions
* Merge changes
* Fix linting error?
* Update SearchSidebar.scss
* Tap to add on mobile
* Change the add course modal to show course info
  - (preliminary)
* Tweak course card styles
* Make lone row still follow grid structure
* slightly increase button size
* Remove dead code
* comments
* cursor when dragging
* Improve placeholders
  • Loading branch information
Awesome-E authored Jan 14, 2025
1 parent 607f14d commit c4d9029
Show file tree
Hide file tree
Showing 34 changed files with 727 additions and 1,242 deletions.
748 changes: 124 additions & 624 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"bootstrap": "^4.6.2",
"node-html-parser": "^6.1.13",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-bootstrap": "^1.6.8",
"react-bootstrap-icons": "^1.11.4",
"react-bootstrap-range-slider": "^3.0.8",
Expand All @@ -23,6 +22,7 @@
"react-redux": "^9.1.2",
"react-responsive": "^10.0.0",
"react-router-dom": "^7.0.2",
"react-sortablejs": "^6.1.4",
"react-transition-group": "^4.4.5",
"react-twemoji": "^0.6.0",
"semantic-ui-css": "^2.5.0",
Expand Down Expand Up @@ -51,7 +51,6 @@
"devDependencies": {
"@peterportal/types": "workspace:*",
"@types/react": "^18.3.12",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^18.3.1",
"@types/react-google-recaptcha": "^2.1.9",
"@types/react-transition-group": "^4.4.11",
Expand Down
20 changes: 20 additions & 0 deletions site/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,23 @@ button {
}
}
}

.ui-overlay {
position: fixed;
inset: 0;
background-color: #6664;
opacity: 0;
transition: opacity 0.5s;
cursor: default;
pointer-events: none;

&.enter-active,
&.enter-done {
opacity: 1;
pointer-events: all;
}
&.exit {
pointer-events: none;
opacity: 0;
}
}
4 changes: 4 additions & 0 deletions site/src/component/SearchHitContainer/SearchHitContainer.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
.search-hit-container {
padding-top: 2vh;
overflow-y: auto;

> *:not(:last-child) {
margin-bottom: 20px;
}
}

.no-results {
Expand Down
15 changes: 14 additions & 1 deletion site/src/component/SearchModule/SearchModule.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
.search-module {
button {
font: inherit;
padding: 0;
appearance: none;
-webkit-appearance: none;
width: 40px;
text-align: center;
justify-content: center;
}
}

.search-bar {
border-color: #80bdff;
padding: 2vh 2vh;
padding: 8px 14px;
font-size: 16px;
}

[data-theme='dark'] {
Expand Down
44 changes: 20 additions & 24 deletions site/src/component/SearchModule/SearchModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState, useEffect, FC, useCallback } from 'react';
import './SearchModule.scss';
import Form from 'react-bootstrap/Form';
import InputGroup from 'react-bootstrap/InputGroup';
import { Bag, Search } from 'react-bootstrap-icons';
import { Search } from 'react-bootstrap-icons';

import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { CourseGQLData, ProfessorGQLData, SearchIndex } from '../../types/types';
Expand All @@ -21,7 +21,7 @@ interface SearchModuleProps {
const SearchModule: FC<SearchModuleProps> = ({ index }) => {
const dispatch = useAppDispatch();
const search = useAppSelector((state) => state.search[index]);
const showCourseBag = useAppSelector((state) => state.roadmap.showCourseBag);
const [searchQuery, setSearchQuery] = useState('');
const [pendingRequest, setPendingRequest] = useState<number | null>(null);

const fuzzySearch = useCallback(
Expand All @@ -48,48 +48,44 @@ const SearchModule: FC<SearchModuleProps> = ({ index }) => {
fuzzySearch(search.query);
}, [search.query, fuzzySearch]);

const searchAfterTimeout = (query: string) => {
if (pendingRequest) {
clearTimeout(pendingRequest);
const searchImmediately = (query: string) => {
if (pendingRequest) clearTimeout(pendingRequest);
if (location.pathname === '/roadmap') {
dispatch(setShowCourseBag(!query));
}
const timeout = window.setTimeout(() => {
if (query && query !== search.query) {
dispatch(setQuery({ index, query }));
setPendingRequest(null);
}, SEARCH_TIMEOUT_MS);
}
};
const searchAfterTimeout = (query: string) => {
setSearchQuery(query);
if (pendingRequest) clearTimeout(pendingRequest);
const timeout = window.setTimeout(() => searchImmediately(query), SEARCH_TIMEOUT_MS);
setPendingRequest(timeout);
};

const coursePlaceholder = 'Search a course number or department';
const coursePlaceholder = 'Search for a course...';
const professorPlaceholder = 'Search a professor';
const placeholder = index === 'courses' ? coursePlaceholder : professorPlaceholder;

return (
<div className="search-module">
<Form.Group>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text>
<Search />
</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
className="search-bar"
aria-label="search"
type="text"
type="search"
placeholder={placeholder}
onChange={(e) => searchAfterTimeout(e.target.value)}
defaultValue={search.query}
/>
{
// only show course bag icon on roadmap page
location.pathname === '/roadmap' && (
<InputGroup.Append>
<InputGroup.Text onClick={() => dispatch(setShowCourseBag(!showCourseBag))}>
<Bag style={{ color: showCourseBag ? 'var(--primary)' : 'var(--text-color)', cursor: 'pointer' }} />
</InputGroup.Text>
</InputGroup.Append>
)
}
<InputGroup.Append>
<button className="input-group-text" onClick={() => searchImmediately(searchQuery)}>
<Search />
</button>
</InputGroup.Append>
</InputGroup>
</Form.Group>
</div>
Expand Down
5 changes: 2 additions & 3 deletions site/src/component/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { setSidebarStatus } from '../../store/slices/uiSlice';
import Footer from '../Footer/Footer';
import trpc from '../../trpc';
import { useIsLoggedIn } from '../../hooks/isLoggedIn';
import UIOverlay from '../UIOverlay/UIOverlay';

const SideBar = () => {
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -111,9 +112,7 @@ const SideBar = () => {
<>
<div className="sidebar mini">{links}</div>
<CSSTransition in={showSidebar} timeout={500} unmountOnExit>
{/* Clicking this is only an alternative action to something that is already accessible */}
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events */}
<div className="sidebar-overlay" onClick={closeSidebar}></div>
<UIOverlay zIndex={399} onClick={closeSidebar}></UIOverlay>
</CSSTransition>
<CSSTransition in={showSidebar} timeout={500} unmountOnExit>
<div className="sidebar">
Expand Down
18 changes: 0 additions & 18 deletions site/src/component/SideBar/Sidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,6 @@
display: none;
}
}
.sidebar-overlay {
position: fixed;
inset: 0;
background-color: #6664;
z-index: 399;
opacity: 0;
transition: opacity 0.5s;
cursor: default;

&.enter-active,
&.enter-done {
opacity: 1;
}
&.exit {
pointer-events: none;
opacity: 0;
}
}

[data-theme='dark'] {
.sidebar:not(.mini) {
Expand Down
11 changes: 11 additions & 0 deletions site/src/component/UIOverlay/UIOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const UIOverlay = ({
zIndex,
...props
}: { zIndex: number; passedRef?: React.RefObject<HTMLDivElement> } & JSX.IntrinsicElements['div']) => {
const passedRef = props.passedRef;
delete props.passedRef;
// Clicking this is only an alternative action to something that is already accessible
return <div className="ui-overlay" {...props} ref={passedRef} style={{ zIndex }}></div>;
};

export default UIOverlay;
44 changes: 44 additions & 0 deletions site/src/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,47 @@ $mobile-cutoff: 800px;
padding: 16px 20px;
}
}

@mixin clickable($hoverVal: 0.8, $activeVal: 0.5) {
transition: opacity 0.2s;
cursor: pointer;
&:hover {
opacity: $hoverVal;
}
&:active {
opacity: $activeVal;
}
}

@mixin bottom-overlay($zIndex: 400) {
position: fixed;
top: unset;
left: 0;
width: 100%;
bottom: 0;
z-index: $zIndex;
max-height: calc(100% - 120px);
padding-bottom: 40px;
transform: translateY(100%);
transition: transform 0.3s;
&.enter,
&.enter-done {
transform: translateY(0);
}
}

@mixin bottom-button() {
position: fixed;
bottom: 0;
left: 0;
height: 40px;
background-color: var(--zot-blue);
width: 100%;
border: none;
appearance: none;
color: white;
font: inherit;
font-weight: 600;
font-size: 14px;
@include clickable(0.9, 0.7);
}
23 changes: 23 additions & 0 deletions site/src/helpers/sortable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ReactSortableProps, SortableOptions } from 'react-sortablejs';
import { CourseGQLData } from '../types/types';

const baseSortable: SortableOptions = {
animation: 150,
forceFallback: true,
fallbackOnBody: true,
filter: '.btn',
};

export const quarterSortable: SortableOptions & Partial<ReactSortableProps<CourseGQLData>> = {
...baseSortable,
setList: () => {},
group: { name: 'courses' },
};

export const courseSearchSortable: SortableOptions & Partial<ReactSortableProps<CourseGQLData>> = {
...baseSortable,
setList: () => {},
sort: false,
revertOnSpill: true,
group: { name: 'courses', pull: 'clone', put: false },
};
4 changes: 4 additions & 0 deletions site/src/helpers/util.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ export const unionTerms = (courseHistory: CourseWithTermsLookup) => {

return sortTerms(union);
};

export function deepCopy<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj));
}
13 changes: 13 additions & 0 deletions site/src/hooks/namedAcademicTerm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { quarterDisplayNames } from '../helpers/planner';
import { useAppSelector } from '../store/hooks';

export const useNamedAcademicTerm = () => {
const planner = useAppSelector((state) => state.roadmap.plans[state.roadmap.currentPlanIndex].content.yearPlans);
const { year, quarter } = useAppSelector((state) => state.roadmap.currentYearAndQuarter) || {};

if (year == null || quarter == null) return { year: null, quarter: null };

const quarterName = quarterDisplayNames[planner[year].quarters[quarter].name];
const yearName = planner[year].startYear + Number(quarterName === quarterDisplayNames.Fall);
return { year: yearName, quarter: quarterName };
};
69 changes: 68 additions & 1 deletion site/src/pages/RoadmapPage/AddCoursePopup.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,71 @@
@use '../../globals.scss';

.add-course-modal {
@include globals.bottom-overlay(500);
background-color: var(--overlay1);

&:is(.enter, .enter-done) + .ui-overlay {
opacity: 1;
pointer-events: all;
}

button.fixed {
@include globals.bottom-button();
}

.modal-dialog {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: auto;
margin: 0;
max-width: 100%;
.modal-content {
border-radius: 8px 8px 0 0;
}
}

.modal-header {
gap: 8px;
.spacer {
margin-left: auto;
}
}

h2,
.unit-count {
font-size: 20px;
}

button:not(.fixed),
button.close {
background: none;
font: inherit;
border: none;
width: 20px;
height: 20px;
display: flex;
align-items: center;
padding: 0;

@include globals.clickable(0.8, 0.5);
}

button.close-button {
width: 32px;
}

.quarter-offerings-section {
display: flex;
gap: 6px;
color: var(--text-secondary);
.quarter-indicator-container {
margin-left: 0;
}
}
}

.add-course-form {
border-radius: var(--border-radius);
padding: 2rem;
}
Loading

0 comments on commit c4d9029

Please sign in to comment.