Skip to content

Commit

Permalink
feat: add lazy libs provider, add drawer drags animations
Browse files Browse the repository at this point in the history
  • Loading branch information
TomatoVan committed Mar 3, 2024
1 parent cd48677 commit a588818
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 65 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@
"react-router-dom": "^6.2.1",
"react-virtualized": "^9.22.3",
"@headlessui/react": "^1.6.6",
"react-device-detect": "^2.2.2"
"react-device-detect": "^2.2.2",
"@use-gesture/react": "^10.2.19",
"@react-spring/web": "^9.5.2"
},
"loki": {
"configurations": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NotificationIcon from 'shared/assets/icons/notification-20-20.svg';
import { Popover } from 'shared/ui/Popups';
import { Drawer } from 'shared/ui/Drawer/Drawer';
import { BrowserView, MobileView } from 'react-device-detect';
import { AnimationProvider } from 'shared/lib/components/AnimationProvider';
import { NotificationList } from '../../../../entities/Notification';
import cls from './NotificationButton.module.scss';

Expand Down Expand Up @@ -37,9 +38,11 @@ export const NotificationButton = memo(() => {
</BrowserView>
<MobileView>
{trigger}
<Drawer isOpen={isOpen} onClose={onCloseDrawer}>
<NotificationList />
</Drawer>
<AnimationProvider>
<Drawer isOpen={isOpen} onClose={onCloseDrawer}>
<NotificationList />
</Drawer>
</AnimationProvider>
</MobileView>
</div>
);
Expand Down
50 changes: 50 additions & 0 deletions src/shared/lib/components/AnimationProvider/AnimationProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
createContext, ReactNode, useContext, useEffect, useMemo, useRef, useState,
} from 'react';

type SpringType = typeof import('@react-spring/web');
type GestureType = typeof import('@use-gesture/react');

interface AnimationContextPayload {
Gesture?: GestureType;
Spring?: SpringType;
isLoaded?: boolean;
}

const AnimationContext = createContext<AnimationContextPayload>({});

// Both libs depend on each other
const getAsyncAnimationModules = async () => Promise.all([
import('@react-spring/web'),
import('@use-gesture/react'),
]);

export const useAnimationLibs = () => useContext(AnimationContext) as Required<AnimationContextPayload>;

export const AnimationProvider = ({ children }: {children: ReactNode}) => {
const SpringRef = useRef<SpringType>();
const GestureRef = useRef<GestureType>();
const [isLoaded, setIsLoaded] = useState(false);

useEffect(() => {
getAsyncAnimationModules().then(([Spring, Gesture]) => {
SpringRef.current = Spring;
GestureRef.current = Gesture;
setIsLoaded(true);
});
}, []);

const value = useMemo(() => ({
Gesture: GestureRef.current,
Spring: SpringRef.current,
isLoaded,
}), [isLoaded]);

return (
<AnimationContext.Provider
value={value}
>
{children}
</AnimationContext.Provider>
);
};
1 change: 1 addition & 0 deletions src/shared/lib/components/AnimationProvider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AnimationProvider, useAnimationLibs } from './AnimationProvider';
47 changes: 11 additions & 36 deletions src/shared/ui/Drawer/Drawer.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,11 @@
bottom: 0;
right: 0;
left: 0;
opacity: 0;
pointer-events: none;
z-index: -1;
z-index: 100;
display: flex;
align-items: flex-end;
}

.content {
height: 70%;
background: var(--bg-color);
bottom: 0;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
position: relative;
width: 100%;
min-height: 100px;
padding: 20px;
transition: 0.3s transform;
transform: translateY(100%);
overflow-y: auto;
overflow-x: hidden;
z-index: 10000;
}

.content::before {
content: "";
position: relative;
display: block;
width: 100px;
height: 10px;
background: var(--bg-color);
margin: auto;
bottom: 40px;
border-radius: 12px;
}

.opened {
pointer-events: auto;
opacity: 1;
Expand All @@ -50,8 +19,14 @@
}
}

.isClosing {
.content {
transform: translateY(100%);
}
.sheet {
z-index: var(--modal-z-index);
position: fixed;
left: 2vw;
height: calc(100vh + 100px);
width: 96vw;
border-radius: 12px 12px 0;
background: var(--bg-color);
touch-action: none;
padding: 15px;
}
100 changes: 75 additions & 25 deletions src/shared/ui/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,107 @@
import { classNames, Mods } from 'shared/lib/classNames/classNames';
import React, { memo } from 'react';
import { classNames } from 'shared/lib/classNames/classNames';
import React, {
memo, ReactNode, useCallback, useEffect,
} from 'react';
import { useTheme } from 'app/providers/ThemeProvider';
import { useModal } from 'shared/lib/hook/useModal/useModal';
import { Portal } from '../Portal/Portal';
import { useAnimationLibs } from 'shared/lib/components/AnimationProvider';
import { Overlay } from '../Overlay/Overlay';
import cls from './Drawer.module.scss';
import { Portal } from '../Portal/Portal';

interface DrawerProps {
className?: string;
children?: React.ReactNode;
children: ReactNode;
isOpen?: boolean;
onClose?: () => void;
lazy?: boolean;
lazy?: boolean;
}

export const Drawer = memo((props: DrawerProps) => {
const height = window.innerHeight - 100;

export const DrawerContent = memo((props: DrawerProps) => {
const { Spring, Gesture } = useAnimationLibs();
const [{ y }, api] = Spring.useSpring(() => ({ y: height }));
const { theme } = useTheme();
const {
className,
children,
isOpen,
onClose,
isOpen,
lazy,
} = props;

const { theme } = useTheme();
const openDrawer = useCallback(() => {
api.start({ y: 0, immediate: false });
}, [api]);

const {
isClosing,
close,
isMounted,
} = useModal({
onClose,
animationDelay: 300,
isOpen,
});
useEffect(() => {
if (isOpen) {
openDrawer();
}
}, [api, isOpen, openDrawer]);

const mods: Mods = {
[cls.opened]: isOpen,
[cls.isClosing]: isClosing,
const close = (velocity = 0) => {
api.start({
y: height,
immediate: false,
config: { ...Spring.config.stiff, velocity },
onResolve: onClose,
});
};

if (lazy && !isMounted) {
const bind = Gesture.useDrag(
({
last,
velocity: [, vy],
direction: [, dy],
movement: [, my],
cancel,
}) => {
if (my < -70) cancel();

if (last) {
if (my > height * 0.5 || (vy > 0.5 && dy > 0)) {
close();
} else {
openDrawer();
}
} else {
api.start({ y: my, immediate: true });
}
},
{
from: () => [0, y.get()], filterTaps: true, bounds: { top: 0 }, rubberband: true,
},
);

if (!isOpen) {
return null;
}

const display = y.to((py) => (py < height ? 'block' : 'none'));

return (
<Portal>
<div className={classNames(cls.Drawer, mods, [className, theme])}>
<div className={classNames(cls.Drawer, {}, [className, theme, 'app_drawer'])}>
<Overlay onClick={close} />
<div className={cls.content}>
<Spring.a.div
className={cls.sheet}
style={{ display, bottom: `calc(-100vh + ${height - 100}px)`, y }}
{...bind()}
>
{children}
</div>
</Spring.a.div>
</div>
</Portal>
);
});

export const Drawer = memo((props: DrawerProps) => {
const { isLoaded } = useAnimationLibs();

if (!isLoaded) {
return null;
}

return <DrawerContent {...props} />;
});

0 comments on commit a588818

Please sign in to comment.