@evoke-platform/ui-components 1.6.0-dev.1 → 1.6.0-dev.10

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 (41) hide show
  1. package/dist/published/components/core/SwipeableDrawer/SwipeableDrawer.d.ts +4 -0
  2. package/dist/published/components/core/SwipeableDrawer/SwipeableDrawer.js +8 -0
  3. package/dist/published/components/core/SwipeableDrawer/index.d.ts +3 -0
  4. package/dist/published/components/core/SwipeableDrawer/index.js +3 -0
  5. package/dist/published/components/core/index.d.ts +2 -1
  6. package/dist/published/components/core/index.js +2 -1
  7. package/dist/published/components/custom/BuilderGrid/BuilderGrid.js +27 -27
  8. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +87 -57
  9. package/dist/published/components/custom/Form/tests/Form.test.js +1 -1
  10. package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
  11. package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +3 -3
  12. package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +7 -1
  13. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +7 -1
  14. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -3
  15. package/dist/published/components/custom/FormField/Select/Select.js +17 -5
  16. package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +13 -3
  17. package/dist/published/components/custom/HistoryLog/HistoryData.d.ts +1 -0
  18. package/dist/published/components/custom/HistoryLog/HistoryData.js +14 -6
  19. package/dist/published/components/custom/HistoryLog/HistoryLoading.d.ts +4 -1
  20. package/dist/published/components/custom/HistoryLog/HistoryLoading.js +14 -8
  21. package/dist/published/components/custom/HistoryLog/index.d.ts +2 -0
  22. package/dist/published/components/custom/HistoryLog/index.js +4 -4
  23. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.d.ts +33 -0
  24. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.js +143 -0
  25. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.test.d.ts +1 -0
  26. package/dist/published/components/custom/ResponsiveOverflow/ResponsiveOverflow.test.js +100 -0
  27. package/dist/published/components/custom/ResponsiveOverflow/index.d.ts +4 -0
  28. package/dist/published/components/custom/ResponsiveOverflow/index.js +3 -0
  29. package/dist/published/components/custom/index.d.ts +1 -0
  30. package/dist/published/components/custom/index.js +1 -0
  31. package/dist/published/index.d.ts +1 -1
  32. package/dist/published/index.js +1 -1
  33. package/dist/published/stories/ResponsiveOverflow.stories.d.ts +8 -0
  34. package/dist/published/stories/ResponsiveOverflow.stories.js +91 -0
  35. package/dist/published/theme/hooks.d.ts +78 -0
  36. package/dist/published/theme/hooks.js +65 -0
  37. package/dist/published/theme/hooks.test.d.ts +1 -0
  38. package/dist/published/theme/hooks.test.js +187 -0
  39. package/dist/published/theme/index.d.ts +2 -2
  40. package/dist/published/theme/index.js +2 -2
  41. package/package.json +2 -1
@@ -10,6 +10,8 @@ export type HistoryLogProps = {
10
10
  loading?: boolean;
11
11
  /** The title displayed above the timeline */
12
12
  title?: string;
13
+ /** Whether to hide the timeline in the history log */
14
+ hideTimeline?: boolean;
13
15
  };
14
16
  /**
15
17
  * Renders a timeline of the instance's history log.
@@ -20,7 +20,7 @@ const { format } = require('small-date');
20
20
  * @returns {JSX.Element} A timeline view representing the instance's history.
21
21
  */
22
22
  export const HistoryLog = (props) => {
23
- const { object, history, loading, title } = props;
23
+ const { object, history, loading, title, hideTimeline } = props;
24
24
  const [historyMap, setHistoryMap] = useState({});
25
25
  const [documentHistory, setDocumentHistory] = useState({});
26
26
  const [referencedObjects, setReferencedObjects] = useState([]);
@@ -91,7 +91,7 @@ export const HistoryLog = (props) => {
91
91
  if (records.length) {
92
92
  return (React.createElement(React.Fragment, { key: date },
93
93
  React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
94
- React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } }),
94
+ !hideTimeline && (React.createElement(Circle, { color: "primary", sx: { fontSize: '12px', marginRight: '12px' } })),
95
95
  React.createElement(Typography, { sx: { fontWeight: 600, fontSize: '16px' } },
96
96
  format(new Date(), 'yyyy-MM-dd') === date
97
97
  ? 'Today'
@@ -99,14 +99,14 @@ export const HistoryLog = (props) => {
99
99
  ' ',
100
100
  "\u00A0"),
101
101
  React.createElement(Typography, { sx: { fontWeight: 600, fontSize: '16px', color: '#637381' } }, format(new Date(date + ' 00:00:000'), 'MMM dd, yyyy'))),
102
- React.createElement(HistoricalData, { object: object, records: records, documentHistory: documentHistory, referencedObjects: referencedObjects })));
102
+ React.createElement(HistoricalData, { object: object, records: records, documentHistory: documentHistory, referencedObjects: referencedObjects, hideTimeline: hideTimeline })));
103
103
  }
104
104
  return null;
105
105
  }),
106
106
  !loading && filteredHistory && Object.values(filteredHistory).every((v) => !v.length) && (React.createElement(Box, { width: '100%', display: 'grid', justifyContent: 'center', marginTop: '60px' },
107
107
  React.createElement(Typography, { fontSize: '20px', fontWeight: 700 }, "You Have No History"),
108
108
  React.createElement(Typography, { fontSize: '14px', fontWeight: 400 }, "Try modifying the history type."))),
109
- loading && React.createElement(HistoryLoading, null),
109
+ loading && React.createElement(HistoryLoading, { hideTimeline: hideTimeline }),
110
110
  React.createElement(Snackbar, { open: showSnackbar, handleClose: () => setShowSnackbar(false), message: 'Error occurred when loading referenced objects', error: true })));
111
111
  };
112
112
  export default HistoryLog;
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ export interface ResponsiveOverflowProps {
3
+ /**
4
+ * Children elements to be displayed horizontally
5
+ */
6
+ children: React.ReactNode[];
7
+ /**
8
+ * Text for the overflow button. Default is "More"
9
+ */
10
+ moreButtonText?: string;
11
+ /**
12
+ * Custom button element to replace the default "More" button
13
+ */
14
+ customMoreButton?: React.ReactNode;
15
+ /**
16
+ * Maximum number of children to be displayed before overflowing
17
+ */
18
+ maxVisible?: number;
19
+ /**
20
+ * Function to render each overflow menu item. Receives the overflowed child and its index.
21
+ */
22
+ renderOverflowMenuItem?: (item: React.ReactNode) => React.ReactNode;
23
+ /**
24
+ * If true, the container will take full width
25
+ */
26
+ fullWidth?: boolean;
27
+ }
28
+ /**
29
+ * ResponsiveOverflow is a container component that displays children horizontally,
30
+ * automatically moving overflow items into a dropdown menu when there isn't enough space.
31
+ */
32
+ export declare const ResponsiveOverflow: (props: ResponsiveOverflowProps) => React.JSX.Element;
33
+ export default ResponsiveOverflow;
@@ -0,0 +1,143 @@
1
+ import { KeyboardArrowDownRounded } from '@mui/icons-material';
2
+ import { Box, Menu, MenuItem } from '@mui/material';
3
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { Button } from '../../core';
5
+ const styles = {
6
+ container: {
7
+ display: 'flex',
8
+ alignItems: 'center',
9
+ flex: '1 1 auto',
10
+ minWidth: 0,
11
+ overflow: 'hidden',
12
+ justifyContent: 'flex-end',
13
+ },
14
+ moreButton: {
15
+ color: '#637381',
16
+ fontSize: '14px',
17
+ fontWeight: 700,
18
+ minWidth: 'unset',
19
+ '& .MuiButton-endIcon': { margin: '0px' },
20
+ },
21
+ menuPaper: {
22
+ border: '0.5px solid #919EAB52',
23
+ borderRadius: '8px',
24
+ minWidth: '150px',
25
+ boxShadow: '0px 8px 16px 0px #0000001F',
26
+ },
27
+ horizontalItems: {
28
+ display: 'inline-block',
29
+ verticalAlign: 'top',
30
+ whiteSpace: 'nowrap',
31
+ },
32
+ fullWidthItems: {
33
+ padding: '0 4px',
34
+ flexGrow: 1,
35
+ },
36
+ };
37
+ /**
38
+ * ResponsiveOverflow is a container component that displays children horizontally,
39
+ * automatically moving overflow items into a dropdown menu when there isn't enough space.
40
+ */
41
+ export const ResponsiveOverflow = (props) => {
42
+ const { children, customMoreButton, maxVisible, renderOverflowMenuItem, moreButtonText, fullWidth } = props;
43
+ const containerRef = useRef(null);
44
+ const itemRefs = useRef([]);
45
+ const moreButtonBoxRef = useRef(null);
46
+ const [overflowItems, setOverflowItems] = useState([]);
47
+ const [anchorEl, setAnchorEl] = useState(null);
48
+ // Initialize refs array with correct length
49
+ useEffect(() => {
50
+ itemRefs.current = itemRefs.current.slice(0, children.length);
51
+ handleResize();
52
+ }, [children]);
53
+ const handleResize = useCallback(() => {
54
+ if (!containerRef.current)
55
+ return;
56
+ // Reset all items to visible before measuring
57
+ itemRefs.current.forEach((item) => {
58
+ if (item) {
59
+ item.style.display = 'inline-block';
60
+ }
61
+ });
62
+ const availableWidth = containerRef.current.getBoundingClientRect().width;
63
+ let usedWidth = 0;
64
+ let maxFit = children.length;
65
+ // Determine which items fit and which overflow
66
+ for (let i = 0; i < itemRefs.current.length; i++) {
67
+ const item = itemRefs.current[i];
68
+ if (!item) {
69
+ continue;
70
+ }
71
+ const itemWidth = item.getBoundingClientRect().width;
72
+ // Calculate if we need the "More" button
73
+ const remainingItems = itemRefs.current.length - i - 1;
74
+ const moreButtonWidth = remainingItems > 0 && moreButtonBoxRef.current
75
+ ? moreButtonBoxRef.current.getBoundingClientRect().width
76
+ : 0;
77
+ if (usedWidth + itemWidth + (remainingItems > 0 ? moreButtonWidth : 0) > availableWidth) {
78
+ maxFit = i;
79
+ break;
80
+ }
81
+ usedWidth += itemWidth;
82
+ }
83
+ // If maxVisible is set, limit the number of visible items
84
+ if (typeof maxVisible === 'number') {
85
+ maxFit = Math.min(maxFit, maxVisible);
86
+ }
87
+ const newOverflow = [];
88
+ itemRefs.current.forEach((item, idx) => {
89
+ if (!item) {
90
+ return;
91
+ }
92
+ else if (idx >= maxFit) {
93
+ item.style.display = 'none';
94
+ if (children[idx]) {
95
+ newOverflow.push(children[idx]);
96
+ }
97
+ }
98
+ else {
99
+ item.style.display = 'inline-block';
100
+ }
101
+ });
102
+ setOverflowItems(newOverflow);
103
+ // Close menu if nothing is overflowing
104
+ if (newOverflow.length === 0) {
105
+ setAnchorEl(null);
106
+ }
107
+ }, [children, maxVisible]);
108
+ // Set up resize observer
109
+ useEffect(() => {
110
+ const resizeObserver = new ResizeObserver(() => {
111
+ requestAnimationFrame(handleResize);
112
+ });
113
+ if (containerRef.current) {
114
+ resizeObserver.observe(containerRef.current);
115
+ }
116
+ handleResize();
117
+ return () => {
118
+ if (containerRef.current) {
119
+ resizeObserver.unobserve(containerRef.current);
120
+ }
121
+ resizeObserver.disconnect();
122
+ };
123
+ }, [handleResize]);
124
+ const handleMenuOpen = (e) => setAnchorEl(e.currentTarget);
125
+ const handleMenuClose = () => setAnchorEl(null);
126
+ return (React.createElement(Box, { ref: containerRef, sx: styles.container },
127
+ children.map((item, index) => (React.createElement(Box, { ref: (el) => (itemRefs.current[index] = el), key: index, sx: { ...styles.horizontalItems, ...(fullWidth ? styles.fullWidthItems : {}) } }, item))),
128
+ !!overflowItems.length && (React.createElement(React.Fragment, null,
129
+ React.createElement(Box, { ref: moreButtonBoxRef, onClick: anchorEl ? handleMenuClose : handleMenuOpen }, customMoreButton ?? (React.createElement(Button, { variant: "text", sx: styles.moreButton, size: "small", endIcon: React.createElement(KeyboardArrowDownRounded, null) }, moreButtonText ?? 'More'))),
130
+ React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: handleMenuClose, disablePortal: false, disableScrollLock: true, keepMounted: true, anchorOrigin: {
131
+ vertical: 'bottom',
132
+ horizontal: 'right',
133
+ }, transformOrigin: {
134
+ vertical: 'top',
135
+ horizontal: 'right',
136
+ }, sx: { '& .MuiPaper-root': styles.menuPaper } }, overflowItems.map((item, idx) => {
137
+ if (renderOverflowMenuItem) {
138
+ return renderOverflowMenuItem(item);
139
+ }
140
+ return (React.createElement(MenuItem, { key: idx, onClick: handleMenuClose }, item));
141
+ }))))));
142
+ };
143
+ export default ResponsiveOverflow;
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,100 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import React from 'react';
5
+ import { vi } from 'vitest';
6
+ import Button from '../../core/Button/Button';
7
+ import ResponsiveOverflow from './ResponsiveOverflow';
8
+ let mockResizeCallback = null;
9
+ const originalResizeObserver = global.ResizeObserver;
10
+ const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
11
+ describe('ResponsiveOverflow', () => {
12
+ // Helper function to simulate resize events
13
+ const simulateResize = (width) => {
14
+ Element.prototype.getBoundingClientRect = vi.fn(() => ({
15
+ width,
16
+ }));
17
+ if (mockResizeCallback) {
18
+ mockResizeCallback([], {});
19
+ }
20
+ };
21
+ beforeEach(() => {
22
+ mockResizeCallback = null;
23
+ global.ResizeObserver = class ResizeObserver {
24
+ constructor(callback) {
25
+ mockResizeCallback = callback;
26
+ }
27
+ observe() { }
28
+ unobserve() { }
29
+ disconnect() { }
30
+ };
31
+ });
32
+ afterEach(() => {
33
+ global.ResizeObserver = originalResizeObserver;
34
+ Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
35
+ });
36
+ it('renders children correctly', () => {
37
+ render(React.createElement(ResponsiveOverflow, null,
38
+ React.createElement(Button, null, "Button 1"),
39
+ React.createElement(Button, null, "Button 2"),
40
+ React.createElement(Button, null, "Button 3")));
41
+ expect(screen.getByText('Button 1')).toBeInTheDocument();
42
+ expect(screen.getByText('Button 2')).toBeInTheDocument();
43
+ expect(screen.getByText('Button 3')).toBeInTheDocument();
44
+ });
45
+ it('displays custom button text when overflowing', async () => {
46
+ render(React.createElement(ResponsiveOverflow, { moreButtonText: "Show More" },
47
+ React.createElement(Button, null, "Button 1"),
48
+ React.createElement(Button, null, "Button 2"),
49
+ React.createElement(Button, null, "Button 3"),
50
+ React.createElement(Button, null, "Button 4")));
51
+ simulateResize(80); // Narrow width to force overflow
52
+ await screen.findByRole('button', { name: 'Show More' });
53
+ });
54
+ it('displays custom more button when overflowing', async () => {
55
+ const customButton = React.createElement(Button, { "data-testid": "custom-more" }, "Custom More");
56
+ render(React.createElement(ResponsiveOverflow, { customMoreButton: customButton },
57
+ React.createElement(Button, null, "Button 1"),
58
+ React.createElement(Button, null, "Button 2"),
59
+ React.createElement(Button, null, "Button 3"),
60
+ React.createElement(Button, null, "Button 4")));
61
+ simulateResize(80);
62
+ await screen.findByRole('button', { name: 'Custom More' });
63
+ });
64
+ it('shows More button when triggered by ResizeObserver', async () => {
65
+ render(React.createElement(ResponsiveOverflow, { moreButtonText: "More" },
66
+ React.createElement(Button, null, "Button 1"),
67
+ React.createElement(Button, null, "Button 2"),
68
+ React.createElement(Button, null, "Button 3"),
69
+ React.createElement(Button, null, "Button 4"),
70
+ React.createElement(Button, null, "Button 5"),
71
+ React.createElement(Button, null, "Button 6"),
72
+ React.createElement(Button, null, "Button 7"),
73
+ React.createElement(Button, null, "Button 8"),
74
+ React.createElement(Button, null, "Button 9"),
75
+ React.createElement(Button, null, "Button 10")));
76
+ simulateResize(80);
77
+ const moreBtn = await screen.findByRole('button', { name: 'More' });
78
+ await screen.findByRole('button', { name: 'Button 1' });
79
+ await userEvent.click(moreBtn);
80
+ // Button 9 should be in the overflow menu as a menuitem
81
+ await screen.findByRole('menuitem', { name: 'Button 9' });
82
+ });
83
+ it('handles dynamic resizing from wide to narrow conditions', async () => {
84
+ render(React.createElement(ResponsiveOverflow, { moreButtonText: "More" },
85
+ React.createElement(Button, null, "Button 1"),
86
+ React.createElement(Button, null, "Button 2"),
87
+ React.createElement(Button, null, "Button 3"),
88
+ React.createElement(Button, null, "Button 4"),
89
+ React.createElement(Button, null, "Button 5"),
90
+ React.createElement(Button, null, "Button 6")));
91
+ simulateResize(800);
92
+ // With wide container, all buttons should fit
93
+ // More button should not be visible
94
+ expect(screen.queryByRole('button', { name: 'More' })).not.toBeInTheDocument();
95
+ // Now simulate narrow conditions - force overflow
96
+ simulateResize(200);
97
+ // With narrow container, More button should now be visible
98
+ await screen.findByRole('button', { name: 'More' });
99
+ });
100
+ });
@@ -0,0 +1,4 @@
1
+ import ResponsiveOverflow, { ResponsiveOverflowProps } from './ResponsiveOverflow';
2
+ export { ResponsiveOverflow };
3
+ export type { ResponsiveOverflowProps };
4
+ export default ResponsiveOverflow;
@@ -0,0 +1,3 @@
1
+ import ResponsiveOverflow from './ResponsiveOverflow';
2
+ export { ResponsiveOverflow };
3
+ export default ResponsiveOverflow;
@@ -9,5 +9,6 @@ export { HistoryLog } from './HistoryLog';
9
9
  export { MenuBar } from './Menubar';
10
10
  export { MultiSelect } from './MultiSelect';
11
11
  export { RepeatableField } from './RepeatableField';
12
+ export { ResponsiveOverflow } from './ResponsiveOverflow';
12
13
  export { RichTextViewer } from './RichTextViewer';
13
14
  export { UserAvatar } from './UserAvatar';
@@ -8,5 +8,6 @@ export { HistoryLog } from './HistoryLog';
8
8
  export { MenuBar } from './Menubar';
9
9
  export { MultiSelect } from './MultiSelect';
10
10
  export { RepeatableField } from './RepeatableField';
11
+ export { ResponsiveOverflow } from './ResponsiveOverflow';
11
12
  export { RichTextViewer } from './RichTextViewer';
12
13
  export { UserAvatar } from './UserAvatar';
@@ -2,7 +2,7 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMe
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
3
  export * from './colors';
4
4
  export * from './components/core';
5
- export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
5
+ export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
6
6
  export type { FormRef } from './components/custom';
7
7
  export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
8
8
  export { Box, Container, Grid, Stack } from './components/layout';
@@ -2,7 +2,7 @@ export { ClickAwayListener, createTheme, darken, lighten, styled, Toolbar, useMe
2
2
  export { CalendarPicker, DateTimePicker, MonthPicker, PickersDay, StaticDateTimePicker, StaticTimePicker, TimePicker, YearPicker, } from '@mui/x-date-pickers';
3
3
  export * from './colors';
4
4
  export * from './components/core';
5
- export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, RichTextViewer, UserAvatar, } from './components/custom';
5
+ export { BuilderGrid, CriteriaBuilder, DataGrid, ErrorComponent, Form, FormField, getReadableQuery, HistoryLog, MenuBar, MultiSelect, RepeatableField, ResponsiveOverflow, RichTextViewer, UserAvatar, } from './components/custom';
6
6
  export { NumericFormat } from './components/custom/FormField/InputFieldComponent';
7
7
  export { Box, Container, Grid, Stack } from './components/layout';
8
8
  export * from './theme';
@@ -0,0 +1,8 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import React from 'react';
3
+ import { ResponsiveOverflowProps } from '../components/custom/ResponsiveOverflow';
4
+ declare const _default: Meta<import("@storybook/react").Args>;
5
+ export default _default;
6
+ export declare const ResizableDemo: () => React.JSX.Element;
7
+ export declare const WithLinks: Story<ResponsiveOverflowProps>;
8
+ export declare const WithCustomMoreButton: Story<ResponsiveOverflowProps>;
@@ -0,0 +1,91 @@
1
+ import { KeyboardArrowDownRounded } from '@mui/icons-material';
2
+ import { Box, Paper, Typography } from '@mui/material';
3
+ import React, { useState } from 'react';
4
+ import Button from '../components/core/Button/Button';
5
+ import Link from '../components/core/Link/Link';
6
+ import ResponsiveOverflow from '../components/custom/ResponsiveOverflow';
7
+ export default {
8
+ title: 'Custom/ResponsiveOverflow',
9
+ component: ResponsiveOverflow,
10
+ parameters: {
11
+ componentSubtitle: 'A container that handles overflow with a dropdown menu',
12
+ },
13
+ argTypes: {
14
+ moreButtonText: {
15
+ control: 'text',
16
+ description: 'Text for the overflow button',
17
+ defaultValue: 'More',
18
+ },
19
+ customMoreButton: {
20
+ control: false,
21
+ description: 'Custom button element to replace the default More button',
22
+ },
23
+ children: {
24
+ control: false,
25
+ description: 'React elements to display in the container',
26
+ },
27
+ },
28
+ };
29
+ const Template = (args) => React.createElement(ResponsiveOverflow, { ...args });
30
+ // Interactive demo with resizable container
31
+ export const ResizableDemo = () => {
32
+ const [width, setWidth] = useState(800);
33
+ const handleChange = (event) => {
34
+ setWidth(Number(event.target.value));
35
+ };
36
+ return (React.createElement(Box, null,
37
+ React.createElement(Typography, { variant: "h6", gutterBottom: true }, "Resize Container"),
38
+ React.createElement(Box, { sx: { mb: 2 } },
39
+ React.createElement(Typography, { variant: "body2", gutterBottom: true },
40
+ "Container Width: ",
41
+ width,
42
+ "px"),
43
+ React.createElement("input", { type: "range", min: 200, max: 1000, value: width, onChange: handleChange,
44
+ // eslint-disable-next-line no-inline-styles/no-inline-styles
45
+ style: { width: '100%' }, "aria-label": "Adjust container width", title: "Drag to resize container" })),
46
+ React.createElement(Paper, { elevation: 1, sx: {
47
+ width: `${width}px`,
48
+ padding: 2,
49
+ transition: 'width 0.3s ease',
50
+ backgroundColor: '#f5f5f5',
51
+ } },
52
+ React.createElement(ResponsiveOverflow, { moreButtonText: "More" },
53
+ React.createElement(Button, { variant: "contained", color: "primary" }, "Dashboard"),
54
+ React.createElement(Button, { variant: "contained" }, "Applications"),
55
+ React.createElement(Button, { variant: "contained" }, "Inspections"),
56
+ React.createElement(Button, { variant: "contained" }, "Reports"),
57
+ React.createElement(Button, { variant: "contained" }, "Licenses"),
58
+ React.createElement(Button, { variant: "contained" }, "Permits"),
59
+ React.createElement(Button, { variant: "contained" }, "Requests"),
60
+ React.createElement(Button, { variant: "contained" }, "Locations")))));
61
+ };
62
+ export const WithLinks = Template.bind({});
63
+ WithLinks.args = {
64
+ moreButtonText: 'More',
65
+ children: [
66
+ React.createElement(Link, { key: "1" }, "Applications"),
67
+ React.createElement(Link, { key: "2" }, "Inspections"),
68
+ React.createElement(Link, { key: "3" }, "Licenses"),
69
+ React.createElement(Link, { key: "4" }, "Permits"),
70
+ React.createElement(Link, { key: "5" }, "Requests"),
71
+ React.createElement(Link, { key: "6" }, "Compliance"),
72
+ ],
73
+ };
74
+ export const WithCustomMoreButton = Template.bind({});
75
+ WithCustomMoreButton.args = {
76
+ customMoreButton: (React.createElement(Button, { variant: "contained", endIcon: React.createElement(KeyboardArrowDownRounded, null) }, "More Options")),
77
+ children: [
78
+ React.createElement(Button, { variant: "contained", color: "primary", key: "1" }, "Dashboard"),
79
+ React.createElement(Button, { variant: "contained", key: "2" }, "Applications"),
80
+ React.createElement(Button, { variant: "contained", key: "3" }, "Inspections"),
81
+ React.createElement(Button, { variant: "contained", key: "4" }, "Reports"),
82
+ React.createElement(Button, { variant: "contained", key: "5" }, "Licenses"),
83
+ React.createElement(Button, { variant: "contained", key: "6" }, "Permits"),
84
+ React.createElement(Button, { variant: "contained", key: "7" }, "Requests"),
85
+ React.createElement(Button, { variant: "contained", key: "8" }, "Documents"),
86
+ React.createElement(Button, { variant: "contained", key: "9" }, "Locations"),
87
+ React.createElement(Button, { variant: "contained", key: "10" }, "Calendar"),
88
+ React.createElement(Button, { variant: "contained", key: "11" }, "Compliance"),
89
+ React.createElement(Button, { variant: "contained", key: "12" }, "Notifications"),
90
+ ],
91
+ };
@@ -29,3 +29,81 @@ export declare function useResponsive(): {
29
29
  /** Returns true if viewport width is between the specified breakpoints */
30
30
  between: (start: Breakpoint, end: Breakpoint) => boolean;
31
31
  };
32
+ export interface WidgetSizeInfo {
33
+ /** The ref to attach to the widget container */
34
+ ref: (element: HTMLElement | SVGElement | null) => void;
35
+ /** Width in pixels */
36
+ width: number;
37
+ /** Height in pixels */
38
+ height: number;
39
+ /** Widget size breakpoints */
40
+ breakpoints: {
41
+ /** Extra small: width < 800px */
42
+ isXs: boolean;
43
+ /** Small: 800px <= width < 1000px */
44
+ isSm: boolean;
45
+ /** Medium: 1000px <= width < 1200px */
46
+ isMd: boolean;
47
+ /** Large: 1200px <= width < 1400px */
48
+ isLg: boolean;
49
+ /** Extra large: width >= 1400px */
50
+ isXl: boolean;
51
+ /** Compact height: height < 300px */
52
+ isCompact: boolean;
53
+ /** Tall height: height >= 600px */
54
+ isTall: boolean;
55
+ };
56
+ /** Bounds information from react-use-measure */
57
+ bounds: {
58
+ /**
59
+ * left is usually the same as x, but can differ if width is negative.
60
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/DOMRect
61
+ */
62
+ left: number;
63
+ /**
64
+ * top is usually the same as y, but can differ if height is negative.
65
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/DOMRect
66
+ */
67
+ top: number;
68
+ width: number;
69
+ height: number;
70
+ bottom: number;
71
+ right: number;
72
+ x: number;
73
+ y: number;
74
+ };
75
+ /** Check if widget is above a breakpoint */
76
+ isAbove: (breakpoint: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => boolean;
77
+ /** Check if widget is below a breakpoint */
78
+ isBelow: (breakpoint: 'xs' | 'sm' | 'md' | 'lg' | 'xl') => boolean;
79
+ }
80
+ /**
81
+ * Custom hook that measures widget dimensions and provides responsive breakpoints
82
+ * based on the actual widget size rather than viewport size.
83
+ *
84
+ * @param options Configuration options for the measurement
85
+ * @returns WidgetSizeInfo object with ref, dimensions, and breakpoints
86
+ *
87
+ * @example
88
+ * ```tsx
89
+ * const { ref, width, breakpoints } = useWidgetSize();
90
+ *
91
+ * return (
92
+ * <div ref={ref}>
93
+ * {breakpoints.isXs ? 'Compact view' : 'Full view'}
94
+ * <p>Widget width: {width}px</p>
95
+ * </div>
96
+ * );
97
+ * ```
98
+ */
99
+ export declare const useWidgetSize: (options?: {
100
+ /** Debounce measurement updates (ms) */
101
+ debounce?: number;
102
+ /** Throttle measurement updates (ms) */
103
+ throttle?: number;
104
+ /** Scroll containers to observe */
105
+ scroll?: boolean;
106
+ /** Resize containers to observe */
107
+ resize?: boolean;
108
+ }) => WidgetSizeInfo;
109
+ export default useWidgetSize;
@@ -1,5 +1,6 @@
1
1
  import { useTheme } from '@mui/material';
2
2
  import useMediaQuery from '@mui/material/useMediaQuery';
3
+ import useMeasure from 'react-use-measure';
3
4
  /**
4
5
  * Custom hook for responsive design breakpoints using size terminology.
5
6
  * Breakpoints based on MUI default theme:
@@ -33,3 +34,67 @@ export function useResponsive() {
33
34
  between: (start, end) => useMediaQuery(theme.breakpoints.between(start, end)),
34
35
  };
35
36
  }
37
+ /**
38
+ * Custom hook that measures widget dimensions and provides responsive breakpoints
39
+ * based on the actual widget size rather than viewport size.
40
+ *
41
+ * @param options Configuration options for the measurement
42
+ * @returns WidgetSizeInfo object with ref, dimensions, and breakpoints
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * const { ref, width, breakpoints } = useWidgetSize();
47
+ *
48
+ * return (
49
+ * <div ref={ref}>
50
+ * {breakpoints.isXs ? 'Compact view' : 'Full view'}
51
+ * <p>Widget width: {width}px</p>
52
+ * </div>
53
+ * );
54
+ * ```
55
+ */
56
+ export const useWidgetSize = (options) => {
57
+ const [ref, bounds] = useMeasure({
58
+ debounce: options?.debounce ?? 16,
59
+ scroll: options?.scroll ?? true,
60
+ offsetSize: true,
61
+ ...options,
62
+ });
63
+ const width = bounds.width || 0;
64
+ const height = bounds.height || 0;
65
+ // Widget-specific breakpoints based on container width
66
+ const breakpoints = {
67
+ isXs: width < 800,
68
+ isSm: width >= 800 && width < 1000,
69
+ isMd: width >= 1000 && width < 1200,
70
+ isLg: width >= 1200 && width < 1400,
71
+ isXl: width >= 1400,
72
+ isCompact: height < 300,
73
+ isTall: height >= 600,
74
+ };
75
+ // Breakpoint thresholds
76
+ const breakpointThresholds = {
77
+ xs: 800,
78
+ sm: 1000,
79
+ md: 1200,
80
+ lg: 1400,
81
+ xl: Infinity,
82
+ };
83
+ // Utility functions for responsive checks
84
+ const isAbove = (breakpoint) => {
85
+ return width >= breakpointThresholds[breakpoint];
86
+ };
87
+ const isBelow = (breakpoint) => {
88
+ return width < breakpointThresholds[breakpoint];
89
+ };
90
+ return {
91
+ ref,
92
+ width,
93
+ height,
94
+ breakpoints,
95
+ bounds,
96
+ isAbove,
97
+ isBelow,
98
+ };
99
+ };
100
+ export default useWidgetSize;
@@ -0,0 +1 @@
1
+ export {};