From 9a51f68f5f2e24f1641d9dbb2f05782f15d4941b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 28 May 2024 07:10:34 +0200 Subject: [PATCH] chore: new create project dialog UI fixes (#7167) This PR addresses several related fixes to the new project creation dialog to prevent unnecessary growing and shifting: - use a fixed width for the guidance sidebar - use a fixed height for the guidance code snippet - use a fixed height for the mobile guidance - use a fixed width for the mode selector button - cap description height This is a little tricky because we don't want the changes for the dialog to affect other forms. As such, I've added some new options you can use when you create the guidance components / sidebar. --- .../common/FormTemplate/FormTemplate.tsx | 175 +++++++++++++----- .../CreateProjectDialog.tsx | 1 + .../Project/CreateProject/NewProjectForm.tsx | 3 +- .../Project/CreateProject/SelectionButton.tsx | 18 +- 4 files changed, 144 insertions(+), 53 deletions(-) diff --git a/frontend/src/component/common/FormTemplate/FormTemplate.tsx b/frontend/src/component/common/FormTemplate/FormTemplate.tsx index 1783b6a95d..bc681b6d40 100644 --- a/frontend/src/component/common/FormTemplate/FormTemplate.tsx +++ b/frontend/src/component/common/FormTemplate/FormTemplate.tsx @@ -14,7 +14,7 @@ import Info from '@mui/icons-material/Info'; import Loader from '../Loader/Loader'; import copy from 'copy-to-clipboard'; import useToast from 'hooks/useToast'; -import type React from 'react'; +import React from 'react'; import { type ReactNode, useState } from 'react'; import { ReactComponent as MobileGuidanceBG } from 'assets/img/mobileGuidanceBg.svg'; import { formTemplateSidebarWidth } from './FormTemplate.styles'; @@ -36,6 +36,7 @@ interface ICreateProps { footer?: ReactNode; compact?: boolean; showGuidance?: boolean; + useFixedSidebar?: boolean; } const StyledContainer = styled('section', { @@ -54,7 +55,21 @@ const StyledContainer = styled('section', { }, })); -const StyledRelativeDiv = styled('div')(({ theme }) => relative); +const StyledMobileGuidanceWrapper = styled('div', { + shouldForwardProp: (prop) => !['guidanceHeight'].includes(prop.toString()), +})<{ guidanceHeight?: string }>(({ theme, guidanceHeight }) => ({ + ...relative, + // todo: review this. We're reaching down into a nested + // component, but due to the component structure, it'd be a + // lot of work to pass this down as a prop. + ...(guidanceHeight + ? { + aside: { + height: guidanceHeight, + }, + } + : {}), +})); const StyledMain = styled('div')(({ theme }) => ({ display: 'flex', @@ -151,20 +166,32 @@ const StyledInfoIcon = styled(Info)(({ theme }) => ({ fill: theme.palette.primary.contrastText, })); -const StyledSidebar = styled('aside')(({ theme }) => ({ - backgroundColor: theme.palette.background.sidebar, - padding: theme.spacing(4), - flexGrow: 0, - flexShrink: 0, - width: formTemplateSidebarWidth, - [theme.breakpoints.down(1100)]: { - width: '100%', - color: 'red', - }, - [theme.breakpoints.down(500)]: { - padding: theme.spacing(4, 2), - }, -})); +const StyledSidebar = styled('aside', { + shouldForwardProp: (prop) => + !['sidebarWidth', 'fixedCodeHeight'].includes(prop.toString()), +})<{ sidebarWidth?: string; fixedCodeHeight?: string }>( + ({ theme, sidebarWidth, fixedCodeHeight }) => ({ + backgroundColor: theme.palette.background.sidebar, + padding: theme.spacing(4), + flexGrow: 0, + flexShrink: 0, + width: sidebarWidth || formTemplateSidebarWidth, + [theme.breakpoints.down(1100)]: { + width: '100%', + color: 'red', + }, + [theme.breakpoints.down(500)]: { + padding: theme.spacing(4, 2), + }, + ...(fixedCodeHeight + ? { + pre: { + height: fixedCodeHeight, + }, + } + : {}), + }), +); const StyledDescriptionCard = styled('article')(({ theme }) => ({ display: 'flex', @@ -218,6 +245,7 @@ const FormTemplate: React.FC = ({ footer, compact, showGuidance = true, + useFixedSidebar, }) => { const { setToastData } = useToast(); const smallScreen = useMediaQuery(`(max-width:${1099}px)`); @@ -265,19 +293,23 @@ const FormTemplate: React.FC = ({ } }; + const SidebarComponent = useFixedSidebar ? FixedGuidance : Guidance; + return ( + - + } /> @@ -312,7 +344,7 @@ const FormTemplate: React.FC = ({ = ({ formatApiCode === undefined, !(showDescription || showLink), )} - + } /> @@ -380,7 +412,11 @@ interface IGuidanceProps { showLink?: boolean; } -const Guidance: React.FC = ({ +const GuidanceContent: React.FC< + IGuidanceProps & { + fixedDocumentationHeight?: string; + } +> = ({ description, children, documentationLink, @@ -388,38 +424,79 @@ const Guidance: React.FC = ({ documentationLinkLabel = 'Learn more', showDescription = true, showLink = true, + fixedDocumentationHeight, }) => { + const StyledDocumentationIconWrapper = styled('div')(({ theme }) => ({ + height: '2rem', + display: 'grid', + placeItems: 'center', + svg: { + width: '100%', + }, + })); + + const StyledDocumentationWrapper = styled('div')({ + height: fixedDocumentationHeight, + overflowY: 'auto', + }); + + const DocsWrapper = fixedDocumentationHeight + ? StyledDocumentationWrapper + : React.Fragment; + + return ( + <> + + + + {documentationIcon} + + } + /> + {description} + + } + /> + + + + + {documentationLinkLabel} + + + } + /> + + {children} + + ); +}; + +const Guidance: React.FC = (props) => { return ( - - - {description} - - } - /> + + + ); +}; - - - - {documentationLinkLabel} - - - } - /> - {children} +const FixedGuidance: React.FC = (props) => { + return ( + + ); }; diff --git a/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx b/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx index 35f8a588f2..cc0fe3efed 100644 --- a/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx +++ b/frontend/src/component/project/Project/CreateProject/CreateProjectDialog/CreateProjectDialog.tsx @@ -143,6 +143,7 @@ export const CreateProjectDialogue = ({ documentationLink={documentation.link?.url} documentationLinkLabel={documentation.link?.label} formatApiCode={formatApiCode} + useFixedSidebar > = ({ className='description' label='Description (optional)' multiline - maxRows={20} + maxRows={3} value={projectDesc} onChange={(e) => setProjectDesc(e.target.value)} data-testid={PROJECT_DESCRIPTION_INPUT} @@ -285,6 +285,7 @@ export const NewProjectForm: React.FC = ({ button={{ label: projectMode, icon: , + labelWidth: `${`protected`.length}ch`, }} search={{ label: 'Filter project mode options', diff --git a/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx b/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx index 16705f9e4d..002fcfe7c6 100644 --- a/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx +++ b/frontend/src/component/project/Project/CreateProject/SelectionButton.tsx @@ -1,6 +1,13 @@ import Search from '@mui/icons-material/Search'; import { v4 as uuidv4 } from 'uuid'; -import { Box, Button, InputAdornment, List, ListItemText } from '@mui/material'; +import { + Box, + Button, + InputAdornment, + List, + ListItemText, + styled, +} from '@mui/material'; import { type FC, type ReactNode, useRef, useState, useMemo } from 'react'; import { StyledCheckbox, @@ -75,7 +82,7 @@ const useSelectionManagement = ({ type CombinedSelectProps = { options: Array<{ label: string; value: string }>; onChange: (value: string) => void; - button: { label: string; icon: ReactNode }; + button: { label: string; icon: ReactNode; labelWidth?: string }; search: { label: string; placeholder: string; @@ -132,6 +139,11 @@ const CombinedSelect: FC = ({ const filteredOptions = options?.filter((option) => option.label.toLowerCase().includes(searchText.toLowerCase()), ); + + const ButtonLabel = styled('span')(() => ({ + width: button.labelWidth || 'unset', + })); + return ( <> @@ -145,7 +157,7 @@ const CombinedSelect: FC = ({ } }} > - {button.label} + {button.label}