@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
464 lines (463 loc) • 15.6 kB
JavaScript
"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