UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

485 lines (484 loc) 17.3 kB
"use client"; import _extends from "@babel/runtime-corejs3/helpers/esm/extends"; import _defineProperty from "@babel/runtime-corejs3/helpers/esm/defineProperty"; var _Hr; import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import Context from "../../shared/Context.js"; import { warn, isTrue, makeUniqueId, validateDOMAttributes, dispatchCustomElementEvent, extendPropsWithContextInClassComponent, keycode } from "../../shared/component-helper.js"; import HeightAnimation from "../height-animation/HeightAnimation.js"; import { skeletonDOMAttributes, createSkeletonClass } from "../skeleton/SkeletonHelper.js"; import { spacingPropTypes, createSpacingClasses } from "../space/SpacingHelper.js"; import Hr from "../../elements/hr/Hr.js"; import GlobalStatusController, { GlobalStatusInterceptor } 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"; export default class GlobalStatus extends React.PureComponent { static getIcon({ state, icon, icon_size }) { if (typeof icon === 'string') { let IconToLoad = icon; switch (state) { case 'info': case 'information': case 'success': IconToLoad = InfoIcon; break; case 'warning': case 'warn': IconToLoad = WarnIcon; break; case 'error': default: IconToLoad = ErrorIcon; } icon = React.createElement(Icon, { icon: React.createElement(IconToLoad, { state: state }), size: icon_size, inheritColor: false }); } return icon; } static getDerivedStateFromProps(props, state) { if (state._items !== props.items) { state.globalStatus = GlobalStatusProvider.combineMessages([state.globalStatus, props]); } state._items = props.items; return state; } constructor(props) { super(props); _defineProperty(this, "state", { globalStatus: null, isActive: false }); _defineProperty(this, "isPassive", () => { return this.props.show !== 'auto' && isTrue(this.props.show) === false; }); _defineProperty(this, "setVisible", () => { if (this.isPassive()) { return; } this.setState({ isActive: true }); }); _defineProperty(this, "setHidden", () => { this.setState({ isActive: false }); }); _defineProperty(this, "onKeyDownHandler", e => { switch (keycode(e)) { case 'escape': case 'esc': e.preventDefault(); this.closeHandler(); break; } }); _defineProperty(this, "closeHandler", () => { this.provider.add({ status_id: 'internal-close', show: false }); if (this.initialActiveElement) { try { this.initialActiveElement.focus(); this.initialActiveElement = null; } catch (e) { warn(e); } } dispatchCustomElementEvent(this._globalStatus, 'on_hide', this._globalStatus); }); _defineProperty(this, "gotoItem", (event, item) => { event.persist(); const keyCode = keycode(event); if (item.item_id && typeof document !== 'undefined' && typeof window !== 'undefined' && keyCode === 'space' || keyCode === 'enter' || typeof keyCode === 'undefined') { event.preventDefault(); try { const element = document.getElementById(item.item_id); 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); } } }); _defineProperty(this, "itemsRenderHandler", ({ status_anchor_text, lang }) => (item, i) => { const text = item !== null && item !== void 0 && item.text ? item.text : typeof item === 'string' ? item : null; if (!text) { return null; } const id = item.id || item.item_id ? `${item.item_id}-${i}` : makeUniqueId(); let anchorText = status_anchor_text; if (React.isValidElement(item.status_anchor_label)) { anchorText = React.createElement(React.Fragment, null, typeof status_anchor_text === 'string' ? status_anchor_text.replace('%s', '').trim() : status_anchor_text, ' ', item.status_anchor_label); } else { anchorText = String(item.status_anchor_text || status_anchor_text).replace('%s', item.status_anchor_label || '').replace(/[: ]$/g, ''); } const useAutolink = item.item_id && isTrue(item.status_anchor_url); return React.createElement("li", { key: i }, React.createElement("p", { id: id, className: "dnb-p" }, text), item && (useAutolink || item.status_anchor_url) && React.createElement("a", { className: "dnb-anchor", "aria-describedby": id, lang: lang, href: useAutolink ? `#${item.item_id}` : item.status_anchor_url, onClick: e => this.gotoItem(e, item), onKeyDown: e => this.gotoItem(e, item) }, anchorText)); }); _defineProperty(this, "onAnimationStart", state => { this.setState({ isAnimating: true }); switch (state) { case 'opening': this.scrollToStatus(); } }); _defineProperty(this, "onAnimationEnd", state => { switch (state) { case 'opened': this.setFocus(); dispatchCustomElementEvent(this._globalStatus, 'on_open', this._globalStatus); break; case 'adjusted': if (!isTrue(this.props.omit_set_focus_on_update)) { this.setFocus(); } dispatchCustomElementEvent(this._globalStatus, 'on_adjust', this._globalStatus); break; case 'closed': dispatchCustomElementEvent(this._globalStatus, 'on_close', this._globalStatus); break; } }); _defineProperty(this, "onOpen", isOpened => { if (isOpened) { dispatchCustomElementEvent(this._globalStatus, 'on_show', this._globalStatus); } }); this._wrapperRef = React.createRef(); this.provider = GlobalStatusProvider.create(props.id); this.state.globalStatus = this._globalStatus = this.provider.init(props); if (isTrue(props.show)) { this.state.isActive = true; } this.provider.onUpdate(globalStatus => { if (globalStatus.on_close) { this._globalStatus = globalStatus; } this.setState({ globalStatus }); if (isTrue(this.props.autoclose) && this._hadContent && !this.hasContent(globalStatus) && !isTrue(this.props.show) || typeof globalStatus.show !== 'undefined' && !isTrue(globalStatus.show)) { this.setHidden(); } else if (isTrue(this.props.show) || typeof globalStatus.show !== 'undefined' && isTrue(globalStatus.show)) { this._hadContent = this.hasContent(globalStatus); this.setVisible(); } }); this.initialActiveElement = null; } componentWillUnmount() { clearTimeout(this._scrollToStatusTimeout); this.provider.empty(); } componentDidUpdate(prevProps) { if (prevProps !== this.props) { const globalStatus = extendPropsWithContextInClassComponent(this.props, GlobalStatus.defaultProps, this.context.globalStatus); this.setState({ globalStatus }); } if (prevProps.show !== this.props.show) { if (isTrue(this.props.show)) { this.setVisible(); } else { this.setHidden(); } } } hasContent(globalStatus) { var _globalStatus$items; return Boolean(((_globalStatus$items = globalStatus.items) === null || _globalStatus$items === void 0 ? void 0 : _globalStatus$items.length) > 0 || globalStatus.text); } correctStatus(state) { switch (state) { case 'information': state = 'info'; break; } return state; } setFocus() { if (typeof document !== 'undefined' && document.activeElement !== this._wrapperRef.current) { this.initialActiveElement = document.activeElement; } if (this._wrapperRef.current && !isTrue(this.props.omit_set_focus)) { this._wrapperRef.current.focus({ preventScroll: true }); } } async scrollToStatus(isDone = null) { if (typeof window === 'undefined' || isTrue(this.state.globalStatus.autoscroll) === false) { return; } try { const element = this._wrapperRef.current; this._scrollToStatusTimeout = isElementVisible(element, isDone); if (element && typeof element.scrollIntoView === 'function') { await wait(1); element.scrollIntoView({ block: 'center', behavior: 'smooth' }); } else { const top = element.offsetTop; if (window.scrollTo) { window.scrollTo({ top, behavior: 'smooth' }); } else { window.scrollTop = top; } } } catch (e) { warn('GlobalStatus: Could not scroll into view!', e); } } render() { var _this$context; const { isActive } = this.state; const fallbackProps = extendPropsWithContextInClassComponent(this.props, GlobalStatus.defaultProps, this.context.getTranslation(this.props).GlobalStatus); const props = extendPropsWithContextInClassComponent(GlobalStatusProvider.combineMessages([this.context.globalStatus, this.state.globalStatus]), GlobalStatus.defaultProps, fallbackProps); const lang = this.context.locale; const { title, default_title, state: rawState, className, no_animation, hide_close_button, close_text, status_anchor_text, skeleton, id, item, items, autoclose, show, delay, autoscroll, text, icon, icon_size, children, ...attributes } = props; const wrapperParams = { id, key: 'global-status', className: classnames("dnb-global-status__wrapper dnb-no-focus", createSkeletonClass('font', skeleton, this.context), createSpacingClasses(props), className), 'aria-live': isActive ? 'assertive' : 'off', onKeyDown: this.onKeyDownHandler, tabIndex: '-1' }; const state = this.correctStatus(rawState); const iconToRender = GlobalStatus.getIcon({ state, icon: icon || fallbackProps.icon, icon_size: icon_size || fallbackProps.icon_size, theme: ((_this$context = this.context) === null || _this$context === void 0 || (_this$context = _this$context.theme) === null || _this$context === void 0 ? void 0 : _this$context.name) || 'ui' }); const titleToRender = title || fallbackProps.title || fallbackProps.default_title; const noAnimation = isTrue(no_animation); const itemsToRender = props.items || []; const contentToRender = props.text || props.children; const params = { className: `dnb-global-status dnb-global-status--${state}`, ...attributes }; skeletonDOMAttributes(params, skeleton, this.context); validateDOMAttributes(this.props, params); const renderedItems = itemsToRender.length > 0 && React.createElement("ul", { className: "dnb-ul" }, itemsToRender.map(this.itemsRenderHandler({ status_anchor_text, lang }))); const hasContent = renderedItems || contentToRender; const renderedContent = React.createElement(React.Fragment, null, title !== false && React.createElement(React.Fragment, null, React.createElement("div", { className: "dnb-global-status__title", role: titleToRender !== null && titleToRender !== void 0 && titleToRender.type ? undefined : 'paragraph', lang: lang }, React.createElement("span", { className: "dnb-global-status__icon" }, iconToRender), titleToRender, !isTrue(hide_close_button) && React.createElement(Button, { text: close_text, title: close_text, variant: state === 'success' ? 'secondary' : 'tertiary', className: "dnb-global-status__close-button", icon: "close", on_click: this.closeHandler, size: "medium", icon_position: "left" })), hasContent && React.createElement("div", { className: "dnb-global-status__message" }, React.createElement("div", { className: 'dnb-global-status__message__content' + (!renderedItems ? " dnb-space__bottom--small" : "") }, typeof contentToRender === 'string' ? React.createElement("p", { className: "dnb-p" }, contentToRender) : contentToRender, renderedItems)), _Hr || (_Hr = React.createElement(Hr, { breakout: true })))); return React.createElement("div", _extends({}, wrapperParams, { ref: this._wrapperRef }), React.createElement("section", params, React.createElement(HeightAnimation, { className: "dnb-global-status__shell", duration: 800, delay: delay, open: isActive, animate: !noAnimation, onAnimationEnd: this.onAnimationEnd, onAnimationStart: this.onAnimationStart, onOpen: this.onOpen }, React.createElement(Section, { element: "div", variant: state, className: "dnb-global-status__content" }, renderedContent)))); } } _defineProperty(GlobalStatus, "contextType", Context); _defineProperty(GlobalStatus, "defaultProps", { id: 'main', status_id: 'status-main', title: null, default_title: null, text: null, items: [], icon: 'error', icon_size: 'medium', state: 'error', show: 'auto', autoscroll: true, autoclose: true, no_animation: false, close_text: 'Lukk', hide_close_button: false, omit_set_focus: false, omit_set_focus_on_update: true, delay: null, status_anchor_text: null, skeleton: null, className: null, children: null, on_adjust: null, on_open: null, on_show: null, on_close: null, on_hide: null }); process.env.NODE_ENV !== "production" ? GlobalStatus.propTypes = { id: PropTypes.string, status_id: PropTypes.string, title: PropTypes.oneOfType([PropTypes.node, PropTypes.bool]), default_title: PropTypes.string, text: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), items: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.array]), icon: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), icon_size: PropTypes.string, state: PropTypes.oneOf(['error', 'info', 'warning', 'success']), show: PropTypes.oneOf(['auto', true, false, 'true', 'false']), autoscroll: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), autoclose: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), no_animation: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), delay: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), close_text: PropTypes.node, hide_close_button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), omit_set_focus: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), omit_set_focus_on_update: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), status_anchor_text: PropTypes.node, skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), ...spacingPropTypes, className: PropTypes.string, children: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.node]), on_adjust: PropTypes.func, on_open: PropTypes.func, on_show: PropTypes.func, on_close: PropTypes.func, on_hide: PropTypes.func } : void 0; GlobalStatus.create = (...args) => new GlobalStatusInterceptor(...args); GlobalStatus.Update = GlobalStatus.create; GlobalStatus.Add = GlobalStatusController; GlobalStatus.Remove = GlobalStatusController.Remove; 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)); GlobalStatus._supportsSpacingProps = true; //# sourceMappingURL=GlobalStatus.js.map