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
MasWho committed Jan 16, 2024
1 parent 64ff69e commit e3f4515
Show file tree
Hide file tree
Showing 23 changed files with 236 additions and 132 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 updateFormHeight = () => {
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", updateFormHeight);
useEffect(updateFormHeight, [...dependencies, ref]);

return (
<div ref={ref} className="scrollable-form">
<div className="details">{children}</div>
</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
40 changes: 23 additions & 17 deletions src/components/forms/DiskDeviceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import DiskDeviceFormRoot from "./DiskDeviceFormRoot";
import DiskDeviceFormInherited from "./DiskDeviceFormInherited";
import DiskDeviceFormCustom from "./DiskDeviceFormCustom";
import classnames from "classnames";
import ScrollableForm from "components/ScrollableForm";

interface Props {
formik: InstanceAndProfileFormikProps;
Expand Down Expand Up @@ -58,23 +59,28 @@ const DiskDeviceForm: FC<Props> = ({ formik, project }) => {
"disk-device-form--edit": !formik.values.readOnly,
})}
>
{/* hidden submit to enable enter key in inputs */}
<Input type="submit" hidden />
<DiskDeviceFormRoot
formik={formik}
project={project}
pools={pools}
profiles={profiles}
/>
<DiskDeviceFormInherited
formik={formik}
inheritedVolumes={inheritedVolumes}
/>
<DiskDeviceFormCustom
formik={formik}
project={project}
inheritedVolumes={inheritedVolumes}
/>
<ScrollableForm
dependencies={[notify.notification]}
belowId="form-footer"
>
{/* hidden submit to enable enter key in inputs */}
<Input type="submit" hidden />
<DiskDeviceFormRoot
formik={formik}
project={project}
pools={pools}
profiles={profiles}
/>
<DiskDeviceFormInherited
formik={formik}
inheritedVolumes={inheritedVolumes}
/>
<DiskDeviceFormCustom
formik={formik}
project={project}
inheritedVolumes={inheritedVolumes}
/>
</ScrollableForm>
</div>
);
};
Expand Down
8 changes: 5 additions & 3 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,9 +27,10 @@ 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">
<ScrollableForm dependencies={[notify.notification]} belowId="form-footer">
<Row>
<Col size={12}>
<Input
Expand Down Expand Up @@ -91,7 +93,7 @@ const EditInstanceDetails: FC<Props> = ({ formik, project }) => {
setSelected={(value) => void formik.setFieldValue("profiles", value)}
readOnly={readOnly}
/>
</div>
</ScrollableForm>
);
};

Expand Down
8 changes: 6 additions & 2 deletions src/pages/instances/forms/InstanceCreateDetailsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Input,
Row,
Select,
useNotify,
} from "@canonical/react-components";
import ProfileSelect from "pages/profiles/ProfileSelector";
import SelectImageBtn from "pages/images/actions/SelectImageBtn";
Expand All @@ -17,6 +18,7 @@ import { LxdImageType, RemoteImage } from "types/image";
import InstanceLocationSelect from "pages/instances/forms/InstanceLocationSelect";
import UseCustomIsoBtn from "pages/images/actions/UseCustomIsoBtn";
import AutoExpandingTextArea from "components/AutoExpandingTextArea";
import ScrollableForm from "components/ScrollableForm";

export interface InstanceDetailsFormValues {
name?: string;
Expand Down Expand Up @@ -66,6 +68,8 @@ const InstanceCreateDetailsForm: FC<Props> = ({
onSelectImage,
project,
}) => {
const notify = useNotify();

function figureBaseImageName() {
const image = formik.values.image;
return image
Expand All @@ -74,7 +78,7 @@ const InstanceCreateDetailsForm: FC<Props> = ({
}

return (
<div className="details">
<ScrollableForm dependencies={[notify.notification]} belowId="form-footer">
<Row>
<Col size={12}>
<Input
Expand Down Expand Up @@ -162,7 +166,7 @@ const InstanceCreateDetailsForm: FC<Props> = ({
: ""
}
/>
</div>
</ScrollableForm>
);
};

Expand Down
4 changes: 2 additions & 2 deletions src/pages/networks/forms/NetworkFormBridge.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { FC } from "react";
import { Input, Select } from "@canonical/react-components";
import { FormikProps } from "formik/dist/types";
import ConfigurationTable from "components/ConfigurationTable";
import { getConfigurationRow } from "components/ConfigurationRow";
import { NetworkFormValues } from "pages/networks/forms/NetworkForm";
import ScrollableConfigurationTable from "components/forms/ScrollableConfigurationTable";

interface Props {
formik: FormikProps<NetworkFormValues>;
}

const NetworkFormBridge: FC<Props> = ({ formik }) => {
return (
<ConfigurationTable
<ScrollableConfigurationTable
rows={[
getConfigurationRow({
formik,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/networks/forms/NetworkFormDns.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { FC } from "react";
import { Input, Select, Textarea } from "@canonical/react-components";
import { FormikProps } from "formik/dist/types";
import ConfigurationTable from "components/ConfigurationTable";
import { getConfigurationRow } from "components/ConfigurationRow";
import { NetworkFormValues } from "pages/networks/forms/NetworkForm";
import ScrollableConfigurationTable from "components/forms/ScrollableConfigurationTable";

interface Props {
formik: FormikProps<NetworkFormValues>;
}

const NetworkFormDns: FC<Props> = ({ formik }) => {
return (
<ConfigurationTable
<ScrollableConfigurationTable
rows={[
getConfigurationRow({
formik,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/networks/forms/NetworkFormIpv4.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { FC } from "react";
import { Input, Select, Textarea } from "@canonical/react-components";
import { FormikProps } from "formik/dist/types";
import ConfigurationTable from "components/ConfigurationTable";
import { getConfigurationRow } from "components/ConfigurationRow";
import { NetworkFormValues } from "pages/networks/forms/NetworkForm";
import { optionTrueFalse } from "util/instanceOptions";
import ScrollableConfigurationTable from "components/forms/ScrollableConfigurationTable";

interface Props {
formik: FormikProps<NetworkFormValues>;
Expand All @@ -14,7 +14,7 @@ const NetworkFormIpv4: FC<Props> = ({ formik }) => {
const hasDhcp = formik.values.ipv4_dhcp !== "false";

return (
<ConfigurationTable
<ScrollableConfigurationTable
rows={[
getConfigurationRow({
formik,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/networks/forms/NetworkFormIpv6.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { FC } from "react";
import { Input, Select, Textarea } from "@canonical/react-components";
import { FormikProps } from "formik/dist/types";
import ConfigurationTable from "components/ConfigurationTable";
import { getConfigurationRow } from "components/ConfigurationRow";
import { NetworkFormValues } from "pages/networks/forms/NetworkForm";
import { optionTrueFalse } from "util/instanceOptions";
import ScrollableConfigurationTable from "components/forms/ScrollableConfigurationTable";

interface Props {
formik: FormikProps<NetworkFormValues>;
Expand All @@ -14,7 +14,7 @@ const NetworkFormIpv6: FC<Props> = ({ formik }) => {
const hasDhcp = formik.values.ipv6_dhcp !== "false";

return (
<ConfigurationTable
<ScrollableConfigurationTable
rows={[
getConfigurationRow({
formik,
Expand Down
14 changes: 11 additions & 3 deletions src/pages/networks/forms/NetworkFormMain.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React, { FC, ReactNode } from "react";
import { Col, Input, Row, Select } from "@canonical/react-components";
import {
Col,
Input,
Row,
Select,
useNotify,
} from "@canonical/react-components";
import { FormikProps } from "formik/dist/types";
import IpAddressSelector from "pages/networks/forms/IpAddressSelector";
import ConfigurationTable from "components/ConfigurationTable";
Expand All @@ -9,13 +15,15 @@ import { NetworkFormValues } from "pages/networks/forms/NetworkForm";
import NetworkTypeSelector from "pages/networks/forms/NetworkTypeSelector";
import { optionTrueFalse } from "util/instanceOptions";
import AutoExpandingTextArea from "components/AutoExpandingTextArea";
import ScrollableForm from "components/ScrollableForm";

interface Props {
formik: FormikProps<NetworkFormValues>;
project: string;
}

const NetworkFormMain: FC<Props> = ({ formik, project }) => {
const notify = useNotify();
const getFormProps = (id: "network" | "name" | "description") => {
return {
id: id,
Expand All @@ -29,7 +37,7 @@ const NetworkFormMain: FC<Props> = ({ formik, project }) => {
};

return (
<>
<ScrollableForm dependencies={[notify.notification]} belowId="form-footer">
<Row>
<Col size={12}>
<NetworkTypeSelector formik={formik} />
Expand Down Expand Up @@ -145,7 +153,7 @@ const NetworkFormMain: FC<Props> = ({ formik, project }) => {
: []),
]}
/>
</>
</ScrollableForm>
);
};

Expand Down
Loading

0 comments on commit e3f4515

Please sign in to comment.