@homebound/beam 2.90.1 → 2.91.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.
@@ -6,23 +6,25 @@ const react_1 = require("react");
6
6
  const react_aria_1 = require("react-aria");
7
7
  const Icon_1 = require("./Icon");
8
8
  const Css_1 = require("../Css");
9
+ const utils_1 = require("../utils");
10
+ const defaultTestId_1 = require("../utils/defaultTestId");
9
11
  function ButtonGroup(props) {
10
12
  const { buttons, disabled = false, size = "sm" } = props;
11
- return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.mPx(4).$ }, { children: buttons.map(({ disabled: buttonDisabled, ...buttonProps }, i) => ((0, jsx_runtime_1.jsx)(GroupButton, Object.assign({}, buttonProps, {
12
- // Disable the button if the ButtonGroup is disabled or if the current
13
- // button is disabled.
14
- disabled: disabled || buttonDisabled,
15
- size,
16
- }), i))) }), void 0));
13
+ const tid = (0, utils_1.useTestIds)(props, "buttonGroup");
14
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.mPx(4).$ }, tid, { children: buttons.map(({ disabled: buttonDisabled, ...buttonProps }, i) => (
15
+ // Disable the button if the ButtonGroup is disabled or if the current button is disabled.
16
+ (0, jsx_runtime_1.jsx)(GroupButton, Object.assign({}, buttonProps, { disabled: disabled || buttonDisabled, size: size }, tid), i))) }), void 0));
17
17
  }
18
18
  exports.ButtonGroup = ButtonGroup;
19
19
  function GroupButton(props) {
20
+ var _a;
20
21
  const { icon, text, active, onClick: onPress, disabled, size, ...otherProps } = props;
21
22
  const ariaProps = { onPress, isDisabled: disabled, ...otherProps };
22
23
  const ref = (0, react_1.useRef)(null);
23
24
  const { buttonProps, isPressed } = (0, react_aria_1.useButton)(ariaProps, ref);
24
25
  const { isFocusVisible, focusProps } = (0, react_aria_1.useFocusRing)();
25
26
  const { hoverProps, isHovered } = (0, react_aria_1.useHover)(ariaProps);
27
+ const tid = (0, utils_1.useTestIds)(props);
26
28
  return ((0, jsx_runtime_1.jsxs)("button", Object.assign({ ref: ref }, buttonProps, focusProps, hoverProps, { css: {
27
29
  ...Css_1.Css.buttonBase.$,
28
30
  ...getButtonStyles(),
@@ -31,7 +33,7 @@ function GroupButton(props) {
31
33
  ...(active ? activeStyles : {}),
32
34
  ...(isPressed ? pressedStyles : isHovered ? hoverStyles : {}),
33
35
  ...(icon ? iconStyles[size] : {}),
34
- } }, { children: [icon && (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: icon }, void 0), text] }), void 0));
36
+ } }, tid[(0, defaultTestId_1.defaultTestId)((_a = text !== null && text !== void 0 ? text : icon) !== null && _a !== void 0 ? _a : "button")], { children: [icon && (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: icon }, void 0), text] }), void 0));
35
37
  }
36
38
  const pressedStyles = Css_1.Css.bgGray200.$;
37
39
  const activeStyles = Css_1.Css.bgGray300.$;
@@ -11,6 +11,6 @@ function Chip(props) {
11
11
  return ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: {
12
12
  ...Css_1.Css.dif.aic.br16.sm.pl1.px1.pyPx(2).gray900.bgGray200.$,
13
13
  ...xss,
14
- } }, tid, { title: text }, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.lineClamp1.$ }, { children: text }), void 0) }), void 0));
14
+ } }, tid, { title: text }, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.lineClamp1.breakAll.$ }, { children: text }), void 0) }), void 0));
15
15
  }
16
16
  exports.Chip = Chip;
@@ -79,20 +79,18 @@ function SuperDrawer() {
79
79
  // Preventing clicks from triggering parent onClick
80
80
  onClick: (e) => e.stopPropagation() }, { children: [(0, jsx_runtime_1.jsxs)("header", Object.assign({ css: Css_1.Css.df.p3.bb.bGray200.df.aic.jcsb.$ }, { children: [(0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.aic.$ }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ css: Css_1.Css.xl2Em.gray900.mr2.$ }, testId.title, { ref: drawerHeaderRef }, { children: !modalState.current && (title || null) }), void 0), !modalState.current && (titleLeftContent || null)] }), void 0), !modalState.current && (
81
81
  // Forcing height to 32px to match title height
82
- (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.childGap3.aic.hPx(32).$ }, { children: [titleRightContent || null, (0, jsx_runtime_1.jsx)(components_1.ButtonGroup, { buttons: [
82
+ (0, jsx_runtime_1.jsxs)("div", Object.assign({ css: Css_1.Css.df.childGap3.aic.hPx(32).$ }, { children: [titleRightContent || null, (0, jsx_runtime_1.jsx)(components_1.ButtonGroup, Object.assign({ buttons: [
83
83
  {
84
84
  icon: "chevronLeft",
85
85
  onClick: () => onPrevClick && onPrevClick(),
86
86
  disabled: !onPrevClick || isDetail,
87
- ...testId.prev,
88
87
  },
89
88
  {
90
89
  icon: "chevronRight",
91
90
  onClick: () => onNextClick && onNextClick(),
92
91
  disabled: !onNextClick || isDetail,
93
- ...testId.next,
94
92
  },
95
- ] }, void 0), (0, jsx_runtime_1.jsx)(components_1.IconButton, Object.assign({ icon: "x", onClick: closeDrawer }, testId.close), void 0)] }), void 0))] }), void 0), content, modalState.current && (
93
+ ] }, testId.headerActions), void 0), (0, jsx_runtime_1.jsx)(components_1.IconButton, Object.assign({ icon: "x", onClick: closeDrawer }, testId.close), void 0)] }), void 0))] }), void 0), content, modalState.current && (
96
94
  // Forcing some design constraints on the modal component
97
95
  (0, jsx_runtime_1.jsxs)("div", Object.assign({ css:
98
96
  // topPX(81) is the offset from the header
@@ -8,7 +8,7 @@ const utils_1 = require("../utils");
8
8
  function Tag({ text, type, xss, ...otherProps }) {
9
9
  const typeStyles = getStyles(type);
10
10
  const tid = (0, utils_1.useTestIds)(otherProps);
11
- return ((0, jsx_runtime_1.jsx)("span", Object.assign({}, tid, { css: { ...Css_1.Css.dib.tinyEm.ttu.px1.pyPx(4).gray900.br4.$, ...typeStyles, ...xss }, title: text }, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.lineClamp1.$ }, { children: text }), void 0) }), void 0));
11
+ return ((0, jsx_runtime_1.jsx)("span", Object.assign({}, tid, { css: { ...Css_1.Css.dib.tinyEm.ttu.px1.pyPx(4).gray900.br4.$, ...typeStyles, ...xss }, title: text }, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.lineClamp1.breakAll.$ }, { children: text }), void 0) }), void 0));
12
12
  }
13
13
  exports.Tag = Tag;
14
14
  function getStyles(type) {
@@ -18,6 +18,6 @@ function ToggleChip(props) {
18
18
  ":hover:not(:disabled)": Css_1.Css.bgGray300.$,
19
19
  ":disabled": Css_1.Css.cursorNotAllowed.$,
20
20
  ...xss,
21
- }, disabled: disabled, onClick: onClick }, tid, { children: [(0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.prPx(6).tl.lineClamp1.if(disabled).prPx(4).$, title: text }, { children: text }), void 0), !disabled && ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.fs0.br16.bgGray400.$ }, { children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: "x", color: Css_1.Palette.Gray700 }, void 0) }), void 0))] }), void 0));
21
+ }, disabled: disabled, onClick: onClick }, tid, { children: [(0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.prPx(6).tl.lineClamp1.breakAll.if(disabled).prPx(4).$, title: text }, { children: text }), void 0), !disabled && ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: Css_1.Css.fs0.br16.bgGray400.$ }, { children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { icon: "x", color: Css_1.Palette.Gray700 }, void 0) }), void 0))] }), void 0));
22
22
  }
23
23
  exports.ToggleChip = ToggleChip;
@@ -25,7 +25,8 @@ function ChipSelectField(props) {
25
25
  const isDisabled = !!disabled;
26
26
  const showClearButton = !disabled && clearable && !!value;
27
27
  const chipStyles = Css_1.Css[typeScale].tl.bgGray300.gray900.br16.pxPx(10).pyPx(2).$;
28
- const [isFocused, setIsFocused] = (0, react_1.useState)(false);
28
+ // Controls showing the focus border styles.
29
+ const [visualFocus, setVisualFocus] = (0, react_1.useState)(false);
29
30
  const [isClearFocused, setIsClearFocused] = (0, react_1.useState)(false);
30
31
  const { focusProps } = (0, react_aria_1.useFocus)({
31
32
  onFocus: (e) => {
@@ -35,10 +36,15 @@ function ChipSelectField(props) {
35
36
  }
36
37
  (0, utils_1.maybeCall)(onFocus);
37
38
  },
38
- // Do not call onBlur if we just opened the menu
39
- onBlur: () => !state.isOpen && (0, utils_1.maybeCall)(onBlur),
40
- // Do not change focus state if select menu is opened
41
- onFocusChange: (isFocused) => !state.isOpen && setIsFocused(isFocused),
39
+ onBlur: (e) => {
40
+ // Do not call onBlur if focus moved to within the Popover
41
+ if (popoverRef.current && popoverRef.current.contains(e.relatedTarget)) {
42
+ return;
43
+ }
44
+ (0, utils_1.maybeCall)(onBlur);
45
+ },
46
+ // Do not change visual focus state if select menu is opened
47
+ onFocusChange: (isFocused) => !state.isOpen && setVisualFocus(isFocused),
42
48
  });
43
49
  const { focusProps: clearFocusProps } = (0, react_aria_1.useFocus)({ onFocusChange: setIsClearFocused });
44
50
  const buttonRef = (0, react_1.useRef)(null);
@@ -65,19 +71,19 @@ function ChipSelectField(props) {
65
71
  const isPersistent = isPersistentItem(o);
66
72
  const value = isPersistent ? o.id : getOptionValue(o);
67
73
  const label = isPersistent ? o.name : getOptionLabel(o);
68
- return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: isPersistent ? (label) : ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: { ...Css_1.Css.lineClamp1.$, ...chipStyles }, title: label }, { children: label }), void 0)) }), value));
74
+ return ((0, jsx_runtime_1.jsx)(react_stately_1.Item, Object.assign({ textValue: label }, { children: isPersistent ? (label) : ((0, jsx_runtime_1.jsx)("span", Object.assign({ css: { ...Css_1.Css.lineClamp1.breakAll.$, ...chipStyles }, title: label }, { children: label }), void 0)) }), value));
69
75
  });
70
76
  const selectHookProps = {
71
77
  label,
72
78
  isDisabled,
73
79
  items: listData.items,
74
80
  children: selectChildren,
81
+ autoFocus: true,
75
82
  };
76
83
  const state = (0, react_stately_1.useSelectState)({
77
84
  ...selectHookProps,
78
- autoFocus: false,
79
85
  selectedKey: (0, Value_1.valueToKey)(value),
80
- disallowEmptySelection: true,
86
+ disallowEmptySelection: false,
81
87
  onSelectionChange: (key) => {
82
88
  if (key === createNewOpt.id) {
83
89
  setShowInput(true);
@@ -87,11 +93,21 @@ function ChipSelectField(props) {
87
93
  if (selectedItem) {
88
94
  onSelect(key, selectedItem);
89
95
  }
96
+ // Per UX, when an option is selected then we want to call our `onBlur` callback and remove the focus styles. The field _is_ still in focus but that is only to retain tab position in the DOM.
97
+ // We cannot simply call `buttonRef.current.blur()` here because `state.isOpen === true` and we keep the visualFocus shown when the menu is open.
98
+ setVisualFocus(false);
99
+ (0, utils_1.maybeCall)(onBlur);
90
100
  },
91
101
  onOpenChange: (isOpen) => {
92
- if (!isOpen && buttonRef.current) {
93
- // When closing reset the focus to the button element.
94
- buttonRef.current.focus();
102
+ var _a;
103
+ if (!isOpen) {
104
+ // When closing, reset the focus to the button element. This is to retain "tab position" in the document, allowing hte user to hit "Tab" and move to the next tabbable element.
105
+ // If the menu closed due to a user selecting an option, then the field will not visually appear focused.
106
+ (_a = buttonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
107
+ }
108
+ else {
109
+ // If opened, set visual focus to true. It is possible to be in a state where the browser focus is on this element, but we are not "visually" focused (see `onSelectionChange`). If the user opens the menu again, we should trigger the visual focus.
110
+ setVisualFocus(true);
95
111
  }
96
112
  },
97
113
  });
@@ -128,23 +144,21 @@ function ChipSelectField(props) {
128
144
  }, onBlur: removeCreateNewField }, tid.createNewField), void 0)), (0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: wrapperRef, css: {
129
145
  ...chipStyles,
130
146
  ...Css_1.Css.dif.relative.p0.mwPx(32).if(!value).bgGray200.$,
131
- ...(isFocused ? Css_1.Css.bshFocus.$ : {}),
147
+ ...(visualFocus ? Css_1.Css.bshFocus.$ : {}),
132
148
  ...(showInput ? Css_1.Css.dn.$ : {}),
133
149
  } }, { children: [(0, jsx_runtime_1.jsx)(Label_1.Label, Object.assign({ label: label, labelProps: labelProps, hidden: true }, tid.label), void 0), (0, jsx_runtime_1.jsx)("button", Object.assign({}, (0, react_aria_1.mergeProps)(focusProps, buttonProps), { ref: buttonRef, css: {
134
150
  ...Css_1.Css.tl.br16.pxPx(10).pyPx(2).outline0.if(showClearButton).prPx(4).borderRadius("16px 0 0 16px").$,
135
151
  ...(isDisabled ? Css_1.Css.cursorNotAllowed.gray700.$ : {}),
136
152
  "&:hover:not(:disabled)": Css_1.Css.bgGray400.if(!value).bgGray300.$,
137
- }, title: state.selectedItem ? state.selectedItem.textValue : placeholder }, tid, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({}, valueProps, { css: Css_1.Css.lineClamp1.$ }, { children: state.selectedItem ? state.selectedItem.textValue : placeholder }), void 0) }), void 0), showClearButton && (
138
- // Apply a tabIndex=-1 to remove need for addresses this focus behavior separately from the rest of the button.
139
- // This will require the user to click on the button if they want to remove it.
140
- (0, jsx_runtime_1.jsx)("button", Object.assign({}, clearFocusProps, { css: {
153
+ }, title: state.selectedItem ? state.selectedItem.textValue : placeholder }, tid, { children: (0, jsx_runtime_1.jsx)("span", Object.assign({}, valueProps, { css: Css_1.Css.lineClamp1.breakAll.$ }, { children: state.selectedItem ? state.selectedItem.textValue : placeholder }), void 0) }), void 0), showClearButton && ((0, jsx_runtime_1.jsx)("button", Object.assign({}, clearFocusProps, { css: {
141
154
  ...Css_1.Css.prPx(4).borderRadius("0 16px 16px 0").outline0.$,
142
155
  "&:hover": Css_1.Css.bgGray400.$,
143
156
  ...(isClearFocused ? Css_1.Css.boxShadow(`0px 0px 0px 2px rgba(3,105,161,1)`).$ : {}),
144
157
  }, onClick: () => {
145
158
  onSelect(undefined, undefined);
159
+ (0, utils_1.maybeCall)(onBlur);
146
160
  setIsClearFocused(false);
147
- }, "aria-label": "Remove" }, tid.clearButton, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "x", inc: typeScale === "xs" ? 2 : undefined }, void 0) }), void 0))] }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: buttonRef, popoverRef: popoverRef, positionProps: overlayProps, onClose: state.close, isOpen: state.isOpen, shouldCloseOnBlur: true }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, menuProps, { listBoxRef: listBoxRef, state: state, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, positionProps: overlayProps }), void 0) }), void 0))] }, void 0));
161
+ }, "aria-label": "Remove" }, tid.clearButton, { children: (0, jsx_runtime_1.jsx)(components_1.Icon, { icon: "x", inc: typeScale === "xs" ? 2 : undefined }, void 0) }), void 0))] }), void 0), state.isOpen && ((0, jsx_runtime_1.jsx)(internal_1.Popover, Object.assign({ triggerRef: buttonRef, popoverRef: popoverRef, positionProps: overlayProps, onClose: state.close, isOpen: state.isOpen, shouldCloseOnBlur: true }, { children: (0, jsx_runtime_1.jsx)(ListBox_1.ListBox, Object.assign({}, menuProps, { listBoxRef: listBoxRef, state: state, getOptionLabel: getOptionLabel, getOptionValue: getOptionValue, positionProps: overlayProps, positionOffset: 8 }), void 0) }), void 0))] }, void 0));
148
162
  const tooltipText = selectHookProps.isDisabled && typeof disabled !== "boolean" ? disabled : undefined;
149
163
  return tooltipText ? ((0, jsx_runtime_1.jsx)(components_1.Tooltip, Object.assign({ title: tooltipText, placement: "top" }, { children: field }), void 0)) : (field);
150
164
  }
@@ -75,6 +75,13 @@ function DateField(props) {
75
75
  state.close();
76
76
  (0, utils_1.maybeCall)(onBlur);
77
77
  },
78
+ onKeyDown: (e) => {
79
+ var _a;
80
+ if (e.key === "Enter") {
81
+ // Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
82
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
83
+ }
84
+ },
78
85
  }, inputRef);
79
86
  const { triggerProps, overlayProps } = (0, react_aria_1.useOverlayTrigger)({ type: "dialog" }, state, buttonRef);
80
87
  const { buttonProps } = (0, react_aria_1.useButton)({
@@ -47,6 +47,13 @@ function NumberField(props) {
47
47
  onBlur: () => {
48
48
  valueRef.current = { wip: false };
49
49
  },
50
+ onKeyDown: (e) => {
51
+ var _a;
52
+ if (e.key === "Enter") {
53
+ // Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
54
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
55
+ }
56
+ },
50
57
  validationState: errorMsg !== undefined ? "invalid" : "valid",
51
58
  label: label,
52
59
  isDisabled: disabled,
@@ -43,9 +43,12 @@ function TextAreaField(props) {
43
43
  ...(preventNewLines
44
44
  ? {
45
45
  onKeyDown: (e) => {
46
+ var _a;
46
47
  // Prevent user from typing the new line character
47
- if (e.keyCode === 13) {
48
+ if (e.key === "Enter") {
48
49
  e.preventDefault();
50
+ // And then leave the field
51
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
49
52
  }
50
53
  },
51
54
  onInput: (e) => {
@@ -16,7 +16,16 @@ function TextField(props) {
16
16
  value,
17
17
  };
18
18
  const inputRef = (0, react_1.useRef)(null);
19
- const { labelProps, inputProps } = (0, react_aria_1.useTextField)(textFieldProps, inputRef);
19
+ const { labelProps, inputProps } = (0, react_aria_1.useTextField)({
20
+ ...textFieldProps,
21
+ onKeyDown: (e) => {
22
+ var _a;
23
+ if (e.key === "Enter") {
24
+ // Blur the field when the user hits the enter key - as if they are "committing" the value and done with the field
25
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
26
+ }
27
+ },
28
+ }, inputRef);
20
29
  // Construct our TextFieldApi to give access to some imperative methods
21
30
  if (api) {
22
31
  api.current = {
@@ -24,6 +24,7 @@ function SelectFieldBase(props) {
24
24
  const { compact, 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
+ var _a;
27
28
  // Close menu upon selection change only for Single selection mode
28
29
  if (!multiselect) {
29
30
  state.close();
@@ -70,6 +71,8 @@ function SelectFieldBase(props) {
70
71
  selectedKeys: [firstKey],
71
72
  selectedOptions: firstSelectedOption ? [firstSelectedOption] : [],
72
73
  });
74
+ // When a single select menu item changes, then blur the field
75
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
73
76
  }
74
77
  selectionChanged && onSelect([...keys.values()].map(Value_1.keyToValue));
75
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/beam",
3
- "version": "2.90.1",
3
+ "version": "2.91.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",