@homebound/beam 2.362.2 → 2.364.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/components/Table/GridTableApi.d.ts +17 -1
- package/dist/components/Table/GridTableApi.js +74 -1
- package/dist/components/Table/components/Row.js +4 -2
- package/dist/components/Table/components/cell.d.ts +5 -1
- package/dist/components/Table/utils/utils.d.ts +2 -0
- package/dist/components/Table/utils/utils.js +2 -1
- package/dist/inputs/TextField.d.ts +10 -0
- package/dist/inputs/TextField.js +5 -1
- 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() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MutableRefObject } from "react";
|
|
2
2
|
import { VirtuosoHandle } from "react-virtuoso";
|
|
3
|
-
import { GridRowLookup } from "../index";
|
|
3
|
+
import { GridRowLookup, MaybeFn } from "../index";
|
|
4
4
|
import { GridDataRow } from "./components/Row";
|
|
5
5
|
import { DiscriminateUnion, Kinded } from "./types";
|
|
6
6
|
import { TableState } from "./utils/TableState";
|
|
@@ -51,6 +51,18 @@ export type GridTableApi<R extends Kinded> = {
|
|
|
51
51
|
deleteRows(ids: string[]): void;
|
|
52
52
|
getVisibleColumnIds(): string[];
|
|
53
53
|
setVisibleColumns(ids: string[]): void;
|
|
54
|
+
/**
|
|
55
|
+
* Triggers the table's current content to be downloaded as a CSV file.
|
|
56
|
+
*
|
|
57
|
+
* This currently assumes client-side pagination/sorting, i.e. we have the full dataset in memory.
|
|
58
|
+
*/
|
|
59
|
+
downloadToCsv(fileName: string): void;
|
|
60
|
+
/**
|
|
61
|
+
* Copies the table's current content to the clipboard.
|
|
62
|
+
*
|
|
63
|
+
* This currently assumes client-side pagination/sorting, i.e. we have the full dataset in memory.
|
|
64
|
+
*/
|
|
65
|
+
copyToClipboard(): Promise<void>;
|
|
54
66
|
};
|
|
55
67
|
/** Adds per-row methods to the `api`, i.e. for getting currently-visible children. */
|
|
56
68
|
export type GridRowApi<R extends Kinded> = GridTableApi<R> & {
|
|
@@ -84,4 +96,8 @@ export declare class GridTableApiImpl<R extends Kinded> implements GridTableApi<
|
|
|
84
96
|
setVisibleColumns(ids: string[]): void;
|
|
85
97
|
getVisibleColumnIds(): string[];
|
|
86
98
|
deleteRows(ids: string[]): void;
|
|
99
|
+
downloadToCsv(fileName: string): void;
|
|
100
|
+
copyToClipboard(): Promise<void>;
|
|
101
|
+
generateCsvContent(): string[];
|
|
87
102
|
}
|
|
103
|
+
export declare function maybeApply<T>(maybeFn: MaybeFn<T>): T;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GridTableApiImpl = exports.useGridTableApi = void 0;
|
|
3
|
+
exports.maybeApply = exports.GridTableApiImpl = exports.useGridTableApi = void 0;
|
|
4
4
|
const mobx_1 = require("mobx");
|
|
5
5
|
const mobx_utils_1 = require("mobx-utils");
|
|
6
6
|
const react_1 = require("react");
|
|
@@ -107,11 +107,84 @@ class GridTableApiImpl {
|
|
|
107
107
|
deleteRows(ids) {
|
|
108
108
|
this.tableState.deleteRows(ids);
|
|
109
109
|
}
|
|
110
|
+
downloadToCsv(fileName) {
|
|
111
|
+
// Create a link element, set the download attribute with the provided filename
|
|
112
|
+
const link = document.createElement("a");
|
|
113
|
+
if (link.download === undefined)
|
|
114
|
+
throw new Error("This browser does not support the download attribute.");
|
|
115
|
+
// Create a Blob from the CSV content
|
|
116
|
+
const url = URL.createObjectURL(new Blob([this.generateCsvContent().join("\n")], { type: "text/csv;charset=utf-8;" }));
|
|
117
|
+
link.setAttribute("href", url);
|
|
118
|
+
link.setAttribute("download", fileName);
|
|
119
|
+
link.style.visibility = "hidden";
|
|
120
|
+
document.body.appendChild(link);
|
|
121
|
+
link.click();
|
|
122
|
+
document.body.removeChild(link);
|
|
123
|
+
}
|
|
124
|
+
copyToClipboard() {
|
|
125
|
+
// Copy the CSV content to the clipboard
|
|
126
|
+
const content = this.generateCsvContent().join("\n");
|
|
127
|
+
return navigator.clipboard.writeText(content).catch((err) => {
|
|
128
|
+
// Let the user know the copy failed...
|
|
129
|
+
window.alert("Failed to copy to clipboard, probably due to browser restrictions.");
|
|
130
|
+
throw err;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// visibleForTesting, not part of the GridTableApi
|
|
134
|
+
// ...although maybe it could be public someday, to allow getting the raw the CSV content
|
|
135
|
+
// and then sending it somewhere else, like directly to a gsheet.
|
|
136
|
+
generateCsvContent() {
|
|
137
|
+
// Convert the array of rows into CSV format
|
|
138
|
+
return this.tableState.visibleRows.map((rs) => {
|
|
139
|
+
const values = this.tableState.visibleColumns
|
|
140
|
+
.filter((c) => !c.isAction)
|
|
141
|
+
.map((c) => {
|
|
142
|
+
// Just guessing for level=1
|
|
143
|
+
const maybeContent = (0, index_1.applyRowFn)(c, rs.row, this, 1, true, undefined);
|
|
144
|
+
if ((0, index_1.isGridCellContent)(maybeContent)) {
|
|
145
|
+
const cell = maybeContent;
|
|
146
|
+
const content = maybeApply(cell.content);
|
|
147
|
+
// Anything not isJSX (like a string) we can put into the CSV directly
|
|
148
|
+
if (!(0, index_1.isJSX)(content))
|
|
149
|
+
return content;
|
|
150
|
+
// Otherwise use the value/sortValue values
|
|
151
|
+
return cell.value ? maybeApply(cell.value) : cell.sortValue ? maybeApply(cell.sortValue) : "-";
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// ReactNode
|
|
155
|
+
return (0, index_1.isJSX)(maybeContent) ? "-" : maybeContent;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return values.map(toCsvString).map(escapeCsvValue).join(",");
|
|
159
|
+
});
|
|
160
|
+
}
|
|
110
161
|
}
|
|
111
162
|
exports.GridTableApiImpl = GridTableApiImpl;
|
|
163
|
+
function toCsvString(value) {
|
|
164
|
+
if (value === null || value === undefined)
|
|
165
|
+
return "";
|
|
166
|
+
if (typeof value === "string")
|
|
167
|
+
return value;
|
|
168
|
+
if (typeof value === "number")
|
|
169
|
+
return value.toString();
|
|
170
|
+
if (typeof value === "boolean")
|
|
171
|
+
return value ? "true" : "false";
|
|
172
|
+
return String(value);
|
|
173
|
+
}
|
|
174
|
+
function escapeCsvValue(value) {
|
|
175
|
+
// Wrap values with special chars in quotes, and double quotes themselves
|
|
176
|
+
if (value.includes('"') || value.includes(",") || value.includes("\n")) {
|
|
177
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
178
|
+
}
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
112
181
|
function bindMethods(instance) {
|
|
113
182
|
Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).forEach((key) => {
|
|
114
183
|
if (instance[key] instanceof Function && key !== "constructor")
|
|
115
184
|
instance[key] = instance[key].bind(instance);
|
|
116
185
|
});
|
|
117
186
|
}
|
|
187
|
+
function maybeApply(maybeFn) {
|
|
188
|
+
return typeof maybeFn === "function" ? maybeFn() : maybeFn;
|
|
189
|
+
}
|
|
190
|
+
exports.maybeApply = maybeApply;
|
|
@@ -119,14 +119,16 @@ function RowImpl(props) {
|
|
|
119
119
|
currentColspan -= 1;
|
|
120
120
|
return null;
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
// Combine all our drag stuff into a mini-context/parameter object...
|
|
123
|
+
const dragData = {
|
|
123
124
|
rowRenderRef: ref,
|
|
124
125
|
onDragStart,
|
|
125
126
|
onDragEnd,
|
|
126
127
|
onDrop,
|
|
127
128
|
onDragEnter,
|
|
128
129
|
onDragOver: onDragOverDebounced,
|
|
129
|
-
}
|
|
130
|
+
};
|
|
131
|
+
const maybeContent = (0, utils_1.applyRowFn)(column, row, rowApi, level, isExpanded, dragData);
|
|
130
132
|
// Only use the `numExpandedColumns` as the `colspan` when rendering the "Expandable Header"
|
|
131
133
|
currentColspan =
|
|
132
134
|
(0, utils_1.isGridCellContent)(maybeContent) && typeof maybeContent.colspan === "number"
|
|
@@ -6,10 +6,14 @@ import { Properties, Typography } from "../../../Css";
|
|
|
6
6
|
/**
|
|
7
7
|
* Allows a cell to be more than just a RectNode, i.e. declare its alignment or
|
|
8
8
|
* primitive value for filtering and sorting.
|
|
9
|
+
*
|
|
10
|
+
* For a given column, the `GridColumn` can either return a static `GridCellContent`, or
|
|
11
|
+
* more likely use a function that returns a per-column/per-row `GridCellContent` that defines
|
|
12
|
+
* the value (and it's misc alignment/css/etc) for this specific cell.
|
|
9
13
|
*/
|
|
10
14
|
export type GridCellContent = {
|
|
11
15
|
/** The JSX content of the cell. Virtual tables that client-side sort should use a function to avoid perf overhead. */
|
|
12
|
-
content: ReactNode
|
|
16
|
+
content: MaybeFn<ReactNode>;
|
|
13
17
|
alignment?: GridCellAlignment;
|
|
14
18
|
/** Allow value to be a function in case it's a dynamic value i.e. reading from an inline-edited proxy. */
|
|
15
19
|
value?: MaybeFn<number | string | Date | boolean | null | undefined>;
|
|
@@ -22,6 +22,8 @@ export declare const ASC: "ASC";
|
|
|
22
22
|
export declare const DESC: "DESC";
|
|
23
23
|
export declare const emptyCell: GridCellContent;
|
|
24
24
|
export declare function getFirstOrLastCellCss<R extends Kinded>(style: GridStyle, columnIndex: number, columns: GridColumnWithId<R>[]): Properties;
|
|
25
|
+
/** A heuristic to detect the result of `React.createElement` / i.e. JSX. */
|
|
26
|
+
export declare function isJSX(content: any): boolean;
|
|
25
27
|
export declare function getAlignment(column: GridColumnWithId<any>, maybeContent: ReactNode | GridCellContent): GridCellAlignment;
|
|
26
28
|
export declare function getJustification(column: GridColumnWithId<any>, maybeContent: ReactNode | GridCellContent, as: RenderAs, alignment: GridCellAlignment): {
|
|
27
29
|
textAlign: import("csstype").Property.TextAlign | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTableRefWidthStyles = exports.recursivelyGetContainingRow = exports.isCursorBelowMidpoint = exports.insertAtIndex = exports.loadArrayOrUndefined = exports.zIndices = exports.reservedRowKinds = exports.KEPT_GROUP = exports.EXPANDABLE_HEADER = exports.TOTALS = exports.HEADER = exports.matchesFilter = exports.maybeApplyFunction = exports.getJustification = exports.getAlignment = exports.getFirstOrLastCellCss = exports.emptyCell = exports.DESC = exports.ASC = exports.applyRowFn = exports.isGridCellContent = exports.toContent = void 0;
|
|
3
|
+
exports.getTableRefWidthStyles = exports.recursivelyGetContainingRow = exports.isCursorBelowMidpoint = exports.insertAtIndex = exports.loadArrayOrUndefined = exports.zIndices = exports.reservedRowKinds = exports.KEPT_GROUP = exports.EXPANDABLE_HEADER = exports.TOTALS = exports.HEADER = exports.matchesFilter = exports.maybeApplyFunction = exports.getJustification = exports.getAlignment = exports.isJSX = exports.getFirstOrLastCellCss = exports.emptyCell = exports.DESC = exports.ASC = exports.applyRowFn = exports.isGridCellContent = exports.toContent = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
5
|
const Icon_1 = require("../../Icon");
|
|
6
6
|
const ExpandableHeader_1 = require("../components/ExpandableHeader");
|
|
@@ -98,6 +98,7 @@ exports.getFirstOrLastCellCss = getFirstOrLastCellCss;
|
|
|
98
98
|
function isJSX(content) {
|
|
99
99
|
return typeof content === "object" && content && "type" in content && "props" in content;
|
|
100
100
|
}
|
|
101
|
+
exports.isJSX = isJSX;
|
|
101
102
|
const alignmentToJustify = {
|
|
102
103
|
left: "flex-start",
|
|
103
104
|
center: "center",
|
|
@@ -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
|