@evoke-platform/ui-components 1.9.0-testing.4 → 1.9.0-testing.5
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/published/components/custom/FormV2/FormRenderer.js +10 -5
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -3
- package/dist/published/components/custom/FormV2/components/ActionButtons.js +7 -3
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormContext.js +2 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +3 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +9 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +7 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +3 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -6
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +3 -5
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +9 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +6 -0
- package/dist/published/theme/hooks.d.ts +27 -9
- package/dist/published/theme/hooks.js +21 -17
- package/dist/published/theme/hooks.test.js +11 -11
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { useObject } from '@evoke-platform/context';
|
|
|
2
2
|
import { isEmpty, isEqual } from 'lodash';
|
|
3
3
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { useForm } from 'react-hook-form';
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetSize } from '../../../theme';
|
|
6
6
|
import { Button, Skeleton, Typography } from '../../core';
|
|
7
7
|
import { Box } from '../../layout';
|
|
8
8
|
import ActionButtons from './components/ActionButtons';
|
|
@@ -19,8 +19,12 @@ function FormRenderer(props) {
|
|
|
19
19
|
});
|
|
20
20
|
const hasSections = entries.some((entry) => entry.type === 'sections');
|
|
21
21
|
const isModal = !!document.querySelector('.MuiDialog-container');
|
|
22
|
-
const {
|
|
23
|
-
|
|
22
|
+
const { ref: containerRef, breakpoints, isBelow, width, } = useWidgetSize({
|
|
23
|
+
scroll: false,
|
|
24
|
+
defaultWidth: 1200,
|
|
25
|
+
});
|
|
26
|
+
const { isXs, isSm } = breakpoints;
|
|
27
|
+
const isSmallerThanMd = isBelow('md');
|
|
24
28
|
const objectStore = useObject(objectId);
|
|
25
29
|
const [expandedSections, setExpandedSections] = useState([]);
|
|
26
30
|
const [fetchedOptions, setFetchedOptions] = useState({});
|
|
@@ -101,7 +105,7 @@ function FormRenderer(props) {
|
|
|
101
105
|
handleValidation(entries, register, getValues(), action?.parameters, instance);
|
|
102
106
|
}, [action?.parameters, instance, entries, register, getValues]);
|
|
103
107
|
if (parameters && (!actionId || action)) {
|
|
104
|
-
return (React.createElement(
|
|
108
|
+
return (React.createElement(Box, { ref: containerRef },
|
|
105
109
|
((isSubmitted && !isEmpty(errors)) || (isSmallerThanMd && hasSections) || title) && (React.createElement(Box, { sx: {
|
|
106
110
|
paddingX: isSmallerThanMd ? 2 : 3,
|
|
107
111
|
paddingTop: '0px',
|
|
@@ -143,7 +147,7 @@ function FormRenderer(props) {
|
|
|
143
147
|
fontWeight: 400,
|
|
144
148
|
fontSize: '14px',
|
|
145
149
|
}, onClick: handleCollapseAll }, "Collapse all")))),
|
|
146
|
-
React.createElement(ValidationErrorDisplay, { formId: form.id, title: title, errors: errors, showSubmitError: isSubmitted }))),
|
|
150
|
+
React.createElement(ValidationErrorDisplay, { formId: form.id, title: title, errors: errors, showSubmitError: isSubmitted, isSmallerThanMd: isSmallerThanMd }))),
|
|
147
151
|
React.createElement(FormContext.Provider, { value: {
|
|
148
152
|
fetchedOptions,
|
|
149
153
|
setFetchedOptions: updateFetchedOptions,
|
|
@@ -163,6 +167,7 @@ function FormRenderer(props) {
|
|
|
163
167
|
triggerFieldReset,
|
|
164
168
|
showSubmitError: isSubmitted,
|
|
165
169
|
associatedObject,
|
|
170
|
+
width,
|
|
166
171
|
} },
|
|
167
172
|
React.createElement(Box, { sx: {
|
|
168
173
|
paddingX: isSm || isXs ? 2 : 3,
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { ExpandMoreOutlined } from '@mui/icons-material';
|
|
2
2
|
import { isEqual } from 'lodash';
|
|
3
3
|
import React, { useEffect } from 'react';
|
|
4
|
-
import { useFormContext } from '../../../../theme/hooks';
|
|
4
|
+
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
5
5
|
import { Accordion, AccordionDetails, AccordionSummary, Typography } from '../../../core';
|
|
6
6
|
import { Box } from '../../../layout';
|
|
7
7
|
import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
|
|
8
8
|
import { getErrorCountForSection } from './utils';
|
|
9
9
|
function AccordionSections(props) {
|
|
10
10
|
const { entry } = props;
|
|
11
|
-
const { errors, expandedSections, setExpandedSections, expandAll, setExpandAll, showSubmitError } = useFormContext();
|
|
11
|
+
const { errors, expandedSections, setExpandedSections, expandAll, setExpandAll, showSubmitError, width } = useFormContext();
|
|
12
|
+
const { isAbove } = useWidgetSize({
|
|
13
|
+
scroll: false,
|
|
14
|
+
defaultWidth: width,
|
|
15
|
+
});
|
|
12
16
|
const lastSection = entry.sections.length - 1;
|
|
13
17
|
function collectNestedSections(entries = []) {
|
|
14
18
|
const nestedSections = [];
|
|
@@ -32,7 +36,7 @@ function AccordionSections(props) {
|
|
|
32
36
|
sectionList.forEach((section, index) => {
|
|
33
37
|
expandedSections.push({
|
|
34
38
|
label: section.label,
|
|
35
|
-
expanded: expandAll ?? index === 0,
|
|
39
|
+
expanded: expandAll ?? (index === 0 || isAbove('md')),
|
|
36
40
|
id: section?.id,
|
|
37
41
|
isNested: isNested,
|
|
38
42
|
});
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { isEmpty, omit } from 'lodash';
|
|
2
2
|
import React, { useContext, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { useWidgetSize } from '../../../../theme';
|
|
4
4
|
import { Button, LoadingButton } from '../../../core';
|
|
5
5
|
import { Box } from '../../../layout';
|
|
6
6
|
import { FormContext } from './FormContext';
|
|
7
7
|
import { entryIsVisible, getEntryId, getNestedParameterIds, isAddressProperty, scrollIntoViewWithOffset, } from './utils';
|
|
8
8
|
function ActionButtons(props) {
|
|
9
9
|
const { onSubmit, submitButtonLabel, actionType, handleSubmit, onReset, unregister, isModal, entries, setValue, formId, } = props;
|
|
10
|
-
const { isXs } = useResponsive();
|
|
11
10
|
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
|
12
|
-
const { getValues, instance, errors } = useContext(FormContext);
|
|
11
|
+
const { getValues, instance, errors, width } = useContext(FormContext);
|
|
12
|
+
const { breakpoints } = useWidgetSize({
|
|
13
|
+
scroll: false,
|
|
14
|
+
defaultWidth: width,
|
|
15
|
+
});
|
|
16
|
+
const { isXs } = breakpoints;
|
|
13
17
|
const unregisterHiddenFields = (entriesToCheck) => {
|
|
14
18
|
entriesToCheck.forEach((entry) => {
|
|
15
19
|
if (entry.type === 'sections' || entry.type === 'columns') {
|
|
@@ -61,7 +61,9 @@ export const ActionDialog = (props) => {
|
|
|
61
61
|
const handleFormSave = async (data) => {
|
|
62
62
|
return handleSubmit(action, data, instanceId);
|
|
63
63
|
};
|
|
64
|
-
return (React.createElement(Dialog, {
|
|
64
|
+
return (React.createElement(Dialog, { PaperProps: {
|
|
65
|
+
sx: { maxWidth: '950px', width: '100%' },
|
|
66
|
+
}, fullWidth: true, open: open, onClose: (e, reason) => reason !== 'backdropClick' && onClose() },
|
|
65
67
|
React.createElement(DialogTitle, { sx: { ...styles.dialogTitle, borderBottom: action.type === 'delete' ? undefined : '1px solid #e9ecef' } },
|
|
66
68
|
React.createElement(IconButton, { sx: styles.closeIcon, onClick: onClose },
|
|
67
69
|
React.createElement(Close, { fontSize: "small" })),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useApiServices } from '@evoke-platform/context';
|
|
2
2
|
import React, { useState } from 'react';
|
|
3
3
|
import { AutorenewRounded, Close, FileWithExtension, LaunchRounded } from '../../../../../../icons';
|
|
4
|
+
import { useWidgetSize } from '../../../../../../theme';
|
|
5
|
+
import { useFormContext } from '../../../../../../theme/hooks';
|
|
4
6
|
import { Button, Dialog, DialogContent, DialogTitle, IconButton, Menu, MenuItem, Typography, } from '../../../../../core';
|
|
5
7
|
import { Grid } from '../../../../../layout';
|
|
6
8
|
import { getPrefixedUrl } from '../../utils';
|
|
@@ -29,7 +31,13 @@ const DocumentView = (props) => {
|
|
|
29
31
|
} }, document?.name))));
|
|
30
32
|
};
|
|
31
33
|
export const DocumentViewerCell = (props) => {
|
|
32
|
-
const { instance, propertyId, setSnackbarError
|
|
34
|
+
const { instance, propertyId, setSnackbarError } = props;
|
|
35
|
+
const { width } = useFormContext();
|
|
36
|
+
const { isBelow } = useWidgetSize({
|
|
37
|
+
scroll: false,
|
|
38
|
+
defaultWidth: width,
|
|
39
|
+
});
|
|
40
|
+
const smallerThanMd = isBelow('md');
|
|
33
41
|
const apiServices = useApiServices();
|
|
34
42
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
35
43
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -4,8 +4,7 @@ import { DateTime } from 'luxon';
|
|
|
4
4
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
5
5
|
import sift from 'sift';
|
|
6
6
|
import { Edit, ExpandMoreOutlined, TrashCan } from '../../../../../../icons';
|
|
7
|
-
import {
|
|
8
|
-
import { useFormContext } from '../../../../../../theme/hooks';
|
|
7
|
+
import useWidgetSize, { useFormContext } from '../../../../../../theme/hooks';
|
|
9
8
|
import { Accordion, AccordionDetails, AccordionSummary, Button, IconButton, Skeleton, Snackbar, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, } from '../../../../../core';
|
|
10
9
|
import { Box } from '../../../../../layout';
|
|
11
10
|
import { getReadableQuery } from '../../../../CriteriaBuilder';
|
|
@@ -36,11 +35,14 @@ const styles = {
|
|
|
36
35
|
};
|
|
37
36
|
const RepeatableField = (props) => {
|
|
38
37
|
const { fieldDefinition, canUpdateProperty, criteria, viewLayout, entry, createActionId, updateActionId, deleteActionId, } = props;
|
|
39
|
-
const { fetchedOptions, setFetchedOptions, instance } = useFormContext();
|
|
38
|
+
const { fetchedOptions, setFetchedOptions, instance, width } = useFormContext();
|
|
39
|
+
const { isBelow } = useWidgetSize({
|
|
40
|
+
scroll: false,
|
|
41
|
+
defaultWidth: width,
|
|
42
|
+
});
|
|
43
|
+
const smallerThanMd = isBelow('md');
|
|
40
44
|
const { instanceChanges } = useNotification();
|
|
41
45
|
const apiServices = useApiServices();
|
|
42
|
-
const { smallerThan } = useResponsive();
|
|
43
|
-
const smallerThanMd = smallerThan('md');
|
|
44
46
|
const [reloadOnErrorTrigger, setReloadOnErrorTrigger] = useState(true);
|
|
45
47
|
const [criteriaObjects, setCriteriaObjects] = useState([]);
|
|
46
48
|
const [selectedRow, setSelectedRow] = useState();
|
|
@@ -491,7 +491,9 @@ const ObjectPropertyInput = (props) => {
|
|
|
491
491
|
event.stopPropagation();
|
|
492
492
|
setOpenCreateDialog(true);
|
|
493
493
|
}, "aria-label": `Add` }, "Add")))),
|
|
494
|
-
openCreateDialog && (React.createElement(React.Fragment, null, nestedFieldsView ? (React.createElement(RelatedObjectInstance, { id: id, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, nestedFieldsView: nestedFieldsView, mode: mode, displayOption: displayOption, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: form?.id, actionId: createActionId, setSnackbarError: setSnackbarError, fieldDefinition: fieldDefinition })) : (React.createElement(Dialog, { fullWidth: true,
|
|
494
|
+
openCreateDialog && (React.createElement(React.Fragment, null, nestedFieldsView ? (React.createElement(RelatedObjectInstance, { id: id, handleClose: handleClose, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, nestedFieldsView: nestedFieldsView, mode: mode, displayOption: displayOption, setOptions: setOptions, options: options, filter: updatedCriteria, layout: layout, formId: form?.id, actionId: createActionId, setSnackbarError: setSnackbarError, fieldDefinition: fieldDefinition })) : (React.createElement(Dialog, { fullWidth: true, PaperProps: {
|
|
495
|
+
sx: { maxWidth: '950px', width: '100%' },
|
|
496
|
+
}, open: openCreateDialog, onClose: (e, reason) => reason !== 'backdropClick' && handleClose },
|
|
495
497
|
React.createElement(DialogTitle, { sx: {
|
|
496
498
|
fontSize: '18px',
|
|
497
499
|
fontWeight: 700,
|
|
@@ -2,8 +2,7 @@ import { useApiServices, useAuthenticationContext, } from '@evoke-platform/conte
|
|
|
2
2
|
import { WarningRounded } from '@mui/icons-material';
|
|
3
3
|
import DOMPurify from 'dompurify';
|
|
4
4
|
import React, { useEffect, useMemo } from 'react';
|
|
5
|
-
import {
|
|
6
|
-
import { useFormContext } from '../../../../theme/hooks';
|
|
5
|
+
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
7
6
|
import { TextField, Typography } from '../../../core';
|
|
8
7
|
import { Box } from '../../../layout';
|
|
9
8
|
import FormField from '../../FormField';
|
|
@@ -38,13 +37,17 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
38
37
|
}
|
|
39
38
|
export function RecursiveEntryRenderer(props) {
|
|
40
39
|
const { entry, isDocument } = props;
|
|
41
|
-
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor, parameters, handleChange, fieldHeight, triggerFieldReset, associatedObject, } = useFormContext();
|
|
40
|
+
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor, parameters, handleChange, fieldHeight, triggerFieldReset, associatedObject, width, } = useFormContext();
|
|
42
41
|
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
43
42
|
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
44
43
|
return null;
|
|
45
44
|
}
|
|
46
|
-
const {
|
|
47
|
-
|
|
45
|
+
const { isBelow, breakpoints } = useWidgetSize({
|
|
46
|
+
scroll: false,
|
|
47
|
+
defaultWidth: width,
|
|
48
|
+
});
|
|
49
|
+
const { isXs } = breakpoints;
|
|
50
|
+
const isSmallerThanMd = isBelow('md');
|
|
48
51
|
const apiServices = useApiServices();
|
|
49
52
|
const userAccount = useAuthenticationContext()?.account;
|
|
50
53
|
const entryId = getEntryId(entry) || 'defaultId';
|
|
@@ -168,7 +171,12 @@ export function RecursiveEntryRenderer(props) {
|
|
|
168
171
|
}
|
|
169
172
|
}
|
|
170
173
|
else if (entry.type === 'columns') {
|
|
171
|
-
return (React.createElement(Box, { sx: {
|
|
174
|
+
return (React.createElement(Box, { sx: {
|
|
175
|
+
display: 'flex',
|
|
176
|
+
alignItems: 'flex-start',
|
|
177
|
+
gap: '30px',
|
|
178
|
+
flexDirection: isXs ? 'column' : 'row',
|
|
179
|
+
} }, entry.columns.map((column, colIndex) => (
|
|
172
180
|
// calculating the width like this rather than flex={column.width} to prevent collections from being too wide
|
|
173
181
|
React.createElement(Box, { key: colIndex, sx: { width: isXs ? '100%' : `calc(${(column.width / 12) * 100}% - 15px)` } }, column.entries?.map((columnEntry, entryIndex) => {
|
|
174
182
|
return (React.createElement(RecursiveEntryRenderer, { key: entryIndex + (columnEntry?.parameterId ?? ''), entry: columnEntry }));
|
|
@@ -5,6 +5,7 @@ export type ValidationErrorDisplayProps = {
|
|
|
5
5
|
title?: string;
|
|
6
6
|
errors?: FieldErrors;
|
|
7
7
|
showSubmitError?: boolean;
|
|
8
|
+
isSmallerThanMd: boolean;
|
|
8
9
|
};
|
|
9
10
|
declare function ValidationErrorDisplay(props: ValidationErrorDisplayProps): React.JSX.Element | null;
|
|
10
11
|
export default ValidationErrorDisplay;
|
package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { useResponsive } from '../../../../../theme';
|
|
3
2
|
import { List, ListItem, Typography } from '../../../../core';
|
|
4
3
|
import { Box } from '../../../../layout';
|
|
5
4
|
function ValidationErrorDisplay(props) {
|
|
6
|
-
const { formId, title, errors, showSubmitError } = props;
|
|
7
|
-
const { isSm, isXs } = useResponsive();
|
|
5
|
+
const { formId, title, errors, showSubmitError, isSmallerThanMd } = props;
|
|
8
6
|
function extractErrorMessages(errors) {
|
|
9
7
|
const messages = [];
|
|
10
8
|
for (const key in errors) {
|
|
@@ -31,8 +29,8 @@ function ValidationErrorDisplay(props) {
|
|
|
31
29
|
border: '1px solid #721c24',
|
|
32
30
|
padding: '8px 24px',
|
|
33
31
|
borderRadius: '4px',
|
|
34
|
-
marginBottom:
|
|
35
|
-
marginTop: !title ? (
|
|
32
|
+
marginBottom: isSmallerThanMd ? 2 : 3,
|
|
33
|
+
marginTop: !title ? (isSmallerThanMd ? -2 : -3) : undefined,
|
|
36
34
|
} },
|
|
37
35
|
React.createElement(Typography, { sx: { color: '#721c24', mt: '16px', mb: '8px' } }, "Please fix the following errors before submitting:"),
|
|
38
36
|
React.createElement(List, { sx: {
|
|
@@ -10,6 +10,12 @@ import { expect, it } from 'vitest';
|
|
|
10
10
|
import FormRenderer from '../FormRenderer';
|
|
11
11
|
import { accessibility508Object, createSpecialtyForm, jsonLogicDisplayTestSpecialtyForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, simpleConditionDisplayTestSpecialtyForm, specialtyObject, specialtyTypeObject, UpdateAccessibilityFormOne, UpdateAccessibilityFormTwo, users, } from './test-data';
|
|
12
12
|
expect.extend(matchers);
|
|
13
|
+
// Mock ResizeObserver
|
|
14
|
+
global.ResizeObserver = class ResizeObserver {
|
|
15
|
+
observe() { }
|
|
16
|
+
unobserve() { }
|
|
17
|
+
disconnect() { }
|
|
18
|
+
};
|
|
13
19
|
const removePoppers = () => {
|
|
14
20
|
const portalSelectors = ['.MuiAutocomplete-popper'];
|
|
15
21
|
portalSelectors.forEach((selector) => {
|
|
@@ -372,6 +378,9 @@ describe('Form component', () => {
|
|
|
372
378
|
await user.tab();
|
|
373
379
|
await user.tab();
|
|
374
380
|
await user.keyboard('{Enter}');
|
|
381
|
+
await waitFor(() => {
|
|
382
|
+
expect(screen.getByText('+ Add New')).toBeInTheDocument();
|
|
383
|
+
});
|
|
375
384
|
await user.keyboard('{ArrowUp}'); // Navigate to "Add New" option
|
|
376
385
|
await user.keyboard('{Enter}');
|
|
377
386
|
await waitFor(() => {
|
|
@@ -10,6 +10,12 @@ import { expect, it } from 'vitest';
|
|
|
10
10
|
import FormRendererContainer from '../FormRendererContainer';
|
|
11
11
|
import { createSpecialtyForm, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
|
|
12
12
|
expect.extend(matchers);
|
|
13
|
+
// Mock ResizeObserver
|
|
14
|
+
global.ResizeObserver = class ResizeObserver {
|
|
15
|
+
observe() { }
|
|
16
|
+
unobserve() { }
|
|
17
|
+
disconnect() { }
|
|
18
|
+
};
|
|
13
19
|
const removePoppers = () => {
|
|
14
20
|
const portalSelectors = ['.MuiAutocomplete-popper'];
|
|
15
21
|
portalSelectors.forEach((selector) => {
|
|
@@ -39,19 +39,19 @@ export interface WidgetSizeInfo {
|
|
|
39
39
|
height: number;
|
|
40
40
|
/** Widget size breakpoints */
|
|
41
41
|
breakpoints: {
|
|
42
|
-
/** Extra small: width <
|
|
42
|
+
/** Extra small: width < sm threshold (default: < 600px) */
|
|
43
43
|
isXs: boolean;
|
|
44
|
-
/** Small:
|
|
44
|
+
/** Small: sm <= width < md (default: 600px - 899px) */
|
|
45
45
|
isSm: boolean;
|
|
46
|
-
/** Medium:
|
|
46
|
+
/** Medium: md <= width < lg (default: 900px - 1199px) */
|
|
47
47
|
isMd: boolean;
|
|
48
|
-
/** Large:
|
|
48
|
+
/** Large: lg <= width < xl (default: 1200px - 1535px) */
|
|
49
49
|
isLg: boolean;
|
|
50
|
-
/** Extra large: width >=
|
|
50
|
+
/** Extra large: width >= xl (default: >= 1536px) */
|
|
51
51
|
isXl: boolean;
|
|
52
|
-
/** Compact height: height < 300px */
|
|
52
|
+
/** Compact height: height < compact threshold (default: < 300px) */
|
|
53
53
|
isCompact: boolean;
|
|
54
|
-
/** Tall height: height >= 600px */
|
|
54
|
+
/** Tall height: height >= tall threshold (default: >= 600px) */
|
|
55
55
|
isTall: boolean;
|
|
56
56
|
};
|
|
57
57
|
/** Bounds information from react-use-measure */
|
|
@@ -74,9 +74,9 @@ export interface WidgetSizeInfo {
|
|
|
74
74
|
y: number;
|
|
75
75
|
};
|
|
76
76
|
/** Check if widget is above a breakpoint */
|
|
77
|
-
isAbove: (breakpoint:
|
|
77
|
+
isAbove: (breakpoint: Breakpoint) => boolean;
|
|
78
78
|
/** Check if widget is below a breakpoint */
|
|
79
|
-
isBelow: (breakpoint:
|
|
79
|
+
isBelow: (breakpoint: Breakpoint) => boolean;
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
82
|
* Custom hook that measures widget dimensions and provides responsive breakpoints
|
|
@@ -106,6 +106,23 @@ export declare const useWidgetSize: (options?: {
|
|
|
106
106
|
scroll?: boolean;
|
|
107
107
|
/** Resize containers to observe */
|
|
108
108
|
resize?: boolean;
|
|
109
|
+
/** Initial width to assume the Ref is */
|
|
110
|
+
defaultWidth?: number;
|
|
111
|
+
/** Custom breakpoint thresholds (defaults to MUI theme breakpoints) */
|
|
112
|
+
breakpoints?: {
|
|
113
|
+
xs?: number;
|
|
114
|
+
sm?: number;
|
|
115
|
+
md?: number;
|
|
116
|
+
lg?: number;
|
|
117
|
+
xl?: number;
|
|
118
|
+
};
|
|
119
|
+
/** Height breakpoint thresholds */
|
|
120
|
+
heightBreakpoints?: {
|
|
121
|
+
/** Compact height threshold (default: 300) */
|
|
122
|
+
compact?: number;
|
|
123
|
+
/** Tall height threshold (default: 600) */
|
|
124
|
+
tall?: number;
|
|
125
|
+
};
|
|
109
126
|
}) => WidgetSizeInfo;
|
|
110
127
|
export default useWidgetSize;
|
|
111
128
|
export declare function useFormContext(): {
|
|
@@ -130,4 +147,5 @@ export declare function useFormContext(): {
|
|
|
130
147
|
instanceId?: string | undefined;
|
|
131
148
|
propertyId?: string | undefined;
|
|
132
149
|
} | undefined;
|
|
150
|
+
width: number;
|
|
133
151
|
};
|
|
@@ -56,31 +56,35 @@ export function useResponsive() {
|
|
|
56
56
|
* ```
|
|
57
57
|
*/
|
|
58
58
|
export const useWidgetSize = (options) => {
|
|
59
|
+
const theme = useTheme();
|
|
59
60
|
const [ref, bounds] = useMeasure({
|
|
60
61
|
debounce: options?.debounce ?? 16,
|
|
61
62
|
scroll: options?.scroll ?? true,
|
|
62
63
|
offsetSize: true,
|
|
63
64
|
...options,
|
|
64
65
|
});
|
|
65
|
-
const width = bounds.width || 0;
|
|
66
|
+
const width = bounds.width || options?.defaultWidth || 0;
|
|
66
67
|
const height = bounds.height || 0;
|
|
67
|
-
//
|
|
68
|
-
const breakpoints = {
|
|
69
|
-
isXs: width < 800,
|
|
70
|
-
isSm: width >= 800 && width < 1000,
|
|
71
|
-
isMd: width >= 1000 && width < 1200,
|
|
72
|
-
isLg: width >= 1200 && width < 1400,
|
|
73
|
-
isXl: width >= 1400,
|
|
74
|
-
isCompact: height < 300,
|
|
75
|
-
isTall: height >= 600,
|
|
76
|
-
};
|
|
77
|
-
// Breakpoint thresholds
|
|
68
|
+
// Use custom breakpoints if provided, otherwise default to MUI theme breakpoints
|
|
78
69
|
const breakpointThresholds = {
|
|
79
|
-
xs:
|
|
80
|
-
sm:
|
|
81
|
-
md:
|
|
82
|
-
lg:
|
|
83
|
-
xl:
|
|
70
|
+
xs: options?.breakpoints?.xs ?? theme.breakpoints.values.xs,
|
|
71
|
+
sm: options?.breakpoints?.sm ?? theme.breakpoints.values.sm,
|
|
72
|
+
md: options?.breakpoints?.md ?? theme.breakpoints.values.md,
|
|
73
|
+
lg: options?.breakpoints?.lg ?? theme.breakpoints.values.lg,
|
|
74
|
+
xl: options?.breakpoints?.xl ?? theme.breakpoints.values.xl,
|
|
75
|
+
};
|
|
76
|
+
const heightThresholds = {
|
|
77
|
+
compact: options?.heightBreakpoints?.compact ?? 300,
|
|
78
|
+
tall: options?.heightBreakpoints?.tall ?? 600,
|
|
79
|
+
};
|
|
80
|
+
const breakpoints = {
|
|
81
|
+
isXs: width < breakpointThresholds.sm,
|
|
82
|
+
isSm: width >= breakpointThresholds.sm && width < breakpointThresholds.md,
|
|
83
|
+
isMd: width >= breakpointThresholds.md && width < breakpointThresholds.lg,
|
|
84
|
+
isLg: width >= breakpointThresholds.lg && width < breakpointThresholds.xl,
|
|
85
|
+
isXl: width >= breakpointThresholds.xl,
|
|
86
|
+
isCompact: height < heightThresholds.compact,
|
|
87
|
+
isTall: height >= heightThresholds.tall,
|
|
84
88
|
};
|
|
85
89
|
// Utility functions for responsive checks
|
|
86
90
|
const isAbove = (breakpoint) => {
|
|
@@ -46,8 +46,8 @@ describe('useWidgetSize', () => {
|
|
|
46
46
|
expect(result.current.height).toBe(456);
|
|
47
47
|
});
|
|
48
48
|
describe('breakpoints', () => {
|
|
49
|
-
it('should set isXs for width <
|
|
50
|
-
setMockBounds({ width:
|
|
49
|
+
it('should set isXs for width < 600', () => {
|
|
50
|
+
setMockBounds({ width: 599, height: 100 });
|
|
51
51
|
const { result } = renderHook(() => useWidgetSize());
|
|
52
52
|
expect(result.current.breakpoints.isXs).toBe(true);
|
|
53
53
|
expect(result.current.breakpoints.isSm).toBe(false);
|
|
@@ -55,15 +55,15 @@ describe('useWidgetSize', () => {
|
|
|
55
55
|
expect(result.current.breakpoints.isLg).toBe(false);
|
|
56
56
|
expect(result.current.breakpoints.isXl).toBe(false);
|
|
57
57
|
});
|
|
58
|
-
it('should set isSm for
|
|
59
|
-
setMockBounds({ width:
|
|
58
|
+
it('should set isSm for 600 <= width < 900', () => {
|
|
59
|
+
setMockBounds({ width: 600, height: 100 });
|
|
60
60
|
let { result } = renderHook(() => useWidgetSize());
|
|
61
61
|
expect(result.current.breakpoints.isXs).toBe(false);
|
|
62
62
|
expect(result.current.breakpoints.isSm).toBe(true);
|
|
63
63
|
expect(result.current.breakpoints.isMd).toBe(false);
|
|
64
64
|
expect(result.current.breakpoints.isLg).toBe(false);
|
|
65
65
|
expect(result.current.breakpoints.isXl).toBe(false);
|
|
66
|
-
setMockBounds({ width:
|
|
66
|
+
setMockBounds({ width: 899, height: 100 });
|
|
67
67
|
result = renderHook(() => useWidgetSize()).result;
|
|
68
68
|
expect(result.current.breakpoints.isXs).toBe(false);
|
|
69
69
|
expect(result.current.breakpoints.isSm).toBe(true);
|
|
@@ -71,8 +71,8 @@ describe('useWidgetSize', () => {
|
|
|
71
71
|
expect(result.current.breakpoints.isLg).toBe(false);
|
|
72
72
|
expect(result.current.breakpoints.isXl).toBe(false);
|
|
73
73
|
});
|
|
74
|
-
it('should set isMd for
|
|
75
|
-
setMockBounds({ width:
|
|
74
|
+
it('should set isMd for 900 <= width < 1200', () => {
|
|
75
|
+
setMockBounds({ width: 900, height: 100 });
|
|
76
76
|
let { result } = renderHook(() => useWidgetSize());
|
|
77
77
|
expect(result.current.breakpoints.isXs).toBe(false);
|
|
78
78
|
expect(result.current.breakpoints.isSm).toBe(false);
|
|
@@ -87,7 +87,7 @@ describe('useWidgetSize', () => {
|
|
|
87
87
|
expect(result.current.breakpoints.isLg).toBe(false);
|
|
88
88
|
expect(result.current.breakpoints.isXl).toBe(false);
|
|
89
89
|
});
|
|
90
|
-
it('should set isLg for 1200 <= width <
|
|
90
|
+
it('should set isLg for 1200 <= width < 1536', () => {
|
|
91
91
|
setMockBounds({ width: 1200, height: 100 });
|
|
92
92
|
let { result } = renderHook(() => useWidgetSize());
|
|
93
93
|
expect(result.current.breakpoints.isXs).toBe(false);
|
|
@@ -95,7 +95,7 @@ describe('useWidgetSize', () => {
|
|
|
95
95
|
expect(result.current.breakpoints.isMd).toBe(false);
|
|
96
96
|
expect(result.current.breakpoints.isLg).toBe(true);
|
|
97
97
|
expect(result.current.breakpoints.isXl).toBe(false);
|
|
98
|
-
setMockBounds({ width:
|
|
98
|
+
setMockBounds({ width: 1535, height: 100 });
|
|
99
99
|
result = renderHook(() => useWidgetSize()).result;
|
|
100
100
|
expect(result.current.breakpoints.isXs).toBe(false);
|
|
101
101
|
expect(result.current.breakpoints.isSm).toBe(false);
|
|
@@ -103,8 +103,8 @@ describe('useWidgetSize', () => {
|
|
|
103
103
|
expect(result.current.breakpoints.isLg).toBe(true);
|
|
104
104
|
expect(result.current.breakpoints.isXl).toBe(false);
|
|
105
105
|
});
|
|
106
|
-
it('should set isXl for width >=
|
|
107
|
-
setMockBounds({ width:
|
|
106
|
+
it('should set isXl for width >= 1536', () => {
|
|
107
|
+
setMockBounds({ width: 1536, height: 100 });
|
|
108
108
|
let { result } = renderHook(() => useWidgetSize());
|
|
109
109
|
expect(result.current.breakpoints.isXs).toBe(false);
|
|
110
110
|
expect(result.current.breakpoints.isSm).toBe(false);
|