Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Portal): take in consideration parent theme #1506

Merged
merged 4 commits into from
Apr 24, 2024
Merged
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
58 changes: 28 additions & 30 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,34 +102,32 @@ export function Modal({
});

return (
<Portal container={container}>
<CSSTransition
nodeRef={containerRef}
in={open}
addEndListener={(done) =>
containerRef.current?.addEventListener('animationend', done)
}
classNames={getCSSTransitionClassNames(b)}
mountOnEnter={!keepMounted}
unmountOnExit={!keepMounted}
appear={true}
onEnter={() => {
setInTransition(true);
onTransitionEnter?.();
}}
onExit={() => {
setInTransition(true);
onTransitionExit?.();
}}
onEntered={() => {
setInTransition(false);
onTransitionEntered?.();
}}
onExited={() => {
setInTransition(false);
onTransitionExited?.();
}}
>
<CSSTransition
nodeRef={containerRef}
in={open}
addEndListener={(done) => containerRef.current?.addEventListener('animationend', done)}
classNames={getCSSTransitionClassNames(b)}
mountOnEnter={!keepMounted}
unmountOnExit={!keepMounted}
appear={true}
onEnter={() => {
setInTransition(true);
onTransitionEnter?.();
}}
onExit={() => {
setInTransition(true);
onTransitionExit?.();
}}
onEntered={() => {
setInTransition(false);
onTransitionEntered?.();
}}
onExited={() => {
setInTransition(false);
onTransitionExited?.();
}}
>
<Portal container={container}>
<div ref={containerRef} style={style} className={b({open}, className)} data-qa={qa}>
<div className={b('content-aligner')}>
<div className={b('content-wrapper')}>
Expand Down Expand Up @@ -157,7 +155,7 @@ export function Modal({
</div>
</div>
</div>
</CSSTransition>
</Portal>
</Portal>
</CSSTransition>
);
}
50 changes: 24 additions & 26 deletions src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,30 +151,28 @@ export function Popup({
});

return (
<Portal container={container} disablePortal={disablePortal}>
<CSSTransition
nodeRef={containerRef}
in={open}
addEndListener={(done) =>
containerRef.current?.addEventListener('animationend', done)
}
classNames={getCSSTransitionClassNames(b)}
mountOnEnter={!keepMounted}
unmountOnExit={!keepMounted}
appear={true}
onEnter={() => {
onTransitionEnter?.();
}}
onEntered={() => {
onTransitionEntered?.();
}}
onExit={() => {
onTransitionExit?.();
}}
onExited={() => {
onTransitionExited?.();
}}
>
<CSSTransition
nodeRef={containerRef}
in={open}
addEndListener={(done) => containerRef.current?.addEventListener('animationend', done)}
classNames={getCSSTransitionClassNames(b)}
mountOnEnter={!keepMounted}
unmountOnExit={!keepMounted}
appear={true}
onEnter={() => {
onTransitionEnter?.();
}}
onEntered={() => {
onTransitionEntered?.();
}}
onExit={() => {
onTransitionExit?.();
}}
onExited={() => {
onTransitionExited?.();
}}
>
<Portal container={container} disablePortal={disablePortal}>
<div
ref={handleRef}
style={styles.popper}
Expand Down Expand Up @@ -211,7 +209,7 @@ export function Popup({
</div>
</FocusTrap>
</div>
</CSSTransition>
</Portal>
</Portal>
</CSSTransition>
);
}
9 changes: 9 additions & 0 deletions src/components/Portal/Portal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use '../variables';

$block: '.#{variables.$ns}portal';

#{$block} {
&__theme-wrapper {
display: contents;
}
}
21 changes: 20 additions & 1 deletion src/components/Portal/Portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import React from 'react';
import ReactDOM from 'react-dom';

import {usePortalContainer} from '../../hooks';
import {ThemeProvider} from '../theme';
import {useThemeContext} from '../theme/useThemeContext';
import {block} from '../utils/cn';

import './Portal.scss';

const b = block('portal');

export interface PortalProps {
container?: HTMLElement;
Expand All @@ -12,12 +19,24 @@ export interface PortalProps {

export function Portal({container, children, disablePortal}: PortalProps) {
const defaultContainer = usePortalContainer();
const {scoped} = useThemeContext();

const containerNode = container ?? defaultContainer;

if (disablePortal) {
return <React.Fragment>{children}</React.Fragment>;
}

return containerNode ? ReactDOM.createPortal(children, containerNode) : null;
return containerNode
? ReactDOM.createPortal(
scoped ? (
<ThemeProvider rootClassName={b('theme-wrapper')} scoped>
{children}
</ThemeProvider>
) : (
children
),
containerNode,
)
: null;
}
20 changes: 8 additions & 12 deletions src/components/layout/LayoutProvider/LayoutProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import {LayoutContext} from '../contexts/LayoutContext';
import {useCurrentActiveMediaQuery} from '../hooks/useCurrentActiveMediaQuery';
import type {LayoutTheme, MediaType, RecursivePartial} from '../types';
import {makeLayoutDefaultTheme} from '../utils/makeLayoutDefaultTheme';
import {overrideLayoutTheme} from '../utils/overrideLayoutTheme';

export interface PrivateLayoutProviderProps {
config?: RecursivePartial<LayoutTheme>;
Expand All @@ -20,19 +20,15 @@ export function PrivateLayoutProvider({
config: override,
initialMediaQuery,
}: PrivateLayoutProviderProps) {
const theme = React.useMemo(() => makeLayoutDefaultTheme({override}), [override]);
const parentContext = React.useContext(LayoutContext);
const theme = React.useMemo(
() => overrideLayoutTheme({theme: parentContext.theme, override}),
[override, parentContext.theme],
);
const activeMediaQuery = useCurrentActiveMediaQuery(theme.breakpoints, initialMediaQuery);

return (
<LayoutContext.Provider
value={{
activeMediaQuery,
theme,
}}
>
{children}
</LayoutContext.Provider>
);
const value = React.useMemo(() => ({activeMediaQuery, theme}), [activeMediaQuery, theme]);
return <LayoutContext.Provider value={value}>{children}</LayoutContext.Provider>;
}

interface LayoutProviderProps {
Expand Down
23 changes: 5 additions & 18 deletions src/components/layout/hooks/useCurrentActiveMediaQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ export const makeCurrentActiveMediaExpressions = (
xxxl: `(min-width: ${mediaToValue.xxxl}px)`,
});

const safeMatchMedia = (query: string | number): MediaQueryList => {
const safeMatchMedia = (query: string): MediaQueryList => {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
return mockMediaQueryList;
}

return window.matchMedia(String(query));
return window.matchMedia(query);
};

class Queries {
Expand Down Expand Up @@ -81,33 +81,20 @@ export const useCurrentActiveMediaQuery = (
const [state, _setState] = React.useState<MediaType>(initialMediaQuery);

React.useLayoutEffect(() => {
let mounted = true;

const queries = new Queries(breakpointsMap);

const setState = () => {
_setState(queries.getCurrentActiveMedia());
};

const onChange = () => {
if (!mounted) {
return;
}

setState();
};

queries.addListeners(onChange);
queries.addListeners(setState);

setState();

return () => {
mounted = false;
queries.removeListeners(onChange);
queries.removeListeners(setState);
};
// don't support runtime breakpoint redefinition. Breakpoints defined only one at LayoutTheme
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [breakpointsMap]);
Copy link
Contributor

Choose a reason for hiding this comment

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

if user pass layout theme object like

<ThemeProvider layout={{
  config: {
    breakpoints: {...}
  }
}} >...</ThemeProvider>

and App will have some state upper ThemeProvider it will cause this function to be constantly recalculated.

We need good reasons to be able to set breakpoints dynamically in runtime

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if config never changes, the user can move its creation outside the component. Otherwise, he can expect the changes to be taken into account.


return state;
};
18 changes: 0 additions & 18 deletions src/components/layout/utils/makeLayoutDefaultTheme.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/components/layout/utils/overrideLayoutTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable valid-jsdoc */
import merge from 'lodash/merge';

import type {LayoutTheme, RecursivePartial} from '../types';

interface OverrideLayoutThemeOptions {
theme: LayoutTheme;
override?: RecursivePartial<LayoutTheme>;
}

/**
* Use this function to override default `DEFAULT_LAYOUT_THEME`
*/
export function overrideLayoutTheme({theme, override}: OverrideLayoutThemeOptions): LayoutTheme {
return merge(theme, override);
}
9 changes: 3 additions & 6 deletions src/components/theme/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ export function ThemeProvider({
theme,
themeValue,
direction,
scoped,
}) satisfies ThemeContextProps,
[theme, themeValue, direction],
[theme, themeValue, direction, scoped],
);

const themeSettingsContext = React.useMemo(
Expand All @@ -102,11 +103,7 @@ export function ThemeProvider({
},
rootClassName,
)}
dir={
hasParentProvider && direction === parentDirection
? undefined
: direction
}
dir={direction}
>
{children}
</div>
Expand Down
Loading
Loading