Skip to content

Question finder redesign #1026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 62 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
be5fd1c
Prefer namespace-independent RS imports
jacbn Jul 3, 2024
62b6c32
StyledCheckbox: colours, minor code cleanup
jacbn Jul 4, 2024
b219880
Genericise dropdown chevron styles
jacbn Jul 4, 2024
539eac0
Initial QF redesign; layout and filters
jacbn Jul 4, 2024
b526d49
Merge branch 'master' into question-finder-redesign
skyepurchase Jul 23, 2024
f934829
Implement Ada filter structure
skyepurchase Jul 24, 2024
3b1dcf4
Implement filter counts
skyepurchase Jul 24, 2024
b74b486
Implement filter bar functionality
skyepurchase Jul 25, 2024
b245f44
Search bar styling
skyepurchase Jul 25, 2024
5f652a8
Implement apply filters button
skyepurchase Jul 26, 2024
9d02b77
Implement temporary question status filters
skyepurchase Jul 29, 2024
b07bef7
Make filter button sticky within filter card
skyepurchase Jul 29, 2024
23a8348
Add difficulty modal link
skyepurchase Jul 29, 2024
cf6e563
Fix style changes on Physics
skyepurchase Jul 29, 2024
9058438
Add book filter options
skyepurchase Jul 29, 2024
1b04942
Revert book exclusion option
skyepurchase Jul 30, 2024
8929e04
Increase width of question finder on Physics
skyepurchase Jul 30, 2024
065bd2a
Implement visual filters
skyepurchase Jul 30, 2024
029cb07
Fix difficulty icon alignment
skyepurchase Jul 30, 2024
cbffd3e
Implement book question exclusion
skyepurchase Jul 29, 2024
148ca7c
Add topic count and clearing to Physics
skyepurchase Jul 31, 2024
5e3e9a0
Disable apply filters when no filters change
skyepurchase Jul 31, 2024
42f64b8
Refactor out QuestionFinderFilterPanel
mlt47 Jul 30, 2024
5046858
Refactor QF list state to use reducer
mlt47 Jul 30, 2024
1fea9dd
Use CSS only for CollapsibleList transitions
mlt47 Jul 30, 2024
072ef33
Remove exclude books check
mlt47 Jul 31, 2024
1a2691f
Remove unused code & comments from new QF
mlt47 Jul 31, 2024
77c28f9
Remove revision mode option
skyepurchase Jul 31, 2024
7aace79
Remove invalid exam boards based on stage
skyepurchase Jul 31, 2024
5df55c3
Improve filter panel on small screens
skyepurchase Aug 1, 2024
e829976
Fix closed filters on large screens
skyepurchase Aug 1, 2024
43748af
Implement new summary item difficulty styling
skyepurchase Aug 1, 2024
5f1790b
Add modal for difficulty explanation
jacbn Aug 5, 2024
6563965
Improve collapsible list animation
jacbn Aug 5, 2024
c4cfe92
Remove unnecessary div around collapsible lists
jacbn Aug 5, 2024
efc3806
Restyle question list group items
skyepurchase Aug 5, 2024
5a9108b
Restyle 'Load more' button
skyepurchase Aug 5, 2024
f7073b1
CSS improvements on lg screens
jacbn Aug 5, 2024
2e03f47
Correct sizing and y-alignment of progress icon
jacbn Aug 5, 2024
f35af71
Make search input button clickable
jacbn Aug 5, 2024
49ec3f7
Make the entire filter dropdown clickable on mobile
jacbn Aug 6, 2024
ece4a90
Tidy "no results" box, allow customising Collapsibles
jacbn Aug 6, 2024
66fe6ec
Trial displaying question difficulty on xs devices
jacbn Aug 6, 2024
57edbd5
Change Ada page title
skyepurchase Aug 5, 2024
142844c
Fix 'apply filters' padding
skyepurchase Aug 5, 2024
a5d24dd
Add Ada background image
skyepurchase Aug 5, 2024
b3cd8c5
Improve question block logic
skyepurchase Aug 6, 2024
2e320f5
Move difficulty icons below title on small screens
skyepurchase Aug 6, 2024
88a6e0a
Improve styling on Physics
skyepurchase Aug 6, 2024
71f604f
Revert commit 66fe6ec
skyepurchase Aug 6, 2024
a15eb43
Fill results from user context if no URL params provided
jacbn Aug 7, 2024
338d662
Tidy up hook calls, move related code together
jacbn Aug 7, 2024
65aba47
Continue refactoring for code clarity
jacbn Aug 7, 2024
4fc63c0
Update difficulty modal texts
jacbn Aug 12, 2024
266009b
Change hide complete filter wording on Physics
skyepurchase Aug 12, 2024
cf2df2d
Improve apply filters disable condition
skyepurchase Aug 12, 2024
115ab84
Use hexagons for Physics filter count
skyepurchase Aug 12, 2024
5e3b691
Fix ESLint warning
skyepurchase Aug 12, 2024
8c291c2
Fix buggy nested collapsible list behaviour
jacbn Aug 12, 2024
f61d385
Remove `data-targetHeight` on collapsibles' rows
jacbn Aug 12, 2024
2e595b2
Add white background to "Apply filters" button
jacbn Aug 12, 2024
5db0c53
Remove console log
jacbn Aug 12, 2024
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
3 changes: 3 additions & 0 deletions public/assets/common/icons/completed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/common/icons/filter-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/common/icons/incorrect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/assets/common/icons/not-started.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/IsaacAppTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ export interface QuestionSearchQuery {
stages?: string;
difficulties?: string;
examBoards?: string;
questionCategories?: string;
fasttrack?: boolean;
hideCompleted?: boolean;
startIndex?: number;
Expand Down
59 changes: 59 additions & 0 deletions src/app/components/elements/CollapsibleList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useLayoutEffect, useRef, useState } from "react";
import { Col, Row } from "reactstrap";
import { Spacer } from "./Spacer";
import { FilterCount } from "./svg/FilterCount";
import classNames from "classnames";

export interface CollapsibleListProps {
title?: string;
asSubList?: boolean;
expanded: boolean;
toggle: () => void;
numberSelected?: number;
children?: React.ReactNode;
className?: string;
}

export const CollapsibleList = (props: CollapsibleListProps) => {
const {expanded, toggle} = props;
const [expandedHeight, setExpandedHeight] = useState(0);
const listRef = useRef<HTMLDivElement>(null);
const headRef = useRef<HTMLDivElement>(null);

useLayoutEffect(() => {
if (!listRef.current) return;
setExpandedHeight(listRef.current.clientHeight);
}, [listRef.current]);

useLayoutEffect(() => {
if (expanded) {
setExpandedHeight(listRef?.current ? [...listRef.current.children].map(c =>
c.getAttribute("data-targetHeight") ? parseInt(c.getAttribute("data-targetHeight") as string) : c.clientHeight
).reduce((a, b) => a + b, 0) : 0);
}
}, [expanded, props.children]);

const title = props.title && props.asSubList ? props.title : <b>{props.title}</b>;

return <Col className={props.className} data-targetHeight={(headRef.current?.offsetHeight ?? 0) + (expanded ? expandedHeight : 0)}>
<div className="row collapsible-head" ref={headRef}>
<button className={classNames("w-100 d-flex align-items-center p-3 bg-white text-start", {"ps-4": props.asSubList})} onClick={toggle}>
{title && <span>{title}</span>}
<Spacer/>
{(props.numberSelected ?? 0) > 0
&& <FilterCount count={props.numberSelected ?? 0} />}
<img className={classNames("icon-dropdown-90", {"active": expanded})} src={"/assets/common/icons/chevron_right.svg"} alt="" />
</button>
</div>
<Row
className={`collapsible-body overflow-hidden ${expanded ? "open" : "closed"}`}
style={{height: expanded ? expandedHeight : 0, maxHeight: expanded ? expandedHeight : 0}}
>
<Col>
<div ref={listRef} className={classNames({"ms-2": props.asSubList})}>
{props.children}
</div>
</Col>
</Row>
</Col>;
};
61 changes: 46 additions & 15 deletions src/app/components/elements/StageAndDifficultySummaryIcons.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,52 @@
import React from "react";
import classNames from "classnames";
import {isAda, isPhy, STAGE, stageLabelMap} from "../../services";
import {simpleDifficultyLabelMap, siteSpecific, STAGE, stageLabelMap} from "../../services";
import {DifficultyIcons} from "./svg/DifficultyIcons";
import {ViewingContext} from "../../../IsaacAppTypes";
import { Difficulty } from "../../../IsaacApiTypes";

export function StageAndDifficultySummaryIcons({audienceViews, className}: {audienceViews: ViewingContext[], className?: string}) {
// FIXME find a better way than hiding the whole thing on mobile
return <div className={classNames(className, "mt-1", {"d-none d-sm-flex flex-wrap justify-content-end align-items-baseline": isAda, "d-sm-flex mt-md-0": isPhy})}>
{audienceViews.map((view, i) =>
<div key={`${view.stage} ${view.difficulty} ${view.examBoard}`} className={classNames("align-self-center", {"ms-sm-3 ms-md-2": isPhy && (i > 0), "d-flex d-md-block": isPhy, "d-block text-center mx-2 my-1": isAda})}>
{view.stage && view.stage !== STAGE.ALL && <div className="hierarchy-tags text-center">
{stageLabelMap[view.stage]}
</div>}
{view.difficulty && <div className={classNames("hierarchy-tags text-center", {"ms-md-0 ms-2": isPhy})}>
<DifficultyIcons difficulty={view.difficulty} />
</div>}
</div>)
}
</div>
export function StageAndDifficultySummaryIcons({audienceViews, className, stack}: {
audienceViews: ViewingContext[],
className?: string,
stack?: boolean,
}) {
const difficulties: Difficulty[] = audienceViews.map(v => v.difficulty).filter(v => v !== undefined);
return siteSpecific(
<div className={classNames(className, "mt-1 d-sm-flex mt-md-0")}>
{audienceViews.map((view, i) =>
<div key={`${view.stage} ${view.difficulty} ${view.examBoard}`} className={classNames("align-self-center d-flex d-md-block", {"ms-sm-3 ms-md-2": i > 0})}>
{view.stage && view.stage !== STAGE.ALL && <div className="hierarchy-tags text-center">
{stageLabelMap[view.stage]}
</div>}
{view.difficulty && <div className="hierarchy-tags text-center ms-md-0 ms-2">
<DifficultyIcons difficulty={view.difficulty} />
</div>}
</div>)
}
</div>,
<div className={classNames(className, "d-sm-flex flex-wrap mt-1 align-items-baseline", {"justify-content-end": !stack})}>
{
difficulties.every((v, _i, arr) => v === arr[0])
? <div key={`${difficulties[0]}`} className={classNames("align-self-center d-flex align-items-center")}>
{difficulties.length > 0 && <>
<div className="hierarchy-tags text-center me-2">
{simpleDifficultyLabelMap[difficulties[0]]}
</div>
<div className="hierarchy-tags text-center">
<DifficultyIcons difficulty={difficulties[0]} blank classnames="mt-n1"/>
</div>
</>}
</div>
: audienceViews.map(view =>
<div key={`${view.difficulty}`} className={classNames("align-self-center d-block text-center mx-2 my-1")}>
{view.stage && view.stage !== STAGE.ALL && <div className="hierarchy-tags text-center">
{stageLabelMap[view.stage]}
</div>}
{view.difficulty && <div className="hierarchy-tags text-center">
<DifficultyIcons difficulty={view.difficulty} />
</div>}
</div>)
}
</div>,
);
}
13 changes: 8 additions & 5 deletions src/app/components/elements/inputs/StyledCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,32 @@ import classNames from "classnames";
// A custom checkbox, dealing with mouse and keyboard input. Pass `onChange((e : ChangeEvent) => void)`, `checked: bool`, and `label: Element` as required as props to use.

export const StyledCheckbox = (props : InputProps) => {

const {label, ignoreLabelHover, className, ...rest} = props;

const [checked, setChecked] = useState(props.checked ?? false);
const id = useMemo(() => {return (props.id ?? "") + "-" + v4();}, [props.id]);
const onCheckChange = (e: React.ChangeEvent<HTMLInputElement>) => {
props.onChange && props.onChange(e);
setChecked(e.target.checked);
};

// if `checked` is changed externally, reflect this here
useEffect(() => {
setChecked(props.checked ?? false);
}, [props.checked]);

return <div className="styled-checkbox-wrapper">
<div className="me-2 mb-3">
<div className="me-2 my-2">
{checked && <div className="tick"/>}
<Input {...props} id={id} type="checkbox" className={classNames(props.className ?? "", {"checked" : checked})}
<Input {...rest} id={id} type="checkbox" className={classNames(className ?? "", {"checked" : checked})}
onChange={(e) => onCheckChange(e)}
// If the user toggles with a keyboard, this does not change the state of the checkbox, so we need to do it manually (with modification to `target`
// as this is a keyboard event, not a change event). We also prevent default to avoid submitting the outer form.
onKeyDown={(e) => ifKeyIsEnter(() => {onCheckChange({...e, target: {...e.currentTarget, checked: !e.currentTarget.checked}}); e.preventDefault();})(e)}
/>
</div>
{props.label && <label htmlFor={id} className={classNames({"text-muted" : props.disabled, "pt-1" : isPhy})} {...props.label.props}/>}
{label && <label htmlFor={id} className={classNames({"text-muted" : props.disabled, "pt-1" : isPhy, "hover-override" : ignoreLabelHover})} {...label.props}/>}
<Spacer/>
</div>;
};
};
Loading