@sqrzro/ui 4.0.0-alpha.0 → 4.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/buttons/ActionButton/index.d.ts +7 -0
- package/dist/components/buttons/ActionButton/index.js +33 -0
- package/dist/components/buttons/Button/index.d.ts +65 -0
- package/dist/components/buttons/Button/index.js +50 -0
- package/dist/components/buttons/ConfirmableButton/index.d.ts +7 -0
- package/dist/components/buttons/ConfirmableButton/index.js +13 -0
- package/dist/components/buttons/TextButton/index.d.ts +5 -0
- package/dist/components/buttons/TextButton/index.js +6 -0
- package/dist/components/collections/Collection/index.d.ts +15 -0
- package/dist/components/collections/Collection/index.js +45 -0
- package/dist/components/collections/EmptyMessage/index.d.ts +26 -0
- package/dist/components/collections/EmptyMessage/index.js +17 -0
- package/dist/components/collections/EmptyMessageAction/index.d.ts +6 -0
- package/dist/components/collections/EmptyMessageAction/index.js +19 -0
- package/dist/components/collections/List/index.d.ts +6 -0
- package/dist/components/collections/List/index.js +13 -0
- package/dist/components/collections/ListClientComponent/index.d.ts +7 -0
- package/dist/components/collections/ListClientComponent/index.js +9 -0
- package/dist/components/collections/ListItem/index.d.ts +14 -0
- package/dist/components/collections/ListItem/index.js +28 -0
- package/dist/components/collections/ListItemMeta/index.d.ts +11 -0
- package/dist/components/collections/ListItemMeta/index.js +22 -0
- package/dist/components/collections/ListItemSecondary/index.d.ts +6 -0
- package/dist/components/collections/ListItemSecondary/index.js +16 -0
- package/dist/components/collections/Pagination/index.d.ts +19 -0
- package/dist/components/collections/Pagination/index.js +17 -0
- package/dist/components/collections/Table/index.d.ts +9 -0
- package/dist/components/collections/Table/index.js +13 -0
- package/dist/components/collections/TableClientComponent/index.d.ts +14 -0
- package/dist/components/collections/TableClientComponent/index.js +30 -0
- package/dist/components/collections/interfaces.d.ts +57 -0
- package/dist/components/collections/interfaces.js +1 -0
- package/dist/components/collections/lang.d.ts +4 -0
- package/dist/components/collections/lang.js +4 -0
- package/dist/components/collections/utility/filter-columns.d.ts +3 -0
- package/dist/components/collections/utility/filter-columns.js +8 -0
- package/dist/components/collections/utility/get-selected-from-search-params.d.ts +2 -0
- package/dist/components/collections/utility/get-selected-from-search-params.js +5 -0
- package/dist/components/collections/utility/is-paginated.d.ts +3 -0
- package/dist/components/collections/utility/is-paginated.js +4 -0
- package/dist/components/collections/utility/set-selected-to-search-params.d.ts +2 -0
- package/dist/components/collections/utility/set-selected-to-search-params.js +4 -0
- package/dist/components/index.d.ts +16 -0
- package/dist/components/index.js +9 -0
- package/dist/components/modals/ConfirmModal/index.d.ts +7 -0
- package/dist/components/modals/ConfirmModal/index.js +23 -0
- package/dist/components/modals/Modal/index.d.ts +19 -0
- package/dist/components/modals/Modal/index.js +17 -0
- package/dist/components/modals/ModalActions/index.d.ts +9 -0
- package/dist/components/modals/ModalActions/index.js +8 -0
- package/dist/components/modals/ModalLauncher/index.d.ts +5 -0
- package/dist/components/modals/ModalLauncher/index.js +14 -0
- package/dist/components/utility/ActionList/index.d.ts +12 -0
- package/dist/components/utility/ActionList/index.js +9 -0
- package/dist/components/utility/Assistive/index.d.ts +8 -0
- package/dist/components/utility/Assistive/index.js +8 -0
- package/dist/components/utility/ClassNames/index.d.ts +6 -0
- package/dist/components/utility/ClassNames/index.js +7 -0
- package/dist/components/utility/Container/index.d.ts +9 -0
- package/dist/components/utility/Container/index.js +8 -0
- package/dist/components/utility/Link/index.d.ts +19 -0
- package/dist/components/utility/Link/index.js +33 -0
- package/dist/components/utility/Loader/index.d.ts +8 -0
- package/dist/components/utility/Loader/index.js +9 -0
- package/dist/components/utility/Page/index.d.ts +18 -0
- package/dist/components/utility/Page/index.js +34 -0
- package/dist/components/utility/Popover/index.d.ts +14 -0
- package/dist/components/utility/Popover/index.js +71 -0
- package/dist/components/utility/RootLayout/index.d.ts +12 -0
- package/dist/components/utility/RootLayout/index.js +14 -0
- package/dist/components/utility/Toast/index.d.ts +11 -0
- package/dist/components/utility/Toast/index.js +11 -0
- package/dist/components/utility/Toaster/index.d.ts +8 -0
- package/dist/components/utility/Toaster/index.js +57 -0
- package/dist/filters/components/FilterBar/index.d.ts +9 -0
- package/dist/filters/components/FilterBar/index.js +9 -0
- package/dist/filters/components/FilterBarClientComponent/index.d.ts +12 -0
- package/dist/filters/components/FilterBarClientComponent/index.js +68 -0
- package/dist/filters/components/FilterClearButton/index.d.ts +5 -0
- package/dist/filters/components/FilterClearButton/index.js +6 -0
- package/dist/filters/components/FilterControl/index.d.ts +12 -0
- package/dist/filters/components/FilterControl/index.js +10 -0
- package/dist/filters/components/FilterItem/index.d.ts +14 -0
- package/dist/filters/components/FilterItem/index.js +43 -0
- package/dist/filters/components/FilterPanel/index.d.ts +19 -0
- package/dist/filters/components/FilterPanel/index.js +36 -0
- package/dist/filters/filters/BooleanFilter/index.d.ts +3 -0
- package/dist/filters/filters/BooleanFilter/index.js +10 -0
- package/dist/filters/filters/CalendarFilter/index.d.ts +3 -0
- package/dist/filters/filters/CalendarFilter/index.js +10 -0
- package/dist/filters/filters/DateFilter/index.d.ts +3 -0
- package/dist/filters/filters/DateFilter/index.js +21 -0
- package/dist/filters/filters/DropdownFilter/index.d.ts +3 -0
- package/dist/filters/filters/DropdownFilter/index.js +10 -0
- package/dist/filters/filters/Filter/index.d.ts +16 -0
- package/dist/filters/filters/Filter/index.js +13 -0
- package/dist/filters/filters/MultiFilter/index.d.ts +3 -0
- package/dist/filters/filters/MultiFilter/index.js +9 -0
- package/dist/filters/filters/SearchFilter/index.d.ts +4 -0
- package/dist/filters/filters/SearchFilter/index.js +30 -0
- package/dist/filters/filters/interfaces.d.ts +10 -0
- package/dist/filters/filters/interfaces.js +1 -0
- package/dist/filters/hooks/useFilters.d.ts +5 -0
- package/dist/filters/hooks/useFilters.js +25 -0
- package/dist/filters/index.d.ts +2 -0
- package/dist/filters/index.js +2 -0
- package/dist/filters/interfaces.d.ts +28 -0
- package/dist/filters/interfaces.js +1 -0
- package/dist/filters/lang.d.ts +1 -0
- package/dist/filters/lang.js +1 -0
- package/dist/filters/utility/check-has-filters.d.ts +2 -0
- package/dist/filters/utility/check-has-filters.js +8 -0
- package/dist/filters/utility/create-client-filter-map.d.ts +3 -0
- package/dist/filters/utility/create-client-filter-map.js +14 -0
- package/dist/filters/utility/filter.d.ts +16 -0
- package/dist/filters/utility/filter.js +120 -0
- package/dist/filters/utility/get-quick-dates.d.ts +3 -0
- package/dist/filters/utility/get-quick-dates.js +90 -0
- package/dist/filters/utility/parse-filters.d.ts +3 -0
- package/dist/filters/utility/parse-filters.js +16 -0
- package/dist/filters/utility/parse-page.d.ts +2 -0
- package/dist/filters/utility/parse-page.js +8 -0
- package/dist/filters/utility/render-value.d.ts +3 -0
- package/dist/filters/utility/render-value.js +57 -0
- package/dist/filters/utility/transform-boolean.d.ts +2 -0
- package/dist/filters/utility/transform-boolean.js +10 -0
- package/dist/filters/utility/transform-date.d.ts +2 -0
- package/dist/filters/utility/transform-date.js +29 -0
- package/dist/filters/utility/transform-multi.d.ts +2 -0
- package/dist/filters/utility/transform-multi.js +9 -0
- package/dist/forms/components/Dropdown/index.d.ts +22 -0
- package/dist/forms/components/Dropdown/index.js +41 -0
- package/dist/forms/components/DropdownList/index.d.ts +8 -0
- package/dist/forms/components/DropdownList/index.js +20 -0
- package/dist/forms/components/EditableForm/index.d.ts +24 -0
- package/dist/forms/components/EditableForm/index.js +23 -0
- package/dist/forms/components/EditableFormField/index.d.ts +14 -0
- package/dist/forms/components/EditableFormField/index.js +37 -0
- package/dist/forms/components/EditableFormFields/index.d.ts +7 -0
- package/dist/forms/components/EditableFormFields/index.js +20 -0
- package/dist/forms/components/Form/index.d.ts +12 -0
- package/dist/forms/components/Form/index.js +9 -0
- package/dist/forms/components/FormError/index.d.ts +9 -0
- package/dist/forms/components/FormError/index.js +8 -0
- package/dist/forms/components/FormField/index.d.ts +25 -0
- package/dist/forms/components/FormField/index.js +37 -0
- package/dist/forms/components/FormFields/index.d.ts +10 -0
- package/dist/forms/components/FormFields/index.js +22 -0
- package/dist/forms/components/FormLabel/index.d.ts +10 -0
- package/dist/forms/components/FormLabel/index.js +8 -0
- package/dist/forms/components/FormSubmit/index.d.ts +4 -0
- package/dist/forms/components/FormSubmit/index.js +9 -0
- package/dist/forms/components/ModalForm/index.d.ts +13 -0
- package/dist/forms/components/ModalForm/index.js +25 -0
- package/dist/forms/components/PasswordInput/index.d.ts +11 -0
- package/dist/forms/components/PasswordInput/index.js +17 -0
- package/dist/forms/components/StaticTextInput/index.d.ts +22 -0
- package/dist/forms/components/StaticTextInput/index.js +22 -0
- package/dist/forms/components/Switch/index.d.ts +11 -0
- package/dist/forms/components/Switch/index.js +17 -0
- package/dist/forms/components/TextInput/index.d.ts +34 -0
- package/dist/forms/components/TextInput/index.js +30 -0
- package/dist/forms/hooks/useDropdown.d.ts +10 -0
- package/dist/forms/hooks/useDropdown.js +14 -0
- package/dist/forms/hooks/useEditableForm.d.ts +24 -0
- package/dist/forms/hooks/useEditableForm.js +34 -0
- package/dist/forms/hooks/useForm.d.ts +136 -0
- package/dist/forms/hooks/useForm.js +209 -0
- package/dist/forms/hooks/useModalForm.d.ts +14 -0
- package/dist/forms/hooks/useModalForm.js +29 -0
- package/dist/forms/index.d.ts +27 -0
- package/dist/forms/index.js +17 -0
- package/dist/forms/interfaces.d.ts +32 -0
- package/dist/forms/interfaces.js +1 -0
- package/dist/forms/utility/extract-editable-input-props.d.ts +8 -0
- package/dist/forms/utility/extract-editable-input-props.js +19 -0
- package/dist/forms/utility/extract-input-props.d.ts +9 -0
- package/dist/forms/utility/extract-input-props.js +37 -0
- package/dist/hooks/index.d.ts +0 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useClickOutside.d.ts +93 -0
- package/dist/hooks/useClickOutside.js +124 -0
- package/dist/hooks/usePagination.d.ts +17 -0
- package/dist/hooks/usePagination.js +46 -0
- package/dist/hooks/useSearchParamsHref.d.ts +6 -0
- package/dist/hooks/useSearchParamsHref.js +33 -0
- package/dist/hooks/useToast.d.ts +18 -0
- package/dist/hooks/useToast.js +25 -0
- package/dist/navigation/components/AppNavigation/index.d.ts +13 -0
- package/dist/navigation/components/AppNavigation/index.js +15 -0
- package/dist/navigation/components/AppNavigationItem/index.d.ts +6 -0
- package/dist/navigation/components/AppNavigationItem/index.js +9 -0
- package/dist/navigation/components/Tabs/index.d.ts +7 -0
- package/dist/navigation/components/Tabs/index.js +9 -0
- package/dist/navigation/hooks/useNavigation.d.ts +10 -0
- package/dist/navigation/hooks/useNavigation.js +38 -0
- package/dist/navigation/index.d.ts +5 -0
- package/dist/navigation/index.js +3 -0
- package/dist/navigation/interfaces.d.ts +7 -0
- package/dist/navigation/interfaces.js +1 -0
- package/dist/styles/config.d.ts +64 -0
- package/dist/styles/config.js +43 -0
- package/dist/styles/icons.d.ts +15 -0
- package/dist/styles/icons.js +7 -0
- package/dist/styles/index.d.ts +2 -0
- package/dist/styles/index.js +1 -0
- package/dist/styles/interfaces.d.ts +41 -0
- package/dist/styles/interfaces.js +1 -0
- package/dist/styles/tw.d.ts +9 -0
- package/dist/styles/tw.js +15 -0
- package/dist/utility/index.d.ts +1 -0
- package/dist/utility/index.js +1 -0
- package/dist/utility/interfaces.d.ts +35 -0
- package/dist/utility/interfaces.js +1 -1
- package/package.json +55 -8
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1
- package/dist/lists/List/index.d.ts +0 -5
- package/dist/lists/List/index.js +0 -6
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, useState, useTransition } from 'react';
|
|
3
|
+
import { formatTitle } from '@sqrzro/utility';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import useDeepCompareEffect from 'use-deep-compare-effect';
|
|
6
|
+
import useToast from '../../hooks/useToast';
|
|
7
|
+
const DEFAULT_TOAST_MESSAGES = {
|
|
8
|
+
server: 'There was a problem submitting your request. Please try again later.',
|
|
9
|
+
success: 'Your request was submitted successfully.',
|
|
10
|
+
validation: 'There was a problem with your submission. Please check the form and try again.',
|
|
11
|
+
};
|
|
12
|
+
function getErrorsForField(errors, name) {
|
|
13
|
+
if (!errors || Object.keys(errors).length === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const fieldErrors = Object.entries(errors)
|
|
17
|
+
.filter(([key]) => key === name || key.startsWith(`${name}.`) || key.startsWith(`${name}[`))
|
|
18
|
+
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});
|
|
19
|
+
return Object.keys(fieldErrors).length ? fieldErrors : null;
|
|
20
|
+
}
|
|
21
|
+
function getLabel(name, label) {
|
|
22
|
+
return label || formatTitle(name);
|
|
23
|
+
}
|
|
24
|
+
function getToastMessage(key, toasts) {
|
|
25
|
+
if (toasts === false || toasts?.[key] === false) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
return toasts?.[key] || DEFAULT_TOAST_MESSAGES[key];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* ## Overview
|
|
32
|
+
*
|
|
33
|
+
* `useForm` is a custom React hook that facilitates handling server-side form submission,
|
|
34
|
+
* including state management, error handling, and form data processing. It's designed to work
|
|
35
|
+
* with forms where the data is submitted to and validated by a server.
|
|
36
|
+
*
|
|
37
|
+
* ## Type Parameters
|
|
38
|
+
*
|
|
39
|
+
* - `Request extends object`: The shape of the request data.
|
|
40
|
+
* - `Response = Request`: The shape of the response data, defaulting to the same type as `Request`.
|
|
41
|
+
*
|
|
42
|
+
* ## Arguments
|
|
43
|
+
*
|
|
44
|
+
* `useForm` takes an object with the following properties:
|
|
45
|
+
*
|
|
46
|
+
* - `defaults`: Initial default values for the form data.
|
|
47
|
+
* - `hiddenFields`: Fields that should be included in the form submission but not displayed.
|
|
48
|
+
* - `onError`: Callback function for handling errors during form submission.
|
|
49
|
+
* - `onSubmit`: Function to handle the form submission. Expected to return a promise.
|
|
50
|
+
* - `onSuccess`: Callback function to be called upon a successful submission.
|
|
51
|
+
* - `onValidationError`: Callback function for handling validation errors.
|
|
52
|
+
* - `toasts`: Configuration for displaying success or error toasts.
|
|
53
|
+
*
|
|
54
|
+
* ## Returns
|
|
55
|
+
*
|
|
56
|
+
* `useForm` returns an object with the following properties:
|
|
57
|
+
*
|
|
58
|
+
* - `errors`: An object containing any form errors.
|
|
59
|
+
* - `fieldProps`: Function to generate props for a form field.
|
|
60
|
+
* - `formData`: Current form data.
|
|
61
|
+
* - `formProps`: Props for the form element including the submit handler.
|
|
62
|
+
* - `isLoading`: Boolean indicating if the form submission is in progress.
|
|
63
|
+
* - `resetForm`: Function to reset the form to its initial state.
|
|
64
|
+
* - `setFormData`: Function to update a specific piece of form data.
|
|
65
|
+
*
|
|
66
|
+
* ## Functionality
|
|
67
|
+
*
|
|
68
|
+
* - **Form Data Management:**
|
|
69
|
+
* - Manages form data state and provides handleChange and setFormData functions to update form
|
|
70
|
+
* data.
|
|
71
|
+
*
|
|
72
|
+
* - **Form Submission:**
|
|
73
|
+
* - Handles form submission, including displaying loading state, submitting data to the provided
|
|
74
|
+
* onSubmit function, and handling server-side validation errors or other errors.
|
|
75
|
+
*
|
|
76
|
+
* - **Error Handling:**
|
|
77
|
+
* - Manages form error state and handles displaying error messages.
|
|
78
|
+
*
|
|
79
|
+
* - **Toasts:**
|
|
80
|
+
* - Optionally displays success or error toasts based on the form submission outcome.
|
|
81
|
+
*
|
|
82
|
+
* - **Reset Form:**
|
|
83
|
+
* - Provides a function to reset the form to its default state.
|
|
84
|
+
*
|
|
85
|
+
* ## Example
|
|
86
|
+
*
|
|
87
|
+
* ```jsx
|
|
88
|
+
* import React from 'react';
|
|
89
|
+
* import { useForm } from './path-to-useForm';
|
|
90
|
+
*
|
|
91
|
+
* const MyFormComponent = () => {
|
|
92
|
+
* const { fieldProps, formProps, isLoading, resetForm } = useForm<MyFormData>({
|
|
93
|
+
* defaults: { name: '', email: '' },
|
|
94
|
+
* onSubmit: async (formData) => {
|
|
95
|
+
* // Submit form data to the server
|
|
96
|
+
* },
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* return (
|
|
100
|
+
* <form {...formProps}>
|
|
101
|
+
* <input {...fieldProps('name', 'Name')} />
|
|
102
|
+
* <input {...fieldProps('email', 'Email')} />
|
|
103
|
+
* <button type="submit" disabled={isLoading}>Submit</button>
|
|
104
|
+
* <button type="button" onClick={resetForm}>Reset</button>
|
|
105
|
+
* </form>
|
|
106
|
+
* );
|
|
107
|
+
* };
|
|
108
|
+
*
|
|
109
|
+
* export default MyFormComponent;
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* In this example, `MyFormComponent` uses the `useForm` hook to manage form state, handle
|
|
113
|
+
* submissions, display errors, and reset the form.
|
|
114
|
+
*
|
|
115
|
+
* ## Notes
|
|
116
|
+
*
|
|
117
|
+
* - Ensure proper types are passed to the useForm hook to align with the expected structure
|
|
118
|
+
* of your form data and server responses.
|
|
119
|
+
* - The hook's functionality can be extended or customized based on specific use cases, such as
|
|
120
|
+
* handling additional form events or integrating with different server APIs.
|
|
121
|
+
*/
|
|
122
|
+
function useForm({ defaults = {}, onError, onSubmit, onSuccess, onValidation, redirectOnSuccess, refreshOnSuccess, toasts, }) {
|
|
123
|
+
const innerRef = useRef(null);
|
|
124
|
+
const router = useRouter();
|
|
125
|
+
const [isLoading, startTransition] = useTransition();
|
|
126
|
+
const [errors, setErrors] = useState(null);
|
|
127
|
+
const [data, setData] = useState(defaults);
|
|
128
|
+
const { toastError, toastSuccess } = useToast();
|
|
129
|
+
function setFormData(key, value) {
|
|
130
|
+
setData((prev) => ({ ...prev, [key]: value }));
|
|
131
|
+
}
|
|
132
|
+
function handleChange(event) {
|
|
133
|
+
setData((prev) => ({ ...prev, [event.target.name]: event.target.value }));
|
|
134
|
+
}
|
|
135
|
+
async function handleSuccess(response) {
|
|
136
|
+
await onSuccess?.(response);
|
|
137
|
+
if (redirectOnSuccess) {
|
|
138
|
+
const redirect = typeof redirectOnSuccess === 'function'
|
|
139
|
+
? redirectOnSuccess(response)
|
|
140
|
+
: redirectOnSuccess;
|
|
141
|
+
if (redirect) {
|
|
142
|
+
router.push(redirect);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (refreshOnSuccess) {
|
|
146
|
+
router.refresh();
|
|
147
|
+
}
|
|
148
|
+
toastSuccess(getToastMessage('success', toasts));
|
|
149
|
+
}
|
|
150
|
+
function handleError(message) {
|
|
151
|
+
onError?.(message);
|
|
152
|
+
toastError(getToastMessage('server', toasts));
|
|
153
|
+
}
|
|
154
|
+
function handleValidation(messages) {
|
|
155
|
+
onValidation?.(messages);
|
|
156
|
+
setErrors(messages);
|
|
157
|
+
toastError(getToastMessage('validation', toasts));
|
|
158
|
+
}
|
|
159
|
+
async function handleSubmit() {
|
|
160
|
+
if (!onSubmit) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
setErrors(null);
|
|
164
|
+
const fnReturn = await onSubmit(data);
|
|
165
|
+
if (fnReturn.error) {
|
|
166
|
+
return handleError(fnReturn.error);
|
|
167
|
+
}
|
|
168
|
+
if (fnReturn.validation) {
|
|
169
|
+
return handleValidation(fnReturn.validation);
|
|
170
|
+
}
|
|
171
|
+
if (fnReturn.data) {
|
|
172
|
+
return handleSuccess(fnReturn.data);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function fieldProps(name, label) {
|
|
176
|
+
return {
|
|
177
|
+
error: getErrorsForField(errors, name),
|
|
178
|
+
label: getLabel(name, label),
|
|
179
|
+
name: name,
|
|
180
|
+
onChange: handleChange,
|
|
181
|
+
value: data[name],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function resetForm() {
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
setData((defaults || {}));
|
|
187
|
+
setErrors(null);
|
|
188
|
+
}, 1);
|
|
189
|
+
}
|
|
190
|
+
useDeepCompareEffect(() => {
|
|
191
|
+
setData({ ...data, ...defaults });
|
|
192
|
+
}, [defaults]);
|
|
193
|
+
return {
|
|
194
|
+
errors,
|
|
195
|
+
fieldProps,
|
|
196
|
+
formData: data,
|
|
197
|
+
formProps: {
|
|
198
|
+
action: handleSubmit,
|
|
199
|
+
},
|
|
200
|
+
isLoading,
|
|
201
|
+
resetForm,
|
|
202
|
+
setErrors,
|
|
203
|
+
setFormData,
|
|
204
|
+
submitForm: () => {
|
|
205
|
+
setTimeout(() => innerRef.current?.requestSubmit(), 1);
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export default useForm;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Action } from '../../utility/interfaces';
|
|
2
|
+
import type { ModalFormProps } from '../components/ModalForm';
|
|
3
|
+
import type { UseFormArgs, UseFormReturn } from './useForm';
|
|
4
|
+
export interface UseModalFormArgs<Request, Response> extends UseFormArgs<Request, Response> {
|
|
5
|
+
actions?: Action[];
|
|
6
|
+
icon?: React.ReactNode;
|
|
7
|
+
submitLabel?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface UseModalFormReturn<Request> extends Omit<UseFormReturn<Request>, 'formProps'> {
|
|
11
|
+
formProps: Omit<ModalFormProps, 'children'>;
|
|
12
|
+
}
|
|
13
|
+
declare function useModalForm<Request extends object, Response = Request>({ actions, icon, submitLabel, title, ...useFormArgs }: UseModalFormArgs<Request, Response>): UseModalFormReturn<Request>;
|
|
14
|
+
export default useModalForm;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRouter } from 'next/navigation';
|
|
3
|
+
import useSearchParamsHref from '../../hooks/useSearchParamsHref';
|
|
4
|
+
import useForm from './useForm';
|
|
5
|
+
function useModalForm({ actions, icon, submitLabel, title, ...useFormArgs }) {
|
|
6
|
+
const { setSearchParamsHref } = useSearchParamsHref();
|
|
7
|
+
const router = useRouter();
|
|
8
|
+
function handleSuccess(response) {
|
|
9
|
+
useFormArgs.onSuccess?.(response);
|
|
10
|
+
setSearchParamsHref('action', null);
|
|
11
|
+
setTimeout(() => {
|
|
12
|
+
router.refresh();
|
|
13
|
+
}, 1);
|
|
14
|
+
}
|
|
15
|
+
const useFormReturn = useForm({
|
|
16
|
+
...useFormArgs,
|
|
17
|
+
onSuccess: handleSuccess,
|
|
18
|
+
toasts: false,
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
...useFormReturn,
|
|
22
|
+
formProps: {
|
|
23
|
+
formProps: useFormReturn.formProps,
|
|
24
|
+
modalProps: { actions, icon, title },
|
|
25
|
+
submitLabel,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export default useModalForm;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export * from './interfaces';
|
|
2
|
+
export type { DropdownClassNames, DropdownProps } from './components/Dropdown';
|
|
3
|
+
export { default as Dropdown } from './components/Dropdown';
|
|
4
|
+
export type { EditableFormClassNames, EditableFormProps } from './components/EditableForm';
|
|
5
|
+
export { default as EditableForm } from './components/EditableForm';
|
|
6
|
+
export type { EditableFormFieldProps } from './components/EditableFormField';
|
|
7
|
+
export { default as EditableFormField } from './components/EditableFormField';
|
|
8
|
+
export type { FormClassNames, FormProps } from './components/Form';
|
|
9
|
+
export { default as Form } from './components/Form';
|
|
10
|
+
export type { FormFieldClassNames, FormFieldProps } from './components/FormField';
|
|
11
|
+
export { default as FormField } from './components/FormField';
|
|
12
|
+
export type { FormSubmitProps } from './components/FormSubmit';
|
|
13
|
+
export { default as FormSubmit } from './components/FormSubmit';
|
|
14
|
+
export type { ModalFormProps } from './components/ModalForm';
|
|
15
|
+
export { default as ModalForm } from './components/ModalForm';
|
|
16
|
+
export type { PasswordInputClassNames, PasswordInputProps } from './components/PasswordInput';
|
|
17
|
+
export { default as PasswordInput } from './components/PasswordInput';
|
|
18
|
+
export type { TextInputClassNames, TextInputProps } from './components/TextInput';
|
|
19
|
+
export { default as TextInput } from './components/TextInput';
|
|
20
|
+
export type { EditableDropdownFormFieldProps, EditableTextFormFieldProps, } from './components/EditableFormFields';
|
|
21
|
+
export { EditableDropdownFormField, EditableTextFormField } from './components/EditableFormFields';
|
|
22
|
+
export type { DropdownFormFieldProps, PasswordFormFieldProps, TextFormFieldProps, } from './components/FormFields';
|
|
23
|
+
export { DropdownFormField, PasswordFormField, TextFormField } from './components/FormFields';
|
|
24
|
+
export { default as useDropdown } from './hooks/useDropdown';
|
|
25
|
+
export { default as useEditableForm } from './hooks/useEditableForm';
|
|
26
|
+
export { default as useForm } from './hooks/useForm';
|
|
27
|
+
export { default as useModalForm } from './hooks/useModalForm';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from './interfaces';
|
|
2
|
+
export { default as Dropdown } from './components/Dropdown';
|
|
3
|
+
export { default as EditableForm } from './components/EditableForm';
|
|
4
|
+
export { default as EditableFormField } from './components/EditableFormField';
|
|
5
|
+
export { default as Form } from './components/Form';
|
|
6
|
+
export { default as FormField } from './components/FormField';
|
|
7
|
+
export { default as FormSubmit } from './components/FormSubmit';
|
|
8
|
+
export { default as ModalForm } from './components/ModalForm';
|
|
9
|
+
export { default as PasswordInput } from './components/PasswordInput';
|
|
10
|
+
export { default as TextInput } from './components/TextInput';
|
|
11
|
+
export { EditableDropdownFormField, EditableTextFormField } from './components/EditableFormFields';
|
|
12
|
+
export { DropdownFormField, PasswordFormField, TextFormField } from './components/FormFields';
|
|
13
|
+
// Hooks
|
|
14
|
+
export { default as useDropdown } from './hooks/useDropdown';
|
|
15
|
+
export { default as useEditableForm } from './hooks/useEditableForm';
|
|
16
|
+
export { default as useForm } from './hooks/useForm';
|
|
17
|
+
export { default as useModalForm } from './hooks/useModalForm';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { EditableFormFieldProps } from './components/EditableFormField';
|
|
2
|
+
import type { FormFieldProps } from './components/FormField';
|
|
3
|
+
export type Default<T> = {
|
|
4
|
+
[K in keyof T]?: T[K] | null;
|
|
5
|
+
};
|
|
6
|
+
export interface DropdownObject<T = string> {
|
|
7
|
+
$data?: Record<string, string>;
|
|
8
|
+
details?: string | null;
|
|
9
|
+
isDisabled?: boolean;
|
|
10
|
+
label: string;
|
|
11
|
+
value: T;
|
|
12
|
+
}
|
|
13
|
+
export type EditingStatus = 'CANCELLED' | 'EDITING' | 'SAVED';
|
|
14
|
+
export type EditableFormFieldComponentProps<T, V extends T = T> = Omit<EditableFormFieldProps<T, V>, 'render'>;
|
|
15
|
+
export type FormFieldComponentProps<T, V extends T = T> = Omit<FormFieldProps<T, V>, 'render'>;
|
|
16
|
+
export interface InputEventTarget<T> {
|
|
17
|
+
name: string;
|
|
18
|
+
value?: T;
|
|
19
|
+
}
|
|
20
|
+
export interface InputEvent<T = unknown> {
|
|
21
|
+
$data?: Record<string, string>;
|
|
22
|
+
target: InputEventTarget<T>;
|
|
23
|
+
}
|
|
24
|
+
export interface InputProps<V, T extends V = V> {
|
|
25
|
+
hasError?: boolean;
|
|
26
|
+
id?: string;
|
|
27
|
+
isDisabled?: boolean;
|
|
28
|
+
name: string;
|
|
29
|
+
onChange?: (event: InputEvent<T>) => void;
|
|
30
|
+
onKeyDown?: React.KeyboardEventHandler;
|
|
31
|
+
value?: V;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EditableFormFieldComponentProps } from '../interfaces';
|
|
2
|
+
type InferFieldValue<U> = U extends EditableFormFieldComponentProps<infer T> ? T : never;
|
|
3
|
+
export declare function getEditableFieldPropKeys<T>(): (keyof EditableFormFieldComponentProps<T>)[];
|
|
4
|
+
declare function extractEditableInputProps<U extends EditableFormFieldComponentProps<InferFieldValue<U>>>(data: U): {
|
|
5
|
+
fieldProps: EditableFormFieldComponentProps<InferFieldValue<U>>;
|
|
6
|
+
inputProps: Omit<U, keyof EditableFormFieldComponentProps<InferFieldValue<U>>>;
|
|
7
|
+
};
|
|
8
|
+
export default extractEditableInputProps;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getFieldPropKeys, hasKey } from './extract-input-props';
|
|
2
|
+
export function getEditableFieldPropKeys() {
|
|
3
|
+
return [...getFieldPropKeys(), 'renderValue', 'status'];
|
|
4
|
+
}
|
|
5
|
+
function extractEditableInputProps(data) {
|
|
6
|
+
const fieldPropKeys = getEditableFieldPropKeys();
|
|
7
|
+
const fieldProps = {};
|
|
8
|
+
const inputProps = {};
|
|
9
|
+
for (const key in data) {
|
|
10
|
+
if (hasKey(fieldPropKeys, key)) {
|
|
11
|
+
fieldProps[key] = data[key];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
inputProps[key] = data[key];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { fieldProps, inputProps };
|
|
18
|
+
}
|
|
19
|
+
export default extractEditableInputProps;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FormFieldComponentProps } from '../interfaces';
|
|
2
|
+
type InferFieldValue<U> = U extends FormFieldComponentProps<infer T> ? T : never;
|
|
3
|
+
export declare function getFieldPropKeys<T>(): (keyof FormFieldComponentProps<T>)[];
|
|
4
|
+
export declare function hasKey<K extends PropertyKey>(obj: readonly K[], key: PropertyKey): key is K;
|
|
5
|
+
declare function extractInputProps<U extends FormFieldComponentProps<InferFieldValue<U>>>(data: U): {
|
|
6
|
+
fieldProps: FormFieldComponentProps<InferFieldValue<U>>;
|
|
7
|
+
inputProps: Omit<U, keyof FormFieldComponentProps<InferFieldValue<U>>>;
|
|
8
|
+
};
|
|
9
|
+
export default extractInputProps;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function getFieldPropKeys() {
|
|
2
|
+
return [
|
|
3
|
+
'action',
|
|
4
|
+
'details',
|
|
5
|
+
'error',
|
|
6
|
+
'hasAssistiveError',
|
|
7
|
+
'hasAssistiveLabel',
|
|
8
|
+
'hasError',
|
|
9
|
+
'id',
|
|
10
|
+
'isContentOnly',
|
|
11
|
+
'isDisabled',
|
|
12
|
+
'isOptional',
|
|
13
|
+
'label',
|
|
14
|
+
'name',
|
|
15
|
+
'onChange',
|
|
16
|
+
'onKeyDown',
|
|
17
|
+
'value',
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
export function hasKey(obj, key) {
|
|
21
|
+
return obj.includes(key);
|
|
22
|
+
}
|
|
23
|
+
function extractInputProps(data) {
|
|
24
|
+
const fieldPropKeys = getFieldPropKeys();
|
|
25
|
+
const fieldProps = {};
|
|
26
|
+
const inputProps = {};
|
|
27
|
+
for (const key in data) {
|
|
28
|
+
if (hasKey(fieldPropKeys, key)) {
|
|
29
|
+
fieldProps[key] = data[key];
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
inputProps[key] = data[key];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { fieldProps, inputProps };
|
|
36
|
+
}
|
|
37
|
+
export default extractInputProps;
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
export interface UseClickOutsideOptions {
|
|
3
|
+
shouldHandleEnterKey?: boolean;
|
|
4
|
+
}
|
|
5
|
+
type UseClickOutsideReturn<T> = [boolean, (bool: boolean) => void, RefObject<T | null>];
|
|
6
|
+
/**
|
|
7
|
+
* # useClickOutside
|
|
8
|
+
*
|
|
9
|
+
* ## Overview
|
|
10
|
+
*
|
|
11
|
+
* `useClickOutside` is a custom React hook that provides functionality to detect and respond to
|
|
12
|
+
* clicks outside a specified element (`ref`). It's commonly used to close modal windows, dropdown
|
|
13
|
+
* menus, or any other UI elements that should be hidden when the user interacts with the rest of
|
|
14
|
+
* the application. Additionally, this hook can handle specific keyboard events.
|
|
15
|
+
*
|
|
16
|
+
* ## Usage
|
|
17
|
+
*
|
|
18
|
+
* This hook is ideal for components that need to react to clicks outside of them or require
|
|
19
|
+
* specific keyboard navigation like closing a modal on pressing the Escape key.
|
|
20
|
+
*
|
|
21
|
+
* ## Type Parameters
|
|
22
|
+
*
|
|
23
|
+
* - `T extends HTMLElement = HTMLDivElement`: The generic type `T` is used for the ref's HTML
|
|
24
|
+
* element type, defaulting to `HTMLDivElement`.
|
|
25
|
+
*
|
|
26
|
+
* ## Arguments
|
|
27
|
+
*
|
|
28
|
+
* `useClickOutside` takes an optional `options` object with the following property:
|
|
29
|
+
*
|
|
30
|
+
* - `shouldHandleEnterKey` (optional): A boolean that indicates whether the hook should also handle
|
|
31
|
+
* the 'Enter' key, in addition to the 'Escape' key, for closing the component.
|
|
32
|
+
*
|
|
33
|
+
* ## Returns
|
|
34
|
+
*
|
|
35
|
+
* `useClickOutside` returns a tuple (array with three elements) with the following types:
|
|
36
|
+
*
|
|
37
|
+
* 1. `isOpen`: A boolean state indicating whether the component is open.
|
|
38
|
+
* 2. `setIsOpen`: A function to set the `isOpen` state.
|
|
39
|
+
* 3. `node`: A ref object to be attached to the component.
|
|
40
|
+
*
|
|
41
|
+
* ## Functionality
|
|
42
|
+
*
|
|
43
|
+
* - **Click Outside Detection:**
|
|
44
|
+
* - The `handleClickOutside` function checks if the click event target is outside the `node` (the
|
|
45
|
+
* element the ref is attached to). If the click is outside, it sets the `isOpen` state to `false`
|
|
46
|
+
* after a delay.
|
|
47
|
+
*
|
|
48
|
+
* - **Keyboard Event Handling:**
|
|
49
|
+
* - The `handleKeyDown` function listens for keyboard events and closes the component (sets
|
|
50
|
+
* `isOpen` to `false`) when the 'Escape' key is pressed, or the 'Enter' key if
|
|
51
|
+
* `shouldHandleEnterKey` is true.
|
|
52
|
+
*
|
|
53
|
+
* - **Lifecycle Management:**
|
|
54
|
+
* - The `mounted` ref tracks if the component is still mounted to prevent state updates on
|
|
55
|
+
* unmounted components.
|
|
56
|
+
* - Event listeners for clicks and keyboard events are added on component mount and removed on
|
|
57
|
+
* component unmount.
|
|
58
|
+
*
|
|
59
|
+
* ## Example
|
|
60
|
+
*
|
|
61
|
+
* ```jsx
|
|
62
|
+
* import React, { useRef } from 'react';
|
|
63
|
+
* import useClickOutside from './path-to-useClickOutside';
|
|
64
|
+
*
|
|
65
|
+
* function MyComponent() {
|
|
66
|
+
* const [isOpen, setIsOpen, node] = useClickOutside<HTMLDivElement>();
|
|
67
|
+
*
|
|
68
|
+
* return (
|
|
69
|
+
* <div ref={node}>
|
|
70
|
+
* {isOpen && <div>Content that should close</div>}
|
|
71
|
+
* <button onClick={() => setIsOpen(true)}>Open</button>
|
|
72
|
+
* </div>
|
|
73
|
+
* );
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* export default MyComponent;
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* In this example, `MyComponent` uses the `useClickOutside` hook to control the visibility of its
|
|
80
|
+
* content. The content is hidden when a click occurs outside the component or when the 'Escape' key
|
|
81
|
+
* is pressed.
|
|
82
|
+
*
|
|
83
|
+
* ## Notes
|
|
84
|
+
*
|
|
85
|
+
* - Ensure that the `node` ref is properly attached to the DOM element you want to monitor for
|
|
86
|
+
* outside clicks.
|
|
87
|
+
* - This hook manages an `isOpen` state internally but exposes it along with its setter function
|
|
88
|
+
* for external control.
|
|
89
|
+
* - The hook adds and removes event listeners for click and keyboard events to avoid memory leaks
|
|
90
|
+
* and unintended behavior in single-page applications.
|
|
91
|
+
*/
|
|
92
|
+
declare function useClickOutside<T extends HTMLElement = HTMLDivElement>(options?: UseClickOutsideOptions): UseClickOutsideReturn<T>;
|
|
93
|
+
export default useClickOutside;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* # useClickOutside
|
|
4
|
+
*
|
|
5
|
+
* ## Overview
|
|
6
|
+
*
|
|
7
|
+
* `useClickOutside` is a custom React hook that provides functionality to detect and respond to
|
|
8
|
+
* clicks outside a specified element (`ref`). It's commonly used to close modal windows, dropdown
|
|
9
|
+
* menus, or any other UI elements that should be hidden when the user interacts with the rest of
|
|
10
|
+
* the application. Additionally, this hook can handle specific keyboard events.
|
|
11
|
+
*
|
|
12
|
+
* ## Usage
|
|
13
|
+
*
|
|
14
|
+
* This hook is ideal for components that need to react to clicks outside of them or require
|
|
15
|
+
* specific keyboard navigation like closing a modal on pressing the Escape key.
|
|
16
|
+
*
|
|
17
|
+
* ## Type Parameters
|
|
18
|
+
*
|
|
19
|
+
* - `T extends HTMLElement = HTMLDivElement`: The generic type `T` is used for the ref's HTML
|
|
20
|
+
* element type, defaulting to `HTMLDivElement`.
|
|
21
|
+
*
|
|
22
|
+
* ## Arguments
|
|
23
|
+
*
|
|
24
|
+
* `useClickOutside` takes an optional `options` object with the following property:
|
|
25
|
+
*
|
|
26
|
+
* - `shouldHandleEnterKey` (optional): A boolean that indicates whether the hook should also handle
|
|
27
|
+
* the 'Enter' key, in addition to the 'Escape' key, for closing the component.
|
|
28
|
+
*
|
|
29
|
+
* ## Returns
|
|
30
|
+
*
|
|
31
|
+
* `useClickOutside` returns a tuple (array with three elements) with the following types:
|
|
32
|
+
*
|
|
33
|
+
* 1. `isOpen`: A boolean state indicating whether the component is open.
|
|
34
|
+
* 2. `setIsOpen`: A function to set the `isOpen` state.
|
|
35
|
+
* 3. `node`: A ref object to be attached to the component.
|
|
36
|
+
*
|
|
37
|
+
* ## Functionality
|
|
38
|
+
*
|
|
39
|
+
* - **Click Outside Detection:**
|
|
40
|
+
* - The `handleClickOutside` function checks if the click event target is outside the `node` (the
|
|
41
|
+
* element the ref is attached to). If the click is outside, it sets the `isOpen` state to `false`
|
|
42
|
+
* after a delay.
|
|
43
|
+
*
|
|
44
|
+
* - **Keyboard Event Handling:**
|
|
45
|
+
* - The `handleKeyDown` function listens for keyboard events and closes the component (sets
|
|
46
|
+
* `isOpen` to `false`) when the 'Escape' key is pressed, or the 'Enter' key if
|
|
47
|
+
* `shouldHandleEnterKey` is true.
|
|
48
|
+
*
|
|
49
|
+
* - **Lifecycle Management:**
|
|
50
|
+
* - The `mounted` ref tracks if the component is still mounted to prevent state updates on
|
|
51
|
+
* unmounted components.
|
|
52
|
+
* - Event listeners for clicks and keyboard events are added on component mount and removed on
|
|
53
|
+
* component unmount.
|
|
54
|
+
*
|
|
55
|
+
* ## Example
|
|
56
|
+
*
|
|
57
|
+
* ```jsx
|
|
58
|
+
* import React, { useRef } from 'react';
|
|
59
|
+
* import useClickOutside from './path-to-useClickOutside';
|
|
60
|
+
*
|
|
61
|
+
* function MyComponent() {
|
|
62
|
+
* const [isOpen, setIsOpen, node] = useClickOutside<HTMLDivElement>();
|
|
63
|
+
*
|
|
64
|
+
* return (
|
|
65
|
+
* <div ref={node}>
|
|
66
|
+
* {isOpen && <div>Content that should close</div>}
|
|
67
|
+
* <button onClick={() => setIsOpen(true)}>Open</button>
|
|
68
|
+
* </div>
|
|
69
|
+
* );
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* export default MyComponent;
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* In this example, `MyComponent` uses the `useClickOutside` hook to control the visibility of its
|
|
76
|
+
* content. The content is hidden when a click occurs outside the component or when the 'Escape' key
|
|
77
|
+
* is pressed.
|
|
78
|
+
*
|
|
79
|
+
* ## Notes
|
|
80
|
+
*
|
|
81
|
+
* - Ensure that the `node` ref is properly attached to the DOM element you want to monitor for
|
|
82
|
+
* outside clicks.
|
|
83
|
+
* - This hook manages an `isOpen` state internally but exposes it along with its setter function
|
|
84
|
+
* for external control.
|
|
85
|
+
* - The hook adds and removes event listeners for click and keyboard events to avoid memory leaks
|
|
86
|
+
* and unintended behavior in single-page applications.
|
|
87
|
+
*/
|
|
88
|
+
function useClickOutside(options) {
|
|
89
|
+
const mounted = useRef(true);
|
|
90
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
91
|
+
const node = useRef(null);
|
|
92
|
+
function handleClickOutside(event) {
|
|
93
|
+
if (node.current?.contains(event.target)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
if (mounted) {
|
|
98
|
+
setIsOpen(false);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
mounted.current = true;
|
|
104
|
+
function handleKeyDown(event) {
|
|
105
|
+
const keys = options?.shouldHandleEnterKey ? ['Escape', 'Enter'] : ['Escape'];
|
|
106
|
+
if (keys.includes(event.key)) {
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
if (mounted) {
|
|
109
|
+
setIsOpen(false);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
document.addEventListener('click', handleClickOutside);
|
|
115
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
116
|
+
return () => {
|
|
117
|
+
mounted.current = false;
|
|
118
|
+
document.removeEventListener('click', handleClickOutside);
|
|
119
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
120
|
+
};
|
|
121
|
+
}, [options?.shouldHandleEnterKey]);
|
|
122
|
+
return [isOpen, (bool) => setIsOpen(bool), node];
|
|
123
|
+
}
|
|
124
|
+
export default useClickOutside;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface UsePaginationArgs {
|
|
2
|
+
limit: number;
|
|
3
|
+
page: number;
|
|
4
|
+
total: number;
|
|
5
|
+
}
|
|
6
|
+
export interface UsePaginationReturn {
|
|
7
|
+
links: ({
|
|
8
|
+
href: string;
|
|
9
|
+
label: string;
|
|
10
|
+
} | null)[];
|
|
11
|
+
nextLink?: string | null;
|
|
12
|
+
pageCount: number;
|
|
13
|
+
previousLink?: string | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function getLink(pathname: string, searchParams: URLSearchParams, page: number): string;
|
|
16
|
+
declare function usePagination({ limit, page, total }: Readonly<UsePaginationArgs>): UsePaginationReturn;
|
|
17
|
+
export default usePagination;
|