├── .eslintignore ├── .prettierrc.js ├── .gitignore ├── .npmignore ├── .huskyrc.js ├── src ├── utils.js ├── lightgalleryContext.js ├── index.js ├── withLightgallery.js ├── useLightgallery.js ├── LightgalleryItem.js └── LightgalleryProvider.js ├── .babelrc.json ├── example ├── styles.css ├── index.html └── index.js ├── .eslintrc.js ├── .github └── workflows │ └── npm-publish.yml ├── LICENSE ├── rollup.config.js ├── package.json └── readme.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 4, 3 | }; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | dist 4 | .example_dist 5 | .cache 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /src 3 | /example 4 | /.yarn-error.log 5 | /.cache 6 | /.example_dist 7 | /.github -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | "pre-commit": "yarn run eslint", 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generator of class names for elements 3 | */ 4 | export const addPrefix = (str) => `react_lightgallery_${str}`; 5 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /src/lightgalleryContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const lightgalleryContext = createContext(); 4 | export default lightgalleryContext; 5 | -------------------------------------------------------------------------------- /example/styles.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Open+Sans&display=swap"); 2 | 3 | .navbar-logo { 4 | font-family: "Open Sans"; 5 | font-size: 24px; 6 | color: #fff; 7 | background-color: #0a4f56; 8 | } 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { LightgalleryItem } from "./LightgalleryItem"; 2 | import { LightgalleryProvider } from "./LightgalleryProvider"; 3 | import { useLightgallery } from "./useLightgallery"; 4 | import { withLightgallery } from "./withLightgallery"; 5 | 6 | export { 7 | LightgalleryItem, 8 | LightgalleryProvider, 9 | useLightgallery, 10 | withLightgallery, 11 | }; 12 | -------------------------------------------------------------------------------- /src/withLightgallery.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLightgallery } from "./useLightgallery"; 3 | import hoistNonReactStatic from "hoist-non-react-statics"; 4 | 5 | export const withLightgallery = (WrappedComponent) => { 6 | const WithLightgallery = (props) => { 7 | const { openGallery } = useLightgallery(); 8 | return ; 9 | }; 10 | hoistNonReactStatic(WithLightgallery, WrappedComponent); 11 | WithLightgallery.displayName = `withLightgallary(${WrappedComponent.displayName})`; 12 | return WithLightgallery; 13 | }; 14 | 15 | export default withLightgallery; 16 | -------------------------------------------------------------------------------- /src/useLightgallery.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { lightgalleryContext } from "./lightgalleryContext"; 3 | 4 | export const useLightgallery = () => { 5 | const { hasGroup, openGalleryByIndex: _openGalleryByIndex } = useContext( 6 | lightgalleryContext 7 | ); 8 | const openGallery = (group_name, item_idx = 0) => { 9 | if (!group_name) { 10 | throw new Error( 11 | "You must to provide 'group_name' on call function 'openGallery'" 12 | ); 13 | } 14 | if (!hasGroup(group_name)) { 15 | throw new Error(`Group '${group_name}' is not exists`); 16 | } 17 | _openGalleryByIndex(item_idx, group_name); 18 | }; 19 | return { openGallery }; 20 | }; 21 | 22 | export default useLightgallery; 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | es6: true, 6 | }, 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "prettier", 11 | "plugin:import/errors", 12 | "plugin:import/warnings", 13 | ], 14 | parser: "babel-eslint", 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | ecmaVersion: 2018, 20 | sourceType: "module", 21 | }, 22 | plugins: ["react", "prettier", "import"], 23 | rules: { 24 | indent: ["error", 4], 25 | "linebreak-style": ["error", "unix"], 26 | quotes: ["error", "double"], 27 | semi: ["error", "always"], 28 | "no-console": "off", 29 | "no-prototype-builtins": "off", 30 | }, 31 | globals: { 32 | lightGallery: true, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - workflow-configuration 7 | jobs: 8 | npm-publish: 9 | name: npm-publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@master 14 | - name: Set up Node.js 15 | uses: actions/setup-node@master 16 | with: 17 | node-version: 12.0.0 18 | - name: Install yarn 19 | run: npm install yarn -g 20 | - name: Install deps 21 | run: yarn install 22 | - name: Build bundle 23 | run: yarn build 24 | - name: Publish if version has been updated 25 | uses: pascalgn/npm-publish-action@06e0830ea83eea10ed4a62654eeaedafb8bf50fc 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 VLZH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const resolve = require("rollup-plugin-node-resolve"); 2 | const babel = require("rollup-plugin-babel"); 3 | const commonjs = require("rollup-plugin-commonjs"); 4 | const { terser } = require("rollup-plugin-terser"); 5 | 6 | const isProduction = process.env.NODE_ENV === "production"; 7 | 8 | module.exports = { 9 | input: "src/index.js", 10 | output: { 11 | file: "./dist/index.js", 12 | format: "cjs", 13 | }, 14 | plugins: [ 15 | resolve({ 16 | // pass custom options to the resolve plugin 17 | customResolveOptions: { 18 | moduleDirectory: "node_modules", 19 | }, 20 | }), 21 | babel({ 22 | exclude: "node_modules/**", // only transpile our source code 23 | }), 24 | commonjs({ 25 | include: "node_modules/**", 26 | namedExports: {}, 27 | }), 28 | // production plugins 29 | ...(isProduction ? [terser()] : []), 30 | ], 31 | external: [ 32 | "browser-or-node", 33 | "lg-autoplay.js", 34 | "lg-fullscreen.js", 35 | "lg-hash.js", 36 | "lg-pager.js", 37 | "lg-share.js", 38 | "lg-thumbnail.js", 39 | "lg-video.js", 40 | "lg-zoom.js", 41 | "lightgallery.js", 42 | "prop-types", 43 | "react", 44 | "react-dom", 45 | "uniqid", 46 | ], 47 | inlineDynamicImports: true, 48 | }; 49 | -------------------------------------------------------------------------------- /src/LightgalleryItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PT from "prop-types"; 3 | import uniqid from "uniqid"; 4 | // 5 | import lightgalleryContext from "./lightgalleryContext"; 6 | import { addPrefix } from "./utils"; 7 | 8 | export class LightgalleryItem extends Component { 9 | static propTypes = { 10 | children: PT.any, 11 | group: PT.string.isRequired, 12 | src: PT.string.isRequired, 13 | thumb: PT.string, 14 | subHtml: PT.oneOfType([PT.string, PT.object]), 15 | downloadUrl: PT.string, 16 | itemClassName: PT.string, 17 | }; 18 | static contextType = lightgalleryContext; 19 | 20 | state = { 21 | id: uniqid(), 22 | }; 23 | 24 | componentDidMount() { 25 | this.register(); 26 | } 27 | 28 | componentWillUnmount() { 29 | this.unregister(); 30 | } 31 | 32 | /** 33 | * Register this slide in provider 34 | */ 35 | register = () => { 36 | const { src, thumb = src, subHtml = "", downloadUrl = "" } = this.props; 37 | this.context.registerPhoto(this.state.id, this.props.group, { 38 | src, 39 | thumb, 40 | subHtml, 41 | downloadUrl, 42 | }); 43 | }; 44 | 45 | /** 46 | * Unregister this slide in provider 47 | */ 48 | unregister = () => { 49 | this.context.unregisterPhoto(this.state.id, this.props.group); 50 | }; 51 | 52 | /** 53 | * Open this slide 54 | */ 55 | open = () => { 56 | this.context.openGallery(this.state.id, this.props.group); 57 | }; 58 | 59 | render() { 60 | const { itemClassName = addPrefix("item"), children } = this.props; 61 | return ( 62 |
63 | {children} 64 |
65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 15 | 19 | react-lightgallery example 20 | 21 | 22 | 23 | 52 | 53 |
54 |
55 | 58 |
59 | 60 |
61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-lightgallery", 3 | "version": "0.9.0", 4 | "description": "React wrapper for https://github.com/sachinchoolur/lightgallery.js", 5 | "keywords": [ 6 | "react", 7 | "gallery", 8 | "image-gallery", 9 | "lightbox", 10 | "lightgallery.js", 11 | "lg-zoom.js", 12 | "lg-thumbnail.js", 13 | "lg-video.js", 14 | "lightgallery" 15 | ], 16 | "main": "dist/index.js", 17 | "scripts": { 18 | "build": "NODE_ENV=production yarn rollup -c ./rollup.config.js", 19 | "build:example": "NODE_ENV=production yarn rollup -c ./rollup.config.js", 20 | "start": "yarn rollup --watch -c ./rollup.config.js", 21 | "start:example": "parcel serve --out-dir ./.example_dist ./example/index.html", 22 | "lint": "yarn eslint ./" 23 | }, 24 | "author": "VLZH", 25 | "repository": "https://github.com/VLZH/react-lightgallery.git", 26 | "license": "MIT", 27 | "dependencies": { 28 | "browser-or-node": "^1.3.0", 29 | "hoist-non-react-statics": "^3.3.2", 30 | "lg-autoplay.js": "^1.2.0", 31 | "lg-fullscreen.js": "^1.2.0", 32 | "lg-hash.js": "^1.0.0", 33 | "lg-pager.js": "^1.0.0", 34 | "lg-share.js": "^1.3.0", 35 | "lg-thumbnail.js": "^1.2.0", 36 | "lg-video.js": "^1.2.0", 37 | "lg-zoom.js": "^1.2.0", 38 | "lightgallery.js": "^1.2.0", 39 | "lodash": "^4.17.15", 40 | "prop-types": "^15.7.2", 41 | "uniqid": "^5.2.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.10.2", 45 | "@babel/plugin-proposal-class-properties": "^7.10.1", 46 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 47 | "@babel/preset-env": "^7.10.2", 48 | "@babel/preset-react": "^7.10.1", 49 | "babel-eslint": "10.1.0", 50 | "eslint": "^7.2.0", 51 | "eslint-config-prettier": "^6.11.0", 52 | "eslint-plugin-import": "^2.21.2", 53 | "eslint-plugin-prettier": "^3.1.3", 54 | "eslint-plugin-react": "^7.20.0", 55 | "husky": "^4.2.5", 56 | "parcel-bundler": "^1.12.3", 57 | "prettier": "^2.0.5", 58 | "react": "^16.13.1", 59 | "react-dom": "^16.13.1", 60 | "rollup": "^2.15.0", 61 | "rollup-plugin-babel": "^4.4.0", 62 | "rollup-plugin-commonjs": "^10.1.0", 63 | "rollup-plugin-node-resolve": "^5.2.0", 64 | "rollup-plugin-terser": "^6.1.0", 65 | "rollup-plugin-uglify": "^6.0.3" 66 | }, 67 | "peerDependencies": { 68 | "react": "^16.13.1", 69 | "react-dom": "^16.13.1" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import PT from "prop-types"; 3 | import ReactDOM from "react-dom"; 4 | import { 5 | LightgalleryProvider, 6 | LightgalleryItem, 7 | withLightgallery, 8 | useLightgallery, 9 | } from "../dist/index.js"; 10 | // 11 | import "./styles.css"; 12 | import "lightgallery.js/dist/css/lightgallery.css"; 13 | 14 | const GROUP1 = [ 15 | [ 16 | "https://images.unsplash.com/photo-1592549585866-486f41343aaf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 17 | "https://images.unsplash.com/photo-1592549585866-486f41343aaf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 18 | ], 19 | [ 20 | "https://images.unsplash.com/photo-1594614271360-0ed9a570ae15?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 21 | "https://images.unsplash.com/photo-1594614271360-0ed9a570ae15?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 22 | ], 23 | ]; 24 | 25 | const GROUP2 = [ 26 | "https://images.unsplash.com/photo-1594818898109-44704fb548f6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 27 | "https://images.unsplash.com/photo-1594818896795-35ad7bcf3c6a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 28 | "https://images.unsplash.com/photo-1594818896744-57eca4d47b07?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80", 29 | "https://images.unsplash.com/photo-1594818897077-aec41f55241f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1951&q=80", 30 | ]; 31 | 32 | const PhotoItem = ({ image, thumb, group }) => ( 33 |
34 | 35 | 36 | 37 |
38 | ); 39 | PhotoItem.propTypes = { 40 | image: PT.string.isRequired, 41 | thumb: PT.string, 42 | group: PT.string.isRequired, 43 | }; 44 | 45 | const OpenButtonWithHoc = withLightgallery( 46 | ({ openGallery, className, ...rest }) => { 47 | return ( 48 | 91 | 92 |
93 | {visible ? ( 94 | console.info("onBeforeOpen")} 96 | onAfterOpen={() => console.info("onAfterOpen")} 97 | onSlideItemLoad={() => console.info("onSlideItemLoad")} 98 | onBeforeSlide={() => console.info("onBeforeSlide")} 99 | onAfterSlide={() => console.info("onAfterSlide")} 100 | onBeforePrevSlide={() => 101 | console.info("onBeforePrevSlide") 102 | } 103 | onBeforeNextSlide={() => 104 | console.info("onBeforeNextSlide") 105 | } 106 | onDragstart={() => console.info("onDragstart")} 107 | onDragmove={() => console.info("onDragmove")} 108 | onDragend={() => console.info("onDragend")} 109 | onSlideClick={() => console.info("onSlideClick")} 110 | onBeforeClose={() => console.info("onBeforeClose")} 111 | onCloseAfter={() => console.info("onCloseAfter")} 112 | > 113 |

Group 1

114 |
121 | {GROUP1.map((p, idx) => ( 122 | 128 | ))} 129 |
130 |

Group 2

131 |
138 | {GROUP2.map((p, idx) => ( 139 | 140 | ))} 141 |
142 |
143 | 144 | Open first photos group (using hoc) 145 | 146 | 147 | Open second photos group (using hook) 148 | 149 | 150 | Open second photos group with index 3 (using 151 | hook) 152 | 153 |
154 |
155 | ) : null} 156 |
157 | 158 | ); 159 | } 160 | 161 | const rootElement = document.getElementById("root"); 162 | ReactDOM.render(, rootElement); 163 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This package is react wrapper for: [lightgallery.js](https://sachinchoolur.github.io/lightgallery.js) 4 | 5 | ![npm](https://img.shields.io/npm/dm/react-lightgallery) ![GitHub issues](https://img.shields.io/github/issues-raw/vlzh/react-lightgallery) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/react-lightgallery) 6 | 7 | # Installation 8 | 9 | ```bash 10 | yarn add react-lightgallery 11 | ``` 12 | 13 | or 14 | 15 | ```bash 16 | npm install --save react-lightgallery 17 | ``` 18 | 19 | # Run example 20 | 21 | ``` 22 | git clone git@github.com:VLZH/react-lightgallery.git 23 | # go to the project folder 24 | cd ./react-lightgallery 25 | # install dependencies 26 | yarn install 27 | # run example 28 | yarn start:example 29 | ``` 30 | 31 | ## Live demo 32 | 33 | [![Edit react-lightgallery1](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/mo45kpo92j?fontsize=14) 34 | 35 | # Usage 36 | 37 | Import `.css` file in your code: 38 | 39 | ```javascript 40 | // some Root.js file 41 | import "lightgallery.js/dist/css/lightgallery.css"; 42 | ``` 43 | 44 | Add the **provider** to your a high-level component 45 | 46 | ```javascript 47 | // some Root.js file 48 | import React from "react"; 49 | import { LightgalleryProvider } from "react-lightgallery"; 50 | 51 | class App extends React.Component { 52 | render() { 53 | return ( 54 | 62 | // your components 63 | 64 | ); 65 | } 66 | } 67 | ``` 68 | 69 | The Provider is the manager of `photo-groups` in a case when you want to have several sets of photos, also this is settings storage for lightgallery.js 70 | 71 | Wrap some elements by `` 72 | 73 | ```javascript 74 | // some PhotoItem.js file 75 | import { LightgalleryItem } from "react-lightgallery"; 76 | 77 | const PhotoItem = ({ image, url, title }) => ( 78 |
79 | 80 | 81 | 82 | 83 | 90 | 91 | 92 | 93 |
94 | ); 95 | ``` 96 | 97 | # Props 98 | 99 | ## LightgalleryProvider 100 | 101 | | Prop | Type | Default | Required | Description | 102 | | --------------------- | -------- | ---------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | 103 | | lightgallerySettings | Object | {} | no | Setting for lightgallery. [More information](https://sachinchoolur.github.io/lightgallery.js/docs/api.html#options) | 104 | | galleryClassName | String | "react_lightgallery_gallery" | no | Class name of gallery target element | 105 | | portalElementSelector | String | body | no | Portal target element for adding divelement(lightgallery target element) | 106 | | plugins | String[] | [ "lg-fullscreen.js", "lg-thumbnail.js", "lg-video.js", "lg-zoom.js" ] | no | List of enabled plugins | 107 | 108 | ### List of supported plugins 109 | 110 | - lg-autoplay.js 111 | - lg-fullscreen.js 112 | - lg-hash.js 113 | - lg-pager.js 114 | - lg-thumbnail.js 115 | - lg-video.js 116 | - lg-zoom.js 117 | - lg-share.j 118 | 119 | ### Supported Events 120 | 121 | You can access to events by using these **props**: 122 | 123 | | Prop | Type | 124 | | ----------------- | -------- | 125 | | onAfterOpen | Function | 126 | | onSlideItemLoad | Function | 127 | | onBeforeSlide | Function | 128 | | onAfterSlide | Function | 129 | | onBeforePrevSlide | Function | 130 | | onBeforeNextSlide | Function | 131 | | onDragstart | Function | 132 | | onDragmove | Function | 133 | | onDragend | Function | 134 | | onSlideClick | Function | 135 | | onBeforeClose | Function | 136 | | onCloseAfter | Function | 137 | 138 | Example of using events: 139 | 140 | ```javascript 141 | class App extends React.Component { 142 | render() { 143 | return ( 144 | { 146 | console.log(lightgallery_object); 147 | console.log( 148 | `Prev slide index: ${event.detail.prevIndex}; Current index: ${event.detail.index}` 149 | ); 150 | }} 151 | > 152 | // your components 153 | 154 | ); 155 | } 156 | } 157 | ``` 158 | 159 | ## LightgalleryItem 160 | 161 | | Prop | Type | Default | Required | Description | 162 | | ------------- | ------ | ------------------------- | -------- | --------------------------------------------------------------- | 163 | | group | String | undefined | yes | Name of group of photos set | 164 | | src | String | undefined | yes | Url to image | 165 | | thumb | String | same as `src`👆 | no | Url to image | 166 | | downloadUrl | String | undefined | no | Link for download link | 167 | | subHtml | String | undefined | no | id or class name of an object(div) which contain your sub html. | 168 | | itemClassName | String | "react_lightgallery_item" | no | class name of wrapper(div) of children | 169 | 170 | # HOCs and Hooks 171 | 172 | > ⚠️ Note! 173 | > You should to use this HOCs and hooks only inside of `LightgalleryProvider` 174 | 175 | ## useLightgallery 176 | 177 | React hook that returns `openGallery` function for opening of a group. 178 | 179 | ### Example 180 | 181 | ```javascript 182 | import React, { useCallback } from "react"; 183 | import { useLightgallery } from "react-lightgallery"; 184 | 185 | function MySuperButton({ group_name }) { 186 | const { openGallery } = useLightgallery(); 187 | 188 | const open = useCallback(() => { 189 | openGallery(group_name, 5); // you must to define target group, index (second parameter) is optional 190 | }, [group_name]); 191 | 192 | return ; 193 | } 194 | ``` 195 | 196 | ## withLightgallery 197 | 198 | React HOC for providing `openGallery` function. 199 | 200 | ### Example 201 | 202 | ```javascript 203 | import React, { useCallback } from "react"; 204 | import { withLightgallery } from "react-lightgallery"; 205 | 206 | @withLightgallery 207 | class MySuperButton(){ 208 | open = () => { 209 | this.props.openGallery("very_cool_group") 210 | } 211 | 212 | render() { 213 | return ; 214 | } 215 | } 216 | ``` 217 | 218 | # TODO 219 | 220 | - Rewrite to typescript 221 | - Remove lightgallery.js and plugins imports and provide this job to user(developer) (new major version) 222 | - Write tests 223 | - Support of video 224 | - Access to specific events through LightgalleryItem, like: `onOpen`, `onLeave`, `onEnter` 225 | - More options from lightgallery for LightgalleryItem 226 | -------------------------------------------------------------------------------- /src/LightgalleryProvider.js: -------------------------------------------------------------------------------- 1 | import React, { Component, createRef } from "react"; 2 | import { createPortal } from "react-dom"; 3 | import PT from "prop-types"; 4 | import debounce from "lodash/debounce"; 5 | import { isBrowser } from "browser-or-node"; 6 | // 7 | import { lightgalleryContext } from "./lightgalleryContext"; 8 | import { addPrefix } from "./utils"; 9 | 10 | const PLUGINS_LIST = [ 11 | "lg-autoplay.js", 12 | "lg-fullscreen.js", 13 | "lg-hash.js", 14 | "lg-pager.js", 15 | "lg-thumbnail.js", 16 | "lg-video.js", 17 | "lg-zoom.js", 18 | "lg-share.js", 19 | ]; 20 | 21 | const DEFAULT_PLUGINS = [ 22 | "lg-fullscreen.js", 23 | "lg-thumbnail.js", 24 | "lg-video.js", 25 | "lg-zoom.js", 26 | ]; 27 | 28 | export class LightgalleryProvider extends Component { 29 | static defaultProps = { 30 | plugins: DEFAULT_PLUGINS, 31 | }; 32 | 33 | static propTypes = { 34 | children: PT.any, 35 | plugins: PT.arrayOf(PT.oneOf(PLUGINS_LIST)), 36 | // https://sachinchoolur.github.io/lightgallery.js/docs/api.html#lightgallery-core 37 | lightgallerySettings: PT.object, 38 | galleryClassName: PT.string, 39 | portalElementSelector: PT.string, 40 | // events 41 | onBeforeOpen: PT.func, 42 | onAfterOpen: PT.func, 43 | onSlideItemLoad: PT.func, 44 | onBeforeSlide: PT.func, 45 | onAfterSlide: PT.func, 46 | onBeforePrevSlide: PT.func, 47 | onBeforeNextSlide: PT.func, 48 | onDragstart: PT.func, 49 | onDragmove: PT.func, 50 | onDragend: PT.func, 51 | onSlideClick: PT.func, 52 | onBeforeClose: PT.func, 53 | onCloseAfter: PT.func, 54 | // special callback that will call on loading of lightgallery.js 55 | onLightgalleryImport: PT.func, 56 | }; 57 | 58 | gallery_element = createRef(); 59 | 60 | _groups = {}; 61 | // keep event handlers for deleting they from element 62 | _listeners = {}; 63 | // this component will unmount and we must prevent forceUpdate call from children 64 | _will_unmount = false; 65 | 66 | componentDidMount() { 67 | this.loadLightgalleryJS(); 68 | } 69 | 70 | componentWillUnmount() { 71 | this.destroy(); 72 | } 73 | 74 | _forceUpdate = debounce(this.forceUpdate, 50); 75 | 76 | /** 77 | * Lightgallery.js have a lot of browser-only code 78 | * For minimize bundle size on first load of page we will load lightgallery.js only using dynamic import 79 | * If lightgallery already loaded on page we will ignore load operation 80 | */ 81 | loadLightgalleryJS = () => { 82 | const { plugins, onLightgalleryImport } = this.props; 83 | if (isBrowser && !window.lgData) { 84 | import("lightgallery.js").then(() => { 85 | if (plugins.includes("lg-autoplay.js")) { 86 | import("lg-autoplay.js").then(); 87 | } 88 | if (plugins.includes("lg-fullscreen.js")) { 89 | import("lg-fullscreen.js").then(); 90 | } 91 | if (plugins.includes("lg-hash.js")) { 92 | import("lg-hash.js").then(); 93 | } 94 | if (plugins.includes("lg-pager.js")) { 95 | import("lg-pager.js").then(); 96 | } 97 | if (plugins.includes("lg-thumbnail.js")) { 98 | import("lg-thumbnail.js").then(); 99 | } 100 | if (plugins.includes("lg-video.js")) { 101 | import("lg-video.js").then(); 102 | } 103 | if (plugins.includes("lg-zoom.js")) { 104 | import("lg-zoom.js").then(); 105 | } 106 | if (plugins.includes("lg-share.js")) { 107 | import("lg-share.js").then(); 108 | } 109 | if (onLightgalleryImport) { 110 | onLightgalleryImport(); 111 | } 112 | }); 113 | } 114 | }; 115 | 116 | destroy = () => { 117 | this._will_unmount = true; 118 | this._forceUpdate.cancel(); 119 | this.destroyExistGallery(); 120 | }; 121 | 122 | /** 123 | * Get unique id of gallery (Example: 'lg3') 124 | */ 125 | getLgUid = () => { 126 | if (this.gallery_element.current) 127 | return this.gallery_element.current.getAttribute("lg-uid"); 128 | }; 129 | 130 | hasGroup = (group_name) => { 131 | return this._groups.hasOwnProperty(group_name); 132 | }; 133 | 134 | /** 135 | * Register new photo in group 136 | * After first operation we will add deferred task for rerender 137 | */ 138 | registerPhoto = (item_uid, group_name, options) => { 139 | this._groups = { 140 | ...this._groups, 141 | [group_name]: [ 142 | ...(this._groups[group_name] || []), 143 | { ...options, uid: item_uid }, 144 | ], 145 | }; 146 | this._forceUpdate(); 147 | }; 148 | 149 | /** 150 | * Remove photo from group. 151 | * After first operation we will add deferred task for rerender. 152 | * If this gallery already marked as to be unmount we will ignore this operations, firstly for ignoring calling _forceUpdate 153 | */ 154 | unregisterPhoto = (item_uid, group_name) => { 155 | if (this._will_unmount) return; 156 | this._groups = { 157 | ...this._groups, 158 | [group_name]: this._groups[group_name].filter( 159 | (opts) => opts.uid !== item_uid 160 | ), 161 | }; 162 | this._forceUpdate(); 163 | }; 164 | 165 | /** 166 | * Get lightgallery object 167 | */ 168 | getLightgalleryObject = () => { 169 | return window.lgData[this.getLgUid()]; 170 | }; 171 | 172 | /** 173 | * Destroy already exists lightgallery and remove all listeners 174 | */ 175 | destroyExistGallery = () => { 176 | if ( 177 | typeof window === "object" && 178 | window.lgData && 179 | window.lgData[this.getLgUid()] 180 | ) { 181 | this.removeListeners(); 182 | this.getLightgalleryObject().destroy(true); 183 | } 184 | }; 185 | 186 | /** 187 | * Register new listener on gallery HTMLElement 188 | * @prop {string} event_type - event name/type 189 | * @prop {function} system_handler - system handler for correct working of this component 190 | */ 191 | setUpListener = (event_type, system_handler) => { 192 | const el = this.gallery_element.current; 193 | const handler = (event) => { 194 | if (this.props[event_type]) { 195 | this.props[event_type](event, this.getLightgalleryObject()); 196 | } 197 | if (system_handler) { 198 | system_handler(); 199 | } 200 | }; 201 | el.addEventListener(event_type, handler); 202 | // TODO: remove extra check 203 | if (this._listeners[event_type]) { 204 | console.error(`Event ${event_type} already exist in _listeners`); 205 | } 206 | this._listeners[event_type] = handler; 207 | }; 208 | 209 | /** 210 | * Remove listener from slider-element 211 | * @prop {string} event_type - event name/type 212 | */ 213 | removeListener = (event_type) => { 214 | const el = this.gallery_element.current; 215 | // TODO: remove extra check 216 | if (this._listeners[event_type]) { 217 | el.removeEventListener(event_type, this._listeners[event_type]); 218 | delete this._listeners[event_type]; 219 | } 220 | }; 221 | 222 | removeListeners = () => { 223 | for (const key in this._listeners) { 224 | this.removeListener(key); 225 | } 226 | }; 227 | 228 | /** 229 | * Setup listeners for events of interest to us 230 | */ 231 | setupListeners = () => { 232 | this.setUpListener("onBeforeOpen"); 233 | this.setUpListener("onAfterOpen"); 234 | this.setUpListener("onSlideItemLoad"); 235 | this.setUpListener("onBeforeSlide"); 236 | this.setUpListener("onAfterSlide"); 237 | this.setUpListener("onBeforePrevSlide"); 238 | this.setUpListener("onBeforeNextSlide"); 239 | this.setUpListener("onDragstart"); 240 | this.setUpListener("onDragmove"); 241 | this.setUpListener("onDragend"); 242 | this.setUpListener("onSlideClick"); 243 | this.setUpListener("onBeforeClose"); 244 | this.setUpListener("onCloseAfter", () => { 245 | setTimeout(() => { 246 | this.destroyExistGallery(); 247 | }, 0); 248 | }); 249 | }; 250 | 251 | /** 252 | * Returns group by name, if group with specific name does not exist this function print error message to console. 253 | * @param {String} group_name name of group 254 | */ 255 | getGroupByName = (group_name) => { 256 | if (!this.hasGroup(group_name)) { 257 | console.error( 258 | `Trying to open undefined group with name '${group_name}'` 259 | ); 260 | return; 261 | } 262 | const current_group = this._groups[group_name]; 263 | return current_group; 264 | }; 265 | 266 | /** 267 | * Open gallery with specific item by item unique id. 268 | * @param {String} item_uid unique id of item 269 | * @param {String} group_name name of group 270 | */ 271 | openGallery = (item_uid, group_name) => { 272 | const current_group = this.getGroupByName(group_name); 273 | const index = Math.max( 274 | current_group.findIndex((i) => i.uid === item_uid), 275 | 0 276 | ); 277 | this.openGalleryByIndex(index, group_name); 278 | }; 279 | 280 | /** 281 | * @param {Number} item_idx number of slide 282 | * @param {String} group_name name of group 283 | */ 284 | openGalleryByIndex = (item_idx = 0, group_name) => { 285 | if (!this.gallery_element.current) { 286 | console.error( 287 | "Error on trying to open gallery; ref 'gallery_element' is not defined" 288 | ); 289 | return; 290 | } 291 | // force destroy previous gallery (most expected that gallery already destroyed on closing of gallery) 292 | this.destroyExistGallery(); 293 | // open new gallery 294 | const current_group = this.getGroupByName(group_name); 295 | lightGallery(this.gallery_element.current, { 296 | ...(this.props.lightgallerySettings || {}), 297 | dynamic: true, 298 | dynamicEl: current_group, 299 | index: item_idx, 300 | }); 301 | this.setupListeners(); 302 | }; 303 | 304 | render() { 305 | const { 306 | galleryClassName = addPrefix("gallery"), 307 | portalElementSelector, 308 | } = this.props; 309 | let portalTarget = null; 310 | if (isBrowser) { 311 | portalTarget = document.body; 312 | if (portalElementSelector) { 313 | const el = document.querySelector(portalElementSelector); 314 | if (!el) { 315 | console.error( 316 | "There is cannot to find element by selector: `${portalElementSelector}` lightgallery element will be added to document.body" 317 | ); 318 | } 319 | portalTarget = el; 320 | } 321 | } 322 | return ( 323 | 332 | {this.props.children} 333 | {portalTarget && 334 | createPortal( 335 |
, 339 | portalTarget 340 | )} 341 | 342 | ); 343 | } 344 | } 345 | --------------------------------------------------------------------------------