/
alt3rmann
/
label-studio
Обзор
Документация
Войти
/
alt3rmann
/
label-studio
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
web/libs/editor/src/utils/bem.ts
245 строк
6 KB
Julio Sgarbi
feat: LEAP-1173: Disable the postpone option if the skip interface isn't set (#6048)
03 июл 2024, 18:35
Не верифицирован
03 июл 2024, 18:35
470b83b
Код
Авторство
О чём код?
import { type ComponentClass, type Context, createContext, createElement, type CSSProperties, type FC, forwardRef, type FunctionComponent, type ReactHTML, type ReactSVG, useContext, } from "react"; interface CNMod { [key: string]: unknown; } export interface CN { block: (name: string) => CN; elem: (name: string) => CN; mod: (mods?: CNMod) => CN; mix: (...mix: CNMix[] | undefined[]) => CN; select: (root: Document | Element) => Element | null; selectAll: (root: Document | Element) => NodeList | null; closest: (target: Element) => Element | null; toString: () => string; toClassName: () => string; toCSSSelector: () => string; } type CNMix = string | CN | undefined; interface CNOptions { elem?: string; mod?: Record<string, unknown>; mix?: CNMix | CNMix[] | undefined | undefined; } type ComponentType = FC<any> | ComponentClass<unknown, unknown> | FunctionComponent<unknown>; type TagNameType = keyof ReactHTML | keyof ReactSVG | string; type TagNames = keyof JSX.IntrinsicElements; type TagAttrs<T extends keyof JSX.IntrinsicElements> = JSX.IntrinsicElements[T]; export type CNTagName = ComponentType | TagNameType; type WrappedComponentProps<CN extends FC<any>, TN extends TagNames> = { component?: CN; tag?: TN | CN | string; } & { name: string; block?: string; mod?: CNMod; mix?: CNMix | CNMix[]; className?: string; style?: CSSProperties; component?: FC | CNTagName; } & ({ [key in keyof TagAttrs<TN>]: TagAttrs<TN>[key]; } & { [key in keyof Parameters<CN>[0]]: Parameters<CN>[0][key]; }); const CSS_PREFIX = process.env.CSS_PREFIX ?? "dm-"; const assembleClass = (block: string, elem?: string, mix?: CNMix | CNMix[], mod?: CNMod) => { const rootName = block; const elemName = elem ? `${rootName}__${elem}` : null; const stateName = Object.entries(mod ?? {}).reduce((res, [key, value]) => { const stateClass = [elemName ?? rootName]; if (value === null || value === undefined) return res; if (value !== false) { stateClass.push(key); if (value !== true) stateClass.push(value as string); res.push(stateClass.join("_")); } return res; }, [] as string[]); const finalClass: string[] = []; finalClass.push(elemName ?? rootName); finalClass.push(...stateName); if (mix) { const mixes = Array.isArray(mix) ? mix : [mix]; const mixMap = ([] as CNMix[]) .concat(...mixes) .filter((m) => { if (typeof m === "string") { return m.trim() !== ""; } return m !== undefined && m !== null; }) .map((m) => { if (typeof m === "string") { return m; } return m?.toClassName?.(); }) .reduce((res, cls) => [...res, ...cls!.split(/\s+/)], [] as string[]); finalClass.push(...Array.from(new Set(mixMap))); } const attachNamespace = (cls: string) => { if (typeof cls !== "string") console.error("Non-string classname: ", cls); return String(cls).startsWith(CSS_PREFIX) ? cls : `${CSS_PREFIX}${cls}`; }; return finalClass.map(attachNamespace).join(" "); }; export const BlockContext = createContext<CN | null>(null); export const cn = (block: string, options: CNOptions = {}): CN => { const { elem, mix, mod } = options ?? {}; const blockName = block; const classNameBuilder: CN = { block(name) { return cn(name, { elem, mix, mod }); }, elem(name) { return cn(block, { elem: name, mix, mod }); }, mod(newMod = {}) { const stateOverride = Object.assign({}, mod ?? {}, newMod); return cn(block ?? blockName, { elem, mix, mod: stateOverride }); }, mix(...mix) { return cn(block, { elem, mix, mod }); }, select(root = document) { return root.querySelector(this.toCSSSelector()); }, selectAll(root = document) { return root.querySelectorAll(this.toCSSSelector()); }, closest(root) { return root.closest(this.toCSSSelector()); }, toString() { return assembleClass(block, elem, mix, mod); }, toClassName() { return this.toString(); }, toCSSSelector() { return `.${this.toClassName().replace(/(\s+)/g, ".")}`; }, }; Object.defineProperty(classNameBuilder, "Block", { value: Block }); Object.defineProperty(classNameBuilder, "Elem", { value: Elem }); Object.defineProperty(classNameBuilder, "__class", { value: { block, elem, mix, mod, }, }); return classNameBuilder; }; export const BemWithSpecifiContext = (context?: Context<CN | null>) => { const Context = context ?? createContext<CN | null>(null); const Block = forwardRef( <T extends FC<any>, D extends TagNames>( { tag = "div", name, mod, mix, ...rest }: WrappedComponentProps<T, D>, ref: any, ) => { const rootClass = cn(name); const finalMix = ([] as [CNMix?]).concat(mix).filter((cn) => !!cn); const className = rootClass .mod(mod) .mix(...(finalMix as CNMix[]), rest.className) .toClassName(); const finalProps = { ...rest, ref, className } as any; return createElement( Context.Provider, { value: rootClass, }, createElement(tag, finalProps), ); }, ); const Elem = forwardRef( <T extends FC<any>, D extends TagNames>( { tag = "div", component, block, name, mod, mix, ...rest }: WrappedComponentProps<T, D>, ref: any, ) => { const blockCtx = useContext(Context); const finalMix = ([] as [CNMix?]).concat(mix).filter((cn) => !!cn); const className = (block ? cn(block) : blockCtx)! .elem(name) .mod(mod) .mix(...(finalMix as CNMix[]), rest.className) .toClassName(); const finalProps: any = { ...rest, ref, className }; if (typeof tag !== "string") finalProps.block = blockCtx; if (component) finalProps.tag = tag; return createElement(component ?? tag, finalProps); }, ); Block.displayName = "Block"; Elem.displayName = "Elem"; return { Block, Elem, Context }; }; export const { Block, Elem } = BemWithSpecifiContext(BlockContext); export const useBEM = () => { return useContext(BlockContext)!; };