Repository: guonanci/react-images-viewer
Branch: master
Commit: 67a0da9b536b
Files: 58
Total size: 117.2 KB
Directory structure:
gitextract_t5wlb761/
├── .babelrc
├── .coveralls.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github/
│ ├── CONTRIBUTING.md
│ ├── HISTORY.md
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── README_CN.md
├── __mocks__/
│ └── ImgsViewer.js
├── __test__/
│ ├── Gallery.test.js
│ ├── jestsetup.js
│ ├── rafShim.js
│ └── utils.test.js
├── ci.sh
├── examples/
│ └── src/
│ ├── .gitignore
│ ├── .npmignore
│ ├── app.js
│ ├── app_CN.js
│ ├── components/
│ │ ├── DownloadButton/
│ │ │ ├── icon.js
│ │ │ └── index.js
│ │ ├── Gallery.js
│ │ └── Spinner.js
│ ├── example.less
│ ├── index.html
│ ├── index_CN.html
│ └── standalone.html
├── index.js
├── jest.config.js
├── new-branch.sh
├── package-scripts.js
├── package.json
├── rollup.config.js
├── src/
│ ├── ImgsViewer.js
│ ├── components/
│ │ ├── Arrow.js
│ │ ├── Container.js
│ │ ├── Footer.js
│ │ ├── Header.js
│ │ ├── Icon.js
│ │ ├── PaginatedThumbnails.js
│ │ ├── Portal.js
│ │ ├── Spinner.js
│ │ ├── Thumbnail.js
│ │ └── Thumbnails.js
│ ├── icons/
│ │ ├── Close.js
│ │ ├── arrow_left.js
│ │ ├── arrow_right.js
│ │ └── close.js
│ ├── theme.js
│ └── utils/
│ ├── constant.js
│ └── util.js
└── webpack.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"ignore": ["node-modules"],
"presets": ["env", "react"],
"plugins": [ ["transform-object-rest-spread", { "useBuiltIns": true }], "transform-object-assign"],
"env": {
"test": {
"presets": [
["env"], "react"
],
"plugins": ["istanbul"]
}
}
}
================================================
FILE: .coveralls.yml
================================================
service_name: travis-ci
repo_token: S5CSBnH2886RuV3s0ecSjaam2N4JOjsC7
# coverageReporter: {
# dir: path.join(__dirname, 'coverage'),
# reporters: [
# {type: 'html'},
# {type: 'lcov', subdir: 'lcov'} // lcov
# ]
# },
================================================
FILE: .editorconfig
================================================
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .eslintignore
================================================
lib/*
dist/*
coverage/*
node_modules/*
================================================
FILE: .eslintrc.js
================================================
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
parserOptions: {
sourceType: 'module'
},
env: {
node: true,
browser: true,
commonjs: true,
es6: true,
jest: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
],
// required to lint *.vue files
plugins: [
'react',
],
// check if imports actually resolve
settings: {
react: {
createClass: 'createReactClass',
pragma: 'React',
version: '16.4.1'
},
},
rules: {
'no-unused-vars': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-console': process.env.NODE_ENV === 'production' ? 2 : 0,
"no-unused-vars": [2, { "vars": "all", "args": "after-used" }],
'global-require': 0,
}
}
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
Thanks for your interest in react-images-viewer. All forms fo contribution ares welcome, from issue reports to PRs and documentation / write-ups.
Before you open a PR:
* If you're planning to add or change a major feature in a PR, please ensure the change is aligned with the project roadmap by opening an issue first, especially if you're going to spend a lot of time on it.
* In development, run `yarn start` to build(+watch) the project source, and run the [development server](http://localhost:8000).
* Please ensure all the examples work correctly after your change. If you're adding a major new use-case, add a new example demonstrating its use.
* Please **DO NOT** commit the build files. Make sure **ONLY** your changes to `/src/` and `/examples/src` are included in your PR.
* Be careful to follow the code style of the project. Run `yarn ru lint` after your changes and ensure you do not introduce any new errors or warnings.
* All new features and changes need documentation.
* Make sure yarn.lock is updated when you add or upgrade dependencies.
================================================
FILE: .github/HISTORY.md
================================================
# React-Images-Viewer
### v1.6.8 / 2022-01-03
- Update: fix issues: #28, #38
### v1.3.0 / 2018-07-24
- Update: Make README.md and README_ZN.md desciption more specific
- Fix: correct `package.json - main` field value
---
### v1.0.5 / 2018-07-24
- Basic, thumbnailed, themed usage all work well
- Feature: you can use `up, right, bottom, left, space, esc` to navigate quickly
- Component API is very brief and precise
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
**Steps to reproduce the behavior:**
**Expected behavior:**
**Actual behavior:**
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Please make sure the following is filled in before submitting your Pull Request, thanks! -->
**Description of changes:**
**Related issues (if any):**
**Checks:**
- [ ] Please confirm `yarn run lint` ran successfully
- [ ] Please confirm that only `/src` and `/examples/src` are committed
- [ ] [if new feature] Please confim that docuemtation was added to the README.md
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build
lib
dist
examples/dist
# Webstorm
.idea/
# VSCode
.vscode/
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Site build
.publish
# Dependency directories
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules/
jspm_packages/
# temp files
.swp
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
__test__/__snapshots__/
.js.snap
# MacOS generated file
.DS_Store
================================================
FILE: .npmignore
================================================
examples
================================================
FILE: .travis.yml
================================================
language: node_js
sudo: false
node_js:
- stable
cache:
directories:
- node_modules
install:
|
npm install -g yarn
npm --version
yarn
script:
- yarn lint
- yarn test
- yarn coverage
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 yiminanci
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# react-images-viewer
[](https://travis-ci.org/guonanci/react-images-viewer)
<!-- [](https://coveralls.io/github/guonanci/react-images-viewer?branch=master) -->
[](https://github.com/facebook/jest)
A react library that view photos list easily, and a simple, responsive viewer component for displaying an array of images.
[中文文档](./README_CN.md)
## Quick start
```bash
# recommended
yarn add react-images-viewer
```
or
```bash
npm install react-images-viewer --save
```
```jsx
import React from "react";
import ImgsViewer from "react-images-viewer";
export default class Demo extends React.Component {
render() {
return (
<ImgsViewer
imgs={[
{ src: "http://example.com/img1.jpg" },
{ src: "http://example.com/img2.png" },
]}
currImg={this.state.currImg}
isOpen={this.state.viewerIsOpen}
onClickPrev={this.gotoPrevious}
onClickNext={this.gotoNext}
onClose={this.closeViewer}
/>
);
}
}
```
## Demo & Example
Live Demo: [guonanci.github.io/react-images-viewer](https://guonanci.github.io/react-images-viewer)
```bash
yarn install
yarn
yarn start
```
Then open [`localhost:8000`](http://localhost:8000) in a browser.
### Using srcSet
Example using srcSet:
```jsx
<ImgsViewer
imgs={IMG_SET}
...
/>
```
```js
const IMG_SET = [
{
src: "http://example.com/img1.svg",
caption: "A forest",
// As an array
srcSet: [
"http://example.com/img1_1024.jpg 1024w",
"http://example.com/img1_800.jpg 800w",
"http://example.com/img1_500.jpg 500w",
"http://example.com/img1_320.jpg 320w",
],
},
{
src: "http://example.com/img2.svg",
// As a string
srcSet:
"http://example.com/img2_1024.jpg 1024w, http://example.com/img2_800.jpg 800w, http://example.com/img2_500.jpg 500w, http://example.com/img2_320.jpg 320w",
},
];
```
## Options
| Property | Type | Default | Description |
| :------------------ | :--------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------ |
| backdropCloseable | `boolean` | `false` | Allow users to exit the viewer by clicking the backdrop |
| closeBtnTitle | `str` | `undefined` | Customize close esc title |
| enableKeyboardInput | `boolean` | `true` | Supports keyboard input - <code>space, esc</code>, <code> arrow left, arrow up</code>, and <code>arrow right, arrow down</code> |
| currImg | `number` | `0` | Required if you want to navigate the imgsViewer, The index of the image to display initially |
| customControls | arr | `undefined` | An array of elements to display as custom controls on the top of viewer |
| imgs | arr | `undefined` | Required. Array of image objects, See img opts table below |
| imgCountSeparator | `str` | `' / '` | Customize separator in the image count |
| isOpen | `boolean` | `false` | Required if you want to navigate the imgsViewer, Whether or not the viewer is displayed |
| leftArrowTitle | `str` | `undefined` | Customize of left arrow title |
| onClickPrev | `function` | `undefined` | Required if you want to navigate the imgsViewer, and fired on request of the previous image |
| onClickNext | `function` | `undefined` | Required if you want to navigate the imgsViewer, and fired on request of the next image |
| onClose | `function` | `undefined` | Required if you want to close the imgsViewer, and handle closing of the viewer |
| onClickImg | `function` | `undefined` | Handle click on current image |
| onClickThumbnail | `function` | `undefined` | Handle click on thumbnail |
| preloadNextImg | `boolean` | `true` | Whether to preload the next available image |
| rightArrowTitle | `str` | `undefined` | Customize right arrow title |
| showCloseBtn | `boolean` | `true` | Optionally display a close 'X' button in top right corner |
| showImgCount | `boolean` | `true` | Optionally display image index, e.g., "2 of 20" |
| width | `number` | `1024` | Maximum width of the carousel; defaults to 1024px |
| spinnerDisabled | `boolean` | `false` | Disable Spinner |
| spinner | `function` | `DefaultSpinner` | Spinner component class |
| spinnerColor | `str` | `'#fff'` | Color of spinner |
| spinnerSize | `number` | `50` | Size of spinner |
| preventAutoScroll | `boolean` | `true` | Determines whether auto-scrolling is prevented |
## Images Object
| Property | Type | Default | Description |
| :------- | :-------- | :---------- | :---------- |
| src | `str` | `undefined` | Required |
| srcSet | `string[]` | `undefined` | Optional |
| caption | `str` | `undefined` | Optional |
| alt | `str` | `undefined` | Optional |
================================================
FILE: README_CN.md
================================================
# react-images-viewer
[](https://travis-ci.org/guonanci/react-images-viewer)
<!-- [](https://coveralls.io/github/guonanci/react-images-viewer?branch=master) -->
[](https://github.com/facebook/jest)
一个简单易用,响应式,放大并查看一组图片的 React 库。
[English document](./README.md)
## 快速入门
```bash
# 推荐
yarn add react-images-viewer
```
or
```bash
npm install react-images-viewer --save
```
```jsx
import React from "react";
import ImgsViewer from "react-images-viewer";
export default class Demo extends React.Component {
render() {
return (
<ImgsViewer
imgs={[
{ src: "http://example.com/img1.jpg" },
{ src: "http://example.com/img2.png" },
]}
currImg={this.state.currImg}
isOpen={this.state.viewerIsOpen}
onClickPrev={this.gotoPrevious}
onClickNext={this.gotoNext}
onClose={this.closeViewer}
/>
);
}
}
```
## 例子
线上案例: [guonanci.github.io/react-images-viewer](https://guonanci.github.io/react-images-viewer)
```bash
yarn install
yarn
yarn start
```
然后就可以在浏览器打开 [`localhost:8000`](http://localhost:8000) 窗口了。
### srcSet 使用
例子:
```jsx
<Lightbox
imgs={IMG_SET}
...
>
```
```js
const IMG_SET = [
{
src: "http://example.com/img1.svg",
caption: "A forest",
// As an array
srcSet: [
"http://example.com/img1_1024.jpg 1024w",
"http://example.com/img1_800.jpg 800w",
"http://example.com/img1_500.jpg 500w",
"http://example.com/img1_320.jpg 320w",
],
},
{
src: "http://example.com/img2.svg",
// As a string
srcSet:
"http://example.com/img2_1024.jpg 1024w, http://example.com/img2_800.jpg 800w, http://example.com/img2_500.jpg 500w, http://example.com/img2_320.jpg 320w",
},
];
```
## 选项
| Property | Type | Default | Description |
| :------------------ | :--------- | :--------------- | :-------------------------------------------------------------------------------------------------- |
| backdropCloseable | `布尔值` | `false` | 是否通过点击半透明幕布来退出浏览 |
| closeBtnTitle | `字符串` | `undefined` | 关闭按钮的 title |
| enableKeyboardInput | `布尔值` | `true` | 支持键盘输入 - <code>空格键,esc</code>, <code>左箭头,上箭头</code>,和<code>右箭头,下箭头</code> |
| currImg | `数值类型` | `0` | 必须项(如果需要导航的话),初始化图像的索引 |
| customCtrls | `数组` | `undefined` | 图片查看器顶部的控件元素数组 |
| imgs | `数组` | `undefined` | 必须项. 图片元素数组,图像选项见下表。 |
| imgsSeparator | `字符串` | `' / '` | 图片计数分隔符 |
| isOpen | `布尔值` | `false` | 必须项(如果需要导航的话),图片是否显示 |
| leftArrowTitle | `字符串` | `undefined` | 左箭头的 title |
| onClickPrev | `函数` | `undefined` | 必须项(如果需要导航的话),请求上一张时触发 |
| onClickNext | `函数` | `undefined` | 必须项(如果需要导航的话),请求下一张时触发 |
| onClose | `函数` | `undefined` | 关闭查看器的回调 |
| onClickImg | `函数` | `undefined` | 点起当前图片的回调 |
| onClickThumbnail | `函数` | `undefined` | 缩略点击的回调 |
| preloadNextImg | `布尔值` | `true` | 是否预加载下一张图片 |
| rightArrowTitle | `字符串` | `undefined` | 右箭头的 title |
| showCloseBtn | `布尔值` | `true` | 右上角是否显示 X 按钮 |
| showImgCount | `布尔值` | `true` | 是否显示图片索引 |
| width | `数值类型` | `1024` | 轮播器的最大的宽度,默认值 1024px |
| spinnerDisabled | `布尔值` | `false` | 禁用加载器 |
| spinner | `函数` | `DefaultSpinner` | 加载器组件 |
| spinnerColor | `字符串` | `'#fff'` | 加载器颜色 |
| spinnerSize | `数值类型` | `50` | 加载器尺寸 |
| preventAutoScroll | `布尔值` | `true` | 是否阻止自动滚动 |
## Images Object
| Property | Type | Default | Description |
| :------- | :--------------------- | :---------- | :---------- |
| src | `字符串` | `undefined` | 必须项 |
| srcSet | `字符串`数组或者字符串 | `undefined` | 可选项 |
| caption | `字符串` | `undefined` | 可选项 |
| alt | `字符串` | `undefined` | 可选项 |
================================================
FILE: __mocks__/ImgsViewer.js
================================================
================================================
FILE: __test__/Gallery.test.js
================================================
import React from 'react'
import ImgsViewer from '../src/ImgsViewer'
import { mount } from 'enzyme'
const props1 = {
onClose: () => {},
imgs: [
{
src: 'https://images.unsplash.com/photo-1526382551041-3c817fc3d478?dpr=2&auto=format&w=1024&h=1024'
},
{
src: 'https://images.unsplash.com/photo-1522985225914-17a10a58c8ec?dpr=2&auto=format&w=1024&h=1024',
caption: 'Photo by Blake Cheek',
}
]
}
const props2 = {
onClose: () => {},
imgs: [
{
src: 'https://images.unsplash.com/photo-1522931698295-e7b4d3e4188f?dpr=2&auto=format&w=1024&h=1024',
thumnail: 'https://images.unsplash.com/photo-1522931698295-e7b4d3e4188f?dpr=2&auto=format&crop=faces&fit=crop&w=300&h=300',
},
{
src: 'https://images.unsplash.com/photo-1482398650355-d4c6462afa0e?dpr=2&auto=format&w=1024&h=1024',
thumnail: 'https://images.unsplash.com/photo-1482398650355-d4c6462afa0e?dpr=2&auto=format&crop=faces&fit=crop&w=240&h=159',
}
]
}
it('sum', () => {
expect(1 + 1).toBe(2)
})
// it('renders correctly', () => {
// const component = mount(
// <ImgsViewer { ...props1 } />
// )
// component.setState({ isOpen: true })
// expect(component).toMatchSnapshot()
// })
// it('renders with thumbnail correctly', () => {
// const component = mount(
// <ImgsViewer { ...props2 } />
// )
// component.setState({ isOpen: true })
// expect(component).toMatchSnapshot()
// })
// it('renders correctly after click', () => {
// const component = mount(
// <ImgsViewer { ...props2 } />
// )
// component.setState({ isOpen: true })
// setTimeout(() => {
// component.find('figure').first() && component.find('figure').first().simulate('click')
// expect(component).toMatchSnapshot()
// }, 10000)
// })
// it('unmount', () => {
// const component = mount(
// <ImgsViewer { ...props2 } />
// )
// component.setState({ isOpen: true })
// component.unmount()
// })
================================================
FILE: __test__/jestsetup.js
================================================
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure({ adapter: new Adapter() })
================================================
FILE: __test__/rafShim.js
================================================
global.requestAnimationFrame = callback => setTimeout(callback, 0)
================================================
FILE: __test__/utils.test.js
================================================
import { deepMerge, canUseDom, bindFunctions } from '../src/utils/util'
describe('the deepMerge function', () => {
test('merge numb, stri in one level', () => expect(
deepMerge({ a: 1, b: 2 }, { a: '3', b: 4 })).toEqual({ a: '3', b: 4 })
)
test('replace str with arr', () => expect(
deepMerge({ a: 1 }, { a: ['3', 4] })).toEqual({ a: { 0: '3', 1: 4} })
)
test('replace arr with obj', () => expect(
deepMerge({ a: [1, 2] }, { a: { b: 1, c: '2' } })).toEqual({ a: { 0: 1, 1: 2, b: 1, c: '2' } })
)
test('replace arr with obj', () => expect(
deepMerge({ a: [1, 2] }, { a: { b: 1, c: '2' } })).toEqual({ a: { 0: 1, 1: 2, b: 1, c: '2' } })
)
test('replace obj with obj', () => expect(
deepMerge({ a: { b: 1 } }, { a: { b: '2' } })).toEqual({ a: { b: '2' } })
)
test('replace arr with arr', () => expect(
deepMerge({ a: [1] }, { a: { b: [2, 3] } })).toEqual({ a: { 0: 1, b: [2, 3] } })
)
test('replace obj with num', () => expect(
deepMerge({ a: 1 }, 2)).toEqual({ a: 1 })
)
test('replace num with num', () => expect(
deepMerge(1, 2)).toEqual({ })
)
})
================================================
FILE: ci.sh
================================================
#!/bin/bash -e
# daily push, but add all unStaged changes.
commit_msg="$1"
git add .
git commit -m "$commit_msg"
git pull
git push
================================================
FILE: examples/src/.gitignore
================================================
## This file is here to ensure it is included in the gh-pages branch,
## when 'gulp deploy' is used to push updates to the demo site.
# Dependency directory
node_modules
================================================
FILE: examples/src/.npmignore
================================================
================================================
FILE: examples/src/app.js
================================================
import React from "react";
import { render } from "react-dom";
import Gallery from "./components/Gallery";
import CustomSpinner from "./components/Spinner";
import "./example.less";
function makeUnsplashSrc(id) {
return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=1024&h=1024`;
}
function makeUnsplashSrcSet(id, size) {
return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=${size} ${size}w`;
}
function makeUnsplashThumbnail(id, orientation = "landscape") {
const dimensions = orientation === "square" ? "w=300&h=300" : "w=240&h=159";
return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&crop=faces&fit=crop&${dimensions}`;
}
// Unsplash images from the "Adventure" collection
// https://unsplash.com/collections/369/adventure
const DEFAULT_IMAGES = [
{
id: "1526382551041-3c817fc3d478",
caption: "Photo by Simon Alexander",
orientation: "square",
useForDemo: true,
},
{
id: "1522985225914-17a10a58c8ec",
caption: "Photo by Blake Cheek",
orientation: "square",
useForDemo: true,
},
{
id: "1522931698295-e7b4d3e4188f",
caption: "Photo by Oliver Sjöström",
orientation: "square",
useForDemo: true,
},
{
id: "1516175663209-ac2459a5652f",
caption: "Photo by Jeremy Bishop",
orientation: "square",
useForDemo: true,
},
{
id: "1515911601378-97de98db6dda",
caption: "Photo by Emily Reider",
orientation: "square",
useForDemo: true,
},
];
const THEMED_IMAGES = [
{
id: "1506773090264-ac0b07293a64",
caption: "Photo by Dan Grinwis",
orientation: "square",
useForDemo: true,
},
{
id: "1482398650355-d4c6462afa0e",
caption: "Photo by Andrew Neel",
orientation: "landscape",
useForDemo: true,
},
{
id: "1514949823529-bdcc933a9339",
caption: "Photo by Kristopher Roller",
orientation: "landscape",
useForDemo: true,
},
{
id: "1503293962593-47247718a17a",
caption: "Photo by Jeremy Bishop",
orientation: "landscape",
useForDemo: true,
},
{
id: "1509914398892-963f53e6e2f1",
caption: "Photo by Linus Nylund",
orientation: "landscape",
useForDemo: true,
},
];
const THUMBNAIL_IMAGES = [
{
id: "1501963422762-3d89bd989568",
caption: "Photo by Jeremy Bishop",
orientation: "landscape",
useForDemo: true,
},
{
id: "1418846531910-2b7bb1043512",
caption: "Photo by Vincentiu Solomon",
orientation: "landscape",
useForDemo: true,
},
{
id: "1509529711801-deac231925ac",
caption: "Photo by Joshua Earle",
orientation: "landscape",
useForDemo: true,
},
{
id: "1499062229216-7c6349e898fb",
caption: "Photo by Leio McLaren",
orientation: "square",
useForDemo: true,
},
{
id: "1495619744764-2cc11fcbe5f0",
caption: "Photo by Philipp Kämmerer",
orientation: "square",
useForDemo: true,
},
{
id: "1488584433697-7ccc1148d30c",
caption: "Photo by Flecher Clay",
orientation: "square",
},
{
id: "1478562853135-c3c9e3ef7905",
caption: "Photo by Austin Neil",
orientation: "landscape",
},
{
id: "1476111021705-ac3b3304fe20",
caption: "Photo by Dino Reichmuth",
orientation: "square",
},
{
id: "1478001517127-fccc92f54906",
caption: "Photo by Joshua Earle",
orientation: "landscape",
},
{
id: "1455383333344-451b6147021b",
caption: "Photo by Joshua Earle",
orientation: "landscape",
},
];
const theme = {
// container
container: {
background: "rgba(255, 255, 255, .9)",
},
// arrows
arrow: {
backgroundColor: "rgba(255, 255, 255, .8)",
fill: "#222",
opacity: 0.6,
transition: "opacity 200ms",
":hover": {
opacity: 1,
},
},
arrow__size__medium: {
borderRadius: 40,
height: 40,
marginTop: -20,
"@media (min-width: 768px)": {
height: 70,
padding: 15,
},
},
arrow__direction__left: { marginLeft: 10 },
arrow__direction__right: { marginRight: 10 },
close: {
fill: "#d40000",
opacity: 0.6,
transition: "all 200ms",
":hover": {
opacity: 1,
},
},
// footer
footer: {
color: "#000",
},
footerCount: {
color: "rgba(0, 0, 0, .6)",
},
// thumbnails
thumbnail: {},
thumbnail__active: {
boxShadow: "0 0 0 2px #00d8ff",
},
};
// const theme = {
// container: {
// background: "rgba(255, 255, 255, .9)",
// },
// arrow: {
// backgroundColor: "#222",
// fill: "#222",
// opacity: 0.6,
// transition: "opacity 200ms",
// marginLeft: 300,
// },
// arrow__size__medium: {
// borderRadius: 40,
// height: 40,
// marginTop: -20,
// marginLeft: 300,
// },
// arrow__direction__left: { marginLeft: 300, background: "blue" },
// arrow__direction__right: { marginRight: 10, background: "blue" },
// close: {
// fill: "#d40000",
// opacity: 0.6,
// transition: "all 200ms",
// },
// footer: {
// color: "#000",
// },
// footerCount: {
// color: "rgba(0, 0, 0, .6)",
// },
// thumbnail: {
// activeBorderColor: "#fff",
// size: 50,
// gutter: 2,
// },
// thumbnail__active: {
// boxShadow: "0 0 0 2px #00d8ff",
// },
// };
render(
<div>
<div>
<p>
Photos courtesy of{" "}
<a
href="https://unsplash.com/"
target="_blank"
rel="noopener noreferrer"
>
Unsplash
</a>
. Use your keyboard to navigate <kbd>left, up</kbd>{" "}
<kbd>right, down</kbd> <kbd>space, esc</kbd> — Also, try resizing
your browser window.
</p>
</div>
<h3>Default Options</h3>
<Gallery
imgs={DEFAULT_IMAGES.map(({ caption, id, orientation, useForDemo }) => ({
src: makeUnsplashSrc(id),
thumbnail: makeUnsplashThumbnail(id, orientation),
srcSet: [
makeUnsplashSrcSet(id, 1024),
makeUnsplashSrcSet(id, 800),
makeUnsplashSrcSet(id, 500),
makeUnsplashSrcSet(id, 320),
],
caption,
orientation,
useForDemo,
}))}
/>
<h3>With Thumbnails</h3>
<Gallery
imgs={THUMBNAIL_IMAGES.map(
({ caption, id, orientation, useForDemo }) => ({
src: makeUnsplashSrc(id),
thumbnail: makeUnsplashThumbnail(id, orientation),
srcSet: [
makeUnsplashSrcSet(id, 1024),
makeUnsplashSrcSet(id, 800),
makeUnsplashSrcSet(id, 500),
makeUnsplashSrcSet(id, 320),
],
caption,
orientation,
useForDemo,
})
)}
showThumbnails
/>
<h3>Themed ImgsViewer</h3>
<Gallery
imgs={THEMED_IMAGES.map(({ caption, id, orientation, useForDemo }) => ({
src: makeUnsplashSrc(id),
thumbnail: makeUnsplashThumbnail(id, orientation),
srcSet: [
makeUnsplashSrcSet(id, 1024),
makeUnsplashSrcSet(id, 800),
makeUnsplashSrcSet(id, 500),
makeUnsplashSrcSet(id, 320),
],
caption,
orientation,
useForDemo,
}))}
theme={theme}
spinner={CustomSpinner}
spinnerColor={"#d40000"}
spinnerSize={150}
showThumbnails
/>
</div>,
document.getElementById("example")
);
================================================
FILE: examples/src/app_CN.js
================================================
import React from 'react'
import { render } from 'react-dom'
import Gallery from './components/Gallery'
import CustomSpinner from './components/Spinner'
import './example.less'
function makeUnsplashSrc (id) {
return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=1024&h=1024`
}
function makeUnsplashSrcSet(id, size) {
return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&w=${size} ${size}w`
}
function makeUnsplashThumbnail (id, orientation = 'landscape') {
const dimensions = orientation === 'square' ?
'w=300&h=300' :
'w=240&h=159'
return `https://images.unsplash.com/photo-${id}?dpr=2&auto=format&crop=faces&fit=crop&${dimensions}`
}
// Unsplash images from the "Adventure" collection
// https://unsplash.com/collections/369/adventure
const DEFAULT_IMAGES = [
{ id: '1526382551041-3c817fc3d478', caption: '照片来自Simon Alexander', orientation: 'square', useForDemo: true },
{ id: '1522985225914-17a10a58c8ec', caption: '照片来自Blake Cheek', orientation: 'square', useForDemo: true },
{ id: '1522931698295-e7b4d3e4188f', caption: '照片来自Oliver Sjöström', orientation: 'square', useForDemo: true },
{ id: '1516175663209-ac2459a5652f', caption: '照片来自Jeremy Bishop', orientation: 'square', useForDemo: true },
{ id: '1515911601378-97de98db6dda', caption: '照片来自Emily Reider', orientation: 'square', useForDemo: true },
]
const THEMED_IMAGES = [
{ id: '1506773090264-ac0b07293a64', caption: '照片来自Dan Grinwis', orientation: 'square', useForDemo: true },
{ id: '1482398650355-d4c6462afa0e', caption: '照片来自Andrew Neel', orientation: 'landscape', useForDemo: true },
{ id: '1514949823529-bdcc933a9339', caption: '照片来自Kristopher Roller', orientation: 'landscape', useForDemo: true },
{ id: '1503293962593-47247718a17a', caption: '照片来自Jeremy Bishop', orientation: 'landscape', useForDemo: true },
{ id: '1509914398892-963f53e6e2f1', caption: '照片来自Linus Nylund', orientation: 'landscape', useForDemo: true },
]
const THUMBNAIL_IMAGES = [
{ id: '1501963422762-3d89bd989568', caption: '照片来自Jeremy Bishop', orientation: 'landscape', useForDemo: true },
{ id: '1418846531910-2b7bb1043512', caption: '照片来自Vincentiu Solomon', orientation: 'landscape', useForDemo: true },
{ id: '1509529711801-deac231925ac', caption: '照片来自Joshua Earle', orientation: 'landscape', useForDemo: true },
{ id: '1499062229216-7c6349e898fb', caption: '照片来自Leio McLaren', orientation: 'square', useForDemo: true },
{ id: '1495619744764-2cc11fcbe5f0', caption: '照片来自Philipp Kämmerer', orientation: 'square', useForDemo: true },
{ id: '1488584433697-7ccc1148d30c', caption: '照片来自Flecher Clay', orientation: 'square' },
{ id: '1478562853135-c3c9e3ef7905', caption: '照片来自Austin Neil', orientation: 'landscape' },
{ id: '1476111021705-ac3b3304fe20', caption: '照片来自Dino Reichmuth', orientation: 'square' },
{ id: '1478001517127-fccc92f54906', caption: '照片来自Joshua Earle', orientation: 'landscape' },
{ id: '1455383333344-451b6147021b', caption: '照片来自Joshua Earle', orientation: 'landscape' },
]
const theme = {
// container
container: {
background: 'rgba(255, 255, 255, .9)'
},
// arrows
arrow: {
backgroundColor: 'rgba(255, 255, 255, .8)',
fill: '#222',
opacity: .6,
transition: 'opacity 200ms',
':hover': {
opacity: 1,
}
},
arrow__size__medium: {
borderRadius: 40,
height: 40,
marginTop: -20,
'@media (min-width: 768px)': {
height: 70,
padding: 15,
}
},
arrow__direction__left: { marginLeft: 10 },
arrow__direction__right: { marginRight: 10 },
close: {
fill: '#d40000',
opacity: .6,
transition: 'all 200ms',
':hover': {
opacity: 1
}
},
// footer
footer: {
color: '#000'
},
footerCount: {
color: 'rgba(0, 0, 0, .6)'
},
// thumbnails
thumbnail:{
},
thumbnail__active: {
boxShadow: '0 0 0 2px #00d8ff'
}
};
render(
<div>
<div>
<p>照片均来自
<a href="https://unsplash.com/" target="_blank" rel="noopener noreferrer">Unsplash</a>.
采用以下键盘按钮来导航:
<kbd>向上,向左(上一张)</kbd> <kbd>向下,向右(下一张)</kbd> <kbd>空格,Esc(关闭)</kbd> —
你也可以尝试改变一下浏览器窗口大小~
</p>
</div>
<h3>默认选项</h3>
<Gallery
imgs={DEFAULT_IMAGES.map(({ caption, id, orientation, useForDemo }) => ({
src: makeUnsplashSrc(id),
thumbnail: makeUnsplashThumbnail(id, orientation),
srcSet: [
makeUnsplashSrcSet(id, 1024),
makeUnsplashSrcSet(id, 800),
makeUnsplashSrcSet(id, 500),
makeUnsplashSrcSet(id, 320)
],
caption,
orientation,
useForDemo,
}))}
/>
<h3>缩略图同步</h3>
<Gallery
imgs={THUMBNAIL_IMAGES.map(({ caption, id, orientation, useForDemo }) => ({
src: makeUnsplashSrc(id),
thumbnail: makeUnsplashThumbnail(id, orientation),
srcSet: [
makeUnsplashSrcSet(id, 1024),
makeUnsplashSrcSet(id, 800),
makeUnsplashSrcSet(id, 500),
makeUnsplashSrcSet(id, 320)
],
caption,
orientation,
useForDemo,
}))}
showThumbnails
/>
<h3>主题可定制</h3>
<Gallery
imgs={THEMED_IMAGES.map(({ caption, id, orientation, useForDemo }) => ({
src: makeUnsplashSrc(id),
thumbnail: makeUnsplashThumbnail(id, orientation),
srcSet: [
makeUnsplashSrcSet(id, 1024),
makeUnsplashSrcSet(id, 800),
makeUnsplashSrcSet(id, 500),
makeUnsplashSrcSet(id, 320)
],
caption,
orientation,
useForDemo,
}))}
theme={theme}
spinner={CustomSpinner}
spinnerColor={'#d40000'}
spinnerSize={150}
showThumbnails
/>
</div>,
document.getElementById('example-CN')
)
================================================
FILE: examples/src/components/DownloadButton/icon.js
================================================
module.exports = (
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 24 30" enable-background="new 0 0 24 24" xml:space="preserve">'
+ '<g>'
+ '<polygon fill="white" points="19,14 19,18 5,18 5,14 2,14 2,18 2,22 5,22 19,22 22,22 22,18 22,14 "/>'
+ '<polygon fill="white" points="15,9.9 15,2 9,2 9,9.9 5.8,9.9 12,16.1 18.2,9.9 "/>'
+ '</g>'
+ '</svg>'
);
================================================
FILE: examples/src/components/DownloadButton/index.js
================================================
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import DownloadIcon from './icon'
class DownloadButton extends Component {
constructor () {
super()
}
render () {
return (
<button
title="Download"
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
float: 'left',
height: 40,
outline: 'none',
padding: 10,
position: 'relative',
left: -10,
top: 0,
verticalAlign: 'bottom',
width: 40,
}}
onClick={this.props.handler}
>
<span dangerouslySetInnerHTML={{ __html: DownloadIcon }} />
</button>
)
}
}
export default DownloadButton
================================================
FILE: examples/src/components/Gallery.js
================================================
import React, { Component } from 'react'
import ImgsViewer from 'react-images-viewer'
import PropTypes from 'prop-types'
import { css, StyleSheet } from 'aphrodite/no-important'
class Gallery extends Component {
constructor () {
super()
this.state = {
isOpen: false,
currImg: 0,
}
this.gotoNext = this.gotoNext.bind(this)
this.gotoPrev = this.gotoPrev.bind(this)
this.gotoImg = this.gotoImg.bind(this)
this.handleClickImg = this.handleClickImg.bind(this)
this.closeImgsViewer = this.closeImgsViewer.bind(this)
this.openImgsViewer = this.openImgsViewer.bind(this)
}
openImgsViewer (index, event) {
event.preventDefault()
this.setState({
currImg: index,
isOpen: true,
})
}
closeImgsViewer () {
this.setState({
currImg: 0,
isOpen: false,
})
}
gotoPrev () {
this.setState({
currImg: this.state.currImg - 1
})
}
gotoNext () {
this.setState({
currImg: this.state.currImg + 1
})
}
gotoImg (index) {
this.setState({
currImg: index
})
}
handleClickImg () {
if (this.state.currImg === this.props.imgs.length - 1) return
this.gotoNext()
}
renderGallery () {
const { imgs } = this.props
if (!imgs) return
const gallery = imgs.filter(i => i.useForDemo).map((obj, i) => {
return (
<a
href={obj.src}
className={css(classes.thumbnail, classes[obj.orientation])}
key={i}
onClick={(e) => this.openImgsViewer(i, e)}
>
<img src={obj.thumbnail} className={css(classes.source)} />
</a>
)
})
return (
<div className={css(classes.gallery)}>
{gallery}
</div>
)
}
render () {
return (
<div>
{this.props.heading && <h2>{this.props.heading}</h2>}
{this.props.subheading && <p>{this.props.subheading}</p>}
{this.renderGallery()}
<ImgsViewer
backdropCloseable
currImg={this.state.currImg}
imgs={this.props.imgs}
isOpen={this.state.isOpen}
onClickImg={this.handleClickImg}
onClickNext={this.gotoNext}
onClickPrev={this.gotoPrev}
onClickThumbnail={this.gotoImg}
onClose={this.closeImgsViewer}
preventScroll={this.props.preventScroll}
showThumbnails={this.props.showThumbnails}
spinner={this.props.spinner}
spinnerColor={this.props.spinnerColor}
spinnerSize={this.props.spinnerSize}
theme={this.props.theme}
/>
</div>
)
}
}
Gallery.displayName = 'Gallery'
Gallery.propTypes = {
preventScroll: PropTypes.bool,
spinner: PropTypes.func,
spinnerColor: PropTypes.string,
spinnerSize: PropTypes.number,
theme: PropTypes.object,
heading: PropTypes.string,
imgs: PropTypes.array,
showThumbnails: PropTypes.bool,
subheading: PropTypes.string,
}
const gutter = {
small: 2,
large: 4,
}
const classes = StyleSheet.create({
gallery: {
marginRight: -gutter.small,
overflow: 'hidden',
'@media (min-width: 500px)': {
marginRight: -gutter.large,
}
},
// anchor
thumbnail: {
boxSizing: 'border-box',
display: 'block',
float: 'left',
lineHeight: 0,
paddingRight: gutter.small,
paddingBottom: gutter.small,
overflow: 'hidden',
'@media (min-width: 500px)': {
paddingRight: gutter.large,
paddingBottom: gutter.large,
}
},
// orientation
landscape: {
width: '30%',
},
square: {
paddingBottom: gutter.large,
width: '40%',
'@media (min-width: 500px)': {
paddingBottom: gutter.large,
}
},
// actual <img />
source: {
border: 0,
display: 'block',
height: 'auto',
maxWidth: '100%',
width: 'auto'
},
})
export default Gallery
================================================
FILE: examples/src/components/Spinner.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
const Spinner = (props) => {
const classes = StyleSheet.create(styles(props))
return (
<div className={css(classes.spinner)}>
<div className={css(classes.square)} />
</div>
)
}
Spinner.propTypes = {
color: PropTypes.string,
size: PropTypes.number,
}
const squareKeyframes = {
'0%': {
top: 0,
left: '25%',
opacity: 1,
},
'25%': {
top: '50%',
left: '50%',
opacity: .75,
},
'75%': {
top: '50%',
left: 0,
opacity: .5,
},
'100%': {
top: 0,
left: '25%',
opacity: 1,
}
}
const styles = ({ color, size }) => ({
spinner: {
display: 'inline-block',
position: 'relative',
width: size,
height: size,
},
square: {
position: 'absolute',
width: size / 10,
height: size / 10,
border: `4px solid ${color}`,
borderRadius: '50%',
background: color,
animationName: squareKeyframes,
animationDuration: '2s',
animationTimingFunction: 'linear',
animationIterationCount: 'infinite',
}
})
export default Spinner
================================================
FILE: examples/src/example.less
================================================
// Cmomon Example Styles
// Constans
// example site
@gutter: 20px;
@table-cell-gutter: 10px;
@left-col-width: 180px;
@heading-color: #000;
@kbd-bg-color: #fafafa;
@kbd-border-color: #ccc;
@link-color: #00d7ff;
@text-color: #333;
@nav-gutter: 30px;
@nav-item-padding: 5px;
@nav-padding: @nav-gutter - @nav-item-padding;
// Base
body {
background-color: '#fff';
color: @text-color;
font-family: 'Misrosoft Yahei', Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
}
a {
color: @link-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
hr {
border: none;
border-top: 1px solid rgba(0, 0, 0, .1);
margin-top: 2em;
margin-bottom: 2em;
}
.container {
margin-right: auto;
margin-left: auto;
max-width: 940px;
padding: 0 @gutter;
}
// Columns
.left-col {
display: none;
float: left;
position: fixed;
width: @left-col-width;
z-index: 1;
}
// Navigation
.page-nav {
list-style: none;
margin: 0;
padding: 0 @nav-padding 0 0;
text-align: right;
}
.page-nav__item {
}
.page-nav__link {
display: block;
padding: @nav-item-padding;
}
// Headings
h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
color: @heading-color;
font-weight: 500;
line-height: 1;
margin-bottom: 0.66em;
margin-top: 0;
}
h1, .h1 {
font-size: 3em;
}
h2, .h2 {
font-size: 2em;
font-weight: 300;
padding-top: @nav-gutter;
}
h3, .h3 {
color: #999;
font-size: 1em;
font-weight: bold;
margin-top: 2em;
text-transform: uppercase;
}
h4, .h4 {
font-size: 1em;
}
h5, .h5 {
font-size: .85em;
}
h6, .h6 {
font-size: .75em;
}
// Layout
// ------------------------------
// header
.page-header {
padding: @gutter 0;
p {
margin: 0;
}
a {
border-bottom: 1px solid fade(white, 30%);
text-decoration: none;
&:hover,
&:focus {
border-bottom-color: white;
outline: none;
text-decoration: none;
}
}
}
.page-header__title {
font-style: 3em;
}
.page-header__subtitle {
color: #777777;
font-size: 1.125em;
font-weight: 300;
}
// subheader
.page-subheader {
background-color: mix(@link-color, white, 8%);
border-radius: 4px;
padding: @gutter;
}
.page-subheader__button {
float: right;
}
.page-subheader__link {
border-bottom: 1px solid fade(@link-color, 30%);
outline: none;
text-decoration: none;
&:hover,
&:focus {
border-bottom-color: @link-color;
outline: none;
text-decoration: none;
}
}
// content
.page-content {
padding-bottom: @nav-gutter;
}
// footer
.page-footer {
background-color: #fafafa;
color: #999;
padding: @nav-gutter 0;
text-align: center;
}
// options table
.options-table {
border-collapse: collapse;
border-spacing: 0;
margin-left: -@table-cell-gutter;
margin-right: -@table-cell-gutter;
min-width: 100%;
width: 100%;
td, th {
padding: @table-cell-gutter;
}
th {
border-bottom: 1px solid rgba(0, 0, 0, .05);
color: #999999;
font-weight: normal;
}
td {
border-top: 1px solid rgba(0, 0, 0, .05);
}
}
// Miscellaneous
// --------------------
.section {
margin-bottom: 40px;
}
.hint {
font-size: .85em;
margin: 15px 0;
color: #666;
}
kbd {
background-color: @kbd-bg-color;
border-radius: 3px;
border: 1px solid @kbd-border-color;
border-bottom-color: darken(@kbd-border-color, 4%);
border-top-color: lighten(@kbd-border-color, 4%);
box-shadow: 0 1px 1px rgba(0, 0, 0, .12), 0 2px 0 0 rgba(255, 255, 255, .7) inset;
color: #666666;
display: inline-block;
font-family: 'Microsoft Yahei';
font-size: .85em;
font-weight: 500;
line-height: inherit;
padding: 1px 4px;
white-space: nowrap;
// little hack to tweak 'visual-middle' alignment
position: relative;
top: -1px;
}
pre {
background-color: #eeeeee;
border-radius: 4px;
margin-bottom: @nav-gutter;
}
// Responsive
// --------------------
// largee phones and up
@media (min-width: 481px) {
.page-header,
.page-nav {
padding-bottom: @nav-gutter;
padding-top: @nav-gutter;
}
.page-header__title {
font-size: 4em;
}
.page-subheader {
font-size: 1.125em;
line-height: 28px;
}
}
// large phones and down
@media (max-width: 768px) {
.options-table-container {
overflow-x: scroll;
overflow-y: hidden;
width: 100%;
-ms-overflow-style: -ms-autohiding-scrollbar;
-webkit-overflow-scrolling: touch;
}
.options-table {
table-layout: auto;
td {
white-space: nowrap;
}
}
}
// tablets and up
@media (min-width: 769px) {
.left-col {
display: block;
}
.right-col {
padding-left: @left-col-width;
}
.page-content {
padding-left: @nav-gutter;
position: relative;
&::before {
background: linear-gradient(90deg, fade(#e9e9e9, 0%) 94%, #e9e9e9), linear-gradient(90deg, fade(#f6f6f6, 0%) 50%, #f6f6f6);
border-right: 1px solid #e6e6e6;
content: " ";
margin-left: -(@left-col-width + @nav-gutter);
height: 100%;
position: fixed;
width: @left-col-width;
}
}
.page-header__subtitle {
font-size: 2em;
}
.page-footer {
border: none;
bottom: 0;
float: left;
margin-left: -@nav-gutter;
position: fixed;
text-align: right;
width: @left-col-width;
z-index: 1;
}
.page-footer__copyright--small {
display: none;
}
}
================================================
FILE: examples/src/index.html
================================================
<!doctype html>
<head>
<meta charset="UTF-8">
<title>React-Images-Viewer</title>
<meta name="viewport" content="width=device-width, user-scalable=no, maximum-initial-scale=1.0, initial-scale=1.0">
<meta name="keywords" content="react,reactjs,react component,imgsViewer,react images,react-images,react images viewer,react photos,react photos list,react-images-viewer,ui,javascript">
<meta name="description" content="A react library that view photos list easily, and a simple, responsive imgsViewer component for display an array of images">
<meta name="og:locale" content="zh-cn">
<meta name="og:title" content="React-Images-Viewer">
<meta name="og:description" content="A react library that view photos list easily, and a simple, responsive imgsViewer component for display an array of images">
<meta name="og:url" content="https://guonanci.github.io/react-images-viewer">
<meta name="og:site_name" content="React-Images-Viewer">
<meta name="og:type" content="article">
<link rel="stylesheet" href="example.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<div class="page-wrapper">
<div class="container">
<nav class="left-col">
<ul class="page-nav">
<li class="page-nav__item">
<a href="#example" class="page-nav__link">Examples</a>
</li>
<li class="page-nav__item">
<a href="#getting-started" class="page-nav__link">Getting started</a>
</li>
<li class="page-nav__item">
<a href="#options" class="page-nav__link">Options</a>
</li>
<li class="page-nav__item">
<a href="#license" class="page-nav__link">License</a>
</li>
<li class="page-nav__item">
<a href="#help" class="page-nav__link">Help</a>
</li>
<li class="page-nav__item">
<a href="./index_CN.html" class="page-nav__link">中文</a>
</li>
</ul>
</nav>
<div class="right-col">
<div class="page-content">
<header class="page-header">
<h1 class="page-header__title">React Images Viewer</h1>
<p class="page-header__subtitle">A simple, responsive imgsViewer component for <a href="http://facebook.github.io/react/" target="_blank">ReactJS</a> to display an array of images.</p>
</header>
<div class="page-subheader">
<a href="https://github.com/guonanci/react-images-viewer" class="page-subheader__link" target="_blank">Code and Docs on GitHub</a>
<span class="page-subheader__button">
<a href="https://github.com/guonanci/react-images-viewer" id="github-stars-button" class="github-button" data-style="" data-count-href="/guonanci/react-images-viewer/stargazers" data-count-api="/repos/">Star</a>
</span>
</div>
<div class="page-body">
<section id="examples" class="section-examples">
<h2>Examples</h2>
<div id="example"></div>
</section>
<section id="getting-started" class="section-getting-started">
<h2>Getting Started</h2>
<p>1. Install using yarn:</p>
<pre>yarn add react-images-viewer</pre>
<p>… or npm:</p>
<pre>npm install react-images-viewer --save</pre>
<p>2. Example usage with JSX:</p>
<pre>
<code>
<ImgsViewer
imgs={[
{ src: '../images/photo-1.jpg' },
{ src: '../images/photo-2.jpg' }
]}
isOpen={this.state.isOpen}
onClickPrev={this.gotoPrevImg}
onClickNext={this.gotoNextImg}
onClose={this.closeImgsViewer}
/>
</code>
</pre>
</section>
<section id="options" class="section-options">
<h2>Options</h2>
<div class="options-table-container">
<table class="options-table">
<thead>
<tr>
<th align="left">Property</th>
<th align="left">Type</th>
<th align="left">Default</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">backdropCloseable</td>
<td align="left">bool</td>
<td align="left">false</td>
<td align="left">Allow users to exit the imgsViewer by clicking the backdrop</td>
</tr>
<tr>
<td align="left">currImg</td>
<td align="left">number</td>
<td align="left">0</td>
<td align="left">The index of the image to display initially</td>
</tr>
<tr>
<td align="left">customControls</td>
<td align="left">array</td>
<td align="left">undefined</td>
<td align="left">An array of elements to display as custom controls on the top of imgsViewer</td>
</tr>
<tr>
<td align="left">enableKeyboardInput</td>
<td align="left">bool</td>
<td align="left">true</td>
<td align="left">Supports keyboard input -
<code>space, esc</code>,
<code>arrow left, arrow up</code>, and
<code>arrow right, arrow down</code>
</td>
</tr>
<tr>
<td align="left">
<a href="#images">imgs</a>
</td>
<td align="left">array</td>
<td align="left">undefined</td>
<td align="left">Required. An array of objects containing valid src and srcset values of img element</td>
</tr>
<tr>
<td align="left">imgCountSeparator</td>
<td align="left">string</td>
<td align="left">' / '</td>
<td align="left">Custom separator for the image count</td>
</tr>
<tr>
<td align="left">isOpen</td>
<td align="left">bool</td>
<td align="left">false</td>
<td align="left">Whether or not the imgsViewer is displayed</td>
</tr>
<tr>
<td align="left">onClickPrev</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">Fired on request of the previous image</td>
</tr>
<tr>
<td align="left">onClickNext</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">Fired on request of the next image</td>
</tr>
<tr>
<td align="left">onClickImg</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">Handle click event on the current image</td>
</tr>
<tr>
<td align="left">onClickThumbnail</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">Handle click on thumbnail</td>
</tr>
<tr>
<td align="left">onClose</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">Required. Handle closing of the imgsViewer</td>
</tr>
<tr>
<td align="left">preloadNextImg</td>
<td align="left">bool</td>
<td align="left">true</td>
<td align="left">Whether to preload the next available image.</td>
</tr>
<tr>
<td align="left">preventAutoScroll</td>
<td align="left">bool</td>
<td align="left">true</td>
<td align="left">Determines whether auto-scrolling is prevented</td>
</tr>
<tr>
<td align="left">showCloseBtn</td>
<td align="left">bool</td>
<td align="left">true</td>
<td align="left">Optionally display a close "X" button in top right corner</td>
</tr>
<tr>
<td align="left">showImgCount</td>
<td align="left">bool</td>
<td align="left">true</td>
<td align="left">Optionally display image index, e.g., "3 of 20"</td>
</tr>
<tr>
<td align="left">showThumbnails</td>
<td align="left">bool</td>
<td align="left">false</td>
<td align="left">Optionally display thumbnails beneath the imgsViewer</td>
</tr>
<tr>
<td align="left">theme</td>
<td align="left">object</td>
<td align="left">undefined</td>
<td align="left">Pass through styles to theme each component;
<code>arrow</code>,
<code>container</code>, etc.</td>
</tr>
<tr>
<td align="left">width</td>
<td align="left">number</td>
<td align="left">1024</td>
<td align="left">Maximum width of the carousel; defaults to 1024px</td>
</tr>
</tbody>
</table>
</div>
<h2>Images</h2>
<div class="options-table-container" id="images">
<table class="options-table">
<thead>
<tr>
<th align="left">Property</th>
<th align="left">Type</th>
<th align="left">Default</th>
<th align="left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">src</td>
<td align="left">str</td>
<td align="left">undefined</td>
<td align="left">Required. The primary image path</td>
</tr>
<tr>
<td align="left">srcset</td>
<td align="left">arr</td>
<td align="left">undefined</td>
<td align="left">List of alternative image sizes</td>
</tr>
<tr>
<td align="left">caption</td>
<td align="left">str</td>
<td align="left">undefined</td>
<td align="left">Displayed benath the current image. Great for description or attribution</td>
</tr>
<tr>
<td align="left">thumbnail</td>
<td align="left">str</td>
<td align="left">undefined</td>
<td align="left">Thumbnail to display if
<code>showThumbnails</code> is set to
<code>true</code>
</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="license" class="section-license">
<h2>License</h2>
<p>React Images Viewer is free to use for personal and commercial projects under
<a target="_blank" href="https://github.com/guonanci/react-images-viewer/blob/master/LICENSE">the MIT license</a>.</p>
<p>Attribution is not required, but greatly appreciated. It does not have to be user-facing and can remain within the code.</p>
</section>
<section id="help" class="section-help">
<h2>Help</h2>
<h3>Have a question?</h3>
<p>Follow the
<a target="_blank" href="https://github.com/guonanci/react-images-viewer#quick-start">quick start guide</a> on GitHub to get up and running quickly. Please do not use Github Issues to report personal support
requests.</p>
<h3>Found a bug?</h3>
<p>If you find a bug, please read the
<a target="_blank" href="https://github.com/guonanci/react-images-viewer/blob/master/.github/CONTRIBUTING.md">Contribution Guildelines</a> before you
<a target="_blank" href="https://github.com/guonanci/react-images-viewer/issues">report the issue</a>.</p>
</section>
</div>
</div>
</div>
<footer class="page-footer">
<span class="page-footer__copyright--small">Copyright </span>
<span class="page-footer__copyright--large">© </span>
<a href="https://www.jianshu.com/u/1a404f3eb7d9" target="_blank">Guo Nanci</a> 2018
</footer>
</div>
</div>
<script>
document.getElementById('github-stars-button').dataset.style = window.innerWidth > 480 ? 'mega' : null;
</script>
<script src="common.js"></script>
<script src="app.js"></script>
<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script>
</body>
================================================
FILE: examples/src/index_CN.html
================================================
<!doctype html>
<head>
<meta charset="UTF-8">
<title>React-Images-Viewer</title>
<meta name="viewport" content="width=device-width, user-scalable=no, maximum-initial-scale=1.0, initial-scale=1.0">
<meta name="keywords" content="react,reactjs,react component,imgsViewer,react images,react-images,react images viewer,react photos,react photos list,react-images-viewer,ui,javascript">
<meta name="description" content="A react library that view photos list easily, and a simple, responsive imgsViewer component for display an array of images">
<meta name="og:locale" content="zh-cn">
<meta name="og:title" content="React-Images-Viewer">
<meta name="og:description" content="A react library that view photos list easily, and a simple, responsive imgsViewer component for display an array of images">
<meta name="og:url" content="https://guonanci.github.io/react-images-viewer">
<meta name="og:site_name" content="React-Images-Viewer">
<meta name="og:type" content="article">
<link rel="stylesheet" href="example.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<div class="page-wrapper">
<div class="container">
<nav class="left-col">
<ul class="page-nav">
<li class="page-nav__item">
<a href="#example" class="page-nav__link">例子</a>
</li>
<li class="page-nav__item">
<a href="#getting-started" class="page-nav__link">入门</a>
</li>
<li class="page-nav__item">
<a href="#options" class="page-nav__link">选项</a>
</li>
<li class="page-nav__item">
<a href="#license" class="page-nav__link">许可证</a>
</li>
<li class="page-nav__item">
<a href="#help" class="page-nav__link">帮助</a>
</li>
<li class="page-nav__item">
<a href="./index.html" class="page-nav__link">English</a>
</li>
</ul>
</nav>
<div class="right-col">
<div class="page-content">
<header class="page-header">
<h1 class="page-header__title">React Images Viewer</h1>
<p class="page-header__subtitle">一个简单易用,响应式,放大并查看一组图片的 React 库。</p>
</header>
<div class="page-subheader">
<a href="https://github.com/guonanci/react-images-viewer" class="page-subheader__link" target="_blank">Github上的源码,文档</a>
<span class="page-subheader__button">
<a href="https://github.com/guonanci/react-images-viewer" id="github-stars-button" class="github-button" data-style="" data-count-href="/guonanci/react-images-viewer/stargazers"
data-count-api="/repos/"></a>
</span>
</div>
<div class="page-body">
<section id="examples" class="section-examples">
<h2>例子</h2>
<div id="example-CN"></div>
</section>
<section id="getting-started" class="section-getting-started">
<h2>入门</h2>
<p>1. 依靠 yarn 来安装</p>
<pre>yarn add react-images</pre>
<p>… 或者 npm:</p>
<pre>npm install react-images --save</pre>
<p>2. JSX 使用案例:</p>
<pre>
<code>
<Viewer
imgs={[
{ src: '../images/photo-1.jpg' },
{ src: '../images/photo-2.jpg' }
]}
isOpen={this.state.isOpen}
onClickPrev={this.gotoPrevImg}
onClickNext={this.gotoNextImg}
onClose={this.closeImgsViewer}
/>
</code>
</pre>
</section>
<section id="options" class="section-options">
<h2>选项</h2>
<div class="options-table-container">
<table class="options-table">
<thead>
<tr>
<th align="left">属性名</th>
<th align="left">类型</th>
<th align="left">默认值</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">backdropCloseable</td>
<td align="left">布尔值</td>
<td align="left">false</td>
<td align="left">是否可以通过点击幕布退出查看</td>
</tr>
<tr>
<td align="left">currImg</td>
<td align="left">数值型</td>
<td align="left">0</td>
<td align="left">当前图片初始化索引</td>
</tr>
<tr>
<td align="left">customControls</td>
<td align="left">数组</td>
<td align="left">undefined</td>
<td align="left">照片查看器顶部控件的元素数组</td>
</tr>
<tr>
<td align="left">enableKeyboardInput</td>
<td align="left">布尔值</td>
<td align="left">true</td>
<td align="left">支持键盘输入 -
<code>空格键,Esc</code>,
<code>左箭头,上箭头</code>, and
<code>右箭头,下箭头</code>
</td>
</tr>
<tr>
<td align="left">
<a href="#images">imgs</a>
</td>
<td align="left">数组</td>
<td align="left">undefined</td>
<td align="left">必须项。一个包含具备img 元素src,srcset 合法值的对象数组。</td>
</tr>
<tr>
<td align="left">imgCountSeparator</td>
<td align="left">string</td>
<td align="left">' / '</td>
<td align="left">图片计数器分隔符</td>
</tr>
<tr>
<td align="left">isOpen</td>
<td align="left">布尔值</td>
<td align="left">false</td>
<td align="left">图片查看器是否非法</td>
</tr>
<tr>
<td align="left">onClickPrev</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">请求上一张图片时触发</td>
</tr>
<tr>
<td align="left">onClickNext</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">请求下一张图片时触发</td>
</tr>
<tr>
<td align="left">onClickImg</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">当前图片的点击事件处理器</td>
</tr>
<tr>
<td align="left">onClickThumbnail</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">缩略图的点击事件处理器</td>
</tr>
<tr>
<td align="left">onClose</td>
<td align="left">func</td>
<td align="left">undefined</td>
<td align="left">必须项,照片查看器的关闭事件处理器</td>
</tr>
<tr>
<td align="left">preloadNextImg</td>
<td align="left">布尔值</td>
<td align="left">true</td>
<td align="left">是否预加载下一张图片</td>
</tr>
<tr>
<td align="left">preventScroll</td>
<td align="left">布尔值</td>
<td align="left">true</td>
<td align="left">自动滚动是否阻止</td>
</tr>
<tr>
<td align="left">showCloseBtn</td>
<td align="left">布尔值</td>
<td align="left">true</td>
<td align="left">是否在右上角显示 X 关闭按钮</td>
</tr>
<tr>
<td align="left">showImgCount</td>
<td align="left">布尔值</td>
<td align="left">true</td>
<td align="left">是否显示图片索引,比如“3 / 20”</td>
</tr>
<tr>
<td align="left">showThumbnails</td>
<td align="left">布尔值</td>
<td align="left">false</td>
<td align="left">是否在查看器下方显示缩略图</td>
</tr>
<tr>
<td align="left">theme</td>
<td align="left">对象</td>
<td align="left">undefined</td>
<td align="left">传递给每个组件的主题化样式;
<code>arrow键</code>,
<code>container键</code>,等等。</td>
</tr>
<tr>
<td align="left">width</td>
<td align="left">数值型</td>
<td align="left">1024</td>
<td align="left">轮播器的最大宽度,默认为1024px</td>
</tr>
</tbody>
</table>
</div>
<h2>Imgs</h2>
<div class="options-table-container" id="images">
<table class="options-table">
<thead>
<tr>
<th align="left">属性名</th>
<th align="left">类型</th>
<th align="left">默认值</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">src</td>
<td align="left">字符串</td>
<td align="left">undefined</td>
<td align="left">必须项. 主图片路径</td>
</tr>
<tr>
<td align="left">srcset</td>
<td align="left">arr</td>
<td align="left">undefined</td>
<td align="left">可替代图片尺寸列表</td>
</tr>
<tr>
<td align="left">caption</td>
<td align="left">字符串</td>
<td align="left">undefined</td>
<td align="left">当前图片下显示,对于描述或者归属很有用。</td>
</tr>
<tr>
<td align="left">thumbnail</td>
<td align="left">字符串</td>
<td align="left">undefined</td>
<td align="left">
<code>showThumbnails</code>被设置成
<code>true</code>的话要显示的缩略图
</td>
</tr>
</tbody>
</table>
</div>
</section>
<section id="license" class="section-license">
<h2>许可证</h2>
<p>React Images Viewer在
<a target="_blank" href="https://github.com/guonanci/react-images-viewer/blob/master/LICENSE">MIT证书</a>下,对于个人或者商业用途都是免费的</p>。
<p>它不是必须项,但是十分感谢。没有面向用户而且保留源代码。</p>
</section>
<section id="help" class="section-help">
<h2>帮助</h2>
<h3>需要帮助?</h3>
<p>遵循
<a target="_blank" href="https://github.com/guonanci/react-images-viewer#quick-start">github 上的快速入门指南</a>,并运行起来。 请不要出于个人目的滥用 github issues。
</p>
<h3>发现 bug?</h3>
<p>如果你找到 bug 了,请在你
<a target="_blank" href="https://github.com/guonanci/react-images-viewer/issues">报告issue</a>.</p>
之前阅读<a target="_blank" href="https://github.com/guonanci/react-images-viewer/blob/master/.github/CONTRIBUTING.md">贡献指南</a>一番。
</section>
</div>
</div>
</div>
<footer class="page-footer">
<span class="page-footer__copyright--small">贡献指南</span>
<span class="page-footer__copyright--large">© </span>
<a href="https://www.jianshu.com/u/1a404f3eb7d9" target="_blank">郭南赐</a> 2018
</footer>
</div>
</div>
<script>
document.getElementById('github-stars-button').dataset.style = window.innerWidth > 480 ? 'mega' : null;
</script>
<script src="common.js"></script>
<script src="app_CN.js"></script>
<script async defer id="github-bjs" src="https://buttons.github.io/buttons.js"></script>
</body>
================================================
FILE: examples/src/standalone.html
================================================
<!doctype html>
<head>
<meta charset="UTF-8">
<title>React-Images-Viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scable=no, maximum-scale=1, width=device-width">
<meta name="keywords" content="react,reactjs,react component,imgsViewer,react images,react-images,react images viewer,react photos,react photos list,react-images-viewer,ui,javascript">
<meta name="description" content="A react library that view photos list easily, and a simple, responsive imgsViewer component for display an array of images">
<meta name="og:locale" content="zh-cn">
<meta property="og:title" content="React-Images">
<meta property="og:description" content="A simple, responsive Lightbox component for displaying an array of images.">
<meta property="og:url" content="https://guonanci.github.io/react-images-viewer">
<meta property="og:site_name" content="React-Images-Viewer">
<meta property="og:type" content="article">
<link rel="stylesheet" href="example.css">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>
<body>
<div class="page-wrapper">
<div id="app">
</div>
</div>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js" charset="utf-8"></script>
<script src="https://unpkg.com/react@15.6.1/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.6.1/dist/react-dom.js"></script>
<script src="https://unpkg.com/prop-types@15.5.10/prop-types.js"></script>
<script src="https://unpkg.com/aphrodite/dist/aphrodite.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-transition-group/1.1.3/react-transition-group.js"></script>
<script src="https://unpkg.com/react-photo-gallery@6.0.13/dist/react-photo-gallery.min.js"></script>
<script src="https://rawgit.com/neptunian/react-scrolllock/master/dist/react-scrolllock.min.js"></script>
<script src="https://unpkg.com/react-images-viewer@1.0.0/dist/react-images-viewer.min.js"></script>
<script type="text/babel">
const photos = [
{ src: 'https://images.unsplash.com/photo-1526784725085-c69e947bf92e?w=1024&h=1024', width:1, height:1 },
{ src: 'https://images.unsplash.com/photo-1522985225914-17a10a58c8ec?w=1024&h=1024', width:1, height:1 },
{ src: 'https://images.unsplash.com/photo-1522931698295-e7b4d3e4188f?w=1024&h=1024', width:1, height:1 },
{ src: 'https://images.unsplash.com/photo-1516175663209-ac2459a5652f?w=1024&h=1024', width:1, height:1 },
{ src: 'https://images.unsplash.com/photo-1515911601378-97de98db6dda?w=1024&h=1024', width:1, height:1 },
]
class App extends React.Component {
constructor () {
super()
this.state = {
currImg: 0
}
this.closeImgsViewer = this.closeImgsViewer.bind(this)
this.openViewer = this.openViewer.bind(this)
this.gotoNext = this.gotoNext.bind(this)
this.gotoPrev = this.gotoPrev.bind(this)
}
openViewer (event, obj) {
this.setState({
currImg: obj.index,
viewerIsOpen: true,
})
}
closeImgsViewer () {
this.setState({
currImg: 0,
viewerIsOpen: false,
})
}
gotoPrev () {
this.setState({
currImg: this.state.currImg - 1
})
}
gotoNext () {
this.setState({
currImg: this.state.currImg + 1
})
}
render () {
return (
<div>
<Gallery photos={photos} onClick={this.openViewer} />
<ImgsViewer
imgs={photos}
onClose={this.closeImgsViewer}
onClickPrev={this.gotoPrev}
onClickNext={this.gotoNext}
currImg={this.state.currImg}
isOpen={this.state.viewerIsOpen}
/>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
</script>
</body>
================================================
FILE: index.js
================================================
'use strict';
module.exports = function(number, locale) {
return number.toLocaleString(locale);
};
================================================
FILE: jest.config.js
================================================
module.exports = {
verbose: true,
collectCoverage: true,
collectCoverageFrom: ['src/*.{js, jsx}'],
setupFiles: ['./__test__/rafShim.js', './__test__/jestsetup.js'],
testURL: 'http://localhost/',
// moduleFileExtensions: [
// 'js',
// 'jsx',
// ],
// moduleNameMapper: {
// '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/fileMock.js',
// '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
// },
// transform: {
// '^.+\\.js$': 'babel-jest',
// }
}
================================================
FILE: new-branch.sh
================================================
git checkout -b $1
git push -u origin $1
git branch --all
================================================
FILE: package-scripts.js
================================================
const npsUtils = require('nps-utils')
const path = require('path')
const { series, rimraf, concurrent } = npsUtils
module.exports = {
scripts: {
build: {
description: 'clean dist directory and run all builds',
default: series(
rimraf('dist'),
rimraf('lib'),
concurrent.nps('build.rollup', 'build.babel', 'build.less')
),
rollup: 'rollup --config',
babel: 'babel src -d lib',
less: series(
'lessc examples/src/example.less examples/dist/example.css'
),
},
publish: {
default: series(
rimraf('examples/dist'),
'webpack --progress -p',
// 'git pull',
'git subtree push --prefix examples/dist origin gh-pages'
// 'git push origin `git subtree split --prefix examples/dist gh-pages`:gh-pages --force'
)
}
}
}
================================================
FILE: package.json
================================================
{
"name": "react-images-viewer",
"version": "1.7.1",
"description": "Create an react-images-viewer component.",
"main": "lib/ImgsViewer.js",
"jsnext:main": "dist/react-images-viewer.es.js",
"scripts": {
"build": "nps build",
"publish:examples": "NODE_ENV=production nps publish",
"start": "webpack-dev-server --progress",
"lint": "eslint src/** examples/src/app.js examples/src/components/Gallery.js",
"test": "jest",
"coverage": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/guonanci/react-images-viewer.git"
},
"keywords": [
"react",
"react-component",
"react images",
"react-images",
"react-images-viewer",
"lightbox",
"react lightbox",
"react-lightbox",
"carousel",
"react-carousel",
"react carousel",
"gallery",
"react-gallery",
"react gallery",
"react-photo",
"react-photo-viewer"
],
"author": "yiminanci <yiminanci@gmail.com> (https://juejin.cn/user/3491704660305111)",
"license": "MIT",
"responsitory": {
"type": "git",
"url": "https://github.com/guonanci/react-images-viewer.git"
},
"bugs": {
"url": "https://github.com/guonanci/react-images-viewer/issues"
},
"homepage": "https://github.com/guonanci/react-images-viewer#readme",
"devDependencies": {
"babel": "^6.23.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.5",
"babel-jest": "^23.4.2",
"babel-loader": "^7.1.5",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"coveralls": "^3.0.2",
"css-loader": "^1.0.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.15.6",
"eslint": "^5.1.0",
"eslint-plugin-react": "^7.10.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"jest": "^23.4.2",
"jest-cli": "^23.4.2",
"less": "^3.5.3",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^0.4.1",
"nps": "^5.9.2",
"nps-utils": "^1.6.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-test-renderer": "^16.4.1",
"regenerator-runtime": "^0.12.0",
"rollup": "^0.62.0",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-uglify": "^4.0.0",
"style-loader": "^0.21.0",
"uglify-es": "^3.3.9",
"webpack": "^4.16.2",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5"
},
"peerDependencies": {
"react": "^15.0 || ^16.0 || ^17.0",
"react-dom": "^15.0 || ^16.0 || ^17.0"
},
"dependencies": {
"aphrodite": "^2.2.2",
"prop-types": "^15.6.2",
"react-scrolllock": "^3.0.1",
"react-transition-group": "^2.4.0"
}
}
================================================
FILE: rollup.config.js
================================================
import babel from 'rollup-plugin-babel'
import resolve from 'rollup-plugin-node-resolve'
import { uglify } from 'rollup-plugin-uglify'
import { minify } from 'uglify-es'
const name = 'ImgsViewer'
const path = 'dist/react-images-viewer'
const globals = {
'prop-types': 'PropTypes',
'react-dom': 'ReactDOM',
react: 'React',
aphrodite: 'aphrodite',
'aphrodite/no-important': 'aphrodite',
'react-scrolllock': 'ScrollLock',
'react-transition-group': 'ReactTransitionGroup',
'react-spinners': 'BounceLoader',
}
const external = Object.keys(globals)
const babelOptions = (production) => {
let result = {
babelrc: false,
presets: ['react', ['env', { modules: false }]],
plugins: [['transform-object-rest-spread', { useBuiltIns: true }], 'transform-object-rest-spread', 'external-helpers']
}
if (production) {
result.plugins.push('transform-react-remove-prop-types')
}
return result
}
export default [
{
input: 'src/ImgsViewer.js',
output: {
file: path + '.es.js',
format: 'es',
},
external: external,
plugins: [babel(babelOptions(false))]
},
{
input: 'src/ImgsViewer.js',
output: {
name: name,
file: path + '.js',
format: 'umd',
globals: globals,
},
external: external,
plugins: [babel(babelOptions(false)), resolve()]
},
{
input: 'src/ImgsViewer.js',
output: {
name: name,
file: path + '.min.js',
format: 'umd',
globals: globals,
},
external: external,
plugins: [babel(babelOptions(true)), resolve(), uglify({}, minify)],
}
]
================================================
FILE: src/ImgsViewer.js
================================================
import PropTypes from "prop-types";
import React, { Component, Fragment } from "react";
import { StyleSheet, css } from "aphrodite";
import ScrollLock from "react-scrolllock";
import defaultTheme from "./theme";
import Arrow from "./components/Arrow";
import Container from "./components/Container";
import Footer from "./components/Footer";
import Header from "./components/Header";
import PaginatedThumbnails from "./components/PaginatedThumbnails";
import Portal from "./components/Portal";
import DefaultSpinner from "./components/Spinner";
import { bindFunctions, canUseDom, deepMerge } from "./utils/util";
function normalizeSourceSet(data) {
const sourceSet = data.srcSet || data.srcset;
if (Array.isArray(sourceSet)) {
return sourceSet.join();
}
return sourceSet;
}
const ThemeContext = React.createContext({
theme: defaultTheme,
toggleTheme: (newTheme) => {},
});
class ImgsViewer extends Component {
constructor(props) {
super(props);
this.theme = deepMerge(defaultTheme, this.props.theme);
this.classes = StyleSheet.create(
deepMerge(defaultStyles, this.props.theme)
);
this.toggleTheme = (theme) => {
this.setState(() => ({ theme }));
};
this.state = {
imgLoaded: false,
theme: this.theme,
toggleTheme: this.toggleTheme,
};
bindFunctions.call(this, [
"gotoNext",
"gotoPrev",
"closeBackdrop",
"handleKeyboardInput",
"handleImgLoaded",
]);
}
componentDidMount() {
if (this.props.isOpen) {
if (this.props.enableKeyboardInput) {
window.addEventListener("keydown", this.handleKeyboardInput);
}
if (typeof this.props.currImg === "number") {
this.preloadImg(this.props.currImg, this.handleImgLoaded);
}
}
}
// static getDerivedStateFromProps (nextProps, prevState) {
UNSAFE_componentWillReceiveProps(nextProps) {
if (!canUseDom) return;
// const instance = this
// always to preload imgs with both directions
// then when user changs direction, img also show quickly
if (nextProps.preloadNextImg) {
const nextIdx = nextProps.currImg + 1;
const prevIdx = nextProps.currImg - 1;
// debugger
// if (!this) return null
this.preloadImg(prevIdx);
this.preloadImg(nextIdx);
}
// preload currImg
if (
this.props.currImg !== nextProps.currImg ||
(!this.props.isOpen && nextProps.isOpen)
) {
const img = this.preloadImgData(
nextProps.imgs[nextProps.currImg],
this.handleImgLoaded
);
if (img)
this.setState({
imgLoaded: img.complete,
});
}
// add/remove event listeners
if (
!this.props.isOpen &&
nextProps.isOpen &&
nextProps.enableKeyboardInput
) {
window.addEventListener("keydown", this.handleKeyboardInput);
}
if (!nextProps.isOpen && nextProps.enableKeyboardInput) {
window.removeEventListener("keydown", this.handleKeyboardInput);
}
return null;
}
componentWillUnmount() {
if (this.props.enableKeyboardInput) {
window.removeEventListener("keydown", this.handleKeyboardInput);
}
}
// ====================
// Methods
// ====================
preloadImg(idx, onload) {
return this.preloadImgData(this.props.imgs[idx], onload);
}
preloadImgData(data, onload) {
if (!data) return;
const img = new Image();
const sourceSet = normalizeSourceSet(data);
// Todo: add error handling for missing imgs
img.onerror = onload;
img.onload = onload;
img.src = data.src;
if (sourceSet) img.srcset = sourceSet;
return img;
}
gotoNext(event) {
const { currImg, imgs } = this.props;
const { imgLoaded } = this.state;
if (!imgLoaded || currImg === imgs.length - 1) return;
if (event) {
event.preventDefault();
event.stopPropagation();
}
this.props.onClickNext();
}
gotoPrev(event) {
const { currImg } = this.props;
const { imgLoaded } = this.state;
if (!imgLoaded || currImg === 0) return;
if (event) {
event.preventDefault();
event.stopPropagation();
}
this.props.onClickPrev();
}
closeBackdrop(event) {
if (
event.target.id === "viewerBackdrop" ||
event.target.tagName === "FIGURE"
) {
this.props.onClose();
}
}
handleKeyboardInput(event) {
const { keyCode } = event;
if (keyCode === 37 || keyCode === 33 || keyCode === 38) {
// left, pageup, up
this.gotoPrev(event);
return true;
} else if (keyCode === 39 || keyCode === 34 || keyCode === 40) {
// right, pagedown, down
this.gotoNext(event);
return true;
} else if (keyCode === 27 || keyCode === 32) {
// esc, space
this.props.onClose();
return true;
}
return false;
}
handleImgLoaded() {
this.setState({
imgLoaded: true,
});
}
// ====================
// Renderers
// ====================
renderArrowPrev(theme) {
if (this.props.currImg === 0) return null;
return (
<Arrow
theme={theme}
direction="left"
icon="arrowLeft"
onClick={this.gotoPrev}
title={this.props.leftArrowTitle}
type="button"
/>
);
}
renderArrowNext(theme) {
if (this.props.currImg === this.props.imgs.length - 1) return null;
return (
<Arrow
theme={theme}
direction="right"
icon="arrowRight"
onClick={this.gotoNext}
title={this.props.rightArrowTitle}
type="button"
/>
);
}
renderDialog(newState) {
const { backdropCloseable, isOpen, showThumbnails, width } = this.props;
const { imgLoaded } = this.state;
if (!isOpen) return <span key="closed" />;
const offsetThumbnails = showThumbnails
? this.theme.thumbnail.size + this.theme.container.gutter.vertical
: 0;
return (
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => {
theme = newState.theme;
return (
<Container
theme={theme}
key="open"
onClick={backdropCloseable && this.closeBackdrop}
onTouchEnd={backdropCloseable && this.closeBackdrop}
>
<Fragment>
<div
className={css(this.classes.content)}
style={{
marginBottom: offsetThumbnails,
maxWidth: width,
}}
>
{imgLoaded && this.renderHeader(theme)}{" "}
{this.renderImgs(theme)}
{this.renderSpinner()} {imgLoaded && this.renderFooter(theme)}
</div>
{imgLoaded && this.renderThumbnails(theme)}
{imgLoaded && this.renderArrowPrev(theme)}
{imgLoaded && this.renderArrowNext(theme)}
{this.props.preventScroll && <ScrollLock />}
</Fragment>
</Container>
);
}}
</ThemeContext.Consumer>
);
}
renderImgs(theme) {
const { currImg, imgs, onClickImg, showThumbnails } = this.props;
const { imgLoaded } = this.state;
if (!imgs || !imgs.length) return null;
const img = imgs[currImg];
const sourceSet = normalizeSourceSet(img);
const sizes = sourceSet ? "100vw" : null;
const thumbnailsSize = showThumbnails ? theme.thumbnail.size : 0;
const heightOffset = `${
theme.header.height +
theme.footer.height +
thumbnailsSize +
theme.container.gutter.vertical
}px`;
return (
<figure className={css(this.classes.figure)}>
<img
className={css(this.classes.img, imgLoaded && this.classes.imgLoaded)}
onClick={onClickImg}
sizes={sizes}
alt={img.alt}
src={img.src}
srcSet={sourceSet}
style={{
cursor: onClickImg ? "pointer" : "auto",
maxHeight: `calc(100vh - ${heightOffset}`,
}}
/>
</figure>
);
}
renderThumbnails(theme) {
const {
imgs,
currImg,
leftArrowTitle,
rightArrowTitle,
onClickThumbnail,
showThumbnails,
thumbnailOffset,
} = this.props;
if (!showThumbnails) return null;
return (
<PaginatedThumbnails
theme={theme}
leftTitle={leftArrowTitle}
rightTitle={rightArrowTitle}
currImg={currImg}
imgs={imgs}
offset={thumbnailOffset}
onClickThumbnail={onClickThumbnail}
/>
);
}
renderHeader(theme) {
const { closeBtnTitle, customControls, onClose, showCloseBtn } = this.props;
return (
<Header
theme={theme}
customControls={customControls}
onClose={onClose}
showCloseBtn={showCloseBtn}
closeBtnTitle={closeBtnTitle}
/>
);
}
renderFooter(theme) {
const { currImg, imgs, imgCountSeparator, showImgCount } = this.props;
if (!imgs || !imgs.length) return null;
return (
<Footer
theme={theme}
caption={imgs[currImg].caption}
countCurr={currImg + 1}
countSeparator={imgCountSeparator}
countTotal={imgs.length}
showCount={showImgCount}
/>
);
}
renderSpinner() {
const { spinner, spinnerDisabled, spinnerColor, spinnerSize } = this.props;
const { imgLoaded } = this.state;
const Spinner = spinner;
if (spinnerDisabled) return null;
return (
<div
className={css(
this.classes.spinner,
!imgLoaded && this.classes.spinnerActive
)}
>
<Spinner color={spinnerColor} size={spinnerSize} />
</div>
);
}
render() {
return (
<ThemeContext.Provider value={this.state}>
<Portal> {this.renderDialog(this.state)} </Portal>
</ThemeContext.Provider>
);
}
}
ImgsViewer.propTypes = {
backdropCloseable: PropTypes.bool,
closeBtnTitle: PropTypes.string,
currImg: PropTypes.number,
customControls: PropTypes.arrayOf(PropTypes.node),
enableKeyboardInput: PropTypes.bool,
imgCountSeparator: PropTypes.string,
imgs: PropTypes.arrayOf(
PropTypes.shape({
src: PropTypes.string.isRequired,
srcSet: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
caption: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
thumbnail: PropTypes.string,
})
).isRequired,
isOpen: PropTypes.bool,
leftArrowTitle: PropTypes.string,
onClickImg: PropTypes.func,
onClickNext: PropTypes.func,
onClickPrev: PropTypes.func,
onClickThumbnail: PropTypes.func,
onClose: PropTypes.func.isRequired,
preloadNextImg: PropTypes.bool,
preventScroll: PropTypes.bool,
rightArrowTitle: PropTypes.string,
showCloseBtn: PropTypes.bool,
showImgCount: PropTypes.bool,
showThumbnails: PropTypes.bool,
spinnerDisabled: PropTypes.bool,
spinner: PropTypes.func,
spinnerColor: PropTypes.string,
spinnerSize: PropTypes.number,
theme: PropTypes.object,
thumbnailOffset: PropTypes.number,
width: PropTypes.number,
};
ImgsViewer.defaultProps = {
currImg: 0,
enableKeyboardInput: true,
imgCountSeparator: " / ",
onClickShowNextImg: true,
preloadNextImg: true,
preventScroll: true,
showCloseBtn: true,
showImgCount: true,
spinnerDisabled: false,
spinner: DefaultSpinner,
spinnerColor: "#fff",
spinnerSize: 50,
theme: {},
thumbnailOffset: 2,
width: 1024,
};
const defaultStyles = {
content: {
position: "relative",
},
figure: {
margin: 0, // remove browser default
},
img: {
display: "block", // removes browser default gutter
height: "auto",
margin: "0 auto", // main center on very short screens or very narrow img
maxWidth: "100%",
// disable user select
WebkitTouchCallout: "none",
userSelect: "none",
// opacity animation on image load
opacity: 0,
transition: "opacity .3s",
},
imgLoaded: {
opacity: 1,
},
spinner: {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
// opacity animation to make spinner appear with delay
opacity: 0,
transition: "opacity .3s",
pointerEvents: "none",
},
spinnerActive: {
opacity: 1,
},
};
export default ImgsViewer;
================================================
FILE: src/components/Arrow.js
================================================
import PropTypes from "prop-types";
import React from "react";
import { css, StyleSheet } from "aphrodite/no-important";
import defaults from "../theme";
import { deepMerge } from "../utils/util";
import Icon from "./Icon";
function Arrow({ direction, icon, onClick, size, theme, ...props }) {
const classes = StyleSheet.create(deepMerge(defaultStyles, theme));
return (
<button
type="button" // default: submit
className={css(
classes.arrow,
classes["arrow__direction__" + direction],
size && classes["arrow__size__" + size]
)}
onClick={onClick}
onTouchEnd={onClick}
{...props}
>
<Icon
fill={(!!theme.arrow && theme.arrow.fill) || defaults.arrow.fill}
type={icon}
/>
</button>
);
}
Arrow.propTypes = {
theme: PropTypes.object,
direction: PropTypes.oneOf(["left", "right"]),
icon: PropTypes.string,
onClick: PropTypes.func.isRequired,
size: PropTypes.oneOf(["medium", "small"]).isRequired,
};
Arrow.defaultProps = {
size: "medium",
};
const defaultStyles = {
arrow: {
background: "none",
border: "none",
borderRadius: 4,
cursor: "pointer",
outline: "none",
padding: 10, // increase hit area
position: "absolute",
top: "50%",
// disable user select
WebkitTouchCallout: "none",
userSelect: "none",
},
// sizes
arrow__size__medium: {
height: defaults.arrow.height,
marginTop: defaults.arrow.height / -2,
width: 40,
"@media (min-width: 768px)": {
width: 70,
},
},
arrow__size__small: {
height: defaults.thumbnail.size,
marginTop: defaults.thumbnail.size / -2,
width: 30,
"@media (min-width: 500px)": {
width: 40,
},
},
// direciton
arrow__direction__right: {
right: defaults.container.gutter.horizontal,
},
arrow__direction__left: {
left: defaults.container.gutter.horizontal,
},
};
export default Arrow;
================================================
FILE: src/components/Container.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
import defaults from '../theme'
import { deepMerge } from '../utils/util'
function Container (props) {
const classes = StyleSheet.create(deepMerge(defaultStyles, props.theme))
return (
<div
id="viewerBackdrop"
className={css(classes.container)}
{...props}
/>
)
}
Container.propTypes = {
theme: PropTypes.object
}
const defaultStyles = {
container: {
alignItems: 'center',
backdropColor: defaults.container.background,
boxSizing: 'border-box',
display: 'flex',
height: '100%',
justifyContent: 'center',
left: 0,
paddingTop: defaults.container.gutter.vertical,
paddingRight: defaults.container.gutter.horizontal,
paddingBottom: defaults.container.gutter.vertical,
paddingLeft: defaults.container.gutter.horizontal,
position: 'fixed',
top: 0,
width: '100%',
zIndex: defaults.container.zIndex,
}
}
export default Container
================================================
FILE: src/components/Footer.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
import defaults from '../theme'
import { deepMerge } from '../utils/util'
function Footer ({ caption, countCurr, countSeparator, countTotal, showCount, theme, ...props }) {
if (!caption && !showCount) return null
const classes = StyleSheet.create(deepMerge(defaultStyles, theme))
const imgCount = showCount ? (
<div className={css(classes.footerCount)}>
{countCurr}
{countSeparator}
{countTotal}
</div>
) : <span />
return (
<div className={css(classes.footer)} {...props}>
{caption ? (
<figcaption className={css(classes.footerCaption)}>
{caption}
</figcaption>
) : <span />}
{imgCount}
</div>
)
}
Footer.propTypes ={
theme: PropTypes.object,
caption: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
countCurr: PropTypes.number,
countSeparator: PropTypes.string,
countTotal: PropTypes.number,
showCount: PropTypes.bool,
}
const defaultStyles = {
footer: {
boxSizing: 'border-box',
color: defaults.footer.color,
cursor: 'auto',
display: 'flex',
justifyContent: 'space-between',
left: 0,
lineHeight: 1.3,
paddingTop: defaults.footer.gutter.vertical,
paddingRight: defaults.footer.gutter.horizontal,
paddingBottom: defaults.footer.gutter.vertical,
paddingLeft: defaults.footer.gutter.horizontal,
},
footerCount: {
color: defaults.footer.count.color,
fontSize: defaults.footer.count.fontSize,
paddingLeft: '1em', // add a small gutter for the caption
},
footerCaption: {
flex: '1 1 0',
}
}
export default Footer
================================================
FILE: src/components/Header.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
import defaults from '../theme'
import { deepMerge } from '../utils/util'
import Icon from './Icon'
function Header ({ customControls, onClose, showCloseBtn, closeBtnTitle, theme, ...props }) {
const classes = StyleSheet.create(deepMerge(defaultStyles, theme))
return (
<div className={css(classes.header)} {...props}>
{customControls ? customControls : <span />}
{!!showCloseBtn && (
<button
title={closeBtnTitle}
className={css(classes.close)}
onClick={onClose}
>
<Icon fill={!!theme.close && theme.close.fill || defaults.close.fill} type="close" />
</button>
)}
</div>
)
}
Header.propTypes = {
theme: PropTypes.object,
customControls: PropTypes.array,
onClose: PropTypes.func.isRequired,
showCloseBtn: PropTypes.bool,
closeBtnTitle: PropTypes.string,
}
const defaultStyles = {
header: {
display: 'flex',
justifyContent: 'space-between',
height: defaults.header.height,
},
close: {
background: 'none',
border: 'none',
cursor: 'pointer',
outline: 'none',
position: 'relative',
top: 0,
verticalAlign: 'bottom',
zIndex: 1,
// increase hit area
height: 40,
marginRight: -10,
padding: 10,
width: 40,
}
}
export default Header
================================================
FILE: src/components/Icon.js
================================================
import PropTypes from "prop-types";
import React from "react";
import arrowLeft from "../icons/arrow_left";
import arrowRight from "../icons/arrow_right";
import close from "../icons/close";
const icons = { arrowLeft, arrowRight, close };
const Icon = ({ fill, type, ...props }) => {
const icon = icons[type];
return <span dangerouslySetInnerHTML={{ __html: icon(fill) }} {...props} />;
};
Icon.propTypes = {
fill: PropTypes.string,
type: PropTypes.oneOf(Object.keys(icons)),
};
Icon.defaultProps = {
fill: "#fff",
};
export default Icon;
================================================
FILE: src/components/PaginatedThumbnails.js
================================================
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
import Thumbnail from './Thumbnail'
import Arrow from './Arrow'
import theme from '../theme'
const classes = StyleSheet.create({
paginatedThumbnails: {
bottom: theme.container.gutter.vertical,
height: theme.thumbnail.size,
padding: '0 50px',
position: 'absolute',
textAlign: 'center',
whiteSpace: 'nowrap',
left: '50%',
transform: 'translateX(-50%)',
}
})
const arrowStyles = {
height: theme.thumbnail.size + (theme.thumbnail.gutter * 2),
width: 40,
}
export default class PaginatedThumbnails extends Component {
constructor (props) {
super(props)
this.state = {
hasCustomPage: false,
}
this.gotoPrev = this.gotoPrev.bind(this)
this.gotoNext = this.gotoNext.bind(this)
}
UNSAFE_componentWillReceiveProps (nextProps) {
if (nextProps.currImg !== this.props.currImg) {
this.setState({
hasCustomPage: false,
})
}
}
// ====================
// Methods
// ====================
getFirst () {
const { currImg, offset } = this.props
if(this.state.hasCustomPage) {
return this.clampFirst(this.state.first)
}
return this.clampFirst(currImg - offset)
}
setFirst (event, newFirst) {
const { first } = this.state
if (event) {
event.preventDefault()
event.stopPropagation()
}
if (first === newFirst) return
this.setState({
hasCustomPage: true,
first: newFirst
})
}
gotoPrev(event) {
this.setFirst(event, this.getFirst() - this.props.offset)
}
gotoNext(event) {
this.setFirst(event, this.getFirst() + this.props.offset)
}
clampFirst (value) {
const { imgs, offset } = this.props
const totalCount = 2 * offset + 1 // show $offset extra thumbnails on each side
if (value < 0) {
return 0
} else if (value + totalCount > imgs.length) { // Too far
return imgs.length - totalCount
} else {
return value
}
}
// ====================
// Renderers
// ====================
renderArrowPrev (theme) {
const { leftTitle } = this.props
if (this.getFirst() <= 0) return null
return (
<Arrow
theme={theme}
direction="left"
size="small"
icon="arrowLeft"
onClick={this.gotoPrev}
style={arrowStyles}
title={leftTitle}
type="button"
/>
)
}
renderArrowNext (theme) {
const { offset, imgs, rightTitle } = this.props
const totalCount = 2 * offset + 1
if (this.getFirst() + totalCount >= imgs.length) return null
return (
<Arrow
theme={theme}
direction="right"
size="small"
icon="arrowRight"
onClick={this.gotoNext}
style={arrowStyles}
title={rightTitle}
type="button"
/>
)
}
render () {
const { imgs, currImg, onClickThumbnail, offset, theme } = this.props
const totalCount = 2 * offset + 1 // show $offset extra thumbnails on each side
let thumbnails = []
let baseOffset = 0
if (imgs.length <= totalCount) {
thumbnails = imgs
} else { // Try to center current image in list
baseOffset = this.getFirst()
thumbnails = imgs.slice(baseOffset, baseOffset + totalCount)
}
return (
<div className={css(classes.paginatedThumbnails)}>
{this.renderArrowPrev(theme)}
{thumbnails.map((img, idx) => (
<Thumbnail
theme={theme}
key={baseOffset + idx}
{...img}
index={baseOffset + idx}
onClick={onClickThumbnail}
active={baseOffset + idx === currImg}
/>
))}
{this.renderArrowNext(theme)}
</div>
)
}
}
PaginatedThumbnails.propTypes = {
theme: PropTypes.object,
leftTitle: PropTypes.string,
rightTitle: PropTypes.string,
currImg: PropTypes.number,
imgs: PropTypes.array,
offset: PropTypes.number,
onClickThumbnail: PropTypes.func.isRequired,
}
================================================
FILE: src/components/Portal.js
================================================
import React, { Component } from 'react'
import PropsTypes from 'prop-types'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import { render, unmountComponentAtNode } from 'react-dom'
export default class Portal extends Component {
constructor() {
super()
this.portalElement = null
}
componentDidMount() {
const div = document.createElement('div')
document.body.appendChild(div)
this.portalElement = div
this.componentDidUpdate()
}
componentDidUpdate() {
// Animate fade on mount/unmount
const duration = 200
const styles = `
.fade-enter { opacity: 0.01; }
.fade-enter.fade-enter-active { opacity: 1; transition: opacity ${duration}ms; }
.fade-leave { opacity: 1; }
.fade-leave.fade-leave-active { opacity: .01; transition: opacity ${duration}ms; }
`
render(
<div>
<style>{styles}</style>
<TransitionGroup
{...this.props}>
<CSSTransition timeout={{ enter: duration, exit: duration }} className="fade">
<div>{this.props.children}</div>
</CSSTransition>
</TransitionGroup>
</div>,
this.portalElement
)
}
componentWillUnmount() {
unmountComponentAtNode(this.portalElement)
document.body.removeChild(this.portalElement)
}
render() {
return null
}
}
Portal.propTypes = {
children: PropsTypes.arrayOf(PropsTypes.any),
}
================================================
FILE: src/components/Spinner.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
const Spinner = props => {
const classes = StyleSheet.create(styles(props))
return (
<div className={css(classes.bouncingLoader)}>
<div className={css(classes.child)} />
<div className={css(classes.child, classes.child2)} />
<div className={css(classes.child, classes.child3)} />
</div>
)
}
Spinner.propTypes = {
color: PropTypes.string,
size: PropTypes.number,
}
const bouncingKeyframes = (size) => ({
'0%': {
opacity: 1,
transform: 'translateY(0)',
},
'100%': {
opacity: .1,
transform: `translateY(-${size}px)`,
}
})
const styles = ({ color, size }) => ({
bouncingLoader: {
display: 'flex',
justifyContent: 'center',
},
child: {
width: size,
height: size,
margin: `${3 * size}px ${ .2 * size}px`,
background: color,
borderRadius: '50%',
animationName: bouncingKeyframes(size),
animationDuration: '.6s',
animationDirection: 'alternate',
animationIterationCount: 'infinite',
},
child2: {
animationDelay: '0.2s',
},
child3: {
animationDelay: '0.4s',
}
})
export default Spinner
================================================
FILE: src/components/Thumbnail.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
import defaults from '../theme'
import { deepMerge } from '../utils/util'
function Thumbnail ({ index, src, thumbnail, active, onClick, theme }) {
const url = thumbnail || src
const classes = StyleSheet.create(deepMerge(defaultStyles, theme))
return (
<div
className={css(classes.thumbnail, active && classes.thumbnail__active)}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onClick(index)
}}
style={{ backgroundImage: `url("${url}")` }}
/>
)
}
Thumbnail.propTypes = {
theme: PropTypes.object,
active: PropTypes.bool,
index: PropTypes.number,
onClick: PropTypes.func.isRequired,
src: PropTypes.string,
thumbnail: PropTypes.string,
}
const defaultStyles = {
thumbnail: {
backgroundPosition: 'center',
backgroundSize: 'cover',
borderRadius: 2,
boxShadow: 'inset 0 0 0 1px hsla(0, 0%, 100%, .2)',
cursor: 'pointer',
display: 'inline-block',
height: defaults.thumbnail.size,
margin: defaults.thumbnail.gutter,
overflow: 'hidden',
width: defaults.thumbnail.size,
},
thumbnail__active: {
boxShadow:`inset 0 0 0 2px ${defaults.thumbnail.activeBorderColor}`,
}
}
export default Thumbnail
================================================
FILE: src/components/Thumbnails.js
================================================
import PropTypes from 'prop-types'
import React from 'react'
import { css, StyleSheet } from 'aphrodite/no-important'
import Thumbnail from './Thumbnail'
import defaults from '../theme'
function Thumbnails ({ currImg, imgs, onClickThumbnail }) {
return (
<div className={css(classes.thumbnail)}>
{imgs.map((img, idx) => {
<Thumbnail
{...img}
active={idx === currImg}
index={idx}
key={idx}
onClick={onClickThumbnail}
/>
})}
</div>
)
}
Thumbnails.propTypes = {
currImg: PropTypes.number,
imgs: PropTypes.array,
onClickThumbnail: PropTypes.func.isRequired,
}
const classes = StyleSheet.create({
Thumbnails: {
bottom: defaults.container.gutter.vertical,
color: '#fff',
height: defaults.thumbnail.height,
left: defaults.container.gutter.horizontal,
overflowX: 'scroll',
overflowY: 'hidden',
position: 'absolute',
right: defaults.container.gutter.horizontal,
textAlign: 'center',
whiteSpace: 'nowrap',
}
})
export default Thumbnails
================================================
FILE: src/icons/Close.js
================================================
export default (fill) => (
`<svg fill="${fill}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
</svg>`
);
================================================
FILE: src/icons/arrow_left.js
================================================
export default (fill) => (
`<svg fill="${fill}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 512 512" xml:space="preserve">
<path d="M213.7,256L213.7,256L213.7,256L380.9,81.9c4.2-4.3,4.1-11.4-0.2-15.8l-29.9-30.6c-4.3-4.4-11.3-4.5-15.5-0.2L131.1,247.9 c-2.2,2.2-3.2,5.2-3,8.1c-0.1,3,0.9,5.9,3,8.1l204.2,212.7c4.2,4.3,11.2,4.2,15.5-0.2l29.9-30.6c4.3-4.4,4.4-11.5,0.2-15.8 L213.7,256z"/>
</svg>`
)
================================================
FILE: src/icons/arrow_right.js
================================================
export default (fill) => (
`<svg fill="${fill}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 512 512" xml:space="preserve">
<path d="M298.3,256L298.3,256L298.3,256L131.1,81.9c-4.2-4.3-4.1-11.4,0.2-15.8l29.9-30.6c4.3-4.4,11.3-4.5,15.5-0.2l204.2,212.7 c2.2,2.2,3.2,5.2,3,8.1c0.1,3-0.9,5.9-3,8.1L176.7,476.8c-4.2,4.3-11.2,4.2-15.5-0.2L131.3,446c-4.3-4.4-4.4-11.5-0.2-15.8 L298.3,256z"/>
</svg>`
);
================================================
FILE: src/icons/close.js
================================================
export default (fill) => (
`<svg fill="${fill}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path d="M443.6,387.1L312.4,255.4l131.5-130c5.4-5.4,5.4-14.2,0-19.6l-37.4-37.6c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4 L256,197.8L124.9,68.3c-2.6-2.6-6.1-4-9.8-4c-3.7,0-7.2,1.5-9.8,4L68,105.9c-5.4,5.4-5.4,14.2,0,19.6l131.5,130L68.4,387.1 c-2.6,2.6-4.1,6.1-4.1,9.8c0,3.7,1.4,7.2,4.1,9.8l37.4,37.6c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1L256,313.1l130.7,131.1 c2.7,2.7,6.2,4.1,9.8,4.1c3.5,0,7.1-1.3,9.8-4.1l37.4-37.6c2.6-2.6,4.1-6.1,4.1-9.8C447.7,393.2,446.2,389.7,443.6,387.1z"/>
</svg>`
);
================================================
FILE: src/theme.js
================================================
// ===================
// Theme
// ===================
const theme = {}
// container
theme.container = {
background: 'rgba(0, 0, 0, .8)',
gutter: {
horizontal: 10,
vertical: 10,
},
zIndex: 2001
}
// header
theme.header = {
height: 40,
}
theme.close = {
fill: 'white'
}
// footer
theme.footer = {
color: '#fff',
count: {
color: 'rgba(255, 255, 255, .75)',
fontSize: '.85em'
},
height: 40,
gutter: {
horizontal: 0,
vertical: 5,
}
}
// thumbnails
theme.thumbnail = {
activeBorderColor: '#fff',
size: 50,
gutter: 2,
}
// arrow
theme.arrow = {
background: 'none',
fill: '#fff',
height: 120,
}
export default theme
================================================
FILE: src/utils/constant.js
================================================
// obj
export const OBJ_KEYS = Object.keys
================================================
FILE: src/utils/util.js
================================================
export function deepMerge(source, target = {}) {
const extended = Object.assign({}, target);
Object.keys(source).forEach((key) => {
if (typeof source[key] !== "object" || !source[key]) {
extended[key] = source[key];
} else {
if (!target[key]) {
extended[key] = source[key];
} else {
extended[key] = deepMerge(target[key], source[key]);
}
}
});
return extended;
}
// export function deepMerge(source, target = {}) {
// // initialize with source styles
// const styles = { ...source }
// // massage in target styles
// Object.keys(target).forEach(key => {
// if (source[key]) {
// styles[key] = (rsCss, props) => {
// return target[key](source[key](rsCss, props), props)
// }
// } else {
// styles[key] = target[key]
// }
// })
// return styles
// }
export const canUseDom = !!(
typeof window !== "undefined" &&
window.document &&
window.document.createElement
);
/**
* Bind multiple conponent methods:
* @param {this} context
* @param {Array} functions
*
* constructor() {
* ...
* bindFunctions.call(this, ['handleClick', 'handleOther'])
* }
*/
export function bindFunctions(functions) {
functions.forEach((f) => (this[f] = this[f].bind(this)));
}
================================================
FILE: webpack.config.js
================================================
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
mode: "development",
context: path.resolve(__dirname, "examples/src"),
entry: {
app: "./app.js",
app_CN: "./app_CN.js",
common: ["react-images-viewer"],
},
output: {
path: path.resolve(__dirname, "examples/dist"),
filename: "[name].js",
publicPath: "/",
},
devtool: "inline-source-map",
devServer: {
contentBase: path.resolve(__dirname, "examples/src"),
host: "0.0.0.0",
port: 8001,
},
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
use: [
{
loader: "babel-loader",
options: { presets: ["react", "env"] },
},
],
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
{
test: /\.html$/,
use: [
{
loader: "html-loader",
},
],
},
],
},
resolve: {
alias: {
"react-images-viewer": path.resolve(__dirname, "src/ImgsViewer"),
},
},
optimization: {
splitChunks: {
name: true,
// filename: 'common.js',
// minChunks: 2,
cacheGroups: {
common: {
test: /[\\/]node_modules[\\/]/,
name: "common",
chunks: "all",
},
},
},
},
plugins: [
new HtmlWebpackPlugin({
filename: "index.html",
inject: false,
template: path.resolve(__dirname, "examples/src/index.html"),
}),
new HtmlWebpackPlugin({
filename: "index_CN.html",
inject: false,
template: path.resolve(__dirname, "examples/src/index_CN.html"),
}),
new MiniCssExtractPlugin({
filename: "example.css",
chunkFilename: "example.css",
}),
],
};
gitextract_t5wlb761/ ├── .babelrc ├── .coveralls.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── CONTRIBUTING.md │ ├── HISTORY.md │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── README_CN.md ├── __mocks__/ │ └── ImgsViewer.js ├── __test__/ │ ├── Gallery.test.js │ ├── jestsetup.js │ ├── rafShim.js │ └── utils.test.js ├── ci.sh ├── examples/ │ └── src/ │ ├── .gitignore │ ├── .npmignore │ ├── app.js │ ├── app_CN.js │ ├── components/ │ │ ├── DownloadButton/ │ │ │ ├── icon.js │ │ │ └── index.js │ │ ├── Gallery.js │ │ └── Spinner.js │ ├── example.less │ ├── index.html │ ├── index_CN.html │ └── standalone.html ├── index.js ├── jest.config.js ├── new-branch.sh ├── package-scripts.js ├── package.json ├── rollup.config.js ├── src/ │ ├── ImgsViewer.js │ ├── components/ │ │ ├── Arrow.js │ │ ├── Container.js │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── Icon.js │ │ ├── PaginatedThumbnails.js │ │ ├── Portal.js │ │ ├── Spinner.js │ │ ├── Thumbnail.js │ │ └── Thumbnails.js │ ├── icons/ │ │ ├── Close.js │ │ ├── arrow_left.js │ │ ├── arrow_right.js │ │ └── close.js │ ├── theme.js │ └── utils/ │ ├── constant.js │ └── util.js └── webpack.config.js
SYMBOL INDEX (73 symbols across 15 files)
FILE: examples/src/app.js
function makeUnsplashSrc (line 7) | function makeUnsplashSrc(id) {
function makeUnsplashSrcSet (line 10) | function makeUnsplashSrcSet(id, size) {
function makeUnsplashThumbnail (line 13) | function makeUnsplashThumbnail(id, orientation = "landscape") {
constant DEFAULT_IMAGES (line 22) | const DEFAULT_IMAGES = [
constant THEMED_IMAGES (line 54) | const THEMED_IMAGES = [
constant THUMBNAIL_IMAGES (line 86) | const THUMBNAIL_IMAGES = [
FILE: examples/src/app_CN.js
function makeUnsplashSrc (line 7) | function makeUnsplashSrc (id) {
function makeUnsplashSrcSet (line 10) | function makeUnsplashSrcSet(id, size) {
function makeUnsplashThumbnail (line 13) | function makeUnsplashThumbnail (id, orientation = 'landscape') {
constant DEFAULT_IMAGES (line 24) | const DEFAULT_IMAGES = [
constant THEMED_IMAGES (line 31) | const THEMED_IMAGES = [
constant THUMBNAIL_IMAGES (line 38) | const THUMBNAIL_IMAGES = [
FILE: examples/src/components/DownloadButton/index.js
class DownloadButton (line 5) | class DownloadButton extends Component {
method constructor (line 6) | constructor () {
method render (line 9) | render () {
FILE: examples/src/components/Gallery.js
class Gallery (line 6) | class Gallery extends Component {
method constructor (line 7) | constructor () {
method openImgsViewer (line 22) | openImgsViewer (index, event) {
method closeImgsViewer (line 29) | closeImgsViewer () {
method gotoPrev (line 35) | gotoPrev () {
method gotoNext (line 40) | gotoNext () {
method gotoImg (line 45) | gotoImg (index) {
method handleClickImg (line 50) | handleClickImg () {
method renderGallery (line 54) | renderGallery () {
method render (line 78) | render () {
FILE: src/ImgsViewer.js
function normalizeSourceSet (line 17) | function normalizeSourceSet(data) {
class ImgsViewer (line 32) | class ImgsViewer extends Component {
method constructor (line 33) | constructor(props) {
method componentDidMount (line 57) | componentDidMount() {
method UNSAFE_componentWillReceiveProps (line 68) | UNSAFE_componentWillReceiveProps(nextProps) {
method componentWillUnmount (line 112) | componentWillUnmount() {
method preloadImg (line 122) | preloadImg(idx, onload) {
method preloadImgData (line 125) | preloadImgData(data, onload) {
method gotoNext (line 140) | gotoNext(event) {
method gotoPrev (line 153) | gotoPrev(event) {
method closeBackdrop (line 166) | closeBackdrop(event) {
method handleKeyboardInput (line 174) | handleKeyboardInput(event) {
method handleImgLoaded (line 191) | handleImgLoaded() {
method renderArrowPrev (line 201) | renderArrowPrev(theme) {
method renderArrowNext (line 215) | renderArrowNext(theme) {
method renderDialog (line 229) | renderDialog(newState) {
method renderImgs (line 274) | renderImgs(theme) {
method renderThumbnails (line 310) | renderThumbnails(theme) {
method renderHeader (line 335) | renderHeader(theme) {
method renderFooter (line 348) | renderFooter(theme) {
method renderSpinner (line 364) | renderSpinner() {
method render (line 382) | render() {
FILE: src/components/Arrow.js
function Arrow (line 9) | function Arrow({ direction, icon, onClick, size, theme, ...props }) {
FILE: src/components/Container.js
function Container (line 8) | function Container (props) {
FILE: src/components/Footer.js
function Footer (line 7) | function Footer ({ caption, countCurr, countSeparator, countTotal, showC...
FILE: src/components/Header.js
function Header (line 9) | function Header ({ customControls, onClose, showCloseBtn, closeBtnTitle,...
FILE: src/components/PaginatedThumbnails.js
class PaginatedThumbnails (line 27) | class PaginatedThumbnails extends Component {
method constructor (line 28) | constructor (props) {
method UNSAFE_componentWillReceiveProps (line 38) | UNSAFE_componentWillReceiveProps (nextProps) {
method getFirst (line 50) | getFirst () {
method setFirst (line 57) | setFirst (event, newFirst) {
method gotoPrev (line 72) | gotoPrev(event) {
method gotoNext (line 75) | gotoNext(event) {
method clampFirst (line 78) | clampFirst (value) {
method renderArrowPrev (line 96) | renderArrowPrev (theme) {
method renderArrowNext (line 113) | renderArrowNext (theme) {
method render (line 131) | render () {
FILE: src/components/Portal.js
class Portal (line 7) | class Portal extends Component {
method constructor (line 8) | constructor() {
method componentDidMount (line 12) | componentDidMount() {
method componentDidUpdate (line 18) | componentDidUpdate() {
method componentWillUnmount (line 41) | componentWillUnmount() {
method render (line 45) | render() {
FILE: src/components/Thumbnail.js
function Thumbnail (line 9) | function Thumbnail ({ index, src, thumbnail, active, onClick, theme }) {
FILE: src/components/Thumbnails.js
function Thumbnails (line 7) | function Thumbnails ({ currImg, imgs, onClickThumbnail }) {
FILE: src/utils/constant.js
constant OBJ_KEYS (line 2) | const OBJ_KEYS = Object.keys
FILE: src/utils/util.js
function deepMerge (line 1) | function deepMerge(source, target = {}) {
function bindFunctions (line 51) | function bindFunctions(functions) {
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (131K chars).
[
{
"path": ".babelrc",
"chars": 285,
"preview": "{\n \"ignore\": [\"node-modules\"],\n \"presets\": [\"env\", \"react\"],\n \"plugins\": [ [\"transform-object-rest-spread\", { \"useBui"
},
{
"path": ".coveralls.yml",
"chars": 246,
"preview": "service_name: travis-ci\nrepo_token: S5CSBnH2886RuV3s0ecSjaam2N4JOjsC7\n# coverageReporter: {\n# dir: path.join(__dirna"
},
{
"path": ".editorconfig",
"chars": 284,
"preview": "\n# This file is for unifying the coding style for different editors and IDEs\n# editorconfig.org\nroot = true\n [*]\nend_of_"
},
{
"path": ".eslintignore",
"chars": 39,
"preview": "lib/*\ndist/*\ncoverage/*\nnode_modules/*\n"
},
{
"path": ".eslintrc.js",
"chars": 939,
"preview": "// https://eslint.org/docs/user-guide/configuring\n\nmodule.exports = {\n root: true,\n parser: 'babel-eslint',\n parserOp"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 1083,
"preview": "# Contributing\n\nThanks for your interest in react-images-viewer. All forms fo contribution ares welcome, from issue repo"
},
{
"path": ".github/HISTORY.md",
"chars": 424,
"preview": "# React-Images-Viewer\n\n### v1.6.8 / 2022-01-03\n\n- Update: fix issues: #28, #38\n\n### v1.3.0 / 2018-07-24\n\n- Update: Make "
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 83,
"preview": "**Steps to reproduce the behavior:**\n\n**Expected behavior:**\n\n**Actual behavior:**\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 379,
"preview": "<!-- Please make sure the following is filled in before submitting your Pull Request, thanks! -->\n\n**Description of chan"
},
{
"path": ".gitignore",
"chars": 1228,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Build\nlib\ndist\nexamples/dist\n\n# Webstorm\n.idea/\n\n# V"
},
{
"path": ".npmignore",
"chars": 9,
"preview": "examples\n"
},
{
"path": ".travis.yml",
"chars": 202,
"preview": "language: node_js\nsudo: false\nnode_js:\n - stable\ncache:\n directories:\n - node_modules\ninstall:\n|\n npm install -g y"
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2018 yiminanci\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 7319,
"preview": "# react-images-viewer\n\n[](https://t"
},
{
"path": "README_CN.md",
"chars": 6141,
"preview": "# react-images-viewer\n\n[](https://t"
},
{
"path": "__mocks__/ImgsViewer.js",
"chars": 0,
"preview": ""
},
{
"path": "__test__/Gallery.test.js",
"chars": 1958,
"preview": "import React from 'react'\nimport ImgsViewer from '../src/ImgsViewer'\nimport { mount } from 'enzyme'\n\nconst props1 = {\n "
},
{
"path": "__test__/jestsetup.js",
"chars": 120,
"preview": "import { configure } from 'enzyme'\nimport Adapter from 'enzyme-adapter-react-16'\n\nconfigure({ adapter: new Adapter() })\n"
},
{
"path": "__test__/rafShim.js",
"chars": 67,
"preview": "global.requestAnimationFrame = callback => setTimeout(callback, 0)\n"
},
{
"path": "__test__/utils.test.js",
"chars": 1113,
"preview": "import { deepMerge, canUseDom, bindFunctions } from '../src/utils/util'\n\ndescribe('the deepMerge function', () => {\n te"
},
{
"path": "ci.sh",
"chars": 131,
"preview": "#!/bin/bash -e\n# daily push, but add all unStaged changes.\ncommit_msg=\"$1\"\ngit add .\ngit commit -m \"$commit_msg\"\ngit pul"
},
{
"path": "examples/src/.gitignore",
"chars": 171,
"preview": "## This file is here to ensure it is included in the gh-pages branch,\n## when 'gulp deploy' is used to push updates to t"
},
{
"path": "examples/src/.npmignore",
"chars": 0,
"preview": ""
},
{
"path": "examples/src/app.js",
"chars": 7352,
"preview": "import React from \"react\";\nimport { render } from \"react-dom\";\nimport Gallery from \"./components/Gallery\";\nimport Custom"
},
{
"path": "examples/src/app_CN.js",
"chars": 5783,
"preview": "import React from 'react'\nimport { render } from 'react-dom'\nimport Gallery from './components/Gallery'\nimport CustomSpi"
},
{
"path": "examples/src/components/DownloadButton/icon.js",
"chars": 480,
"preview": "module.exports = (\n '<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\""
},
{
"path": "examples/src/components/DownloadButton/index.js",
"chars": 762,
"preview": "import PropTypes from 'prop-types'\nimport React, { Component } from 'react'\nimport DownloadIcon from './icon'\n\nclass Dow"
},
{
"path": "examples/src/components/Gallery.js",
"chars": 3860,
"preview": "import React, { Component } from 'react'\nimport ImgsViewer from 'react-images-viewer'\nimport PropTypes from 'prop-types'"
},
{
"path": "examples/src/components/Spinner.js",
"chars": 1163,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\n\nc"
},
{
"path": "examples/src/example.less",
"chars": 5220,
"preview": "// Cmomon Example Styles\n\n\n\n// Constans\n// example site\n@gutter:\t\t\t\t\t\t\t20px;\n@table-cell-gutter: \t10px;\n@left-col-width:"
},
{
"path": "examples/src/index.html",
"chars": 14234,
"preview": "<!doctype html>\n<head>\n <meta charset=\"UTF-8\">\n <title>React-Images-Viewer</title>\n <meta name=\"viewport\" content=\"wi"
},
{
"path": "examples/src/index_CN.html",
"chars": 12876,
"preview": "<!doctype html>\n<head>\n <meta charset=\"UTF-8\">\n <title>React-Images-Viewer</title>\n <meta name=\"viewport\" content=\"wi"
},
{
"path": "examples/src/standalone.html",
"chars": 4004,
"preview": "<!doctype html>\n<head>\n <meta charset=\"UTF-8\">\n <title>React-Images-Viewer</title>\n <meta name=\"viewport\" content=\"wi"
},
{
"path": "index.js",
"chars": 102,
"preview": "'use strict';\n\nmodule.exports = function(number, locale) {\n return number.toLocaleString(locale);\n};\n"
},
{
"path": "jest.config.js",
"chars": 553,
"preview": "module.exports = {\n verbose: true,\n collectCoverage: true,\n collectCoverageFrom: ['src/*.{js, jsx}'],\n setupFiles: ["
},
{
"path": "new-branch.sh",
"chars": 58,
"preview": "git checkout -b $1\ngit push -u origin $1\ngit branch --all\n"
},
{
"path": "package-scripts.js",
"chars": 852,
"preview": "const npsUtils = require('nps-utils')\nconst path = require('path')\nconst { series, rimraf, concurrent } = npsUtils\n\nmod"
},
{
"path": "package.json",
"chars": 3055,
"preview": "{\n \"name\": \"react-images-viewer\",\n \"version\": \"1.7.1\",\n \"description\": \"Create an react-images-viewer component.\",\n "
},
{
"path": "rollup.config.js",
"chars": 1596,
"preview": "import babel from 'rollup-plugin-babel'\nimport resolve from 'rollup-plugin-node-resolve'\nimport { uglify } from 'rollup-"
},
{
"path": "src/ImgsViewer.js",
"chars": 12393,
"preview": "import PropTypes from \"prop-types\";\nimport React, { Component, Fragment } from \"react\";\nimport { StyleSheet, css } from "
},
{
"path": "src/components/Arrow.js",
"chars": 1957,
"preview": "import PropTypes from \"prop-types\";\nimport React from \"react\";\nimport { css, StyleSheet } from \"aphrodite/no-important\";"
},
{
"path": "src/components/Container.js",
"chars": 1041,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\n\ni"
},
{
"path": "src/components/Footer.js",
"chars": 1720,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\nim"
},
{
"path": "src/components/Header.js",
"chars": 1426,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\n\ni"
},
{
"path": "src/components/Icon.js",
"chars": 554,
"preview": "import PropTypes from \"prop-types\";\nimport React from \"react\";\nimport arrowLeft from \"../icons/arrow_left\";\nimport arrow"
},
{
"path": "src/components/PaginatedThumbnails.js",
"chars": 4115,
"preview": "import PropTypes from 'prop-types'\nimport React, { Component } from 'react'\nimport { css, StyleSheet } from 'aphrodite/n"
},
{
"path": "src/components/Portal.js",
"chars": 1422,
"preview": "import React, { Component } from 'react'\nimport PropsTypes from 'prop-types'\nimport { CSSTransition, TransitionGroup } f"
},
{
"path": "src/components/Spinner.js",
"chars": 1229,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\n\nc"
},
{
"path": "src/components/Thumbnail.js",
"chars": 1344,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\n\ni"
},
{
"path": "src/components/Thumbnails.js",
"chars": 1071,
"preview": "import PropTypes from 'prop-types'\nimport React from 'react'\nimport { css, StyleSheet } from 'aphrodite/no-important'\nim"
},
{
"path": "src/icons/Close.js",
"chars": 772,
"preview": "export default (fill) => (\n\t`<svg fill=\"${fill}\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://ww"
},
{
"path": "src/icons/arrow_left.js",
"chars": 503,
"preview": "export default (fill) => (\n `<svg fill=\"${fill}\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://w"
},
{
"path": "src/icons/arrow_right.js",
"chars": 503,
"preview": "export default (fill) => (\n `<svg fill=\"${fill}\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://w"
},
{
"path": "src/icons/close.js",
"chars": 772,
"preview": "export default (fill) => (\n\t`<svg fill=\"${fill}\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://ww"
},
{
"path": "src/theme.js",
"chars": 677,
"preview": "// ===================\n// Theme\n// ===================\n\nconst theme = {}\n\n// container\ntheme.container = {\n background:"
},
{
"path": "src/utils/constant.js",
"chars": 43,
"preview": "// obj\nexport const OBJ_KEYS = Object.keys\n"
},
{
"path": "src/utils/util.js",
"chars": 1283,
"preview": "export function deepMerge(source, target = {}) {\n const extended = Object.assign({}, target);\n\n Object.keys(source).fo"
},
{
"path": "webpack.config.js",
"chars": 1934,
"preview": "const path = require(\"path\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nconst MiniCssExtractPlugin = req"
}
]
About this extraction
This page contains the full source code of the guonanci/react-images-viewer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (117.2 KB), approximately 33.5k tokens, and a symbol index with 73 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.