UNPKG

@engie-group/fluid-design-system-react

Version:

Fluid Design System React

189 lines (163 loc) 5.78 kB
import React, { createContext, useContext } from 'react'; const isUndefinedOrNull = (value: unknown): value is null | undefined => { return typeof value === 'undefined' || value === null; }; /** * Generates a string from arguments that can be a string, number, array or objects with {key: value} where key is a class and value is a boolean */ const classNames = (...args: unknown[]): string | undefined => { const classes = []; for (const arg of args) { if (!arg) { continue; } const argType = typeof arg; if (argType === 'string' || argType === 'number') { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { const inner = Utils.classNames.apply(null, arg); if (inner) { classes.push(inner); } } } else if (argType === 'object') { const typedArg = arg as Record<string, unknown>; if (typedArg.toString === Object.prototype.toString) { for (const key in typedArg) { if (Object.prototype.hasOwnProperty.call(typedArg, key) && typedArg[key]) { classes.push(key); } } } else { classes.push(typedArg.toString()); } } } if (classes.length === 0) { return undefined; } return classes.join(' '); }; const coerceFunction = (func: (...args: unknown[]) => unknown) => { if (!isUndefinedOrNull(func) && typeof func === 'function') { return func; } else { return null; } }; type MergeRefsCallback<T> = (instance: T | null) => void; type MergeRefsList<T> = Array<React.MutableRefObject<T | null> | React.Ref<T> | null | undefined>; const mergeRefs = <T = unknown>(refs: MergeRefsList<T>): MergeRefsCallback<T> => { return (value) => { refs.forEach((ref) => { if (typeof ref === 'function') { ref(value); } else if (ref != null) { (ref as React.MutableRefObject<T | null>).current = value; } }); }; }; const normalizeString = (text: string) => { return text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); }; const normalizeAndSearchInText = (text: string, search: string): boolean => { if (Utils.isUndefinedOrNull(text) || Utils.isUndefinedOrNull(search)) { return false; } const normalizedText = Utils.normalizeString(text); // removed in 2.13.0: `normalizedSearch` used to filter out (, ) and \ through `.replace(/\(|\)|\\/gi, '');` const normalizedSearch = Utils.normalizeString(search); const regExp = new RegExp(Utils.escapeRegExp(normalizedSearch), 'gi'); return normalizedText.search(regExp) !== -1; }; // Escape special characters in a string (so regex behave properly) const escapeRegExp = (string: string): string => { return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); }; const highlightTextAsHtml = ( content: string, textToHighlight: string, caseSensitive = false, escapeAccents = true, openingTag = '<mark class="nj-highlight">', closingTag = '</mark>' ): string => { const regexFlags = caseSensitive ? 'g' : 'gi'; // Escape the textToHighlight to handle special characters const escapedTextToHighlight = Utils.escapeRegExp(textToHighlight); let innerHtml: string; if (Utils.isUndefinedOrNull(textToHighlight)) { innerHtml = content; } else { if (escapeAccents) { const regExp = new RegExp(Utils.normalizeString(escapedTextToHighlight), regexFlags); const matches = Utils.normalizeString(content).matchAll(regExp); let finalText = content; let buffer = 0; if (!Utils.isUndefinedOrNull(matches)) { for (const match of matches) { const updatedIndex = buffer + match.index; const textBeforeOccurrence = finalText.slice(0, updatedIndex); const occurrence = finalText.slice(updatedIndex, updatedIndex + textToHighlight.length); const textAfterOccurrence = finalText.slice( updatedIndex + textToHighlight.length, finalText.length ); finalText = `${textBeforeOccurrence}${openingTag}${occurrence}${closingTag}${textAfterOccurrence}`; buffer = buffer + openingTag.length + closingTag.length; } } innerHtml = finalText; } else { const regExp = new RegExp(escapedTextToHighlight, regexFlags); innerHtml = content.replace(regExp, `${openingTag}$&${closingTag}`); } } return innerHtml; }; const omit = <T extends NonNullable<unknown>, K extends string>(obj: T, ...keys: readonly K[]) => { return Object.fromEntries( Object.entries(obj).filter(([key]) => { return !keys.includes(key as K); }) ) as T; }; const initializeContext = <T>( rootComponentNamespace?: string, message: string = 'Context not provided' ) => { const InitializedContext = createContext<T | undefined>(undefined); const useInitializedContext = () => { const context = useContext(InitializedContext); if (!context) { const errorMessage = rootComponentNamespace ? `${rootComponentNamespace} components must be wrapped in <${rootComponentNamespace}Root> or <${rootComponentNamespace}.Root> component` : message; throw new Error(errorMessage); } return context; }; return [InitializedContext, useInitializedContext] as const; }; const initializeSoftContext = <T>() => { const InitializedContext = createContext<T | undefined>(undefined); const useInitializedContext = () => { return useContext(InitializedContext); }; return [InitializedContext, useInitializedContext] as const; }; export const Utils = { isUndefinedOrNull, classNames, coerceFunction, mergeRefs, normalizeString, normalizeAndSearchInText, escapeRegExp, highlightTextAsHtml, omit, initializeContext, initializeSoftContext };