+
+
+
+ {images}
+
+
+ {!this.props.prevSrc ? '' :
+
+ }
-
- {images}
-
-
- {!this.props.prevSrc ? '' :
-
- }
-
- {!this.props.nextSrc ? '' :
-
- }
-
-
-
- -
- {this.props.imageTitle}
-
-
-
-
- {!this.props.toolbarButtons ? '' : this.props.toolbarButtons.map(function(button, i) {
- return (- {button}
);
- })}
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
-
-
+ {!this.props.nextSrc ? '' :
+
+ }
+
+
+
+ -
+ {this.props.imageTitle}
+
+
+
+
+ {!this.props.toolbarButtons ? '' : this.props.toolbarButtons.map((button, i) => (
+ - {button}
+ ))}
+
+ -
+
+
+
+ -
+
+
+
+ -
+
+
+
-
-
+
+
);
}
-});
+}
+
+ReactImageLightbox.propTypes = {
+ //-----------------------------
+ // Image sources
+ //-----------------------------
+
+ // Main display image url
+ mainSrc: PropTypes.string.isRequired,
+
+ // Previous display image url (displayed to the left)
+ // If left undefined, movePrev actions will not be performed, and the button not displayed
+ prevSrc: PropTypes.string,
+
+ // Next display image url (displayed to the right)
+ // If left undefined, moveNext actions will not be performed, and the button not displayed
+ nextSrc: PropTypes.string,
+
+ //-----------------------------
+ // Image thumbnail sources
+ //-----------------------------
+
+ // Thumbnail image url corresponding to props.mainSrc
+ mainSrcThumbnail: PropTypes.string,
+
+ // Thumbnail image url corresponding to props.prevSrc
+ prevSrcThumbnail: PropTypes.string,
+
+ // Thumbnail image url corresponding to props.nextSrc
+ nextSrcThumbnail: PropTypes.string,
+
+ //-----------------------------
+ // Event Handlers
+ //-----------------------------
+
+ // Close window event
+ // Should change the parent state such that the lightbox is not rendered
+ onCloseRequest: PropTypes.func.isRequired,
+
+ // Move to previous image event
+ // Should change the parent state such that props.prevSrc becomes props.mainSrc,
+ // props.mainSrc becomes props.nextSrc, etc.
+ onMovePrevRequest: PropTypes.func,
+
+ // Move to next image event
+ // Should change the parent state such that props.nextSrc becomes props.mainSrc,
+ // props.mainSrc becomes props.prevSrc, etc.
+ onMoveNextRequest: PropTypes.func,
+
+ //-----------------------------
+ // Download discouragement settings
+ //-----------------------------
+
+ // Enable download discouragement (prevents [right-click -> Save Image As...])
+ discourageDownloads: PropTypes.bool,
+
+ //-----------------------------
+ // Animation settings
+ //-----------------------------
+
+ // Disable all animation
+ animationDisabled: PropTypes.bool,
+
+ // Disable animation on actions performed with keyboard shortcuts
+ animationOnKeyInput: PropTypes.bool,
+
+ // Animation duration (ms)
+ animationDuration: PropTypes.number,
+
+ //-----------------------------
+ // Keyboard shortcut settings
+ //-----------------------------
+
+ // Required interval of time (ms) between key actions
+ // (prevents excessively fast navigation of images)
+ keyRepeatLimit: PropTypes.number,
+
+ // Amount of time (ms) restored after each keyup
+ // (makes rapid key presses slightly faster than holding down the key to navigate images)
+ keyRepeatKeyupBonus: PropTypes.number,
+
+ //-----------------------------
+ // Image info
+ //-----------------------------
+
+ // Image title
+ imageTitle: PropTypes.node,
+
+ //-----------------------------
+ // Other
+ //-----------------------------
+
+ // Array of custom toolbar buttons
+ toolbarButtons: PropTypes.arrayOf(PropTypes.node),
+
+ // Padding (px) between the edge of the window and the lightbox
+ imagePadding: PropTypes.number,
+
+ // When true, clicks outside of the image close the lightbox
+ clickOutsideToClose: PropTypes.bool,
+};
+
+ReactImageLightbox.defaultProps = {
+ onMovePrevRequest: () => {},
+ onMoveNextRequest: () => {},
+
+ discourageDownloads: false,
+
+ animationDisabled: false,
+ animationOnKeyInput: false,
+ animationDuration: 300,
+
+ keyRepeatLimit: 180,
+ keyRepeatKeyupBonus: 40,
+
+ imagePadding: 10,
+ clickOutsideToClose: true,
+};
-module.exports = Radium.call(this, ReactImageLightbox);
+export default ReactImageLightbox;
diff --git a/src/style.scss b/src/style.scss
new file mode 100644
index 00000000..220e6ba1
--- /dev/null
+++ b/src/style.scss
@@ -0,0 +1,201 @@
+$toolbarHeight: 50px;
+
+@keyframes closeWindow {
+ 0% { opacity: 1; }
+ 100% { opacity: 0; }
+}
+
+.outer {
+ background-color: rgba(0, 0, 0, 0.85);
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ width: 100%;
+ height: 100%;
+}
+
+.outerClosing {
+ opacity: 0;
+}
+
+.inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.image {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ margin: auto;
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.imagePrev {
+ left: -100%;
+ right: 100%;
+}
+
+.imageNext {
+ left: 100%;
+ right: -100%;
+}
+
+.imageDiscourager {
+ background-repeat: no-repeat;
+ background-position: center;
+ background-size: contain;
+}
+
+.navButtons {
+ border: none;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 20px;
+ height: 34px;
+ padding: 40px 30px;
+ margin: auto;
+ cursor: pointer;
+ opacity: 0.7;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ &:active {
+ opacity: 0.7;
+ }
+}
+
+.navButtonPrev {
+ left: 0;
+ background: rgba(0, 0, 0, 0.2) url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDE5LDMgLTIsLTIgLTE2LDE2IDE2LDE2IDEsLTEgLTE1LC0xNSAxNSwtMTUgeiIgZmlsbD0iI0ZGRiIvPjwvc3ZnPg==') no-repeat center;
+}
+
+.navButtonNext {
+ right: 0;
+ background: rgba(0, 0, 0, 0.2) url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjM0Ij48cGF0aCBkPSJtIDEsMyAyLC0yIDE2LDE2IC0xNiwxNiAtMSwtMSAxNSwtMTUgLTE1LC0xNSB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+') no-repeat center;
+}
+
+.downloadBlocker {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-image: url('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
+ background-size: cover;
+}
+
+.toolbar {
+ background-color: rgba(0, 0, 0, 0.5);
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ height: $toolbarHeight;
+ display: flex;
+ justify-content: space-between;
+}
+
+.toolbarSide {
+ height: $toolbarHeight;
+ margin: 0;
+}
+
+.toolbarSideNoFlex {
+ height: auto;
+ line-height: $toolbarHeight;
+ max-width: 48%;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+}
+
+.toolbarLeftSide {
+ padding-left: 20px;
+ padding-right: 0;
+ flex: 0 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.toolbarLeftSideNoFlex {
+ left: 0;
+ overflow: visible;
+}
+
+.toolbarRightSide {
+ padding-left: 0;
+ padding-right: 20px;
+ flex: 0 0 auto;
+}
+
+.toolbarRightSideNoFlex {
+ right: 0;
+}
+
+.toolbarItem {
+ display: inline-block;
+ line-height: $toolbarHeight;
+ padding: 0;
+ color: #FFFFFF;
+ font-size: 120%;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.toolbarItemChild {
+ vertical-align: middle;
+}
+
+.builtinButton {
+ width: 40px;
+ height: 35px;
+ cursor: pointer;
+ border: none;
+ opacity: 0.7;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ &:active {
+ outline: none;
+ }
+}
+
+.builtinButtonDisabled {
+ cursor: default;
+ opacity: 0.5;
+
+ &:hover {
+ opacity: 0.5;
+ }
+}
+
+.closeButton {
+ background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj48cGF0aCBkPSJtIDEsMyAxLjI1LC0xLjI1IDcuNSw3LjUgNy41LC03LjUgMS4yNSwxLjI1IC03LjUsNy41IDcuNSw3LjUgLTEuMjUsMS4yNSAtNy41LC03LjUgLTcuNSw3LjUgLTEuMjUsLTEuMjUgNy41LC03LjUgLTcuNSwtNy41IHoiIGZpbGw9IiNGRkYiLz48L3N2Zz4=') no-repeat center;
+}
+
+.zoomInButton {
+ background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PHBhdGggZD0iTTEyIDV2NiIvPjwvZz48Y2lyY2xlIGN4PSIxMiIgY3k9IjgiIHI9IjciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+') no-repeat center;
+}
+
+.zoomOutButton {
+ background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PGcgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTEgMTlsNi02Ii8+PHBhdGggZD0iTTkgOGg2Ii8+PC9nPjxjaXJjbGUgY3g9IjEyIiBjeT0iOCIgcj0iNyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=') no-repeat center;
+}
+
+.outerAnimating {
+ animation-name: closeWindow;
+}
diff --git a/src/util.js b/src/util.js
new file mode 100644
index 00000000..e2e1662f
--- /dev/null
+++ b/src/util.js
@@ -0,0 +1,49 @@
+/**
+ * Get the version of Internet Explorer in use, or undefined
+ *
+ * @return {?number} ieVersion - IE version as an integer, or undefined if not IE
+ */
+export function getIEVersion() {
+ const match = navigator.userAgent.match(/(?:MSIE |Trident\/.*; rv:)(\d+)/);
+ return match ? parseInt(match[1], 10) : undefined;
+}
+
+/**
+ * Placeholder for future translate functionality
+ */
+export function translate(str, replaceStrings = null) {
+ if (!str) {
+ return '';
+ }
+
+ let translated = str;
+ if (replaceStrings) {
+ Object.keys(replaceStrings).forEach(placeholder => {
+ translated = translated.replace(placeholder, replaceStrings[placeholder]);
+ });
+ }
+
+ return translated;
+}
+
+
+export function getWindowWidth() {
+ return window.innerWidth ||
+ document.documentElement.clientWidth ||
+ document.body.clientWidth;
+}
+
+export function getWindowHeight() {
+ return window.innerHeight ||
+ document.documentElement.clientHeight ||
+ document.body.clientHeight;
+}
+
+// Returns true if this window is rendered as an iframe inside another window
+export function isInIframe() {
+ try {
+ return window.self !== window.top;
+ } catch (e) {
+ return true;
+ }
+}
diff --git a/webpack.config.demo.babel.js b/webpack.config.demo.babel.js
new file mode 100644
index 00000000..1160d7c3
--- /dev/null
+++ b/webpack.config.demo.babel.js
@@ -0,0 +1,62 @@
+import HtmlWebpackPlugin from 'html-webpack-plugin';
+import path from 'path';
+import webpack from 'webpack';
+import autoprefixer from 'autoprefixer';
+
+module.exports = {
+ devtool: 'source-map',
+ entry: {
+ demo: './src/examples/cats/app',
+ },
+ output: {
+ path: 'build',
+ filename: 'static/[name].js',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ inject: true,
+ template: './src/examples/cats/index.html',
+ }),
+ new webpack.DefinePlugin({
+ 'process.env': {
+ 'NODE_ENV': JSON.stringify('production'),
+ 'BABEL_ENV': JSON.stringify('production')
+ },
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ },
+ }),
+ ],
+ postcss: [
+ autoprefixer,
+ ],
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ loaders: ['babel'],
+ include: path.join(__dirname, 'src')
+ },
+ {
+ test: /\.scss$/,
+ loaders: [
+ 'style-loader',
+ 'css-loader?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]',
+ 'postcss-loader',
+ 'sass-loader',
+ ],
+ include: path.join(__dirname, 'src')
+ },
+ {
+ test: /\.(jpe?g|png|gif)$/,
+ loaders: [
+ 'file-loader?name=static/[name]-[hash:6].[ext]',
+ ],
+ include: path.join(__dirname, 'src')
+ },
+ ],
+ }
+};
diff --git a/webpack.config.dev.babel.js b/webpack.config.dev.babel.js
new file mode 100644
index 00000000..e78c131d
--- /dev/null
+++ b/webpack.config.dev.babel.js
@@ -0,0 +1,57 @@
+import HtmlWebpackPlugin from 'html-webpack-plugin';
+import path from 'path';
+import webpack from 'webpack';
+import autoprefixer from 'autoprefixer';
+
+module.exports = {
+ devtool: 'eval',
+ entry: {
+ demo: './src/examples/cats/app',
+ },
+ output: {
+ path: 'build',
+ filename: 'static/[name].js',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ filename: 'index.html',
+ inject: true,
+ template: './src/examples/cats/index.html'
+ }),
+ new webpack.HotModuleReplacementPlugin(),
+ new webpack.NoErrorsPlugin(),
+ ],
+ postcss: [
+ autoprefixer,
+ ],
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ loaders: ['react-hot', 'babel'],
+ include: path.join(__dirname, 'src')
+ },
+ {
+ test: /\.scss$/,
+ loaders: [
+ 'style-loader',
+ 'css-loader?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]',
+ 'postcss-loader',
+ 'sass-loader',
+ ],
+ include: path.join(__dirname, 'src')
+ },
+ {
+ test: /\.(jpe?g|png|gif)$/,
+ loaders: [
+ 'file-loader?name=static/[name]-[hash:6].[ext]',
+ ],
+ include: path.join(__dirname, 'src')
+ },
+ ],
+ },
+ devServer: {
+ contentBase: 'build',
+ port: 3001
+ },
+};
diff --git a/webpack.config.umd.babel.js b/webpack.config.umd.babel.js
new file mode 100644
index 00000000..b60a7d12
--- /dev/null
+++ b/webpack.config.umd.babel.js
@@ -0,0 +1,54 @@
+import path from 'path';
+import webpack from 'webpack';
+import autoprefixer from 'autoprefixer';
+
+module.exports = {
+ entry: {
+ 'react-image-lightbox': './src/index',
+ },
+ output: {
+ path: path.join(__dirname, 'dist', 'umd'),
+ filename: '[name].js',
+ libraryTarget: 'umd',
+ library: 'ReactImageLightbox',
+ },
+ resolve: {
+ extensions: ['', '.js']
+ },
+ devtool: 'source-map',
+ plugins: [
+ new webpack.optimize.OccurenceOrderPlugin(),
+ new webpack.optimize.UglifyJsPlugin({
+ compress: {
+ warnings: false
+ },
+ }),
+ ],
+ postcss: [
+ autoprefixer,
+ ],
+ externals: {
+ react: 'react',
+ 'react-dom': 'react-dom',
+ 'react-modal': 'react-modal',
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.jsx?$/,
+ loaders: ['babel'],
+ include: path.join(__dirname, 'src')
+ },
+ {
+ test: /\.scss$/,
+ loaders: [
+ 'style-loader',
+ 'css-loader?modules&importLoaders=1&localIdentName=[local]___[hash:base64:5]',
+ 'postcss-loader',
+ 'sass-loader',
+ ],
+ include: path.join(__dirname, 'src')
+ },
+ ]
+ }
+};