@primer/react
Version:
An implementation of GitHub's Primer Design System using React
225 lines (218 loc) • 10.4 kB
JavaScript
'use strict';
var behaviors = require('@primer/behaviors');
var React = require('react');
var styled = require('styled-components');
var Box = require('../Box/Box.js');
var TextInput = require('../TextInput/TextInput.js');
var constants = require('../constants.js');
var index = require('../ActionList/index.js');
var useFocusZone = require('../hooks/useFocusZone.js');
var useId = require('../hooks/useId.js');
var useProvidedRefOrCreate = require('../hooks/useProvidedRefOrCreate.js');
var useProvidedStateOrCreate = require('../hooks/useProvidedStateOrCreate.js');
var useScrollFlash = require('../hooks/useScrollFlash.js');
var VisuallyHidden = require('../VisuallyHidden/VisuallyHidden.js');
var FilteredActionListLoaders = require('./FilteredActionListLoaders.js');
var reactIs = require('react-is');
var useAnnouncements = require('./useAnnouncements.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
var styled__default = /*#__PURE__*/_interopDefault(styled);
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
const menuScrollMargins = {
startMargin: 0,
endMargin: 8
};
const StyledHeader = styled__default.default.div.withConfig({
displayName: "FilteredActionListWithModernActionList__StyledHeader",
componentId: "sc-1d2kw5v-0"
})(["box-shadow:0 1px 0 ", ";z-index:1;"], constants.get('colors.border.default'));
function FilteredActionList({
loading = false,
placeholderText,
filterValue: externalFilterValue,
loadingType = FilteredActionListLoaders.FilteredActionListLoadingTypes.bodySpinner,
onFilterChange,
onListContainerRefChanged,
onInputRefChanged,
items,
textInputProps,
inputRef: providedInputRef,
sx,
groupMetadata,
showItemDividers,
className,
announcementsEnabled = true,
...listProps
}) {
const [filterValue, setInternalFilterValue] = useProvidedStateOrCreate.useProvidedStateOrCreate(externalFilterValue, undefined, '');
const onInputChange = React.useCallback(e => {
const value = e.target.value;
onFilterChange(value, e);
setInternalFilterValue(value);
}, [onFilterChange, setInternalFilterValue]);
const scrollContainerRef = React.useRef(null);
const inputRef = useProvidedRefOrCreate.useProvidedRefOrCreate(providedInputRef);
const [listContainerElement, setListContainerElement] = React.useState(null);
const activeDescendantRef = React.useRef();
const listId = useId.useId();
const inputDescriptionTextId = useId.useId();
const onInputKeyPress = React.useCallback(event => {
if (event.key === 'Enter' && activeDescendantRef.current) {
event.preventDefault();
event.nativeEvent.stopImmediatePropagation();
// Forward Enter key press to active descendant so that item gets activated
const activeDescendantEvent = new KeyboardEvent(event.type, event.nativeEvent);
activeDescendantRef.current.dispatchEvent(activeDescendantEvent);
}
}, [activeDescendantRef]);
const listContainerRefCallback = React.useCallback(node => {
setListContainerElement(node);
onListContainerRefChanged === null || onListContainerRefChanged === undefined ? undefined : onListContainerRefChanged(node);
}, [onListContainerRefChanged]);
React.useEffect(() => {
onInputRefChanged === null || onInputRefChanged === undefined ? undefined : onInputRefChanged(inputRef);
}, [inputRef, onInputRefChanged]);
useFocusZone.useFocusZone({
containerRef: {
current: listContainerElement
},
bindKeys: behaviors.FocusKeys.ArrowVertical | behaviors.FocusKeys.PageUpDown,
focusOutBehavior: 'wrap',
focusableElementFilter: element => {
return !(element instanceof HTMLInputElement);
},
activeDescendantFocus: inputRef,
onActiveDescendantChanged: (current, previous, directlyActivated) => {
activeDescendantRef.current = current;
if (current && scrollContainerRef.current && directlyActivated) {
behaviors.scrollIntoView(current, scrollContainerRef.current, menuScrollMargins);
}
}
}, [
// List container isn't in the DOM while loading. Need to re-bind focus zone when it changes.
listContainerElement]);
React.useEffect(() => {
// if items changed, we want to instantly move active descendant into view
if (activeDescendantRef.current && scrollContainerRef.current) {
behaviors.scrollIntoView(activeDescendantRef.current, scrollContainerRef.current, {
...menuScrollMargins,
behavior: 'auto'
});
}
}, [items]);
useAnnouncements.useAnnouncements(items, {
current: listContainerElement
}, inputRef, announcementsEnabled);
useScrollFlash(scrollContainerRef);
function getItemListForEachGroup(groupId) {
const itemsInGroup = [];
for (const item of items) {
// Look up the group associated with the current item.
if (item.groupId === groupId) {
itemsInGroup.push(item);
}
}
return itemsInGroup;
}
return /*#__PURE__*/React__default.default.createElement(Box, {
display: "flex",
flexDirection: "column",
overflow: "hidden",
sx: sx,
className: className,
"data-testid": "filtered-action-list"
}, /*#__PURE__*/React__default.default.createElement(StyledHeader, null, /*#__PURE__*/React__default.default.createElement(TextInput, _extends({
ref: inputRef,
block: true,
width: "auto",
color: "fg.default",
value: filterValue,
onChange: onInputChange,
onKeyPress: onInputKeyPress,
placeholder: placeholderText,
role: "combobox",
"aria-expanded": "true",
"aria-autocomplete": "list",
"aria-controls": listId,
"aria-label": placeholderText,
"aria-describedby": inputDescriptionTextId,
loaderPosition: 'leading',
loading: loading && !loadingType.appearsInBody
}, textInputProps))), /*#__PURE__*/React__default.default.createElement(VisuallyHidden.VisuallyHidden, {
id: inputDescriptionTextId
}, "Items will be filtered as you type"), /*#__PURE__*/React__default.default.createElement(Box, {
ref: scrollContainerRef,
overflow: "auto",
display: "flex",
flexGrow: 1
}, loading && scrollContainerRef.current && loadingType.appearsInBody ? /*#__PURE__*/React__default.default.createElement(FilteredActionListLoaders.FilteredActionListBodyLoader, {
loadingType: loadingType,
height: scrollContainerRef.current.clientHeight
}) : /*#__PURE__*/React__default.default.createElement(index.ActionList, _extends({
ref: listContainerRefCallback,
showDividers: showItemDividers
}, listProps, {
role: "listbox",
id: listId,
sx: {
flexGrow: 1
}
}), groupMetadata !== null && groupMetadata !== undefined && groupMetadata.length ? groupMetadata.map((group, index$1) => {
var _group$header, _group$header2;
return /*#__PURE__*/React__default.default.createElement(index.ActionList.Group, {
key: index$1
}, /*#__PURE__*/React__default.default.createElement(index.ActionList.GroupHeading, {
variant: (_group$header = group.header) !== null && _group$header !== undefined && _group$header.variant ? group.header.variant : undefined
}, (_group$header2 = group.header) !== null && _group$header2 !== undefined && _group$header2.title ? group.header.title : `Group ${group.groupId}`), getItemListForEachGroup(group.groupId).map((item, index) => {
var _ref, _item$key, _item$id;
const key = (_ref = (_item$key = item.key) !== null && _item$key !== undefined ? _item$key : (_item$id = item.id) === null || _item$id === undefined ? undefined : _item$id.toString()) !== null && _ref !== undefined ? _ref : index.toString();
return /*#__PURE__*/React__default.default.createElement(MappedActionListItem, _extends({
key: key
}, item, {
renderItem: listProps.renderItem
}));
}));
}) : items.map((item, index) => {
var _ref2, _item$key2, _item$id2;
const key = (_ref2 = (_item$key2 = item.key) !== null && _item$key2 !== undefined ? _item$key2 : (_item$id2 = item.id) === null || _item$id2 === undefined ? undefined : _item$id2.toString()) !== null && _ref2 !== undefined ? _ref2 : index.toString();
return /*#__PURE__*/React__default.default.createElement(MappedActionListItem, _extends({
key: key
}, item, {
renderItem: listProps.renderItem
}));
}))));
}
FilteredActionList.displayName = "FilteredActionList";
function MappedActionListItem(item) {
// keep backward compatibility for renderItem
// escape hatch for custom Item rendering
if (typeof item.renderItem === 'function') return item.renderItem(item);
const {
id,
description,
descriptionVariant,
text,
trailingVisual: TrailingVisual,
leadingVisual: LeadingVisual,
trailingText,
trailingIcon: TrailingIcon,
onAction,
children,
...rest
} = item;
return /*#__PURE__*/React__default.default.createElement(index.ActionList.Item, _extends({
role: "option"
// @ts-ignore - for now
,
onSelect: e => {
if (typeof onAction === 'function') onAction(item, e);
},
"data-id": id
}, rest), LeadingVisual ? /*#__PURE__*/React__default.default.createElement(index.ActionList.LeadingVisual, null, /*#__PURE__*/React__default.default.createElement(LeadingVisual, null)) : null, children, text, description ? /*#__PURE__*/React__default.default.createElement(index.ActionList.Description, {
variant: descriptionVariant
}, description) : null, TrailingVisual ? /*#__PURE__*/React__default.default.createElement(index.ActionList.TrailingVisual, null, typeof TrailingVisual !== 'string' && reactIs.isValidElementType(TrailingVisual) ? /*#__PURE__*/React__default.default.createElement(TrailingVisual, null) : TrailingVisual) : TrailingIcon || trailingText ? /*#__PURE__*/React__default.default.createElement(index.ActionList.TrailingVisual, null, trailingText, TrailingIcon && /*#__PURE__*/React__default.default.createElement(TrailingIcon, null)) : null);
}
MappedActionListItem.displayName = "MappedActionListItem";
FilteredActionList.displayName = 'FilteredActionList';
exports.FilteredActionList = FilteredActionList;