@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
475 lines (474 loc) • 15 kB
JavaScript
"use client";
import React, { useCallback, useContext, useEffect, useRef } from 'react';
import useMountEffect from "../../shared/helpers/useMountEffect.js";
import useUpdateEffect from "../../shared/helpers/useUpdateEffect.js";
import clsx from 'clsx';
import withComponentMarkers from "../../shared/helpers/withComponentMarkers.js";
import { useTheme, Context } from "../../shared/index.js";
import useId from "../../shared/helpers/useId.js";
import { validateDOMAttributes, processChildren, extendPropsWithContext, removeUndefinedProps } from "../../shared/component-helper.js";
import HeightAnimation from "../height-animation/HeightAnimation.js";
import { applySpacing } from "../space/SpacingUtils.js";
import Icon from "../icon/Icon.js";
import GlobalStatusProvider from "../global-status/GlobalStatusProvider.js";
import { skeletonDOMAttributes, createSkeletonClass } from "../skeleton/SkeletonHelper.js";
import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js";
import ui from "../../style/themes/ui/properties.js";
import sbanken from "../../style/themes/sbanken/properties.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const properties = {
ui,
sbanken
};
const formStatusDefaultProps = {
show: true,
icon: 'error',
iconSize: 'medium',
size: 'default',
state: 'error'
};
function getContent(props) {
if (props.text) {
if (props.text === true) {
return null;
}
return props.text;
}
return processChildren(props);
}
function correctStatus(state) {
return state;
}
function getIcon({
state,
icon,
iconSize
}) {
if (typeof icon !== 'string') {
return icon;
}
let IconToLoad = ErrorIcon;
switch (correctStatus(state)) {
case 'information':
case 'success':
IconToLoad = InfoIcon;
break;
case 'warning':
IconToLoad = WarnIcon;
break;
case 'marketing':
IconToLoad = MarketingIcon;
break;
case 'error':
default:
IconToLoad = ErrorIcon;
}
return _jsx(Icon, {
icon: _jsx(IconToLoad, {
title: null,
state: state
}),
size: iconSize,
inheritColor: false
});
}
function FormStatusComponent(ownProps) {
const {
ref,
...restOwnProps
} = ownProps;
const context = useContext(Context);
const props = extendPropsWithContext({
...formStatusDefaultProps,
...removeUndefinedProps({
...restOwnProps
})
}, formStatusDefaultProps, {
skeleton: context?.skeleton
}, pickFormElementProps(context?.formElement), context?.FormStatus);
const {
id: idProp,
text,
globalStatus,
label,
show,
noAnimation,
state: rawStateProp,
widthElement,
widthSelector,
...restOfProps
} = props;
const id = useId(idProp);
const statusId = `${id}-gs`;
const elementRef = useRef(null);
const isMountedRef = useRef(false);
const contentCacheRef = useRef(null);
const stateCacheRef = useRef(undefined);
const ownPropsRef = useRef(restOwnProps);
ownPropsRef.current = restOwnProps;
useEffect(() => {
if (typeof ref === 'function') {
ref(elementRef.current);
} else if (ref) {
ref.current = elementRef.current;
}
});
const shouldAnimate = noAnimation === false;
const fillCache = useCallback(() => {
const content = shouldAnimate && getContent(ownPropsRef.current);
if (content && content !== contentCacheRef.current) {
contentCacheRef.current = content;
}
const stateVal = shouldAnimate && correctStatus(rawStateProp);
if (stateVal) {
stateCacheRef.current = stateVal;
}
}, [shouldAnimate, rawStateProp]);
const isReadyToGetVisible = useCallback((p = props) => {
return p.show && getContent(p) ? true : false;
}, [props.show, props.text, props.children]);
const updateWidth = useCallback(() => {
if (elementRef.current) {
var _widthElement$current;
setMaxWidthToElement({
element: elementRef.current,
widthElement: (_widthElement$current = widthElement?.current) !== null && _widthElement$current !== void 0 ? _widthElement$current : null,
widthSelector: widthSelector
});
}
}, [widthElement, widthSelector]);
const propsRef = useRef(props);
propsRef.current = props;
const isReadyToGetVisibleRef = useRef(isReadyToGetVisible);
isReadyToGetVisibleRef.current = isReadyToGetVisible;
const globalStatusRef = useRef(null);
useMountEffect(() => {
globalStatusRef.current = GlobalStatusProvider.init(globalStatus?.id || context?.FormStatus?.globalStatus?.id || context?.formElement?.globalStatus?.id || 'main', provider => {
const currentProps = propsRef.current;
if (currentProps.state === 'error' && isReadyToGetVisibleRef.current()) {
provider.add({
state: currentProps.state,
statusId,
item: {
itemId: id,
text: currentProps.globalStatus?.message || currentProps.text || currentProps.children,
statusAnchorLabel: currentProps.label,
statusAnchorUrl: true
},
...currentProps.globalStatus
});
}
});
return () => {
globalStatusRef.current?.remove(statusId);
};
});
useMountEffect(() => {
isMountedRef.current = true;
const init = () => {
if (isMountedRef.current) {
globalStatusRef.current?.isReady();
updateWidth();
fillCache();
}
};
if (document.readyState === 'complete') {
init();
} else if (typeof window !== 'undefined') {
window.addEventListener('load', init);
}
if (typeof window !== 'undefined') {
window.addEventListener('resize', updateWidth);
}
return () => {
isMountedRef.current = false;
if (typeof window !== 'undefined') {
window.removeEventListener('load', init);
window.removeEventListener('resize', updateWidth);
}
};
});
const prevPropsRef = useRef(restOwnProps);
useUpdateEffect(() => {
const prevProps = prevPropsRef.current;
prevPropsRef.current = restOwnProps;
const state = props.state;
const {
children
} = props;
if (prevProps.text !== text || prevProps.children !== children || prevProps.show !== show || prevProps.globalStatus?.show !== globalStatus?.show || prevProps.state !== state) {
fillCache();
if (state === 'error') {
if (show) {
globalStatusRef.current?.update(statusId, {
state,
statusId,
item: {
itemId: id,
text: globalStatus?.message || text || children,
statusAnchorLabel: label,
statusAnchorUrl: true
},
...globalStatus
}, {
preventRestack: true
});
} else if (!getContent(restOwnProps)) {
globalStatusRef.current?.remove(statusId);
}
}
if (isReadyToGetVisible()) {
updateWidth();
}
}
});
const state = correctStatus(rawStateProp) || stateCacheRef.current;
const iconToRender = getIcon({
state,
icon: restOfProps.icon,
iconSize: restOfProps.iconSize
});
const contentToRender = getContent(restOwnProps);
const hasStringContent = typeof contentToRender === 'string' && contentToRender.length > 0;
const {
title,
size,
variant,
className,
stretch,
shellSpace,
textId,
skeleton,
role,
icon: _icon,
iconSize: _iconSize,
...rest
} = restOfProps;
const params = applySpacing(props, {
className: clsx(`dnb-form-status dnb-form-status__size--${size}`, className, state && `dnb-form-status--${state}`, variant && `dnb-form-status__variant--${variant}`, stretch && 'dnb-form-status--stretch', hasStringContent && 'dnb-form-status--has-content'),
id: !String(idProp).startsWith('null') ? id : null,
title,
role,
...rest
});
if (!role) {
switch (state) {
case 'information':
params.role = 'status';
break;
default:
params.role = 'alert';
}
}
const textParams = {
className: clsx('dnb-form-status__text', createSkeletonClass('font', skeleton, context)),
id: !String(textId).startsWith('null') ? textId : null
};
const shellParams = applySpacing({
space: shellSpace
}, {
className: 'dnb-form-status__shell'
});
skeletonDOMAttributes(params, skeleton, context);
validateDOMAttributes(restOwnProps, params);
validateDOMAttributes(null, textParams);
return _jsx(HeightAnimation, {
element: "span",
open: isReadyToGetVisible(),
animate: shouldAnimate,
duration: 600,
...params,
ref: elementRef,
children: _jsxs("span", {
...shellParams,
children: [iconToRender, _jsx("span", {
...textParams,
children: contentToRender || contentCacheRef.current
})]
})
});
}
FormStatusComponent.displayName = 'FormStatus';
const FormStatus = React.memo(FormStatusComponent);
withComponentMarkers(FormStatus, {
_supportsSpacingProps: true
});
export default FormStatus;
export const ErrorIcon = props => {
const {
title = 'error'
} = props || {};
const isSbankenTheme = useTheme()?.name === 'sbanken';
const fill = isSbankenTheme ? properties.sbanken['--sb-color-magenta'] : properties.ui['--color-fire-red'];
const line = isSbankenTheme ? properties.sbanken['--sb-color-magenta-light-2'] : properties.ui['--color-white'];
return _jsxs("svg", {
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
...props,
children: [_jsx("title", {
children: title
}), _jsx("path", {
d: "M23.625 17.864A3.547 3.547 0 0120.45 23H3.548a3.546 3.546 0 01-3.172-5.136l8.45-14.902a3.548 3.548 0 016.347 0l8.452 14.902z",
fill: fill
}), _jsx("path", {
d: "M12 16.286a1.286 1.286 0 100 2.572 1.286 1.286 0 000-2.572z",
fill: line
}), _jsx("path", {
d: "M12 13.818v-5",
stroke: line,
strokeWidth: "1.5",
strokeLinecap: "round",
strokeLinejoin: "round"
})]
});
};
export const WarnIcon = props => {
const {
title = 'error'
} = props || {};
const isSbankenTheme = useTheme()?.name === 'sbanken';
const fill = isSbankenTheme ? properties.sbanken['--sb-color-yellow-dark'] : properties.ui['--color-accent-yellow'];
const line = isSbankenTheme ? properties.sbanken['--sb-color-black'] : properties.ui['--color-black-80'];
return _jsxs("svg", {
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
...props,
children: [_jsx("title", {
children: title
}), _jsx("path", {
d: "M23.625 17.864A3.547 3.547 0 0120.45 23H3.548a3.546 3.546 0 01-3.172-5.136l8.45-14.902a3.548 3.548 0 016.347 0l8.452 14.902z",
fill: fill
}), _jsx("path", {
d: "M12 16.286a1.286 1.286 0 100 2.572 1.286 1.286 0 000-2.572z",
fill: line
}), _jsx("path", {
d: "M12 13.818v-5",
stroke: line,
strokeWidth: "1.5",
strokeLinecap: "round",
strokeLinejoin: "round"
})]
});
};
export const InfoIcon = props => {
const {
title = 'information'
} = props || {};
const isSbankenTheme = useTheme()?.name === 'sbanken';
let fill = isSbankenTheme ? properties.sbanken['--sb-color-green-dark-2'] : properties.ui['--color-sea-green'];
if (props && props?.state === 'success') {
fill = isSbankenTheme ? properties.sbanken['--sb-color-green-dark-3'] : properties.ui['--color-summer-green'];
}
const line = isSbankenTheme ? properties.sbanken['--sb-color-green-light-2'] : properties.ui['--color-white'];
return _jsxs("svg", {
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
...props,
children: [_jsx("title", {
children: title
}), _jsx("path", {
fillRule: "evenodd",
clipRule: "evenodd",
d: "M11.268 0a11.25 11.25 0 105.566 21.017l6.112 2.91a.75.75 0 001-1l-2.911-6.112A11.234 11.234 0 0011.268 0z",
fill: fill
}), _jsx("circle", {
cx: "11",
cy: "6.5",
r: ".5",
fill: "#fff",
stroke: line
}), _jsx("path", {
d: "M13.75 16H13a1.5 1.5 0 01-1.5-1.5v-3.75a.75.75 0 00-.75-.75H10",
stroke: line,
strokeWidth: "1.5",
strokeLinecap: "round",
strokeLinejoin: "round"
})]
});
};
export const MarketingIcon = props => {
const {
title = 'marketing'
} = props || {};
const isSbankenTheme = useTheme()?.name === 'sbanken';
const fill = isSbankenTheme ? properties.sbanken['--sb-color-violet-light'] : properties.ui['--color-black-80'];
return _jsxs("svg", {
width: "24",
height: "24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
...props,
children: [_jsx("title", {
children: title
}), _jsx("path", {
d: "M6 15.25H4.5c-2.042 0-3.75-1.707-3.75-3.75S2.458 7.75 4.5 7.75H6v7.5ZM7.5 15.25c4.801 0 8.846 1.897 12.75 4.5V3.25c-3.904 2.603-7.949 4.5-12.75 4.5v7.5ZM23.25 10a.75.75 0 0 0-1.5 0h1.5Zm-1.5 3a.75.75 0 0 0 1.5 0h-1.5ZM8.483 21.043a.75.75 0 1 0 1.034-1.086l-1.034 1.086ZM21.75 10v3h1.5v-3h-1.5ZM6 15.25a8.058 8.058 0 0 0 2.483 5.793l1.034-1.086A6.559 6.559 0 0 1 7.5 15.25H6Z",
fill: fill
})]
});
};
export function setMaxWidthToElement({
element,
id = null,
widthElement = null,
widthSelector = null
}) {
if (!(element && typeof window !== 'undefined')) {
return;
}
try {
if (!id && !widthSelector) {
id = element.getAttribute('id');
}
widthSelector = widthSelector || id?.replace('-form-status', '') || id;
let width = sumElementWidth({
widthElement,
widthSelector
});
if (width > 40) {
const maxWidth = 30 * 16;
if (width < maxWidth) {
width = maxWidth;
}
const remWidth = `${width / 16}rem`;
const style = window.getComputedStyle(element);
const hasCustomWidth = element.style.maxWidth ? false : style.minWidth !== '' && style.minWidth !== 'auto' || style.maxWidth !== '' && style.maxWidth !== 'none';
if (!hasCustomWidth) {
element.style.maxWidth = remWidth;
}
}
} catch (e) {}
}
function sumElementWidth({
widthElement,
widthSelector
}) {
let width = 0;
if (typeof document === 'undefined') {
return width;
}
try {
const ids = widthElement ? [widthElement] : widthSelector.split(/, |,/g);
width = ids.reduce((acc, cur) => {
const elem = typeof cur === 'string' ? cur[0] === '.' ? document.querySelector(cur) : document.getElementById(cur) : cur;
let elemWidth = elem && elem.offsetWidth || parseFloat(String(window.getComputedStyle(elem).width)) || 0;
if (/em|rem/.test(String(window.getComputedStyle(elem).width))) {
elemWidth = parseFloat(String(window.getComputedStyle(elem).width)) * 16;
}
if (elemWidth > 0) {
if (acc > 0) {
acc += 16;
}
acc += elemWidth;
}
return acc;
}, width);
} catch (e) {}
return width;
}
//# sourceMappingURL=FormStatus.js.map