@primer/react
Version:
An implementation of GitHub's Primer Design System using React
763 lines (730 loc) • 31.8 kB
JavaScript
var octiconsReact = require('@primer/octicons-react');
var React = require('react');
var SelectPanelMessage = require('./SelectPanelMessage.js');
var IconButton = require('../Button/IconButton.js');
var LinkButton = require('../Button/LinkButton.js');
var Button = require('../Button/Button.js');
var useId = require('../hooks/useId.js');
var useProvidedStateOrCreate = require('../hooks/useProvidedStateOrCreate.js');
var useSafeTimeout = require('../hooks/useSafeTimeout.js');
var FilteredActionListLoaders = require('../FilteredActionList/FilteredActionListLoaders.js');
var liveRegionElement = require('@primer/live-region-element');
var SelectPanel_module = require('./SelectPanel.module.css.js');
var clsx = require('clsx');
var index = require('../node_modules/@github/mini-throttle/dist/index.js');
var useResponsiveValue = require('../hooks/useResponsiveValue.js');
var jsxRuntime = require('react/jsx-runtime');
var useFeatureFlag = require('../FeatureFlags/useFeatureFlag.js');
var useProvidedRefOrCreate = require('../hooks/useProvidedRefOrCreate.js');
var AnchoredOverlay = require('../AnchoredOverlay/AnchoredOverlay.js');
var Heading = require('../Heading/Heading.js');
var FilteredActionList = require('../FilteredActionList/FilteredActionList.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
// we add a delay so that it does not interrupt default screen reader announcement and queues after it
const SHORT_DELAY_MS = 500;
const LONG_DELAY_MS = 1000;
const EMPTY_MESSAGE = {
title: 'No items available',
description: ''
};
const DefaultEmptyMessage = /*#__PURE__*/jsxRuntime.jsx(SelectPanelMessage.SelectPanelMessage, {
variant: "empty",
title: EMPTY_MESSAGE.title,
children: EMPTY_MESSAGE.description
}, "empty-message");
async function announceText(text, delayMs = SHORT_DELAY_MS) {
const liveRegion = document.querySelector('live-region');
liveRegion === null || liveRegion === void 0 ? void 0 : liveRegion.clear(); // clear previous announcements
await liveRegionElement.announce(text, {
delayMs,
from: liveRegion ? liveRegion : undefined // announce will create a liveRegion if it doesn't find one
});
}
async function announceLoading() {
await announceText('Loading.');
}
// onCancel is optional with variant=anchored, but required with variant=modal
function isMultiSelectVariant(selected) {
return Array.isArray(selected);
}
const focusZoneSettings = {
// Let FilteredActionList handle focus zone
disabled: true
};
const areItemsEqual = (itemA, itemB) => {
// prefer checking equivality by item.id
if (typeof itemA.id !== 'undefined') return itemA.id === itemB.id;else return itemA === itemB;
};
const doesItemsIncludeItem = (items, item) => {
return items.some(i => areItemsEqual(i, item));
};
const defaultRenderAnchor = props => {
const {
children,
...rest
} = props;
return /*#__PURE__*/jsxRuntime.jsx(Button.ButtonComponent, {
trailingAction: octiconsReact.TriangleDownIcon,
...rest,
children: children
});
};
defaultRenderAnchor.displayName = "defaultRenderAnchor";
function Panel({
open,
onOpenChange,
renderAnchor = defaultRenderAnchor,
anchorRef: externalAnchorRef,
placeholder,
placeholderText = 'Filter items',
inputLabel = placeholderText,
selected,
title = isMultiSelectVariant(selected) ? 'Select items' : 'Select an item',
subtitle,
onSelectedChange,
filterValue: externalFilterValue,
onFilterChange: externalOnFilterChange,
items,
footer,
textInputProps,
overlayProps,
sx,
loading,
initialLoadingType = 'spinner',
className,
height,
width,
id,
message,
notice,
onCancel,
variant = 'anchored',
secondaryAction,
showSelectedOptionsFirst = true,
disableFullscreenOnNarrow,
align,
showSelectAll = false,
...listProps
}) {
var _listProps$groupMetad;
const titleId = useId.useId();
const subtitleId = useId.useId();
const [dataLoadedOnce, setDataLoadedOnce] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);
const [filterValue, setInternalFilterValue] = useProvidedStateOrCreate.useProvidedStateOrCreate(externalFilterValue, undefined, '');
const {
safeSetTimeout,
safeClearTimeout
} = useSafeTimeout();
const loadingDelayTimeoutId = React.useRef(null);
const loadingManagedInternally = loading === undefined;
const loadingManagedExternally = !loadingManagedInternally;
const [inputRef, setInputRef] = React__default.default.useState(null);
const [listContainerElement, setListContainerElement] = React.useState(null);
const [needsNoItemsAnnouncement, setNeedsNoItemsAnnouncement] = React.useState(false);
const isNarrowScreenSize = useResponsiveValue.useResponsiveValue({
narrow: true,
regular: false,
wide: false
}, false);
const [selectedOnSort, setSelectedOnSort] = React.useState([]);
const [prevItems, setPrevItems] = React.useState([]);
const [prevOpen, setPrevOpen] = React.useState(open);
const initialHeightRef = React.useRef(0);
const initialScaleRef = React.useRef(1);
const noticeRef = React.useRef(null);
const [isKeyboardVisible, setIsKeyboardVisible] = React.useState(false);
const [availablePanelHeight, setAvailablePanelHeight] = React.useState(undefined);
const KEYBOARD_VISIBILITY_THRESHOLD = 10;
const featureFlagFullScreenOnNarrow = useFeatureFlag.useFeatureFlag('primer_react_select_panel_fullscreen_on_narrow');
const usingFullScreenOnNarrow = disableFullscreenOnNarrow ? false : featureFlagFullScreenOnNarrow;
const shouldOrderSelectedFirst = useFeatureFlag.useFeatureFlag('primer_react_select_panel_order_selected_at_top') && showSelectedOptionsFirst;
// Single select modals work differently, they have an intermediate state where the user has selected an item but
// has not yet confirmed the selection. This is the only time the user can cancel the selection.
const isSingleSelectModal = variant === 'modal' && !isMultiSelectVariant(selected);
const [intermediateSelected, setIntermediateSelected] = React.useState(isSingleSelectModal ? selected : undefined);
// Reset the intermediate selected item when the panel is open/closed
React.useEffect(() => {
setIntermediateSelected(isSingleSelectModal ? selected : undefined);
}, [isSingleSelectModal, open, selected]);
const onListContainerRefChanged = React.useCallback(node => {
setListContainerElement(node);
if (!node && needsNoItemsAnnouncement) {
setNeedsNoItemsAnnouncement(false);
}
}, [needsNoItemsAnnouncement]);
const onInputRefChanged = React.useCallback(ref => {
setInputRef(ref);
}, [setInputRef]);
const resetSort = React.useCallback(() => {
if (isMultiSelectVariant(selected)) {
setSelectedOnSort(selected);
} else if (selected) {
setSelectedOnSort([selected]);
} else {
setSelectedOnSort([]);
}
}, [selected]);
const onFilterChange = React.useCallback((value, e) => {
if (loadingManagedInternally) {
if (loadingDelayTimeoutId.current) {
safeClearTimeout(loadingDelayTimeoutId.current);
}
if (dataLoadedOnce) {
// If data has already been loaded once, delay the spinner a bit. This also helps
// not show and then immediately hide the spinner if items are loaded quickly, i.e.
// not async.
loadingDelayTimeoutId.current = safeSetTimeout(() => {
setIsLoading(true);
announceLoading();
}, LONG_DELAY_MS);
} else {
// If this is the first data load and there are no items, show the loading spinner
// immediately
if (items.length === 0) {
setIsLoading(true);
}
// We still want to announce if loading is taking too long
loadingDelayTimeoutId.current = safeSetTimeout(() => {
announceLoading();
}, LONG_DELAY_MS);
}
}
externalOnFilterChange(value, e);
setInternalFilterValue(value);
if (!value) {
resetSort();
}
}, [loadingManagedInternally, externalOnFilterChange, setInternalFilterValue, dataLoadedOnce, safeSetTimeout, safeClearTimeout, items.length, resetSort]);
const handleSelectAllChange = React.useCallback(checked => {
// Exit early if not in multi-select mode
if (!isMultiSelectVariant(selected)) {
return;
}
const multiSelectOnChange = onSelectedChange;
const selectedArray = selected;
const selectedItemsNotInFilteredView = selectedArray.filter(selectedItem => !items.some(item => areItemsEqual(item, selectedItem)));
if (checked) {
multiSelectOnChange([...selectedItemsNotInFilteredView, ...items]);
} else {
multiSelectOnChange(selectedItemsNotInFilteredView);
}
}, [items, onSelectedChange, selected]);
// disable body scroll when the panel is open on narrow screens
React.useEffect(() => {
if (open && isNarrowScreenSize && usingFullScreenOnNarrow) {
const bodyOverflowStyle = document.body.style.overflow || '';
// If the body is already set to overflow: hidden, it likely means
// that there is already a modal open. In that case, we should bail
// so we don't re-enable scroll after the second dialog is closed.
if (bodyOverflowStyle === 'hidden') {
return;
}
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = bodyOverflowStyle;
};
}
}, [isNarrowScreenSize, open, usingFullScreenOnNarrow]);
React.useEffect(() => {
if (open) {
if (items.length === 0 && !(isLoading || loading)) {
// we need to wait for the listContainerElement to disappear before announcing no items, otherwise it will be interrupted
setNeedsNoItemsAnnouncement(true);
}
}
if (loadingManagedExternally) {
if (items.length > 0) {
setDataLoadedOnce(true);
}
return;
}
if (isLoading || items.length > 0) {
setIsLoading(false);
setDataLoadedOnce(true);
}
if (loadingDelayTimeoutId.current) {
safeClearTimeout(loadingDelayTimeoutId.current);
}
// Only fire this effect if items have changed
// eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [items]);
React.useEffect(() => {
if (inputRef !== null && inputRef !== void 0 && inputRef.current) {
const ref = inputRef.current;
// We would normally expect AnchoredOverlay's focus trap to automatically focus the input,
// but for some reason the ref isn't populated until _after_ the panel is open, which is
// too late. So, we focus manually here.
if (open) {
ref.focus();
}
}
}, [inputRef, open]);
// Manage loading announcements when loadingManagedExternally
React.useEffect(() => {
if (loadingManagedExternally) {
if (isLoading) {
// Delay the announcement a bit, just in case the loading is quick
loadingDelayTimeoutId.current = safeSetTimeout(() => {
announceLoading();
}, LONG_DELAY_MS);
} else {
// If loading is done, we can clear the loading announcement
if (loadingDelayTimeoutId.current) {
safeClearTimeout(loadingDelayTimeoutId.current);
}
}
}
}, [isLoading, loadingManagedExternally, safeSetTimeout, safeClearTimeout]);
// Populate panel with items on first open
React.useEffect(() => {
if (loadingManagedExternally) return;
// If data was already loaded once, do nothing
if (dataLoadedOnce) return;
// Only load data when the panel is open
if (open) {
// Only trigger filter change event if there are no items
if (items.length === 0) {
// Trigger filter event to populate panel on first open
onFilterChange(filterValue, null);
}
}
}, [open, dataLoadedOnce, onFilterChange, filterValue, items, loadingManagedExternally, listContainerElement]);
React.useEffect(() => {
if (!window.visualViewport || !open || !isNarrowScreenSize) {
return;
}
initialHeightRef.current = window.visualViewport.height;
initialScaleRef.current = window.visualViewport.scale;
const handleViewportChange = index.debounce(() => {
if (window.visualViewport) {
const currentScale = window.visualViewport.scale;
const isZooming = currentScale !== initialScaleRef.current;
if (!isZooming) {
const currentHeight = window.visualViewport.height;
const keyboardVisible = initialHeightRef.current - currentHeight > KEYBOARD_VISIBILITY_THRESHOLD;
setIsKeyboardVisible(keyboardVisible);
setAvailablePanelHeight(keyboardVisible ? currentHeight : undefined);
}
}
}, 100);
// keeping this check to satisfy typescript but need eslint to ignore redundancy rule
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (window.visualViewport) {
// Using visualViewport to more reliably detect viewport changes across different browsers, which specifically requires these listeners
// eslint-disable-next-line github/prefer-observers
window.visualViewport.addEventListener('resize', handleViewportChange);
// eslint-disable-next-line github/prefer-observers
window.visualViewport.addEventListener('scroll', handleViewportChange);
}
return () => {
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', handleViewportChange);
window.visualViewport.removeEventListener('scroll', handleViewportChange);
}
handleViewportChange.cancel();
};
}, [open, isNarrowScreenSize]);
React.useEffect(() => {
const announceNotice = async () => {
if (!noticeRef.current) return;
const liveRegion = document.querySelector('live-region');
liveRegion === null || liveRegion === void 0 ? void 0 : liveRegion.clear();
await liveRegionElement.announceFromElement(noticeRef.current, {
from: liveRegion ? liveRegion : undefined
});
};
if (open && notice) {
announceNotice();
}
}, [notice, open]);
const anchorRef = useProvidedRefOrCreate.useProvidedRefOrCreate(externalAnchorRef);
const onOpen = React.useCallback(gesture => onOpenChange(true, gesture), [onOpenChange]);
const onCancelRequested = React.useCallback(() => {
onOpenChange(false, 'cancel');
}, [onOpenChange]);
const onClose = React.useCallback(gesture => {
// Clicking outside should cancel the selection only on modals
if (variant === 'modal' && gesture === 'click-outside') {
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
}
if (gesture === 'close') {
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
onCancelRequested();
} else {
onOpenChange(false, gesture);
}
}, [onOpenChange, variant, onCancel, onCancelRequested]);
const renderMenuAnchor = React.useMemo(() => {
if (renderAnchor === null) {
return null;
}
const selectedItems = Array.isArray(selected) ? selected : [...(selected ? [selected] : [])];
return props => {
return renderAnchor({
...props,
children: selectedItems.length ? selectedItems.map(item => item.text).join(', ') : placeholder
});
};
}, [placeholder, renderAnchor, selected]);
const isItemCurrentlySelected = React.useCallback(item => {
// For multi-select, we just need to check if the item is in the selected array
if (isMultiSelectVariant(selected)) {
return doesItemsIncludeItem(selected, item);
}
// For single-select modal, there is an intermediate state when the user has selected
// an item but has not yet saved the selection. We need to check for this state.
if (isSingleSelectModal) {
return (intermediateSelected === null || intermediateSelected === void 0 ? void 0 : intermediateSelected.id) !== undefined ? intermediateSelected.id === item.id : intermediateSelected === item;
}
// For single-select anchored, we just need to check if the item is the selected item
return (selected === null || selected === void 0 ? void 0 : selected.id) !== undefined ? selected.id === item.id : selected === item;
}, [selected, intermediateSelected, isSingleSelectModal]);
const itemsToRender = React.useMemo(() => {
return items.map(item => {
return {
...item,
role: 'option',
id: item.id,
selected: 'selected' in item && item.selected === undefined ? undefined : isItemCurrentlySelected(item),
onAction: (itemFromAction, event) => {
var _item$onAction;
(_item$onAction = item.onAction) === null || _item$onAction === void 0 ? void 0 : _item$onAction.call(item, itemFromAction, event);
if (event.defaultPrevented) {
return;
}
if (isMultiSelectVariant(selected)) {
const otherSelectedItems = selected.filter(selectedItem => !areItemsEqual(selectedItem, item));
const newSelectedItems = doesItemsIncludeItem(selected, item) ? otherSelectedItems : [...otherSelectedItems, item];
const multiSelectOnChange = onSelectedChange;
multiSelectOnChange(newSelectedItems);
return;
}
if (isSingleSelectModal) {
if ((intermediateSelected === null || intermediateSelected === void 0 ? void 0 : intermediateSelected.id) === item.id) {
// if the item is already selected, we need to unselect it
setIntermediateSelected(undefined);
} else {
setIntermediateSelected(item);
}
return;
}
// single select anchored, direct save on click
const singleSelectOnChange = onSelectedChange;
singleSelectOnChange(item === selected ? undefined : item);
onClose('selection');
}
};
}).sort((itemA, itemB) => {
if (shouldOrderSelectedFirst) {
// itemA is selected (for sorting purposes) if an object in selectedOnSort matches every property of itemA, except for the selected property
const itemASelected = selectedOnSort.some(item => Object.entries(item).every(([key, value]) => {
if (key === 'selected') {
return true;
}
return itemA[key] === value;
}));
// itemB is selected (for sorting purposes) if an object in selectedOnSort matches every property of itemA, except for the selected property
const itemBSelected = selectedOnSort.some(item => Object.entries(item).every(([key, value]) => {
if (key === 'selected') {
return true;
}
return itemB[key] === value;
}));
// order selected items first
if (itemASelected > itemBSelected) {
return -1;
} else if (itemASelected < itemBSelected) {
return 1;
}
}
return 0;
});
}, [onClose, onSelectedChange, items, selected, isItemCurrentlySelected, isSingleSelectModal, intermediateSelected, shouldOrderSelectedFirst, selectedOnSort]);
if (prevItems !== items) {
setPrevItems(items);
if (prevItems.length === 0 && items.length > 0) {
resetSort();
}
}
if (open !== prevOpen) {
setPrevOpen(open);
resetSort();
}
const focusTrapSettings = {
initialFocusRef: inputRef || undefined
};
const extendedTextInputProps = React.useMemo(() => {
return {
sx: {
m: 2
},
contrast: true,
leadingVisual: octiconsReact.SearchIcon,
'aria-label': inputLabel,
...textInputProps
};
}, [inputLabel, textInputProps]);
const loadingType = () => {
if (dataLoadedOnce) {
return FilteredActionListLoaders.FilteredActionListLoadingTypes.input;
} else {
if (initialLoadingType === 'spinner') {
return FilteredActionListLoaders.FilteredActionListLoadingTypes.bodySpinner;
} else {
return FilteredActionListLoaders.FilteredActionListLoadingTypes.bodySkeleton;
}
}
};
const iconForNoticeVariant = {
info: /*#__PURE__*/jsxRuntime.jsx(octiconsReact.InfoIcon, {
size: 16
}),
warning: /*#__PURE__*/jsxRuntime.jsx(octiconsReact.AlertIcon, {
size: 16
}),
error: /*#__PURE__*/jsxRuntime.jsx(octiconsReact.StopIcon, {
size: 16
})
};
function getMessage() {
if (items.length === 0 && !message) {
return DefaultEmptyMessage;
} else if (message) {
return /*#__PURE__*/jsxRuntime.jsx(SelectPanelMessage.SelectPanelMessage, {
title: message.title,
variant: message.variant,
icon: message.icon,
action: message.action,
children: message.body
});
}
}
// We add permanent save and cancel buttons on:
// - modals
const showPermanentCancelSaveButtons = variant === 'modal';
// The next two could be collapsed, left them separate for readability
// We add a responsive save and cancel button on:
// - anchored panels with multi select if there is onCancel
const showResponsiveCancelSaveButtons = variant !== 'modal' && usingFullScreenOnNarrow && isMultiSelectVariant(selected) && onCancel !== undefined;
// The responsive save and close button is only covering a very specific case:
// - anchored panel with multi select if there is no onCancel.
// This variant should disappear in the future, once onCancel is required,
// but for now we need to support it so there is a user friendly way to close the panel.
const showResponsiveSaveAndCloseButton = variant !== 'modal' && usingFullScreenOnNarrow && isMultiSelectVariant(selected) && onCancel === undefined;
// If there is any element in the footer, we render it.
const renderFooter = secondaryAction !== undefined || showPermanentCancelSaveButtons || showResponsiveSaveAndCloseButton || showResponsiveCancelSaveButtons;
// If there's any permanent elements in the footer, we show it always.
// The save button is only shown on small screens.
const displayFooter = secondaryAction !== undefined || showPermanentCancelSaveButtons ? 'always' : showResponsiveSaveAndCloseButton || showResponsiveCancelSaveButtons ? 'only-small' : undefined;
const stretchSecondaryAction = showResponsiveSaveAndCloseButton || showResponsiveCancelSaveButtons ? 'only-big' : showPermanentCancelSaveButtons ? 'never' : 'always';
const stretchSaveButton = showResponsiveSaveAndCloseButton && secondaryAction === undefined ? 'only-small' : 'never';
/*
* SelectPanel uses two close button implementations for different use cases:
*
* 1. AnchoredOverlay close button - Enabled on narrow screens (showXCloseIcon logic)
*
* 2. SelectPanel modal close button - Used for modal variant on wider screens
* (variant === 'modal' && !isNarrowScreenSize logic below)
*
* The dual approach handles different responsive behaviors: AnchoredOverlay manages
* close functionality for narrow fullscreen, while SelectPanel handles modal close on desktop.
*/
const showXCloseIcon = (onCancel !== undefined || !isMultiSelectVariant(selected)) && usingFullScreenOnNarrow;
const currentResponsiveVariant = useResponsiveValue.useResponsiveValue(usingFullScreenOnNarrow ? {
regular: 'anchored',
narrow: 'fullscreen'
} : undefined, 'anchored');
return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
children: [/*#__PURE__*/jsxRuntime.jsx(AnchoredOverlay.AnchoredOverlay, {
renderAnchor: renderMenuAnchor,
anchorRef: anchorRef,
align: align,
open: open,
onOpen: onOpen,
onClose: onClose,
overlayProps: {
role: 'dialog',
'aria-labelledby': titleId,
'aria-describedby': subtitle ? subtitleId : undefined,
...overlayProps,
...(variant === 'modal' ? {
/* override AnchoredOverlay position */
top: '50vh',
left: '50vw',
anchorSide: undefined
} : {}),
style: {
/* override AnchoredOverlay position */
transform: variant === 'modal' ? 'translate(-50%, -50%)' : undefined,
// set maxHeight based on calculated availablePanelHeight when keyboard is visible
...(isKeyboardVisible ? {
maxHeight: availablePanelHeight !== undefined ? `${availablePanelHeight}px` : 'auto'
} : {})
}
},
focusTrapSettings: focusTrapSettings,
focusZoneSettings: focusZoneSettings,
height: height,
width: width,
anchorId: id,
variant: usingFullScreenOnNarrow ? {
regular: 'anchored',
narrow: 'fullscreen'
} : undefined,
pinPosition: !height,
className: SelectPanel_module.Overlay,
displayCloseButton: showXCloseIcon,
closeButtonProps: {
'aria-label': 'Cancel and close'
},
children: /*#__PURE__*/jsxRuntime.jsxs("div", {
className: SelectPanel_module.Wrapper,
"data-variant": variant,
children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
className: SelectPanel_module.Header,
"data-variant": currentResponsiveVariant,
children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
children: [/*#__PURE__*/jsxRuntime.jsx(Heading, {
as: "h1",
id: titleId,
className: SelectPanel_module.Title,
children: title
}), subtitle ? /*#__PURE__*/jsxRuntime.jsx("div", {
id: subtitleId,
className: SelectPanel_module.Subtitle,
children: subtitle
}) : null]
}), variant === 'modal' && !isNarrowScreenSize ? /*#__PURE__*/jsxRuntime.jsx(IconButton.IconButton, {
type: "button",
variant: "invisible",
icon: octiconsReact.XIcon,
"aria-label": "Cancel and close",
className: SelectPanel_module.ResponsiveCloseButton,
onClick: () => {
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
onCancelRequested();
}
}) : null]
}), notice && /*#__PURE__*/jsxRuntime.jsxs("div", {
ref: noticeRef,
"data-variant": notice.variant,
className: SelectPanel_module.Notice,
children: [iconForNoticeVariant[notice.variant], /*#__PURE__*/jsxRuntime.jsx("div", {
children: notice.text
})]
}), /*#__PURE__*/jsxRuntime.jsx(FilteredActionList.FilteredActionList, {
filterValue: filterValue,
onFilterChange: onFilterChange,
onListContainerRefChanged: onListContainerRefChanged,
onInputRefChanged: onInputRefChanged,
placeholderText: placeholderText,
...listProps,
variant: (_listProps$groupMetad = listProps.groupMetadata) !== null && _listProps$groupMetad !== void 0 && _listProps$groupMetad.length ? 'horizontal-inset' : 'inset',
role: "listbox"
// browsers give aria-labelledby precedence over aria-label so we need to make sure
// we don't accidentally override props.aria-label
,
"aria-labelledby": listProps['aria-label'] ? undefined : titleId,
"aria-multiselectable": isMultiSelectVariant(selected) ? 'true' : 'false',
selectionVariant: isSingleSelectModal ? 'radio' : isMultiSelectVariant(selected) ? 'multiple' : 'single',
items: itemsToRender,
textInputProps: extendedTextInputProps,
loading: loading || isLoading && !message,
loadingType: loadingType(),
onSelectAllChange: showSelectAll ? handleSelectAllChange : undefined
// hack because the deprecated ActionList does not support this prop
,
message: getMessage(),
messageText: {
title: (message === null || message === void 0 ? void 0 : message.title) || EMPTY_MESSAGE.title,
description: typeof (message === null || message === void 0 ? void 0 : message.body) === 'string' ? message.body : EMPTY_MESSAGE.description
},
fullScreenOnNarrow: usingFullScreenOnNarrow
// inheriting height and maxHeight ensures that the FilteredActionList is never taller
// than the Overlay (which would break scrolling the items)
,
sx: sx,
className: clsx.clsx(className, SelectPanel_module.FilteredActionList)
}), footer ? /*#__PURE__*/jsxRuntime.jsx("div", {
className: SelectPanel_module.Footer,
children: footer
}) : renderFooter ? /*#__PURE__*/jsxRuntime.jsxs("div", {
"data-display-footer": displayFooter,
"data-stretch-secondary-action": stretchSecondaryAction,
"data-stretch-save-button": stretchSaveButton,
className: clsx.clsx(SelectPanel_module.Footer, SelectPanel_module.ResponsiveFooter),
children: [/*#__PURE__*/jsxRuntime.jsx("div", {
"data-stretch-secondary-action": stretchSecondaryAction,
className: SelectPanel_module.SecondaryAction,
children: secondaryAction
}), showPermanentCancelSaveButtons || showResponsiveCancelSaveButtons ? /*#__PURE__*/jsxRuntime.jsxs("div", {
"data-stretch-save-button": stretchSaveButton,
className: clsx.clsx(SelectPanel_module.CancelSaveButtons, {
[SelectPanel_module.ResponsiveSaveButton]: showResponsiveCancelSaveButtons
}),
children: [/*#__PURE__*/jsxRuntime.jsx(Button.ButtonComponent, {
size: "medium",
onClick: () => {
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
onCancelRequested();
},
children: "Cancel"
}), /*#__PURE__*/jsxRuntime.jsx(Button.ButtonComponent, {
block: onCancel === undefined,
variant: "primary",
size: "medium",
onClick: () => {
if (isSingleSelectModal) {
const singleSelectOnChange = onSelectedChange;
singleSelectOnChange(intermediateSelected);
}
onClose(variant === 'modal' ? 'selection' : 'click-outside');
},
children: "Save"
})]
}) : null, showResponsiveSaveAndCloseButton ? /*#__PURE__*/jsxRuntime.jsx("div", {
className: SelectPanel_module.ResponsiveSaveButton,
"data-stretch-save-button": stretchSaveButton,
children: /*#__PURE__*/jsxRuntime.jsx(Button.ButtonComponent, {
block: true,
variant: "primary",
size: "medium",
onClick: () => {
onClose('click-outside');
},
children: "Save and close"
})
}) : null]
}) : null]
})
}), variant === 'modal' && open ? /*#__PURE__*/jsxRuntime.jsx("div", {
className: SelectPanel_module.Backdrop
}) : null]
});
}
const SecondaryButton = props => {
return /*#__PURE__*/jsxRuntime.jsx(Button.ButtonComponent, {
block: true,
...props,
children: props.children
});
};
SecondaryButton.displayName = "SecondaryButton";
const SecondaryLink = props => {
return /*#__PURE__*/jsxRuntime.jsx(LinkButton.LinkButton, {
...props,
variant: "invisible",
block: true,
children: props.children
});
};
SecondaryLink.displayName = "SecondaryLink";
const SelectPanel = Object.assign(Panel, {
SecondaryActionButton: SecondaryButton,
SecondaryActionLink: SecondaryLink
});
exports.SelectPanel = SelectPanel;
;