Skip to content

Commit 6b05d72

Browse files
committed
Merge branch 'redesign-2024' of https://github.com/isaacphysics/isaac-react-app into redesign/page/my-tests
2 parents 58f8767 + 5880ece commit 6b05d72

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1044
-593
lines changed
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 10 additions & 0 deletions
Loading
Lines changed: 11 additions & 0 deletions
Loading

src/app/components/elements/AffixButton.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import classNames from "classnames";
55

66
interface AffixProps {
77
affix: ReactNode;
8-
position: "prefix" | "suffix";
8+
position: "prefix" | "suffix" | "center";
99
type: "text" | "icon" | "icon-img";
10+
affixClassName?: string;
1011
}
1112

1213
export interface AffixButtonProps extends ButtonProps {
@@ -27,14 +28,28 @@ const renderAffix = (affix: AffixProps, className?: string) => {
2728
export const AffixButton = (props: AffixButtonProps) => {
2829
const { affix, children, className, ...rest } = props;
2930
return <Button {...rest} className={classNames("d-inline-flex align-items-center", className)}>
30-
{affix.position !== "suffix" && <>
31-
{renderAffix(affix, "me-2")}
31+
{affix.position === "prefix" && <>
32+
{renderAffix(affix, classNames("me-2", affix.affixClassName))}
3233
<Spacer/>
3334
</>}
34-
{children}
35+
{affix.position === "center" ? <>
36+
<Spacer/>
37+
{renderAffix(affix, affix.affixClassName)}
38+
<Spacer/>
39+
</> : children}
3540
{affix.position === "suffix" && <>
3641
<Spacer/>
37-
{renderAffix(affix, "ms-2")}
42+
{renderAffix(affix, classNames("ms-2", affix.affixClassName))}
3843
</>}
3944
</Button>;
4045
};
46+
47+
interface IconButtonProps extends ButtonProps {
48+
icon: string;
49+
affixClassName?: string;
50+
}
51+
52+
export const IconButton = (props: IconButtonProps) => {
53+
const { icon, className, affixClassName, ...rest } = props;
54+
return <AffixButton {...rest} className={classNames(className, "p-3")} affix={{affix: icon, position: "center", type: "icon", affixClassName}}/>;
55+
};

src/app/components/elements/PrintButton.tsx

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, {useState} from "react";
22
import {printingSettingsSlice, useAppDispatch} from "../../state";
33
import {Button} from "reactstrap";
4+
import { IconButton } from "./AffixButton";
5+
import { siteSpecific } from "../../services";
46

57
interface PrintProps {
68
questionPage?: boolean;
@@ -13,12 +15,24 @@ export const PrintButton = ({questionPage}: PrintProps ) => {
1315

1416
return questionPage ?
1517
<>
16-
<button
17-
className="print-icon btn-action"
18-
onClick={() => setQuestionPrintOpen(!questionPrintOpen)}
19-
aria-label="Print page"
20-
/>
21-
{questionPrintOpen && <div className="question-actions-link-box">
18+
{siteSpecific(
19+
<IconButton
20+
icon="icon-print"
21+
className="w-max-content h-max-content not-mobile"
22+
affixClassName="icon-color-black"
23+
aria-label="Print page"
24+
title="Print page"
25+
color="tint"
26+
data-bs-theme="neutral"
27+
onClick={() => setQuestionPrintOpen(!questionPrintOpen)}
28+
/>,
29+
<button
30+
className="print-icon btn-action not-mobile"
31+
onClick={() => setQuestionPrintOpen(!questionPrintOpen)}
32+
aria-label="Print page"
33+
/>
34+
)}
35+
{questionPrintOpen && <div className="question-actions-link-box not-mobile">
2236
<div className="question-actions-link text-nowrap">
2337
<Button
2438
size={"sm"}
@@ -46,12 +60,27 @@ export const PrintButton = ({questionPage}: PrintProps ) => {
4660
</div>}
4761
</>
4862
:
49-
<button
50-
className="print-icon btn-action"
51-
onClick={() => {
52-
dispatch(printingSettingsSlice.actions.enableHints(false));
53-
setTimeout(window.print, 100);
54-
}}
55-
aria-label="Print page"
56-
/>;
63+
siteSpecific(
64+
<IconButton
65+
icon="icon-print"
66+
className="w-max-content h-max-content not-mobile"
67+
affixClassName="icon-color-black"
68+
aria-label="Print page"
69+
title="Print page"
70+
color="tint"
71+
data-bs-theme="neutral"
72+
onClick={() => {
73+
dispatch(printingSettingsSlice.actions.enableHints(false));
74+
setTimeout(window.print, 100);
75+
}}
76+
/>,
77+
<button
78+
className="print-icon btn-action not-mobile"
79+
onClick={() => {
80+
dispatch(printingSettingsSlice.actions.enableHints(false));
81+
setTimeout(window.print, 100);
82+
}}
83+
aria-label="Print page"
84+
/>
85+
);
5786
};
Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from "react";
22
import {logAction, useAppDispatch} from "../../state";
3+
import { siteSpecific } from "../../services";
4+
import { IconButton } from "./AffixButton";
35

46
export const ReportButton = ({pageId} : {pageId?: string}) => {
57
const dispatch = useAppDispatch();
@@ -12,13 +14,28 @@ export const ReportButton = ({pageId} : {pageId?: string}) => {
1214
dispatch(logAction(eventDetails));
1315
}
1416

15-
return <button
16-
className="report-icon btn-action"
17-
aria-label="Report a problem (opens in new tab)"
18-
title="Report a problem (opens in new tab)"
19-
onClick={(event) => {
20-
logPageReport();
21-
window.open(pageId ? `/contact?preset=contentProblem&page=${pageId}` : "/contact?preset=contentProblem", "_blank");
22-
}}
23-
/>;
17+
return siteSpecific(
18+
<IconButton
19+
icon="icon-flag"
20+
className="w-max-content h-max-content"
21+
affixClassName="icon-color-black"
22+
aria-label="Report a problem (opens in new tab)"
23+
title="Report a problem (opens in new tab)"
24+
color="tint"
25+
data-bs-theme="neutral"
26+
onClick={() => {
27+
logPageReport();
28+
window.open(pageId ? `/contact?preset=contentProblem&page=${pageId}` : "/contact?preset=contentProblem", "_blank");
29+
}}
30+
/>,
31+
<button
32+
className="report-icon btn-action"
33+
aria-label="Report a problem (opens in new tab)"
34+
title="Report a problem (opens in new tab)"
35+
onClick={() => {
36+
logPageReport();
37+
window.open(pageId ? `/contact?preset=contentProblem&page=${pageId}` : "/contact?preset=contentProblem", "_blank");
38+
}}
39+
/>
40+
);
2441
};

src/app/components/elements/ShareLink.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, {useEffect, useRef, useState} from "react";
22
import {isMobile, isPhy, isTutorOrAbove, PATHS, siteSpecific, useOutsideCallback} from "../../services";
33
import {selectors, useAppSelector} from "../../state";
44
import classNames from "classnames";
5+
import { IconButton } from "./AffixButton";
56

67
export const ShareLink = ({linkUrl, reducedWidthLink, gameboardId, clickAwayClose, outline, className}: {linkUrl: string; reducedWidthLink?: boolean; gameboardId?: string; clickAwayClose?: boolean; outline?: boolean; className?: string}) => {
78
const [showShareLink, setShowShareLink] = useState(false);
@@ -41,8 +42,20 @@ export const ShareLink = ({linkUrl, reducedWidthLink, gameboardId, clickAwayClos
4142
const buttonAriaLabel = showShareLink ? "Hide share link" : "Get share link";
4243
const linkWidth = isMobile() || reducedWidthLink ? 192 : (shareUrl.length * siteSpecific(9, 6));
4344
const showDuplicateAndEdit = gameboardId && isTutorOrAbove(user);
44-
return <div ref={shareLinkDivRef} className={classNames("share-link-icon", className)}>
45-
<button className={siteSpecific("btn-action", classNames({"outline": outline}))} onClick={(e) => {e.preventDefault(); toggleShareLink();}} aria-label={buttonAriaLabel} />
45+
return <div ref={shareLinkDivRef} className={classNames(className, "share-link-icon", {"w-max-content d-inline-flex": isPhy})}>
46+
{siteSpecific(
47+
<IconButton
48+
icon="icon-share"
49+
className="w-max-content h-max-content"
50+
affixClassName="icon-color-black"
51+
aria-label="Share page"
52+
title="Share page"
53+
color="tint"
54+
data-bs-theme="neutral"
55+
onClick={(e) => { e.preventDefault(); toggleShareLink(); }}
56+
/>,
57+
<button className={siteSpecific("btn-action", classNames({"outline": outline}))} onClick={(e) => {e.preventDefault(); toggleShareLink();}} aria-label={buttonAriaLabel} />
58+
)}
4659
<div className={`share-link ${showShareLink ? "d-block" : ""} ${showDuplicateAndEdit ? "double-height" : ""}`} style={{width: linkWidth}}>
4760
<input type="text" readOnly ref={shareLink} value={shareUrl} onClick={(e) => e.preventDefault()} aria-label="Share URL" />
4861
{showDuplicateAndEdit && <React.Fragment>

src/app/components/elements/TitleAndBreadcrumb.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const BreadcrumbTrail = ({currentPageTitle, intermediateCrumbs = [], collectionT
2828
...intermediateCrumbs
2929
]);
3030

31-
return !!breadcrumbHistory.length && <Breadcrumb className={classNames("py-md-2 mb-3 mb-md-0 bread", siteSpecific("container-override", "px-md-0"))}>
31+
return !!breadcrumbHistory.length && <Breadcrumb className={classNames("mb-3 mb-md-0 bread", siteSpecific("container-override py-2", "px-md-0 py-md-2"))}>
3232
{breadcrumbHistory.map((breadcrumb) => formatBreadcrumbHistoryItem(breadcrumb, disallowLaTeX))}
3333
{isAda && formatBreadcrumbItem(currentPageTitle, disallowLaTeX)}
3434
</Breadcrumb>;

src/app/components/elements/cards/GameboardCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const GameboardCard = (props: GameboardCardProps) => {
113113
? above['md'](deviceSize) && <Button className="mb-2" color="keyline" onClick={(e) => {e.preventDefault(); setAssignmentsDetails.toggleAssignModal?.();}}>
114114
Assign{!isDefined(setAssignmentsDetails.groupCount) || setAssignmentsDetails.groupCount > 0 && " / Unassign"}
115115
</Button>
116-
: boardLink && <div className="card-share-link">
116+
: boardLink && <div className="d-flex justify-content-end card-share-link">
117117
<ShareLink linkUrl={boardLink} gameboardId={gameboard.id} reducedWidthLink clickAwayClose />
118118
</div>
119119
}

src/app/components/elements/layout/SidebarLayout.tsx

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { ChangeEvent, RefObject, useEffect, useRef, useState } from "react";
2-
import { Col, ColProps, RowProps, Input, Offcanvas, OffcanvasBody, OffcanvasHeader, Row } from "reactstrap";
2+
import { Col, ColProps, RowProps, Input, Offcanvas, OffcanvasBody, OffcanvasHeader, Row, DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from "reactstrap";
33
import partition from "lodash/partition";
44
import classNames from "classnames";
55
import { AssignmentDTO, ContentSummaryDTO, IsaacConceptPageDTO, QuestionDTO, QuizAssignmentDTO, QuizAttemptDTO, RegisteredUserDTO } from "../../../../IsaacApiTypes";
@@ -17,6 +17,7 @@ import { Spacer } from "../Spacer";
1717
import { StyledTabPicker } from "../inputs/StyledTabPicker";
1818
import { GroupSelector } from "../../pages/Groups";
1919
import { QuizRubricButton, SectionProgress } from "../quiz/QuizAttemptComponent";
20+
import { formatISODateOnly } from "../DateString";
2021
import { StyledDropdown } from "../inputs/DropdownInput";
2122

2223
export const SidebarLayout = (props: RowProps) => {
@@ -676,6 +677,116 @@ export const SignupSidebar = ({activeTab} : {activeTab: number}) => {
676677
</ContentSidebar>;
677678
};
678679

680+
interface SetQuizzesSidebarProps extends SidebarProps {
681+
titleFilter?: string;
682+
setTitleFilter: React.Dispatch<React.SetStateAction<string | undefined>>;
683+
};
684+
685+
export const SetQuizzesSidebar = (props: SetQuizzesSidebarProps) => {
686+
const { titleFilter, setTitleFilter } = props;
687+
const deviceSize = useDeviceSize();
688+
689+
return <ContentSidebar buttonTitle="Search & Filter">
690+
{above["lg"](deviceSize) && <div className="section-divider mt-5"/>}
691+
<h5>Search &amp; Filter</h5>
692+
<span className="quiz-filter-date-span mt-2">Title</span>
693+
<Input
694+
id="available-quizzes-title-filter" type="search"
695+
value={titleFilter} onChange={event => setTitleFilter(event.target.value)}
696+
placeholder="Search by title" aria-label="Search by title"
697+
/>
698+
</ContentSidebar>;
699+
};
700+
701+
interface ManageQuizzesSidebarProps extends SidebarProps {
702+
manageQuizzesTitleFilter: string;
703+
setManageQuizzesTitleFilter: React.Dispatch<React.SetStateAction<string>>;
704+
quizStartDate: Date | undefined;
705+
setQuizStartDate: React.Dispatch<React.SetStateAction<Date | undefined>>;
706+
quizSetDateFilterType: string;
707+
setQuizSetDateFilterType: React.Dispatch<React.SetStateAction<string>>;
708+
quizDueDate: Date | undefined;
709+
setQuizDueDate: React.Dispatch<React.SetStateAction<Date | undefined>>;
710+
quizDueDateFilterType: string;
711+
setQuizDueDateFilterType: React.Dispatch<React.SetStateAction<string>>;
712+
manageQuizzesGroupNameFilter: string;
713+
setManageQuizzesGroupNameFilter: React.Dispatch<React.SetStateAction<string>>;
714+
};
715+
716+
export const ManageQuizzesSidebar = (props: ManageQuizzesSidebarProps) => {
717+
const { manageQuizzesTitleFilter, setManageQuizzesTitleFilter, quizStartDate, setQuizStartDate,
718+
quizSetDateFilterType, setQuizSetDateFilterType, quizDueDate, setQuizDueDate, quizDueDateFilterType,
719+
setQuizDueDateFilterType, manageQuizzesGroupNameFilter, setManageQuizzesGroupNameFilter} = props;
720+
721+
const deviceSize = useDeviceSize();
722+
723+
const dateFilterTypeSelector = (dateFilterType: string, setDateFilterType: React.Dispatch<React.SetStateAction<string>>) => <UncontrolledDropdown>
724+
<DropdownToggle className="bg-transparent border-0 px-2" caret>{dateFilterType}</DropdownToggle>
725+
<DropdownMenu>
726+
<DropdownItem onClick={() => setDateFilterType('after')}>
727+
after
728+
</DropdownItem>
729+
<DropdownItem onClick={() => setDateFilterType('before')}>
730+
before
731+
</DropdownItem>
732+
<DropdownItem onClick={() => setDateFilterType('on')}>
733+
on
734+
</DropdownItem>
735+
</DropdownMenu>
736+
</UncontrolledDropdown>;
737+
738+
const titleFilterInput = <div className="my-2">
739+
<span className="quiz-filter-date-span">Title</span>
740+
<Input
741+
id="manage-quizzes-title-filter" type="search"
742+
value={manageQuizzesTitleFilter} onChange={event => setManageQuizzesTitleFilter(event.target.value)}
743+
placeholder="Search by title" aria-label="Search by title"
744+
/>
745+
</div>;
746+
747+
const groupFilterInput = <div className="my-2">
748+
<span className="quiz-filter-date-span">Group</span>
749+
<Input
750+
id="manage-quizzes-group-name-filter" type="search"
751+
value={manageQuizzesGroupNameFilter} onChange={event => setManageQuizzesGroupNameFilter(event.target.value)}
752+
placeholder="Search by group" aria-label="Search by group"
753+
/>
754+
</div>;
755+
756+
const setDateFilterInput = <div className="my-2">
757+
<div className="d-flex align-items-center">
758+
<span className="quiz-filter-date-span">Starting</span>
759+
{dateFilterTypeSelector(quizSetDateFilterType, setQuizSetDateFilterType)}
760+
</div>
761+
<Input
762+
id="manage-quizzes-set-date-filter" type="date" className="quiz-filter-date-input"
763+
value={quizStartDate && !isNaN(quizStartDate.valueOf()) ? formatISODateOnly(quizStartDate) : undefined} onChange={event => setQuizStartDate(new Date(event.target.value))}
764+
placeholder="Filter by set date" aria-label="Filter by set date"
765+
/>
766+
</div>;
767+
768+
const dueDateFilterInput = <div className="my-2">
769+
<div className="d-flex align-items-center">
770+
<span className="quiz-filter-date-span">Due</span>
771+
{dateFilterTypeSelector(quizDueDateFilterType, setQuizDueDateFilterType)}
772+
</div>
773+
<Input
774+
id="manage-quizzes-due-date-filter" type="date" className="quiz-filter-date-input"
775+
value={quizDueDate && !isNaN(quizDueDate.valueOf()) ? formatISODateOnly(quizDueDate) : undefined} onChange={event => setQuizDueDate(new Date(event.target.value))}
776+
placeholder="Filter by due date" aria-label="Filter by due date"
777+
/>
778+
</div>;
779+
780+
return <ContentSidebar buttonTitle="Search & Filter">
781+
{above["lg"](deviceSize) && <div className="section-divider mt-5"/>}
782+
<h5>Search & Filter</h5>
783+
{titleFilterInput}
784+
{groupFilterInput}
785+
{setDateFilterInput}
786+
{dueDateFilterInput}
787+
</ContentSidebar>;
788+
};
789+
679790
interface QuizStatusCheckboxProps extends React.HTMLAttributes<HTMLLabelElement> {
680791
status: QuizStatus;
681792
statusFilter: QuizStatus[];

0 commit comments

Comments
 (0)