lucid-ui
Version:
A UI component library from Xandr.
391 lines • 23.5 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SearchableSelectDumb = void 0;
var react_1 = __importDefault(require("react"));
var prop_types_1 = __importDefault(require("prop-types"));
var lodash_1 = __importDefault(require("lodash"));
var component_types_1 = require("../../util/component-types");
var style_helpers_1 = require("../../util/style-helpers");
var text_manipulation_1 = require("../../util/text-manipulation");
var state_management_1 = require("../../util/state-management");
var reducers = __importStar(require("./SearchableSelect.reducers"));
var ChevronIcon_1 = __importDefault(require("../Icon/ChevronIcon/ChevronIcon"));
var DropMenu_1 = require("../DropMenu/DropMenu");
var LoadingIcon_1 = __importDefault(require("../Icon/LoadingIcon/LoadingIcon"));
var SearchField_1 = require("../SearchField/SearchField");
var Validation_1 = require("../Validation/Validation");
var cx = style_helpers_1.lucidClassNames.bind('&-SearchableSelect');
var any = prop_types_1.default.any, bool = prop_types_1.default.bool, func = prop_types_1.default.func, node = prop_types_1.default.node, number = prop_types_1.default.number, object = prop_types_1.default.object, shape = prop_types_1.default.shape, string = prop_types_1.default.string, oneOfType = prop_types_1.default.oneOfType;
var Placeholder = function (_props) { return null; };
Placeholder.displayName = 'SearchableSelect.Placeholder';
Placeholder.peek = {
description: "The content rendered in the control when there is no option is selected. Also rendered in the option list to remove current selection.",
};
Placeholder.propName = 'Placeholder';
Placeholder.propTypes = {};
/** OptionGroup Child Component */
var OptionGroup = function (_props) { return null; };
OptionGroup.displayName = 'SearchableSelect.OptionGroup';
OptionGroup.peek = {
description: "A special kind of `Option` that is always rendered at the top of the menu and has an `optionIndex` of `null`. Useful for unselect.",
};
OptionGroup.propName = 'OptionGroup';
OptionGroup.propTypes = DropMenu_1.DropMenuDumb.OptionGroup.propTypes;
OptionGroup.defaultProps = DropMenu_1.DropMenuDumb.OptionGroup.defaultProps;
/** Option.Selected Child Component */
var Selected = function (_props) { return null; };
Selected.displayName = 'SearchableSelect.Option.Selected';
Selected.peek = {
description: "Customizes the rendering of the `Option` when it is selected and is displayed instead of the `Placeholder`.",
};
Selected.propName = 'Selected';
Selected.propTypes = {};
/** Option Child Component */
var Option = function (_props) { return null; };
Option.displayName = 'SearchableSelect.Option';
Option.peek = {
description: "A selectable option in the list.",
};
Option.Selected = Selected;
Option.propName = 'Option';
Option.propTypes = __assign({
/**
Customizes the rendering of the Option when it is selected and is
displayed instead of the Placeholder.
*/
Selected: any, value: string, filterText: string }, DropMenu_1.DropMenuDumb.Option.propTypes);
Option.defaultProps = DropMenu_1.DropMenuDumb.Option.defaultProps;
var defaultProps = {
hasReset: true,
isSelectionHighlighted: true,
isDisabled: false,
isInvisible: false,
isLoading: false,
optionFilter: text_manipulation_1.propsSearch,
searchText: '',
selectedIndex: null,
DropMenu: DropMenu_1.DropMenuDumb.defaultProps,
Error: null,
onSearch: lodash_1.default.noop,
onSelect: lodash_1.default.noop,
};
/** SearchableSelect Component */
var SearchableSelect = /** @class */ (function (_super) {
__extends(SearchableSelect, _super);
function SearchableSelect() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.UNSAFE_componentWillReceiveProps = function (nextProps) {
// only preprocess options data when it changes (via new props) - better performance than doing this each render
var _a = DropMenu_1.DropMenuDumb.preprocessOptionData(nextProps, SearchableSelect), optionGroups = _a.optionGroups, flattenedOptionsData = _a.flattenedOptionsData, ungroupedOptionData = _a.ungroupedOptionData, optionGroupDataLookup = _a.optionGroupDataLookup;
_this.setState({
optionGroups: optionGroups,
flattenedOptionsData: flattenedOptionsData,
ungroupedOptionData: ungroupedOptionData,
optionGroupDataLookup: optionGroupDataLookup,
});
};
_this.handleSearch = function (searchText) {
var _a = _this.props, onSearch = _a.onSearch, optionFilter = _a.optionFilter;
var flattenedOptionsData = _this.state.flattenedOptionsData;
var firstVisibleIndex = lodash_1.default.get(lodash_1.default.find(flattenedOptionsData, function (_a) {
var optionProps = _a.optionProps;
return optionFilter(searchText, optionProps);
}), 'optionIndex');
onSearch(searchText, firstVisibleIndex);
};
_this.handleExpand = function (_a) {
var props = _a.props, event = _a.event;
var dropMenuProps = _this.props.DropMenu;
dropMenuProps.onExpand && dropMenuProps.onExpand({ event: event, props: props });
_this.setState({ isFocusOnSearchFieldRequired: true });
};
_this.setSearchField = function (e) {
if (e && _this.state.isFocusOnSearchFieldRequired) {
_this.setState({ isFocusOnSearchFieldRequired: false });
// use setTimeout to prevent scroll from safari
setTimeout(function () {
e.focus({ preventScroll: true });
}, 0);
}
};
_this.renderUnderlinedChildren = function (childText, searchText) {
var _a = (0, text_manipulation_1.partitionText)(childText, new RegExp(lodash_1.default.escapeRegExp(searchText), 'i'), searchText.length), pre = _a[0], match = _a[1], post = _a[2];
return [
pre && (react_1.default.createElement("span", { key: 'pre', className: cx('&-Option-underline-pre') }, pre)),
match && (react_1.default.createElement("span", { key: 'match', className: cx('&-Option-underline-match') }, match)),
post && (react_1.default.createElement("span", { key: 'post', className: cx('&-Option-underline-post') }, post)),
];
};
_this.renderOption = function (optionProps, optionIndex) {
var _a = _this.props, isLoading = _a.isLoading, optionFilter = _a.optionFilter, searchText = _a.searchText;
if (searchText) {
return (react_1.default.createElement(DropMenu_1.DropMenuDumb.Option, __assign({ isDisabled: isLoading }, lodash_1.default.omit(optionProps, ['children', 'Selected', 'filterText']), { isHidden: !optionFilter(searchText, optionProps), key: 'SearchableSelectOption' + optionIndex }), lodash_1.default.isString(optionProps.children)
? _this.renderUnderlinedChildren(optionProps.children, searchText)
: lodash_1.default.isFunction(optionProps.children)
? react_1.default.createElement(optionProps.children, { searchText: searchText })
: optionProps.children));
}
return (react_1.default.createElement(DropMenu_1.DropMenuDumb.Option, __assign({ key: 'SearchableSelectOption' + optionIndex }, lodash_1.default.omit(optionProps, ['children', 'Selected', 'filterText']), { isDisabled: optionProps.isDisabled || isLoading }), lodash_1.default.isFunction(optionProps.children)
? react_1.default.createElement(optionProps.children, { searchText: searchText })
: optionProps.children));
};
return _this;
}
SearchableSelect.prototype.UNSAFE_componentWillMount = function () {
// preprocess the options data before rendering
var _a = DropMenu_1.DropMenuDumb.preprocessOptionData(this.props, SearchableSelect), optionGroups = _a.optionGroups, flattenedOptionsData = _a.flattenedOptionsData, ungroupedOptionData = _a.ungroupedOptionData, optionGroupDataLookup = _a.optionGroupDataLookup;
this.setState({
optionGroups: optionGroups,
flattenedOptionsData: flattenedOptionsData,
ungroupedOptionData: ungroupedOptionData,
optionGroupDataLookup: optionGroupDataLookup,
});
};
SearchableSelect.prototype.renderOptions = function () {
var _this = this;
var searchText = this.props.searchText;
var _a = this.state, optionGroups = _a.optionGroups, optionGroupDataLookup = _a.optionGroupDataLookup, ungroupedOptionData = _a.ungroupedOptionData;
// for each option group passed in, render a DropMenu.OptionGroup, any
// label will be included in it's children, render each option inside the
// group
var options = lodash_1.default.map(optionGroups, function (optionGroupProps, optionGroupIndex) {
var childOptions = lodash_1.default.map(lodash_1.default.get(optionGroupDataLookup, optionGroupIndex), function (_a) {
var optionProps = _a.optionProps, optionIndex = _a.optionIndex;
return _this.renderOption(optionProps, optionIndex);
});
var visibleChildrenCount = lodash_1.default.filter(childOptions, function (option) { return !option.props.isHidden; }).length;
return (react_1.default.createElement(DropMenu_1.DropMenuDumb.OptionGroup, __assign({ isHidden: visibleChildrenCount === 0, key: 'SearchableSelectOptionGroup' + optionGroupIndex }, optionGroupProps),
optionGroupProps.children,
childOptions));
// then render all the ungrouped options at the end
}).concat(lodash_1.default.map(ungroupedOptionData, function (_a) {
var optionProps = _a.optionProps, optionIndex = _a.optionIndex;
return _this.renderOption(optionProps, optionIndex);
}));
var visibleOptionsCount = lodash_1.default.filter(options, function (option) { return !option.props.isHidden; }).length;
return visibleOptionsCount > 0 ? (options) : (react_1.default.createElement(DropMenu_1.DropMenuDumb.Option, { isDisabled: true },
react_1.default.createElement("span", { className: cx('&-noresults') },
"No results match \"",
searchText,
"\"")));
};
SearchableSelect.prototype.render = function () {
var _a = this, props = _a.props, _b = _a.props, style = _b.style, className = _b.className, hasReset = _b.hasReset, isDisabled = _b.isDisabled, isInvisible = _b.isInvisible, isLoading = _b.isLoading, isSelectionHighlighted = _b.isSelectionHighlighted, maxMenuHeight = _b.maxMenuHeight, searchText = _b.searchText, selectedIndex = _b.selectedIndex, onSelect = _b.onSelect, dropMenuProps = _b.DropMenu;
var direction = dropMenuProps.direction, optionContainerStyle = dropMenuProps.optionContainerStyle, isExpanded = dropMenuProps.isExpanded;
var flattenedOptionsData = this.state.flattenedOptionsData;
var searchFieldProps = lodash_1.default.get((0, component_types_1.getFirst)(props, SearchField_1.SearchFieldDumb) || react_1.default.createElement(SearchField_1.SearchFieldDumb, { placeholder: 'Search...' }), 'props');
var placeholderProps = lodash_1.default.first(lodash_1.default.map((0, component_types_1.findTypes)(this.props, SearchableSelect.Placeholder), 'props'));
var errorChildProps = lodash_1.default.first(lodash_1.default.map((0, component_types_1.findTypes)(props, Validation_1.Validation.Error), 'props'));
var placeholder = lodash_1.default.get(placeholderProps, 'children', 'Select');
var isItemSelected = lodash_1.default.isNumber(selectedIndex);
return (react_1.default.createElement("div", { className: cx('&', className), style: style },
react_1.default.createElement(DropMenu_1.DropMenuDumb, __assign({}, dropMenuProps, { optionContainerStyle: lodash_1.default.assign({}, optionContainerStyle, !lodash_1.default.isNil(maxMenuHeight) ? { maxHeight: maxMenuHeight } : null), isDisabled: isDisabled, onSelect: onSelect, selectedIndices: lodash_1.default.isNumber(selectedIndex) ? [selectedIndex] : [], onExpand: this.handleExpand }),
react_1.default.createElement(DropMenu_1.DropMenuDumb.Control, null,
react_1.default.createElement("div", { tabIndex: 0, className: cx('&-Control', {
'&-Control-is-highlighted': (!isDisabled && isItemSelected && isSelectionHighlighted) ||
(isExpanded && isSelectionHighlighted),
'&-Control-is-selected': !isDisabled &&
isItemSelected &&
isSelectionHighlighted &&
!(errorChildProps && errorChildProps.children),
'&-Control-is-expanded': isExpanded,
'&-Control-is-invisible': isInvisible,
'&-Control-is-disabled': isDisabled,
'&-Control-is-error': errorChildProps && errorChildProps.children,
}) },
react_1.default.createElement("span", __assign({}, (!isItemSelected ? placeholderProps : null), { className: cx('&-Control-content', !isItemSelected ? lodash_1.default.get(placeholderProps, 'className') : null) }), lodash_1.default.isNumber(selectedIndex)
? lodash_1.default.get((0, component_types_1.getFirst)(flattenedOptionsData[selectedIndex].optionProps, SearchableSelect.Option.Selected), 'props.children') ||
(function (Children) {
return lodash_1.default.isFunction(Children) ? react_1.default.createElement(Children, null) : Children;
})(flattenedOptionsData[selectedIndex].optionProps.children)
: placeholder),
react_1.default.createElement(ChevronIcon_1.default, { size: 12, direction: isExpanded ? direction : 'down' }))),
react_1.default.createElement(DropMenu_1.DropMenuDumb.Header, { className: cx('&-Search-container') },
react_1.default.createElement(SearchField_1.SearchFieldDumb, __assign({}, searchFieldProps, { autoComplete: searchFieldProps.autoComplete || 'off', onChange: this.handleSearch, value: searchText, ref: this.setSearchField }))),
isLoading && (react_1.default.createElement(DropMenu_1.DropMenuDumb.Option, { key: 'SearchableSelectLoading', className: cx('&-Loading'), isDisabled: true },
react_1.default.createElement(LoadingIcon_1.default, null))),
hasReset && isItemSelected && (react_1.default.createElement(DropMenu_1.DropMenuDumb.NullOption, __assign({}, placeholderProps), placeholder)),
this.renderOptions()),
errorChildProps &&
errorChildProps.children &&
errorChildProps.children !== true ? (react_1.default.createElement("div", __assign({}, lodash_1.default.omit(errorChildProps, ['initialState', 'callbackId']), { className: cx('&-error-content') }), errorChildProps.children)) : null));
};
SearchableSelect.displayName = 'SearchableSelect';
SearchableSelect.peek = {
description: "A selector control (like native `<select>`) which is used to select a single option from a dropdown list using a `SearchField`. Supports option groups with and without labels.",
categories: ['controls', 'selectors'],
madeFrom: ['DropMenu', 'SearchField'],
};
SearchableSelect.defaultProps = defaultProps;
SearchableSelect.reducers = reducers;
SearchableSelect.Placeholder = Placeholder;
SearchableSelect.Option = Option;
SearchableSelect.OptionGroup = OptionGroup;
SearchableSelect.SearchField = SearchField_1.SearchFieldDumb;
SearchableSelect.NullOption = DropMenu_1.DropMenuDumb.NullOption;
SearchableSelect.FixedOption = DropMenu_1.DropMenuDumb.FixedOption;
SearchableSelect.propTypes = {
/**
Should be instances of {\`SearchableSelect.Placeholder\`,
\`SearchableSelect.Option\`, \`SearchableSelect.OptionGroup\`}. Other
direct child elements will not render.
*/
children: node,
className: string /**
Appended to the component-specific class names set on the root elements.
Applies to *both* the control and the flyout menu.
*/,
/**
Styles that are passed through to root element.
*/
style: object,
/**
Allows user to reset the \`optionIndex\` to \`null\` if they select the
placeholder at the top of the options list. If \`false\`, it will not
render the placeholder in the menu.
*/
hasReset: bool,
/**
Disables the SearchableSelect from being clicked or focused.
*/
isDisabled: bool,
/**
The SearchableSelect will be invisible.
*/
isInvisible: bool,
/**
Displays a centered LoadingIcon to allow for asynchronous loading of
options.
*/
isLoading: bool,
/**
Applies primary color styling to the control when an item is selected.
*/
isSelectionHighlighted: bool,
/**
The max height of the fly-out menu.
*/
maxMenuHeight: oneOfType([number, string]),
onSearch: func /**
Called when the user enters a value to search for; the set of visible
Options will be filtered using the value. Has the signature
\`(searchText, firstVisibleIndex, {props, event}) => {}\` where
\`searchText\` is the value from the \`SearchField\` and
\`firstVisibleIndex\` is the index of the first option that will be
visible after filtering.
*/,
/**
Called when an option is selected. Has the signature
\`(optionIndex, {props, event}) => {}\` where \`optionIndex\` is the new
\`selectedIndex\` or \`null\`.
*/
onSelect: func,
/**
The function that will be run against each Option's props to determine
whether it should be visible or not. The default behavior of the function
is to match, ignoring case, against any text node descendant of the
\`Option\`. Has the signature \`(searchText, optionProps)\` If \`true\`,
option will be visible. If \`false\`, the option will not be visible.
*/
optionFilter: func,
/**
The current search text to filter the list of options by.
*/
searchText: string,
/**
The currently selected \`SearchableSelect.Option\` index or \`null\` if
nothing is selected.
*/
selectedIndex: number,
/**
Object of DropMenu props which are passed thru to the underlying DropMenu
component.
*/
DropMenu: shape(DropMenu_1.DropMenuDumb.propTypes),
Placeholder: any /**
*Child Element* - The content rendered in the control when there is no
option is selected. Also rendered in the option list to remove current
selection.
*/,
Option: any /**
*Child Element* - These are menu options. The \`optionIndex\` is in-order
of rendering regardless of group nesting, starting with index \`0\`.
Each \`Option\` may be passed a prop called \`isDisabled\` to disable
selection of that \`Option\`. Any other props pass to Option will be
available from the \`onSelect\` handler.
*/,
FixedOption: any /**
*Child Element* - A special kind of \`Option\` that is always rendered at the top of
the menu.
*/,
NullOption: any /**
*Child Element* - A special kind of \`Option\` that is always rendered at
the top of the menu and has an \`optionIndex\` of \`null\`. Useful for
unselect.
*/,
OptionGroup: any /**
*Child Element* - Used to group \`Option\`s within the menu. Any
non-\`Option\`s passed in will be rendered as a label for the group.
*/,
/**
In most cases this will be a string, but it also accepts any valid React
element. If this is a falsey value, then no error message will be
displayed. If this is the literal \`true\`, it will add the
\`-is-error\` class to the wrapper div, but not render the
\`-error-content\` \`div\`.
*/
Error: any,
};
return SearchableSelect;
}(react_1.default.Component));
exports.SearchableSelectDumb = SearchableSelect;
exports.default = (0, state_management_1.buildModernHybridComponent)(SearchableSelect, { reducers: reducers });
//# sourceMappingURL=SearchableSelect.js.map