This commit is contained in:
Stefan Pejcic
2024-11-07 19:03:37 +01:00
parent c6df945ed5
commit 09f9f9502d
2472 changed files with 620417 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export * from "./useForm";
export * from "./useModalForm";
export * from "./useStepsForm";

View File

@@ -0,0 +1,253 @@
import React from "react";
import { MockJSONServer, TestWrapper, render, waitFor } from "@test";
import { Routes, Route } from "react-router-dom";
import { useForm } from ".";
import { Select, TextInput } from "@mantine/core";
import { useSelect } from "@hooks/useSelect";
import { Edit } from "@components/crud";
import type { IRefineOptions, HttpError } from "@refinedev/core";
import { act } from "react-dom/test-utils";
const renderForm = ({
refineCoreProps,
refineOptions,
useFormProps,
}: {
useFormProps?: any;
refineCoreProps?: any;
refineOptions?: IRefineOptions;
}) => {
const EditPage = () => {
const {
saveButtonProps,
getInputProps,
refineCore: { query, formLoading },
} = useForm({
...useFormProps,
refineCoreProps: {
...refineCoreProps,
resource: "posts",
id: refineCoreProps.action === "edit" ? "1" : undefined,
action: refineCoreProps.action,
},
initialValues: {
title: "",
content: "",
category: {
id: "",
},
tags: [],
},
});
const { selectProps, queryResult: categoriesQueryResult } = useSelect({
resource: "categories",
defaultValue: query?.data?.data?.category?.id,
});
if (formLoading || categoriesQueryResult.isLoading) {
return <div>loading</div>;
}
return (
<Edit saveButtonProps={saveButtonProps}>
<form>
<TextInput
mt={8}
id="title"
label="Title"
placeholder="Title"
{...getInputProps("title")}
/>
<TextInput
mt={8}
id="content"
label="Content"
placeholder="Content"
{...getInputProps("content")}
/>
<Select
mt={8}
id="categoryId"
label="Category"
placeholder="Pick one"
{...getInputProps("category.id")}
{...selectProps}
/>
<TextInput placeholder="Tag 1" {...getInputProps(`tags.${0}`)} />
<TextInput placeholder="Tag 2" {...getInputProps(`tags.${1}`)} />
</form>
</Edit>
);
};
return render(
<Routes>
<Route path="/" element={<EditPage />} />
</Routes>,
{
wrapper: TestWrapper({
options: refineOptions,
i18nProvider: {
changeLocale: () => Promise.resolve(),
getLocale: () => "en",
translate: (key: string) => {
if (key === "form.error.content") {
return "Translated content error";
}
return key;
},
},
dataProvider: {
...MockJSONServer,
create: async () => {
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
title: ["Title is required"],
"category.id": ["Category is required"],
content: {
key: "form.error.content",
message: "Content is required",
},
"tags.0": ["Tag 0 is required"],
"tags.1": ["Tag 1 is required"],
},
};
return Promise.reject(error);
},
update: async () => {
const error: HttpError = {
message: "An error occurred while updating the record.",
statusCode: 400,
errors: {
title: ["Title is required"],
"category.id": ["Category is required"],
content: {
key: "form.error.content",
message: "Content is required",
},
"tags.0": ["Tag 0 is required"],
"tags.1": ["Tag 1 is required"],
},
};
return Promise.reject(error);
},
getMany: async () => {
return Promise.resolve([
{
id: 1,
name: "lorem ipsum dolor",
},
{
id: 2,
name: "Sit amet consectetur",
},
]);
},
},
}),
},
);
};
describe("useForm hook", () => {
it.each(["edit", "create"] as const)(
"should set %s-form errors from data provider",
async (action) => {
const onMutationErrorMock = jest.fn();
const { getByTestId, getByText } = renderForm({
refineCoreProps: {
action: action,
onMutationError: onMutationErrorMock,
},
});
await act(() => Promise.resolve());
await waitFor(() => {
expect(document.body).not.toHaveTextContent("loading");
});
await act(() => {
getByTestId("refine-save-button").click();
return Promise.resolve();
});
await waitFor(() => {
expect(document.body).not.toHaveTextContent("loading");
expect(onMutationErrorMock).toHaveBeenCalledTimes(1);
});
expect(getByText("Title is required")).toBeInTheDocument();
expect(getByText("Category is required")).toBeInTheDocument();
expect(getByText("Translated content error")).toBeInTheDocument();
expect(getByText("Tag 0 is required")).toBeInTheDocument();
expect(getByText("Tag 1 is required")).toBeInTheDocument();
},
);
it.each([
{
action: "edit",
disableFromRefineOption: false,
disableFromHook: true,
},
{
action: "edit",
disableFromRefineOption: true,
disableFromHook: false,
},
{
action: "create",
disableFromRefineOption: false,
disableFromHook: true,
},
{
action: "create",
disableFromRefineOption: true,
disableFromHook: false,
},
] as const)("should disable server-side validation", async (testCase) => {
const onMutationErrorMock = jest.fn();
const { getByTestId, queryByText } = renderForm({
refineOptions: {
disableServerSideValidation: testCase.disableFromRefineOption,
},
useFormProps: {
disableServerSideValidation: testCase.disableFromHook,
},
refineCoreProps: {
action: testCase.action,
onMutationError: onMutationErrorMock,
},
});
await act(() => Promise.resolve());
await waitFor(() => {
expect(document.body).not.toHaveTextContent("loading");
});
await act(() => {
getByTestId("refine-save-button").click();
return Promise.resolve();
});
await waitFor(() => {
expect(document.body).not.toHaveTextContent("loading");
expect(onMutationErrorMock).toHaveBeenCalledTimes(1);
});
await waitFor(() => {
expect(queryByText("Title is required")).not.toBeInTheDocument();
expect(queryByText("Category is required")).not.toBeInTheDocument();
expect(queryByText("Translated content error")).not.toBeInTheDocument();
expect(queryByText("Field is not valid.")).not.toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,259 @@
import React, { useEffect } from "react";
import {
useForm as useMantineForm,
type UseFormReturnType as UseMantineFormReturnType,
} from "@mantine/form";
import get from "lodash/get";
import has from "lodash/has";
import set from "lodash/set";
import type { UseFormInput } from "@mantine/form/lib/types";
import {
type BaseRecord,
type HttpError,
useForm as useFormCore,
useWarnAboutChange,
type UseFormProps as UseFormCoreProps,
type UseFormReturnType as UseFormReturnTypeCore,
useTranslate,
useRefineContext,
flattenObjectKeys,
} from "@refinedev/core";
type FormVariableType<TVariables, TTransformed> = ReturnType<
NonNullable<
UseFormInput<
TVariables,
(values: TVariables) => TTransformed
>["transformValues"]
>
>;
export type UseFormReturnType<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = UseMantineFormReturnType<
TVariables,
(values: TVariables) => TTransformed
> & {
refineCore: UseFormReturnTypeCore<
TQueryFnData,
TError,
FormVariableType<TVariables, TTransformed>,
TData,
TResponse,
TResponseError
>;
saveButtonProps: {
disabled: boolean;
onClick: (e: React.PointerEvent<HTMLButtonElement>) => void;
};
};
export type UseFormProps<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = {
refineCoreProps?: UseFormCoreProps<
TQueryFnData,
TError,
FormVariableType<TVariables, TTransformed>,
TData,
TResponse,
TResponseError
> & {
warnWhenUnsavedChanges?: boolean;
};
} & UseFormInput<TVariables, (values: TVariables) => TTransformed> & {
/**
* Disables server-side validation
* @default false
* @see {@link https://refine.dev/docs/advanced-tutorials/forms/server-side-form-validation/}
*/
disableServerSideValidation?: boolean;
};
export const useForm = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
>({
refineCoreProps,
disableServerSideValidation: disableServerSideValidationProp = false,
...rest
}: UseFormProps<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> = {}): UseFormReturnType<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> => {
const { options } = useRefineContext();
const disableServerSideValidation =
options?.disableServerSideValidation || disableServerSideValidationProp;
const translate = useTranslate();
const warnWhenUnsavedChangesProp = refineCoreProps?.warnWhenUnsavedChanges;
const { warnWhenUnsavedChanges: warnWhenUnsavedChangesRefine, setWarnWhen } =
useWarnAboutChange();
const warnWhenUnsavedChanges =
warnWhenUnsavedChangesProp ?? warnWhenUnsavedChangesRefine;
const useMantineFormResult = useMantineForm<
TVariables,
(values: TVariables) => TTransformed
>({
...rest,
});
const {
setValues,
onSubmit: onMantineSubmit,
isDirty,
resetDirty,
setFieldError,
values,
} = useMantineFormResult;
const useFormCoreResult = useFormCore<
TQueryFnData,
TError,
FormVariableType<TVariables, TTransformed>,
TData,
TResponse,
TResponseError
>({
...refineCoreProps,
onMutationError: (error, _variables, _context) => {
if (disableServerSideValidation) {
refineCoreProps?.onMutationError?.(error, _variables, _context);
return;
}
const errors = error?.errors;
for (const key in errors) {
const fieldError = errors[key];
let newError = "";
if (Array.isArray(fieldError)) {
newError = fieldError.join(" ");
}
if (typeof fieldError === "string") {
newError = fieldError;
}
if (typeof fieldError === "boolean") {
newError = "Field is not valid.";
}
if (typeof fieldError === "object" && "key" in fieldError) {
const translatedMessage = translate(
fieldError.key,
fieldError.message,
);
newError = translatedMessage;
}
setFieldError(key, newError);
}
refineCoreProps?.onMutationError?.(error, _variables, _context);
},
});
const { query, formLoading, onFinish, onFinishAutoSave } = useFormCoreResult;
useEffect(() => {
if (typeof query?.data !== "undefined") {
const fields: any = {};
const registeredFields = flattenObjectKeys(rest.initialValues ?? {});
const data = query?.data?.data ?? {};
Object.keys(registeredFields).forEach((key) => {
const hasValue = has(data, key);
const dataValue = get(data, key);
if (hasValue) {
set(fields, key, dataValue);
}
});
setValues(fields);
resetDirty(fields);
}
}, [query?.data]);
const isValuesChanged = isDirty();
useEffect(() => {
if (warnWhenUnsavedChanges) {
setWarnWhen(isValuesChanged);
}
}, [isValuesChanged]);
useEffect(() => {
if (isValuesChanged && refineCoreProps?.autoSave && values) {
setWarnWhen(false);
const transformedValues = rest.transformValues
? rest.transformValues(values)
: (values as unknown as TTransformed);
onFinishAutoSave(transformedValues).catch((error) => error);
}
}, [values]);
const onSubmit: (typeof useMantineFormResult)["onSubmit"] =
(handleSubmit, handleValidationFailure) => async (e) => {
setWarnWhen(false);
return await onMantineSubmit(handleSubmit, handleValidationFailure)(e);
};
const saveButtonProps = {
disabled: formLoading,
onClick: (e: React.PointerEvent<HTMLButtonElement>) => {
onSubmit(
(v) => onFinish(v).catch((error) => error),
() => false,
// @ts-expect-error event type is not compatible with pointer event
)(e);
},
};
return {
...useMantineFormResult,
onSubmit,
refineCore: useFormCoreResult,
saveButtonProps,
};
};

View File

@@ -0,0 +1,47 @@
import { useModalForm } from "..";
import { MockJSONServer, TestWrapper, waitFor, renderHook, act } from "@test";
describe("useModalForm hook", () => {
it("should `meta[syncWithLocationKey]` overrided by default", async () => {
const mockGetOne = jest.fn();
const mockUpdate = jest.fn();
const { result } = renderHook(
() =>
useModalForm({
syncWithLocation: true,
refineCoreProps: {
id: 5,
action: "edit",
resource: "posts",
},
}),
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
getOne: mockGetOne,
update: mockUpdate,
},
}),
},
);
await act(async () => {
result.current.modal.show();
});
await waitFor(() => expect(result.current.modal.visible).toBe(true));
await waitFor(() => {
expect(mockGetOne).toBeCalledTimes(1);
expect(mockGetOne).toBeCalledWith(
expect.objectContaining({
meta: expect.objectContaining({
"modal-posts-edit": undefined,
}),
}),
);
});
});
});

View File

@@ -0,0 +1,325 @@
import { useCallback } from "react";
import {
type BaseKey,
type BaseRecord,
type FormWithSyncWithLocationParams,
type HttpError,
useGo,
useModal,
useParsed,
useResource,
useUserFriendlyName,
useTranslate,
useWarnAboutChange,
useInvalidate,
} from "@refinedev/core";
import { useForm, type UseFormProps, type UseFormReturnType } from "../useForm";
import type { UseFormInput } from "@mantine/form/lib/types";
import React from "react";
export type UseModalFormReturnType<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = UseFormReturnType<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> & {
modal: {
submit: (
values: ReturnType<
NonNullable<
UseFormInput<
TVariables,
(values: TVariables) => TTransformed
>["transformValues"]
>
>,
) => void;
close: () => void;
show: (id?: BaseKey) => void;
visible: boolean;
title: string;
};
};
export type UseModalFormProps<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = UseFormProps<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> & {
modalProps?: {
defaultVisible?: boolean;
autoSubmitClose?: boolean;
autoResetForm?: boolean;
};
} & FormWithSyncWithLocationParams;
export const useModalForm = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
>({
modalProps,
refineCoreProps,
syncWithLocation,
...rest
}: UseModalFormProps<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> = {}): UseModalFormReturnType<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> => {
const [initiallySynced, setInitiallySynced] = React.useState(false);
const invalidate = useInvalidate();
const translate = useTranslate();
const {
resource: resourceProp,
action: actionProp,
autoSave,
invalidates,
dataProviderName,
} = refineCoreProps ?? {};
const {
defaultVisible = false,
autoSubmitClose = true,
autoResetForm = true,
} = modalProps ?? {};
const {
resource,
action: actionFromParams,
identifier,
} = useResource(resourceProp);
const parsed = useParsed();
const go = useGo();
const getUserFriendlyName = useUserFriendlyName();
const action = actionProp ?? actionFromParams ?? "";
const syncingId = !(
typeof syncWithLocation === "object" && syncWithLocation?.syncId === false
);
const syncWithLocationKey =
typeof syncWithLocation === "object" && "key" in syncWithLocation
? syncWithLocation.key
: resource && action && syncWithLocation
? `modal-${identifier}-${action}`
: undefined;
const useMantineFormResult = useForm<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
>({
refineCoreProps: {
...refineCoreProps,
meta: {
...(syncWithLocationKey ? { [syncWithLocationKey]: undefined } : {}),
...refineCoreProps?.meta,
},
},
...rest,
});
const {
reset,
refineCore: { onFinish, id, setId, autoSaveProps },
saveButtonProps,
onSubmit,
} = useMantineFormResult;
const { visible, show, close } = useModal({
defaultVisible,
});
React.useEffect(() => {
if (initiallySynced === false && syncWithLocationKey) {
const openStatus = parsed?.params?.[syncWithLocationKey]?.open;
if (typeof openStatus === "boolean") {
if (openStatus) {
show();
}
} else if (typeof openStatus === "string") {
if (openStatus === "true") {
show();
}
}
if (syncingId) {
const idFromParams = parsed?.params?.[syncWithLocationKey]?.id;
if (idFromParams) {
setId?.(idFromParams);
}
}
setInitiallySynced(true);
}
}, [syncWithLocationKey, parsed, syncingId, setId]);
React.useEffect(() => {
if (initiallySynced === true) {
if (visible && syncWithLocationKey) {
go({
query: {
[syncWithLocationKey]: {
...parsed?.params?.[syncWithLocationKey],
open: true,
...(syncingId && id && { id }),
},
},
options: { keepQuery: true },
type: "replace",
});
} else if (syncWithLocationKey && !visible) {
go({
query: {
[syncWithLocationKey]: undefined,
},
options: { keepQuery: true },
type: "replace",
});
}
}
}, [id, visible, show, syncWithLocationKey, syncingId]);
const submit = async (
values: ReturnType<
NonNullable<
UseFormInput<
TVariables,
(values: TVariables) => TTransformed
>["transformValues"]
>
>,
) => {
await onFinish(values);
if (autoSubmitClose) {
close();
}
if (autoResetForm) {
reset();
}
};
const { warnWhen, setWarnWhen } = useWarnAboutChange();
const handleClose = useCallback(() => {
if (autoSaveProps.status === "success" && autoSave?.invalidateOnClose) {
invalidate({
id,
invalidates: invalidates || ["list", "many", "detail"],
dataProviderName,
resource: identifier,
});
}
if (warnWhen) {
const warnWhenConfirm = window.confirm(
translate(
"warnWhenUnsavedChanges",
"Are you sure you want to leave? You have unsaved changes.",
),
);
if (warnWhenConfirm) {
setWarnWhen(false);
} else {
return;
}
}
setId?.(undefined);
close();
}, [warnWhen, autoSaveProps.status]);
const handleShow = useCallback(
(showId?: BaseKey) => {
if (typeof showId !== "undefined") {
setId?.(showId);
}
const needsIdToOpen = action === "edit" || action === "clone";
const hasId = typeof showId !== "undefined" || typeof id !== "undefined";
if (needsIdToOpen ? hasId : true) {
show();
}
},
[id],
);
const title = translate(
`${identifier}.titles.${actionProp}`,
undefined,
`${getUserFriendlyName(
`${actionProp} ${
resource?.meta?.label ??
resource?.options?.label ??
resource?.label ??
identifier
}`,
"singular",
)}`,
);
return {
modal: {
submit,
close: handleClose,
show: handleShow,
visible,
title,
},
...useMantineFormResult,
saveButtonProps: {
...saveButtonProps,
// @ts-expect-error event type is not compatible with pointer event
onClick: (e) => onSubmit(submit)(e),
},
};
};

View File

@@ -0,0 +1,141 @@
import { useState } from "react";
import type { BaseRecord, HttpError } from "@refinedev/core";
import { useForm, type UseFormProps, type UseFormReturnType } from "../useForm";
export type UseStepsFormReturnType<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = UseFormReturnType<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> & {
steps: {
currentStep: number;
gotoStep: (step: number) => void;
};
};
export type UseStepsFormProps<
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
> = UseFormProps<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> & {
/**
* @description Configuration object for the steps.
* `defaultStep`: Allows you to set the initial step.
*
* `isBackValidate`: Whether to validation the current step when going back.
* @type `{
defaultStep?: number;
isBackValidate?: boolean;
}`
* @default `defaultStep = 0` `isBackValidate = false`
*/
stepsProps?: {
defaultStep?: number;
isBackValidate?: boolean;
};
};
export const useStepsForm = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TVariables = Record<string, unknown>,
TTransformed = TVariables,
TData extends BaseRecord = TQueryFnData,
TResponse extends BaseRecord = TData,
TResponseError extends HttpError = TError,
>({
stepsProps,
...rest
}: UseStepsFormProps<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> = {}): UseStepsFormReturnType<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
> => {
const { defaultStep = 0, isBackValidate = false } = stepsProps ?? {};
const [current, setCurrent] = useState(defaultStep);
const useMantineFormResult = useForm<
TQueryFnData,
TError,
TVariables,
TTransformed,
TData,
TResponse,
TResponseError
>({
...rest,
});
const { validate } = useMantineFormResult;
const go = (step: number) => {
let targetStep = step;
if (step < 0) {
targetStep = 0;
}
setCurrent(targetStep);
};
const gotoStep = (step: number) => {
if (step === current) {
return;
}
if (step < current && !isBackValidate) {
go(step);
return;
}
const isValid = !validate().hasErrors;
if (isValid) {
go(step);
}
};
return {
...useMantineFormResult,
steps: {
currentStep: current,
gotoStep,
},
};
};

View File

@@ -0,0 +1,4 @@
export * from "./form";
export * from "./useSelect";
export * from "./useSiderVisible";
export * from "./useThemedLayoutContext";

View File

@@ -0,0 +1,78 @@
import type { SelectProps } from "@mantine/core";
import type { QueryObserverResult } from "@tanstack/react-query";
import {
useSelect as useSelectCore,
type BaseRecord,
type GetManyResponse,
type GetListResponse,
type HttpError,
type UseSelectProps,
type BaseOption,
type Prettify,
} from "@refinedev/core";
export type UseSelectReturnType<
TData extends BaseRecord = BaseRecord,
TOption extends BaseOption = BaseOption,
> = {
selectProps: Prettify<
Omit<SelectProps, "data"> & {
data: TOption[];
}
>;
query: QueryObserverResult<GetListResponse<TData>>;
defaultValueQuery: QueryObserverResult<GetManyResponse<TData>>;
/**
* @deprecated Use `query` instead
*/
queryResult: QueryObserverResult<GetListResponse<TData>>;
/**
* @deprecated Use `defaultValueQuery` instead
*/
defaultValueQueryResult: QueryObserverResult<GetManyResponse<TData>>;
};
/**
* `useSelect` hook is used to fetch data from the dataProvider and return the options for the select box.
*
* It uses `getList` method as query function from the dataProvider that is
* passed to {@link https://refine.dev/docs/api-reference/core/components/refine-config `<Refine>`}.
*
* @see {@link https://refine.dev/docs/api-reference/mantine/hooks/useSelect} for more details.
*
* @typeParam TQueryFnData - Result data returned by the query function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}
* @typeParam TError - Custom error object that extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#httperror `HttpError`}
* @typeParam TData - Result data returned by the `select` function. Extends {@link https://refine.dev/docs/api-reference/core/interfaceReferences#baserecord `BaseRecord`}. Defaults to `TQueryFnData`
*
*/
export const useSelect = <
TQueryFnData extends BaseRecord = BaseRecord,
TError extends HttpError = HttpError,
TData extends BaseRecord = TQueryFnData,
TOption extends BaseOption = BaseOption,
>(
props: UseSelectProps<TQueryFnData, TError, TData>,
): UseSelectReturnType<TData, TOption> => {
const { query, defaultValueQuery, onSearch, options } = useSelectCore<
TQueryFnData,
TError,
TData,
TOption
>(props);
return {
selectProps: {
data: options,
onSearchChange: onSearch,
searchable: true,
filterDataOnExactSearchMatch: true,
clearable: true,
},
query,
defaultValueQuery,
queryResult: query,
defaultValueQueryResult: defaultValueQuery,
};
};

View File

@@ -0,0 +1,29 @@
import { useContext } from "react";
import { ThemedLayoutContext } from "@contexts";
export type UseSiderVisibleType = {
siderVisible: boolean;
drawerSiderVisible: boolean;
setSiderVisible: (visible: boolean) => void;
setDrawerSiderVisible: (visible: boolean) => void;
};
/**
* @deprecated Please use `useThemedLayoutContext` instead.
*/
export const useSiderVisible = (): UseSiderVisibleType => {
const {
mobileSiderOpen,
siderCollapsed,
setMobileSiderOpen,
setSiderCollapsed,
} = useContext(ThemedLayoutContext);
return {
siderVisible: mobileSiderOpen,
setSiderVisible: setMobileSiderOpen,
drawerSiderVisible: siderCollapsed,
setDrawerSiderVisible: setSiderCollapsed,
};
};

View File

@@ -0,0 +1,22 @@
import { useContext } from "react";
import { ThemedLayoutContext } from "@contexts";
import type { IThemedLayoutContext } from "@contexts/themedLayoutContext/IThemedLayoutContext";
export type UseThemedLayoutContextType = IThemedLayoutContext;
export const useThemedLayoutContext = (): UseThemedLayoutContextType => {
const {
mobileSiderOpen,
siderCollapsed,
setMobileSiderOpen,
setSiderCollapsed,
} = useContext(ThemedLayoutContext);
return {
mobileSiderOpen,
siderCollapsed,
setMobileSiderOpen,
setSiderCollapsed,
};
};