Skip to content

Commit

Permalink
[ENH] Made UI responsive for smaller screens (#339)
Browse files Browse the repository at this point in the history
* Implemented a mui theme

* Added logo to assets

* Replaced logo url with local in navbar

* Implemented loading animation for resultcontainer

* Set the `textTransform` for buttons to `none`

* Updated components' layout

* Removed unncessary styles

* Modified modalities' bg color

* Updated affected test

* Removed full screen condition from `AuthDialog` component

* Implemented `SmallScreenSizeDialog` component

* Updated app layout

* Implemented `SmallScreenSizeDialog` component test

* Added a test for `SmallScreenSizeDialog`

* Renamed `Alert` e2e test to `Feedback`

* Addressed comment from PR review
  • Loading branch information
rmanaem authored Nov 5, 2024
1 parent bd57b48 commit d60f970
Show file tree
Hide file tree
Showing 17 changed files with 216 additions and 123 deletions.
2 changes: 1 addition & 1 deletion cypress/component/AuthDialog.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const props = {
onClose: () => {},
};

describe('ContinuousField', () => {
describe('AuthDialog', () => {
it('Displays a MUI dialog with the title and "sing in with google" button', () => {
cy.mount(
<GoogleOAuthProvider clientId="mock-client-id">
Expand Down
5 changes: 3 additions & 2 deletions cypress/component/ResultCard.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ describe('ResultCard', () => {
cy.get('[data-cy="card-some uuid"]').should('contain', '5 subjects match / 10 total subjects');
cy.get('[data-cy="card-some uuid-checkbox"] input').should('be.checked');
cy.get('[data-cy="card-some uuid"] button')
.eq(1)
.should('contain', 'ASL')
.should('have.class', 'bg-zinc-800');
.should('have.css', 'background-color', 'rgb(113, 113, 122)');
cy.get('[data-cy="card-some uuid"] button')
.eq(2)
.should('contain', 'DWI')
.should('have.class', 'bg-red-700');
.should('have.css', 'background-color', 'rgb(205, 92, 92)');

cy.get('[data-cy="card-some uuid-available-pipelines-button"]').trigger('mouseover', {
force: true,
Expand Down
18 changes: 18 additions & 0 deletions cypress/component/SmallScreenSizeDialog.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import SmallScreenSizeDialog from '../../src/components/SmallScreenSizeDialog';

const props = {
onClose: () => {},
};

describe('SmallScreenSizeDialog', () => {
it('Displays a MUI dialog with the title and "sing in with google" button', () => {
cy.mount(<SmallScreenSizeDialog open onClose={props.onClose} />);
cy.get('[data-cy="small-screen-size-dialog"]').should('be.visible');
cy.get('[data-cy="small-screen-size-dialog"]').should('contain', 'Unsupported Screen Size');
cy.get('[data-cy="small-screen-size-dialog"]').should(
'contain',
'not optimized for use on smaller screens'
);
cy.get('[data-cy="close-small-screen-size-dialog-button"]').should('be.visible');
});
});
9 changes: 8 additions & 1 deletion cypress/e2e/Alert.cy.ts → cypress/e2e/Feedback.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
nodeOptions,
} from '../fixtures/mocked-responses';

describe('Alert', () => {
describe('Feedback', () => {
it('Correctly displays and dismisses the alert', () => {
cy.intercept(
{
Expand Down Expand Up @@ -71,4 +71,11 @@ describe('Alert', () => {
cy.get('[data-cy="openneuro-alert"]').find('[data-testid="CloseIcon"]').click();
cy.get('[data-cy="openneuro-alert"]').should('not.exist');
});
it.only('Displays and closes small screen size dialog', () => {
cy.viewport(766, 500);
cy.visit('/');
cy.get('[data-cy="small-screen-size-dialog"]').should('be.visible');
cy.get('[data-cy="close-small-screen-size-dialog-button"]').click();
cy.get('[data-cy="small-screen-size-dialog"]').should('not.exist');
});
});
45 changes: 31 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import axios, { AxiosResponse } from 'axios';
import { Alert, Grow, IconButton } from '@mui/material';
import useMediaQuery from '@mui/material/useMediaQuery';
import CloseIcon from '@mui/icons-material/Close';
import { SnackbarKey, SnackbarProvider, closeSnackbar, enqueueSnackbar } from 'notistack';
import { jwtDecode } from 'jwt-decode';
Expand All @@ -24,9 +25,15 @@ import ResultContainer from './components/ResultContainer';
import Navbar from './components/Navbar';
import AuthDialog from './components/AuthDialog';
import ChatbotFeature from './components/Chatbot';
import SmallScreenSizeDialog from './components/SmallScreenSizeDialog';
import './App.css';
import logo from './assets/logo.png';

function App() {
// Screen is considered small if the width is less than 768px (according to tailwind docs)
const [isScreenSizeSmall, setIsScreenSizeSmall] = useState<boolean>(
useMediaQuery('(max-width: 767px)')
);
const [diagnosisOptions, setDiagnosisOptions] = useState<AttributeOption[]>([]);
const [assessmentOptions, setAssessmentOptions] = useState<AttributeOption[]>([]);
const [availableNodes, setAvailableNodes] = useState<NodeOption[]>([
Expand Down Expand Up @@ -427,15 +434,14 @@ function App() {

return (
<>
<div>
{enableAuth && (
<AuthDialog
open={openAuthDialog}
onAuth={(credential) => login(credential)}
onClose={() => setOpenAuthDialog(false)}
/>
)}
</div>
{enableAuth && (
<AuthDialog
open={openAuthDialog}
onAuth={(credential) => login(credential)}
onClose={() => setOpenAuthDialog(false)}
/>
)}
<SmallScreenSizeDialog open={isScreenSizeSmall} onClose={() => setIsScreenSizeSmall(false)} />
<SnackbarProvider
autoHideDuration={6000}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
Expand Down Expand Up @@ -476,10 +482,11 @@ function App() {
</>
)}

<div>{enableChatbot && <ChatbotFeature setResult={setResult} />}</div>
{enableChatbot && <ChatbotFeature setResult={setResult} />}

<div className="grid grid-cols-4 gap-4">
<div>
<div className="flex flex-wrap gap-3">
{/* 380px is currently the smallest width for the query form without dropdowns being affected */}
<div className="min-w-[380px] max-w-sm flex-1">
<QueryForm
availableNodes={availableNodes}
diagnosisOptions={diagnosisOptions}
Expand Down Expand Up @@ -508,8 +515,18 @@ function App() {
onSubmitQuery={() => submitQuery()}
/>
</div>
<div className={loading ? 'col-span-3 animate-pulse' : 'col-span-3'}>
<ResultContainer response={sortedResults || null} />
<div
className={
loading
? 'flex flex-1 animate-pulse items-center justify-center'
: 'min-w-[600px] flex-1'
}
>
{loading ? (
<img src={logo} alt="Logo" className="max-h-20 animate-bounce" />
) : (
<ResultContainer response={sortedResults || null} />
)}
</div>
</div>
</>
Expand Down
Binary file added src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions src/components/AuthDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
import Button from '@mui/material/Button';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useTheme } from '@mui/material/styles';
import { GoogleLogin } from '@react-oauth/google';

function AuthDialog({
Expand All @@ -16,11 +14,8 @@ function AuthDialog({
onAuth: (credential: string | undefined) => void;
onClose: () => void;
}) {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));

return (
<Dialog fullScreen={fullScreen} open={open} onClose={onClose} data-cy="auth-dialog">
<Dialog open={open} onClose={onClose} data-cy="auth-dialog">
<DialogTitle>
You must log in to a trusted identity provider in order to query all available nodes!
</DialogTitle>
Expand Down
1 change: 0 additions & 1 deletion src/components/CategoricalField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function CategoricalField({
{...params}
label={label}
placeholder="Select an option"
className="w-full"
/>
)}
multiple={multiple}
Expand Down
7 changes: 2 additions & 5 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Logout from '@mui/icons-material/Logout';
import Login from '@mui/icons-material/Login';
import Avatar from '@mui/material/Avatar';
import { enableAuth } from '../utils/constants';
import logo from '../assets/logo.png';

function Navbar({
isLoggedIn,
Expand Down Expand Up @@ -58,11 +59,7 @@ function Navbar({
<Toolbar className="my-4" data-cy="navbar">
<div className="flex w-full items-center justify-between">
<div className="flex items-center">
<img
src="https://raw.githubusercontent.com/neurobagel/documentation/main/docs/imgs/logo/neurobagel_logo.png"
alt="Logo"
height="60"
/>
<img src={logo} alt="Logo" height="60" />
<div className="ml-4">
<Badge badgeContent={latestReleaseTag}>
<Typography variant="h5">Neurobagel Query</Typography>
Expand Down
36 changes: 18 additions & 18 deletions src/components/QueryForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ function QueryForm({
minNumImagingSessionsHelperText !== '';

return (
<div className="grid grid-cols-2 grid-rows-12 gap-2">
<div className="col-span-2">
<div className="flex flex-col gap-2">
<div>
<CategoricalField
label="Neurobagel graph"
options={availableNodes.map((n) => ({
Expand All @@ -105,28 +105,28 @@ function QueryForm({
inputValue={selectedNode}
/>
</div>
<div className="row-start-2">
<div>
<ContinuousField
helperText={minAgeExceedsMaxAge ? '' : minAgeHelperText}
label="Minimum age"
onFieldChange={updateContinuousQueryParams}
/>
</div>
<div className="row-start-2">
<div>
<ContinuousField
helperText={minAgeExceedsMaxAge ? '' : maxAgeHelperText}
label="Maximum age"
onFieldChange={updateContinuousQueryParams}
/>
</div>
{minAgeExceedsMaxAge && (
<div className="col-span-2">
<div>
<FormHelperText error>
Value of maximum age must be greater than or equal to value of minimum age
</FormHelperText>
</div>
)}
<div className="col-span-2">
<div>
<CategoricalField
label="Sex"
options={Object.entries(sexes).map(([key, value]) => ({
Expand All @@ -137,9 +137,9 @@ function QueryForm({
inputValue={sex}
/>
</div>
<div className="col-span-2 row-start-4">
<div className="grid grid-cols-12 items-center gap-4">
<div className="col-span-9">
<div>
<div className="flex flex-row items-center gap-3">
<div className="flex-1">
<CategoricalField
label="Diagnosis"
options={diagnosisOptions.map((d) => ({
Expand All @@ -151,7 +151,7 @@ function QueryForm({
disabled={isControl}
/>
</div>
<div>
<div className="flex-1">
<FormControlLabel
data-cy="healthy-control-checkbox"
control={<Checkbox name="healthyControl" />}
Expand All @@ -161,29 +161,29 @@ function QueryForm({
</div>
</div>
</div>
<div className="col-span-2 row-start-5">
<div>
<ContinuousField
helperText={minNumImagingSessionsHelperText}
label="Minimum number of imaging sessions"
onFieldChange={updateContinuousQueryParams}
/>
</div>
<div className="col-span-2 row-start-6">
<div>
<ContinuousField
helperText={minNumPhenotypicSessionsHelperText}
label="Minimum number of phenotypic sessions"
onFieldChange={updateContinuousQueryParams}
/>
</div>
<div className="col-span-2 row-start-7">
<div>
<CategoricalField
label="Assessment tool"
options={assessmentOptions.map((a) => ({ label: a.Label, id: a.TermURL }))}
onFieldChange={(label, value) => updateCategoricalQueryParams(label, value)}
inputValue={assessmentTool}
/>
</div>
<div className="col-span-2 row-start-8">
<div>
<CategoricalField
label="Imaging modality"
options={Object.entries(modalities).map(([, value]) => ({
Expand All @@ -194,7 +194,7 @@ function QueryForm({
inputValue={imagingModality}
/>
</div>
<div className="col-span-2 row-start-9">
<div>
<CategoricalField
label="Pipeline name"
options={Object.keys(pipelines).map((pipelineURI) => ({
Expand All @@ -211,7 +211,7 @@ function QueryForm({
title={<Typography variant="body1">Please select a pipeline name</Typography>}
placement="right"
>
<div className="col-span-2 row-start-10">
<div>
<CategoricalField
label="Pipeline version"
options={[]}
Expand All @@ -222,7 +222,7 @@ function QueryForm({
</div>
</Tooltip>
) : (
<div className="col-span-2 row-start-10">
<div>
<CategoricalField
label="Pipeline version"
options={Object.values(pipelines[(pipelineName as FieldInputOption).id]).map((v) => ({
Expand All @@ -235,7 +235,7 @@ function QueryForm({
</div>
)}

<div className={pipelineName ? 'col-span-2 row-start-11' : 'row-start-11'}>
<div>
<Button
data-cy="submit-query-button"
disabled={disableSubmit}
Expand Down
43 changes: 26 additions & 17 deletions src/components/ResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,24 @@ const ResultCard = memo(
}) => (
<Card data-cy={`card-${datasetUUID}`}>
<CardContent>
<div className="grid grid-cols-12 items-center gap-2">
<div className="col-end-1">
<Checkbox
data-cy={`card-${datasetUUID}-checkbox`}
checked={checked}
onChange={() => onCheckboxChange(datasetUUID)}
/>
<div className="grid grid-cols-3 items-center">
<div className="flex flex-row items-center">
<div>
<Checkbox
data-cy={`card-${datasetUUID}-checkbox`}
checked={checked}
onChange={() => onCheckboxChange(datasetUUID)}
/>
</div>
<div>
<Typography variant="h5">{datasetName}</Typography>
<Typography variant="subtitle1">from {nodeName}</Typography>
<Typography variant="subtitle2">
{numMatchingSubjects} subjects match / {datasetTotalSubjects} total subjects
</Typography>
</div>
</div>
<div className="col-span-6 col-start-1">
<Typography variant="h5">{datasetName}</Typography>
<Typography variant="subtitle1">from {nodeName}</Typography>
<Typography variant="subtitle2">
{numMatchingSubjects} subjects match / {datasetTotalSubjects} total subjects
</Typography>
</div>
<div className="col-span-2 col-start-7">
<div className="justify-self-center">
{Object.entries(pipelines).length === 0 ? (
<Button
data-cy={`card-${datasetUUID}-available-pipelines-button`}
Expand Down Expand Up @@ -89,13 +91,20 @@ const ResultCard = memo(
</Tooltip>
)}
</div>
<div className="col-span-2 col-start-11 justify-self-end">
<div className="justify-self-end">
<ButtonGroup>
{imageModals.sort().map((modal) => (
<Button
key={modal}
variant="contained"
className={`${modalities[modal].bgColor} shadow-none hover:bg-gray-400 hover:shadow-none`}
disableElevation
sx={{
backgroundColor: modalities[modal].bgColor,
'&:hover': {
backgroundColor: modalities[modal].bgColor,
cursor: 'default',
},
}}
>
{modalities[modal].name}
</Button>
Expand Down
Loading

0 comments on commit d60f970

Please sign in to comment.