UNPKG

@amaui/ui-react

Version:
501 lines (499 loc) 19.3 kB
import _extends from "@babel/runtime/helpers/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["version", "size", "open", "onClose", "value", "items", "minZoom", "maxZoom", "incrementZoom", "overflow", "arrows", "startMain", "endMain", "startImage", "endImage", "startThumbnails", "endThumbnails", "ImageProps", "ImageWrapperProps", "IconButtonProps", "IconStart", "IconEnd", "IconClose", "className"]; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } import React from 'react'; import { clamp, is } from '@amaui/utils'; import { classNames, style as styleMethod, useAmauiTheme } from '@amaui/style-react'; import IconMaterialNavigateNext from '@amaui/icons-material-rounded-react/IconMaterialNavigateNextW100'; import IconMaterialNavigateBefore from '@amaui/icons-material-rounded-react/IconMaterialNavigateBeforeW100'; import IconMaterialClose from '@amaui/icons-material-rounded-react/IconMaterialCloseW100'; import ImageElement from '../Image'; import LineElement from '../Line'; import IconButtonElement from '../IconButton'; import InteractionElement from '../Interaction'; import BackdropElement from '../Backdrop'; import { staticClassName } from '../utils'; import useMediaQuery from '../useMediaQuery'; const useStyle = styleMethod(theme => ({ root: { position: 'relative', '&.amaui-Backdrop-root': { display: 'flex', alignItems: 'center', justifyContent: 'center', userSelect: 'none', zIndex: '14000' }, '& .amaui-Backdrop-backdrop-root': { width: '100%', height: '100%', pointerEvents: 'none' } }, version_regular: { minHeight: 'clamp(240px, 100%, 100vh)' }, pointerEventsAuto: { pointerEvents: 'auto' }, wrapper: { height: '100%' }, header: { padding: theme.methods.space.value(3, 'px'), zIndex: '1' }, main: { position: 'relative', zIndex: '14' }, main_version_modal: { height: '0', padding: theme.methods.space.value(5, 'px'), '& .amaui-Image-root': { maxHeight: '100%' // pointerEvents: 'none' } }, main_version_regular_size_small: { height: '340px' }, main_version_regular_size_regular: { height: '540px' }, main_version_regular_size_large: { height: '740px' }, noOverflow: { overflow: 'hidden' }, footer: { padding: `${theme.methods.space.value(3, 'px')} ${theme.methods.space.value(1.5, 'px')}`, zIndex: '1' }, imageWrapper: { position: 'relative', height: '0px', pointerEvents: 'none', // zIndex: 1, transition: theme.methods.transitions.make(['transform'], { duration: 100, timing_function: 'ease' }) }, image: { objectFit: 'contain', width: 'auto', height: 'auto', maxHeight: '100%', maxWidth: '100%' }, itemsWrapper: { position: 'relative', pointerEvents: 'auto', maxWidth: '100%', userSelect: 'none' }, items: { maxWidth: '1024px', overflow: 'auto hidden', '&::-webkit-scrollbar': { width: '16px', height: '16px' }, '&::-webkit-scrollbar-track, &::-webkit-scrollbar-corner': { background: 'transparent' }, '&::-webkit-scrollbar-thumb': { borderRadius: theme.methods.shape.radius.value(1, 'px'), border: '4px solid transparent', backgroundClip: 'content-box', backgroundColor: 'rgba(221, 221, 221, 0.4)', '&:hover': { backgroundColor: 'rgba(221, 221, 221, 0.7)' } } }, item: { width: '140px', height: '140px', position: 'relative', backgroundSize: 'contain', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', flex: '0 0 auto', border: '2px solid transparent', cursor: 'pointer', userSelect: 'none', transition: theme.methods.transitions.make(['border', 'transform']), '&:active': { transform: 'scale(0.94)' } }, itemSelected: { border: '2px solid !important', borderColor: `${theme.methods.palette.color.value('secondary', 30)} !important` }, arrow: { flex: '0 0 auto', alignSelf: 'center', justifySelf: 'center', transition: theme.methods.transitions.make(['opacity'], { duration: 'xxs' }), '&[disabled]': { opacity: '0' } } }), { name: 'amaui-ImageGallery' }); const ImageGallery = /*#__PURE__*/React.forwardRef((props_, ref) => { const theme = useAmauiTheme(); const props = React.useMemo(() => _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.amauiImageGallery?.props?.default), props_), [props_]); const Line = React.useMemo(() => theme?.elements?.Line || LineElement, [theme]); const Image = React.useMemo(() => theme?.elements?.Image || ImageElement, [theme]); const IconButton = React.useMemo(() => theme?.elements?.IconButton || IconButtonElement, [theme]); const Interaction = React.useMemo(() => theme?.elements?.Interaction || InteractionElement, [theme]); const Backdrop = React.useMemo(() => theme?.elements?.Backdrop || BackdropElement, [theme]); const { version = 'modal', size = 'regular', open: open_, onClose: onClose_, value: value_, items, minZoom: minZoom_ = 1, maxZoom: maxZoom_ = 2, incrementZoom = 0.07, overflow = true, arrows = true, startMain, endMain, startImage, endImage, startThumbnails, endThumbnails, ImageProps, ImageWrapperProps, IconButtonProps, IconStart = IconMaterialNavigateBefore, IconEnd = IconMaterialNavigateNext, IconClose = IconMaterialClose, className } = props, other = _objectWithoutProperties(props, _excluded); const { classes } = useStyle(); const [loaded, setLoaded] = React.useState(false); const [open, setOpen] = React.useState(false); const [value, setValue] = React.useState(0); const [moveValue, setMoveValue] = React.useState(); const [imageRef, setImageRef] = React.useState(); const [keyDown, setKeyDown] = React.useState(); const refs = { root: React.useRef(undefined), version: React.useRef(version), more: React.useRef(undefined), image: React.useRef(), imageWrapper: React.useRef(undefined), media: React.useRef(undefined), zoom: React.useRef(undefined), incrementZoom: React.useRef(undefined), minZoom: React.useRef(undefined), maxZoom: React.useRef(undefined), mouseDown: React.useRef(undefined), mouseMovePrevious: React.useRef(undefined), keyDown: React.useRef(keyDown), useZoom: React.useRef(false) }; const touch = useMediaQuery('(pointer: coarse)', { element: refs.root.current }); const minZoom = clamp(minZoom_, 0.1, 1); const maxZoom = clamp(maxZoom_, 1, 100); const length = clamp((items?.length || 0) - 1, 0); const media = items?.[value]; refs.version.current = version; refs.media.current = !!media; refs.keyDown.current = keyDown; refs.useZoom.current = version === 'modal' || keyDown; refs.incrementZoom.current = incrementZoom; refs.minZoom.current = minZoom; refs.maxZoom.current = maxZoom; const init = React.useCallback(() => { setTimeout(() => { setMoveValue({ left: refs.more.current?.scrollLeft, top: refs.more.current?.scrollTop }); setLoaded(true); }, 14); }, []); const cleanUp = React.useCallback(() => { refs.mouseDown.current = false; }, []); React.useEffect(() => { const onKeyDown = event => { setKeyDown(event.metaKey || event.ctrlKey); }; const onKeyUp = event => { setKeyDown(null); }; window.document.addEventListener('keydown', onKeyDown); window.document.addEventListener('keyup', onKeyUp); return () => { window.document.removeEventListener('keydown', onKeyDown); window.document.removeEventListener('keyup', onKeyUp); }; }, []); React.useEffect(() => { // init if (open) init();else cleanUp(); }, [open]); React.useEffect(() => { if (open_ !== undefined && open !== open_) setOpen(open_); }, [open_]); React.useEffect(() => { if (value_ !== undefined && value !== value_) setValue(clamp(value_, 0, length)); }, [value_]); const onResetZoom = React.useCallback(() => { refs.zoom.current = null; if (refs.imageWrapper.current) { refs.imageWrapper.current.style.transition = 'none'; refs.imageWrapper.current.style.removeProperty('transform'); refs.imageWrapper.current.style.removeProperty('left'); refs.imageWrapper.current.style.removeProperty('top'); setTimeout(() => { refs.imageWrapper.current.style.removeProperty('transition'); }, 14); } }, []); const onValue = React.useCallback(index => { onResetZoom(); setValue(index); }, [onResetZoom]); const onOpen = React.useCallback(() => { setOpen(true); }, []); const onClose = React.useCallback(() => { setOpen(false); setTimeout(() => { setLoaded(false); }, 140); if (is('function', onClose_)) onClose_(); }, [onClose_]); const move = function () { let forward_ = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; const forward = forward_; const rect = refs.more.current.getBoundingClientRect(); const moveValue_ = { left: refs.more.current.scrollLeft + (forward ? 1 : -1) * rect.width, behavior: 'smooth' }; refs.more.current.scrollTo(moveValue_); }; const onMouseDown = React.useCallback(event => { refs.mouseDown.current = true; refs.mouseMovePrevious.current = { x: event.clientX, y: event.clientY }; }, []); const onMouseUp = React.useCallback(() => { refs.mouseDown.current = false; refs.mouseMovePrevious.current = null; }, []); const onWheel = React.useCallback(event => { if (!refs.useZoom.current) return; event.preventDefault(); event.stopPropagation(); let scale = refs.zoom.current?.scale !== undefined ? refs.zoom.current.scale : 1; const part = refs.incrementZoom.current; const up = event.wheelDelta > 0 || event.deltaY < 0; scale = clamp(up ? scale + part : scale - part, refs.minZoom.current, refs.maxZoom.current); // Only allow in 100's decimal places, ie. 1.01 scale = Math.round(scale * 100) / 100; const imageBoundingRect = refs.image.current?.getBoundingClientRect(); if (imageBoundingRect.width <= window.innerWidth) { refs.imageWrapper.current.style.removeProperty('left'); } if (imageBoundingRect.height <= window.innerHeight) { refs.imageWrapper.current.style.removeProperty('top'); } if (scale !== refs.zoom.current?.scale) { refs.zoom.current = { scale }; refs.imageWrapper.current.style.transform = `scale(${scale})`; } }, []); const onMouseMove = React.useCallback(event => { if (!refs.useZoom.current) return; if (!(refs.mouseDown.current && refs.image.current)) return; const x = event.x - (refs.mouseMovePrevious.current?.x || 0); const y = event.y - (refs.mouseMovePrevious.current?.y || 0); refs.mouseMovePrevious.current = { x: event.clientX, y: event.clientY }; let left = Number(refs.imageWrapper.current?.style?.left?.replace('px', '')) || 0; let top = Number(refs.imageWrapper.current?.style?.top?.replace('px', '')) || 0; const imageBoundingRect = refs.image.current.getBoundingClientRect(); if (imageBoundingRect.left <= 0 && imageBoundingRect.left + x <= 0 && imageBoundingRect.width + imageBoundingRect.left + x >= window.innerWidth) { left += x; refs.imageWrapper.current.style.left = `${left}px`; } if (imageBoundingRect.top <= 0 && imageBoundingRect.top + y <= 0 && imageBoundingRect.height + imageBoundingRect.top + y >= window.innerHeight) { top += y; refs.imageWrapper.current.style.top = `${top}px`; } }, []); React.useEffect(() => { if (refs.useZoom.current) { if (imageRef) imageRef.addEventListener('wheel', onWheel, { passive: false }); window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); window.addEventListener('touchend', onMouseUp); } return () => { if (imageRef) imageRef.removeEventListener('wheel', onWheel); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); window.removeEventListener('touchend', onMouseUp); }; }, [imageRef, refs.useZoom.current]); const onScroll = React.useCallback(event => { if (!refs.useZoom.current) return; if (arrows) { setMoveValue({ left: refs.more.current.scrollLeft, top: refs.more.current.scrollTop }); } }, [arrows]); const iconButtonProps = _objectSpread({ version: 'filled', color: 'default', tonal: true, size: 'small' }, IconButtonProps); const arrowPre = moveValue && /*#__PURE__*/React.createElement(IconButton, _extends({ onClick: () => move(false), disabled: !moveValue || refs.more.current?.scrollLeft === 0 }, iconButtonProps, { className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-arrow', 'amaui-ImageGallery-arrow-start'], iconButtonProps?.className, classes.arrow]) }), /*#__PURE__*/React.createElement(IconStart, null)); const arrowPost = moveValue && /*#__PURE__*/React.createElement(IconButton, _extends({ onClick: () => move(), disabled: Math.ceil(refs.more.current?.clientWidth + refs.more.current?.scrollLeft) === refs.more.current?.scrollWidth }, iconButtonProps, { className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-arrow', 'amaui-ImageGallery-arrow-end'], iconButtonProps?.className, classes.arrow]) }), /*#__PURE__*/React.createElement(IconEnd, null)); const more = !!items?.length; const onDragStartImage = React.useCallback(event => { event.preventDefault(); }, []); const url = media?.url || media?.urlSmall || (is('string', media) ? media : ''); const main = /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Line, { justify: "center", align: "center", flex: true, fullWidth: true, className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-main'], classes.main, classes[`main_version_${version}`], classes[`main_version_${version}_size_${size}`], !overflow && classes.noOverflow]) }, startMain, /*#__PURE__*/React.createElement(Line, _extends({ ref: refs.imageWrapper, justify: "center", align: "center", fullWidth: true, flex: true }, ImageWrapperProps, { className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-image-wrapper'], ImageWrapperProps?.className, classes.imageWrapper]), style: _objectSpread({}, ImageWrapperProps?.style) }), startImage, url && /*#__PURE__*/React.createElement(Image, _extends({ ref: item => { refs.image.current = item; setImageRef(item); }, src: url, alt: media?.name, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onTouchStart: onMouseDown, onTouchEnd: onMouseUp, onDragStart: onDragStartImage, lazyLoad: version === 'regular' }, ImageProps, { className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-image'], ImageProps?.className, classes.image, classes.pointerEventsAuto]) })), endImage), endMain), more && /*#__PURE__*/React.createElement(Line, { align: "center", fullWidth: true, className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-footer'], classes.footer]) }, /*#__PURE__*/React.createElement(Line, { gap: 1.5, direction: "row", justify: "center", align: "center", className: classNames([classes.itemsWrapper]) }, startThumbnails, arrows && arrowPre, /*#__PURE__*/React.createElement(Line, { ref: refs.more, gap: 1, direction: "row", align: "center", justify: "flex-start", fullWidth: true, onScroll: onScroll, className: classNames([classes.items]) }, items.map((item, index) => /*#__PURE__*/React.createElement(Line, { onClick: () => onValue(index), className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-item', value === index && 'amaui-ImageGallery-item-selected'], classes.item, value === index && classes.itemSelected]), style: { backgroundImage: `url('${item?.urlSmall || item?.url || item}')` } }, /*#__PURE__*/React.createElement(Interaction, null)))), arrows && arrowPost, endThumbnails))); if (version === 'regular') { return /*#__PURE__*/React.createElement(Line, _extends({ ref: item => { if (ref) { if (is('function', ref)) ref(item);else ref.current = item; } refs.root.current = item; }, fullWidth: true, className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-root', `amaui-ImageGallery-version-${version}`, `amaui-ImageGallery-size-${size}`], className, classes.root, classes[`version_${version}`]]) }, other), main); } return /*#__PURE__*/React.createElement(Backdrop, _extends({ ref: item => { if (ref) { if (is('function', ref)) ref(item);else ref.current = item; } refs.root.current = item; }, open: open, onClose: onClose, className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-root', `amaui-ImageGallery-version-${version}`], className, classes.root, classes[`version_${version}`]]) }, other), /*#__PURE__*/React.createElement(Line, { gap: 0, fullWidth: true, className: classes.wrapper }, /*#__PURE__*/React.createElement(Line, { direction: "row", justify: "flex-end", fullWidth: true, className: classNames([staticClassName('ImageGallery', theme) && ['amaui-ImageGallery-header'], classes.header]) }, /*#__PURE__*/React.createElement(IconButton, { version: "filled", color: theme.palette.light ? 'default' : 'inverted', onClick: onClose, className: classNames([classes.pointerEventsAuto]) }, /*#__PURE__*/React.createElement(IconClose, null))), main)); }); ImageGallery.displayName = 'amaui-ImageGallery'; export default ImageGallery;