From 12aa521ba7d8e934b403c8dfd857d0ba6a36205e Mon Sep 17 00:00:00 2001
From: Lucas Homer <33523751+lucas-homer@users.noreply.github.com>
Date: Fri, 3 Sep 2021 15:39:02 -0700
Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20SQFormScrollableCardsMen?=
=?UTF-8?q?uWrapper?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A wrapper to map CardPopoverMenu items to instances of
SQFormScrollableCard to put many forms "into" one card.
✅ Closes: #347
---
.../SQFormScrollableCardsMenuWrapper.js | 111 +++++++++++++
.../SQFormScrollableCardsMenuWrapper/index.js | 1 +
src/index.js | 1 +
...QFormScrollableCardsMenuWrapper.stories.js | 153 ++++++++++++++++++
4 files changed, 266 insertions(+)
create mode 100644 src/components/SQFormScrollableCardsMenuWrapper/SQFormScrollableCardsMenuWrapper.js
create mode 100644 src/components/SQFormScrollableCardsMenuWrapper/index.js
create mode 100644 stories/SQFormScrollableCardsMenuWrapper.stories.js
diff --git a/src/components/SQFormScrollableCardsMenuWrapper/SQFormScrollableCardsMenuWrapper.js b/src/components/SQFormScrollableCardsMenuWrapper/SQFormScrollableCardsMenuWrapper.js
new file mode 100644
index 00000000..848f40b2
--- /dev/null
+++ b/src/components/SQFormScrollableCardsMenuWrapper/SQFormScrollableCardsMenuWrapper.js
@@ -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 (
+
+
+ }
+ />
+ {SelectedForm}
+
+ );
+}
+
+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
+};
diff --git a/src/components/SQFormScrollableCardsMenuWrapper/index.js b/src/components/SQFormScrollableCardsMenuWrapper/index.js
new file mode 100644
index 00000000..b61e1e89
--- /dev/null
+++ b/src/components/SQFormScrollableCardsMenuWrapper/index.js
@@ -0,0 +1 @@
+export {default} from './SQFormScrollableCardsMenuWrapper';
diff --git a/src/index.js b/src/index.js
index 4dc237d1..8253634f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -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';
diff --git a/stories/SQFormScrollableCardsMenuWrapper.stories.js b/stories/SQFormScrollableCardsMenuWrapper.stories.js
new file mode 100644
index 00000000..671ec335
--- /dev/null
+++ b/stories/SQFormScrollableCardsMenuWrapper.stories.js
@@ -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 (
+
+
+
+
+
+
+ );
+};
+
+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 (
+
+
+
+ );
+}
+
+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 (
+
+
+
+ );
+}