packages/chakra-ui/src/components/crud/edit/index.tsx
import React from "react";
import {
useMutationMode,
useNavigation,
useTranslate,
useUserFriendlyName,
useRefineContext,
useRouterType,
useBack,
useResource,
useGo,
useToPath,
} from "@refinedev/core";
import { Box, Heading, HStack, IconButton, Spinner } from "@chakra-ui/react";
// We use @tabler/icons for icons but you can use any icon library you want.
import { IconArrowLeft } from "@tabler/icons";
import {
DeleteButton,
ListButton,
RefreshButton,
SaveButton,
Breadcrumb,
ListButtonProps,
RefreshButtonProps,
DeleteButtonProps,
SaveButtonProps,
AutoSaveIndicator,
} from "@components";
import { EditProps } from "../types";
import { RefinePageHeaderClassNames } from "@refinedev/ui-types";
export const Edit: React.FC<EditProps> = (props) => {
const {
children,
resource: resourceFromProps,
recordItemId,
deleteButtonProps: deleteButtonPropsFromProps,
mutationMode: mutationModeFromProps,
saveButtonProps: saveButtonPropsFromProps,
canDelete,
dataProviderName,
isLoading,
footerButtons: footerButtonsFromProps,
footerButtonProps,
headerButtons: headerButtonsFromProps,
headerButtonProps,
wrapperProps,
contentProps,
headerProps,
goBack: goBackFromProps,
breadcrumb: breadcrumbFromProps,
title,
autoSaveProps,
} = props;
const translate = useTranslate();
const {
options: { breadcrumb: globalBreadcrumb } = {},
} = useRefineContext();
const { mutationMode: mutationModeContext } = useMutationMode();
const mutationMode = mutationModeFromProps ?? mutationModeContext;
const routerType = useRouterType();
const back = useBack();
const go = useGo();
const { goBack, list: legacyGoList } = useNavigation();
const getUserFriendlyName = useUserFriendlyName();
const {
resource,
action,
id: idFromParams,
identifier,
} = useResource(resourceFromProps);
const goListPath = useToPath({
resource,
action: "list",
});
const id = recordItemId ?? idFromParams;
const breadcrumb =
typeof breadcrumbFromProps === "undefined"
? globalBreadcrumb
: breadcrumbFromProps;
const hasList = resource?.list && !recordItemId;
const isDeleteButtonVisible =
canDelete ??
((resource?.meta?.canDelete ?? resource?.canDelete) ||
deleteButtonPropsFromProps);
const listButtonProps: ListButtonProps | undefined = hasList
? {
...(isLoading ? { disabled: true } : {}),
resource: routerType === "legacy" ? resource?.route : identifier,
}
: undefined;
const refreshButtonProps: RefreshButtonProps | undefined = {
...(isLoading ? { disabled: true } : {}),
resource: routerType === "legacy" ? resource?.route : identifier,
recordItemId: id,
dataProviderName,
};
const deleteButtonProps: DeleteButtonProps | undefined = isDeleteButtonVisible
? ({
...(isLoading ? { disabled: true } : {}),
resource: routerType === "legacy" ? resource?.route : identifier,
mutationMode,
onSuccess: () => {
if (routerType === "legacy") {
legacyGoList(resource?.route ?? resource?.name ?? "");
} else {
go({ to: goListPath });
}
},
recordItemId: id,
dataProviderName,
...deleteButtonPropsFromProps,
} as const)
: undefined;
const saveButtonProps: SaveButtonProps = {
...(isLoading ? { disabled: true } : {}),
...saveButtonPropsFromProps,
};
const defaultHeaderButtons = (
<Box display="flex" flexDirection="row" alignItems="center" gap="2">
{autoSaveProps && <AutoSaveIndicator {...autoSaveProps} />}
{hasList && <ListButton {...listButtonProps} />}
<RefreshButton {...refreshButtonProps} />
</Box>
);
const defaultFooterButtons = (
<>
{isDeleteButtonVisible && <DeleteButton {...deleteButtonProps} />}
<SaveButton {...saveButtonProps} />
</>
);
const buttonBack =
goBackFromProps === (false || null) ? null : (
<IconButton
aria-label="back"
variant="ghost"
size="sm"
onClick={
action !== "list" && typeof action !== "undefined"
? routerType === "legacy"
? goBack
: back
: undefined
}
>
{typeof goBackFromProps !== "undefined" ? (
goBackFromProps
) : (
<IconArrowLeft />
)}
</IconButton>
);
const headerButtons = headerButtonsFromProps
? typeof headerButtonsFromProps === "function"
? headerButtonsFromProps({
defaultButtons: defaultHeaderButtons,
listButtonProps,
refreshButtonProps,
})
: headerButtonsFromProps
: defaultHeaderButtons;
const footerButtons = footerButtonsFromProps
? typeof footerButtonsFromProps === "function"
? footerButtonsFromProps({
defaultButtons: defaultFooterButtons,
deleteButtonProps,
saveButtonProps,
})
: footerButtonsFromProps
: defaultFooterButtons;
const renderTitle = () => {
if (title === false) return null;
if (title) {
if (typeof title === "string" || typeof title === "number") {
return (
<Heading
as="h3"
size="lg"
className={RefinePageHeaderClassNames.Title}
>
{title}
</Heading>
);
}
return title;
}
return (
<Heading as="h3" size="lg" className={RefinePageHeaderClassNames.Title}>
{translate(
`${identifier}.titles.edit`,
`Edit ${getUserFriendlyName(
resource?.meta?.label ??
resource?.options?.label ??
resource?.label ??
identifier,
"singular",
)}`,
)}
</Heading>
);
};
return (
<Box
position="relative"
bg="chakra-body-bg"
borderRadius="md"
px="4"
py="3"
{...wrapperProps}
>
{isLoading && (
<Spinner
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
/>
)}
<Box
mb="3"
display="flex"
justifyContent="space-between"
alignItems="center"
flexWrap={{ base: "wrap", md: "nowrap" }}
gap="3"
{...headerProps}
>
<Box minW={200}>
{typeof breadcrumb !== "undefined" ? (
<>{breadcrumb}</> ?? undefined
) : (
<Breadcrumb />
)}
<HStack spacing={2}>
{buttonBack}
{renderTitle()}
</HStack>
</Box>
<Box
display="flex"
flexWrap="wrap"
justifyContent={{ base: "flex-start", md: "flex-end" }}
gap="2"
{...headerButtonProps}
>
{headerButtons}
</Box>
</Box>
<Box opacity={isLoading ? 0.5 : undefined} {...contentProps}>
{children}
</Box>
<Box
display="flex"
justifyContent="flex-end"
gap="2"
mt={8}
{...footerButtonProps}
>
{footerButtons}
</Box>
</Box>
);
};