Skip to content

Commit

Permalink
Add page component (#1)
Browse files Browse the repository at this point in the history
* Add FormBodyPage Renderer Component

* Rename tab to page on utils/page

* Add storybook story for FormBodyPage Renderer Component

* Add pages and currentPageIndex to questionnarieStore

* Add next and previous button for Page

* Modify Page utils

* Make Page rendering logic on Top Level Renderer to help render

* Upgrade smart-forms-renderer packages version

* Change Page Next and Previous Button Design

* Add Custom Styled Fab for Page Button
  • Loading branch information
ryuuzake authored Jul 27, 2024
1 parent 3ffbd9d commit f006102
Show file tree
Hide file tree
Showing 22 changed files with 680 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/smart-forms-renderer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aehrc/smart-forms-renderer",
"version": "0.36.1",
"version": "0.37.0",
"description": "FHIR Structured Data Captured (SDC) rendering engine for Smart Forms",
"main": "lib/index.js",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { styled } from '@mui/material/styles';
import Fab from '@mui/material/Fab';

export const StandardFab = styled(Fab)(({ theme }) => ({
color: theme.palette.customButton.foreground,
background: theme.palette.customButton.background,
'&:hover': {
background: theme.palette.customButton.backgroundHover
}
}));
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ interface GroupHeadingProps extends PropsWithIsRepeatedAttribute {
qItem: QuestionnaireItem;
readOnly: boolean;
tabIsMarkedAsComplete?: boolean;
pageIsMarkedAsComplete?: boolean;
}

const GroupHeading = memo(function GroupHeading(props: GroupHeadingProps) {
const { qItem, readOnly, tabIsMarkedAsComplete, isRepeated } = props;
const { qItem, readOnly, tabIsMarkedAsComplete, pageIsMarkedAsComplete, isRepeated } = props;

const contextDisplayItems = getContextDisplays(qItem);

Expand All @@ -41,14 +42,15 @@ const GroupHeading = memo(function GroupHeading(props: GroupHeadingProps) {
}

const isTabHeading = tabIsMarkedAsComplete !== undefined;
const isPageHeading = pageIsMarkedAsComplete !== undefined;

return (
<>
<Box display="flex" alignItems="center" width="100%">
<Typography
variant="h6"
fontSize={isTabHeading ? 16 : 15}
color={readOnly && !isTabHeading ? 'text.secondary' : 'text.primary'}>
fontSize={isTabHeading || isPageHeading ? 16 : 15}
color={readOnly && (!isTabHeading || !isPageHeading) ? 'text.secondary' : 'text.primary'}>
<ItemLabelText qItem={qItem} />
</Typography>
<Box flexGrow={1} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
import type { QrRepeatGroup } from '../../../interfaces/repeatGroup.interface';
import useHidden from '../../../hooks/useHidden';
import type { Tabs } from '../../../interfaces/tab.interface';
import type { Pages } from '../../../interfaces/page.interface';
import GroupItemView from './GroupItemView';

interface GroupItemProps
Expand All @@ -41,6 +42,9 @@ interface GroupItemProps
tabIsMarkedAsComplete?: boolean;
tabs?: Tabs;
currentTabIndex?: number;
pageIsMarkedAsComplete?: boolean;
pages?: Pages;
currentPageIndex?: number;
}

function GroupItem(props: GroupItemProps) {
Expand All @@ -52,6 +56,9 @@ function GroupItem(props: GroupItemProps) {
tabIsMarkedAsComplete,
tabs,
currentTabIndex,
pageIsMarkedAsComplete,
pages,
currentPageIndex,
parentIsReadOnly,
parentIsRepeatGroup,
parentRepeatGroupIndex,
Expand Down Expand Up @@ -83,7 +90,7 @@ function GroupItem(props: GroupItemProps) {
}

if (!qItems || !qrItems) {
return <>Unable to load group, something has gone terribly wrong.</>;
return <>Group Item: Unable to load group, something has gone terribly wrong.</>;
}

// If an item has multiple answers, it is a repeat group
Expand All @@ -99,6 +106,9 @@ function GroupItem(props: GroupItemProps) {
tabIsMarkedAsComplete={tabIsMarkedAsComplete}
tabs={tabs}
currentTabIndex={currentTabIndex}
pageIsMarkedAsComplete={pageIsMarkedAsComplete}
pages={pages}
currentPageIndex={currentPageIndex}
parentIsReadOnly={parentIsReadOnly}
parentIsRepeatGroup={parentIsRepeatGroup}
parentRepeatGroupIndex={parentRepeatGroupIndex}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
PropsWithQrRepeatGroupChangeHandler
} from '../../../interfaces/renderProps.interface';
import type { Tabs } from '../../../interfaces/tab.interface';
import type { Pages } from '../../../interfaces/page.interface';
import GroupHeading from './GroupHeading';
import { GroupCard } from './GroupItem.styles';
import TabButtonsWrapper from './TabButtonsWrapper';
Expand All @@ -37,6 +38,7 @@ import Divider from '@mui/material/Divider';
import { getGroupCollapsible } from '../../../utils/qItem';
import useReadOnly from '../../../hooks/useReadOnly';
import { GroupAccordion } from './GroupAccordion.styles';
import PageButtonsWrapper from './PageButtonWrapper';

interface GroupItemViewProps
extends PropsWithQrItemChangeHandler,
Expand All @@ -51,6 +53,9 @@ interface GroupItemViewProps
tabIsMarkedAsComplete?: boolean;
tabs?: Tabs;
currentTabIndex?: number;
pageIsMarkedAsComplete?: boolean;
pages?: Pages;
currentPageIndex?: number;
}

function GroupItemView(props: GroupItemViewProps) {
Expand All @@ -63,12 +68,16 @@ function GroupItemView(props: GroupItemViewProps) {
tabIsMarkedAsComplete,
tabs,
currentTabIndex,
pageIsMarkedAsComplete,
pages,
currentPageIndex,
parentIsReadOnly,
parentIsRepeatGroup,
parentRepeatGroupIndex,
onQrItemChange,
onQrRepeatGroupChange
} = props;
console.log({ pages, currentPageIndex });

const readOnly = useReadOnly(qItem, parentIsReadOnly);

Expand All @@ -91,6 +100,7 @@ function GroupItemView(props: GroupItemViewProps) {
qItem={qItem}
readOnly={readOnly}
tabIsMarkedAsComplete={tabIsMarkedAsComplete}
pageIsMarkedAsComplete={pageIsMarkedAsComplete}
isRepeated={isRepeated}
/>
</AccordionSummary>
Expand All @@ -117,6 +127,7 @@ function GroupItemView(props: GroupItemViewProps) {

{/* Next tab button at the end of each tab group */}
<TabButtonsWrapper currentTabIndex={currentTabIndex} tabs={tabs} />
<PageButtonsWrapper currentPageIndex={currentPageIndex} pages={pages} />
</>
</AccordionDetails>
</GroupAccordion>
Expand All @@ -133,6 +144,7 @@ function GroupItemView(props: GroupItemViewProps) {
qItem={qItem}
readOnly={readOnly}
tabIsMarkedAsComplete={tabIsMarkedAsComplete}
pageIsMarkedAsComplete={pageIsMarkedAsComplete}
isRepeated={isRepeated}
/>
{childQItems.map((qItem: QuestionnaireItem, i) => {
Expand All @@ -155,6 +167,7 @@ function GroupItemView(props: GroupItemViewProps) {

{/* Next tab button at the end of each tab group */}
<TabButtonsWrapper currentTabIndex={currentTabIndex} tabs={tabs} />
<PageButtonsWrapper currentPageIndex={currentPageIndex} pages={pages} />
</GroupCard>
</QGroupContainerBox>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024 Commonwealth Scientific and Industrial Research
* Organisation (CSIRO) ABN 41 687 119 230.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import Iconify from '../../Iconify/Iconify';
import { StandardFab } from '../Button.styles';

interface NextPageButtonProps {
isDisabled: boolean;
onNextPageClick: () => void;
}

function NextPageButton(props: NextPageButtonProps) {
const { isDisabled, onNextPageClick } = props;

return (
<StandardFab size="small" aria-label="next" disabled={isDisabled} onClick={onNextPageClick}>
<Iconify icon="material-symbols:chevron-right-rounded" />
</StandardFab>
);
}

export default NextPageButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { memo } from 'react';
import Box from '@mui/material/Box';
import type { Pages } from '../../../interfaces/page.interface';
import { useQuestionnaireStore } from '../../../stores';
import NextPageButton from './NextPageButton';
import PreviousPageButton from './PreviousPageButton';
import useNextAndPreviousVisiblePages from '../../../hooks/useNextAndPreviousVisiblePages';

interface PageButtonsWrapperProps {
currentPageIndex?: number;
pages?: Pages;
}

const PageButtonsWrapper = memo(function PageButtonsWrapper(props: PageButtonsWrapperProps) {
const { currentPageIndex, pages } = props;

const switchPage = useQuestionnaireStore.use.switchPage();

const { previousPageIndex, nextPageIndex, numOfVisiblePages } = useNextAndPreviousVisiblePages(
currentPageIndex,
pages
);

const pagesNotDefined = currentPageIndex === undefined || pages === undefined;

// Event handlers
function handlePreviousPageButtonClick() {
if (previousPageIndex === null) {
return;
}

switchPage(previousPageIndex);

// Scroll to top of page
window.scrollTo(0, 0);
}

function handleNextPageButtonClick() {
if (nextPageIndex === null) {
return;
}

switchPage(nextPageIndex);

// Scroll to top of page
window.scrollTo(0, 0);
}

if (pagesNotDefined) {
return null;
}

const previousPageButtonHidden = previousPageIndex === null;
const nextPageButtonHidden = nextPageIndex === null;

// This is more of a fallback check to prevent the user from navigating to an invisble page if buttons are visble for some reason
const pageButtonsDisabled = numOfVisiblePages <= 1;

return (
<Box display="flex" mt={3}>
{previousPageButtonHidden ? null : (
<PreviousPageButton
isDisabled={pageButtonsDisabled}
onPreviousPageClick={handlePreviousPageButtonClick}
/>
)}
<Box flexGrow={1} />
{nextPageButtonHidden ? null : (
<NextPageButton
isDisabled={pageButtonsDisabled}
onNextPageClick={handleNextPageButtonClick}
/>
)}
</Box>
);
});

export default PageButtonsWrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024 Commonwealth Scientific and Industrial Research
* Organisation (CSIRO) ABN 41 687 119 230.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from 'react';
import Iconify from '../../Iconify/Iconify';
import { StandardFab } from '../Button.styles';

interface PreviousPageButtonProps {
isDisabled: boolean;
onPreviousPageClick: () => void;
}

function PreviousPageButton(props: PreviousPageButtonProps) {
const { isDisabled, onPreviousPageClick } = props;

return (
<StandardFab size="small" aria-label="back" disabled={isDisabled} onClick={onPreviousPageClick}>
<Iconify icon="material-symbols:chevron-left-rounded" />
</StandardFab>
);
}

export default PreviousPageButton;
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import { useQuestionnaireResponseStore, useQuestionnaireStore } from '../../stor
import cloneDeep from 'lodash.clonedeep';
import { getQrItemsIndex, mapQItemsIndex } from '../../utils/mapItem';
import { updateQrItemsInGroup } from '../../utils/qrItem';
import { everyIsPages } from '../../utils/page';
import type { QrRepeatGroup } from '../../interfaces/repeatGroup.interface';
import FormBodyPage from './FormBodyPage';

/**
* Main component of the form-rendering engine.
Expand Down Expand Up @@ -74,6 +76,25 @@ function BaseRenderer() {
// If an item has multiple answers, it is a repeat group
const topLevelQRItemsByIndex = getQrItemsIndex(topLevelQItems, topLevelQRItems, qItemsIndexMap);

const everyItemIsPage = everyIsPages(topLevelQItems);

if (everyItemIsPage) {
return (
<Fade in={true} timeout={500}>
<Container maxWidth="xl">
<FormBodyPage
topLevelQItems={topLevelQItems}
topLevelQRItems={topLevelQRItemsByIndex}
parentIsReadOnly={readOnly}
onQrItemChange={(newTopLevelQRItem) =>
handleTopLevelQRItemSingleChange(newTopLevelQRItem)
}
/>
</Container>
</Fade>
);
}

return (
<Fade in={true} timeout={500}>
<Container maxWidth="xl">
Expand Down
Loading

0 comments on commit f006102

Please sign in to comment.