Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@types/uuid": "^8.*",
"@viewstools/use-masked-input": "^2.*",
"@worksolutions/ckeditor5": "0.0.3",
"@worksolutions/react-utils": "^1.2.65",
"@worksolutions/react-utils": "^1.2.67",
"@worksolutions/utils": "^1.2.30",
"external-svg-sprite-loader": "^6.*",
"history": "^4.*",
Expand All @@ -40,7 +40,6 @@
"re-resizable": "^6.9.0",
"react": "^17.*",
"react-dom": "^17.*",
"react-popper": "^2.1.0",
"react-router-dom": "^5.*",
"react-transition-group": "^4.4.1",
"react-use": "^17.2.4",
Expand Down
1 change: 1 addition & 0 deletions src/css/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
box-sizing: border-box;
line-height: 0;
}

20 changes: 20 additions & 0 deletions src/primitives/Popper/Manager/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { useMountedState } from "react-use";

export const ManagerReferenceNodeContext = React.createContext<HTMLElement>(null!);
export const ManagerReferenceNodeSetterContext = React.createContext<(elem: HTMLElement) => void>(null!);

export function Manager({ children }: React.PropsWithChildren<{}>) {
const [referenceNode, setReferenceNode] = React.useState<HTMLElement>(null!);
const isMounted = useMountedState();

const handleSetReferenceNode = React.useCallback((node) => isMounted() && setReferenceNode(node), [isMounted]);

return (
<ManagerReferenceNodeContext.Provider value={referenceNode}>
<ManagerReferenceNodeSetterContext.Provider value={handleSetReferenceNode}>
{children}
</ManagerReferenceNodeSetterContext.Provider>
</ManagerReferenceNodeContext.Provider>
);
}
99 changes: 99 additions & 0 deletions src/primitives/Popper/Popper/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { Ref } from "react";
import { provideRef, useVanillaPopper } from "@worksolutions/react-utils";
import { Modifier, Options, Placement, PositioningStrategy, State } from "@popperjs/core/lib";

import { ManagerReferenceNodeContext } from "../Manager";

import { getFirstIfArray } from "../utils";

type ReferenceElement = HTMLElement;

export interface PopperArrowProps {
ref: React.Ref<any>;
style?: Partial<CSSStyleDeclaration | null>;
}

export type PopperChildrenProps = {
ref: Ref<any>;
style?: Partial<CSSStyleDeclaration | undefined>;
placement: Placement;
isReferenceHidden?: boolean | null;
hasPopperEscaped?: boolean | null;
update: (() => Promise<Partial<State>>) | null;
forceUpdate: () => void;
arrowProps: PopperArrowProps;
};

type Modifiers = Array<Partial<Modifier<any, any>>>;
export type PopperChildren = (popperChildrenProps: PopperChildrenProps) => React.ReactNode;

export type PopperProps = {
children: PopperChildren;
innerRef?: Ref<any>;
modifiers?: Modifiers;
placement?: Placement;
strategy?: PositioningStrategy;
referenceElement?: ReferenceElement;
onFirstUpdate?: (arg0: Partial<State>) => void;
};

const noop: any = () => {};
const noopPromise: any = () => Promise.resolve(null);
const EMPTY_MODIFIERS: Modifiers = [];

export function Popper({
placement = "bottom",
strategy = "absolute",
modifiers = EMPTY_MODIFIERS,
referenceElement,
onFirstUpdate,
innerRef,
children,
}: PopperProps) {
const referenceNode = React.useContext(ManagerReferenceNodeContext);

const [popperElement, setPopperElement] = React.useState(null);
const [arrowElement, setArrowElement] = React.useState(null);

React.useEffect(() => {
provideRef(innerRef)(popperElement!);
}, [innerRef, popperElement]);

const options: Options = React.useMemo(
() => ({
placement,
strategy,
onFirstUpdate,
modifiers: [
...modifiers,
{
name: "arrow",
enabled: arrowElement != null,
options: { element: arrowElement },
},
],
}),
[placement, strategy, onFirstUpdate, modifiers, arrowElement],
);

const { state, forceUpdate, update } = useVanillaPopper(referenceElement || referenceNode, popperElement, options);

const childrenProps: PopperChildrenProps = React.useMemo(
() => ({
ref: setPopperElement,
style: state?.styles?.popper,
placement: state ? state.placement : placement,
hasPopperEscaped: state && state.modifiersData.hide ? state.modifiersData.hide.hasPopperEscaped : null,
isReferenceHidden: state && state.modifiersData.hide ? state.modifiersData.hide.isReferenceHidden : null,
arrowProps: {
style: state?.styles?.arrow,
ref: setArrowElement,
},
forceUpdate: forceUpdate || noop,
update: update || noopPromise,
}),
[setPopperElement, setArrowElement, placement, state, update, forceUpdate],
);

return getFirstIfArray(children)(childrenProps);
}
32 changes: 32 additions & 0 deletions src/primitives/Popper/Reference/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { Ref } from "react";
import { provideRef } from "@worksolutions/react-utils";

import { getFirstIfArray } from "../utils";
import { ManagerReferenceNodeSetterContext } from "../Manager";

export type ReferenceChildrenProps = { ref: Ref<any> };
export type ReferenceProps = {
children: (ReferenceChildrenProps: ReferenceChildrenProps) => React.ReactNode;
innerRef?: Ref<any>;
};

export function Reference({ children, innerRef }: ReferenceProps) {
const setReferenceNode = React.useContext(ManagerReferenceNodeSetterContext);

const refHandler = React.useCallback(
(node?: HTMLElement) => {
if (!node) return;
provideRef(innerRef)(node);
setReferenceNode(node);
},
[innerRef, setReferenceNode],
);

React.useEffect(() => () => provideRef(innerRef)(null!), []);

React.useEffect(() => {
console.warn("`Reference` should not be used outside of a `Manager` component.");
}, [setReferenceNode]);

return getFirstIfArray(children)({ ref: refHandler });
}
1 change: 1 addition & 0 deletions src/primitives/Popper/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const getFirstIfArray = <T>(arg: T | T[]): T => (Array.isArray(arg) ? arg[0] : arg);
4 changes: 1 addition & 3 deletions src/primitives/PopupManager/PopperElement/Arrow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from "react";
import { PopperArrowProps } from "react-popper";
import { Placement } from "@popperjs/core/lib/enums";

import { PopperArrowProps } from "primitives/Popper/Popper";
import Wrapper from "../../Wrapper";

import { bottom, boxShadow, child, left, right, top, transform, zIndex } from "../../../styles";
Expand Down Expand Up @@ -40,8 +40,6 @@ interface PopupArrowInterface {
}

function Arrow({ arrowProps, placement }: PopupArrowInterface) {
return null;

const arrowPopperStyles = useCallback(() => reactStylesToStylesComponent(arrowProps.style), [arrowProps.style]);
const arrowPositionStyles = useMemo(() => getArrowPositionStyles(placement), [placement]);
const arrowStyles = useMemo(() => getArrowStyles(placement), [placement]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { Ref, useEffect } from "react";
import { Placement } from "@popperjs/core/lib/enums";
import { useEffectSkipFirst } from "@worksolutions/react-utils";
import { PopperArrowProps } from "react-popper";

import { PopperArrowProps } from "primitives/Popper/Popper";

import Wrapper from "../../Wrapper";
import Arrow from "./Arrow";

interface PopperChildrenProps {
styles?: any;
style: React.CSSProperties;
style: CSSStyleDeclaration;
placement: Placement;
children: React.ReactNode;
hasArrow?: boolean;
Expand All @@ -23,6 +24,7 @@ function PopperElementChildrenWrapper(
) {
useEffectSkipFirst(update, [update, hasArrow]);

useEffect(update, []);
useEffect(() => {
if (!triggerElement) return () => {};

Expand Down
44 changes: 26 additions & 18 deletions src/primitives/PopupManager/PopperElement/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React, { Ref, useMemo } from "react";
import { Placement } from "@popperjs/core/lib/enums";
import { PositioningStrategy } from "@popperjs/core";
import popperMaxSizeModifier from "popper-max-size-modifier";
import { Modifier } from "@popperjs/core/lib";

import { Popper } from "primitives/Popper/Popper";

import { Modifier, Popper as ReactPopper } from "react-popper";
import { zIndex_popup } from "../../../constants/zIndexes";
import PopperElementChildrenWrapper from "./PopperElementChildrenWrapper";
import { popupArrowSize } from "./Arrow";
Expand All @@ -12,8 +14,12 @@ const commonPopperStyles = [zIndex_popup];

const modifierArrowPadding = 12;

function getModifiers(offset?: number): Modifier<string>[] {
function getModifiers(offset?: number): Partial<Modifier<any, any>>[] {
return [
{
name: "flip",
enabled: true,
},
{
name: "arrow",
options: {
Expand Down Expand Up @@ -70,22 +76,24 @@ function PopperElement(
const popperModifiers = useMemo(() => getModifiers(offset), [offset]);

return (
<ReactPopper placement={primaryPlacement} modifiers={popperModifiers} strategy={strategy} innerRef={ref}>
{({ ref, style, placement, arrowProps, update }) => (
<PopperElementChildrenWrapper
ref={ref}
style={style}
styles={[commonPopperStyles, styles]}
placement={placement}
arrowProps={arrowProps}
hasArrow={hasArrow}
update={update}
triggerElement={triggerElement}
>
{children}
</PopperElementChildrenWrapper>
)}
</ReactPopper>
<Popper placement={primaryPlacement} modifiers={popperModifiers} strategy={strategy} innerRef={ref}>
{({ ref, style, placement, arrowProps, update }) => {
return (
<PopperElementChildrenWrapper
ref={ref}
style={style}
styles={[commonPopperStyles, styles]}
placement={placement}
arrowProps={arrowProps}
hasArrow={hasArrow}
update={update}
triggerElement={triggerElement}
>
{children}
</PopperElementChildrenWrapper>
);
}}
</Popper>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from "react";
import { Manager as ReactPopperManager, Reference as ReactPopperReference } from "react-popper";
import { provideRef } from "@worksolutions/react-utils";
import { observer } from "mobx-react-lite";

import { Reference } from "primitives/Popper/Reference";
import { Manager } from "primitives/Popper/Manager";

import VisibilityManager, { VisibilityManagerContextInterface } from "../../VisibilityManager";
import { SetVisibilityContextAndTriggerRef } from "./types";

Expand All @@ -27,14 +29,14 @@ function PopupManagerForClick({
const Element = React.useCallback(
(context: VisibilityManagerContextInterface) => (
<>
<ReactPopperReference>
<Reference>
{({ ref: reactPopperReferenceRef }) => (
<TriggerElement
{...context}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

мне не очень нравиться что попер так сильно связан с VisibilityManagerContextInterface. По факту, я хочу чтобы попер занимался только позиционированием моего элемента. А как и когда его показывать уже должен решать я. И желательно чтоб я это мог пропсами рулить

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

поэтому PopupManagerForClick.tsx и тот же для hover вообще не должны знать что они poper отображают

initRef={provideRef(context.initRef, reactPopperReferenceRef, setVisibilityContextAndTriggerRef(context))}
/>
)}
</ReactPopperReference>
</Reference>
</>
),
[TriggerElement, setVisibilityContextAndTriggerRef],
Expand All @@ -43,12 +45,12 @@ function PopupManagerForClick({
const ignoreElements = React.useMemo(() => [popupElementHtmlNode], [popupElementHtmlNode]);

return (
<ReactPopperManager>
<Manager>
<VisibilityManager outsideClickIgnoreElements={ignoreElements} closeOnClickOutside={closeOnClickOutside}>
{Element}
</VisibilityManager>
{popupElementNode && React.cloneElement(popupElementNode as any, { ref: setPopupElementHtmlNode })}
</ReactPopperManager>
</Manager>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from "react";
import { Manager as ReactPopperManager, Reference as ReactPopperReference } from "react-popper";
import { provideRef } from "@worksolutions/react-utils";
import { observer } from "mobx-react-lite";

import { Manager } from "primitives/Popper/Manager";
import { Reference } from "primitives/Popper/Reference";

import VisibilityManager, { VisibilityManagerContextInterface } from "../../VisibilityManager";
import { SetVisibilityContextAndTriggerRef } from "./types";

Expand All @@ -22,7 +24,7 @@ function PopupManagerForExternalControl({
const Element = React.useCallback(
(context: VisibilityManagerContextInterface) => (
<>
<ReactPopperReference>
<Reference>
{({ ref: reactPopperReferenceRef }) => (
<TriggerElement
toggle={context.toggle}
Expand All @@ -32,18 +34,18 @@ function PopupManagerForExternalControl({
initRef={provideRef(context.initRef, reactPopperReferenceRef, setVisibilityContextAndTriggerRef(context))}
/>
)}
</ReactPopperReference>
</Reference>
</>
),
[TriggerElement, setVisibilityContextAndTriggerRef],
);

return (
<>
<ReactPopperManager>
<Manager>
<VisibilityManager closeOnClickOutside={false}>{Element}</VisibilityManager>
{popupElementNode}
</ReactPopperManager>
</Manager>
</>
);
}
Expand Down
Loading