yet-another-react-lightbox
Advanced tools
Comparing version 1.4.0 to 1.4.1
import * as React from "react"; | ||
import { clsx, cssClass } from "../utils.js"; | ||
import { adjustDevicePixelRatio, clsx, cssClass, hasWindow } from "../utils.js"; | ||
import { useLatest } from "../hooks/index.js"; | ||
import { ErrorIcon, LoadingIcon } from "./Icons.js"; | ||
export const ImageSlide = ({ slide: image, render, rect }) => { | ||
var _a, _b, _c; | ||
var _a; | ||
const [state, setState] = React.useState("loading"); | ||
@@ -38,3 +38,3 @@ const latestState = useLatest(state); | ||
? { | ||
...(rect && typeof window !== "undefined" | ||
...(rect && hasWindow() | ||
? { | ||
@@ -51,9 +51,9 @@ sizes: `${Math.ceil((Math.min(image.aspectRatio ? rect.height * image.aspectRatio : Number.MAX_VALUE, rect.width) / | ||
style: { | ||
maxWidth: `${Math.max(...image.srcSet.map((x) => x.width))}px`, | ||
maxWidth: `${adjustDevicePixelRatio(Math.max(...image.srcSet.map((x) => x.width)))}px`, | ||
}, | ||
} | ||
: { | ||
style: ((_b = (_a = imageRef.current) === null || _a === void 0 ? void 0 : _a.naturalWidth) !== null && _b !== void 0 ? _b : 0) > 0 | ||
style: imageRef.current && ((_a = imageRef.current) === null || _a === void 0 ? void 0 : _a.naturalWidth) > 0 | ||
? { | ||
maxWidth: `${(_c = imageRef.current) === null || _c === void 0 ? void 0 : _c.naturalWidth}px`, | ||
maxWidth: `${adjustDevicePixelRatio(imageRef.current.naturalWidth)}px`, | ||
} | ||
@@ -60,0 +60,0 @@ : undefined, |
@@ -13,10 +13,13 @@ import * as React from "react"; | ||
const updateContainerRect = () => { | ||
const width = node === null || node === void 0 ? void 0 : node.clientWidth; | ||
const height = node === null || node === void 0 ? void 0 : node.clientHeight; | ||
setContainerRect(width !== undefined && height !== undefined | ||
? { | ||
width, | ||
height, | ||
} | ||
: undefined); | ||
if (node) { | ||
const styles = window.getComputedStyle(node); | ||
const parse = (value) => parseFloat(value) || 0; | ||
setContainerRect({ | ||
width: Math.round(node.clientWidth - parse(styles.paddingLeft) - parse(styles.paddingRight)), | ||
height: Math.round(node.clientHeight - parse(styles.paddingTop) - parse(styles.paddingBottom)), | ||
}); | ||
} | ||
else { | ||
setContainerRect(undefined); | ||
} | ||
}; | ||
@@ -23,0 +26,0 @@ updateContainerRect(); |
import * as React from "react"; | ||
export const useEnhancedEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect; | ||
import { hasWindow } from "../utils.js"; | ||
export const useEnhancedEffect = hasWindow() ? React.useLayoutEffect : React.useEffect; |
@@ -6,7 +6,14 @@ import * as React from "react"; | ||
import { CloseIcon, IconButton } from "../components/index.js"; | ||
import { useContainerRect } from "../hooks/useContainerRect.js"; | ||
export const Toolbar = ({ toolbar: { buttons }, labels, render: { buttonClose, iconClose } }) => { | ||
const { publish } = useEvents(); | ||
const { setContainerRef, containerRect } = useContainerRect(); | ||
React.useEffect(() => { | ||
if (containerRect === null || containerRect === void 0 ? void 0 : containerRect.width) { | ||
publish("toolbar-width", containerRect.width); | ||
} | ||
}, [publish, containerRect === null || containerRect === void 0 ? void 0 : containerRect.width]); | ||
const renderCloseButton = () => buttonClose ? (buttonClose()) : (React.createElement(IconButton, { key: "close", label: label(labels, "Close"), icon: CloseIcon, renderIcon: iconClose, onClick: () => publish("close") })); | ||
return (React.createElement("div", { className: cssClass("toolbar") }, buttons === null || buttons === void 0 ? void 0 : buttons.map((button) => (button === "close" ? renderCloseButton() : button)))); | ||
return (React.createElement("div", { ref: setContainerRef, className: cssClass("toolbar") }, buttons === null || buttons === void 0 ? void 0 : buttons.map((button) => (button === "close" ? renderCloseButton() : button)))); | ||
}; | ||
export const ToolbarModule = createModule("toolbar", Toolbar); |
@@ -9,1 +9,3 @@ import * as React from "react"; | ||
export declare const makeUseContext: <T>(name: string, contextName: string, context: React.Context<T | null>) => () => T; | ||
export declare const hasWindow: () => boolean; | ||
export declare const adjustDevicePixelRatio: (value: number) => number; |
@@ -19,1 +19,3 @@ import * as React from "react"; | ||
}; | ||
export const hasWindow = () => typeof window !== "undefined"; | ||
export const adjustDevicePixelRatio = (value) => hasWindow() ? Math.round(value / (window.devicePixelRatio || 1)) : value; |
@@ -1,2 +0,2 @@ | ||
import { Plugin } from "../types.js"; | ||
import { Component, Plugin } from "../types.js"; | ||
declare type TextAlignment = "start" | "end" | "center"; | ||
@@ -20,4 +20,8 @@ declare module "../types.js" { | ||
} | ||
/** Captions plugin context holder */ | ||
export declare const CaptionsComponent: Component; | ||
/** Captions plugin module */ | ||
export declare const CaptionsModule: import("../types.js").Module; | ||
/** Captions plugin */ | ||
export declare const Captions: Plugin; | ||
export default Captions; |
import * as React from "react"; | ||
import { cssClass, cssVar } from "../core/utils.js"; | ||
import { cssClass, cssVar, makeUseContext } from "../core/utils.js"; | ||
import { useEvents } from "../core/contexts/Events.js"; | ||
import { createModule } from "../core/index.js"; | ||
const defaultTextAlign = "start"; | ||
@@ -8,4 +10,9 @@ const defaultMaxLines = 3; | ||
const hasDescription = (slide) => "description" in slide ? typeof slide.description === "string" : false; | ||
const Title = ({ title }) => (React.createElement("div", { className: cls(`title_container`) }, | ||
React.createElement("div", { className: cls("title") }, title))); | ||
const CaptionsContext = React.createContext(null); | ||
const useCaptions = makeUseContext("useCaptions", "CaptionsContext", CaptionsContext); | ||
const Title = ({ title }) => { | ||
const { toolbarWidth } = useCaptions(); | ||
return (React.createElement("div", { className: cls(`title_container`) }, | ||
React.createElement("div", { className: cls("title"), ...(toolbarWidth ? { style: { [cssVar("toolbar_width")]: `${toolbarWidth}px` } } : null) }, title))); | ||
}; | ||
const Description = ({ description, align, maxLines }) => (React.createElement("div", { className: cls("description_container") }, | ||
@@ -20,3 +27,16 @@ React.createElement("div", { className: cls("description"), ...(align !== defaultTextAlign || maxLines !== defaultMaxLines | ||
: null) }, description.split("\n").flatMap((line, index) => [...(index > 0 ? [React.createElement("br", { key: index })] : []), line])))); | ||
export const Captions = ({ augment }) => { | ||
export const CaptionsComponent = ({ children }) => { | ||
const { subscribe } = useEvents(); | ||
const [toolbarWidth, setToolbarWidth] = React.useState(); | ||
React.useEffect(() => subscribe("toolbar-width", (topic, event) => { | ||
if (typeof event === "undefined" || typeof event === "number") { | ||
setToolbarWidth(event); | ||
} | ||
}), [subscribe]); | ||
const context = React.useMemo(() => ({ toolbarWidth }), [toolbarWidth]); | ||
return React.createElement(CaptionsContext.Provider, { value: context }, children); | ||
}; | ||
export const CaptionsModule = createModule("captions", CaptionsComponent); | ||
export const Captions = ({ augment, addParent }) => { | ||
addParent("controller", CaptionsModule); | ||
augment(({ render: { slideFooter: renderFooter, ...restRender }, captions, ...restProps }) => ({ | ||
@@ -23,0 +43,0 @@ render: { |
@@ -6,2 +6,3 @@ import * as React from "react"; | ||
export const FullscreenButton = ({ auto, labels, render }) => { | ||
const [mounted, setMounted] = React.useState(false); | ||
const [fullscreen, setFullscreen] = React.useState(false); | ||
@@ -75,2 +76,3 @@ const latestAuto = useLatest(auto); | ||
}, [containerRef, getFullscreenElement]); | ||
React.useEffect(() => setMounted(true), []); | ||
React.useEffect(() => { | ||
@@ -93,3 +95,3 @@ const events = ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"]; | ||
}, [latestAuto, requestFullscreen]); | ||
if (!isFullscreenEnabled()) | ||
if (!mounted || !isFullscreenEnabled()) | ||
return null; | ||
@@ -96,0 +98,0 @@ return render.buttonFullscreen ? (React.createElement(React.Fragment, null, render.buttonFullscreen({ fullscreen, toggleFullscreen }))) : (React.createElement(IconButton, { label: fullscreen ? label(labels, "Exit Fullscreen") : label(labels, "Enter Fullscreen"), icon: fullscreen ? ExitFullscreenIcon : EnterFullscreenIcon, renderIcon: fullscreen ? render.iconExitFullscreen : render.iconEnterFullscreen, onClick: toggleFullscreen })); |
@@ -29,2 +29,3 @@ import * as React from "react"; | ||
height: "100%", | ||
...(width ? { maxWidth: `${width}px` } : null), | ||
}, className: clsx(cssClass("video_container"), cssClass("flex_center")) }, containerRect && (React.createElement("video", { controls: true, playsInline: true, poster: poster, ...scaleWidthAndHeight() }, sources.map(({ src, type }, index) => (React.createElement("source", { key: index, src: src, type: type }))))))))); | ||
@@ -31,0 +32,0 @@ }; |
{ | ||
"name": "yet-another-react-lightbox", | ||
"version": "1.4.0", | ||
"version": "1.4.1", | ||
"description": "Modern React lightbox component", | ||
@@ -88,7 +88,7 @@ "author": "Igor Danchenko", | ||
"@testing-library/user-event": "^14.2.0", | ||
"@types/jest": "^28.1.0", | ||
"@types/react": "^18.0.10", | ||
"@types/jest": "^28.1.1", | ||
"@types/react": "^18.0.12", | ||
"@types/react-dom": "^18.0.5", | ||
"@typescript-eslint/eslint-plugin": "^5.27.0", | ||
"@typescript-eslint/parser": "^5.27.0", | ||
"@typescript-eslint/eslint-plugin": "^5.27.1", | ||
"@typescript-eslint/parser": "^5.27.1", | ||
"autoprefixer": "^10.4.7", | ||
@@ -105,5 +105,5 @@ "eslint": "^8.17.0", | ||
"husky": "^8.0.1", | ||
"jest": "^28.1.0", | ||
"jest-environment-jsdom": "^28.1.0", | ||
"lint-staged": "^13.0.0", | ||
"jest": "^28.1.1", | ||
"jest-environment-jsdom": "^28.1.1", | ||
"lint-staged": "^13.0.1", | ||
"npm-run-all": "^4.1.5", | ||
@@ -116,3 +116,3 @@ "postcss": "^8.4.14", | ||
"rimraf": "^3.0.2", | ||
"sass": "^1.52.2", | ||
"sass": "^1.52.3", | ||
"ts-jest": "^28.0.4", | ||
@@ -123,2 +123,3 @@ "typescript": "^4.7.3" | ||
"react", | ||
"image", | ||
"lightbox", | ||
@@ -125,0 +126,0 @@ "react lightbox" |
@@ -18,3 +18,3 @@ # Yet Another React Lightbox | ||
- **Customization:** customize any UI element or add your own custom slides | ||
- **No bloat:** never bundle rarely used features; add optional features via plugins | ||
- **No bloat:** never bundle rarely used features; add optional features via plugins | ||
- **TypeScript:** type definitions come built-in in the package | ||
@@ -44,3 +44,3 @@ | ||
```javascript | ||
```jsx | ||
import * as React from "react"; | ||
@@ -63,4 +63,4 @@ import Lightbox from "yet-another-react-lightbox"; | ||
slides={[ | ||
{ src: "/image1.jpg" }, | ||
{ src: "/image2.jpg" }, | ||
{ src: "/image1.jpg" }, | ||
{ src: "/image2.jpg" }, | ||
{ src: "/image3.jpg" }, | ||
@@ -76,4 +76,66 @@ ]} | ||
## Recommended Setup | ||
Unlike many other lightbox libraries, Yet Another React Lightbox doesn't have a concept of "thumbnail" or "original" | ||
(or "full size") images. We use responsive images instead and recommend you provide multiple files of different | ||
resolutions for each image. Yet Another React Lightbox automatically populates `srcSet` / `sizes` attributes and lets | ||
the browser decide which image is more appropriate for its viewport size. | ||
```jsx | ||
import * as React from "react"; | ||
import Lightbox from "yet-another-react-lightbox"; | ||
import "yet-another-react-lightbox/styles.css"; | ||
const App = () => { | ||
const [open, setOpen] = React.useState(false); | ||
return ( | ||
<> | ||
<button type="button" onClick={() => setOpen(true)}> | ||
Open Lightbox | ||
</button> | ||
<Lightbox | ||
open={open} | ||
close={() => setOpen(false)} | ||
slides={[ | ||
{ | ||
src: "/image1x3840.jpg", | ||
alt: "image 1", | ||
aspectRatio: 3 / 2, | ||
srcSet: [ | ||
{ src: "/image1x320.jpg", width: 320 }, | ||
{ src: "/image1x640.jpg", width: 640 }, | ||
{ src: "/image1x1200.jpg", width: 1200 }, | ||
{ src: "/image1x2048.jpg", width: 2048 }, | ||
{ src: "/image1x3840.jpg", width: 3840 }, | ||
] | ||
}, | ||
// ... | ||
]} | ||
/> | ||
</> | ||
); | ||
}; | ||
export default App; | ||
``` | ||
You can also integrate 3rd-party image components (e.g., Next.js Image or Gatsby Image) via a custom render function. | ||
See [examples](https://yet-another-react-lightbox.vercel.app/examples) on the documentation website. | ||
## Plugins | ||
Yet Another React Lightbox allows you to add optional features based on your requirements via plugins. | ||
The following plugins come bundled in the package: | ||
- [Captions](https://yet-another-react-lightbox.vercel.app/plugins/captions) - adds support for slide title and | ||
description | ||
- [Fullscreen](https://yet-another-react-lightbox.vercel.app/plugins/fullscreen) - adds support for fullscreen mode | ||
- [Inline](https://yet-another-react-lightbox.vercel.app/plugins/inline) - adds support for inline rendering mode | ||
- [Video](https://yet-another-react-lightbox.vercel.app/plugins/video) - adds support for video slides | ||
## License | ||
MIT © 2022 [Igor Danchenko](https://github.com/igordanchenko) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
93222
1982
138