Skip to content

Commit

Permalink
[@mantine/carousel] Add useAnimationOffsetEffect hook to fix issue wi…
Browse files Browse the repository at this point in the history
…th incorrect slides offset (#2041)
  • Loading branch information
rtivital committed Aug 12, 2022
1 parent 1c7f1f3 commit a97aaf5
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 1 deletion.
15 changes: 15 additions & 0 deletions docs/src/docs/others/carousel.mdx
Expand Up @@ -138,3 +138,18 @@ Example with [autoplay plugin](https://www.embla-carousel.com/plugins/autoplay/)
### Cards carousel

<Demo data={CarouselDemos.cards} demoProps={{ toggle: true }} />

## Carousel container animation offset

Embla carousel only reads slides positions and sizes upon initialization. When you are using Carousel
component inside animated component you may experience an issue with incorrect slides offset after
animation finishes.

Example of incorrect slides offset calculation (scroll though slides):

<Demo data={CarouselDemos.brokenAnimation} demoProps={{ toggle: true }} />

To solve this issue use `useAnimationOffsetEffect` hook exported from `@mantine/carousel` package.
It accepts embla instance as first argument and transition duration as second:

<Demo data={CarouselDemos.animationOffset} />
49 changes: 48 additions & 1 deletion src/mantine-carousel/src/Carousel.story.tsx
@@ -1,7 +1,8 @@
import React, { useRef, useState } from 'react';
import { Button } from '@mantine/core';
import { Button, Modal } from '@mantine/core';
import Autoplay from 'embla-carousel-autoplay';
import { Carousel } from './Carousel';
import { useAnimationOffsetEffect } from './use-animation-offset-effect';

export default { title: 'Carousel' };

Expand Down Expand Up @@ -93,3 +94,49 @@ export function DynamicSlides() {
</div>
);
}

export function AnimationOffsetEffect() {
const TRANSITION_DURATION = 200;
const [opened, setOpened] = useState(false);
const [embla, setEmbla] = useState(null);

useAnimationOffsetEffect(embla, TRANSITION_DURATION);

return (
<>
<Button onClick={() => setOpened(true)}>Open Modal</Button>
<Modal
opened={opened}
size="300px"
padding={0}
transitionDuration={TRANSITION_DURATION}
withCloseButton={false}
onClose={() => setOpened(false)}
>
<Carousel loop getEmblaApi={setEmbla}>
<Carousel.Slide>
<img
src="https://cataas.com/cat"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/cute"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/angry"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
</Carousel>
</Modal>
</>
);
}
1 change: 1 addition & 0 deletions src/mantine-carousel/src/index.ts
@@ -1,3 +1,4 @@
export { Carousel } from './Carousel';
export { useAnimationOffsetEffect } from './use-animation-offset-effect';
export type { CarouselProps, CarouselStylesNames } from './Carousel';
export type { Embla } from './types';
12 changes: 12 additions & 0 deletions src/mantine-carousel/src/use-animation-offset-effect.ts
@@ -0,0 +1,12 @@
import { useEffect } from 'react';
import { Embla } from './types';

export function useAnimationOffsetEffect(embla: Embla, transitionDuration: number) {
useEffect(() => {
if (embla) {
window.setTimeout(() => {
embla.reInit();
}, transitionDuration);
}
}, [embla, transitionDuration]);
}
111 changes: 111 additions & 0 deletions src/mantine-demos/src/demos/carousel/Carousel.demo.animationOffset.tsx
@@ -0,0 +1,111 @@
import React, { useState } from 'react';
import { Button, Modal, Group } from '@mantine/core';
import { Carousel, useAnimationOffsetEffect } from '@mantine/carousel';

const code = `
import { useState } from 'react';
import { Button, Modal, Group } from '@mantine/core';
import { Carousel, useAnimationOffsetEffect } from '@mantine/carousel';
function Demo() {
const TRANSITION_DURATION = 200;
const [opened, setOpened] = useState(false);
const [embla, setEmbla] = useState(null);
useAnimationOffsetEffect(embla, TRANSITION_DURATION);
return (
<>
<Group position="center">
<Button onClick={() => setOpened(true)}>Open modal with carousel</Button>
</Group>
<Modal
opened={opened}
size="300px"
padding={0}
transitionDuration={TRANSITION_DURATION}
withCloseButton={false}
onClose={() => setOpened(false)}
>
<Carousel loop getEmblaApi={setEmbla}>
<Carousel.Slide>
<img
src="https://cataas.com/cat"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/cute"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/angry"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
</Carousel>
</Modal>
</>
);
}
`;

function Demo() {
const TRANSITION_DURATION = 200;
const [opened, setOpened] = useState(false);
const [embla, setEmbla] = useState(null);

useAnimationOffsetEffect(embla, TRANSITION_DURATION);

return (
<>
<Group position="center">
<Button onClick={() => setOpened(true)}>Open modal with carousel</Button>
</Group>
<Modal
opened={opened}
size="300px"
padding={0}
transitionDuration={TRANSITION_DURATION}
withCloseButton={false}
onClose={() => setOpened(false)}
>
<Carousel loop getEmblaApi={setEmbla}>
<Carousel.Slide>
<img
src="https://cataas.com/cat"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/cute"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/angry"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
</Carousel>
</Modal>
</>
);
}

export const animationOffset: MantineDemo = {
type: 'demo',
component: Demo,
code,
};
105 changes: 105 additions & 0 deletions src/mantine-demos/src/demos/carousel/Carousel.demo.brokenAnimation.tsx
@@ -0,0 +1,105 @@
import React, { useState } from 'react';
import { Button, Modal, Group } from '@mantine/core';
import { Carousel } from '@mantine/carousel';

const code = `
import { useState } from 'react';
import { Button, Modal, Group } from '@mantine/core';
import { Carousel } from '@mantine/carousel';
function Demo() {
const TRANSITION_DURATION = 200;
const [opened, setOpened] = useState(false);
return (
<>
<Group position="center">
<Button onClick={() => setOpened(true)}>Open carousel with broken offset</Button>
</Group>
<Modal
opened={opened}
size="300px"
padding={0}
transitionDuration={TRANSITION_DURATION}
withCloseButton={false}
onClose={() => setOpened(false)}
>
<Carousel loop>
<Carousel.Slide>
<img
src="https://cataas.com/cat"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/cute"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/angry"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
</Carousel>
</Modal>
</>
);
}
`;

function Demo() {
const TRANSITION_DURATION = 200;
const [opened, setOpened] = useState(false);

return (
<>
<Group position="center">
<Button onClick={() => setOpened(true)}>Open carousel with broken offset</Button>
</Group>
<Modal
opened={opened}
size="300px"
padding={0}
transitionDuration={TRANSITION_DURATION}
withCloseButton={false}
onClose={() => setOpened(false)}
>
<Carousel loop>
<Carousel.Slide>
<img
src="https://cataas.com/cat"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/cute"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
<Carousel.Slide>
<img
src="https://cataas.com/cat/angry"
alt=""
style={{ width: 300, height: 200, objectFit: 'cover' }}
/>
</Carousel.Slide>
</Carousel>
</Modal>
</>
);
}

export const brokenAnimation: MantineDemo = {
type: 'demo',
component: Demo,
code,
};
2 changes: 2 additions & 0 deletions src/mantine-demos/src/demos/carousel/index.ts
Expand Up @@ -12,3 +12,5 @@ export { controlsHover } from './Carousel.demo.controlsHover';
export { configurator } from './Carousel.demo.configurator';
export { progress } from './Carousel.demo.progress';
export { cards } from './Carousel.demo.cards';
export { animationOffset } from './Carousel.demo.animationOffset';
export { brokenAnimation } from './Carousel.demo.brokenAnimation';

0 comments on commit a97aaf5

Please sign in to comment.