UNPKG

@ui5/webcomponents-react

Version:

React Wrapper for UI5 Web Components and additional components

382 lines (379 loc) 15 kB
'use client'; import '@ui5/webcomponents-fiori/dist/illustrations/UnableToLoad.js'; import BarDesign from '@ui5/webcomponents/dist/types/BarDesign.js'; import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js'; import ListSelectionMode from '@ui5/webcomponents/dist/types/ListSelectionMode.js'; import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js'; import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js'; import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js'; import navDownIcon from '@ui5/webcomponents-icons/dist/navigation-down-arrow.js'; import searchIcon from '@ui5/webcomponents-icons/dist/search.js'; import { enrichEventWithDetails, useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base'; import { clsx } from 'clsx'; import { Children, cloneElement, forwardRef, isValidElement, useCallback, useEffect, useRef, useState } from 'react'; import { MANAGE, MY_VIEWS, SAVE, SAVE_AS, SEARCH, SEARCH_VARIANT, SELECT_VIEW } from '../../i18n/i18n-defaults.js'; import { stopPropagation } from '../../internal/stopPropagation.js'; import { VariantManagementContext } from '../../internal/VariantManagementContext.js'; import { Bar, Button, Icon, IllustratedMessage, Input, List, ResponsivePopover, Title } from '../../webComponents/index.js'; import { FlexBox } from '../FlexBox/index.js'; import { ManageViewsDialog } from './ManageViewsDialog.js'; import { SaveViewDialog } from './SaveViewDialog.js'; import { classNames, styleData } from './VariantManagement.module.css.js'; /** * The VariantManagement can be used to manage variants (views). You can use this component to create and maintain personalization changes. * * __Note:__ On the user interface, variants are generally referred to as "views". * * ### Matching header styles * * To ensure consistent header styles for different use-cases of the `VariantManagement`, we recommend setting the following styles to the `ui5-title` component: * * #### DynamicPage & ObjectPage * * - `font-family: var(--sapObjectHeader_Title_FontFamily);` * * __Header expanded__ * * - `font-size: var(--sapObjectHeader_Title_FontSize);` * * __Header collapsed/snapped__ * * - `font-size: var(--sapObjectHeader_Title_SnappedFontSize);` * * #### Tables * * - `font-size: var(--sapGroup_Title_FontSize);` * * #### Example * * ```css * .variantManagement [data-component-name="VariantManagementTitle"] { * font-family: var(--sapObjectHeader_Title_FontFamily); * font-size: var(--sapObjectHeader_Title_FontSize); * } * ``` * ```jsx * <VariantManagement className="variantManagement"> * ``` * */ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; const VariantManagement = /*#__PURE__*/forwardRef((props, ref) => { const i18nBundle = useI18nBundle('@ui5/webcomponents-react'); const { titleText = i18nBundle.getText(MY_VIEWS), className, style, placement = PopoverPlacement.Bottom, level = TitleLevel.H4, size = TitleLevel.H4, onSelect, closeOnItemSelect, disabled, onSaveAs, onSaveManageViews, showOnlyFavorites, inErrorState, hideShare, children, hideManageVariants, hideApplyAutomatically, hideSetAsDefault, hideCreatedBy, hideSaveAs, dirtyStateText = '*', dirtyState, onSave, onManageViewsCancel, onSaveViewCancel, ...rest } = props; useStylesheet(styleData, VariantManagement.displayName); const popoverRef = useRef(null); const [safeChildren, setSafeChildren] = useState(Children.toArray(children)); useEffect(() => { setSafeChildren(Children.toArray(children)); }, [children]); const [popoverOpen, setPopoverOpen] = useState(false); const [manageViewsDialogOpen, setManageViewsDialogOpen] = useState(false); const [saveAsDialogOpen, setSaveAsDialogOpen] = useState(false); const [selectedVariant, setSelectedVariant] = useState(() => { const currentSelectedVariant = safeChildren.find(item => /*#__PURE__*/isValidElement(item) && item.props.selected); if (currentSelectedVariant) { return { ...currentSelectedVariant.props, variantItem: currentSelectedVariant.ref }; } }); const [selectedSaveViewInputProps, setSelectedSaveViewInputProps] = useState(selectedVariant?.saveViewInputProps ?? {}); const handleClose = () => { setPopoverOpen(false); }; const handleManageClick = () => { setManageViewsDialogOpen(true); handleClose(); }; const handleManageClose = () => { setManageViewsDialogOpen(false); }; const handleOpenSaveAsDialog = () => { setSaveAsDialogOpen(true); handleClose(); }; const handleSaveAsClose = () => { setSaveAsDialogOpen(false); }; const handleSave = e => { if (typeof onSave === 'function') { onSave(enrichEventWithDetails(e, selectedVariant)); } }; const handleSaveView = (e, selectedVariant) => { if (typeof onSaveAs === 'function') { onSaveAs(enrichEventWithDetails(e, selectedVariant)); } setSelectedVariant(selectedVariant); if (!e.defaultPrevented) { handleSaveAsClose(); } }; const handleSaveManageViews = (e, payload) => { const { defaultView, updatedRows, deletedRows } = payload; const callbackProperties = { deletedVariants: [], prevVariants: [], updatedVariants: [], variants: [] }; setSafeChildren(prev => Children.toArray(prev.map(child => { if (! /*#__PURE__*/isValidElement(child)) { return false; } const castChild = child; let updatedProps = {}; const currentVariant = popoverRef.current.querySelector(`ui5-li[data-children="${castChild.props.children}"]`); callbackProperties.prevVariants.push(castChild.props); if (defaultView) { if (defaultView === castChild.props.children) { updatedProps.isDefault = true; } else if (castChild.props.isDefault) { updatedProps.isDefault = false; } } if (Object.keys(updatedRows).includes(castChild.props.children)) { const { currentVariant: _0, ...rest } = updatedRows[castChild.props.children]; updatedProps = { ...updatedProps, ...rest }; } if (deletedRows.has(castChild.props.children)) { callbackProperties.deletedVariants.push(castChild.props); return false; } if (Object.keys(updatedProps).length > 0) { callbackProperties.updatedVariants.push({ ...castChild.props, ...updatedProps, variantItem: currentVariant, prevVariant: { ...castChild.props } }); } callbackProperties.variants.push({ ...castChild.props, ...updatedProps, variantItem: currentVariant }); return /*#__PURE__*/cloneElement(castChild, updatedProps); }))); if (typeof onSaveManageViews === 'function') { onSaveManageViews(enrichEventWithDetails(e, callbackProperties)); } if (!e.defaultPrevented) { handleManageClose(); } }; const handleOpenVariantManagement = useCallback(e => { popoverRef.current.opener = e.target; setPopoverOpen(true); }, [popoverRef]); const handleCloseVariantManagement = e => { stopPropagation(e); setPopoverOpen(false); }; const searchText = i18nBundle.getText(SEARCH); const saveAsText = i18nBundle.getText(SAVE_AS); const manageText = i18nBundle.getText(MANAGE); const saveText = i18nBundle.getText(SAVE); const a11ySearchText = i18nBundle.getText(SEARCH_VARIANT); const selectViewText = i18nBundle.getText(SELECT_VIEW); const variantManagementClasses = clsx(classNames.container, disabled && classNames.disabled, className); const dirtyStateClasses = clsx(classNames.dirtyState, dirtyStateText !== '*' && classNames.dirtyStateText); const selectVariantEventRef = useRef(undefined); useEffect(() => { if (selectVariantEventRef.current) { if (typeof onSelect === 'function') { onSelect(enrichEventWithDetails(selectVariantEventRef.current, { selectedVariant })); selectVariantEventRef.current = undefined; } } }, [selectedVariant, onSelect]); useEffect(() => { const selectedChild = safeChildren.find(item => /*#__PURE__*/isValidElement(item) && item.props.children === selectedVariant?.children); setSelectedSaveViewInputProps(selectedChild?.props.saveViewInputProps ?? {}); }, [selectedVariant, safeChildren]); const handleVariantItemSelect = e => { setSelectedVariant({ ...e.detail.selectedItems[0].dataset, variantItem: e.detail.selectedItems[0] }); selectVariantEventRef.current = e; if (closeOnItemSelect) { handleClose(); } }; const variantNames = safeChildren.map(item => /*#__PURE__*/isValidElement(item) && typeof item.props?.children === 'string' ? item.props.children : ''); const [favoriteChildren, setFavoriteChildren] = useState(undefined); useEffect(() => { if (showOnlyFavorites) { setFavoriteChildren(safeChildren.filter(child => { return /*#__PURE__*/isValidElement(child) && (child.props.favorite || child.props.isDefault); })); } if (!showOnlyFavorites && favoriteChildren?.length > 0) { setFavoriteChildren(undefined); } }, [showOnlyFavorites, safeChildren]); const safeChildrenWithFavorites = favoriteChildren ?? safeChildren; const showInput = safeChildrenWithFavorites.length > 10; const [filteredChildren, setFilteredChildren] = useState(undefined); const [searchValue, setSearchValue] = useState(''); const handleSearchInput = e => { setSearchValue(e.target.value); setFilteredChildren(safeChildrenWithFavorites.filter(child => typeof child?.props?.children === 'string' && child.props.children.toLowerCase().includes(e.target.value.toLowerCase()))); }; useEffect(() => { if (filteredChildren) { setFilteredChildren(safeChildrenWithFavorites.filter(child => typeof child?.props?.children === 'string' && child.props.children.toLowerCase().includes(searchValue))); } }, [safeChildrenWithFavorites]); // todo: this applies if `readOnly` is set to `false` as well since the value is read via data-attribute and is therefore a string not a boolean const showSaveBtn = dirtyState && !selectedVariant?.hasOwnProperty('readOnly'); return /*#__PURE__*/_jsx("div", { className: variantManagementClasses, style: style, ...rest, ref: ref, children: /*#__PURE__*/_jsxs(VariantManagementContext.Provider, { value: { selectVariantItem: setSelectedVariant }, children: [/*#__PURE__*/_jsxs(FlexBox, { onClick: disabled ? undefined : handleOpenVariantManagement, children: [/*#__PURE__*/_jsx(Title, { level: level, size: size, className: classNames.title, "data-component-name": "VariantManagementTitle", children: selectedVariant?.children }), dirtyState && /*#__PURE__*/_jsx("div", { className: dirtyStateClasses, children: dirtyStateText })] }), /*#__PURE__*/_jsx(Button, { className: clsx(classNames.navDownBtn, 'ui5-content-density-compact'), tooltip: selectViewText, accessibleName: selectViewText, onClick: disabled ? undefined : handleOpenVariantManagement, design: ButtonDesign.Transparent, icon: navDownIcon, disabled: disabled }), /*#__PURE__*/_jsx(ResponsivePopover, { open: popoverOpen, className: classNames.popover, ref: popoverRef, headerText: titleText, placement: placement, footer: (showSaveBtn || !hideSaveAs || !hideManageVariants) && /*#__PURE__*/_jsx(Bar, { design: BarDesign.Footer, className: classNames.footer, endContent: /*#__PURE__*/_jsxs(_Fragment, { children: [!inErrorState && showSaveBtn && /*#__PURE__*/_jsx(Button, { onClick: handleSave, design: ButtonDesign.Emphasized, children: saveText }), !inErrorState && !hideSaveAs && /*#__PURE__*/_jsx(Button, { onClick: handleOpenSaveAsDialog, design: showSaveBtn ? ButtonDesign.Transparent : ButtonDesign.Emphasized, disabled: !selectedVariant || Object.keys(selectedVariant).length === 0, children: saveAsText }), !inErrorState && !hideManageVariants && /*#__PURE__*/_jsx(Button, { onClick: handleManageClick, design: showSaveBtn || !hideSaveAs ? ButtonDesign.Transparent : ButtonDesign.Emphasized, children: manageText })] }) }), onClose: handleCloseVariantManagement, children: inErrorState ? /*#__PURE__*/_jsx(IllustratedMessage, { name: IllustrationMessageType.UnableToLoad }) : /*#__PURE__*/_jsx(List, { onSelectionChange: handleVariantItemSelect, selectionMode: ListSelectionMode.Single, header: showInput ? /*#__PURE__*/_jsx("div", { className: classNames.searchInputContainer, tabIndex: -1, children: /*#__PURE__*/_jsx(Input, { className: classNames.searchInput, accessibleName: a11ySearchText, value: searchValue, placeholder: searchText, onInput: handleSearchInput, showClearIcon: true, icon: /*#__PURE__*/_jsx(Icon, { name: searchIcon, className: classNames.inputIcon }) }) }) : undefined, children: filteredChildren ?? safeChildrenWithFavorites }) }), manageViewsDialogOpen && /*#__PURE__*/_jsx(ManageViewsDialog, { onAfterClose: handleManageClose, onManageViewsCancel: onManageViewsCancel, handleSaveManageViews: handleSaveManageViews, showShare: !hideShare, showApplyAutomatically: !hideApplyAutomatically, showCreatedBy: !hideCreatedBy, showSetAsDefault: !hideSetAsDefault, variantNames: variantNames, showOnlyFavorites: showOnlyFavorites, children: safeChildren }), saveAsDialogOpen && /*#__PURE__*/_jsx(SaveViewDialog, { onSaveViewCancel: onSaveViewCancel, saveViewInputProps: selectedSaveViewInputProps, showShare: !hideShare, showApplyAutomatically: !hideApplyAutomatically, showSetAsDefault: !hideSetAsDefault, onAfterClose: handleSaveAsClose, handleSave: handleSaveView, selectedVariant: selectedVariant, variantNames: variantNames })] }) }); }); VariantManagement.displayName = 'VariantManagement'; export { VariantManagement };