-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 🎸 SQFormScrollableCardsMenuWrapper
A wrapper to map CardPopoverMenu items to instances of SQFormScrollableCard to put many forms "into" one card. ✅ Closes: #347
- Loading branch information
1 parent
4fe288f
commit 12aa521
Showing
4 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
src/components/SQFormScrollableCardsMenuWrapper/SQFormScrollableCardsMenuWrapper.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {default} from './SQFormScrollableCardsMenuWrapper'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |