@homebound/beam 2.78.2 → 2.79.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/dist/components/BeamContext.d.ts +3 -3
- package/dist/components/Filters/DateFilter.d.ts +15 -0
- package/dist/components/Filters/DateFilter.js +37 -0
- package/dist/components/Filters/MultiFilter.d.ts +2 -1
- package/dist/components/Filters/SingleFilter.d.ts +2 -1
- package/dist/components/Filters/index.d.ts +2 -0
- package/dist/components/Filters/index.js +3 -1
- package/dist/components/Filters/testDomain.d.ts +4 -0
- package/dist/components/Filters/testDomain.js +12 -1
- package/dist/components/Label.d.ts +1 -1
- package/dist/components/Label.js +1 -1
- package/dist/components/SuperDrawer/ConfirmCloseModal.d.ts +3 -1
- package/dist/components/SuperDrawer/ConfirmCloseModal.js +7 -6
- package/dist/components/SuperDrawer/useSuperDrawer.d.ts +3 -2
- package/dist/components/SuperDrawer/useSuperDrawer.js +8 -4
- package/dist/forms/BoundMultiSelectField.d.ts +2 -1
- package/dist/forms/BoundSelectField.d.ts +2 -1
- package/dist/inputs/DateField.d.ts +3 -0
- package/dist/inputs/DateField.js +8 -4
- package/dist/inputs/TextFieldBase.d.ts +4 -2
- package/dist/inputs/TextFieldBase.js +49 -17
- package/dist/inputs/internal/SelectFieldBase.d.ts +3 -1
- package/dist/inputs/internal/SelectFieldBase.js +2 -2
- package/dist/inputs/internal/SelectFieldInput.d.ts +3 -1
- package/dist/inputs/internal/SelectFieldInput.js +81 -108
- package/dist/interfaces.d.ts +2 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MutableRefObject, ReactNode } from "react";
|
|
2
2
|
import { ModalProps } from "./Modal/Modal";
|
|
3
3
|
import { ContentStack } from "./SuperDrawer/useSuperDrawer";
|
|
4
|
-
import { CheckFn } from "../types";
|
|
4
|
+
import { CanCloseCheck, CheckFn } from "../types";
|
|
5
5
|
/** The internal state of our Beam context; see useModal and useSuperDrawer for the public APIs. */
|
|
6
6
|
export interface BeamContextState {
|
|
7
7
|
modalState: MutableRefObject<ModalProps | undefined>;
|
|
@@ -15,9 +15,9 @@ export interface BeamContextState {
|
|
|
15
15
|
/** SuperDrawer contentStack, i.e. the main/non-detail content + 0-N detail contents. */
|
|
16
16
|
drawerContentStack: MutableRefObject<readonly ContentStack[]>;
|
|
17
17
|
/** Checks when closing SuperDrawer, for the main/non-detail drawer content. */
|
|
18
|
-
drawerCanCloseChecks: MutableRefObject<
|
|
18
|
+
drawerCanCloseChecks: MutableRefObject<CanCloseCheck[]>;
|
|
19
19
|
/** Checks when closing SuperDrawer Details, a double array to keep per-detail lists. */
|
|
20
|
-
drawerCanCloseDetailsChecks: MutableRefObject<
|
|
20
|
+
drawerCanCloseDetailsChecks: MutableRefObject<CanCloseCheck[][]>;
|
|
21
21
|
/** The ref for defining the portal element's location for Tab actions */
|
|
22
22
|
tabActionsRef: MutableRefObject<HTMLDivElement | null>;
|
|
23
23
|
/** The div for Tab actions to portal into */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Key } from "react";
|
|
2
|
+
import { Filter } from "./types";
|
|
3
|
+
import { Value } from "../../inputs";
|
|
4
|
+
export declare type DateFilterProps<O, V extends Value, DV extends DateFilterValue<V>> = {
|
|
5
|
+
label: string;
|
|
6
|
+
operations: O[];
|
|
7
|
+
getOperationValue: (o: O) => V;
|
|
8
|
+
getOperationLabel: (o: O) => string;
|
|
9
|
+
defaultValue?: DV;
|
|
10
|
+
};
|
|
11
|
+
export declare type DateFilterValue<V extends Value> = {
|
|
12
|
+
op: V;
|
|
13
|
+
value: Date;
|
|
14
|
+
};
|
|
15
|
+
export declare function dateFilter<O, V extends Key>(props: DateFilterProps<O, V, DateFilterValue<V>>): (key: string) => Filter<DateFilterValue<V>>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dateFilter = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const BaseFilter_1 = require("./BaseFilter");
|
|
7
|
+
const Label_1 = require("../Label");
|
|
8
|
+
const Css_1 = require("../../Css");
|
|
9
|
+
const inputs_1 = require("../../inputs");
|
|
10
|
+
const defaultTestId_1 = require("../../utils/defaultTestId");
|
|
11
|
+
function dateFilter(props) {
|
|
12
|
+
return (key) => new DateFilter(key, props);
|
|
13
|
+
}
|
|
14
|
+
exports.dateFilter = dateFilter;
|
|
15
|
+
// Custom option that allows for not selecting an operation
|
|
16
|
+
const anyOption = {};
|
|
17
|
+
class DateFilter extends BaseFilter_1.BaseFilter {
|
|
18
|
+
render(value, setValue, tid, inModal, vertical) {
|
|
19
|
+
const { label, operations, getOperationValue, getOperationLabel } = this.props;
|
|
20
|
+
const [focusedEl, setFocusedEl] = (0, react_1.useState)();
|
|
21
|
+
const commonStyles = Css_1.Css.df.aic.fs1.maxwPx(550).bt.bb.bGray300.$;
|
|
22
|
+
// TODO: Maybe make a `CompoundField` component, which could handle the `display: flex` and any sizing requirements per field and focus states.
|
|
23
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [vertical && (0, jsx_runtime_1.jsx)(Label_1.Label, { label: label }, void 0), (0, jsx_runtime_1.jsxs)("div", Object.assign({}, this.testId(tid), { css: Css_1.Css.df.$ }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
|
|
24
|
+
...commonStyles,
|
|
25
|
+
...Css_1.Css.bl.borderRadius("4px 0 0 4px").if(focusedEl === "op").bLightBlue700.$,
|
|
26
|
+
} }, { children: (0, jsx_runtime_1.jsx)(inputs_1.SelectField, Object.assign({ compact: true, borderless: true, sizeToContent: true, options: [
|
|
27
|
+
// Always show the 'Any' option
|
|
28
|
+
anyOption,
|
|
29
|
+
...operations,
|
|
30
|
+
], getOptionValue: (o) => (o === anyOption ? undefined : getOperationValue(o)), getOptionLabel: (o) => (o === anyOption ? "Any" : getOperationLabel(o)), value: value === null || value === void 0 ? void 0 : value.op, onSelect: (op) =>
|
|
31
|
+
// default the selected date to today if it doesn't exist in the filter's value
|
|
32
|
+
setValue(op ? { op, value: (value === null || value === void 0 ? void 0 : value.value) ? new Date(value.value) : new Date() } : undefined), label: inModal ? `${label} date filter operation` : label, inlineLabel: !inModal && !vertical, hideLabel: inModal || vertical, nothingSelectedText: "Any" }, tid[`${(0, defaultTestId_1.defaultTestId)(this.label)}_dateOperation`], { onFocus: () => setFocusedEl("op"), onBlur: () => setFocusedEl(undefined) }), void 0) }), void 0), (0, jsx_runtime_1.jsx)("div", { css: Css_1.Css.wPx(1).flexNone.bgGray300.if(focusedEl !== undefined).bgLightBlue700.$ }, void 0), (0, jsx_runtime_1.jsx)("div", Object.assign({ css: {
|
|
33
|
+
...commonStyles,
|
|
34
|
+
...Css_1.Css.fg1.br.borderRadius("0 4px 4px 0").if(focusedEl === "date").bLightBlue700.$,
|
|
35
|
+
} }, { children: (0, jsx_runtime_1.jsx)(inputs_1.DateField, Object.assign({ borderless: true, compact: true, inlineLabel: true, value: (value === null || value === void 0 ? void 0 : value.value) ? new Date(value.value) : new Date(), label: "Date", onChange: (d) => setValue({ ...value, value: d }), disabled: !value }, tid[`${(0, defaultTestId_1.defaultTestId)(this.label)}_dateField`], { onFocus: () => setFocusedEl("date"), onBlur: () => setFocusedEl(undefined) }), void 0) }), void 0)] }), void 0)] }, void 0));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -2,7 +2,8 @@ import { Key } from "react";
|
|
|
2
2
|
import { Filter } from "./types";
|
|
3
3
|
import { MultiSelectFieldProps } from "../../inputs/MultiSelectField";
|
|
4
4
|
import { Value } from "../../inputs/Value";
|
|
5
|
-
export declare type MultiFilterProps<O, V extends Value> = Omit<MultiSelectFieldProps<O, V>, "values" | "onSelect"> & {
|
|
5
|
+
export declare type MultiFilterProps<O, V extends Value> = Omit<MultiSelectFieldProps<O, V>, "values" | "onSelect" | "label"> & {
|
|
6
6
|
defaultValue?: V[];
|
|
7
|
+
label?: string;
|
|
7
8
|
};
|
|
8
9
|
export declare function multiFilter<O, V extends Key>(props: MultiFilterProps<O, V>): (key: string) => Filter<V[]>;
|
|
@@ -2,7 +2,8 @@ import { Key } from "react";
|
|
|
2
2
|
import { Filter } from "./types";
|
|
3
3
|
import { SelectFieldProps } from "../../inputs/SelectField";
|
|
4
4
|
import { Value } from "../../inputs/Value";
|
|
5
|
-
export declare type SingleFilterProps<O, V extends Value> = Omit<SelectFieldProps<O, V>, "value" | "onSelect"> & {
|
|
5
|
+
export declare type SingleFilterProps<O, V extends Value> = Omit<SelectFieldProps<O, V>, "value" | "onSelect" | "label"> & {
|
|
6
6
|
defaultValue?: V;
|
|
7
|
+
label?: string;
|
|
7
8
|
};
|
|
8
9
|
export declare function singleFilter<O, V extends Key>(props: SingleFilterProps<O, V>): (key: string) => Filter<V>;
|
|
@@ -10,7 +10,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
10
10
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.toggleFilter = exports.booleanFilter = exports.singleFilter = exports.multiFilter = void 0;
|
|
13
|
+
exports.toggleFilter = exports.booleanFilter = exports.singleFilter = exports.multiFilter = exports.dateFilter = void 0;
|
|
14
|
+
var DateFilter_1 = require("./DateFilter");
|
|
15
|
+
Object.defineProperty(exports, "dateFilter", { enumerable: true, get: function () { return DateFilter_1.dateFilter; } });
|
|
14
16
|
var MultiFilter_1 = require("./MultiFilter");
|
|
15
17
|
Object.defineProperty(exports, "multiFilter", { enumerable: true, get: function () { return MultiFilter_1.multiFilter; } });
|
|
16
18
|
var SingleFilter_1 = require("./SingleFilter");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DateFilterValue } from "./DateFilter";
|
|
1
2
|
import { FilterDefs } from "./types";
|
|
2
3
|
export declare enum Stage {
|
|
3
4
|
StageOne = "ONE",
|
|
@@ -35,8 +36,11 @@ export declare type ProjectFilter = {
|
|
|
35
36
|
status?: string[] | null;
|
|
36
37
|
isTest?: boolean | null;
|
|
37
38
|
doNotUse?: boolean | null;
|
|
39
|
+
date?: DateFilterValue<string>;
|
|
38
40
|
};
|
|
39
41
|
export declare type StageFilter = NonNullable<FilterDefs<ProjectFilter>["stage"]>;
|
|
40
42
|
export declare type StageSingleFilter = NonNullable<FilterDefs<ProjectFilter>["stageSingle"]>;
|
|
43
|
+
export declare type DateFilter = NonNullable<FilterDefs<ProjectFilter>["date"]>;
|
|
41
44
|
export declare const stageFilter: StageFilter;
|
|
42
45
|
export declare const stageSingleFilter: StageSingleFilter;
|
|
46
|
+
export declare const taskDueFilter: DateFilter;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.stageSingleFilter = exports.stageFilter = exports.Stage = void 0;
|
|
3
|
+
exports.taskDueFilter = exports.stageSingleFilter = exports.stageFilter = exports.Stage = void 0;
|
|
4
|
+
const DateFilter_1 = require("./DateFilter");
|
|
4
5
|
const MultiFilter_1 = require("./MultiFilter");
|
|
5
6
|
const SingleFilter_1 = require("./SingleFilter");
|
|
6
7
|
var Stage;
|
|
@@ -22,3 +23,13 @@ exports.stageSingleFilter = (0, SingleFilter_1.singleFilter)({
|
|
|
22
23
|
getOptionValue: (s) => s.code,
|
|
23
24
|
getOptionLabel: (s) => s.name,
|
|
24
25
|
});
|
|
26
|
+
exports.taskDueFilter = (0, DateFilter_1.dateFilter)({
|
|
27
|
+
operations: [
|
|
28
|
+
{ label: "On", value: "ON" },
|
|
29
|
+
{ label: "Before", value: "BEFORE" },
|
|
30
|
+
{ label: "After", value: "AFTER" },
|
|
31
|
+
],
|
|
32
|
+
label: "Task Due",
|
|
33
|
+
getOperationLabel: (o) => o.label,
|
|
34
|
+
getOperationValue: (o) => o.value,
|
|
35
|
+
});
|
|
@@ -9,5 +9,5 @@ interface LabelProps {
|
|
|
9
9
|
/** An internal helper component for rendering form labels. */
|
|
10
10
|
export declare function Label(props: LabelProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
11
11
|
/** Used for showing labels within text fields. */
|
|
12
|
-
export declare function InlineLabel({ labelProps, label, ...others }: LabelProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function InlineLabel({ labelProps, label, contrast, ...others }: LabelProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
13
13
|
export {};
|
package/dist/components/Label.js
CHANGED
|
@@ -12,7 +12,7 @@ function Label(props) {
|
|
|
12
12
|
}
|
|
13
13
|
exports.Label = Label;
|
|
14
14
|
/** Used for showing labels within text fields. */
|
|
15
|
-
function InlineLabel({ labelProps, label, ...others }) {
|
|
15
|
+
function InlineLabel({ labelProps, label, contrast, ...others }) {
|
|
16
16
|
return ((0, jsx_runtime_1.jsxs)("label", Object.assign({}, labelProps, others, { css: Css_1.Css.smEm.nowrap.gray900.prPx(4).add("color", "currentColor").$ }, { children: [label, ":"] }), void 0));
|
|
17
17
|
}
|
|
18
18
|
exports.InlineLabel = InlineLabel;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
interface ConfirmCloseModalProps {
|
|
2
2
|
onClose: () => void;
|
|
3
|
+
discardText?: string;
|
|
4
|
+
continueText?: string;
|
|
3
5
|
}
|
|
4
6
|
/** Modal content to appear when a close checks fails */
|
|
5
|
-
export declare function ConfirmCloseModal(
|
|
7
|
+
export declare function ConfirmCloseModal(props: ConfirmCloseModalProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
6
8
|
export {};
|
|
@@ -5,7 +5,8 @@ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
|
5
5
|
const src_1 = require("../..");
|
|
6
6
|
const BeamContext_1 = require("../BeamContext");
|
|
7
7
|
/** Modal content to appear when a close checks fails */
|
|
8
|
-
function ConfirmCloseModal(
|
|
8
|
+
function ConfirmCloseModal(props) {
|
|
9
|
+
const { onClose, discardText = "Discard Changes", continueText = "Continue Editing" } = props;
|
|
9
10
|
const { modalState } = (0, BeamContext_1.useBeamContext)();
|
|
10
11
|
// TODO: Change to closeModal from useModal when canCloseChecks are reset
|
|
11
12
|
function closeModal() {
|
|
@@ -13,10 +14,10 @@ function ConfirmCloseModal({ onClose }) {
|
|
|
13
14
|
// after a close and could/will cause other close attempts to fail.
|
|
14
15
|
modalState.current = undefined;
|
|
15
16
|
}
|
|
16
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(src_1.ModalHeader, { children: "Confirm" }, void 0), (0, jsx_runtime_1.jsx)(src_1.ModalBody, { children: (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: src_1.Css.tc.wPx(400).$ }, { children: [(0, jsx_runtime_1.jsx)("p", Object.assign({ css: src_1.Css.lgEm.gray900.mb2.$ }, { children: "Are you sure you want to cancel
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(src_1.ModalHeader, { children: "Confirm" }, void 0), (0, jsx_runtime_1.jsx)(src_1.ModalBody, { children: (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: src_1.Css.tc.wPx(400).$ }, { children: [(0, jsx_runtime_1.jsx)("p", Object.assign({ css: src_1.Css.lgEm.gray900.mb2.$ }, { children: "Are you sure you want to cancel?" }), void 0), (0, jsx_runtime_1.jsx)("p", Object.assign({ css: src_1.Css.base.gray700.$ }, { children: "Any data you've entered so far will be lost." }), void 0)] }), void 0) }, void 0), (0, jsx_runtime_1.jsx)(src_1.ModalFooter, Object.assign({ xss: src_1.Css.jcc.$ }, { children: (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: src_1.Css.df.fdc.childGap1.aic.$ }, { children: [(0, jsx_runtime_1.jsx)(src_1.Button, { label: continueText, onClick: closeModal }, void 0), (0, jsx_runtime_1.jsx)(src_1.Button, { variant: "tertiary", label: discardText, onClick: () => {
|
|
18
|
+
// The order of these calls doesn't really matter; close this modal and tell the call to do their close
|
|
19
|
+
onClose();
|
|
20
|
+
closeModal();
|
|
21
|
+
} }, void 0)] }), void 0) }), void 0)] }, void 0));
|
|
21
22
|
}
|
|
22
23
|
exports.ConfirmCloseModal = ConfirmCloseModal;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
+
import { CanCloseCheck } from "../../types";
|
|
2
3
|
export interface OpenInDrawerOpts {
|
|
3
4
|
/** Title of the SuperDrawer */
|
|
4
5
|
title: string;
|
|
@@ -44,13 +45,13 @@ export interface UseSuperDrawerHook {
|
|
|
44
45
|
* false, a confirmation modal will appear allowing the user to confirm
|
|
45
46
|
* the action.
|
|
46
47
|
*/
|
|
47
|
-
addCanCloseDrawerCheck: (canCloseCheck:
|
|
48
|
+
addCanCloseDrawerCheck: (canCloseCheck: CanCloseCheck) => void;
|
|
48
49
|
/**
|
|
49
50
|
* Adds a check when attempting to close a SuperDrawer detail by clicking the
|
|
50
51
|
* "back" button or calling `closeDrawerDetail()`. If any checks returns
|
|
51
52
|
* false, a confirmation modal will appear allowing the user to confirm
|
|
52
53
|
* the action.
|
|
53
54
|
*/
|
|
54
|
-
addCanCloseDrawerDetailCheck: (canCloseCheck:
|
|
55
|
+
addCanCloseDrawerDetailCheck: (canCloseCheck: CanCloseCheck) => void;
|
|
55
56
|
}
|
|
56
57
|
export declare function useSuperDrawer(): UseSuperDrawerHook;
|
|
@@ -12,8 +12,8 @@ function useSuperDrawer() {
|
|
|
12
12
|
function canCloseDrawerDetails(i, doChange) {
|
|
13
13
|
var _a;
|
|
14
14
|
for (const canCloseDrawerDetail of (_a = canCloseDetailsChecks.current[i]) !== null && _a !== void 0 ? _a : []) {
|
|
15
|
-
if (!canCloseDrawerDetail
|
|
16
|
-
openModal({ content: (0, jsx_runtime_1.jsx)(ConfirmCloseModal_1.ConfirmCloseModal, { onClose: doChange }, void 0) });
|
|
15
|
+
if (!canClose(canCloseDrawerDetail)) {
|
|
16
|
+
openModal({ content: (0, jsx_runtime_1.jsx)(ConfirmCloseModal_1.ConfirmCloseModal, Object.assign({ onClose: doChange }, canCloseDrawerDetail), void 0) });
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -40,9 +40,9 @@ function useSuperDrawer() {
|
|
|
40
40
|
}
|
|
41
41
|
// Attempt to close the drawer
|
|
42
42
|
for (const canCloseDrawer of canCloseChecks.current) {
|
|
43
|
-
if (!canCloseDrawer
|
|
43
|
+
if (!canClose(canCloseDrawer)) {
|
|
44
44
|
openModal({
|
|
45
|
-
content: (0, jsx_runtime_1.jsx)(ConfirmCloseModal_1.ConfirmCloseModal, { onClose: doChange }, void 0),
|
|
45
|
+
content: (0, jsx_runtime_1.jsx)(ConfirmCloseModal_1.ConfirmCloseModal, Object.assign({ onClose: doChange }, canCloseDrawer), void 0),
|
|
46
46
|
});
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
@@ -139,3 +139,7 @@ function useSuperDrawer() {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
exports.useSuperDrawer = useSuperDrawer;
|
|
142
|
+
function canClose(canCloseCheck) {
|
|
143
|
+
return ((typeof canCloseCheck === "function" && canCloseCheck()) ||
|
|
144
|
+
(typeof canCloseCheck !== "function" && canCloseCheck.check()));
|
|
145
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FieldState } from "@homebound/form-state/dist/formState";
|
|
2
2
|
import { MultiSelectFieldProps, Value } from "../inputs";
|
|
3
3
|
import { HasIdAndName, Optional } from "../types";
|
|
4
|
-
export declare type BoundMultiSelectFieldProps<O, V extends Value> = Omit<MultiSelectFieldProps<O, V>, "values" | "onSelect" | "onBlur" | "onFocus"> & {
|
|
4
|
+
export declare type BoundMultiSelectFieldProps<O, V extends Value> = Omit<MultiSelectFieldProps<O, V>, "values" | "onSelect" | "onBlur" | "onFocus" | "label"> & {
|
|
5
5
|
onSelect?: (values: V[], opts: O[]) => void;
|
|
6
6
|
field: FieldState<any, V[] | null | undefined>;
|
|
7
|
+
label?: string;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* Wraps `MultiSelectField` and binds it to a form field.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FieldState } from "@homebound/form-state/dist/formState";
|
|
2
2
|
import { SelectFieldProps, Value } from "../inputs";
|
|
3
3
|
import { HasIdAndName, Optional } from "../types";
|
|
4
|
-
export declare type BoundSelectFieldProps<T, V extends Value> = Omit<SelectFieldProps<T, V>, "value" | "onSelect" | "onBlur" | "onFocus"> & {
|
|
4
|
+
export declare type BoundSelectFieldProps<T, V extends Value> = Omit<SelectFieldProps<T, V>, "value" | "onSelect" | "onBlur" | "onFocus" | "label"> & {
|
|
5
5
|
onSelect?: (option: V | undefined) => void;
|
|
6
6
|
field: FieldState<any, V | null | undefined>;
|
|
7
|
+
label?: string;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* Wraps `SelectField` and binds it to a form field.
|
|
@@ -18,5 +18,8 @@ export interface DateFieldProps {
|
|
|
18
18
|
/** Renders the label inside the input field, i.e. for filters. */
|
|
19
19
|
inlineLabel?: boolean;
|
|
20
20
|
placeholder?: string;
|
|
21
|
+
/** If the field should be rendered without a border - This could happen if rendering within a table or as part of a CompoundField */
|
|
22
|
+
borderless?: boolean;
|
|
23
|
+
compact?: boolean;
|
|
21
24
|
}
|
|
22
25
|
export declare function DateField(props: DateFieldProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
package/dist/inputs/DateField.js
CHANGED
|
@@ -23,16 +23,17 @@ function DateField(props) {
|
|
|
23
23
|
const inputWrapRef = (0, react_1.useRef)(null);
|
|
24
24
|
const buttonRef = (0, react_1.useRef)(null);
|
|
25
25
|
const overlayRef = (0, react_1.useRef)(null);
|
|
26
|
-
const
|
|
26
|
+
const showLongFormat = long && readOnly;
|
|
27
|
+
const [inputValue, setInputValue] = (0, react_1.useState)(value ? (showLongFormat ? (0, date_fns_1.format)(value, longFormat) : formatDate(value)) : "");
|
|
27
28
|
const tid = (0, utils_1.useTestIds)(props, (0, defaultTestId_1.defaultTestId)(label));
|
|
28
29
|
(0, react_1.useEffect)(() => {
|
|
29
|
-
setInputValue(value ? (
|
|
30
|
+
setInputValue(value ? (showLongFormat ? (0, date_fns_1.format)(value, longFormat) : formatDate(value)) : "");
|
|
30
31
|
}, [value]);
|
|
31
32
|
const textFieldProps = {
|
|
32
33
|
...others,
|
|
33
34
|
label,
|
|
34
35
|
isDisabled: disabled,
|
|
35
|
-
isReadOnly:
|
|
36
|
+
isReadOnly: readOnly,
|
|
36
37
|
"aria-haspopup": "dialog",
|
|
37
38
|
value: inputValue,
|
|
38
39
|
};
|
|
@@ -77,7 +78,10 @@ function DateField(props) {
|
|
|
77
78
|
placement: "bottom left",
|
|
78
79
|
shouldUpdatePosition: true,
|
|
79
80
|
});
|
|
80
|
-
|
|
81
|
+
// If showing the long format of the date, then leave size undefined. Otherwise we're showing it as "01/01/20", so set size to 8.
|
|
82
|
+
// (Setting the size 8 will currently only impact view of the DateField when placed on the "in page" filters. This is due to other styles set within TextFieldBase)
|
|
83
|
+
const inputSize = showLongFormat ? undefined : 8;
|
|
84
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, Object.assign({}, textFieldProps, { readOnly: readOnly, errorMsg: errorMsg, helperText: helperText, required: required, labelProps: labelProps, inputProps: { ...triggerProps, ...inputProps, size: inputSize }, inputRef: inputRef, inputWrapRef: inputWrapRef, inlineLabel: inlineLabel, onChange: (v) => {
|
|
81
85
|
// hide the calendar if the user is manually entering the date
|
|
82
86
|
state.close();
|
|
83
87
|
if (v) {
|
|
@@ -2,7 +2,7 @@ import type { NumberFieldAria } from "@react-aria/numberfield";
|
|
|
2
2
|
import { InputHTMLAttributes, LabelHTMLAttributes, MutableRefObject, ReactNode, TextareaHTMLAttributes } from "react";
|
|
3
3
|
import { Xss } from "../Css";
|
|
4
4
|
import { BeamTextFieldProps } from "../interfaces";
|
|
5
|
-
interface TextFieldBaseProps extends Pick<BeamTextFieldProps, "label" | "required" | "readOnly" | "errorMsg" | "onBlur" | "onFocus" | "helperText" | "hideLabel" | "placeholder">, Partial<Pick<BeamTextFieldProps, "onChange">> {
|
|
5
|
+
interface TextFieldBaseProps extends Pick<BeamTextFieldProps, "label" | "required" | "readOnly" | "errorMsg" | "onBlur" | "onFocus" | "helperText" | "hideLabel" | "placeholder" | "borderless">, Partial<Pick<BeamTextFieldProps, "onChange">> {
|
|
6
6
|
labelProps?: LabelHTMLAttributes<HTMLLabelElement>;
|
|
7
7
|
inputProps: InputHTMLAttributes<HTMLInputElement> | TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
8
8
|
inputRef?: MutableRefObject<HTMLInputElement | HTMLTextAreaElement | null>;
|
|
@@ -12,9 +12,11 @@ interface TextFieldBaseProps extends Pick<BeamTextFieldProps, "label" | "require
|
|
|
12
12
|
/** TextField specific */
|
|
13
13
|
compact?: boolean;
|
|
14
14
|
/** Styles overrides */
|
|
15
|
-
xss?: Xss<"textAlign">;
|
|
15
|
+
xss?: Xss<"textAlign" | "fontWeight">;
|
|
16
16
|
endAdornment?: ReactNode;
|
|
17
|
+
startAdornment?: ReactNode;
|
|
17
18
|
inlineLabel?: boolean;
|
|
19
|
+
contrast?: boolean;
|
|
18
20
|
}
|
|
19
21
|
export declare function TextFieldBase(props: TextFieldBaseProps): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
20
22
|
export {};
|
|
@@ -14,13 +14,44 @@ const useTestIds_1 = require("../utils/useTestIds");
|
|
|
14
14
|
// Used by both TextField and TextArea
|
|
15
15
|
function TextFieldBase(props) {
|
|
16
16
|
var _a;
|
|
17
|
-
const { label, required, labelProps, hideLabel, inputProps, inputRef, inputWrapRef, groupProps, compact = false, errorMsg, helperText, multiline = false, readOnly, onChange, onBlur, onFocus, xss, endAdornment, inlineLabel, } = props;
|
|
17
|
+
const { label, required, labelProps, hideLabel, inputProps, inputRef, inputWrapRef, groupProps, compact = false, errorMsg, helperText, multiline = false, readOnly, onChange, onBlur, onFocus, xss, endAdornment, startAdornment, inlineLabel, borderless = false, contrast = false, } = props;
|
|
18
18
|
const errorMessageId = `${inputProps.id}-error`;
|
|
19
19
|
const labelSuffix = (0, labelUtils_1.getLabelSuffix)(required);
|
|
20
20
|
const ElementType = multiline ? "textarea" : "input";
|
|
21
|
-
const tid = (0, useTestIds_1.useTestIds)(props, (0, defaultTestId_1.defaultTestId)(label
|
|
21
|
+
const tid = (0, useTestIds_1.useTestIds)(props, (0, defaultTestId_1.defaultTestId)(label));
|
|
22
22
|
const [isFocused, setIsFocused] = (0, react_1.useState)(false);
|
|
23
|
+
const { hoverProps, isHovered } = (0, react_aria_1.useHover)({});
|
|
23
24
|
const { focusWithinProps } = (0, react_aria_1.useFocusWithin)({ onFocusWithinChange: setIsFocused });
|
|
25
|
+
const maybeSmaller = borderless ? 2 : 0;
|
|
26
|
+
const fieldHeight = 40;
|
|
27
|
+
const compactFieldHeight = 32;
|
|
28
|
+
const fieldStyles = {
|
|
29
|
+
container: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).$,
|
|
30
|
+
inputWrapper: {
|
|
31
|
+
...Css_1.Css.sm.df.aic.br4.px1.w100
|
|
32
|
+
.hPx(fieldHeight - maybeSmaller)
|
|
33
|
+
.if(compact)
|
|
34
|
+
.hPx(compactFieldHeight - maybeSmaller).$,
|
|
35
|
+
...Css_1.Css.bgWhite.bGray300.gray900.if(contrast).bgGray700.bGray700.white.$,
|
|
36
|
+
...(!borderless ? Css_1.Css.ba.$ : {}),
|
|
37
|
+
},
|
|
38
|
+
inputWrapperReadOnly: {
|
|
39
|
+
...Css_1.Css.sm.df.aic.w100
|
|
40
|
+
.mhPx(fieldHeight - maybeSmaller)
|
|
41
|
+
.if(compact)
|
|
42
|
+
.mhPx(compactFieldHeight - maybeSmaller).$,
|
|
43
|
+
...Css_1.Css.gray900.if(contrast).white.$,
|
|
44
|
+
},
|
|
45
|
+
input: {
|
|
46
|
+
...Css_1.Css.w100.mw0.outline0.br4.fg1.$,
|
|
47
|
+
// Not using Truss's inline `if` statement here because `addIn` properties do not respect the if statement.
|
|
48
|
+
...(!contrast ? Css_1.Css.bgWhite.$ : Css_1.Css.bgGray700.addIn("&::selection", Css_1.Css.bgGray800.$).$),
|
|
49
|
+
},
|
|
50
|
+
hover: Css_1.Css.bgGray100.if(contrast).bgGray600.bGray600.$,
|
|
51
|
+
focus: Css_1.Css.bLightBlue700.if(contrast).bLightBlue500.$,
|
|
52
|
+
disabled: Css_1.Css.cursorNotAllowed.gray400.bgGray100.if(contrast).gray500.bgGray700.$,
|
|
53
|
+
error: Css_1.Css.bRed600.if(contrast).bRed400.$,
|
|
54
|
+
};
|
|
24
55
|
// Watch for each WIP change, convert empty to undefined, and call the user's onChange
|
|
25
56
|
function onDomChange(e) {
|
|
26
57
|
if (onChange) {
|
|
@@ -34,25 +65,26 @@ function TextFieldBase(props) {
|
|
|
34
65
|
const onFocusChained = (0, react_aria_1.chain)((e) => {
|
|
35
66
|
e.target.select();
|
|
36
67
|
}, onFocus);
|
|
37
|
-
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css:
|
|
38
|
-
//
|
|
39
|
-
...
|
|
40
|
-
...Css_1.Css.
|
|
41
|
-
...(multiline ? Css_1.Css.fdc.aifs.childGap2.$ : Css_1.Css.add({ overflow: "hidden", whiteSpace: "nowrap" }).$),
|
|
68
|
+
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: fieldStyles.container }, groupProps, focusWithinProps, { children: [label && !inlineLabel && ((0, jsx_runtime_1.jsx)(Label_1.Label, Object.assign({ labelProps: labelProps, hidden: hideLabel, label: label, suffix: labelSuffix, contrast: contrast }, tid.label), void 0)), readOnly && ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
|
|
69
|
+
// Use input wrapper to get common styles, but then we need to override some
|
|
70
|
+
...fieldStyles.inputWrapperReadOnly,
|
|
71
|
+
...(multiline ? Css_1.Css.fdc.aifs.childGap2.$ : Css_1.Css.truncate.$),
|
|
42
72
|
...xss,
|
|
43
|
-
}
|
|
73
|
+
}, "data-readonly": "true" }, tid, { children: [!multiline && inlineLabel && label && !hideLabel && ((0, jsx_runtime_1.jsx)(Label_1.InlineLabel, Object.assign({ labelProps: labelProps, label: label }, tid.label), void 0)), multiline
|
|
44
74
|
? (_a = inputProps.value) === null || _a === void 0 ? void 0 : _a.split("\n\n").map((p, i) => ((0, jsx_runtime_1.jsx)("p", Object.assign({ css: Css_1.Css.my1.$ }, { children: p.split("\n").map((sentence, j) => ((0, jsx_runtime_1.jsxs)("span", { children: [sentence, (0, jsx_runtime_1.jsx)("br", {}, void 0)] }, j))) }), i)))
|
|
45
75
|
: inputProps.value] }), void 0)), !readOnly && ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: {
|
|
46
|
-
...
|
|
47
|
-
...(inputProps.disabled ?
|
|
48
|
-
...(isFocused ?
|
|
49
|
-
...(
|
|
76
|
+
...fieldStyles.inputWrapper,
|
|
77
|
+
...(inputProps.disabled ? fieldStyles.disabled : {}),
|
|
78
|
+
...(isFocused && !readOnly ? fieldStyles.focus : {}),
|
|
79
|
+
...(isHovered && !inputProps.disabled && !readOnly && !isFocused ? fieldStyles.hover : {}),
|
|
80
|
+
...(errorMsg ? fieldStyles.error : {}),
|
|
50
81
|
...Css_1.Css.if(multiline).aifs.px0.mh((0, Css_1.px)(96)).$,
|
|
51
|
-
}, ref: inputWrapRef }, { children: [!multiline && inlineLabel && label && !hideLabel && ((0, jsx_runtime_1.jsx)(Label_1.InlineLabel, Object.assign({ labelProps: labelProps, label: label }, tid.label), void 0)), (0, jsx_runtime_1.jsx)(ElementType, Object.assign({}, (0, react_aria_1.mergeProps)(inputProps, { onBlur, onFocus: onFocusChained, onChange: onDomChange }, { "aria-invalid": Boolean(errorMsg), ...(hideLabel ? { "aria-label": label } : {}) }), (errorMsg ? { "aria-errormessage": errorMessageId } : {}), { ref: inputRef, rows: multiline ? 1 : undefined, css: {
|
|
52
|
-
...
|
|
53
|
-
...(inputProps.disabled ?
|
|
82
|
+
} }, hoverProps, { ref: inputWrapRef }, { children: [!multiline && inlineLabel && label && !hideLabel && ((0, jsx_runtime_1.jsx)(Label_1.InlineLabel, Object.assign({ labelProps: labelProps, label: label }, tid.label), void 0)), !multiline && startAdornment && (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.df.aic.fs0.br4.pr1.$ }, { children: startAdornment }), void 0), (0, jsx_runtime_1.jsx)(ElementType, Object.assign({}, (0, react_aria_1.mergeProps)(inputProps, { onBlur, onFocus: onFocusChained, onChange: onDomChange }, { "aria-invalid": Boolean(errorMsg), ...(hideLabel ? { "aria-label": label } : {}) }), (errorMsg ? { "aria-errormessage": errorMessageId } : {}), { ref: inputRef, rows: multiline ? 1 : undefined, css: {
|
|
83
|
+
...fieldStyles.input,
|
|
84
|
+
...(inputProps.disabled ? fieldStyles.disabled : {}),
|
|
85
|
+
...(isHovered && !inputProps.disabled && !readOnly && !isFocused ? fieldStyles.hover : {}),
|
|
86
|
+
...(multiline ? Css_1.Css.h100.p1.add("resize", "none").$ : Css_1.Css.truncate.$),
|
|
54
87
|
...xss,
|
|
55
|
-
|
|
56
|
-
} }, tid), void 0), !multiline && endAdornment && (0, jsx_runtime_1.jsx)("span", { children: endAdornment }, void 0)] }), void 0)), errorMsg && (0, jsx_runtime_1.jsx)(ErrorMessage_1.ErrorMessage, Object.assign({ id: errorMessageId, errorMsg: errorMsg }, tid.errorMsg), void 0), helperText && (0, jsx_runtime_1.jsx)(HelperText_1.HelperText, Object.assign({ helperText: helperText }, tid.helperText), void 0)] }), void 0));
|
|
88
|
+
} }, tid), void 0), !multiline && endAdornment && (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.df.aic.pl1.fs0.$ }, { children: endAdornment }), void 0)] }), void 0)), errorMsg && (0, jsx_runtime_1.jsx)(ErrorMessage_1.ErrorMessage, Object.assign({ id: errorMessageId, errorMsg: errorMsg }, tid.errorMsg), void 0), helperText && (0, jsx_runtime_1.jsx)(HelperText_1.HelperText, Object.assign({ helperText: helperText }, tid.helperText), void 0)] }), void 0));
|
|
57
89
|
}
|
|
58
90
|
exports.TextFieldBase = TextFieldBase;
|
|
@@ -32,7 +32,7 @@ export interface BeamSelectFieldBaseProps<T, V extends Value> extends BeamFocusa
|
|
|
32
32
|
/** Allow placing an icon/decoration within the input field. */
|
|
33
33
|
fieldDecoration?: (opt: T) => ReactNode;
|
|
34
34
|
/** Sets the form field label. */
|
|
35
|
-
label
|
|
35
|
+
label: string;
|
|
36
36
|
hideLabel?: boolean;
|
|
37
37
|
/** Renders the label inside the input field, i.e. for filters. */
|
|
38
38
|
inlineLabel?: boolean;
|
|
@@ -44,4 +44,6 @@ export interface BeamSelectFieldBaseProps<T, V extends Value> extends BeamFocusa
|
|
|
44
44
|
nothingSelectedText?: string;
|
|
45
45
|
/** When set the SelectField is expected to be put on a darker background */
|
|
46
46
|
contrast?: boolean;
|
|
47
|
+
/** If the field should be rendered without a border - This could happen if rendering within a table or as part of a CompoundField */
|
|
48
|
+
borderless?: boolean;
|
|
47
49
|
}
|
|
@@ -21,7 +21,7 @@ const Value_1 = require("../Value");
|
|
|
21
21
|
*/
|
|
22
22
|
function SelectFieldBase(props) {
|
|
23
23
|
var _a;
|
|
24
|
-
const { compact = false, disabled: isDisabled = false, errorMsg, helperText, label, hideLabel, required, inlineLabel, readOnly: isReadOnly = false, onSelect, fieldDecoration, options, onBlur, onFocus, multiselect = false, getOptionLabel, getOptionValue, getOptionMenuLabel = getOptionLabel, sizeToContent = false, values, nothingSelectedText = "", contrast, disabledOptions, ...otherProps } = props;
|
|
24
|
+
const { compact = false, disabled: isDisabled = false, errorMsg, helperText, label, hideLabel, required, inlineLabel, readOnly: isReadOnly = false, onSelect, fieldDecoration, options, onBlur, onFocus, multiselect = false, getOptionLabel, getOptionValue, getOptionMenuLabel = getOptionLabel, sizeToContent = false, values, nothingSelectedText = "", contrast, disabledOptions, borderless, ...otherProps } = props;
|
|
25
25
|
const { contains } = (0, react_aria_1.useFilter)({ sensitivity: "base" });
|
|
26
26
|
function onSelectionChange(keys) {
|
|
27
27
|
// Close menu upon selection change only for Single selection mode
|
|
@@ -177,6 +177,6 @@ function SelectFieldBase(props) {
|
|
|
177
177
|
...positionProps.style,
|
|
178
178
|
width: (_a = comboBoxRef === null || comboBoxRef === void 0 ? void 0 : comboBoxRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth,
|
|
179
179
|
};
|
|
180
|
-
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).$, ref: comboBoxRef }, { children: [(0, jsx_runtime_1.jsx)(SelectFieldInput_1.SelectFieldInput, { buttonProps: buttonProps, buttonRef: triggerRef, compact: compact, errorMsg: errorMsg, helperText: helperText, fieldDecoration: fieldDecoration, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, isDisabled: isDisabled, required: required, isReadOnly: isReadOnly, state: state, onBlur: onBlur, onFocus: onFocus, inlineLabel: inlineLabel, label: label, hideLabel: hideLabel, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, sizeToContent: sizeToContent, contrast: contrast, nothingSelectedText: nothingSelectedText }, void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: triggerRef, popoverRef: popoverRef, positionProps: positionProps, onClose: () => state.close(), isOpen: state.isOpen }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, listBoxProps, { positionProps: positionProps, state: state, compact: compact, listBoxRef: listBoxRef, selectedOptions: fieldState.selectedOptions, getOptionLabel: getOptionLabel, getOptionValue: (o) => (0, Value_1.valueToKey)(getOptionValue(o)), contrast: contrast }), void 0) }), void 0))] }), void 0));
|
|
180
|
+
return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.fdc.w100.maxw((0, Css_1.px)(550)).$, ref: comboBoxRef }, { children: [(0, jsx_runtime_1.jsx)(SelectFieldInput_1.SelectFieldInput, Object.assign({}, otherProps, { buttonProps: buttonProps, buttonRef: triggerRef, compact: compact, errorMsg: errorMsg, helperText: helperText, fieldDecoration: fieldDecoration, inputProps: inputProps, inputRef: inputRef, inputWrapRef: inputWrapRef, isDisabled: isDisabled, required: required, isReadOnly: isReadOnly, state: state, onBlur: onBlur, onFocus: onFocus, inlineLabel: inlineLabel, label: label, hideLabel: hideLabel, labelProps: labelProps, selectedOptions: fieldState.selectedOptions, getOptionValue: getOptionValue, getOptionLabel: getOptionLabel, sizeToContent: sizeToContent, contrast: contrast, nothingSelectedText: nothingSelectedText, borderless: borderless }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: triggerRef, popoverRef: popoverRef, positionProps: positionProps, onClose: () => state.close(), isOpen: state.isOpen }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, listBoxProps, { positionProps: positionProps, state: state, compact: compact, listBoxRef: listBoxRef, selectedOptions: fieldState.selectedOptions, getOptionLabel: getOptionLabel, getOptionValue: (o) => (0, Value_1.valueToKey)(getOptionValue(o)), contrast: contrast }), void 0) }), void 0))] }), void 0));
|
|
181
181
|
}
|
|
182
182
|
exports.SelectFieldBase = SelectFieldBase;
|
|
@@ -19,7 +19,7 @@ interface SelectFieldInputProps<O, V extends Value> {
|
|
|
19
19
|
onFocus?: () => void;
|
|
20
20
|
inlineLabel?: boolean;
|
|
21
21
|
labelProps: LabelHTMLAttributes<HTMLLabelElement>;
|
|
22
|
-
label
|
|
22
|
+
label: string;
|
|
23
23
|
hideLabel?: boolean;
|
|
24
24
|
selectedOptions: O[];
|
|
25
25
|
getOptionValue: (opt: O) => V;
|
|
@@ -27,6 +27,8 @@ interface SelectFieldInputProps<O, V extends Value> {
|
|
|
27
27
|
sizeToContent: boolean;
|
|
28
28
|
contrast?: boolean;
|
|
29
29
|
nothingSelectedText: string;
|
|
30
|
+
/** If the field should be rendered without a border - This could happen if rendering within a table or as part of a CompoundField */
|
|
31
|
+
borderless?: boolean;
|
|
30
32
|
}
|
|
31
33
|
export declare function SelectFieldInput<O, V extends Value>(props: SelectFieldInputProps<O, V>): import("@emotion/react/jsx-runtime").JSX.Element;
|
|
32
34
|
export {};
|
|
@@ -5,121 +5,94 @@ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_aria_1 = require("react-aria");
|
|
7
7
|
const components_1 = require("../../components");
|
|
8
|
-
const HelperText_1 = require("../../components/HelperText");
|
|
9
|
-
const Label_1 = require("../../components/Label");
|
|
10
8
|
const Css_1 = require("../../Css");
|
|
11
|
-
const
|
|
12
|
-
const ErrorMessage_1 = require("../ErrorMessage");
|
|
9
|
+
const TextFieldBase_1 = require("../TextFieldBase");
|
|
13
10
|
const Value_1 = require("../Value");
|
|
14
11
|
const utils_1 = require("../../utils");
|
|
15
12
|
function SelectFieldInput(props) {
|
|
16
|
-
const { inputProps, inputRef, inputWrapRef, buttonProps, buttonRef, compact = false, errorMsg, required, helperText, state, fieldDecoration, isDisabled, isReadOnly, onBlur, onFocus, inlineLabel, label, labelProps, hideLabel, selectedOptions, getOptionValue, getOptionLabel, sizeToContent, contrast = false, nothingSelectedText, } = props;
|
|
17
|
-
const themeStyles = {
|
|
18
|
-
wrapper: Css_1.Css.bgWhite.bGray300.gray900.if(contrast).bgGray700.bGray700.white.$,
|
|
19
|
-
hover: Css_1.Css.bgGray100.if(contrast).bgGray600.bGray600.$,
|
|
20
|
-
focus: Css_1.Css.bLightBlue700.if(contrast).bLightBlue500.$,
|
|
21
|
-
// Not using Truss's inline `if` statement here because `addIn` properties are applied regardless.
|
|
22
|
-
input: !contrast ? Css_1.Css.bgWhite.$ : Css_1.Css.bgGray700.addIn("&::selection", Css_1.Css.bgGray800.$).$,
|
|
23
|
-
disabled: Css_1.Css.cursorNotAllowed.gray400.bgGray100.if(contrast).gray500.bgGray700.$,
|
|
24
|
-
error: Css_1.Css.bRed500.if(contrast).bRed400.$,
|
|
25
|
-
};
|
|
26
|
-
const errorMessageId = `${inputProps.id}-error`;
|
|
13
|
+
const { inputProps, inputRef, inputWrapRef, buttonProps, buttonRef, compact = false, errorMsg, required, helperText, state, fieldDecoration, isDisabled, isReadOnly, onBlur, onFocus, inlineLabel, label, labelProps, hideLabel, selectedOptions, getOptionValue, getOptionLabel, sizeToContent, contrast = false, nothingSelectedText, borderless, ...otherProps } = props;
|
|
27
14
|
const [isFocused, setIsFocused] = (0, react_1.useState)(false);
|
|
28
|
-
const { hoverProps, isHovered } = (0, react_aria_1.useHover)({});
|
|
29
|
-
const hoverStyles = isHovered && !isReadOnly && !isFocused ? themeStyles.hover : {};
|
|
30
|
-
const focusStyles = isFocused && !isReadOnly ? themeStyles.focus : {};
|
|
31
|
-
const errorStyles = errorMsg ? themeStyles.error : {};
|
|
32
|
-
const disabledStyles = isDisabled ? themeStyles.disabled : {};
|
|
33
|
-
const readOnlyStyles = isReadOnly ? Css_1.Css.bn.pl0.pt0.add("backgroundColor", "unset").$ : {};
|
|
34
|
-
const tid = (0, utils_1.useTestIds)(inputProps); // data-testid comes in through here
|
|
35
15
|
const isMultiSelect = state.selectionManager.selectionMode === "multiple";
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}, onKeyDown: (e) => {
|
|
56
|
-
// We need to do some custom logic when using MultiSelect, as react-aria/stately Combobox doesn't support multiselect out of the box.
|
|
57
|
-
if (isMultiSelect) {
|
|
58
|
-
// Enter should toggle the focused item.
|
|
59
|
-
if (e.key === "Enter") {
|
|
60
|
-
// Prevent form submissions if menu is open.
|
|
61
|
-
if (state.isOpen) {
|
|
62
|
-
e.preventDefault();
|
|
63
|
-
}
|
|
64
|
-
state.selectionManager.toggleSelection(state.selectionManager.focusedKey);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
// By default, the Escape key would "revert" changes,
|
|
68
|
-
// but we just want to close the menu and leave the selections as is
|
|
69
|
-
if (e.key === "Escape") {
|
|
70
|
-
state.close();
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
16
|
+
const showNumSelection = isMultiSelect && state.selectionManager.selectedKeys.size > 1;
|
|
17
|
+
// For MultiSelect only show the `fieldDecoration` when input is not in focus.
|
|
18
|
+
const showFieldDecoration = (!isMultiSelect || (isMultiSelect && !isFocused)) && fieldDecoration && selectedOptions.length === 1;
|
|
19
|
+
return ((0, jsx_runtime_1.jsx)(TextFieldBase_1.TextFieldBase, Object.assign({}, otherProps, { inputRef: inputRef, inputWrapRef: inputWrapRef, label: label, readOnly: isReadOnly, hideLabel: hideLabel, labelProps: labelProps, inlineLabel: inlineLabel, compact: compact, required: required, errorMsg: errorMsg, helperText: helperText, contrast: contrast, xss: !inlineLabel ? Css_1.Css.fw5.$ : {}, borderless: borderless, startAdornment: (showNumSelection && ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.wPx(16).hPx(16).fs0.br100.bgLightBlue700.white.tinyEm.df.aic.jcc.$ }, { children: state.selectionManager.selectedKeys.size }), void 0))) ||
|
|
20
|
+
(showFieldDecoration && fieldDecoration(selectedOptions[0])), endAdornment: !isReadOnly && ((0, jsx_runtime_1.jsx)("button", Object.assign({}, buttonProps, { disabled: isDisabled, ref: buttonRef, css: {
|
|
21
|
+
...Css_1.Css.br4.outline0.gray700.if(contrast).gray400.$,
|
|
22
|
+
...(isDisabled ? Css_1.Css.cursorNotAllowed.gray400.if(contrast).gray600.$ : {}),
|
|
23
|
+
} }, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: state.isOpen ? "chevronUp" : "chevronDown" }, void 0) }), void 0)), inputProps: {
|
|
24
|
+
...(0, react_aria_1.mergeProps)(inputProps, { "aria-invalid": Boolean(errorMsg), onInput: () => state.open() }),
|
|
25
|
+
// Not merging the following as we want them to overwrite existing events
|
|
26
|
+
...{
|
|
27
|
+
onKeyDown: (e) => {
|
|
28
|
+
// We need to do some custom logic when using MultiSelect, as react-aria/stately Combobox doesn't support multiselect out of the box.
|
|
29
|
+
if (isMultiSelect) {
|
|
30
|
+
// Enter should toggle the focused item.
|
|
31
|
+
if (e.key === "Enter") {
|
|
32
|
+
// Prevent form submissions if menu is open.
|
|
33
|
+
if (state.isOpen) {
|
|
34
|
+
e.preventDefault();
|
|
73
35
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
state.selectionManager.setSelectedKeys(selectedOptions.length > 0 ? [(0, Value_1.valueToKey)(getOptionValue(selectedOptions[0]))] : []);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
inputProps.onKeyDown && inputProps.onKeyDown(e);
|
|
84
|
-
}, onBlur: () => {
|
|
85
|
-
// We purposefully override onBlur here instead of using mergeProps, b/c inputProps.onBlur
|
|
86
|
-
// goes into useComboBox's onBlur, which calls setFocused(false), which in useComboBoxState
|
|
87
|
-
// detects a) there is no props.selectedKey (b/c we don't pass it), and b) there is an
|
|
88
|
-
// `inputValue`, so it thinks it needs to call `resetInputValue()`.
|
|
89
|
-
//
|
|
90
|
-
// I assume we don't pass `selectedKey` b/c we support multiple keys.
|
|
91
|
-
if (isReadOnly) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
(0, utils_1.maybeCall)(onBlur);
|
|
36
|
+
state.selectionManager.toggleSelection(state.selectionManager.focusedKey);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// By default, the Escape key would "revert" changes,
|
|
40
|
+
// but we just want to close the menu and leave the selections as is
|
|
41
|
+
if (e.key === "Escape") {
|
|
95
42
|
state.close();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Handle single selection Escape key press
|
|
47
|
+
// When a user hits `Escape`, then react-aria calls `state.revert`, which uses `state.selectedKey` to
|
|
48
|
+
// reset the field to its previous value. However, because we use a the Multiple Selection State manager,
|
|
49
|
+
// then our `state.selectedKey` isn't set. So we need to properly reset the state ourselves.
|
|
50
|
+
if (e.key === "Escape") {
|
|
51
|
+
// Triggering `Escape` is basically like re-selecting currently selected option, so do that if there is one.
|
|
52
|
+
state.selectionManager.setSelectedKeys(selectedOptions.length > 0 ? [(0, Value_1.valueToKey)(getOptionValue(selectedOptions[0]))] : []);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
inputProps.onKeyDown && inputProps.onKeyDown(e);
|
|
56
|
+
},
|
|
57
|
+
onBlur: () => {
|
|
58
|
+
// We purposefully override onBlur here instead of using mergeProps, b/c inputProps.onBlur
|
|
59
|
+
// goes into useComboBox's onBlur, which calls setFocused(false), which in useComboBoxState
|
|
60
|
+
// detects a) there is no props.selectedKey (b/c we don't pass it), and b) there is an
|
|
61
|
+
// `inputValue`, so it thinks it needs to call `resetInputValue()`.
|
|
62
|
+
//
|
|
63
|
+
// I assume we don't pass `selectedKey` b/c we support multiple keys.
|
|
64
|
+
if (isReadOnly) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setIsFocused(false);
|
|
68
|
+
(0, utils_1.maybeCall)(onBlur);
|
|
69
|
+
state.close();
|
|
70
|
+
// Always call `setSelectedKeys` onBlur with its existing selected keys..
|
|
71
|
+
// This ensures the field's `input.value` resets to what it should be in case it doesn't currently match.
|
|
72
|
+
state.selectionManager.setSelectedKeys(state.selectionManager.selectedKeys);
|
|
73
|
+
},
|
|
74
|
+
onFocus: () => {
|
|
75
|
+
if (isReadOnly)
|
|
76
|
+
return;
|
|
77
|
+
setIsFocused(true);
|
|
78
|
+
(0, utils_1.maybeCall)(onFocus);
|
|
79
|
+
state.open();
|
|
80
|
+
},
|
|
81
|
+
size:
|
|
82
|
+
// If sizeToContent, then, in order of precedence, base it of from:
|
|
83
|
+
// 1. input's value if any
|
|
84
|
+
// 2. If is MultiSelect and only one option is chosen, then use the length of that option to define the width to avoid size jumping on blur.
|
|
85
|
+
// 3. Use `nothingSelectedText`
|
|
86
|
+
// 4. Default to "1"
|
|
87
|
+
// And do not allow it to grow past a size of 20.
|
|
88
|
+
// TODO: Combine logic to determine the input's value. Similar logic is used in SelectFieldBase, though it is intertwined with other state logic. Such as when to open/close menu, or filter the options within that menu, etc...
|
|
89
|
+
sizeToContent
|
|
90
|
+
? Math.min(String(inputProps.value ||
|
|
91
|
+
(isMultiSelect && selectedOptions.length === 1 && getOptionLabel(selectedOptions[0])) ||
|
|
92
|
+
nothingSelectedText ||
|
|
93
|
+
"").length || 1, 20)
|
|
94
|
+
: undefined,
|
|
95
|
+
},
|
|
96
|
+
} }), void 0));
|
|
124
97
|
}
|
|
125
98
|
exports.SelectFieldInput = SelectFieldInput;
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -38,4 +38,6 @@ export interface BeamTextFieldProps extends BeamFocusableProps {
|
|
|
38
38
|
onFocus?: () => void;
|
|
39
39
|
readOnly?: boolean;
|
|
40
40
|
placeholder?: string;
|
|
41
|
+
/** If the field should be rendered without a border - This could happen if rendering within a table or as part of a CompoundField */
|
|
42
|
+
borderless?: boolean;
|
|
41
43
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -5,3 +5,8 @@ export declare type HasIdAndName<V = string> = {
|
|
|
5
5
|
export declare type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
|
6
6
|
export declare type Callback = () => void;
|
|
7
7
|
export declare type CheckFn = () => boolean;
|
|
8
|
+
export declare type CanCloseCheck = {
|
|
9
|
+
check: CheckFn;
|
|
10
|
+
discardText?: string;
|
|
11
|
+
continueText?: string;
|
|
12
|
+
} | CheckFn;
|