-
Notifications
You must be signed in to change notification settings - Fork 19
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
Feature/deseng668: Added authoring side nav and bottom nav, modified routing, added authoring context, added base page for banner #2581
Changes from 10 commits
d0273d8
cb3439e
9b7a990
02911d0
68c61b0
c145283
e60b306
0702a23
d0cc4f7
66191a9
8569d78
33c3cf2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
import React from 'react'; | ||
import { AppBar, Theme, ThemeProvider, Box, useMediaQuery, Select, MenuItem, SelectChangeEvent } from '@mui/material'; | ||
import { Palette, colors, DarkTheme, BaseTheme } from 'styles/Theme'; | ||
import { When, Unless } from 'react-if'; | ||
import { BodyText } from 'components/common/Typography'; | ||
import { elevations } from 'components/common'; | ||
import { Button } from 'components/common/Input'; | ||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; | ||
import { faCheck } from '@fortawesome/pro-regular-svg-icons'; | ||
import { StatusCircle } from '../../view/AuthoringTab'; | ||
import pagePreview from 'assets/images/pagePreview.png'; | ||
import { AuthoringBottomNavProps, LanguageSelectorProps } from './types'; | ||
import { getLanguageValue } from './AuthoringTemplate'; | ||
|
||
const AuthoringBottomNav = ({ | ||
isDirty, | ||
isValid, | ||
isSubmitting, | ||
currentLanguage, | ||
setCurrentLanguage, | ||
languages, | ||
pageTitle, | ||
}: AuthoringBottomNavProps) => { | ||
const isMediumScreenOrLarger = useMediaQuery((theme: Theme) => theme.breakpoints.up('md')); | ||
const padding = { xs: '1rem 1rem', md: '1rem 1.5rem 1rem 2rem', lg: '1rem 3rem 1rem 2rem' }; | ||
|
||
const buttonStyles = { | ||
height: '2.6rem', | ||
borderRadius: '8px', | ||
border: 'none', | ||
padding: '0 1rem', | ||
minWidth: '8.125rem', | ||
fontSize: '0.9rem', | ||
}; | ||
|
||
return ( | ||
<AppBar | ||
component={'nav'} | ||
position="fixed" | ||
sx={{ | ||
backgroundColor: 'transparent', | ||
borderTopRightRadius: '16px', | ||
minHeight: '5rem', | ||
backgroundClip: 'padding-box', | ||
overflow: 'hidden', | ||
top: 'auto', | ||
left: 0, | ||
bottom: 0, | ||
boxShadow: elevations.default, | ||
}} | ||
data-testid="appbar-authoring-bottom-nav" | ||
> | ||
<Box | ||
sx={{ | ||
background: colors.surface.blue[90], | ||
minHeight: '5rem', | ||
justifyContent: 'flex-start', | ||
padding: padding, | ||
display: 'flex', | ||
alignItems: 'center', | ||
flexWrap: isMediumScreenOrLarger ? 'nowrap' : 'wrap', | ||
}} | ||
> | ||
<ThemeProvider theme={DarkTheme}> | ||
<Box | ||
sx={{ | ||
width: '18.8rem', | ||
minWidth: '18.8rem', | ||
marginBottom: isMediumScreenOrLarger ? '0' : '1rem', | ||
}} | ||
> | ||
<BodyText bold>Currently Authoring</BodyText> | ||
<BodyText sx={{ fontSize: '0.7rem', alignItems: 'center', marginTop: '-5px', display: 'flex' }}> | ||
<span>{pageTitle}</span> | ||
<span style={{ fontSize: '0.4rem', paddingLeft: '0.4rem', paddingRight: '0.4rem' }}> | ||
{'\u2B24'} | ||
</span> | ||
{getLanguageValue(currentLanguage, languages)} | ||
</BodyText> | ||
</Box> | ||
<Box | ||
sx={{ | ||
width: '43.75rem', | ||
justifyContent: 'flex-start', | ||
display: 'flex', | ||
}} | ||
> | ||
<ThemeProvider theme={BaseTheme}> | ||
<LanguageSelector | ||
currentLanguage={currentLanguage} | ||
setCurrentLanguage={setCurrentLanguage} | ||
languages={languages} | ||
isDirty={isDirty} | ||
isSubmitting={isSubmitting} | ||
/> | ||
</ThemeProvider> | ||
|
||
<Button | ||
disabled={!isValid || !isDirty || isSubmitting} | ||
type="submit" | ||
name="request_type" | ||
value="update" | ||
sx={{ | ||
...buttonStyles, | ||
margin: '0 1.2rem', | ||
}} | ||
> | ||
Save Section | ||
</Button> | ||
<Button | ||
disabled={!isValid || !isDirty || isSubmitting} | ||
type="submit" | ||
name="request_type" | ||
value="preview" | ||
sx={{ | ||
...buttonStyles, | ||
marginLeft: 'auto', | ||
}} | ||
> | ||
<img | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an icon instead of an image. Also for screenreaders, as long as it reads something like "preview," "page preview," or "editing preview" then we should be good as far as labelling goes. Jess should have been using FontAwesome icons so it should be available to us There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how to make it into an icon since this is a custom graphic that Jessica created for the button. I exported it from Figma and included it in our assets. If we have a process for converting images to icons then let me know and I will do it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I appreciate that you took the trouble of exactly replicating her design. Is it a PNG? We may still be able to use it as an icon then. You could also switch to a fontawesome icon button with one of these icons here, some of which are close enough to hers: https://fontawesome.com/search?q=magnifying&o=r However you do it, the image tag should be removed here - it'll cause confusion to screen readers. Practically speaking, all they should hear is "button, editing preview" or something like this. Hearing "image" in this context might throw them off. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked it up and apparently I can add alt="" and aria-hidden="true" in order to keep it invisible to a reader. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm alright with this as a quick fix this time. Could you please just test it once you implement? Ideally with VoiceOver |
||
style={{ | ||
paddingRight: '0.3rem', | ||
filter: !isValid || !isDirty || isSubmitting ? 'opacity(40%)' : 'opacity(100%)', | ||
}} | ||
src={pagePreview} | ||
alt="" | ||
aria-hidden="true" | ||
/> | ||
Preview | ||
</Button> | ||
</Box> | ||
<Box style={{ width: '25rem', display: 'flex' }}></Box> | ||
</ThemeProvider> | ||
</Box> | ||
</AppBar> | ||
); | ||
}; | ||
|
||
const LanguageSelector = ({ | ||
currentLanguage, | ||
setCurrentLanguage, | ||
languages, | ||
isDirty, | ||
isSubmitting, | ||
}: LanguageSelectorProps) => { | ||
const handleSelectChange = (event: SelectChangeEvent<string>) => { | ||
const newLanguageCode = event.target.value; | ||
if (isDirty && !isSubmitting) | ||
// todo: Replace this message with our stylized modal message. | ||
window.confirm( | ||
`Are you sure you want to switch to ${ | ||
getLanguageValue(newLanguageCode, languages) || 'another language' | ||
}? You have unsaved changes for the ${ | ||
getLanguageValue(currentLanguage, languages) || 'current' | ||
} language.`, | ||
); | ||
setCurrentLanguage(newLanguageCode); | ||
}; | ||
return ( | ||
<Select | ||
value={currentLanguage} | ||
onChange={handleSelectChange} | ||
sx={{ | ||
height: '2.6rem', | ||
borderRadius: '8px', | ||
width: '9.375rem', | ||
backgroundColor: colors.surface.gray[10], | ||
border: 'none', | ||
color: Palette.text.primary, | ||
fontSize: '0.9rem', | ||
cursor: 'pointer', | ||
'& .MuiSelect-icon': { | ||
color: Palette.text.primary, | ||
}, | ||
}} | ||
renderValue={(value) => { | ||
const completed = false; // todo: Replace with real "completed" boolean value once it is available. | ||
return ( | ||
<span> | ||
<When condition={completed}> | ||
<FontAwesomeIcon style={{ marginRight: '0.3rem' }} icon={faCheck} /> | ||
</When> | ||
{languages.find((language) => language.code === value)?.name} | ||
<Unless condition={completed}> | ||
<StatusCircle required={true} /> | ||
</Unless> | ||
</span> | ||
); | ||
}} | ||
> | ||
{languages.map((language) => { | ||
const completed = false; // todo: Replace with the real "completed" boolean values once they are available. | ||
return ( | ||
<MenuItem value={language.code} key={language.code}> | ||
<When condition={completed}> | ||
<FontAwesomeIcon style={{ marginRight: '0.3rem' }} icon={faCheck} /> | ||
</When> | ||
{language.name} | ||
<Unless condition={completed}> | ||
<StatusCircle required={true} /> | ||
</Unless> | ||
</MenuItem> | ||
); | ||
})} | ||
</Select> | ||
); | ||
}; | ||
|
||
export default AuthoringBottomNav; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,96 @@ | ||||||||
import React from 'react'; | ||||||||
import dayjs, { Dayjs } from 'dayjs'; | ||||||||
import { FormProvider, useForm } from 'react-hook-form'; | ||||||||
import { createSearchParams, useFetcher, Outlet } from 'react-router-dom'; | ||||||||
|
||||||||
export interface EngagementUpdateData { | ||||||||
id: number; | ||||||||
status_id: number; | ||||||||
taxon_id: number; | ||||||||
content_id: number; | ||||||||
name: string; | ||||||||
start_date: Dayjs; | ||||||||
end_date: Dayjs; | ||||||||
description: string; | ||||||||
rich_description: string; | ||||||||
banner_filename: string; | ||||||||
status_block: string[]; | ||||||||
title: string; | ||||||||
icon_name: string; | ||||||||
metadata_value: string; | ||||||||
send_report: boolean; | ||||||||
slug: string; | ||||||||
request_type: string; | ||||||||
} | ||||||||
|
||||||||
export const AuthoringContext = () => { | ||||||||
const fetcher = useFetcher(); | ||||||||
const locationArray = window.location.href.split('/'); | ||||||||
const slug = locationArray[locationArray.length - 1]; | ||||||||
Comment on lines
+28
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if you realized, but you're extracting all this information just to reconstruct your current location - try useLocation() instead.
Suggested change
|
||||||||
const defaultDateValue = dayjs(new Date(1970, 0, 1)); | ||||||||
const engagementUpdateForm = useForm<EngagementUpdateData>({ | ||||||||
defaultValues: { | ||||||||
id: 0, | ||||||||
status_id: 0, | ||||||||
taxon_id: 0, | ||||||||
content_id: 0, | ||||||||
name: '', | ||||||||
start_date: defaultDateValue, | ||||||||
end_date: defaultDateValue, | ||||||||
description: '', | ||||||||
rich_description: '', | ||||||||
banner_filename: '', | ||||||||
status_block: [], | ||||||||
title: '', | ||||||||
icon_name: '', | ||||||||
metadata_value: '', | ||||||||
send_report: undefined, | ||||||||
slug: '', | ||||||||
request_type: '', | ||||||||
}, | ||||||||
mode: 'onSubmit', | ||||||||
reValidateMode: 'onChange', | ||||||||
}); | ||||||||
const onSubmit = async (data: EngagementUpdateData) => { | ||||||||
fetcher.submit( | ||||||||
createSearchParams({ | ||||||||
id: 0 === data.id ? '' : data.id.toString(), | ||||||||
status_id: 0 === data.status_id ? '' : data.status_id.toString(), | ||||||||
taxon_id: 0 === data.taxon_id ? '' : data.taxon_id.toString(), | ||||||||
content_id: 0 === data.content_id ? '' : data.content_id.toString(), | ||||||||
name: data.name, | ||||||||
start_date: | ||||||||
'1970-01-01' === data.start_date.format('YYYY-MM-DD') ? '' : data.start_date.format('YYYY-MM-DD'), | ||||||||
end_date: | ||||||||
'1970-01-01' === data.start_date.format('YYYY-MM-DD') ? '' : data.end_date.format('YYYY-MM-DD'), | ||||||||
description: data.description, | ||||||||
rich_description: data.rich_description, | ||||||||
banner_filename: data.banner_filename, | ||||||||
status_block: data.status_block, | ||||||||
title: data.title, | ||||||||
icon_name: data.icon_name, | ||||||||
metadata_value: data.metadata_value, | ||||||||
send_report: getSendReportValue(data.send_report), | ||||||||
slug: data.slug, | ||||||||
request_type: data.request_type, | ||||||||
}), | ||||||||
{ | ||||||||
method: 'post', | ||||||||
action: `/engagements/${data.id}/details/authoring/${slug}`, | ||||||||
}, | ||||||||
); | ||||||||
}; | ||||||||
|
||||||||
const getSendReportValue = (valueToInterpret: boolean) => { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this like a utility function to convert incompatible data to that used in an HTTP request? Do we not have something for this already? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the data in createSearchParams all needs to be converted to strings. In this particular case, I just needed to convert a boolean value to a string equivalent as 'true' or 'false'. It's quite possible that there is another utility function that does this, I just don't know about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Booleans can't be used with truthy checks because they can return false if their value is false, even though I want that conditional to pass on a false value. So I need to check if the value is undefined separately, then give a value that is either 'true' or 'false'. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I was just curious how this operation for creating an HTTP request gets handled elsewhere and if we could use that. No worries if you want to just stick with this function you've created |
||||||||
if (undefined === valueToInterpret) { | ||||||||
return ''; | ||||||||
} | ||||||||
return valueToInterpret ? 'true' : 'false'; | ||||||||
}; | ||||||||
|
||||||||
return ( | ||||||||
<FormProvider {...engagementUpdateForm}> | ||||||||
<Outlet context={{ onSubmit }} /> | ||||||||
</FormProvider> | ||||||||
); | ||||||||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found a different Unicode character that doesn't require a font size adjustment