@spaced-out/ui-design-system
Version:
Sense UI components library
343 lines (340 loc) • 13.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DEFAULT_LIMIT_VALUE = void 0;
exports.TokenListInput = TokenListInput;
var React = _interopRequireWildcard(require("react"));
var _react2 = require("@floating-ui/react");
var _without = _interopRequireDefault(require("lodash/without"));
var _hooks = require("../../hooks");
var _space = require("../../styles/variables/_space");
var _typography = require("../../types/typography");
var _classify = _interopRequireDefault(require("../../utils/classify"));
var _clickAway = require("../../utils/click-away");
var _mergeRefs = require("../../utils/merge-refs");
var _qa = require("../../utils/qa");
var _tokenListInput = require("../../utils/token-list-input/token-list-input");
var _ButtonDropdown = require("../ButtonDropdown");
var _CircularLoader = require("../CircularLoader");
var _Icon = require("../Icon");
var _Menu = require("../Menu");
var _Text = require("../Text");
var _TokenValueChips = require("./TokenValueChips");
var _Tooltip = require("../Tooltip");
var _TokenListInputModule = _interopRequireDefault(require("./TokenListInput.module.css"));
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
// Type definition for token objects with expected properties
const DEFAULT_LIMIT_VALUE = exports.DEFAULT_LIMIT_VALUE = 100;
function TokenListInput(props) {
const {
classNames,
clickAwayRef,
disabled = false,
error,
errorText,
focusOnMount,
helperText,
inputValue = '',
inputPlaceholder = '',
limit = DEFAULT_LIMIT_VALUE,
isLoading,
locked,
onChange,
menu,
onMenuOpen,
onMenuClose,
onInputBlur,
onInputChange,
onInputFocus,
placeholder,
size = 'medium',
tabIndex,
values,
resolveTokenValue,
inputProps,
elevation = 'modal',
testId,
inputRef: inputRefProp,
actionsSlot = null
} = props;
const menuRef = React.useRef(null);
const {
x,
y,
refs,
strategy,
context
} = (0, _react2.useFloating)({
open: true,
strategy: _ButtonDropdown.STRATEGY_TYPE.absolute,
placement: _ButtonDropdown.ANCHOR_POSITION_TYPE.bottomStart,
whileElementsMounted: _react2.autoUpdate,
middleware: [(0, _react2.flip)(), (0, _react2.offset)(parseInt(_space.spaceXXSmall))]
});
const inputRef = React.useRef(null);
const dropdownWidth = (0, _hooks.useReferenceElementWidth)(refs.reference?.current);
const onOpenToggle = isOpen => {
if (isOpen) {
onMenuOpen?.();
inputRef.current?.focus();
} else {
onMenuClose?.();
inputRef.current?.blur();
}
};
React.useEffect(() => {
if (focusOnMount) {
inputRef.current?.focus();
}
}, [focusOnMount]);
const addValue = value => {
// Type assertion to access expected properties
const valueWithProps = value;
if (locked || !value || valueWithProps.disabled) {
return; // Prevent adding values when disabled or locked
}
onInputChange?.('');
const existingToken = values.find(token => {
const tokenWithProps = token;
return tokenWithProps.key === valueWithProps.key;
});
if (!existingToken) {
onChange([...values, value]);
}
setTimeout(() => {
inputRef?.current?.focus();
}, 0);
};
const removeValue = value => {
if (!disabled && !locked) {
// prevent removing values when disabled or locked
onChange((0, _without.default)(values, value));
}
setTimeout(() => {
inputRef?.current?.focus();
}, 0);
};
const hideInput = values.length >= limit || disabled || locked;
const handleInputKeyDown = event => {
const value = event.currentTarget.value;
const key = event.key;
// Note: adding this Enter key handler to handle the case where the user is typing a new value
// and presses Enter to add the value to the list (maintain parity with the old TokenListInput)
if (key === 'Enter') {
if (value.trim()) {
event.preventDefault();
let firstOption = null;
if (menu?.options && menu.options.length > 0) {
firstOption = (0, _tokenListInput.getFirstOption)(menu.options);
} else if (menu?.groupTitleOptions && menu.groupTitleOptions.length > 0) {
firstOption = (0, _tokenListInput.getFirstOptionFromGroup)(menu.groupTitleOptions);
}
if (firstOption) {
addValue(firstOption);
}
}
} else if (key === 'Backspace') {
if (inputValue === '' && values.length > 0) {
onChange(values.slice(0, -1));
}
} else if (key === 'Escape') {
event.preventDefault();
setTimeout(() => {
inputRef?.current?.blur();
}, 0);
}
};
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_clickAway.ClickAway, {
closeOnEscapeKeypress: true,
onChange: onOpenToggle,
clickAwayRef: clickAwayRef,
children: _ref => {
let {
isOpen,
onOpen,
clickAway,
boundaryRef,
triggerRef
} = _ref;
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
ref: menuRef,
className: (0, _classify.default)(_TokenListInputModule.default.tokenListContainer, classNames?.wrapper),
"data-testid": (0, _qa.generateTestId)({
base: testId,
slot: 'root'
}),
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
ref: (0, _mergeRefs.mergeRefs)([refs.setReference, triggerRef]),
className: _TokenListInputModule.default.tokenListInputWrapper,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
onClick: () => {
if (disabled || locked || values.length >= limit) {
return;
}
if (!isOpen) {
onOpen();
}
},
onFocus: e => {
if (disabled || locked || values.length >= limit) {
return;
}
if (!isOpen) {
onOpen();
}
onInputFocus?.(e);
},
onBlur: e => {
onInputBlur?.(e);
},
className: (0, _classify.default)(_TokenListInputModule.default.box, {
[_TokenListInputModule.default.inputDisabled]: disabled,
[_TokenListInputModule.default.medium]: size === 'medium',
[_TokenListInputModule.default.small]: size === 'small',
[_TokenListInputModule.default.withError]: error,
[_TokenListInputModule.default.inputLocked]: locked
}, classNames?.box),
"data-testid": (0, _qa.generateTestId)({
base: testId,
slot: 'box'
}),
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_TokenValueChips.TokenValueChips, {
values: values,
resolveTokenValue: resolveTokenValue,
disabled: disabled,
locked: locked,
onDismiss: removeValue,
testId: (0, _qa.generateTestId)({
base: testId,
slot: 'chips'
})
}), !hideInput && /*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
...inputProps,
ref: (0, _mergeRefs.mergeRefs)([inputRef, inputRefProp]),
type: "text",
readOnly: locked,
value: inputValue,
placeholder: values.length === 0 ? placeholder || inputPlaceholder : inputPlaceholder,
onChange: event => {
onInputChange?.(event.target.value);
if (!isOpen) {
onOpen();
}
},
disabled: disabled || locked || inputProps?.disabled,
tabIndex: tabIndex,
"data-qa-id": "token-list-input",
onKeyDown: handleInputKeyDown,
className: (0, _classify.default)({
[_TokenListInputModule.default.inputMedium]: size === 'medium',
[_TokenListInputModule.default.inputSmall]: size === 'small'
}, classNames?.input),
autoComplete: "off",
"data-testid": (0, _qa.generateTestId)({
base: testId,
slot: 'input'
})
}), isLoading && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: _TokenListInputModule.default.loaderContainer,
"data-testid": (0, _qa.generateTestId)({
base: testId,
slot: 'loader'
}),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_CircularLoader.CircularLoader, {
size: "small",
colorToken: "colorFillPrimary",
testId: (0, _qa.generateTestId)({
base: testId,
slot: 'loader-icon'
})
})
}), locked && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.Icon, {
name: "lock",
color: disabled ? 'disabled' : 'secondary',
size: "small",
className: _TokenListInputModule.default.lockIcon,
testId: (0, _qa.generateTestId)({
base: testId,
slot: 'lock-icon'
})
})]
}), actionsSlot ? actionsSlot : null]
}), !isOpen && (Boolean(helperText) || errorText) && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: _TokenListInputModule.default.footerTextContainer,
"data-testid": (0, _qa.generateTestId)({
base: testId,
slot: 'footer'
}),
children: error && errorText ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.BodySmall, {
color: _typography.TEXT_COLORS.danger,
testId: (0, _qa.generateTestId)({
base: testId,
slot: 'error'
}),
children: errorText
}) : typeof helperText === 'string' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Text.BodySmall, {
color: _typography.TEXT_COLORS.secondary,
testId: (0, _qa.generateTestId)({
base: testId,
slot: 'helper'
}),
children: helperText
}) : helperText
}), !locked && isOpen && menu && /*#__PURE__*/(0, _jsxRuntime.jsx)(_react2.FloatingPortal, {
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_react2.FloatingFocusManager, {
modal: false,
context: context,
returnFocus: false,
initialFocus: refs.reference,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
className: _TokenListInputModule.default.menuWrapper,
ref: (0, _mergeRefs.mergeRefs)([refs.setFloating, boundaryRef]),
"data-testid": (0, _qa.generateTestId)({
base: testId,
slot: 'menu-wrapper'
}),
style: {
position: strategy,
top: y ?? _space.spaceNone,
left: x ?? _space.spaceNone,
/* NOTE(Sharad): The FloatingPortal renders the menu outside the normal DOM structure,
so its parent is effectively the <body> element. This means the menu
would otherwise default to the body's width. To support fluid width,
we must manually set the dropdown width here; otherwise, it uses a fixed width.
Also, Only treat menu as non-fluid if isFluid is strictly false, since default is true in menu and undefined means fluid. */
...(menu.isFluid !== false && {
'--dropdown-width': dropdownWidth
}),
'--menu-elevation': (0, _Tooltip.getElevationValue)(elevation)
},
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Menu.Menu, {
...menu,
onSelect: option => {
if (values.length >= limit) {
return;
}
addValue(option);
clickAway();
inputRef.current?.focus();
},
size: menu.size || size,
onTabOut: clickAway,
ref: menuRef,
testId: (0, _qa.generateTestId)({
base: testId,
slot: 'menu'
})
})
})
})
})]
})
});
}
});
}
TokenListInput.displayName = 'TokenListInput';