@homebound/beam 2.95.2 → 2.96.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/README.md +1 -1
- package/dist/components/Table/GridTable.d.ts +3 -2
- package/dist/components/Table/GridTable.js +31 -13
- package/dist/forms/BoundCheckboxField.d.ts +1 -1
- package/dist/forms/BoundCheckboxField.js +8 -2
- package/dist/inputs/Checkbox.d.ts +4 -0
- package/dist/inputs/CheckboxBase.js +1 -1
- package/dist/inputs/ChipSelectField.js +1 -1
- package/dist/inputs/SelectField.mock.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ _To see the latest designs, check out the [Figma](https://www.figma.com/file/aWU
|
|
|
23
23
|
|
|
24
24
|
## Beam's API Design Approach
|
|
25
25
|
|
|
26
|
-
Beam is specifically "Homebound's
|
|
26
|
+
Beam is specifically "Homebound's Design System". Given this extremely narrow purpose, we can lean into the simplicity of:
|
|
27
27
|
|
|
28
28
|
- We don't need to support everything for everyone
|
|
29
29
|
- We can prefer API/UX consistency & simplicity over configuration & complexity
|
|
@@ -209,7 +209,7 @@ declare type GridRowKind<R extends Kinded, P extends R["kind"]> = DiscriminateUn
|
|
|
209
209
|
* - For server-side sorting, it's the sortKey to pass back to the server to
|
|
210
210
|
* request "sort by this column".
|
|
211
211
|
*
|
|
212
|
-
* - For client-side sorting, it
|
|
212
|
+
* - For client-side sorting, it's type `number`, to represent the current
|
|
213
213
|
* column being sorted, in which case we use the GridCellContent.value.
|
|
214
214
|
*/
|
|
215
215
|
export declare type GridColumn<R extends Kinded, S = {}> = {
|
|
@@ -270,7 +270,8 @@ export declare type GridCellAlignment = "left" | "right" | "center";
|
|
|
270
270
|
* primitive value for filtering and sorting.
|
|
271
271
|
*/
|
|
272
272
|
export declare type GridCellContent = {
|
|
273
|
-
content
|
|
273
|
+
/** The JSX content of the cell. Virtual tables that client-side sort should use a function to avaid perf overhead. */
|
|
274
|
+
content: ReactNode | (() => ReactNode);
|
|
274
275
|
alignment?: GridCellAlignment;
|
|
275
276
|
/** Allow value to be a function in case it's a dynamic value i.e. reading from an inline-edited proxy. */
|
|
276
277
|
value?: MaybeFn<number | string | Date | boolean | null | undefined>;
|
|
@@ -403,7 +403,7 @@ function maybeAddCardColumns(sizes, firstLastColumnWidth) {
|
|
|
403
403
|
}
|
|
404
404
|
function getIndentationCss(style, rowStyle, columnIndex, maybeContent) {
|
|
405
405
|
// Look for cell-specific indent or row-specific indent (row-specific is only one the first column)
|
|
406
|
-
const indent = (
|
|
406
|
+
const indent = (isGridCellContent(maybeContent) && maybeContent.indent) || (columnIndex === 0 && (rowStyle === null || rowStyle === void 0 ? void 0 : rowStyle.indent));
|
|
407
407
|
return indent === 1 ? style.indentOneCss || {} : indent === 2 ? style.indentTwoCss || {} : {};
|
|
408
408
|
}
|
|
409
409
|
function getFirstOrLastCellCss(style, columnIndex, columns) {
|
|
@@ -451,10 +451,10 @@ function GridRow(props) {
|
|
|
451
451
|
return;
|
|
452
452
|
}
|
|
453
453
|
const maybeContent = applyRowFn(column, row);
|
|
454
|
-
currentColspan =
|
|
454
|
+
currentColspan = isGridCellContent(maybeContent) ? (_a = maybeContent.colspan) !== null && _a !== void 0 ? _a : 1 : 1;
|
|
455
455
|
const canSortColumn = ((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "client" && column.clientSideSort !== false) ||
|
|
456
456
|
((sorting === null || sorting === void 0 ? void 0 : sorting.on) === "server" && !!column.serverSideSortKey);
|
|
457
|
-
const content = toContent(maybeContent, isHeader, canSortColumn, style);
|
|
457
|
+
const content = toContent(maybeContent, isHeader, canSortColumn, (sorting === null || sorting === void 0 ? void 0 : sorting.on) === "client", style, as);
|
|
458
458
|
(0, sortRows_1.ensureClientSideSortValueIsSortable)(sorting, isHeader, column, columnIndex, maybeContent);
|
|
459
459
|
// Note that it seems expensive to calc a per-cell class name/CSS-in-JS output,
|
|
460
460
|
// vs. setting global/table-wide CSS like `style.cellCss` on the root grid div with
|
|
@@ -502,26 +502,44 @@ const ObservedGridRow = react_1.default.memo((props) => ((0, jsx_runtime_1.jsx)(
|
|
|
502
502
|
// Invoke this just as a regular function so that Observer sees the proxy access's
|
|
503
503
|
return GridRow(props);
|
|
504
504
|
} }, void 0)));
|
|
505
|
+
/** A heuristic to detect the result of `React.createElement` / i.e. JSX. */
|
|
506
|
+
function isJSX(content) {
|
|
507
|
+
return typeof content === "object" && content && "type" in content && "props" in content;
|
|
508
|
+
}
|
|
505
509
|
/** If a column def return just string text for a given row, apply some default styling. */
|
|
506
|
-
function toContent(content, isHeader, canSortColumn, style) {
|
|
507
|
-
|
|
510
|
+
function toContent(content, isHeader, canSortColumn, isClientSideSorting, style, as) {
|
|
511
|
+
content = isGridCellContent(content) ? content.content : content;
|
|
512
|
+
if (typeof content === "function") {
|
|
513
|
+
// Actually create the JSX by calling `content()` here (which should be as late as
|
|
514
|
+
// possible, i.e. only for visible rows if we're in a virtual table).
|
|
515
|
+
content = content();
|
|
516
|
+
}
|
|
517
|
+
else if (as === "virtual" && canSortColumn && isClientSideSorting && isJSX(content)) {
|
|
518
|
+
// When using client-side sorting, we call `applyRowFn` not only during rendering, but
|
|
519
|
+
// up-front against all rows (for the currently sorted column) to determine their
|
|
520
|
+
// sort values.
|
|
521
|
+
//
|
|
522
|
+
// Pedantically this means that any table using client-side sorting should not
|
|
523
|
+
// build JSX directly in its GridColumn functions, but this overhead is especially
|
|
524
|
+
// noticeable for large/virtualized tables, so we only enforce using functions
|
|
525
|
+
// for those tables.
|
|
526
|
+
throw new Error("GridTables with as=virtual & sortable columns should use functions that return JSX, instead of JSX");
|
|
527
|
+
}
|
|
528
|
+
if (content && typeof content === "string" && isHeader && canSortColumn) {
|
|
508
529
|
return (0, jsx_runtime_1.jsx)(SortHeader_1.SortHeader, { content: content }, void 0);
|
|
509
530
|
}
|
|
510
531
|
else if (style.emptyCell && isContentEmpty(content)) {
|
|
511
532
|
// If the content is empty and the user specified an `emptyCell` node, return that.
|
|
512
533
|
return style.emptyCell;
|
|
513
534
|
}
|
|
514
|
-
else if (isContentAndSettings(content)) {
|
|
515
|
-
return content.content;
|
|
516
|
-
}
|
|
517
535
|
return content;
|
|
518
536
|
}
|
|
519
|
-
function
|
|
537
|
+
function isGridCellContent(content) {
|
|
520
538
|
return typeof content === "object" && !!content && "content" in content;
|
|
521
539
|
}
|
|
522
540
|
const emptyValues = ["", null, undefined];
|
|
523
541
|
function isContentEmpty(content) {
|
|
524
|
-
return emptyValues.includes(
|
|
542
|
+
return emptyValues.includes(content);
|
|
525
543
|
}
|
|
526
544
|
/** Return the content for a given column def applied to a given row. */
|
|
527
545
|
function applyRowFn(column, row) {
|
|
@@ -548,8 +566,8 @@ const defaultRenderFn = (as) => (key, css, content) => {
|
|
|
548
566
|
};
|
|
549
567
|
exports.GridCollapseContext = react_1.default.createContext({
|
|
550
568
|
headerCollapsed: false,
|
|
551
|
-
isCollapsed: (
|
|
552
|
-
toggleCollapsed: (
|
|
569
|
+
isCollapsed: () => false,
|
|
570
|
+
toggleCollapsed: () => { },
|
|
553
571
|
});
|
|
554
572
|
/** Sets up the `GridContext` so that header cells can access the current sort settings. */
|
|
555
573
|
const headerRenderFn = (columns, column, sortState, setSortKey, as) => (key, css, content) => {
|
|
@@ -588,7 +606,7 @@ const alignmentToTextAlign = {
|
|
|
588
606
|
};
|
|
589
607
|
// For alignment, use: 1) cell def, else 2) column def, else 3) left.
|
|
590
608
|
function getJustification(column, maybeContent, as) {
|
|
591
|
-
const alignment = (
|
|
609
|
+
const alignment = (isGridCellContent(maybeContent) && maybeContent.alignment) || column.align || "left";
|
|
592
610
|
// Always apply text alignment.
|
|
593
611
|
const textAlign = Css_1.Css.add("textAlign", alignmentToTextAlign[alignment]).$;
|
|
594
612
|
if (as === "table") {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FieldState } from "@homebound/form-state";
|
|
2
2
|
import { CheckboxProps } from "../inputs";
|
|
3
|
-
export declare type BoundCheckboxFieldProps = Omit<CheckboxProps, "values" | "onChange" | "label"
|
|
3
|
+
export declare type BoundCheckboxFieldProps = Omit<CheckboxProps, "values" | "onChange" | "label"> & {
|
|
4
4
|
field: FieldState<any, boolean | null | undefined>;
|
|
5
5
|
/** Make optional so that callers can override if they want to. */
|
|
6
6
|
onChange?: (values: boolean) => void;
|
|
@@ -8,7 +8,7 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
const defaultLabel_1 = require("../utils/defaultLabel");
|
|
9
9
|
/** Wraps `Checkbox` and binds it to a form field. */
|
|
10
10
|
function BoundCheckboxField(props) {
|
|
11
|
-
const { field, onChange = (value) => field.set(value), label = (0, defaultLabel_1.defaultLabel)(field.key), ...others } = props;
|
|
11
|
+
const { field, onChange = (value) => field.set(value), label = (0, defaultLabel_1.defaultLabel)(field.key), onFocus, onBlur, ...others } = props;
|
|
12
12
|
const testId = (0, utils_1.useTestIds)(props, field.key);
|
|
13
13
|
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => {
|
|
14
14
|
var _a;
|
|
@@ -16,7 +16,13 @@ function BoundCheckboxField(props) {
|
|
|
16
16
|
// We are triggering blur manually for checkbox fields due to its transactional nature
|
|
17
17
|
onChange(selected);
|
|
18
18
|
field.blur();
|
|
19
|
-
}, errorMsg: field.touched ? field.errors.join(" ") : undefined
|
|
19
|
+
}, errorMsg: field.touched ? field.errors.join(" ") : undefined, onFocus: () => {
|
|
20
|
+
field.focus();
|
|
21
|
+
(0, utils_1.maybeCall)(onFocus);
|
|
22
|
+
}, onBlur: () => {
|
|
23
|
+
field.blur();
|
|
24
|
+
(0, utils_1.maybeCall)(onBlur);
|
|
25
|
+
} }, testId, others), void 0));
|
|
20
26
|
} }, void 0));
|
|
21
27
|
}
|
|
22
28
|
exports.BoundCheckboxField = BoundCheckboxField;
|
|
@@ -14,5 +14,9 @@ export interface CheckboxProps {
|
|
|
14
14
|
selected?: boolean;
|
|
15
15
|
errorMsg?: string;
|
|
16
16
|
helperText?: string | ReactNode;
|
|
17
|
+
/** Callback fired when focus removes from the component */
|
|
18
|
+
onBlur?: () => void;
|
|
19
|
+
/** Callback fired when focus is set to the component */
|
|
20
|
+
onFocus?: () => void;
|
|
17
21
|
}
|
|
18
22
|
export declare function Checkbox(props: CheckboxProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
@@ -23,7 +23,7 @@ function CheckboxBase(props) {
|
|
|
23
23
|
.maxw((0, Css_1.px)(320))
|
|
24
24
|
.if(description !== undefined)
|
|
25
25
|
.maxw((0, Css_1.px)(344))
|
|
26
|
-
.if(isDisabled).cursorNotAllowed.$ }, { children: [(0, jsx_runtime_1.jsx)(react_aria_1.VisuallyHidden, { children: (0, jsx_runtime_1.jsx)("input", Object.assign({ ref: ref }, inputProps, focusProps, tid), void 0) }, void 0), (0, jsx_runtime_1.jsx)("span", Object.assign({}, hoverProps, { css: {
|
|
26
|
+
.if(isDisabled).cursorNotAllowed.$ }, { children: [(0, jsx_runtime_1.jsx)(react_aria_1.VisuallyHidden, { children: (0, jsx_runtime_1.jsx)("input", Object.assign({ ref: ref }, (0, react_aria_1.mergeProps)(inputProps, focusProps), tid), void 0) }, void 0), (0, jsx_runtime_1.jsx)("span", Object.assign({}, hoverProps, { css: {
|
|
27
27
|
...baseStyles,
|
|
28
28
|
...((isSelected || isIndeterminate) && filledBoxStyles),
|
|
29
29
|
...((isSelected || isIndeterminate) && isHovered && filledBoxHoverStyles),
|
|
@@ -42,7 +42,7 @@ function ChipSelectField(props) {
|
|
|
42
42
|
},
|
|
43
43
|
onBlur: (e) => {
|
|
44
44
|
// Do not call onBlur if focus moved to within the Popover
|
|
45
|
-
if (popoverRef.current && popoverRef.current.contains(e.relatedTarget)) {
|
|
45
|
+
if ((popoverRef.current && popoverRef.current.contains(e.relatedTarget)) || showInput) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
(0, utils_1.maybeCall)(onBlur);
|
|
@@ -7,7 +7,7 @@ const utils_1 = require("../utils");
|
|
|
7
7
|
function SelectField(props) {
|
|
8
8
|
const { getOptionValue = (o) => o.id, // if unset, assume O implements HasId
|
|
9
9
|
getOptionLabel = (o) => o.name, // if unset, assume O implements HasName
|
|
10
|
-
value, options, onSelect, readOnly = false, errorMsg, onBlur, onFocus, disabled, } = props;
|
|
10
|
+
value, options, onSelect, readOnly = false, errorMsg, onBlur, onFocus, disabled, disabledOptions = [], } = props;
|
|
11
11
|
const tid = (0, utils_1.useTestIds)(props, "select");
|
|
12
12
|
const currentOption = options.find((o) => getOptionValue(o) === value) || options[0];
|
|
13
13
|
return ((0, jsx_runtime_1.jsxs)("select", Object.assign({}, tid, { value:
|
|
@@ -24,7 +24,7 @@ function SelectField(props) {
|
|
|
24
24
|
},
|
|
25
25
|
// Read Only does not apply to `select` fields, instead we'll add in disabled for tests to verify.
|
|
26
26
|
disabled: !!(disabled || readOnly), "data-error": !!errorMsg, "data-errormsg": errorMsg, "data-readonly": readOnly }, { children: [(0, jsx_runtime_1.jsx)("option", { disabled: true, value: "" }, void 0), options.map((option, i) => {
|
|
27
|
-
return ((0, jsx_runtime_1.jsx)("option", Object.assign({ value: `${getOptionValue(option)}
|
|
27
|
+
return ((0, jsx_runtime_1.jsx)("option", Object.assign({ value: `${getOptionValue(option)}`, disabled: disabledOptions.includes(getOptionValue(option)) }, { children: getOptionLabel(option) }), i));
|
|
28
28
|
})] }), void 0));
|
|
29
29
|
}
|
|
30
30
|
exports.SelectField = SelectField;
|