@homebound/beam 2.362.1 → 2.363.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Modal/TestModalContent.d.ts +1 -0
- package/dist/components/Modal/TestModalContent.js +1 -1
- package/dist/inputs/TextField.d.ts +10 -0
- package/dist/inputs/TextField.js +5 -1
- package/dist/inputs/TreeSelectField/TreeSelectField.js +39 -14
- package/dist/inputs/TreeSelectField/utils.d.ts +1 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ export interface TestModalContentProps {
|
|
|
4
4
|
withTag?: boolean;
|
|
5
5
|
withDateField?: boolean;
|
|
6
6
|
withTextArea?: boolean;
|
|
7
|
+
withTextField?: boolean;
|
|
7
8
|
}
|
|
8
9
|
/** A fake modal content component that we share across the modal and superdrawer stories. */
|
|
9
10
|
export declare function TestModalContent(props: TestModalContentProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -24,7 +24,7 @@ function TestModalContent(props) {
|
|
|
24
24
|
const [date, setDate] = (0, react_1.useState)(formStateDomain_1.jan1);
|
|
25
25
|
const [internalValue, setValue] = (0, react_1.useState)("");
|
|
26
26
|
const { triggerNotice } = (0, Snackbar_1.useSnackbar)();
|
|
27
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Modal_1.ModalHeader, { children: props.withTag ? ((0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.aic.$, children: [(0, jsx_runtime_1.jsx)("span", { children: "Modal Title with Tag" }), (0, jsx_runtime_1.jsx)(Tag_1.Tag, { text: "In progress", type: "info", xss: Css_1.Css.ml1.$ })] })) : props.withTextArea ? ((0, jsx_runtime_1.jsx)(inputs_1.TextAreaField, { label: "Title", placeholder: "Test title", value: internalValue, onChange: (v) => setValue(v), preventNewLines: true, labelStyle: "hidden", borderless: true, xss: Css_1.Css.xl.$ })) : ("The title of the modal that might wrap") }), (0, jsx_runtime_1.jsxs)(Modal_1.ModalBody, { children: [(0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.gap1.fdc.aifs.$, children: [(0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.gap1.$, children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { label: "More", onClick: () => setNumSentences(numSentences + 2) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Clear", onClick: () => setNumSentences(0) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Primary", onClick: () => setPrimaryDisabled(!primaryDisabled) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Trigger Snackbar", onClick: () => triggerNotice({ message: "Snackbar message" }) }), showLeftAction && ((0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Left Action", onClick: () => setLeftActionDisabled(!leftActionDisabled) }))] }), (0, jsx_runtime_1.jsx)("p", { children: "The body content of the modal. This content can be anything!".repeat(numSentences) })] }), withDateField && (0, jsx_runtime_1.jsx)(inputs_1.DateField, { value: date, label: "Date", onChange: setDate })] }), (0, jsx_runtime_1.jsxs)(Modal_1.ModalFooter, { xss: showLeftAction ? Css_1.Css.jcsb.$ : undefined, children: [showLeftAction && ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Clear", onClick: (0, addon_actions_1.action)("Clear Action"), variant: "tertiary", disabled: leftActionDisabled }) })), (0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.gap1.$, children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Cancel", onClick: closeModal, variant: "tertiary" }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Apply", onClick: (0, addon_actions_1.action)("Primary action"), disabled: primaryDisabled })] })] })] }));
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Modal_1.ModalHeader, { children: props.withTag ? ((0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.aic.$, children: [(0, jsx_runtime_1.jsx)("span", { children: "Modal Title with Tag" }), (0, jsx_runtime_1.jsx)(Tag_1.Tag, { text: "In progress", type: "info", xss: Css_1.Css.ml1.$ })] })) : props.withTextField ? ((0, jsx_runtime_1.jsx)(inputs_1.TextField, { label: "Title", placeholder: "Test title", value: internalValue, onChange: (v) => setValue(v), labelStyle: "hidden", onEscapeBubble: true, borderless: true, xss: Css_1.Css.xl.$ })) : props.withTextArea ? ((0, jsx_runtime_1.jsx)(inputs_1.TextAreaField, { label: "Title", placeholder: "Test title", value: internalValue, onChange: (v) => setValue(v), preventNewLines: true, labelStyle: "hidden", borderless: true, xss: Css_1.Css.xl.$ })) : ("The title of the modal that might wrap") }), (0, jsx_runtime_1.jsxs)(Modal_1.ModalBody, { children: [(0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.gap1.fdc.aifs.$, children: [(0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.gap1.$, children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { label: "More", onClick: () => setNumSentences(numSentences + 2) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Clear", onClick: () => setNumSentences(0) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Primary", onClick: () => setPrimaryDisabled(!primaryDisabled) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Trigger Snackbar", onClick: () => triggerNotice({ message: "Snackbar message" }) }), showLeftAction && ((0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Left Action", onClick: () => setLeftActionDisabled(!leftActionDisabled) }))] }), (0, jsx_runtime_1.jsx)("p", { children: "The body content of the modal. This content can be anything!".repeat(numSentences) })] }), withDateField && (0, jsx_runtime_1.jsx)(inputs_1.DateField, { value: date, label: "Date", onChange: setDate })] }), (0, jsx_runtime_1.jsxs)(Modal_1.ModalFooter, { xss: showLeftAction ? Css_1.Css.jcsb.$ : undefined, children: [showLeftAction && ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Clear", onClick: (0, addon_actions_1.action)("Clear Action"), variant: "tertiary", disabled: leftActionDisabled }) })), (0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.gap1.$, children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Cancel", onClick: closeModal, variant: "tertiary" }), (0, jsx_runtime_1.jsx)(Button_1.Button, { label: "Apply", onClick: (0, addon_actions_1.action)("Primary action"), disabled: primaryDisabled })] })] })] }));
|
|
28
28
|
}
|
|
29
29
|
exports.TestModalContent = TestModalContent;
|
|
30
30
|
function TestModalFilterTable() {
|
|
@@ -6,6 +6,16 @@ export interface TextFieldProps<X> extends BeamTextFieldProps<X> {
|
|
|
6
6
|
clearable?: boolean;
|
|
7
7
|
api?: MutableRefObject<TextFieldApi | undefined>;
|
|
8
8
|
onEnter?: VoidFunction;
|
|
9
|
+
/**
|
|
10
|
+
* Allows a TextField to opt-in to bubbling up the escape key event to its parent.
|
|
11
|
+
*
|
|
12
|
+
* Usually this is a bad idea, because escape-in-a-modal might lose the user's WIP (without
|
|
13
|
+
* sufficient "are you sure" checking), and so instead we let callers opt-in to this.
|
|
14
|
+
*
|
|
15
|
+
* Note that react-aria's `useSearchField` / `useComboBox` seems to have this built-in:
|
|
16
|
+
* https://github.com/adobe/react-spectrum/issues/5480
|
|
17
|
+
*/
|
|
18
|
+
onEscapeBubble?: boolean;
|
|
9
19
|
endAdornment?: ReactNode;
|
|
10
20
|
startAdornment?: ReactNode;
|
|
11
21
|
hideErrorMessage?: boolean;
|
package/dist/inputs/TextField.js
CHANGED
|
@@ -8,7 +8,7 @@ const components_1 = require("../components");
|
|
|
8
8
|
const TextFieldBase_1 = require("./TextFieldBase");
|
|
9
9
|
const utils_1 = require("../utils");
|
|
10
10
|
function TextField(props) {
|
|
11
|
-
const { disabled = false, readOnly = false, required, errorMsg, value = "", onBlur, onFocus, api, onEnter, hideErrorMessage, ...otherProps } = props;
|
|
11
|
+
const { disabled = false, readOnly = false, required, errorMsg, value = "", onBlur, onFocus, api, onEnter, onEscapeBubble, hideErrorMessage, ...otherProps } = props;
|
|
12
12
|
const isDisabled = !!disabled;
|
|
13
13
|
const isReadOnly = !!readOnly;
|
|
14
14
|
const textFieldProps = {
|
|
@@ -28,6 +28,10 @@ function TextField(props) {
|
|
|
28
28
|
(0, utils_1.maybeCall)(onEnter);
|
|
29
29
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
30
30
|
}
|
|
31
|
+
else if (e.key === "Escape" && onEscapeBubble) {
|
|
32
|
+
// Allow closing modals from within text fields...
|
|
33
|
+
e.continuePropagation();
|
|
34
|
+
}
|
|
31
35
|
},
|
|
32
36
|
}, inputRef);
|
|
33
37
|
// Construct our TextFieldApi to give access to some imperative methods
|
|
@@ -42,7 +42,19 @@ function TreeSelectField(props) {
|
|
|
42
42
|
const { getOptionValue = (opt) => opt.id, // if unset, assume O implements HasId
|
|
43
43
|
getOptionLabel = (opt) => opt.name, // if unset, assume O implements HasName
|
|
44
44
|
options, onSelect, values, defaultCollapsed = false, ...otherProps } = props;
|
|
45
|
-
const [collapsedKeys, setCollapsedKeys] = (0, react_1.useState)(
|
|
45
|
+
const [collapsedKeys, setCollapsedKeys] = (0, react_1.useState)([]);
|
|
46
|
+
(0, react_1.useEffect)(() => {
|
|
47
|
+
setCollapsedKeys(!Array.isArray(options)
|
|
48
|
+
? []
|
|
49
|
+
: defaultCollapsed
|
|
50
|
+
? options.map((o) => getOptionValue(o))
|
|
51
|
+
: options
|
|
52
|
+
.flatMap(utils_1.flattenOptions)
|
|
53
|
+
.filter((o) => o.defaultCollapsed)
|
|
54
|
+
.map((o) => getOptionValue(o)));
|
|
55
|
+
// Explicitly ignoring `getOptionValue` as it typically isn't memo'd
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
}, [options, defaultCollapsed]);
|
|
46
58
|
const contextValue = (0, react_1.useMemo)(() => ({ collapsedKeys, setCollapsedKeys, getOptionValue }),
|
|
47
59
|
// TODO: validate this eslint-disable. It was automatically ignored as part of https://app.shortcut.com/homebound-team/story/40033/enable-react-hooks-exhaustive-deps-for-react-projects
|
|
48
60
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -84,14 +96,17 @@ function TreeSelectFieldBase(props) {
|
|
|
84
96
|
// Find the options that matches the value. These could be parents or a children.
|
|
85
97
|
const foundOptions = (0, utils_1.findOptions)(initialOptions, (0, Value_1.valueToKey)(v), getOptionValue);
|
|
86
98
|
// Go through the `foundOptions` and get the keys of the options and its children if it has any.
|
|
87
|
-
return foundOptions.flatMap(({ option }) =>
|
|
88
|
-
var _a, _b;
|
|
89
|
-
return [
|
|
90
|
-
(0, Value_1.valueToKey)(getOptionValue(option)),
|
|
91
|
-
...((_b = (_a = option.children) === null || _a === void 0 ? void 0 : _a.flatMap((o) => (0, Value_1.valueToKey)(getOptionValue(o)))) !== null && _b !== void 0 ? _b : []),
|
|
92
|
-
];
|
|
93
|
-
});
|
|
99
|
+
return foundOptions.flatMap(({ option }) => selectOptionAndAllChildren(option));
|
|
94
100
|
}));
|
|
101
|
+
function selectOptionAndAllChildren(maybeParent) {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
// Check if the maybeParent has children, if so, return those as selected keys
|
|
104
|
+
// Do in a recursive way so that children may have children
|
|
105
|
+
return [
|
|
106
|
+
(0, Value_1.valueToKey)(getOptionValue(maybeParent)),
|
|
107
|
+
...((_b = (_a = maybeParent.children) === null || _a === void 0 ? void 0 : _a.flatMap(selectOptionAndAllChildren)) !== null && _b !== void 0 ? _b : []),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
95
110
|
// It is possible that all the children of a parent were considered selected `values`, but the parent wasn't included in the `values` array.
|
|
96
111
|
// In this case, the parent also should be considered a selected option.
|
|
97
112
|
function areAllChildrenSelected(maybeParent) {
|
|
@@ -146,17 +161,27 @@ function TreeSelectFieldBase(props) {
|
|
|
146
161
|
getOptionLabel,
|
|
147
162
|
isReadOnly,
|
|
148
163
|
nothingSelectedText,
|
|
149
|
-
collapsedKeys,
|
|
150
164
|
getOptionValue,
|
|
165
|
+
collapsedKeys,
|
|
151
166
|
]);
|
|
152
167
|
// Initialize the TreeFieldState
|
|
153
168
|
const [fieldState, setFieldState] = (0, react_1.useState)(() => initTreeFieldState());
|
|
169
|
+
(0, react_1.useEffect)(() => {
|
|
170
|
+
// We don't want to do this if initialOptions is not an array, because we would be lazy loading `allOptions`
|
|
171
|
+
if (Array.isArray(options)) {
|
|
172
|
+
setFieldState((prevState) => ({ ...prevState, allOptions: options }));
|
|
173
|
+
}
|
|
174
|
+
}, [options]);
|
|
154
175
|
// Reset the TreeFieldState if the values array changes and doesn't match the selectedOptions
|
|
155
176
|
(0, react_1.useEffect)(() => {
|
|
156
177
|
// if the values does not match the values in the fieldState, then update the fieldState
|
|
157
178
|
const selectedKeys = fieldState.selectedOptions.map((o) => (0, Value_1.valueToKey)(getOptionValue(o)));
|
|
158
|
-
if (
|
|
159
|
-
|
|
179
|
+
if (
|
|
180
|
+
// If the values were cleared
|
|
181
|
+
(values === undefined && selectedKeys.length !== 0) ||
|
|
182
|
+
// Or values were set, but they don't match the selected keys
|
|
183
|
+
(values && (values.length !== selectedKeys.length || !values.every((v) => selectedKeys.includes((0, Value_1.valueToKey)(v)))))) {
|
|
184
|
+
// Then reinitialize
|
|
160
185
|
setFieldState(initTreeFieldState());
|
|
161
186
|
}
|
|
162
187
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -218,7 +243,7 @@ function TreeSelectFieldBase(props) {
|
|
|
218
243
|
setFieldState((prevState) => ({
|
|
219
244
|
...prevState,
|
|
220
245
|
inputValue: "",
|
|
221
|
-
filteredOptions:
|
|
246
|
+
filteredOptions: prevState.allOptions.flatMap((o) => levelOptions(o, 0, false, collapsedKeys, getOptionValue)),
|
|
222
247
|
}));
|
|
223
248
|
}
|
|
224
249
|
}
|
|
@@ -417,10 +442,10 @@ function TreeSelectFieldBase(props) {
|
|
|
417
442
|
...positionProps.style,
|
|
418
443
|
width: (_c = comboBoxRef === null || comboBoxRef === void 0 ? void 0 : comboBoxRef.current) === null || _c === void 0 ? void 0 : _c.clientWidth,
|
|
419
444
|
// Ensures the menu never gets too small.
|
|
420
|
-
minWidth:
|
|
445
|
+
minWidth: 320,
|
|
421
446
|
};
|
|
422
447
|
const fieldMaxWidth = (0, utils_2.getFieldWidth)(fullWidth);
|
|
423
|
-
return ((0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.fdc.w100.maxw(fieldMaxWidth).if(labelStyle === "left").maxw100.$, ref: comboBoxRef, children: [(0, jsx_runtime_1.jsx)(ComboBoxInput_1.ComboBoxInput, { ...otherProps, fullWidth: fullWidth, labelStyle: labelStyle, buttonProps: buttonProps, buttonRef: triggerRef, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, listBoxRef: listBoxRef, state: state, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, selectedOptionsLabels: fieldState.selectedOptionsLabels, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, contrast: contrast, borderless: borderless, tooltip: (0, components_1.resolveTooltip)(disabled, undefined, readOnly), resetField: resetField, nothingSelectedText: nothingSelectedText, isTree: true }), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, { triggerRef: triggerRef, popoverRef: popoverRef, positionProps: positionProps, onClose: () => state.close(), isOpen: state.isOpen, minWidth:
|
|
448
|
+
return ((0, jsx_runtime_1.jsxs)("div", { css: Css_1.Css.df.fdc.w100.maxw(fieldMaxWidth).if(labelStyle === "left").maxw100.$, ref: comboBoxRef, children: [(0, jsx_runtime_1.jsx)(ComboBoxInput_1.ComboBoxInput, { ...otherProps, fullWidth: fullWidth, labelStyle: labelStyle, buttonProps: buttonProps, buttonRef: triggerRef, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, listBoxRef: listBoxRef, state: state, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, selectedOptionsLabels: fieldState.selectedOptionsLabels, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, contrast: contrast, borderless: borderless, tooltip: (0, components_1.resolveTooltip)(disabled, undefined, readOnly), resetField: resetField, nothingSelectedText: nothingSelectedText, isTree: true }), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, { triggerRef: triggerRef, popoverRef: popoverRef, positionProps: positionProps, onClose: () => state.close(), isOpen: state.isOpen, minWidth: 320, children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, { ...listBoxProps, positionProps: positionProps, state: state, listBoxRef: listBoxRef, selectedOptions: fieldState.selectedOptions, getOptionLabel: getOptionLabel, getOptionValue: (o) => (0, Value_1.valueToKey)(getOptionValue(o)), contrast: contrast, horizontalLayout: labelStyle === "left", loading: fieldState.optionsLoading, allowCollapsing: fieldState.allowCollapsing, isTree: true }) }))] }));
|
|
424
449
|
}
|
|
425
450
|
function levelOptions(o, level, filtering, collapsedKeys, getOptionValue) {
|
|
426
451
|
var _a;
|