UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

763 lines (730 loc) • 31.8 kB
'use strict'; 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;