mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
made simple tiny slider
This commit is contained in:
parent
5da9a0547d
commit
3808f2f516
7 changed files with 95 additions and 36 deletions
|
@ -2,7 +2,11 @@ import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 're
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import ResizeSensor from 'resize-sensor';
|
import ResizeSensor from 'resize-sensor';
|
||||||
|
|
||||||
const FullWidth: FC = ({ children }) => {
|
interface IProps {
|
||||||
|
onRefresh?: (width: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FullWidth: FC<IProps> = ({ children, onRefresh }) => {
|
||||||
const sample = useRef<HTMLDivElement>(null);
|
const sample = useRef<HTMLDivElement>(null);
|
||||||
const [clientWidth, setClientWidth] = useState(document.documentElement.clientWidth);
|
const [clientWidth, setClientWidth] = useState(document.documentElement.clientWidth);
|
||||||
|
|
||||||
|
@ -12,11 +16,13 @@ const FullWidth: FC = ({ children }) => {
|
||||||
const { width } = sample.current.getBoundingClientRect();
|
const { width } = sample.current.getBoundingClientRect();
|
||||||
const { clientWidth } = document.documentElement;
|
const { clientWidth } = document.documentElement;
|
||||||
|
|
||||||
|
onRefresh(clientWidth);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: clientWidth,
|
width: clientWidth,
|
||||||
transform: `translate(-${(clientWidth - width) / 2}px, 0)`,
|
transform: `translate(-${(clientWidth - width) / 2}px, 0)`,
|
||||||
};
|
};
|
||||||
}, [sample.current, clientWidth]);
|
}, [sample.current, clientWidth, onRefresh]);
|
||||||
|
|
||||||
const onResize = useCallback(() => setClientWidth(document.documentElement.clientWidth), []);
|
const onResize = useCallback(() => setClientWidth(document.documentElement.clientWidth), []);
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
max-height: calc(100vh - 125px);
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { PRESETS } from '~/constants/urls';
|
||||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||||
import { throttle } from 'throttle-debounce';
|
import { throttle } from 'throttle-debounce';
|
||||||
import { Icon } from '~/components/input/Icon';
|
import { Icon } from '~/components/input/Icon';
|
||||||
|
import { useArrows } from '~/utils/hooks/keys';
|
||||||
|
|
||||||
interface IProps extends INodeComponentProps {}
|
interface IProps extends INodeComponentProps {}
|
||||||
|
|
||||||
|
@ -239,29 +240,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
|
||||||
images,
|
images,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onKeyDown = useCallback(
|
useArrows(onNext, onPrev, is_modal_shown);
|
||||||
event => {
|
|
||||||
if (
|
|
||||||
(event.target.tagName && ['TEXTAREA', 'INPUT'].includes(event.target.tagName)) ||
|
|
||||||
is_modal_shown
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowLeft':
|
|
||||||
return onPrev();
|
|
||||||
case 'ArrowRight':
|
|
||||||
return onNext();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[onNext, onPrev, is_modal_shown]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('keydown', onKeyDown);
|
|
||||||
|
|
||||||
return () => window.removeEventListener('keydown', onKeyDown);
|
|
||||||
}, [onKeyDown]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOffset(0);
|
setOffset(0);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { INodeComponentProps } from '~/redux/node/constants';
|
import { INodeComponentProps } from '~/redux/node/constants';
|
||||||
import { FullWidth } from '~/components/containers/FullWidth';
|
import { FullWidth } from '~/components/containers/FullWidth';
|
||||||
import { useNodeImages } from '~/utils/node';
|
import { useNodeImages } from '~/utils/node';
|
||||||
|
@ -6,7 +6,8 @@ import { getURL } from '~/utils/dom';
|
||||||
import { PRESETS } from '~/constants/urls';
|
import { PRESETS } from '~/constants/urls';
|
||||||
import TinySlider from 'tiny-slider-react';
|
import TinySlider from 'tiny-slider-react';
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
import { TinySliderSettings } from 'tiny-slider';
|
import { TinySliderInstance, TinySliderSettings } from 'tiny-slider';
|
||||||
|
import { useArrows } from '~/utils/hooks/keys';
|
||||||
|
|
||||||
const settings: TinySliderSettings & { center: boolean } = {
|
const settings: TinySliderSettings & { center: boolean } = {
|
||||||
nav: false,
|
nav: false,
|
||||||
|
@ -20,19 +21,67 @@ const settings: TinySliderSettings & { center: boolean } = {
|
||||||
arrowKeys: false,
|
arrowKeys: false,
|
||||||
// prevButton: false,
|
// prevButton: false,
|
||||||
// nextButton: false,
|
// nextButton: false,
|
||||||
|
autoHeight: true,
|
||||||
swipeAngle: 45,
|
swipeAngle: 45,
|
||||||
|
responsive: {
|
||||||
|
0: {
|
||||||
|
edgePadding: 10,
|
||||||
|
gutter: 40,
|
||||||
|
},
|
||||||
|
768: {
|
||||||
|
edgePadding: 50,
|
||||||
|
},
|
||||||
|
1024: {
|
||||||
|
edgePadding: 150,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const NodeImageTinySlider: FC<INodeComponentProps> = ({ node }) => {
|
const NodeImageTinySlider: FC<INodeComponentProps> = ({ node }) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const slides = useRef<HTMLDivElement[]>([]);
|
||||||
const images = useNodeImages(node);
|
const images = useNodeImages(node);
|
||||||
|
const [current, setCurrent] = useState(0);
|
||||||
|
const [height, setHeight] = useState(images[0]?.metadata?.height || 0);
|
||||||
|
|
||||||
|
const onResize = useCallback(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
ref.current.slider.refresh();
|
||||||
|
const el = slides.current[current];
|
||||||
|
if (!el) return;
|
||||||
|
const { height } = el.getBoundingClientRect();
|
||||||
|
setHeight(height);
|
||||||
|
}, [ref.current, slides.current, current]);
|
||||||
|
|
||||||
|
const onIndexChanged = useCallback(({ index }) => {
|
||||||
|
setCurrent(index || 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrent(0);
|
||||||
|
}, [node.id]);
|
||||||
|
|
||||||
|
useEffect(onResize, [slides, current]);
|
||||||
|
|
||||||
|
const onNext = useCallback(() => {
|
||||||
|
if (!ref.current || images.length <= 1 || current === images.length - 1) return;
|
||||||
|
ref.current.slider.goTo(current + 1);
|
||||||
|
}, [ref.current, current, images]);
|
||||||
|
|
||||||
|
const onPrev = useCallback(() => {
|
||||||
|
if (!ref.current || images.length <= 1 || current === 0) return;
|
||||||
|
ref.current.slider.goTo(current - 1);
|
||||||
|
}, [ref.current, current, images]);
|
||||||
|
|
||||||
|
useArrows(onNext, onPrev, false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FullWidth>
|
<FullWidth onRefresh={onResize}>
|
||||||
<div className={styles.slider}>
|
<div className={styles.slider} style={{ height }}>
|
||||||
<TinySlider settings={settings}>
|
<TinySlider settings={settings} ref={ref} onTransitionEnd={onIndexChanged}>
|
||||||
{images.map(image => (
|
{images.map((image, i) => (
|
||||||
<div className={styles.slide}>
|
<div className={styles.slide} key={image.id} ref={el => (slides.current[i] = el)}>
|
||||||
<img src={getURL(image, PRESETS['1600'])} key={image.url} />
|
<img src={getURL(image, PRESETS['1600'])} key={image.url} onLoad={onResize} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</TinySlider>
|
</TinySlider>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
.slider {
|
.slider {
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.25s;
|
||||||
|
|
||||||
:global(.tns-controls) {
|
:global(.tns-controls) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -8,12 +10,12 @@
|
||||||
|
|
||||||
.slide {
|
.slide {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: calc(100vh - 150px);
|
max-height: calc(100vh - 140px);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
}
|
}
|
||||||
|
|
23
src/utils/hooks/keys.ts
Normal file
23
src/utils/hooks/keys.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
export const useArrows = (onNext: () => void, onPrev: () => void, locked) => {
|
||||||
|
const onKeyDown = useCallback(
|
||||||
|
event => {
|
||||||
|
if ((event.target.tagName && ['TEXTAREA', 'INPUT'].includes(event.target.tagName)) || locked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowLeft':
|
||||||
|
return onPrev();
|
||||||
|
case 'ArrowRight':
|
||||||
|
return onNext();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onNext, onPrev, locked]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('keydown', onKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', onKeyDown);
|
||||||
|
}, [onKeyDown]);
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue