@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.
Files changed (18) hide show
  1. package/dist/published/components/custom/FormV2/FormRenderer.js +10 -5
  2. package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -3
  3. package/dist/published/components/custom/FormV2/components/ActionButtons.js +7 -3
  4. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +1 -0
  5. package/dist/published/components/custom/FormV2/components/FormContext.js +2 -0
  6. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +3 -1
  7. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DocumentViewerCell.js +9 -1
  8. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +7 -5
  9. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +3 -1
  10. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +14 -6
  11. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.d.ts +1 -0
  12. package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrorDisplay.js +3 -5
  13. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +9 -0
  14. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +6 -0
  15. package/dist/published/theme/hooks.d.ts +27 -9
  16. package/dist/published/theme/hooks.js +21 -17
  17. package/dist/published/theme/hooks.test.js +11 -11
  18. 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 { useResponsive } from '../../../theme';
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 { isSm, isXs, smallerThan } = useResponsive();
23
- const isSmallerThanMd = smallerThan('md');
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(React.Fragment, null,
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 { useResponsive } from '../../../../theme';
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') {
@@ -24,6 +24,7 @@ type FormContextType = {
24
24
  instanceId?: string;
25
25
  propertyId?: string;
26
26
  };
27
+ width: number;
27
28
  };
28
29
  export declare const FormContext: import("react").Context<FormContextType>;
29
30
  export {};
@@ -14,4 +14,6 @@ export const FormContext = createContext({
14
14
  triggerFieldReset: false,
15
15
  handleChange: () => { },
16
16
  fieldHeight: 'medium',
17
+ // Default width 1200 to match common 'lg' render size and avoid style changes on first render
18
+ width: 1200,
17
19
  });
@@ -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, { maxWidth: 'md', fullWidth: true, open: open, onClose: (e, reason) => reason !== 'backdropClick' && onClose() },
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, smallerThanMd } = props;
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 { useResponsive } from '../../../../../../theme';
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, maxWidth: "md", open: openCreateDialog, onClose: (e, reason) => reason !== 'backdropClick' && handleClose },
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 { useResponsive } from '../../../../theme';
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 { isXs, smallerThan } = useResponsive();
47
- const isSmallerThanMd = smallerThan('md');
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: { display: 'flex', alignItems: 'flex-start', gap: '30px', flexDirection: isXs ? 'column' : 'row' } }, entry.columns.map((column, colIndex) => (
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;
@@ -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: isSm || isXs ? 2 : 3,
35
- marginTop: !title ? (isSm || isXs ? -2 : -3) : undefined,
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 < 800px */
42
+ /** Extra small: width < sm threshold (default: < 600px) */
43
43
  isXs: boolean;
44
- /** Small: 800px <= width < 1000px */
44
+ /** Small: sm <= width < md (default: 600px - 899px) */
45
45
  isSm: boolean;
46
- /** Medium: 1000px <= width < 1200px */
46
+ /** Medium: md <= width < lg (default: 900px - 1199px) */
47
47
  isMd: boolean;
48
- /** Large: 1200px <= width < 1400px */
48
+ /** Large: lg <= width < xl (default: 1200px - 1535px) */
49
49
  isLg: boolean;
50
- /** Extra large: width >= 1400px */
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: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => boolean;
77
+ isAbove: (breakpoint: Breakpoint) => boolean;
78
78
  /** Check if widget is below a breakpoint */
79
- isBelow: (breakpoint: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => boolean;
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
- // Widget-specific breakpoints based on container width
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: 800,
80
- sm: 1000,
81
- md: 1200,
82
- lg: 1400,
83
- xl: Infinity,
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 < 800', () => {
50
- setMockBounds({ width: 799, height: 100 });
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 800 <= width < 1000', () => {
59
- setMockBounds({ width: 800, height: 100 });
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: 999, height: 100 });
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 1000 <= width < 1200', () => {
75
- setMockBounds({ width: 1000, height: 100 });
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 < 1400', () => {
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: 1399, height: 100 });
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 >= 1400', () => {
107
- setMockBounds({ width: 1400, height: 100 });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/ui-components",
3
- "version": "1.9.0-testing.4",
3
+ "version": "1.9.0-testing.5",
4
4
  "description": "",
5
5
  "main": "dist/published/index.js",
6
6
  "module": "dist/published/index.js",