Skip to content

Commit

Permalink
feat: implement scrollable form components
Browse files Browse the repository at this point in the history
Signed-off-by: Mason Hu <[email protected]>
  • Loading branch information
mas-who committed Jan 15, 2024
1 parent 216be28 commit 380bb64
Show file tree
Hide file tree
Showing 12 changed files with 784 additions and 680 deletions.
41 changes: 41 additions & 0 deletions src/components/ScrollableForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { DependencyList, FC, ReactNode, useEffect, useRef } from "react";
import useEventListener from "@use-it/event-listener";
import { getAbsoluteHeightBelow, getParentsBottomSpacing } from "util/helpers";

interface Props {
children: ReactNode;
dependencies: DependencyList;
belowId?: string;
}

const ScrollableForm: FC<Props> = ({
dependencies,
children,
belowId = "",
}) => {
const ref = useRef<HTMLDivElement>(null);

const updateTBodyHeight = () => {
const form = ref.current?.children[0];
if (!form) {
return;
}
const above = form.getBoundingClientRect().top + 1;
const below = getAbsoluteHeightBelow(belowId);
const parentsBottomSpacing = getParentsBottomSpacing(form as HTMLElement);
const offset = Math.ceil(above + below + parentsBottomSpacing);
const style = `height: calc(100vh - ${offset}px); min-height: calc(100vh - ${offset}px)`;
form.setAttribute("style", style);
};

useEventListener("resize", updateTBodyHeight);
useEffect(updateTBodyHeight, [...dependencies, ref]);

return (
<div ref={ref} className="scrollable-form">
{children}
</div>
);
};

export default ScrollableForm;
22 changes: 7 additions & 15 deletions src/components/ScrollableTable.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,28 @@
import React, { DependencyList, FC, ReactNode, useEffect, useRef } from "react";
import useEventListener from "@use-it/event-listener";
import { getParentsBottomSpacing } from "util/helpers";
import { getAbsoluteHeightBelow, getParentsBottomSpacing } from "util/helpers";

interface Props {
children: ReactNode;
dependencies: DependencyList;
belowId?: string;
}

const ScrollableTable: FC<Props> = ({ dependencies, children, belowId }) => {
const ScrollableTable: FC<Props> = ({
dependencies,
children,
belowId = "",
}) => {
const ref = useRef<HTMLDivElement>(null);

const getAbsoluteHeightBelow = () => {
const element = belowId ? document.getElementById(belowId) : undefined;
if (!element) {
return 0;
}
const style = window.getComputedStyle(element);
const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
const padding =
parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
return element.offsetHeight + margin + padding + 1;
};

const updateTBodyHeight = () => {
const table = ref.current?.children[0];
if (!table || table.children.length !== 2) {
return;
}
const tBody = table.children[1];
const above = tBody.getBoundingClientRect().top + 1;
const below = getAbsoluteHeightBelow();
const below = getAbsoluteHeightBelow(belowId);
const parentsBottomSpacing = getParentsBottomSpacing(table as HTMLElement);
const offset = Math.ceil(above + below + parentsBottomSpacing);
const style = `height: calc(100vh - ${offset}px); min-height: calc(100vh - ${offset}px)`;
Expand Down
114 changes: 59 additions & 55 deletions src/pages/instances/forms/EditInstanceDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { FC } from "react";
import { Col, Input, Row } from "@canonical/react-components";
import { Col, Input, Row, useNotify } from "@canonical/react-components";
import ProfileSelect from "pages/profiles/ProfileSelector";
import { FormikProps } from "formik/dist/types";
import { EditInstanceFormValues } from "pages/instances/EditInstance";
import { useSettings } from "context/useSettings";
import MigrateInstanceBtn from "pages/instances/actions/MigrateInstanceBtn";
import { isClusteredServer } from "util/settings";
import AutoExpandingTextArea from "components/AutoExpandingTextArea";
import ScrollableForm from "components/ScrollableForm";

export const instanceEditDetailPayload = (values: EditInstanceFormValues) => {
return {
Expand All @@ -26,72 +27,75 @@ const EditInstanceDetails: FC<Props> = ({ formik, project }) => {
const readOnly = formik.values.readOnly;
const { data: settings } = useSettings();
const isClustered = isClusteredServer(settings);
const notify = useNotify();

return (
<div className="details">
<Row>
<Col size={12}>
<Input
id="name"
name="name"
type="text"
label="Instance name"
help="Click the name in the header to rename the instance"
placeholder="Enter name"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
value={formik.values.name}
error={formik.touched.name ? formik.errors.name : null}
required
disabled={true}
/>
<AutoExpandingTextArea
id="description"
name="description"
label="Description"
placeholder="Enter description"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
value={formik.values.description}
dynamicHeight
disabled={readOnly}
/>
</Col>
</Row>
{isClustered && (
<ScrollableForm dependencies={[notify.notification]} belowId="form-footer">
<div className="details">
<Row>
<Col size={12}>
<Input
id="target"
name="target"
id="name"
name="name"
type="text"
label="Instance location"
value={formik.values.location}
label="Instance name"
help="Click the name in the header to rename the instance"
placeholder="Enter name"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
value={formik.values.name}
error={formik.touched.name ? formik.errors.name : null}
required
disabled={true}
/>
<AutoExpandingTextArea
id="description"
name="description"
label="Description"
placeholder="Enter description"
onBlur={formik.handleBlur}
onChange={formik.handleChange}
value={formik.values.description}
dynamicHeight
disabled={readOnly}
/>
</Col>
{!readOnly && (
<Col size={4}>
<MigrateInstanceBtn
instance={formik.values.name}
location={formik.values.location}
project={project}
onFinish={(newLocation: string) =>
void formik.setFieldValue("location", newLocation)
}
</Row>
{isClustered && (
<Row>
<Col size={12}>
<Input
id="target"
name="target"
type="text"
label="Instance location"
value={formik.values.location}
required
disabled={true}
/>
</Col>
)}
</Row>
)}
<ProfileSelect
project={project}
selected={formik.values.profiles}
setSelected={(value) => void formik.setFieldValue("profiles", value)}
readOnly={readOnly}
/>
</div>
{!readOnly && (
<Col size={4}>
<MigrateInstanceBtn
instance={formik.values.name}
location={formik.values.location}
project={project}
onFinish={(newLocation: string) =>
void formik.setFieldValue("location", newLocation)
}
/>
</Col>
)}
</Row>
)}
<ProfileSelect
project={project}
selected={formik.values.profiles}
setSelected={(value) => void formik.setFieldValue("profiles", value)}
readOnly={readOnly}
/>
</div>
</ScrollableForm>
);
};

Expand Down
Loading

0 comments on commit 380bb64

Please sign in to comment.