mirror of https://github.com/coder/coder.git
220 lines
4.6 KiB
TypeScript
220 lines
4.6 KiB
TypeScript
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
|
import {
|
|
type ComponentProps,
|
|
createContext,
|
|
type FC,
|
|
forwardRef,
|
|
type HTMLProps,
|
|
type ReactNode,
|
|
useContext,
|
|
} from "react";
|
|
import { AlphaBadge, DeprecatedBadge } from "components/Badges/Badges";
|
|
import { Stack } from "components/Stack/Stack";
|
|
import {
|
|
FormFooter as BaseFormFooter,
|
|
type FormFooterProps,
|
|
type FormFooterStyles,
|
|
} from "../FormFooter/FormFooter";
|
|
|
|
type FormContextValue = { direction?: "horizontal" | "vertical" };
|
|
|
|
const FormContext = createContext<FormContextValue>({
|
|
direction: "horizontal",
|
|
});
|
|
|
|
type FormProps = HTMLProps<HTMLFormElement> & {
|
|
direction?: FormContextValue["direction"];
|
|
};
|
|
|
|
export const Form: FC<FormProps> = ({ direction, ...formProps }) => {
|
|
const theme = useTheme();
|
|
|
|
return (
|
|
<FormContext.Provider value={{ direction }}>
|
|
<form
|
|
{...formProps}
|
|
css={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: direction === "horizontal" ? 80 : 40,
|
|
|
|
[theme.breakpoints.down("md")]: {
|
|
gap: 64,
|
|
},
|
|
}}
|
|
/>
|
|
</FormContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const HorizontalForm: FC<HTMLProps<HTMLFormElement>> = ({
|
|
children,
|
|
...formProps
|
|
}) => {
|
|
return (
|
|
<Form direction="horizontal" {...formProps}>
|
|
{children}
|
|
</Form>
|
|
);
|
|
};
|
|
|
|
export const VerticalForm: FC<HTMLProps<HTMLFormElement>> = ({
|
|
children,
|
|
...formProps
|
|
}) => {
|
|
return (
|
|
<Form direction="vertical" {...formProps}>
|
|
{children}
|
|
</Form>
|
|
);
|
|
};
|
|
|
|
interface FormSectionProps {
|
|
children?: ReactNode;
|
|
title: ReactNode;
|
|
description: ReactNode;
|
|
classes?: {
|
|
root?: string;
|
|
sectionInfo?: string;
|
|
infoTitle?: string;
|
|
};
|
|
alpha?: boolean;
|
|
deprecated?: boolean;
|
|
}
|
|
|
|
export const FormSection = forwardRef<HTMLDivElement, FormSectionProps>(
|
|
(
|
|
{
|
|
children,
|
|
title,
|
|
description,
|
|
classes = {},
|
|
alpha = false,
|
|
deprecated = false,
|
|
},
|
|
ref,
|
|
) => {
|
|
const { direction } = useContext(FormContext);
|
|
|
|
return (
|
|
<section
|
|
ref={ref}
|
|
css={[
|
|
styles.formSection,
|
|
direction === "horizontal" && styles.formSectionHorizontal,
|
|
]}
|
|
className={classes.root}
|
|
>
|
|
<div
|
|
css={[
|
|
styles.formSectionInfo,
|
|
direction === "horizontal" && styles.formSectionInfoHorizontal,
|
|
]}
|
|
className={classes.sectionInfo}
|
|
>
|
|
<h2 css={styles.formSectionInfoTitle} className={classes.infoTitle}>
|
|
{title}
|
|
{alpha && <AlphaBadge />}
|
|
{deprecated && <DeprecatedBadge />}
|
|
</h2>
|
|
<div css={styles.formSectionInfoDescription}>{description}</div>
|
|
</div>
|
|
|
|
{children}
|
|
</section>
|
|
);
|
|
},
|
|
);
|
|
|
|
export const FormFields: FC<ComponentProps<typeof Stack>> = (props) => {
|
|
return (
|
|
<Stack
|
|
direction="column"
|
|
spacing={3}
|
|
{...props}
|
|
css={styles.formSectionFields}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const styles = {
|
|
formSection: (theme) => ({
|
|
display: "flex",
|
|
alignItems: "flex-start",
|
|
flexDirection: "column",
|
|
gap: 24,
|
|
|
|
[theme.breakpoints.down("lg")]: {
|
|
flexDirection: "column",
|
|
gap: 16,
|
|
},
|
|
}),
|
|
formSectionHorizontal: {
|
|
flexDirection: "row",
|
|
gap: 120,
|
|
},
|
|
formSectionInfo: (theme) => ({
|
|
width: "100%",
|
|
flexShrink: 0,
|
|
top: 24,
|
|
|
|
[theme.breakpoints.down("md")]: {
|
|
width: "100%",
|
|
position: "initial" as const,
|
|
},
|
|
}),
|
|
formSectionInfoHorizontal: {
|
|
maxWidth: 312,
|
|
position: "sticky",
|
|
},
|
|
formSectionInfoTitle: (theme) => ({
|
|
fontSize: 20,
|
|
color: theme.palette.text.primary,
|
|
fontWeight: 400,
|
|
margin: 0,
|
|
marginBottom: 8,
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 12,
|
|
}),
|
|
|
|
formSectionInfoDescription: (theme) => ({
|
|
fontSize: 14,
|
|
color: theme.palette.text.secondary,
|
|
lineHeight: "160%",
|
|
margin: 0,
|
|
}),
|
|
|
|
formSectionFields: {
|
|
width: "100%",
|
|
},
|
|
} satisfies Record<string, Interpolation<Theme>>;
|
|
|
|
export const FormFooter: FC<Exclude<FormFooterProps, "styles">> = (props) => (
|
|
<BaseFormFooter {...props} styles={footerStyles} />
|
|
);
|
|
|
|
const footerStyles = {
|
|
button: (theme) => ({
|
|
minWidth: 184,
|
|
|
|
[theme.breakpoints.down("md")]: {
|
|
width: "100%",
|
|
},
|
|
}),
|
|
|
|
footer: (theme) => ({
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "flex-start",
|
|
flexDirection: "row-reverse",
|
|
gap: 16,
|
|
|
|
[theme.breakpoints.down("md")]: {
|
|
flexDirection: "column",
|
|
gap: 8,
|
|
},
|
|
}),
|
|
} satisfies FormFooterStyles;
|