UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

464 lines (463 loc) 15.6 kB
"use client"; var _Hr; import React, { useCallback, useContext, useRef, useState, createElement as _createElement } from 'react'; import clsx from 'clsx'; import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js"; import useMountEffect from "../../shared/helpers/useMountEffect.js"; import useUpdateEffect from "../../shared/helpers/useUpdateEffect.js"; import Context from "../../shared/Context.js"; import { warn, makeUniqueId, validateDOMAttributes, dispatchCustomElementEvent } from "../../shared/component-helper.js"; import { extendPropsWithContext } from "../../shared/helpers/extendPropsWithContext.js"; import HeightAnimation from "../height-animation/HeightAnimation.js"; import { skeletonDOMAttributes, createSkeletonClass } from "../skeleton/SkeletonHelper.js"; import { applySpacing } from "../space/SpacingUtils.js"; import Hr from "../../elements/hr/Hr.js"; import GlobalStatusController, { GlobalStatusInterceptor, GlobalStatusRemove } from "./GlobalStatusController.js"; import GlobalStatusProvider from "./GlobalStatusProvider.js"; import Icon from "../icon/Icon.js"; import { InfoIcon, ErrorIcon, WarnIcon } from "../form-status/FormStatus.js"; import Section from "../section/Section.js"; import Button from "../button/Button.js"; import Space from "../space/Space.js"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; const globalStatusDefaultProps = { id: 'main', statusId: 'status-main', title: null, defaultTitle: null, text: null, items: [], icon: 'error', iconSize: 'medium', state: 'error', show: 'auto', autoScroll: true, autoClose: true, noAnimation: false, closeText: 'Lukk', hideCloseButton: false, omitSetFocus: false, omitSetFocusOnUpdate: true, delay: null, statusAnchorText: null, skeleton: null, className: null, children: null, onAdjust: null, onOpen: null, onShow: null, onClose: null, onHide: null }; function getIcon({ state, icon, iconSize }) { if (typeof icon === 'string') { let IconToLoad = ErrorIcon; switch (state) { case 'information': case 'success': IconToLoad = InfoIcon; break; case 'warning': IconToLoad = WarnIcon; break; case 'error': default: IconToLoad = ErrorIcon; } icon = _jsx(Icon, { icon: _jsx(IconToLoad, { state: state }), size: iconSize, inheritColor: false }); } return icon; } function hasContent(globalStatus) { var _globalStatus$items; return Boolean((globalStatus === null || globalStatus === void 0 || (_globalStatus$items = globalStatus.items) === null || _globalStatus$items === void 0 ? void 0 : _globalStatus$items.length) > 0 || (globalStatus === null || globalStatus === void 0 ? void 0 : globalStatus.text)); } function GlobalStatusComponent(ownProps) { var _derivedGlobalStatus, _context$theme; const context = useContext(Context); const wrapperRef = useRef(null); const globalStatusRef = useRef(null); const hadContentRef = useRef(false); const initialActiveElementRef = useRef(null); const scrollToStatusTimeoutRef = useRef(null); const propsWithDefaults = { ...globalStatusDefaultProps, ...ownProps }; const propsRef = useRef(propsWithDefaults); propsRef.current = propsWithDefaults; const providerRef = useRef(null); const [providerGlobalStatus, setProviderGlobalStatus] = useState(() => { var _ownProps$show; const provider = GlobalStatusProvider.create(ownProps.id); providerRef.current = provider; const status = provider.init({ ...ownProps, show: (_ownProps$show = ownProps.show) !== null && _ownProps$show !== void 0 ? _ownProps$show : 'auto' }); globalStatusRef.current = status; return status; }); const [isActive, setIsActive] = useState(ownProps.show === true); const [prevItems, setPrevItems] = useState(ownProps.items); let derivedGlobalStatus = providerGlobalStatus; if (prevItems !== ownProps.items) { setPrevItems(ownProps.items); derivedGlobalStatus = GlobalStatusProvider.combineMessages([providerGlobalStatus, ownProps]); setProviderGlobalStatus(derivedGlobalStatus); } if (ownProps.state !== ((_derivedGlobalStatus = derivedGlobalStatus) === null || _derivedGlobalStatus === void 0 ? void 0 : _derivedGlobalStatus.state)) { derivedGlobalStatus = { ...derivedGlobalStatus, state: ownProps.state }; } useMountEffect(() => { const provider = providerRef.current; provider.onUpdate(status => { if (status.onClose) { globalStatusRef.current = status; } setProviderGlobalStatus(status); const props = propsRef.current; if (props.autoClose && hadContentRef.current && !hasContent(status) && props.show !== true || typeof status.show !== 'undefined' && status.show !== true) { setIsActive(false); } else if (props.show === true || typeof status.show !== 'undefined' && status.show === true) { hadContentRef.current = hasContent(status); if (props.show === 'auto' || props.show === true) { setIsActive(true); } } }); return () => { clearTimeout(scrollToStatusTimeoutRef.current); provider.empty(); }; }); useUpdateEffect(() => { if (ownProps.show === true) { setIsActive(true); } else { setIsActive(false); } }, [ownProps.show]); const setFocus = useCallback(() => { if (typeof document !== 'undefined' && document.activeElement !== wrapperRef.current) { initialActiveElementRef.current = document.activeElement; } if (wrapperRef.current && !propsRef.current.omitSetFocus) { wrapperRef.current.focus({ preventScroll: true }); } }, []); const closeHandler = useCallback(() => { providerRef.current.add({ statusId: 'internal-close', show: false }); if (initialActiveElementRef.current) { try { ; initialActiveElementRef.current.focus(); initialActiveElementRef.current = null; } catch (e) { warn(e); } } dispatchCustomElementEvent(globalStatusRef.current, 'onHide', globalStatusRef.current); }, []); const scrollToStatus = useCallback(async (isDone = null) => { if (typeof window === 'undefined' || propsRef.current.autoScroll === false) { return; } try { const element = wrapperRef.current; scrollToStatusTimeoutRef.current = isElementVisible(element, isDone); if (element && typeof element.scrollIntoView === 'function') { await wait(1); element.scrollIntoView({ block: 'center', behavior: 'smooth' }); } else { const top = element.offsetTop; window.scrollTo({ top, behavior: 'smooth' }); } } catch (e) { warn('GlobalStatus: Could not scroll into view!', e); } }, []); const gotoItem = useCallback((event, item) => { const key = event.key; if (item.itemId && typeof document !== 'undefined' && typeof window !== 'undefined' && key === ' ' || key === 'Enter' || key === undefined) { event.preventDefault(); try { const element = document.getElementById(item.itemId); if (!element) { return; } isElementVisible(element, elem => { try { elem.addEventListener('blur', e => { if (e.target.classList) { ; e.target.removeAttribute('tabindex'); } }); elem.classList.add('dnb-no-focus'); elem.setAttribute('tabindex', '-1'); elem.focus({ preventScroll: true }); } catch (e) { warn(e); } }); if (typeof element.scrollIntoView === 'function') { element.scrollIntoView({ block: 'center', behavior: 'smooth' }); } } catch (e) { warn(e); } } }, []); const onAnimationStart = useCallback(state => { switch (state) { case 'opening': scrollToStatus(); } }, [scrollToStatus]); const onAnimationEnd = useCallback(state => { switch (state) { case 'opened': setFocus(); dispatchCustomElementEvent(globalStatusRef.current, 'onOpen', globalStatusRef.current); break; case 'adjusted': if (!propsRef.current.omitSetFocusOnUpdate) { setFocus(); } dispatchCustomElementEvent(globalStatusRef.current, 'onAdjust', globalStatusRef.current); break; case 'closed': dispatchCustomElementEvent(globalStatusRef.current, 'onClose', globalStatusRef.current); break; } }, [setFocus]); const onOpen = useCallback(isOpened => { if (isOpened) { dispatchCustomElementEvent(globalStatusRef.current, 'onShow', globalStatusRef.current); } }, []); const fallbackProps = extendPropsWithContext(ownProps, globalStatusDefaultProps, context.getTranslation(ownProps).GlobalStatus); const props = extendPropsWithContext(GlobalStatusProvider.combineMessages([context === null || context === void 0 ? void 0 : context.globalStatus, derivedGlobalStatus]), globalStatusDefaultProps, fallbackProps); const lang = context.locale; const { title, defaultTitle, state: rawState, className, noAnimation, hideCloseButton, closeText, statusAnchorText, skeleton, id, item, items, autoClose, show, delay, autoScroll, text, omitSetFocus, omitSetFocusOnUpdate, statusId, icon, iconSize, children, removeOnUnmount, onAdjust: _onAdjust, onOpen: _onOpen, onShow: _onShow, onClose: _onClose, onHide: _onHide, ...attributes } = props; const wrapperParams = applySpacing(props, { id, className: clsx("dnb-global-status__wrapper dnb-no-focus", createSkeletonClass('font', skeleton, context), className), 'aria-live': isActive ? 'assertive' : 'off', onKeyDown: e => { if (e.key === 'Escape') { e.preventDefault(); closeHandler(); } }, tabIndex: -1 }); const state = rawState; const iconToRender = getIcon({ state, icon: icon || fallbackProps.icon, iconSize: iconSize || fallbackProps.iconSize, theme: (context === null || context === void 0 || (_context$theme = context.theme) === null || _context$theme === void 0 ? void 0 : _context$theme.name) || 'ui' }); const titleToRender = title || fallbackProps.title || fallbackProps.defaultTitle; const noAnimationUsed = noAnimation; const itemsToRender = items || []; const contentToRender = text || children; const params = { className: `dnb-global-status dnb-global-status--${state}`, ...attributes }; skeletonDOMAttributes(params, skeleton, context); validateDOMAttributes(ownProps, params); const itemsRenderHandler = (rawItem, i) => { const item = typeof rawItem === 'string' ? { text: rawItem } : rawItem; const text = item.text; if (!text) { return null; } const id = item.id || item.itemId ? `${item.itemId}-${i}` : makeUniqueId(); let anchorText = statusAnchorText; if (React.isValidElement(item.statusAnchorLabel)) { anchorText = _jsxs(_Fragment, { children: [typeof statusAnchorText === 'string' ? statusAnchorText.replace('%s', '').trim() : statusAnchorText, ' ', item.statusAnchorLabel] }); } else { anchorText = String(item.statusAnchorText || statusAnchorText).replace('%s', String(item.statusAnchorLabel || '')).replace(/[: ]$/g, ''); } const useAutolink = item.itemId && item.statusAnchorUrl === true; return _jsxs("li", { children: [_jsx("p", { id: id, className: "dnb-p", children: text }), item && (useAutolink || item.statusAnchorUrl) && _jsx("a", { className: "dnb-anchor", "aria-describedby": id, lang: lang, href: useAutolink ? `#${item.itemId}` : String(item.statusAnchorUrl), onClick: e => gotoItem(e, item), onKeyDown: e => gotoItem(e, item), children: anchorText })] }, i); }; const renderedItems = itemsToRender.length > 0 && _jsx("ul", { className: "dnb-ul", children: itemsToRender.map(itemsRenderHandler) }); const hasContentToRender = renderedItems || contentToRender; const renderedContent = _jsx(_Fragment, { children: title !== false && _jsxs(_Fragment, { children: [_jsxs("div", { className: "dnb-global-status__title", role: React.isValidElement(titleToRender) ? undefined : 'paragraph', lang: lang, children: [_jsx("span", { className: "dnb-global-status__icon", children: iconToRender }), titleToRender, !hideCloseButton && _jsx(Button, { text: closeText, title: typeof closeText === 'string' ? closeText : undefined, variant: state === 'success' ? 'secondary' : 'tertiary', className: "dnb-global-status__close-button", icon: "close", onClick: closeHandler, size: "medium", iconPosition: "left" })] }), hasContentToRender && _jsx("div", { className: "dnb-global-status__message", children: _jsxs(Space, { element: "div", bottom: !renderedItems ? 'small' : undefined, className: "dnb-global-status__message__content", children: [typeof contentToRender === 'string' ? _jsx("p", { className: "dnb-p", children: contentToRender }) : contentToRender, renderedItems] }) }), _Hr || (_Hr = _jsx(Hr, { breakout: true }))] }) }); return _createElement("div", { ...wrapperParams, ref: wrapperRef, key: "global-status" }, _jsx("section", { ...params, children: _jsx(HeightAnimation, { className: "dnb-global-status__shell", duration: 800, delay: delay, open: isActive, animate: !noAnimationUsed, onAnimationEnd: onAnimationEnd, onAnimationStart: onAnimationStart, onOpen: onOpen, children: _jsx(Section, { element: "div", variant: state, className: "dnb-global-status__content", children: renderedContent }) }) })); } GlobalStatusComponent.displayName = 'GlobalStatus'; const GlobalStatus = Object.assign(React.memo(GlobalStatusComponent), { create: props => new GlobalStatusInterceptor(props), Update: null, Add: GlobalStatusController, Remove: GlobalStatusRemove }); GlobalStatus.Update = GlobalStatus.create; withComponentMarkers(GlobalStatus, { _supportsSpacingProps: true }); export default GlobalStatus; const isElementVisible = (elem, callback, delayFallback = 1e3) => { if (typeof IntersectionObserver !== 'undefined') { const intersectionObserver = new IntersectionObserver(entries => { const [entry] = entries; if (entry.isIntersecting) { intersectionObserver.unobserve(elem); if (typeof callback === 'function') { callback(elem); } } }); intersectionObserver.observe(elem); } else { if (typeof callback === 'function') { return setTimeout(() => callback(elem), delayFallback); } } return null; }; const wait = duration => new Promise(r => setTimeout(r, duration)); //# sourceMappingURL=GlobalStatus.js.map