@mezzanine-ui/react 1.0.0-rc.1 → 1.0.0-rc.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/Cascader/Cascader.d.ts +3 -0
- package/Cascader/Cascader.js +241 -0
- package/Cascader/CascaderPanel.d.ts +29 -0
- package/Cascader/CascaderPanel.js +29 -0
- package/Cascader/index.d.ts +5 -0
- package/Cascader/index.js +2 -0
- package/Cascader/typings.d.ts +92 -0
- package/Layout/Layout.d.ts +21 -5
- package/Layout/Layout.js +23 -19
- package/Layout/LayoutContext.d.ts +6 -6
- package/Layout/LayoutContext.js +2 -2
- package/Layout/LayoutHost.d.ts +0 -4
- package/Layout/LayoutHost.js +16 -13
- package/Layout/LayoutLeftPanel.d.ts +19 -0
- package/Layout/LayoutLeftPanel.js +86 -0
- package/Layout/LayoutMain.d.ts +10 -1
- package/Layout/LayoutMain.js +12 -3
- package/Layout/LayoutRightPanel.d.ts +19 -0
- package/Layout/{LayoutSidePanel.js → LayoutRightPanel.js} +32 -36
- package/Layout/index.d.ts +2 -2
- package/Layout/index.js +2 -1
- package/Modal/MediaPreviewModal.d.ts +4 -0
- package/Modal/MediaPreviewModal.js +2 -2
- package/Modal/Modal.d.ts +4 -0
- package/Modal/Modal.js +2 -2
- package/Modal/useModalContainer.js +0 -1
- package/index.d.ts +4 -2
- package/index.js +4 -1
- package/package.json +4 -4
- package/Layout/LayoutSidePanel.d.ts +0 -14
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useContext, useState, useRef, useCallback, useMemo, createElement } from 'react';
|
|
4
|
+
import { cascaderClasses } from '@mezzanine-ui/core/cascader';
|
|
5
|
+
import { offset } from '@floating-ui/react-dom';
|
|
6
|
+
import { MOTION_EASING, MOTION_DURATION } from '@mezzanine-ui/system/motion';
|
|
7
|
+
import { TransitionGroup } from 'react-transition-group';
|
|
8
|
+
import { useControlValueState } from '../Form/useControlValueState.js';
|
|
9
|
+
import { useDocumentEvents } from '../hooks/useDocumentEvents.js';
|
|
10
|
+
import SelectTrigger from '../Select/SelectTrigger.js';
|
|
11
|
+
import Translate from '../Transition/Translate.js';
|
|
12
|
+
import CascaderPanel from './CascaderPanel.js';
|
|
13
|
+
import { FormControlContext } from '../Form/FormControlContext.js';
|
|
14
|
+
import Popper from '../Popper/Popper.js';
|
|
15
|
+
import cx from 'clsx';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Walks the options tree using ids from `value` and returns a new activePath
|
|
19
|
+
* whose items carry proper `children` references from the tree.
|
|
20
|
+
* Items in `value` may omit `children`, so we cannot rely on them directly
|
|
21
|
+
* for panel expansion.
|
|
22
|
+
*/
|
|
23
|
+
function resolveActivePath(options, value) {
|
|
24
|
+
const result = [];
|
|
25
|
+
let currentOptions = options;
|
|
26
|
+
for (const selectedItem of value) {
|
|
27
|
+
const found = currentOptions.find((o) => o.id === selectedItem.id);
|
|
28
|
+
if (!found)
|
|
29
|
+
break;
|
|
30
|
+
result.push(found);
|
|
31
|
+
if (found.children && found.children.length > 0) {
|
|
32
|
+
currentOptions = found.children;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
const Cascader = forwardRef(function Cascader(props, ref) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
const { disabled: disabledFromFormControl, fullWidth: fullWidthFromFormControl, required: requiredFromFormControl, severity, } = useContext(FormControlContext) || {};
|
|
43
|
+
const { className, clearable = false, defaultValue, disabled = disabledFromFormControl || false, dropdownZIndex, error = severity === 'error' || false, fullWidth = fullWidthFromFormControl || false, globalPortal = true, menuMaxHeight, onBlur, onChange: onChangeProp, onFocus, options, placeholder = '', readOnly = false, required = requiredFromFormControl || false, size, value: valueProp, } = props;
|
|
44
|
+
const [open, setOpen] = useState(false);
|
|
45
|
+
const [activePath, setActivePath] = useState([]);
|
|
46
|
+
const [keyboardFocusedIndex, setKeyboardFocusedIndex] = useState(-1);
|
|
47
|
+
const [value, setValue] = useControlValueState({
|
|
48
|
+
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : [],
|
|
49
|
+
value: valueProp,
|
|
50
|
+
});
|
|
51
|
+
const anchorRef = useRef(null);
|
|
52
|
+
const dropdownRef = useRef(null);
|
|
53
|
+
const handleOpen = useCallback(() => {
|
|
54
|
+
if (readOnly || disabled)
|
|
55
|
+
return;
|
|
56
|
+
setActivePath(resolveActivePath(options, value));
|
|
57
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus();
|
|
58
|
+
setOpen(true);
|
|
59
|
+
}, [disabled, onFocus, options, readOnly, value]);
|
|
60
|
+
const handleClose = useCallback(() => {
|
|
61
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur();
|
|
62
|
+
setOpen(false);
|
|
63
|
+
setKeyboardFocusedIndex(-1);
|
|
64
|
+
}, [onBlur]);
|
|
65
|
+
const handleClear = useCallback((event) => {
|
|
66
|
+
event.stopPropagation();
|
|
67
|
+
setValue([]);
|
|
68
|
+
onChangeProp === null || onChangeProp === void 0 ? void 0 : onChangeProp([]);
|
|
69
|
+
if (open) {
|
|
70
|
+
handleClose();
|
|
71
|
+
}
|
|
72
|
+
}, [handleClose, onChangeProp, open, setValue]);
|
|
73
|
+
const handleItemSelect = useCallback((panelIndex, option, isLeaf) => {
|
|
74
|
+
const newActivePath = [...activePath.slice(0, panelIndex), option];
|
|
75
|
+
setActivePath(newActivePath);
|
|
76
|
+
if (isLeaf) {
|
|
77
|
+
setValue(newActivePath);
|
|
78
|
+
onChangeProp === null || onChangeProp === void 0 ? void 0 : onChangeProp(newActivePath);
|
|
79
|
+
handleClose();
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
setKeyboardFocusedIndex(-1);
|
|
83
|
+
}
|
|
84
|
+
}, [activePath, handleClose, onChangeProp, setValue]);
|
|
85
|
+
const panels = useMemo(() => {
|
|
86
|
+
const result = [options];
|
|
87
|
+
for (const activeOption of activePath) {
|
|
88
|
+
if (activeOption.children && activeOption.children.length > 0) {
|
|
89
|
+
result.push(activeOption.children);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}, [activePath, options]);
|
|
97
|
+
useDocumentEvents(() => {
|
|
98
|
+
if (!open)
|
|
99
|
+
return;
|
|
100
|
+
const handleClickAway = (event) => {
|
|
101
|
+
const target = event.target;
|
|
102
|
+
if (!target)
|
|
103
|
+
return;
|
|
104
|
+
const anchor = anchorRef.current;
|
|
105
|
+
const dropdown = dropdownRef.current;
|
|
106
|
+
if ((anchor === null || anchor === void 0 ? void 0 : anchor.contains(target)) || (dropdown === null || dropdown === void 0 ? void 0 : dropdown.contains(target)))
|
|
107
|
+
return;
|
|
108
|
+
handleClose();
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
click: handleClickAway,
|
|
112
|
+
touchend: handleClickAway,
|
|
113
|
+
};
|
|
114
|
+
}, [handleClose, open]);
|
|
115
|
+
useDocumentEvents(() => {
|
|
116
|
+
if (!open)
|
|
117
|
+
return;
|
|
118
|
+
const currentPanelOptions = panels[panels.length - 1];
|
|
119
|
+
return {
|
|
120
|
+
keydown(event) {
|
|
121
|
+
switch (event.key) {
|
|
122
|
+
case 'Escape': {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
handleClose();
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'ArrowDown': {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
let next = keyboardFocusedIndex + 1;
|
|
130
|
+
while (next < currentPanelOptions.length &&
|
|
131
|
+
currentPanelOptions[next].disabled) {
|
|
132
|
+
next++;
|
|
133
|
+
}
|
|
134
|
+
if (next < currentPanelOptions.length) {
|
|
135
|
+
setKeyboardFocusedIndex(next);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'ArrowUp': {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
let prev = keyboardFocusedIndex === -1
|
|
142
|
+
? currentPanelOptions.length - 1
|
|
143
|
+
: keyboardFocusedIndex - 1;
|
|
144
|
+
while (prev >= 0 && currentPanelOptions[prev].disabled) {
|
|
145
|
+
prev--;
|
|
146
|
+
}
|
|
147
|
+
if (prev >= 0) {
|
|
148
|
+
setKeyboardFocusedIndex(prev);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'ArrowRight':
|
|
153
|
+
case 'Enter':
|
|
154
|
+
case ' ': {
|
|
155
|
+
if (keyboardFocusedIndex === -1)
|
|
156
|
+
return;
|
|
157
|
+
const focusedOption = currentPanelOptions[keyboardFocusedIndex];
|
|
158
|
+
if (!focusedOption || focusedOption.disabled)
|
|
159
|
+
return;
|
|
160
|
+
const isLeaf = !focusedOption.children || focusedOption.children.length === 0;
|
|
161
|
+
if (event.key === 'ArrowRight' && isLeaf)
|
|
162
|
+
return;
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
handleItemSelect(panels.length - 1, focusedOption, isLeaf);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case 'ArrowLeft': {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
if (activePath.length === 0)
|
|
170
|
+
return;
|
|
171
|
+
const removedItem = activePath[activePath.length - 1];
|
|
172
|
+
const parentPanel = panels[activePath.length - 1];
|
|
173
|
+
const idx = parentPanel.findIndex((o) => o.id === removedItem.id);
|
|
174
|
+
setActivePath(activePath.slice(0, -1));
|
|
175
|
+
setKeyboardFocusedIndex(idx >= 0 ? idx : -1);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}, [
|
|
182
|
+
activePath,
|
|
183
|
+
handleClose,
|
|
184
|
+
handleItemSelect,
|
|
185
|
+
keyboardFocusedIndex,
|
|
186
|
+
open,
|
|
187
|
+
panels,
|
|
188
|
+
]);
|
|
189
|
+
const displayPath = open ? activePath : value;
|
|
190
|
+
const displayString = displayPath.map((o) => o.name).join(' / ');
|
|
191
|
+
const triggerValue = useMemo(() => {
|
|
192
|
+
if (displayPath.length === 0)
|
|
193
|
+
return undefined;
|
|
194
|
+
return { id: 'cascader-value', name: displayString };
|
|
195
|
+
}, [displayPath.length, displayString]);
|
|
196
|
+
const isPartial = open &&
|
|
197
|
+
activePath.length > 0 &&
|
|
198
|
+
!!((_b = (_a = activePath[activePath.length - 1]) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b.length);
|
|
199
|
+
const zIndexMiddleware = useMemo(() => ({
|
|
200
|
+
name: 'zIndex',
|
|
201
|
+
fn: ({ elements }) => {
|
|
202
|
+
const val = dropdownZIndex !== null && dropdownZIndex !== void 0 ? dropdownZIndex : 1;
|
|
203
|
+
const num = typeof val === 'number' ? val : parseInt(val, 10) || val;
|
|
204
|
+
Object.assign(elements.floating.style, { zIndex: num });
|
|
205
|
+
return {};
|
|
206
|
+
},
|
|
207
|
+
}), [dropdownZIndex]);
|
|
208
|
+
const offsetMiddleware = useMemo(() => offset({ mainAxis: 4 }), []);
|
|
209
|
+
const translateProps = useMemo(() => ({
|
|
210
|
+
duration: {
|
|
211
|
+
enter: MOTION_DURATION.moderate,
|
|
212
|
+
exit: MOTION_DURATION.moderate,
|
|
213
|
+
},
|
|
214
|
+
easing: {
|
|
215
|
+
enter: MOTION_EASING.standard,
|
|
216
|
+
exit: MOTION_EASING.standard,
|
|
217
|
+
},
|
|
218
|
+
}), []);
|
|
219
|
+
return (jsxs("div", { ref: ref, className: cx(cascaderClasses.host, fullWidth && cascaderClasses.hostFullWidth), children: [jsx(Popper, { anchor: anchorRef, open: open, disablePortal: !globalPortal, options: {
|
|
220
|
+
placement: 'bottom-start',
|
|
221
|
+
middleware: [offsetMiddleware, zIndexMiddleware],
|
|
222
|
+
}, children: jsx(TransitionGroup, { component: null, children: open && (createElement(Translate, { ...translateProps, from: "bottom", key: "cascader-dropdown", in: true },
|
|
223
|
+
jsx("div", { children: jsx("div", { ref: dropdownRef, className: cascaderClasses.dropdownPanels, children: panels.map((panelOptions, panelIndex) => {
|
|
224
|
+
var _a, _b, _c;
|
|
225
|
+
return (jsx(CascaderPanel, { activeId: (_a = activePath[panelIndex]) === null || _a === void 0 ? void 0 : _a.id, focusedId: panelIndex === panels.length - 1 &&
|
|
226
|
+
keyboardFocusedIndex >= 0
|
|
227
|
+
? (_b = panels[panelIndex][keyboardFocusedIndex]) === null || _b === void 0 ? void 0 : _b.id
|
|
228
|
+
: undefined, maxHeight: menuMaxHeight, onSelect: (option, isLeaf) => handleItemSelect(panelIndex, option, isLeaf), options: panelOptions, selectedId: value.length > 0 && panelIndex === value.length - 1
|
|
229
|
+
? (_c = value[panelIndex]) === null || _c === void 0 ? void 0 : _c.id
|
|
230
|
+
: undefined }, panelIndex));
|
|
231
|
+
}) }) }))) }) }), jsx(SelectTrigger, { ref: anchorRef, active: open, className: cx(className, isPartial && cascaderClasses.triggerPartial), disabled: disabled, fullWidth: fullWidth, inputProps: {
|
|
232
|
+
onKeyDown: (e) => {
|
|
233
|
+
if (!open && (e.key === ' ' || e.key === 'Enter')) {
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
handleOpen();
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
}, isForceClearable: clearable && !disabled && !readOnly && value.length > 0, mode: "single", onClick: open ? handleClose : handleOpen, onClear: handleClear, placeholder: placeholder, readOnly: readOnly, required: required, size: size, type: error ? 'error' : 'default', value: triggerValue })] }));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
export { Cascader as default };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CascaderOption } from './typings';
|
|
2
|
+
export interface CascaderPanelProps {
|
|
3
|
+
/**
|
|
4
|
+
* The id of the currently active (navigating) option.
|
|
5
|
+
*/
|
|
6
|
+
activeId?: string;
|
|
7
|
+
/**
|
|
8
|
+
* The id of the keyboard-focused option in this panel.
|
|
9
|
+
*/
|
|
10
|
+
focusedId?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The max height for the panel.
|
|
13
|
+
*/
|
|
14
|
+
maxHeight?: number | string;
|
|
15
|
+
/**
|
|
16
|
+
* Called when an option is clicked.
|
|
17
|
+
* `isLeaf` is true if the option has no children.
|
|
18
|
+
*/
|
|
19
|
+
onSelect: (option: CascaderOption, isLeaf: boolean) => void;
|
|
20
|
+
/**
|
|
21
|
+
* The options to render in this panel.
|
|
22
|
+
*/
|
|
23
|
+
options: CascaderOption[];
|
|
24
|
+
/**
|
|
25
|
+
* The id of the confirmed selected option at this level.
|
|
26
|
+
*/
|
|
27
|
+
selectedId?: string;
|
|
28
|
+
}
|
|
29
|
+
export default function CascaderPanel({ activeId, focusedId, maxHeight, onSelect, options, selectedId, }: CascaderPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { cascaderClasses } from '@mezzanine-ui/core/cascader';
|
|
4
|
+
import { CheckedIcon, CaretRightIcon } from '@mezzanine-ui/icons';
|
|
5
|
+
import Icon from '../Icon/Icon.js';
|
|
6
|
+
import cx from 'clsx';
|
|
7
|
+
|
|
8
|
+
function CascaderPanel({ activeId, focusedId, maxHeight, onSelect, options, selectedId, }) {
|
|
9
|
+
const toItemId = (optionId) => `mzn-cascader-option-${optionId}`;
|
|
10
|
+
return (jsx("div", { className: cascaderClasses.panel, style: maxHeight ? { maxHeight } : undefined, children: jsx("ul", { "aria-activedescendant": focusedId ? toItemId(focusedId) : undefined, "aria-label": "Options", role: "listbox", style: { listStyle: 'none', margin: 0, padding: 0 }, tabIndex: -1, children: options.map((option) => {
|
|
11
|
+
const isLeaf = !option.children || option.children.length === 0;
|
|
12
|
+
const isActive = option.id === activeId;
|
|
13
|
+
const isSelected = option.id === selectedId;
|
|
14
|
+
return (jsxs("li", { "aria-disabled": option.disabled || undefined, "aria-expanded": !isLeaf ? isActive : undefined, "aria-selected": isSelected, className: cx(cascaderClasses.item, isActive && cascaderClasses.itemActive, option.disabled && cascaderClasses.itemDisabled, option.id === focusedId && cascaderClasses.itemFocused, isSelected && cascaderClasses.itemSelected), id: toItemId(option.id), onClick: () => {
|
|
15
|
+
if (!option.disabled) {
|
|
16
|
+
onSelect(option, isLeaf);
|
|
17
|
+
}
|
|
18
|
+
}, onKeyDown: (e) => {
|
|
19
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
if (!option.disabled) {
|
|
22
|
+
onSelect(option, isLeaf);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}, role: "option", children: [jsx("span", { className: cascaderClasses.itemLabel, children: option.name }), jsx("span", { className: cascaderClasses.itemAppend, children: isLeaf ? (isSelected && jsx(Icon, { icon: CheckedIcon })) : (jsx(Icon, { icon: CaretRightIcon })) })] }, option.id));
|
|
26
|
+
}) }) }));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { CascaderPanel as default };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default } from './Cascader';
|
|
2
|
+
export type { CascaderProps } from './typings';
|
|
3
|
+
export type { CascaderOption, CascaderSize } from './typings';
|
|
4
|
+
export { default as CascaderPanel } from './CascaderPanel';
|
|
5
|
+
export type { CascaderPanelProps } from './CascaderPanel';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CascaderSize } from '@mezzanine-ui/core/cascader';
|
|
2
|
+
export type { CascaderSize };
|
|
3
|
+
export interface CascaderOption {
|
|
4
|
+
children?: CascaderOption[];
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CascaderBaseProps {
|
|
10
|
+
/**
|
|
11
|
+
* Additional class name.
|
|
12
|
+
*/
|
|
13
|
+
className?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to show the clear button when a value is selected.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
clearable?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the cascader is disabled.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to show in error state.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
error?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the cascader takes full width.
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
fullWidth?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The z-index of the dropdown.
|
|
36
|
+
*/
|
|
37
|
+
dropdownZIndex?: number | string;
|
|
38
|
+
/**
|
|
39
|
+
* Whether to enable portal for the dropdown.
|
|
40
|
+
* @default true
|
|
41
|
+
*/
|
|
42
|
+
globalPortal?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The max height of each cascader panel column.
|
|
45
|
+
*/
|
|
46
|
+
menuMaxHeight?: number | string;
|
|
47
|
+
/**
|
|
48
|
+
* Callback fired when the dropdown is closed.
|
|
49
|
+
*/
|
|
50
|
+
onBlur?: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Callback fired when the dropdown is opened.
|
|
53
|
+
*/
|
|
54
|
+
onFocus?: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* The tree options for the cascader.
|
|
57
|
+
*/
|
|
58
|
+
options: CascaderOption[];
|
|
59
|
+
/**
|
|
60
|
+
* Placeholder text for the trigger input.
|
|
61
|
+
*/
|
|
62
|
+
placeholder?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the cascader is read-only.
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
readOnly?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the cascader is required.
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
required?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* The size of the trigger input.
|
|
75
|
+
* @default 'main'
|
|
76
|
+
*/
|
|
77
|
+
size?: CascaderSize;
|
|
78
|
+
}
|
|
79
|
+
export interface CascaderProps extends CascaderBaseProps {
|
|
80
|
+
/**
|
|
81
|
+
* Uncontrolled default value (array from root to leaf).
|
|
82
|
+
*/
|
|
83
|
+
defaultValue?: CascaderOption[];
|
|
84
|
+
/**
|
|
85
|
+
* Callback fired when the final leaf option is selected.
|
|
86
|
+
*/
|
|
87
|
+
onChange?: (value: CascaderOption[]) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Controlled value (array from root to leaf).
|
|
90
|
+
*/
|
|
91
|
+
value?: CascaderOption[];
|
|
92
|
+
}
|
package/Layout/Layout.d.ts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
2
|
+
import { LayoutLeftPanel, LayoutLeftPanelProps } from './LayoutLeftPanel';
|
|
2
3
|
import { LayoutMain, LayoutMainProps } from './LayoutMain';
|
|
3
|
-
import {
|
|
4
|
+
import { LayoutRightPanel, LayoutRightPanelProps } from './LayoutRightPanel';
|
|
5
|
+
export { LayoutLeftPanel };
|
|
6
|
+
export type { LayoutLeftPanelProps };
|
|
4
7
|
export { LayoutMain };
|
|
5
8
|
export type { LayoutMainProps };
|
|
6
|
-
export {
|
|
7
|
-
export type {
|
|
9
|
+
export { LayoutRightPanel };
|
|
10
|
+
export type { LayoutRightPanelProps };
|
|
8
11
|
export interface LayoutProps extends NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
9
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Slot children. Place `<Navigation>`, `<Layout.LeftPanel>`, `<Layout.Main>`,
|
|
14
|
+
* and `<Layout.RightPanel>` as direct children in any order.
|
|
15
|
+
* They will be re-ordered into the correct DOM sequence automatically.
|
|
16
|
+
*/
|
|
10
17
|
children?: React.ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Additional class name applied to the content wrapper element.
|
|
20
|
+
*/
|
|
21
|
+
contentWrapperClassName?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Additional class name applied to the navigation wrapper element. Only has effect if a `<Navigation>` component is provided as a child.
|
|
24
|
+
*/
|
|
25
|
+
navigationClassName?: string;
|
|
11
26
|
}
|
|
12
27
|
declare const LayoutWithSubComponents: import("react").ForwardRefExoticComponent<LayoutProps & import("react").RefAttributes<HTMLDivElement>> & {
|
|
28
|
+
LeftPanel: typeof LayoutLeftPanel;
|
|
13
29
|
Main: typeof LayoutMain;
|
|
14
|
-
|
|
30
|
+
RightPanel: typeof LayoutRightPanel;
|
|
15
31
|
};
|
|
16
32
|
export default LayoutWithSubComponents;
|
package/Layout/Layout.js
CHANGED
|
@@ -2,36 +2,40 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { forwardRef, Children, isValidElement } from 'react';
|
|
3
3
|
import { layoutClasses } from '@mezzanine-ui/core/layout';
|
|
4
4
|
import { LayoutHost } from './LayoutHost.js';
|
|
5
|
+
import { LayoutLeftPanel } from './LayoutLeftPanel.js';
|
|
5
6
|
import { LayoutMain } from './LayoutMain.js';
|
|
6
|
-
import {
|
|
7
|
+
import { LayoutRightPanel } from './LayoutRightPanel.js';
|
|
8
|
+
import Navigation from '../Navigation/Navigation.js';
|
|
9
|
+
import cx from 'clsx';
|
|
7
10
|
|
|
8
11
|
const Layout = forwardRef(function Layout(props, ref) {
|
|
9
|
-
const { children, ...rest } = props;
|
|
10
|
-
let
|
|
11
|
-
let
|
|
12
|
-
let
|
|
13
|
-
let
|
|
12
|
+
const { children, contentWrapperClassName, navigationClassName, ...rest } = props;
|
|
13
|
+
let navigationNode = null;
|
|
14
|
+
let leftPanelNode = null;
|
|
15
|
+
let mainNode = null;
|
|
16
|
+
let rightPanelNode = null;
|
|
14
17
|
Children.forEach(children, (child) => {
|
|
15
|
-
var _a, _b;
|
|
16
18
|
if (!isValidElement(child))
|
|
17
19
|
return;
|
|
18
|
-
if (child.type ===
|
|
19
|
-
|
|
20
|
-
.children;
|
|
20
|
+
if (child.type === Navigation) {
|
|
21
|
+
navigationNode = child;
|
|
21
22
|
}
|
|
22
|
-
else if (child.type ===
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
else if (child.type === LayoutLeftPanel) {
|
|
24
|
+
leftPanelNode = child;
|
|
25
|
+
}
|
|
26
|
+
else if (child.type === LayoutMain) {
|
|
27
|
+
mainNode = child;
|
|
28
|
+
}
|
|
29
|
+
else if (child.type === LayoutRightPanel) {
|
|
30
|
+
rightPanelNode = child;
|
|
28
31
|
}
|
|
29
32
|
});
|
|
30
|
-
return (jsxs(LayoutHost, { ...rest,
|
|
33
|
+
return (jsxs(LayoutHost, { ...rest, ref: ref, children: [navigationNode && (jsx("div", { className: cx(layoutClasses.navigation, navigationClassName), children: navigationNode })), jsxs("div", { className: cx(layoutClasses.contentWrapper, contentWrapperClassName), children: [leftPanelNode, mainNode, rightPanelNode] })] }));
|
|
31
34
|
});
|
|
32
35
|
const LayoutWithSubComponents = Object.assign(Layout, {
|
|
36
|
+
LeftPanel: LayoutLeftPanel,
|
|
33
37
|
Main: LayoutMain,
|
|
34
|
-
|
|
38
|
+
RightPanel: LayoutRightPanel,
|
|
35
39
|
});
|
|
36
40
|
|
|
37
|
-
export { LayoutMain,
|
|
41
|
+
export { LayoutLeftPanel, LayoutMain, LayoutRightPanel, LayoutWithSubComponents as default };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
export interface LayoutContextValue {
|
|
3
|
+
hostRef: RefObject<HTMLDivElement | null>;
|
|
4
|
+
mainRef: RefObject<HTMLDivElement | null>;
|
|
5
|
+
registerMain: (el: HTMLDivElement | null) => void;
|
|
6
6
|
}
|
|
7
|
-
export declare const
|
|
7
|
+
export declare const LayoutContext: import("react").Context<LayoutContextValue | null>;
|
package/Layout/LayoutContext.js
CHANGED
package/Layout/LayoutHost.d.ts
CHANGED
|
@@ -2,9 +2,5 @@ import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
|
2
2
|
export interface LayoutHostProps extends NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
3
3
|
/** The content rendered inside the layout host. */
|
|
4
4
|
children?: React.ReactNode;
|
|
5
|
-
/** SSR hint: initial open state read from LayoutSidePanel.props.open */
|
|
6
|
-
initialOpen?: boolean;
|
|
7
|
-
/** SSR hint: initial width read from LayoutSidePanel.props.defaultSidePanelWidth */
|
|
8
|
-
initialSidePanelWidth?: number;
|
|
9
5
|
}
|
|
10
6
|
export declare const LayoutHost: import("react").ForwardRefExoticComponent<LayoutHostProps & import("react").RefAttributes<HTMLDivElement>>;
|
package/Layout/LayoutHost.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef,
|
|
3
|
+
import { forwardRef, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { layoutClasses } from '@mezzanine-ui/core/layout';
|
|
5
|
-
import {
|
|
5
|
+
import { LayoutContext } from './LayoutContext.js';
|
|
6
6
|
import cx from 'clsx';
|
|
7
7
|
|
|
8
|
-
const MIN_PANEL_WIDTH = 240;
|
|
9
8
|
const LayoutHost = forwardRef(function LayoutHost(props, ref) {
|
|
10
|
-
const { children, className,
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
setSidePanelWidth(width);
|
|
9
|
+
const { children, className, ...rest } = props;
|
|
10
|
+
const hostRef = useRef(null);
|
|
11
|
+
const mainRef = useRef(null);
|
|
12
|
+
const registerMain = useCallback((el) => {
|
|
13
|
+
mainRef.current = el;
|
|
16
14
|
}, []);
|
|
17
|
-
const contextValue = useMemo(() => ({
|
|
18
|
-
return (jsx(
|
|
19
|
-
|
|
20
|
-
'
|
|
15
|
+
const contextValue = useMemo(() => ({ hostRef, mainRef, registerMain }), [registerMain]);
|
|
16
|
+
return (jsx(LayoutContext.Provider, { value: contextValue, children: jsx("div", { ...rest, className: cx(layoutClasses.host, className), ref: (node) => {
|
|
17
|
+
hostRef.current = node;
|
|
18
|
+
if (typeof ref === 'function') {
|
|
19
|
+
ref(node);
|
|
20
|
+
}
|
|
21
|
+
else if (ref) {
|
|
22
|
+
ref.current = node;
|
|
23
|
+
}
|
|
21
24
|
}, children: children }) }));
|
|
22
25
|
});
|
|
23
26
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ScrollbarProps } from '../Scrollbar';
|
|
2
|
+
export interface LayoutLeftPanelProps {
|
|
3
|
+
/** The content rendered inside the left panel. */
|
|
4
|
+
children?: React.ReactNode;
|
|
5
|
+
/** Additional class name applied to the panel element. */
|
|
6
|
+
className?: string;
|
|
7
|
+
/** Initial width (in px) of the panel. Clamped to a minimum of 240px. */
|
|
8
|
+
defaultWidth?: number;
|
|
9
|
+
/** Callback fired when the panel width changes during resize. */
|
|
10
|
+
onWidthChange?: (width: number) => void;
|
|
11
|
+
/** Controls whether the panel and its divider are visible. */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
/** Props passed to the internal `Scrollbar` component. */
|
|
14
|
+
scrollbarProps?: Omit<ScrollbarProps, 'children'>;
|
|
15
|
+
}
|
|
16
|
+
export declare function LayoutLeftPanel({ children, className, defaultWidth, onWidthChange, open, scrollbarProps, }: LayoutLeftPanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export declare namespace LayoutLeftPanel {
|
|
18
|
+
var displayName: string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useContext, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
|
+
import { layoutClasses } from '@mezzanine-ui/core/layout';
|
|
5
|
+
import { useDocumentEvents } from '../hooks/useDocumentEvents.js';
|
|
6
|
+
import { LayoutContext } from './LayoutContext.js';
|
|
7
|
+
import Scrollbar from '../Scrollbar/Scrollbar.js';
|
|
8
|
+
import cx from 'clsx';
|
|
9
|
+
|
|
10
|
+
const MIN_PANEL_WIDTH = 240;
|
|
11
|
+
const CONTENT_WRAPPER_MIN_WIDTH = 480;
|
|
12
|
+
const ARROW_KEY_STEP = 10;
|
|
13
|
+
function LayoutLeftPanel({ children, className, defaultWidth = 320, onWidthChange, open = false, scrollbarProps = {}, }) {
|
|
14
|
+
const context = useContext(LayoutContext);
|
|
15
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
16
|
+
const [width, setWidth] = useState(() => Math.max(MIN_PANEL_WIDTH, defaultWidth));
|
|
17
|
+
const dragStartRef = useRef(null);
|
|
18
|
+
const rafIdRef = useRef(null);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
return () => {
|
|
21
|
+
if (rafIdRef.current !== null) {
|
|
22
|
+
window.cancelAnimationFrame(rafIdRef.current);
|
|
23
|
+
rafIdRef.current = null;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}, []);
|
|
27
|
+
const handleDividerMouseDown = useCallback((e) => {
|
|
28
|
+
var _a;
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
const mainWidth = ((_a = context === null || context === void 0 ? void 0 : context.mainRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || window.innerWidth;
|
|
31
|
+
const maxWidth = width + Math.max(0, mainWidth - CONTENT_WRAPPER_MIN_WIDTH);
|
|
32
|
+
setIsDragging(true);
|
|
33
|
+
dragStartRef.current = { maxWidth, width, x: e.clientX };
|
|
34
|
+
}, [context, width]);
|
|
35
|
+
const handleDividerKeyDown = useCallback((e) => {
|
|
36
|
+
var _a;
|
|
37
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')
|
|
38
|
+
return;
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
const mainWidth = ((_a = context === null || context === void 0 ? void 0 : context.mainRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || window.innerWidth;
|
|
41
|
+
const maxWidth = width + Math.max(0, mainWidth - CONTENT_WRAPPER_MIN_WIDTH);
|
|
42
|
+
const step = e.key === 'ArrowRight' ? ARROW_KEY_STEP : -ARROW_KEY_STEP;
|
|
43
|
+
const newWidth = Math.min(maxWidth, Math.max(MIN_PANEL_WIDTH, width + step));
|
|
44
|
+
setWidth(newWidth);
|
|
45
|
+
onWidthChange === null || onWidthChange === void 0 ? void 0 : onWidthChange(newWidth);
|
|
46
|
+
}, [context, onWidthChange, width]);
|
|
47
|
+
useDocumentEvents(() => {
|
|
48
|
+
if (!isDragging)
|
|
49
|
+
return undefined;
|
|
50
|
+
return {
|
|
51
|
+
mousemove: (e) => {
|
|
52
|
+
if (!dragStartRef.current)
|
|
53
|
+
return;
|
|
54
|
+
if (rafIdRef.current !== null) {
|
|
55
|
+
window.cancelAnimationFrame(rafIdRef.current);
|
|
56
|
+
}
|
|
57
|
+
rafIdRef.current = window.requestAnimationFrame(() => {
|
|
58
|
+
rafIdRef.current = null;
|
|
59
|
+
if (!dragStartRef.current)
|
|
60
|
+
return;
|
|
61
|
+
const delta = e.clientX - dragStartRef.current.x;
|
|
62
|
+
const newWidth = dragStartRef.current.width + delta;
|
|
63
|
+
const clamped = Math.min(dragStartRef.current.maxWidth, Math.max(MIN_PANEL_WIDTH, newWidth));
|
|
64
|
+
setWidth(clamped);
|
|
65
|
+
onWidthChange === null || onWidthChange === void 0 ? void 0 : onWidthChange(clamped);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
mouseup: () => {
|
|
69
|
+
if (rafIdRef.current !== null) {
|
|
70
|
+
window.cancelAnimationFrame(rafIdRef.current);
|
|
71
|
+
rafIdRef.current = null;
|
|
72
|
+
}
|
|
73
|
+
setIsDragging(false);
|
|
74
|
+
dragStartRef.current = null;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}, [isDragging, onWidthChange]);
|
|
78
|
+
if (!open)
|
|
79
|
+
return null;
|
|
80
|
+
return (jsxs("aside", { "aria-label": "Left panel", className: cx(layoutClasses.sidePanel, layoutClasses.sidePanelLeft, className), style: { inlineSize: width }, children: [jsx("div", { className: layoutClasses.sidePanelContent, children: jsx(Scrollbar, { ...scrollbarProps, children: children }) }), jsx("div", { "aria-label": "Resize left panel", "aria-orientation": "vertical", "aria-valuemin": MIN_PANEL_WIDTH, "aria-valuenow": width, className: cx(layoutClasses.divider, {
|
|
81
|
+
[layoutClasses.dividerDragging]: isDragging,
|
|
82
|
+
}), onKeyDown: handleDividerKeyDown, onMouseDown: handleDividerMouseDown, role: "separator", tabIndex: 0 })] }));
|
|
83
|
+
}
|
|
84
|
+
LayoutLeftPanel.displayName = 'Layout.LeftPanel';
|
|
85
|
+
|
|
86
|
+
export { LayoutLeftPanel };
|
package/Layout/LayoutMain.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import { ScrollbarProps } from '../Scrollbar';
|
|
1
2
|
export interface LayoutMainProps {
|
|
2
3
|
/** The content rendered inside the main area. */
|
|
3
4
|
children?: React.ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Additional class name applied to the main element.
|
|
7
|
+
*/
|
|
8
|
+
className?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Props passed to the internal `Scrollbar` component. If not provided, the main area will still be scrollable but without the custom scrollbar styling and behavior.
|
|
11
|
+
*/
|
|
12
|
+
scrollbarProps?: Omit<ScrollbarProps, 'children'>;
|
|
4
13
|
}
|
|
5
|
-
export declare function LayoutMain(
|
|
14
|
+
export declare function LayoutMain(props: LayoutMainProps): import("react/jsx-runtime").JSX.Element;
|
|
6
15
|
export declare namespace LayoutMain {
|
|
7
16
|
var displayName: string;
|
|
8
17
|
}
|
package/Layout/LayoutMain.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
import { layoutClasses } from '@mezzanine-ui/core/layout';
|
|
5
|
+
import { LayoutContext } from './LayoutContext.js';
|
|
6
|
+
import Scrollbar from '../Scrollbar/Scrollbar.js';
|
|
7
|
+
import cx from 'clsx';
|
|
2
8
|
|
|
3
|
-
function LayoutMain(
|
|
4
|
-
|
|
9
|
+
function LayoutMain(props) {
|
|
10
|
+
var _a;
|
|
11
|
+
const { children, className, scrollbarProps = {} } = props;
|
|
12
|
+
const context = useContext(LayoutContext);
|
|
13
|
+
return (jsx("div", { ref: (_a = context === null || context === void 0 ? void 0 : context.registerMain) !== null && _a !== void 0 ? _a : null, className: cx(layoutClasses.main, className), children: jsx(Scrollbar, { ...scrollbarProps, children: jsx("div", { className: layoutClasses.mainContent, children: children }) }) }));
|
|
5
14
|
}
|
|
6
15
|
LayoutMain.displayName = 'Layout.Main';
|
|
7
16
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ScrollbarProps } from '../Scrollbar';
|
|
2
|
+
export interface LayoutRightPanelProps {
|
|
3
|
+
/** The content rendered inside the right panel. */
|
|
4
|
+
children?: React.ReactNode;
|
|
5
|
+
/** Additional class name applied to the panel element. */
|
|
6
|
+
className?: string;
|
|
7
|
+
/** Initial width (in px) of the panel. Clamped to a minimum of 240px. */
|
|
8
|
+
defaultWidth?: number;
|
|
9
|
+
/** Callback fired when the panel width changes during resize. */
|
|
10
|
+
onWidthChange?: (width: number) => void;
|
|
11
|
+
/** Controls whether the panel and its divider are visible. */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
/** Props passed to the internal `Scrollbar` component. */
|
|
14
|
+
scrollbarProps?: Omit<ScrollbarProps, 'children'>;
|
|
15
|
+
}
|
|
16
|
+
export declare function LayoutRightPanel({ children, className, defaultWidth, onWidthChange, open, scrollbarProps, }: LayoutRightPanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export declare namespace LayoutRightPanel {
|
|
18
|
+
var displayName: string;
|
|
19
|
+
}
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsxs,
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { useContext, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { layoutClasses } from '@mezzanine-ui/core/layout';
|
|
5
5
|
import { useDocumentEvents } from '../hooks/useDocumentEvents.js';
|
|
6
|
-
import {
|
|
6
|
+
import { LayoutContext } from './LayoutContext.js';
|
|
7
|
+
import Scrollbar from '../Scrollbar/Scrollbar.js';
|
|
7
8
|
import cx from 'clsx';
|
|
8
9
|
|
|
9
10
|
const MIN_PANEL_WIDTH = 240;
|
|
11
|
+
const CONTENT_WRAPPER_MIN_WIDTH = 480;
|
|
10
12
|
const ARROW_KEY_STEP = 10;
|
|
11
|
-
function
|
|
12
|
-
const context = useContext(
|
|
13
|
+
function LayoutRightPanel({ children, className, defaultWidth = 320, onWidthChange, open = false, scrollbarProps = {}, }) {
|
|
14
|
+
const context = useContext(LayoutContext);
|
|
13
15
|
const [isDragging, setIsDragging] = useState(false);
|
|
14
|
-
const [
|
|
15
|
-
const maxWidth = typeof window !== 'undefined'
|
|
16
|
-
? window.innerWidth - MIN_PANEL_WIDTH - 1
|
|
17
|
-
: Infinity;
|
|
18
|
-
return Math.min(maxWidth, Math.max(MIN_PANEL_WIDTH, defaultSidePanelWidth));
|
|
19
|
-
});
|
|
16
|
+
const [width, setWidth] = useState(() => Math.max(MIN_PANEL_WIDTH, defaultWidth));
|
|
20
17
|
const dragStartRef = useRef(null);
|
|
21
18
|
const rafIdRef = useRef(null);
|
|
22
19
|
useEffect(() => {
|
|
@@ -27,24 +24,26 @@ function LayoutSidePanel({ children, defaultSidePanelWidth = 320, onSidePanelWid
|
|
|
27
24
|
}
|
|
28
25
|
};
|
|
29
26
|
}, []);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const handleDividerMouseDown = useCallback((e) => {
|
|
28
|
+
var _a;
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
const mainWidth = ((_a = context === null || context === void 0 ? void 0 : context.mainRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || window.innerWidth;
|
|
31
|
+
const maxWidth = width + Math.max(0, mainWidth - CONTENT_WRAPPER_MIN_WIDTH);
|
|
32
|
+
setIsDragging(true);
|
|
33
|
+
dragStartRef.current = { maxWidth, width, x: e.clientX };
|
|
34
|
+
}, [context, width]);
|
|
33
35
|
const handleDividerKeyDown = useCallback((e) => {
|
|
36
|
+
var _a;
|
|
34
37
|
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')
|
|
35
38
|
return;
|
|
36
39
|
e.preventDefault();
|
|
40
|
+
const mainWidth = ((_a = context === null || context === void 0 ? void 0 : context.mainRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || window.innerWidth;
|
|
41
|
+
const maxWidth = width + Math.max(0, mainWidth - CONTENT_WRAPPER_MIN_WIDTH);
|
|
37
42
|
const step = e.key === 'ArrowLeft' ? ARROW_KEY_STEP : -ARROW_KEY_STEP;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}, [onSidePanelWidthChange, sidePanelWidth]);
|
|
43
|
-
const handleDividerMouseDown = useCallback((e) => {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
setIsDragging(true);
|
|
46
|
-
dragStartRef.current = { width: sidePanelWidth, x: e.clientX };
|
|
47
|
-
}, [sidePanelWidth]);
|
|
43
|
+
const newWidth = Math.min(maxWidth, Math.max(MIN_PANEL_WIDTH, width + step));
|
|
44
|
+
setWidth(newWidth);
|
|
45
|
+
onWidthChange === null || onWidthChange === void 0 ? void 0 : onWidthChange(newWidth);
|
|
46
|
+
}, [context, onWidthChange, width]);
|
|
48
47
|
useDocumentEvents(() => {
|
|
49
48
|
if (!isDragging)
|
|
50
49
|
return undefined;
|
|
@@ -59,12 +58,11 @@ function LayoutSidePanel({ children, defaultSidePanelWidth = 320, onSidePanelWid
|
|
|
59
58
|
rafIdRef.current = null;
|
|
60
59
|
if (!dragStartRef.current)
|
|
61
60
|
return;
|
|
62
|
-
const delta = dragStartRef.current.x
|
|
63
|
-
const newWidth = dragStartRef.current.width
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
onSidePanelWidthChange === null || onSidePanelWidthChange === void 0 ? void 0 : onSidePanelWidthChange(clamped);
|
|
61
|
+
const delta = e.clientX - dragStartRef.current.x;
|
|
62
|
+
const newWidth = dragStartRef.current.width - delta;
|
|
63
|
+
const clamped = Math.min(dragStartRef.current.maxWidth, Math.max(MIN_PANEL_WIDTH, newWidth));
|
|
64
|
+
setWidth(clamped);
|
|
65
|
+
onWidthChange === null || onWidthChange === void 0 ? void 0 : onWidthChange(clamped);
|
|
68
66
|
});
|
|
69
67
|
},
|
|
70
68
|
mouseup: () => {
|
|
@@ -76,15 +74,13 @@ function LayoutSidePanel({ children, defaultSidePanelWidth = 320, onSidePanelWid
|
|
|
76
74
|
dragStartRef.current = null;
|
|
77
75
|
},
|
|
78
76
|
};
|
|
79
|
-
}, [isDragging,
|
|
77
|
+
}, [isDragging, onWidthChange]);
|
|
80
78
|
if (!open)
|
|
81
79
|
return null;
|
|
82
|
-
return (jsxs(
|
|
83
|
-
? window.innerWidth - MIN_PANEL_WIDTH - 1
|
|
84
|
-
: undefined, "aria-valuemin": MIN_PANEL_WIDTH, "aria-valuenow": sidePanelWidth, className: cx(layoutClasses.divider, {
|
|
80
|
+
return (jsxs("aside", { "aria-label": "Right panel", className: cx(layoutClasses.sidePanel, layoutClasses.sidePanelRight, className), style: { inlineSize: width }, children: [jsx("div", { "aria-label": "Resize right panel", "aria-orientation": "vertical", "aria-valuemin": MIN_PANEL_WIDTH, "aria-valuenow": width, className: cx(layoutClasses.divider, {
|
|
85
81
|
[layoutClasses.dividerDragging]: isDragging,
|
|
86
|
-
}), onKeyDown: handleDividerKeyDown, onMouseDown: handleDividerMouseDown, role: "separator", tabIndex: 0 }), jsx("
|
|
82
|
+
}), onKeyDown: handleDividerKeyDown, onMouseDown: handleDividerMouseDown, role: "separator", tabIndex: 0 }), jsx("div", { className: layoutClasses.sidePanelContent, children: jsx(Scrollbar, { ...scrollbarProps, children: children }) })] }));
|
|
87
83
|
}
|
|
88
|
-
|
|
84
|
+
LayoutRightPanel.displayName = 'Layout.RightPanel';
|
|
89
85
|
|
|
90
|
-
export {
|
|
86
|
+
export { LayoutRightPanel };
|
package/Layout/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { default, LayoutMain,
|
|
2
|
-
export type { LayoutMainProps, LayoutProps,
|
|
1
|
+
export { default, LayoutLeftPanel, LayoutMain, LayoutRightPanel, } from './Layout';
|
|
2
|
+
export type { LayoutLeftPanelProps, LayoutMainProps, LayoutProps, LayoutRightPanelProps, } from './Layout';
|
|
3
3
|
export type { LayoutHostProps } from './LayoutHost';
|
package/Layout/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
2
2
|
import { ModalContainerProps } from './useModalContainer';
|
|
3
3
|
export interface MediaPreviewModalProps extends Omit<ModalContainerProps, 'children'>, NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
4
|
+
/**
|
|
5
|
+
* The custom class name applied to the modal container.
|
|
6
|
+
*/
|
|
7
|
+
backdropClassName?: string;
|
|
4
8
|
/**
|
|
5
9
|
* The current index of the media being displayed (controlled mode).
|
|
6
10
|
* If provided along with onNext/onPrev, the component operates in controlled mode.
|
|
@@ -15,7 +15,7 @@ import cx from 'clsx';
|
|
|
15
15
|
* Displays media items with navigation controls and a close button.
|
|
16
16
|
*/
|
|
17
17
|
const MediaPreviewModal = forwardRef(function MediaPreviewModal(props, ref) {
|
|
18
|
-
const { className, container, currentIndex: controlledIndex, defaultIndex = 0, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disableNext = false, disablePortal = false, disablePrev = false, enableCircularNavigation = false, mediaItems, onBackdropClick, onClose, onIndexChange, onNext, onPrev, open, showPaginationIndicator = true, ...rest } = props;
|
|
18
|
+
const { backdropClassName, className, container, currentIndex: controlledIndex, defaultIndex = 0, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disableNext = false, disablePortal = false, disablePrev = false, enableCircularNavigation = false, mediaItems, onBackdropClick, onClose, onIndexChange, onNext, onPrev, open, showPaginationIndicator = true, ...rest } = props;
|
|
19
19
|
const { Container: ModalContainer } = useModalContainer();
|
|
20
20
|
// Determine if component is in controlled mode
|
|
21
21
|
const isControlled = controlledIndex !== undefined;
|
|
@@ -169,7 +169,7 @@ const MediaPreviewModal = forwardRef(function MediaPreviewModal(props, ref) {
|
|
|
169
169
|
exit: MOTION_EASING.standard,
|
|
170
170
|
}, in: isCurrent, children: mediaElement.element }, index));
|
|
171
171
|
};
|
|
172
|
-
return (jsxs(ModalContainer, { className:
|
|
172
|
+
return (jsxs(ModalContainer, { className: backdropClassName, container: container, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, ref: ref, children: [jsx("div", { ...rest, className: cx(modalClasses.host, modalClasses.mediaPreview, className), role: "dialog", children: jsx("div", { className: modalClasses.mediaPreviewContent, children: jsx("div", { className: modalClasses.mediaPreviewMediaContainer, children: displayedIndices.map((index) => renderMedia(index)) }) }) }), jsx(ClearActions, { className: modalClasses.mediaPreviewCloseButton, onClick: onClose, type: "embedded", variant: "contrast" }), mediaItems.length > 1 && (jsx("button", { "aria-disabled": isPrevDisabled, "aria-label": "Previous media", className: cx(modalClasses.mediaPreviewNavButton, modalClasses.mediaPreviewNavButtonPrev), disabled: isPrevDisabled, onClick: handlePrev, title: "Previous", type: "button", children: jsx(Icon, { icon: ChevronLeftIcon, size: 16, color: "fixed-light" }) })), mediaItems.length > 1 && (jsx("button", { "aria-disabled": isNextDisabled, "aria-label": "Next media", className: cx(modalClasses.mediaPreviewNavButton, modalClasses.mediaPreviewNavButtonNext), disabled: isNextDisabled, onClick: handleNext, title: "Next", type: "button", children: jsx(Icon, { icon: ChevronRightIcon, size: 16, color: "fixed-light" }) })), showPaginationIndicator && mediaItems.length > 1 && (jsxs("div", { "aria-label": `Page ${currentIndex + 1} of ${mediaItems.length}`, className: modalClasses.mediaPreviewPaginationIndicator, children: [currentIndex + 1, "/", mediaItems.length] }))] }));
|
|
173
173
|
});
|
|
174
174
|
|
|
175
175
|
export { MediaPreviewModal as default };
|
package/Modal/Modal.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
|
4
4
|
import { ModalHeaderProps } from './ModalHeader';
|
|
5
5
|
import { ModalFooterProps } from './ModalFooter';
|
|
6
6
|
interface CommonModalProps extends Omit<ModalContainerProps, 'children'>, Pick<NativeElementPropsWithoutKeyAndRef<'div'>, 'children'>, Partial<Omit<ModalHeaderProps, 'children' | 'className' | 'title'>>, Partial<Omit<ModalFooterProps, 'children' | 'className' | 'confirmText'>> {
|
|
7
|
+
/**
|
|
8
|
+
* The custom class name applied to the modal container.
|
|
9
|
+
*/
|
|
10
|
+
backdropClassName?: string;
|
|
7
11
|
/**
|
|
8
12
|
* Whether to force full screen on any breakpoint.
|
|
9
13
|
* @default false
|
package/Modal/Modal.js
CHANGED
|
@@ -12,14 +12,14 @@ import cx from 'clsx';
|
|
|
12
12
|
* The react component for `mezzanine` modal.
|
|
13
13
|
*/
|
|
14
14
|
const Modal = forwardRef(function Modal(props, ref) {
|
|
15
|
-
const { actionsButtonLayout, annotation, auxiliaryContentButtonProps, auxiliaryContentButtonText, auxiliaryContentChecked, auxiliaryContentLabel, auxiliaryContentOnChange, auxiliaryContentOnClick, auxiliaryContentType, cancelButtonProps, cancelText, children, className, confirmButtonProps, confirmText, container, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal = false, extendedSplitLeftSideContent, extendedSplitRightSideContent, fullScreen = false, loading = false, modalStatusType = 'info', modalType = 'standard', onBackdropClick, onCancel, onClose, onConfirm, open, passwordButtonProps, passwordButtonText, passwordChecked, passwordCheckedLabel, passwordCheckedOnChange, passwordOnClick, showCancelButton, showDismissButton = true, showModalFooter = false, showModalHeader, showStatusTypeIcon, size = 'regular', statusTypeIconLayout, supportingText, supportingTextAlign, title, titleAlign, ...rest } = props;
|
|
15
|
+
const { actionsButtonLayout, annotation, auxiliaryContentButtonProps, auxiliaryContentButtonText, auxiliaryContentChecked, auxiliaryContentLabel, auxiliaryContentOnChange, auxiliaryContentOnClick, auxiliaryContentType, backdropClassName, cancelButtonProps, cancelText, children, className, confirmButtonProps, confirmText, container, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal = false, extendedSplitLeftSideContent, extendedSplitRightSideContent, fullScreen = false, loading = false, modalStatusType = 'info', modalType = 'standard', onBackdropClick, onCancel, onClose, onConfirm, open, passwordButtonProps, passwordButtonText, passwordChecked, passwordCheckedLabel, passwordCheckedOnChange, passwordOnClick, showCancelButton, showDismissButton = true, showModalFooter = false, showModalHeader, showStatusTypeIcon, size = 'regular', statusTypeIconLayout, supportingText, supportingTextAlign, title, titleAlign, ...rest } = props;
|
|
16
16
|
const modalControl = useMemo(() => ({
|
|
17
17
|
loading,
|
|
18
18
|
modalStatusType: modalStatusType,
|
|
19
19
|
}), [loading, modalStatusType]);
|
|
20
20
|
const { Container: ModalContainer } = useModalContainer();
|
|
21
21
|
const renderModalFooter = () => (jsx(ModalFooter, { actionsButtonLayout: actionsButtonLayout, annotation: annotation, auxiliaryContentButtonProps: auxiliaryContentButtonProps, auxiliaryContentButtonText: auxiliaryContentButtonText, auxiliaryContentChecked: auxiliaryContentChecked, auxiliaryContentLabel: auxiliaryContentLabel, auxiliaryContentOnChange: auxiliaryContentOnChange, auxiliaryContentOnClick: auxiliaryContentOnClick, auxiliaryContentType: auxiliaryContentType, cancelButtonProps: cancelButtonProps, cancelText: cancelText, confirmButtonProps: confirmButtonProps, confirmText: confirmText, loading: loading, onCancel: onCancel, onConfirm: onConfirm, passwordButtonProps: passwordButtonProps, passwordButtonText: passwordButtonText, passwordChecked: passwordChecked, passwordCheckedLabel: passwordCheckedLabel, passwordCheckedOnChange: passwordCheckedOnChange, passwordOnClick: passwordOnClick, showCancelButton: showCancelButton }));
|
|
22
|
-
return (jsx(ModalControlContext.Provider, { value: modalControl, children: jsx(ModalContainer, { className:
|
|
22
|
+
return (jsx(ModalControlContext.Provider, { value: modalControl, children: jsx(ModalContainer, { className: backdropClassName, container: container, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, ref: ref, children: jsxs("div", { ...rest, className: cx(modalClasses.host, modalClasses.modalStatusType(modalStatusType), modalClasses.size(size), {
|
|
23
23
|
[modalClasses.fullScreen]: fullScreen,
|
|
24
24
|
[modalClasses.withCloseIcon]: showDismissButton,
|
|
25
25
|
}, className), role: "dialog", children: [showModalHeader && (jsx(ModalHeader, { showStatusTypeIcon: showStatusTypeIcon, statusTypeIconLayout: statusTypeIconLayout, supportingText: supportingText, supportingTextAlign: supportingTextAlign, title: title, titleAlign: titleAlign })), modalType === 'extendedSplit' && (jsxs("div", { className: modalClasses.modalBodyContainerExtendedSplit, children: [jsx("div", { className: modalClasses.modalBodyContainerExtendedSplitRight, children: extendedSplitRightSideContent }), jsxs("div", { className: modalClasses.modalBodyContainerExtendedSplitLeft, children: [jsx("div", { className: modalClasses.modalBodyContainerExtendedSplitLeftSideContent, children: extendedSplitLeftSideContent }), showModalFooter && renderModalFooter()] })] })), (modalType === 'standard' ||
|
package/index.d.ts
CHANGED
|
@@ -42,8 +42,8 @@ export { default as Cropper } from './Cropper';
|
|
|
42
42
|
export type { CropperComponent, CropperProps, CropperPropsBase, CropperSize, } from './Cropper';
|
|
43
43
|
export { default as Icon } from './Icon';
|
|
44
44
|
export type { IconColor, IconProps } from './Icon';
|
|
45
|
-
export { default as Layout, LayoutMain,
|
|
46
|
-
export type { LayoutMainProps, LayoutProps,
|
|
45
|
+
export { default as Layout, LayoutLeftPanel, LayoutMain, LayoutRightPanel, } from './Layout';
|
|
46
|
+
export type { LayoutLeftPanelProps, LayoutMainProps, LayoutProps, LayoutRightPanelProps, } from './Layout';
|
|
47
47
|
export { default as Separator } from './Separator';
|
|
48
48
|
export type { SeparatorProps } from './Separator';
|
|
49
49
|
export { default as Typography } from './Typography';
|
|
@@ -116,6 +116,8 @@ export { PickerTrigger, RangePickerTrigger, usePickerDocumentEventClose, usePick
|
|
|
116
116
|
export type { PickerTriggerProps, RangePickerTriggerProps, UsePickerDocumentEventCloseProps, UsePickerValueProps, } from './Picker';
|
|
117
117
|
export { default as Radio, RadioGroup } from './Radio';
|
|
118
118
|
export type { RadioGroupOrientation, RadioGroupProps, RadioProps, RadioSize, } from './Radio';
|
|
119
|
+
export { default as Cascader, CascaderPanel } from './Cascader';
|
|
120
|
+
export type { CascaderOption, CascaderPanelProps, CascaderProps, CascaderSize, } from './Cascader';
|
|
119
121
|
export { default as Select, SelectControlContext, SelectTrigger, SelectTriggerTags, } from './Select';
|
|
120
122
|
export type { SelectControl, SelectProps, SelectTriggerInputProps, SelectTriggerProps, SelectTriggerTagsProps, SelectValue, } from './Select';
|
|
121
123
|
export { default as Selection } from './Selection';
|
package/index.js
CHANGED
|
@@ -104,6 +104,8 @@ export { default as PickerTrigger } from './Picker/PickerTrigger.js';
|
|
|
104
104
|
export { default as RangePickerTrigger } from './Picker/RangePickerTrigger.js';
|
|
105
105
|
export { default as Radio } from './Radio/Radio.js';
|
|
106
106
|
export { default as RadioGroup } from './Radio/RadioGroup.js';
|
|
107
|
+
export { default as Cascader } from './Cascader/Cascader.js';
|
|
108
|
+
export { default as CascaderPanel } from './Cascader/CascaderPanel.js';
|
|
107
109
|
export { default as Select } from './Select/Select.js';
|
|
108
110
|
export { default as SelectTrigger } from './Select/SelectTrigger.js';
|
|
109
111
|
export { default as SelectTriggerTags } from './Select/SelectTriggerTags.js';
|
|
@@ -161,8 +163,9 @@ export { default as Slide } from './Transition/Slide.js';
|
|
|
161
163
|
export { default as Transition } from './Transition/Transition.js';
|
|
162
164
|
export { default as Translate } from './Transition/Translate.js';
|
|
163
165
|
export { default as Dropdown } from './Dropdown/Dropdown.js';
|
|
166
|
+
export { LayoutLeftPanel } from './Layout/LayoutLeftPanel.js';
|
|
164
167
|
export { LayoutMain } from './Layout/LayoutMain.js';
|
|
165
|
-
export {
|
|
168
|
+
export { LayoutRightPanel } from './Layout/LayoutRightPanel.js';
|
|
166
169
|
export { useStepper } from './Stepper/useStepper.js';
|
|
167
170
|
export { usePagination } from './Pagination/usePagination.js';
|
|
168
171
|
export { TableContext, TableDataContext, TableSuperContext, useTableContext, useTableDataContext, useTableSuperContext } from './Table/TableContext.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mezzanine-ui/react",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.2",
|
|
4
4
|
"description": "React components for mezzanine-ui",
|
|
5
5
|
"author": "Mezzanine",
|
|
6
6
|
"repository": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@floating-ui/dom": "^1.7.4",
|
|
32
32
|
"@floating-ui/react-dom": "^2.1.6",
|
|
33
33
|
"@hello-pangea/dnd": "^18.0.1",
|
|
34
|
-
"@mezzanine-ui/core": "1.0.0-rc.
|
|
35
|
-
"@mezzanine-ui/icons": "1.0.0-rc.
|
|
36
|
-
"@mezzanine-ui/system": "1.0.0-rc.
|
|
34
|
+
"@mezzanine-ui/core": "1.0.0-rc.2",
|
|
35
|
+
"@mezzanine-ui/icons": "1.0.0-rc.2",
|
|
36
|
+
"@mezzanine-ui/system": "1.0.0-rc.2",
|
|
37
37
|
"@tanstack/react-virtual": "^3.13.13",
|
|
38
38
|
"@types/react-transition-group": "^4.4.12",
|
|
39
39
|
"clsx": "^2.1.1",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface LayoutSidePanelProps {
|
|
2
|
-
/** The content rendered inside the side panel. */
|
|
3
|
-
children?: React.ReactNode;
|
|
4
|
-
/** Initial width (in px) of the side panel. Clamped to a minimum of 240px. */
|
|
5
|
-
defaultSidePanelWidth?: number;
|
|
6
|
-
/** Callback fired when the side panel width changes during resize. */
|
|
7
|
-
onSidePanelWidthChange?: (width: number) => void;
|
|
8
|
-
/** Controls whether the side panel and divider are visible. */
|
|
9
|
-
open?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export declare function LayoutSidePanel({ children, defaultSidePanelWidth, onSidePanelWidthChange, open, }: LayoutSidePanelProps): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
-
export declare namespace LayoutSidePanel {
|
|
13
|
-
var displayName: string;
|
|
14
|
-
}
|