Skip to content

Commit

Permalink
feat: 🎸 SQFormScrollableCardsMenuWrapper
Browse files Browse the repository at this point in the history
A wrapper to map CardPopoverMenu items to instances of
SQFormScrollableCard to put many forms "into" one card.

✅ Closes: #347
  • Loading branch information
lucas-homer committed Sep 3, 2021
1 parent 4fe288f commit 12aa521
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardHeader, makeStyles} from '@material-ui/core';
import {CardPopoverMenu} from 'scplus-shared-components';

const useStyles = makeStyles(theme => {
return {
card: {
display: 'grid',
gridTemplateColumns: '1fr',
gridTemplateRows: 'auto 1fr auto',
gridTemplateAreas: `'header' 'content' 'footer'`,
height: '100%'
},
cardHeader: {
gridArea: 'header',
borderBottom: '1px solid rgba(0, 0, 0, 0.12)',
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px`,
/**
* Have to do this because CardPopoverMenu adds a few extra
* pixels from a rogue span, which screws up alignment compared
* to normal SQFormScrollableCard headers or its more generic
* counterpart in MIAV, AdminViewCard.
*/
height: '70px',
alignItems: 'center'
},
action: {
alignSelf: 'unset',
marginTop: 'unset'
}
};
});

function getSelectedComponent(selectedTab, children) {
if (Array.isArray(children)) {
const selectedFormComponent = children.find(
child => child.props.id === selectedTab.value
);
return selectedFormComponent;
}
return children;
}

export default function SQFormScrollableCardsMenuWrapper({
title,
menuItems,
children
}) {
const classes = useStyles();

const [selectedTab, setSelectedTab] = React.useState(menuItems[0]);

const handleChange = selectedMenuItemValue => {
const newSelection = menuItems.find(
item => item.value === selectedMenuItemValue
);

if (selectedTab.value !== newSelection.value) {
setSelectedTab(newSelection);
}
};

const SelectedForm = React.useMemo(() => {
const selected = getSelectedComponent(selectedTab, children);
return selected;
}, [selectedTab, children]);

return (
<Card raised={true} elevation={1} square={true} className={classes.card}>
<CardHeader
title={title}
className={classes.cardHeader}
titleTypographyProps={{variant: 'h4'}}
action={
<CardPopoverMenu
tabs={menuItems}
selectedTab={selectedTab}
selectTab={handleChange}
/>
}
/>
{SelectedForm}
</Card>
);
}

SQFormScrollableCardsMenuWrapper.propTypes = {
/** At least one instance of SQFormScrollableCard where each has
* prop `isHeaderDisabled` === true AND each has a unique string
* `id` prop value that maps to a `value` property of one of the
* `menuItems` objects.
*/
children: PropTypes.oneOfType([
PropTypes.element,
PropTypes.arrayOf(PropTypes.element)
]),
/** For each child instance of SQFormScrollableCard, we need a
* corresponding menuItem object with whatever the popover menu
* item label should be, and a `value` that matches the `id` prop
* value in its corresponding SQFormScrollableCard
* */
menuItems: PropTypes.arrayOf(
PropTypes.objectOf({
label: PropTypes.string,
value: PropTypes.string
})
).isRequired,
/** The Title for the Header component */
title: PropTypes.string
};
1 change: 1 addition & 0 deletions src/components/SQFormScrollableCardsMenuWrapper/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default} from './SQFormScrollableCardsMenuWrapper';
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ export {default as SQFormMultiSelect} from './components/SQForm/SQFormMultiSelec
export {default as SQFormMaskedTextField} from './components/SQForm/SQFormMaskedTextField';
export {default as SQFormHelperText} from './components/SQForm/SQFormHelperText';
export {default as SQFormScrollableCard} from './components/SQFormScrollableCard';
export {default as SQFormScrollableCardsMenuWrapper} from './components/SQFormScrollableCardsMenuWrapper';
export {default as SQFormGuidedWorkflow} from './components/SQFormGuidedWorkflow';
export {default as SQFormMultiValue} from './components/SQForm/SQFormMultiValue';
153 changes: 153 additions & 0 deletions stories/SQFormScrollableCardsMenuWrapper.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from 'react';
import * as yup from 'yup';

import {
SQFormScrollableCardsMenuWrapper,
SQFormScrollableCard,
SQFormTextField,
SQFormCheckbox
} from '../src';
import {createDocsPage} from './utils/createDocsPage';

export default {
title: 'Forms/SQFormScrollableCardsMenuWrapper',
component: SQFormScrollableCardsMenuWrapper,
parameters: {
docs: {page: createDocsPage({showStories: false})}
}
};

export const sqFormScrollableCardsMenuWrapper = () => {
const menuItems = [
{
label: 'Details',
value: 'details'
},
{
label: 'Permissions',
value: 'permissions'
}
];

return (
<div style={{width: '100%', height: '100%'}}>
<SQFormScrollableCardsMenuWrapper
title="[selected user]"
menuItems={menuItems}
>
<ScrollableDetails
id="details" // maps to menuItems[0].value
/>
<ScrollablePermissions
id="permissions" // maps to menuItems[1].value
/>
</SQFormScrollableCardsMenuWrapper>
</div>
);
};

function ScrollableDetails() {
const initialValues = {
name: ''
};

const validationSchema = {
name: yup.string().required('Required')
};

const handleSubmit = (values, actions) => {
window.alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
actions.resetForm();
};

/**
* TODO: This code can/should be removed once this issue is resolved - https://github.com/SelectQuoteLabs/SQForm/issues/436
* Once this problem is resolved, we can remove this code block below and also remove the wrapper container around our SQFormScrollableCard
*/
const [calculatedHeight, setCalculatedHeight] = React.useState(0);

React.useEffect(() => {
const currentElement = document.getElementById(`ResultContainer`);
const topOffset = currentElement?.getBoundingClientRect().top;
const offsetBasedHeight = `calc(100vh - ${topOffset}px - 24px)`;
const parentHeight = currentElement?.parentElement?.clientHeight;
const parentTopOffset = currentElement?.parentElement?.getBoundingClientRect()
.top;
const topDifferential =
topOffset && parentTopOffset ? topOffset - parentTopOffset : 0;
const maxOffsetBasedHeight = `calc(${parentHeight}px - ${topDifferential}px)`;
const calculatedContainerHeight = `min(${offsetBasedHeight}, ${maxOffsetBasedHeight})`;

setCalculatedHeight(calculatedContainerHeight);
}, []);

return (
<SQFormScrollableCard
isHeaderDisabled={true}
initialValues={initialValues}
onSubmit={handleSubmit}
shouldRequireFieldUpdates={true}
validationSchema={validationSchema}
height={calculatedHeight}
title="notApplicableBecauseHeaderDisabled" // bug in SQFormScrollableCard bc it errors if no title prop even though it doesn't render its cardheader
>
<SQFormTextField
name="name"
label="Name"
size={12}
isRequired={Boolean(validationSchema)}
/>
</SQFormScrollableCard>
);
}

function ScrollablePermissions() {
const initialValues = {
isAdmin: false
};

const validationSchema = {
isAdmin: yup.boolean()
};

const handleSubmit = (values, actions) => {
window.alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
actions.resetForm();
};
/**
* TODO: This code can/should be removed once this issue is resolved - https://github.com/SelectQuoteLabs/SQForm/issues/436
* Once this problem is resolved, we can remove this code block below and also remove the wrapper container around our SQFormScrollableCard
*/
const [calculatedHeight, setCalculatedHeight] = React.useState(0);

React.useEffect(() => {
const currentElement = document.getElementById(`ResultContainer`);
const topOffset = currentElement?.getBoundingClientRect().top;
const offsetBasedHeight = `calc(100vh - ${topOffset}px - 24px)`;
const parentHeight = currentElement?.parentElement?.clientHeight;
const parentTopOffset = currentElement?.parentElement?.getBoundingClientRect()
.top;
const topDifferential =
topOffset && parentTopOffset ? topOffset - parentTopOffset : 0;
const maxOffsetBasedHeight = `calc(${parentHeight}px - ${topDifferential}px)`;
const calculatedContainerHeight = `min(${offsetBasedHeight}, ${maxOffsetBasedHeight})`;

setCalculatedHeight(calculatedContainerHeight);
}, []);

return (
<SQFormScrollableCard
isHeaderDisabled={true}
initialValues={initialValues}
onSubmit={handleSubmit}
shouldRequireFieldUpdates={true}
validationSchema={validationSchema}
height={calculatedHeight}
title="notApplicableBecauseHeaderDisabled"
>
<SQFormCheckbox name="isAdmin" label="Admin" size={12} />
</SQFormScrollableCard>
);
}

0 comments on commit 12aa521

Please sign in to comment.