UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

471 lines (459 loc) • 18.1 kB
'use strict'; var reactCompilerRuntime = require('react-compiler-runtime'); var behaviors = require('@primer/behaviors'); var React = require('react'); var styled = require('styled-components'); 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 FilteredActionListLoaders = require('./FilteredActionListLoaders.js'); var FilteredActionList_module = require('./FilteredActionList.module.css.js'); var ActionListContainerContext = require('../ActionList/ActionListContainerContext.js'); var reactIs = require('react-is'); var useAnnouncements = require('./useAnnouncements.js'); var clsx = require('clsx'); var jsxRuntime = require('react/jsx-runtime'); var useFeatureFlag = require('../FeatureFlags/useFeatureFlag.js'); var Box = require('../Box/Box.js'); var TextInput = require('../TextInput/TextInput.js'); var VisuallyHidden = require('../VisuallyHidden/VisuallyHidden.js'); var Checkbox = require('../Checkbox/Checkbox.js'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var styled__default = /*#__PURE__*/_interopDefault(styled); const menuScrollMargins = { startMargin: 0, endMargin: 8 }; const StyledHeader = styled__default.default.div.withConfig({ displayName: "FilteredActionList__StyledHeader", componentId: "sc-1oqgb0s-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, message, messageText, className, selectionVariant, announcementsEnabled = true, fullScreenOnNarrow, onSelectAllChange, ...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 inputAndListContainerRef = React.useRef(null); const listRef = React.useRef(null); const scrollContainerRef = React.useRef(null); const inputRef = useProvidedRefOrCreate.useProvidedRefOrCreate(providedInputRef); const usingRemoveActiveDescendant = useFeatureFlag.useFeatureFlag('primer_react_select_panel_remove_active_descendant'); const [listContainerElement, setListContainerElement] = React.useState(null); const activeDescendantRef = React.useRef(); const listId = useId.useId(); const inputDescriptionTextId = useId.useId(); const [isInputFocused, setIsInputFocused] = React.useState(false); const selectAllChecked = items.length > 0 && items.every(item => item.selected); const selectAllIndeterminate = !selectAllChecked && items.some(item_0 => item_0.selected); const selectAllLabelText = selectAllChecked ? 'Deselect all' : 'Select all'; const getItemListForEachGroup = React.useCallback(groupId => { const itemsInGroup = []; for (const item_1 of items) { // Look up the group associated with the current item. if (item_1.groupId === groupId) { itemsInGroup.push(item_1); } } return itemsInGroup; }, [items]); const onInputKeyDown = React.useCallback(event => { if (event.key === 'ArrowDown') { if (listRef.current) { const firstSelectedItem = listRef.current.querySelector('[role="option"]'); firstSelectedItem === null || firstSelectedItem === void 0 ? void 0 : firstSelectedItem.focus(); event.preventDefault(); } } else if (event.key === 'Enter') { let firstItem; // If there are groups, it's not guaranteed that the first item is the actual first item in the first - // as groups are rendered in the order of the groupId provided if (groupMetadata) { let firstGroupIndex = 0; for (let i = 0; i < groupMetadata.length; i++) { if (getItemListForEachGroup(groupMetadata[i].groupId).length > 0) { break; } else { firstGroupIndex++; } } const firstGroup = groupMetadata[firstGroupIndex].groupId; firstItem = items.filter(item_2 => item_2.groupId === firstGroup)[0]; } else { firstItem = items[0]; } if (firstItem.onAction) { firstItem.onAction(firstItem, event); event.preventDefault(); } } }, [items, groupMetadata, getItemListForEachGroup]); const onInputKeyPress = React.useCallback(event_0 => { if (event_0.key === 'Enter' && activeDescendantRef.current) { event_0.preventDefault(); event_0.nativeEvent.stopImmediatePropagation(); // Forward Enter key press to active descendant so that item gets activated const activeDescendantEvent = new KeyboardEvent(event_0.type, event_0.nativeEvent); activeDescendantRef.current.dispatchEvent(activeDescendantEvent); } }, [activeDescendantRef]); // BEGIN: Todo remove when we remove usingRemoveActiveDescendant const listContainerRefCallback = React.useCallback(node => { setListContainerElement(node); onListContainerRefChanged === null || onListContainerRefChanged === void 0 ? void 0 : onListContainerRefChanged(node); }, [onListContainerRefChanged]); React.useEffect(() => { onInputRefChanged === null || onInputRefChanged === void 0 ? void 0 : onInputRefChanged(inputRef); }, [inputRef, onInputRefChanged]); //END: Todo remove when we remove usingRemoveActiveDescendant useFocusZone.useFocusZone(!usingRemoveActiveDescendant ? { 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); } } } : undefined, [listContainerElement, usingRemoveActiveDescendant]); React.useEffect(() => { if (activeDescendantRef.current && scrollContainerRef.current) { behaviors.scrollIntoView(activeDescendantRef.current, scrollContainerRef.current, { ...menuScrollMargins, behavior: 'auto' }); } }, [items, inputRef]); React.useEffect(() => { if (usingRemoveActiveDescendant) { const inputAndListContainerElement = inputAndListContainerRef.current; if (!inputAndListContainerElement) return; const list = listRef.current; if (!list) return; // Listen for focus changes within the container const handleFocusIn = event_1 => { if (event_1.target === inputRef.current || list.contains(event_1.target)) { setIsInputFocused(inputRef.current && inputRef.current === document.activeElement ? true : false); } }; inputAndListContainerElement.addEventListener('focusin', handleFocusIn); return () => { inputAndListContainerElement.removeEventListener('focusin', handleFocusIn); }; } }, [items, inputRef, listContainerElement, usingRemoveActiveDescendant]); // Re-run when items change to update active indicators React.useEffect(() => { if (usingRemoveActiveDescendant && !loading) { setIsInputFocused(inputRef.current && inputRef.current === document.activeElement ? true : false); } }, [loading, inputRef, usingRemoveActiveDescendant]); useAnnouncements.useAnnouncements(items, usingRemoveActiveDescendant ? listRef : { current: listContainerElement }, inputRef, announcementsEnabled, loading, messageText); useScrollFlash(scrollContainerRef); const handleSelectAllChange = React.useCallback(e_0 => { if (onSelectAllChange) { onSelectAllChange(e_0.target.checked); } }, [onSelectAllChange]); function getBodyContent() { if (loading && scrollContainerRef.current && loadingType.appearsInBody) { return /*#__PURE__*/jsxRuntime.jsx(FilteredActionListLoaders.FilteredActionListBodyLoader, { loadingType: loadingType, height: scrollContainerRef.current.clientHeight }); } if (message) { return message; } let firstGroupIndex_0 = 0; const actionListContent = /*#__PURE__*/jsxRuntime.jsx(index.ActionList, { ref: usingRemoveActiveDescendant ? listRef : listContainerRefCallback, showDividers: showItemDividers, selectionVariant: selectionVariant, ...listProps, role: "listbox", id: listId, sx: { flexGrow: 1 }, children: groupMetadata !== null && groupMetadata !== void 0 && groupMetadata.length ? groupMetadata.map((group, index$1) => { var _group$header, _group$header2; if (index$1 === firstGroupIndex_0 && getItemListForEachGroup(group.groupId).length === 0) { firstGroupIndex_0++; // Increment firstGroupIndex if the first group has no items } return /*#__PURE__*/jsxRuntime.jsxs(index.ActionList.Group, { children: [/*#__PURE__*/jsxRuntime.jsx(index.ActionList.GroupHeading, { variant: (_group$header = group.header) !== null && _group$header !== void 0 && _group$header.variant ? group.header.variant : undefined, children: (_group$header2 = group.header) !== null && _group$header2 !== void 0 && _group$header2.title ? group.header.title : `Group ${group.groupId}` }), getItemListForEachGroup(group.groupId).map(({ key: itemKey, ...item_3 }, itemIndex) => { var _ref, _item_3$id; const key = (_ref = itemKey !== null && itemKey !== void 0 ? itemKey : (_item_3$id = item_3.id) === null || _item_3$id === void 0 ? void 0 : _item_3$id.toString()) !== null && _ref !== void 0 ? _ref : itemIndex.toString(); return /*#__PURE__*/jsxRuntime.jsx(MappedActionListItem, { className: clsx.clsx(FilteredActionList_module.ActionListItem, 'className' in item_3 ? item_3.className : undefined), "data-input-focused": isInputFocused ? '' : undefined, "data-first-child": index$1 === firstGroupIndex_0 && itemIndex === 0 ? '' : undefined, ...item_3, renderItem: listProps.renderItem }, key); })] }, index$1); }) : items.map(({ key: itemKey_0, ...item_4 }, index_0) => { var _ref2, _item_4$id; const key_0 = (_ref2 = itemKey_0 !== null && itemKey_0 !== void 0 ? itemKey_0 : (_item_4$id = item_4.id) === null || _item_4$id === void 0 ? void 0 : _item_4$id.toString()) !== null && _ref2 !== void 0 ? _ref2 : index_0.toString(); return /*#__PURE__*/jsxRuntime.jsx(MappedActionListItem, { className: clsx.clsx(FilteredActionList_module.ActionListItem, 'className' in item_4 ? item_4.className : undefined), "data-input-focused": isInputFocused ? '' : undefined, "data-first-child": index_0 === 0 ? '' : undefined, ...item_4, renderItem: listProps.renderItem }, key_0); }) }); // Use ActionListContainerContext.Provider only for the old behavior (when feature flag is disabled) if (usingRemoveActiveDescendant) { return /*#__PURE__*/jsxRuntime.jsx(ActionListContainerContext.ActionListContainerContext.Provider, { value: { container: 'FilteredActionList', listRole: 'listbox', selectionAttribute: 'aria-selected', selectionVariant, enableFocusZone: true }, children: actionListContent }); } else { return actionListContent; } } return /*#__PURE__*/jsxRuntime.jsxs(Box, { ref: inputAndListContainerRef, display: "flex", flexDirection: "column", overflow: "hidden", sx: sx, className: className, "data-testid": "filtered-action-list", children: [/*#__PURE__*/jsxRuntime.jsx(StyledHeader, { children: /*#__PURE__*/jsxRuntime.jsx(TextInput, { ref: inputRef, block: true, width: "auto", color: "fg.default", value: filterValue, onChange: onInputChange, onKeyPress: onInputKeyPress, onKeyDown: usingRemoveActiveDescendant ? onInputKeyDown : () => {}, 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, className: clsx.clsx(textInputProps === null || textInputProps === void 0 ? void 0 : textInputProps.className, fullScreenOnNarrow && FilteredActionList_module.FullScreenTextInput), ...textInputProps }) }), /*#__PURE__*/jsxRuntime.jsx(VisuallyHidden.VisuallyHidden, { id: inputDescriptionTextId, children: "Items will be filtered as you type" }), onSelectAllChange !== undefined && /*#__PURE__*/jsxRuntime.jsxs("div", { className: FilteredActionList_module.SelectAllContainer, children: [/*#__PURE__*/jsxRuntime.jsx(Checkbox, { id: "select-all-checkbox", className: FilteredActionList_module.SelectAllCheckbox, checked: selectAllChecked, indeterminate: selectAllIndeterminate, onChange: handleSelectAllChange }), /*#__PURE__*/jsxRuntime.jsx("label", { className: FilteredActionList_module.SelectAllLabel, htmlFor: "select-all-checkbox", children: selectAllLabelText })] }), /*#__PURE__*/jsxRuntime.jsx("div", { ref: scrollContainerRef, className: FilteredActionList_module.Container, children: getBodyContent() })] }); } FilteredActionList.displayName = "FilteredActionList"; function MappedActionListItem(item) { const $ = reactCompilerRuntime.c(35); if (typeof item.renderItem === "function") { let t0; if ($[0] !== item) { t0 = item.renderItem(item); $[0] = item; $[1] = t0; } else { t0 = $[1]; } return t0; } let LeadingVisual; let TrailingIcon; let TrailingVisual; let children; let description; let descriptionVariant; let id; let onAction; let rest; let text; let trailingText; if ($[2] !== item) { ({ id, description, descriptionVariant, text, trailingVisual: TrailingVisual, leadingVisual: LeadingVisual, trailingText, trailingIcon: TrailingIcon, onAction, children, ...rest } = item); $[2] = item; $[3] = LeadingVisual; $[4] = TrailingIcon; $[5] = TrailingVisual; $[6] = children; $[7] = description; $[8] = descriptionVariant; $[9] = id; $[10] = onAction; $[11] = rest; $[12] = text; $[13] = trailingText; } else { LeadingVisual = $[3]; TrailingIcon = $[4]; TrailingVisual = $[5]; children = $[6]; description = $[7]; descriptionVariant = $[8]; id = $[9]; onAction = $[10]; rest = $[11]; text = $[12]; trailingText = $[13]; } let t0; if ($[14] !== item || $[15] !== onAction) { t0 = e => { if (typeof onAction === "function") { onAction(item, e); } }; $[14] = item; $[15] = onAction; $[16] = t0; } else { t0 = $[16]; } let t1; if ($[17] !== LeadingVisual) { t1 = LeadingVisual ? /*#__PURE__*/jsxRuntime.jsx(index.ActionList.LeadingVisual, { children: /*#__PURE__*/jsxRuntime.jsx(LeadingVisual, {}) }) : null; $[17] = LeadingVisual; $[18] = t1; } else { t1 = $[18]; } let t2; if ($[19] !== description || $[20] !== descriptionVariant) { t2 = description ? /*#__PURE__*/jsxRuntime.jsx(index.ActionList.Description, { variant: descriptionVariant, children: description }) : null; $[19] = description; $[20] = descriptionVariant; $[21] = t2; } else { t2 = $[21]; } let t3; if ($[22] !== TrailingIcon || $[23] !== TrailingVisual || $[24] !== trailingText) { t3 = TrailingVisual ? /*#__PURE__*/jsxRuntime.jsx(index.ActionList.TrailingVisual, { children: typeof TrailingVisual !== "string" && reactIs.isValidElementType(TrailingVisual) ? /*#__PURE__*/jsxRuntime.jsx(TrailingVisual, {}) : TrailingVisual }) : TrailingIcon || trailingText ? /*#__PURE__*/jsxRuntime.jsxs(index.ActionList.TrailingVisual, { children: [trailingText, TrailingIcon && /*#__PURE__*/jsxRuntime.jsx(TrailingIcon, {})] }) : null; $[22] = TrailingIcon; $[23] = TrailingVisual; $[24] = trailingText; $[25] = t3; } else { t3 = $[25]; } let t4; if ($[26] !== children || $[27] !== id || $[28] !== rest || $[29] !== t0 || $[30] !== t1 || $[31] !== t2 || $[32] !== t3 || $[33] !== text) { t4 = /*#__PURE__*/jsxRuntime.jsxs(index.ActionList.Item, { role: "option", onSelect: t0, "data-id": id, ...rest, children: [t1, children, text, t2, t3] }); $[26] = children; $[27] = id; $[28] = rest; $[29] = t0; $[30] = t1; $[31] = t2; $[32] = t3; $[33] = text; $[34] = t4; } else { t4 = $[34]; } return t4; } FilteredActionList.displayName = 'FilteredActionList'; exports.FilteredActionList = FilteredActionList;