├── .stylelintignore
├── .eslintignore
├── src
├── utils
│ ├── noop.js
│ ├── getPhrase.js
│ ├── calculateThumbnailsContainerDimension.js
│ ├── getPhrasePropTypes.js
│ ├── calculateThumbnailsOffset.js
│ ├── getPhotos.js
│ └── calculateThumbnailsLeftScroll.js
├── .eslintrc
├── components
│ ├── Image
│ │ ├── index.js
│ │ └── Image.js
│ ├── Photo
│ │ ├── index.js
│ │ └── Photo.js
│ ├── Caption
│ │ ├── index.js
│ │ └── Caption.js
│ ├── Control
│ │ ├── index.js
│ │ └── Control.js
│ ├── Gallery
│ │ └── index.js
│ ├── NextButton
│ │ ├── index.js
│ │ └── NextButton.js
│ ├── PrevButton
│ │ ├── index.js
│ │ └── PrevButton.js
│ ├── Thumbnail
│ │ ├── index.js
│ │ └── Thumbnail.js
│ ├── CloseButton
│ │ ├── index.js
│ │ └── CloseButton.js
│ ├── LoadingSpinner
│ │ ├── index.js
│ │ └── LoadingSpinner.js
│ └── TogglePhotoList
│ │ ├── index.js
│ │ └── TogglePhotoList.js
├── common
│ ├── imageDefaultProps.js
│ ├── prop-types.js
│ ├── imagePropTypes.js
│ ├── index.js
│ ├── opacityValidation.js
│ ├── galleryDefaultProps.js
│ └── galleryPropTypes.js
├── shapes
│ ├── SlideDirectionShape.js
│ ├── PhotoShape.js
│ └── PhotosShape.js
├── index.js
├── defaultPhrases.js
├── constants.js
└── ReactBnbGallery.js
├── example
├── src
│ ├── components
│ │ ├── Spacing
│ │ │ ├── constants.js
│ │ │ ├── utils.js
│ │ │ └── index.js
│ │ ├── Footer
│ │ │ ├── index.js
│ │ │ └── Footer.js
│ │ ├── Header
│ │ │ ├── index.js
│ │ │ ├── Header.js
│ │ │ └── logo.svg
│ │ ├── Heading
│ │ │ ├── index.js
│ │ │ └── Heading.js
│ │ ├── PhotoGrid
│ │ │ ├── index.js
│ │ │ ├── utils.js
│ │ │ ├── shuffle.js
│ │ │ ├── PhotoGrid.js
│ │ │ ├── Image.js
│ │ │ └── photos.js
│ │ ├── GitHubButton
│ │ │ ├── index.js
│ │ │ └── GitHubButton.js
│ │ ├── Rule
│ │ │ ├── component.scss
│ │ │ └── index.js
│ │ ├── Anchor
│ │ │ ├── component.scss
│ │ │ └── index.js
│ │ ├── Section
│ │ │ ├── component.scss
│ │ │ └── index.js
│ │ ├── Container
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── Text
│ │ │ └── index.js
│ │ ├── Title
│ │ │ └── index.js
│ │ └── Button
│ │ │ └── index.js
│ ├── pages
│ │ ├── Help
│ │ │ ├── index.js
│ │ │ └── Help.js
│ │ ├── Home
│ │ │ ├── index.js
│ │ │ └── Home.js
│ │ ├── License
│ │ │ ├── index.js
│ │ │ └── License.js
│ │ ├── Options
│ │ │ └── index.js
│ │ ├── Examples
│ │ │ ├── index.js
│ │ │ ├── localization
│ │ │ │ └── es.js
│ │ │ ├── section.css
│ │ │ ├── Examples.js
│ │ │ └── constants.js
│ │ ├── GettingStarted
│ │ │ ├── index.js
│ │ │ └── GettingStarted.js
│ │ └── index.js
│ ├── assets
│ │ ├── common
│ │ │ ├── _reset.scss
│ │ │ ├── _global.scss
│ │ │ ├── normalize
│ │ │ │ ├── _variables.scss
│ │ │ │ └── _vertical-rhythm.scss
│ │ │ └── _variables.scss
│ │ ├── layouts
│ │ │ ├── _content.scss
│ │ │ ├── _footer.scss
│ │ │ └── _header.scss
│ │ ├── components
│ │ │ ├── _titles.scss
│ │ │ ├── _texts.scss
│ │ │ ├── _tables.scss
│ │ │ ├── _photo-grid.scss
│ │ │ ├── _buttons.scss
│ │ │ └── _prism.scss
│ │ ├── main.scss
│ │ └── pages
│ │ │ └── _home.scss
│ ├── index.js
│ ├── utils
│ │ └── withPrism.js
│ ├── App.js
│ └── photos.js
├── public
│ ├── favicon.ico
│ ├── manifest.json
│ └── index.html
├── .gitignore
├── package.json
└── README.md
├── rbg-logo.png
├── docs
├── components
│ ├── Head
│ │ ├── index.js
│ │ └── Head.js
│ ├── Logo
│ │ └── index.js
│ ├── Main
│ │ ├── index.js
│ │ └── Main.js
│ ├── Footer
│ │ ├── Footer.js
│ │ └── index.js
│ ├── Header
│ │ ├── index.js
│ │ └── Header.js
│ ├── Social
│ │ ├── index.js
│ │ └── Social.js
│ ├── Sidebar
│ │ ├── index.js
│ │ └── Sidebar.js
│ ├── PhotoGrid
│ │ ├── index.js
│ │ ├── utils.js
│ │ ├── shuffle.js
│ │ ├── Image.js
│ │ ├── PhotoGrid.js
│ │ └── photos.js
│ ├── SidebarDocs
│ │ ├── index.js
│ │ └── SidebarDocs.js
│ ├── Navigation
│ │ ├── index.js
│ │ ├── SubMenu.js
│ │ └── Navigation.js
│ └── Layout
│ │ ├── index.js
│ │ ├── context.js
│ │ └── Layout.js
├── public
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── vercel.svg
├── postcss.config.js
├── jsconfig.json
├── next.config.js
├── .gitignore
├── css
│ └── styles.css
├── tailwind.config.js
├── pages
│ ├── docs
│ │ ├── help.mdx
│ │ ├── license.mdx
│ │ ├── installation.mdx
│ │ └── options.mdx
│ ├── _document.js
│ ├── _app.js
│ └── index.js
├── package.json
├── data
│ └── getDocsPages.jsx
├── README.md
├── layouts
│ └── docs-page.js
└── constants
│ └── photos.js
├── index.js
├── react-bnb-demo.png
├── netlify.toml
├── .travis.yml
├── tests
├── setup.js
├── components
│ ├── Gallery.test.js
│ └── ReactBnbGallery.test.js
├── utils.test.js
└── test-photos.js
├── .editorconfig
├── .npmignore
├── .gitignore
├── TODO.md
├── jest.config.js
├── .stylelintrc
├── babel.config.js
├── .github
└── workflows
│ └── npm-publish.yml
├── LICENSE
├── .eslintrc
├── rollup.config.js
├── README.md
├── CHANGELOG.md
├── index.d.ts
├── CONTRIBUTING.md
└── package.json
/.stylelintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | jest.config.js
2 |
--------------------------------------------------------------------------------
/src/utils/noop.js:
--------------------------------------------------------------------------------
1 | export default () => {};
2 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/example/src/components/Spacing/constants.js:
--------------------------------------------------------------------------------
1 | export const BASE_UNITS = 4;
2 |
--------------------------------------------------------------------------------
/rbg-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pedropalau/react-bnb-gallery/HEAD/rbg-logo.png
--------------------------------------------------------------------------------
/docs/components/Head/index.js:
--------------------------------------------------------------------------------
1 | import Head from './Head';
2 |
3 | export default Head;
4 |
--------------------------------------------------------------------------------
/docs/components/Logo/index.js:
--------------------------------------------------------------------------------
1 | import Logo from './Logo';
2 |
3 | export default Logo;
4 |
--------------------------------------------------------------------------------
/docs/components/Main/index.js:
--------------------------------------------------------------------------------
1 | import Main from './Main';
2 |
3 | export default Main;
4 |
--------------------------------------------------------------------------------
/src/components/Image/index.js:
--------------------------------------------------------------------------------
1 | import Image from './Image';
2 |
3 | export default Image;
4 |
--------------------------------------------------------------------------------
/src/components/Photo/index.js:
--------------------------------------------------------------------------------
1 | import Photo from './Photo';
2 |
3 | export default Photo;
4 |
--------------------------------------------------------------------------------
/docs/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | const Footer = () => null;
2 |
3 | export default Footer;
4 |
--------------------------------------------------------------------------------
/docs/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import Footer from './Footer';
2 |
3 | export default Footer;
4 |
--------------------------------------------------------------------------------
/docs/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header';
2 |
3 | export default Header;
4 |
--------------------------------------------------------------------------------
/docs/components/Social/index.js:
--------------------------------------------------------------------------------
1 | import Social from './Social';
2 |
3 | export default Social;
4 |
--------------------------------------------------------------------------------
/docs/components/Sidebar/index.js:
--------------------------------------------------------------------------------
1 | import Sidebar from './Sidebar';
2 |
3 | export default Sidebar;
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-unresolved
2 | module.exports = require('./dist');
3 |
--------------------------------------------------------------------------------
/react-bnb-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pedropalau/react-bnb-gallery/HEAD/react-bnb-demo.png
--------------------------------------------------------------------------------
/src/components/Caption/index.js:
--------------------------------------------------------------------------------
1 | import Caption from './Caption';
2 |
3 | export default Caption;
4 |
--------------------------------------------------------------------------------
/src/components/Control/index.js:
--------------------------------------------------------------------------------
1 | import Control from './Control';
2 |
3 | export default Control;
4 |
--------------------------------------------------------------------------------
/src/components/Gallery/index.js:
--------------------------------------------------------------------------------
1 | import Gallery from './Gallery';
2 |
3 | export default Gallery;
4 |
--------------------------------------------------------------------------------
/example/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import Footer from './Footer';
2 |
3 | export default Footer;
4 |
--------------------------------------------------------------------------------
/example/src/components/Header/index.js:
--------------------------------------------------------------------------------
1 | import Header from './Header';
2 |
3 | export default Header;
4 |
--------------------------------------------------------------------------------
/docs/components/PhotoGrid/index.js:
--------------------------------------------------------------------------------
1 | import PhotoGrid from './PhotoGrid';
2 |
3 | export default PhotoGrid;
4 |
--------------------------------------------------------------------------------
/docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pedropalau/react-bnb-gallery/HEAD/docs/public/favicon.ico
--------------------------------------------------------------------------------
/example/src/components/Heading/index.js:
--------------------------------------------------------------------------------
1 | import Heading from './Heading';
2 |
3 | export default Heading;
4 |
--------------------------------------------------------------------------------
/src/components/NextButton/index.js:
--------------------------------------------------------------------------------
1 | import NextButton from './NextButton';
2 |
3 | export default NextButton;
4 |
--------------------------------------------------------------------------------
/src/components/PrevButton/index.js:
--------------------------------------------------------------------------------
1 | import PrevButton from './PrevButton';
2 |
3 | export default PrevButton;
4 |
--------------------------------------------------------------------------------
/src/components/Thumbnail/index.js:
--------------------------------------------------------------------------------
1 | import Thumbnail from './Thumbnail';
2 |
3 | export default Thumbnail;
4 |
--------------------------------------------------------------------------------
/docs/components/SidebarDocs/index.js:
--------------------------------------------------------------------------------
1 | import SidebarDocs from './SidebarDocs';
2 |
3 | export default SidebarDocs;
4 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pedropalau/react-bnb-gallery/HEAD/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/src/components/PhotoGrid/index.js:
--------------------------------------------------------------------------------
1 | import PhotoGrid from './PhotoGrid';
2 |
3 | export default PhotoGrid;
4 |
--------------------------------------------------------------------------------
/example/src/pages/Help/index.js:
--------------------------------------------------------------------------------
1 | import { default as HelpPage } from './Help';
2 |
3 | export default HelpPage;
4 |
--------------------------------------------------------------------------------
/example/src/pages/Home/index.js:
--------------------------------------------------------------------------------
1 | import { default as HomePage } from './Home';
2 |
3 | export default HomePage;
4 |
--------------------------------------------------------------------------------
/src/components/CloseButton/index.js:
--------------------------------------------------------------------------------
1 | import CloseButton from './CloseButton';
2 |
3 | export default CloseButton;
4 |
--------------------------------------------------------------------------------
/docs/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pedropalau/react-bnb-gallery/HEAD/docs/public/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pedropalau/react-bnb-gallery/HEAD/docs/public/favicon-32x32.png
--------------------------------------------------------------------------------
/example/src/components/PhotoGrid/utils.js:
--------------------------------------------------------------------------------
1 | export const getHeightWithProportion = (w, w2, h) => h * (w / w2).toFixed(3);
2 |
--------------------------------------------------------------------------------
/example/src/pages/License/index.js:
--------------------------------------------------------------------------------
1 | import { default as LicensePage } from './License';
2 |
3 | export default LicensePage;
4 |
--------------------------------------------------------------------------------
/example/src/pages/Options/index.js:
--------------------------------------------------------------------------------
1 | import { default as OptionsPage } from './Options';
2 |
3 | export default OptionsPage;
4 |
--------------------------------------------------------------------------------
/example/src/pages/Examples/index.js:
--------------------------------------------------------------------------------
1 | import { default as ExamplesPage } from './Examples';
2 |
3 | export default ExamplesPage;
4 |
--------------------------------------------------------------------------------
/src/components/LoadingSpinner/index.js:
--------------------------------------------------------------------------------
1 | import LoadingSpinner from './LoadingSpinner';
2 |
3 | export default LoadingSpinner;
4 |
--------------------------------------------------------------------------------
/src/components/TogglePhotoList/index.js:
--------------------------------------------------------------------------------
1 | import TogglePhotoList from './TogglePhotoList';
2 |
3 | export default TogglePhotoList;
4 |
--------------------------------------------------------------------------------
/src/common/imageDefaultProps.js:
--------------------------------------------------------------------------------
1 | import noop from '../utils/noop';
2 |
3 | export default {
4 | onLoad: noop,
5 | onError: noop,
6 | };
7 |
--------------------------------------------------------------------------------
/example/src/components/GitHubButton/index.js:
--------------------------------------------------------------------------------
1 | import { default as GitHubButton } from './GitHubButton';
2 |
3 | export default GitHubButton;
4 |
--------------------------------------------------------------------------------
/example/src/components/Spacing/utils.js:
--------------------------------------------------------------------------------
1 | import { BASE_UNITS } from './constants';
2 |
3 | export const units = (value) => BASE_UNITS * value;
4 |
--------------------------------------------------------------------------------
/docs/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'tailwindcss',
4 | 'postcss-nesting',
5 | 'autoprefixer',
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/example/src/assets/common/_reset.scss:
--------------------------------------------------------------------------------
1 | @import 'normalize/variables';
2 | @import 'normalize/vertical-rhythm';
3 | @import 'normalize/normalize-mixin';
4 |
--------------------------------------------------------------------------------
/example/src/pages/GettingStarted/index.js:
--------------------------------------------------------------------------------
1 | import { default as GettingStartedPage } from './GettingStarted';
2 |
3 | export default GettingStartedPage;
4 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | base = "docs"
3 | publish = "out"
4 | command = "yarn export"
5 |
6 | [[plugins]]
7 | package = "netlify-plugin-cache-nextjs"
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | cache: yarn
4 |
5 | node_js:
6 | - 13
7 | - 12
8 | - 10
9 |
10 | script:
11 | - yarn lint
12 | - yarn travis
13 |
--------------------------------------------------------------------------------
/docs/components/Navigation/index.js:
--------------------------------------------------------------------------------
1 | import Navigation, { MenuShape } from './Navigation';
2 |
3 | export default Navigation;
4 |
5 | export {
6 | MenuShape,
7 | };
8 |
--------------------------------------------------------------------------------
/docs/components/PhotoGrid/utils.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | export const getHeightWithProportion = (w, w2, h) => h * (w / w2).toFixed(3);
3 |
--------------------------------------------------------------------------------
/example/src/components/Rule/component.scss:
--------------------------------------------------------------------------------
1 | /* Rule component */
2 | .rule {
3 | border-top: 1px solid #DDDDDD;
4 | margin-top: 24px;
5 | margin-bottom: 24px;
6 | }
7 |
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | import 'jest-enzyme';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/src/common/prop-types.js:
--------------------------------------------------------------------------------
1 | import {
2 | forbidExtraProps,
3 | nonNegativeInteger,
4 | } from 'airbnb-prop-types';
5 |
6 | export {
7 | forbidExtraProps,
8 | nonNegativeInteger,
9 | };
10 |
--------------------------------------------------------------------------------
/example/src/components/Rule/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import './component.scss';
4 |
5 | const Rule = () => (
6 |
7 | );
8 |
9 | export default Rule;
10 |
--------------------------------------------------------------------------------
/docs/jsconfig.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "compilerOptions": {
4 | "baseUrl": "node_modules",
5 | "paths": {
6 | "@/*": ["../*"]
7 | }
8 | },
9 | "exclude": ["node_modules", "out"]
10 | }
11 |
--------------------------------------------------------------------------------
/src/shapes/SlideDirectionShape.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import {
4 | FORWARDS,
5 | BACKWARDS,
6 | } from '../constants';
7 |
8 | export default PropTypes.oneOf([FORWARDS, BACKWARDS]);
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import ReactBnbGallery from './ReactBnbGallery';
2 | import Gallery from './components/Gallery';
3 |
4 | export default ReactBnbGallery;
5 |
6 | export {
7 | ReactBnbGallery,
8 | Gallery,
9 | };
10 |
--------------------------------------------------------------------------------
/src/common/imagePropTypes.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import { forbidExtraProps } from './prop-types';
4 |
5 | export default forbidExtraProps({
6 | onLoad: PropTypes.func,
7 | onError: PropTypes.func,
8 | });
9 |
--------------------------------------------------------------------------------
/example/src/components/Anchor/component.scss:
--------------------------------------------------------------------------------
1 | /* Anchor component */
2 | .anchor {
3 | display: block;
4 | float: left;
5 | line-height: 1.45;
6 | margin-left: -30px;
7 | padding-right: 4px;
8 | visibility: hidden;
9 | }
10 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "react-bnb-gallery",
3 | "name": "react-bnb-gallery",
4 | "start_url": "./index.html",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/getPhrase.js:
--------------------------------------------------------------------------------
1 | export default function getPhrase(phrase, args) {
2 | if (typeof phrase === 'string') return phrase;
3 |
4 | if (typeof phrase === 'function') {
5 | return phrase(args);
6 | }
7 |
8 | return '';
9 | }
10 |
--------------------------------------------------------------------------------
/src/defaultPhrases.js:
--------------------------------------------------------------------------------
1 | const noPhotosProvided = 'No photos to show';
2 | const showPhotoList = 'Show photo list';
3 | const hidePhotoList = 'Hide photo list';
4 |
5 | export default {
6 | noPhotosProvided,
7 | showPhotoList,
8 | hidePhotoList,
9 | };
10 |
--------------------------------------------------------------------------------
/example/src/assets/layouts/_content.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | max-width: $container-max-width;
4 | padding-left: $container-gutter;
5 | padding-right: $container-gutter;
6 | margin-left: auto;
7 | margin-right: auto;
8 | text-align: left;
9 | }
10 |
--------------------------------------------------------------------------------
/src/common/index.js:
--------------------------------------------------------------------------------
1 | export { default as galleryPropTypes } from './galleryPropTypes';
2 | export { default as galleryDefaultProps } from './galleryDefaultProps';
3 | export { default as imagePropTypes } from './imagePropTypes';
4 | export { default as imageDefaultProps } from './imageDefaultProps';
5 |
--------------------------------------------------------------------------------
/src/shapes/PhotoShape.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | export default PropTypes.shape({
4 | photo: PropTypes.string.isRequired,
5 | number: PropTypes.number,
6 | caption: PropTypes.string,
7 | subcaption: PropTypes.string,
8 | thumbnail: PropTypes.string,
9 | });
10 |
--------------------------------------------------------------------------------
/src/shapes/PhotosShape.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import PhotoShape from './PhotoShape';
4 |
5 | export default PropTypes.oneOfType([
6 | PropTypes.string.isRequired,
7 | PropTypes.arrayOf(PropTypes.string.isRequired),
8 | PropTypes.arrayOf(PhotoShape),
9 | ]);
10 |
--------------------------------------------------------------------------------
/example/src/pages/Examples/localization/es.js:
--------------------------------------------------------------------------------
1 | const noPhotosProvided = 'No hay fotos para mostrar';
2 | const showPhotoList = 'Mostrar miniaturas';
3 | const hidePhotoList = 'Ocultar miniaturas';
4 |
5 | export default {
6 | noPhotosProvided,
7 | showPhotoList,
8 | hidePhotoList,
9 | };
10 |
--------------------------------------------------------------------------------
/src/utils/calculateThumbnailsContainerDimension.js:
--------------------------------------------------------------------------------
1 | import {
2 | THUMBNAIL_WIDTH,
3 | THUMBNAIL_OFFSET,
4 | } from '../constants';
5 |
6 | export default function calculateThumbnailsContainerDimension(total) {
7 | return (THUMBNAIL_WIDTH * total) + ((THUMBNAIL_OFFSET * total) - THUMBNAIL_OFFSET);
8 | }
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | coverage
2 | docs
3 | example
4 | tests
5 | .editorconfig
6 | .eslintignore
7 | .eslintrc
8 | .gitignore
9 | .stylelintignore
10 | .stylelintrc
11 | .travis.yml
12 | babel.config.js
13 | CONTRIBUTING.md
14 | jest.config.js
15 | rollup.config.js
16 | TODO.md
17 | yarn.lock
18 | yarn-error.log
19 |
--------------------------------------------------------------------------------
/docs/components/Layout/index.js:
--------------------------------------------------------------------------------
1 | import Layout from './Layout';
2 | import LayoutContext, {
3 | LayoutContextProvider,
4 | LayoutContextConsumer,
5 | } from './context';
6 |
7 | export default Layout;
8 |
9 | export {
10 | LayoutContext,
11 | LayoutContextProvider,
12 | LayoutContextConsumer,
13 | };
14 |
--------------------------------------------------------------------------------
/docs/components/PhotoGrid/shuffle.js:
--------------------------------------------------------------------------------
1 | export default function shuffle(array) {
2 | let i = array.length - 1;
3 | for (; i > 0; i--) {
4 | const j = Math.floor(Math.random() * (i + 1));
5 | const temp = array[i];
6 | array[i] = array[j];
7 | array[j] = temp;
8 | }
9 | return array;
10 | }
11 |
--------------------------------------------------------------------------------
/example/src/components/PhotoGrid/shuffle.js:
--------------------------------------------------------------------------------
1 | export default function shuffle(array) {
2 | let i = array.length - 1;
3 | for (; i > 0; i--) {
4 | const j = Math.floor(Math.random() * (i + 1));
5 | const temp = array[i];
6 | array[i] = array[j];
7 | array[j] = temp;
8 | }
9 | return array;
10 | }
11 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from './App';
5 |
6 | import './assets/main.scss';
7 |
8 | /** The react-bnb-gallery style */
9 | import 'react-bnb-gallery/dist/style.css';
10 |
11 | ReactDOM.render( , document.getElementById('root'));
12 |
--------------------------------------------------------------------------------
/src/common/opacityValidation.js:
--------------------------------------------------------------------------------
1 | const MIN = 0;
2 |
3 | const MAX = 1;
4 |
5 | // eslint-disable-next-line consistent-return
6 | export default function opacityValidation(props, propName) {
7 | if (props[propName] < MIN || props[propName] > MAX) {
8 | return new Error('Invalid value for opacity');
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example/src/components/Section/component.scss:
--------------------------------------------------------------------------------
1 | /* Section component */
2 | .section {
3 | font-size: 16px;
4 | padding-top: 5em;
5 | padding-bottom: 5em;
6 | line-height: 1.45;
7 | }
8 |
9 | .section .content {
10 | max-width: 1200px;
11 | margin-left: auto;
12 | margin-right: auto;
13 | padding-left: 2em;
14 | padding-right: 2em;
15 | }
16 |
--------------------------------------------------------------------------------
/docs/components/Main/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const propTypes = {
5 | children: PropTypes.node.isRequired,
6 | };
7 |
8 | const Main = ({
9 | children,
10 | }) => (
11 |
12 | {children}
13 |
14 | );
15 |
16 | Main.propTypes = propTypes;
17 |
18 | export default Main;
19 |
--------------------------------------------------------------------------------
/src/utils/getPhrasePropTypes.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | export default function defaultPhrases() {
4 | return Object.keys(defaultPhrases)
5 | .reduce((phrases, key) => ({
6 | ...phrases,
7 | [key]: PropTypes.oneOfType([
8 | PropTypes.string,
9 | PropTypes.func,
10 | PropTypes.node,
11 | ]),
12 | }), {});
13 | }
14 |
--------------------------------------------------------------------------------
/docs/next.config.js:
--------------------------------------------------------------------------------
1 | const withMdxEnhanced = require('next-mdx-enhanced');
2 | const withMDX = require('@next/mdx');
3 | const withTM = require('next-transpile-modules');
4 |
5 | module.exports = withMdxEnhanced({
6 | layoutPath: 'layouts',
7 | extendFrontMatter: {
8 | phase: 'prebuild|loader|both',
9 | },
10 | })(withMDX(withTM({
11 | transpileModules: ['react-bnb-gallery'],
12 | })));
13 |
--------------------------------------------------------------------------------
/docs/components/Layout/context.js:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 |
3 | const LayoutContext = createContext({
4 | navigationOpened: false,
5 | });
6 |
7 | const LayoutContextProvider = LayoutContext.Provider;
8 | const LayoutContextConsumer = LayoutContext.Consumer;
9 |
10 | export default LayoutContext;
11 |
12 | export {
13 | LayoutContextProvider,
14 | LayoutContextConsumer,
15 | };
16 |
--------------------------------------------------------------------------------
/example/src/components/Container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const propTypes = {
5 | children: PropTypes.node.isRequired,
6 | };
7 |
8 | const Container = ({
9 | children,
10 | ...rest
11 | }) => (
12 |
13 | {children}
14 |
15 | );
16 |
17 | Container.propTypes = propTypes;
18 |
19 | export default Container;
20 |
--------------------------------------------------------------------------------
/src/utils/calculateThumbnailsOffset.js:
--------------------------------------------------------------------------------
1 | import {
2 | THUMBNAIL_WIDTH,
3 | THUMBNAIL_OFFSET,
4 | } from '../constants';
5 |
6 | export default function calculateThumbnailsOffset(current, bounding) {
7 | const half = (bounding.width / 2) - (THUMBNAIL_WIDTH / 2);
8 | const offset = ((current * THUMBNAIL_WIDTH) + (current * THUMBNAIL_OFFSET)) - half;
9 | return offset <= 0 ? Math.abs(offset) : (offset * -1);
10 | }
11 |
--------------------------------------------------------------------------------
/example/src/utils/withPrism.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Prism from 'prismjs';
3 |
4 | const withPrism = (WrappedComponent) => {
5 | class WithPrismHOC extends React.PureComponent {
6 | componentDidMount() {
7 | Prism.highlightAll();
8 | }
9 |
10 | render() {
11 | return (
12 |
13 | );
14 | }
15 | }
16 |
17 | return WithPrismHOC;
18 | };
19 |
20 | export default withPrism;
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # production
8 | build
9 | dist
10 | .rpt2_cache
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | # Ignore test-related files
21 | /coverage.data
22 | /coverage/
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
--------------------------------------------------------------------------------
/example/src/components/Heading/Heading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Spacing from '../../components/Spacing';
4 | import Rule from '../../components/Rule';
5 | import Title from '../../components/Title';
6 |
7 | const Heading = ({
8 | children,
9 | }) => (
10 |
11 |
12 | {children}
13 |
14 |
15 |
16 | );
17 |
18 | export default Heading;
19 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | #now
4 | now.json
5 |
6 | # dependencies
7 | /node_modules
8 | /.pnp
9 | .pnp.js
10 |
11 | # testing
12 | /coverage
13 |
14 | # production
15 | /build
16 | /dist
17 |
18 | # misc
19 | .DS_Store
20 | .env
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 |
26 | npm-debug.log*
27 | yarn-debug.log*
28 | yarn-error.log*
29 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | This file contains some TODOs for this repository.
4 |
5 | Please, feel free to push your ideas to improve this library.
6 |
7 | ## Done
8 |
9 | - [x] Implement a demo
10 | - [x] Implement better types checking & validations
11 | - [x] Implement better responsive visualization
12 | - [x] Implement Keyboard navigation
13 | - [x] Implement touch swipe
14 |
15 | ## To check
16 |
17 | - [ ] Allow Server Side Rendering
18 | - [ ] Implement more tests
19 | - [ ] Improve documentation
20 |
21 | ## Ideas
22 |
--------------------------------------------------------------------------------
/docs/components/Head/Head.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Head from 'next/head';
5 |
6 | const defaultProps = {};
7 |
8 | const propTypes = {
9 | title: PropTypes.string.isRequired,
10 | };
11 |
12 | const HtmlHead = ({
13 | title,
14 | }) => (
15 |
16 |
17 | {`${title} - bnbgallery`}
18 |
19 |
20 | );
21 |
22 | HtmlHead.defaultProps = defaultProps;
23 | HtmlHead.propTypes = propTypes;
24 |
25 | export default HtmlHead;
26 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | react-bnb-gallery
11 |
12 |
13 |
14 |
15 | You need to enable JavaScript to run this app.
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/utils/getPhotos.js:
--------------------------------------------------------------------------------
1 | import { defaultPhotoProps } from '../constants';
2 |
3 | export function processPhoto(photo, index) {
4 | const props = typeof photo === 'string'
5 | ? { number: (index + 1), photo }
6 | : { ...photo, number: (index + 1) };
7 |
8 | return {
9 | ...defaultPhotoProps,
10 | ...props,
11 | };
12 | }
13 |
14 | export default function getPhotos(photos) {
15 | const photosToProcess = Object.prototype.toString.call(photos) === '[object Array]' ? photos : [photos];
16 | return photosToProcess.map(processPhoto);
17 | }
18 |
--------------------------------------------------------------------------------
/src/common/galleryDefaultProps.js:
--------------------------------------------------------------------------------
1 | import noop from '../utils/noop';
2 | import defaultPhrases from '../defaultPhrases';
3 |
4 | import {
5 | DEFAULT_COLOR,
6 | FORWARDS,
7 | } from '../constants';
8 |
9 | export default {
10 | activePhotoIndex: 0,
11 | activePhotoPressed: noop,
12 | direction: FORWARDS,
13 | nextButtonPressed: noop,
14 | prevButtonPressed: noop,
15 | showThumbnails: true,
16 | photos: [],
17 | preloadSize: 5,
18 | wrap: false,
19 | phrases: defaultPhrases,
20 | light: false,
21 | backgroundColor: DEFAULT_COLOR,
22 | };
23 |
--------------------------------------------------------------------------------
/src/components/LoadingSpinner/LoadingSpinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { forbidExtraProps } from '../../common/prop-types';
5 |
6 | const propTypes = forbidExtraProps({
7 | show: PropTypes.bool,
8 | });
9 |
10 | const defaultProps = {
11 | show: true,
12 | };
13 |
14 | const LoadingSpinner = ({ show }) => (show && (
15 |
16 | ));
17 |
18 | LoadingSpinner.propTypes = propTypes;
19 | LoadingSpinner.defaultProps = defaultProps;
20 |
21 | export default LoadingSpinner;
22 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
32 | # Ignore next-mdx-enhanced cache directory
33 | .mdx-data
34 |
--------------------------------------------------------------------------------
/example/src/assets/components/_titles.scss:
--------------------------------------------------------------------------------
1 | .title {
2 | color: $heading-color;
3 | margin: $heading-spacing;
4 | line-height: $heading-line-height;
5 | font-weight: $heading-font-weight;
6 |
7 | &.title__h1 {
8 | font-size: $h1-font-size;
9 | }
10 |
11 | &.title__h2 {
12 | font-size: $h2-font-size;
13 | }
14 |
15 | &.title__h3 {
16 | font-size: $h3-font-size;
17 | }
18 |
19 | &.title__h4 {
20 | font-size: $h4-font-size;
21 | }
22 |
23 | &.title__h5 {
24 | font-size: $h5-font-size;
25 | }
26 |
27 | &.title__h6 {
28 | font-size: $h6-font-size;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Action } from './Anchor';
2 | export { default as Button } from './Button';
3 | export { default as Container } from './Container';
4 | export { default as Spacing } from './Spacing';
5 | export { default as Text } from './Text';
6 | export { default as Title } from './Title';
7 | export { default as PhotoGrid } from './PhotoGrid';
8 | export { default as GitHubButton } from './GitHubButton';
9 | export { default as Header } from './Header';
10 | export { default as Footer } from './Footer';
11 | export { default as Rule } from './Rule';
12 | export { default as Section } from './Section';
13 |
--------------------------------------------------------------------------------
/docs/css/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | html {
4 | @apply font-sans;
5 | @apply antialiased;
6 | @apply text-gray-900;
7 | @apply bg-gray-50;
8 | }
9 |
10 | .prose code::before,
11 | .prose code::after {
12 | content: none;
13 | display: none;
14 | }
15 |
16 | .prose code {
17 | @apply bg-gray-100;
18 | @apply rounded-md;
19 | @apply border;
20 | @apply border-gray-300;
21 | @apply px-1;
22 | @apply py-px;
23 | }
24 |
25 | .prose h3 code {
26 | @apply bg-transparent;
27 | @apply rounded-none;
28 | @apply border-none;
29 | @apply p-0;
30 | }
31 |
32 | @tailwind components;
33 |
34 | @tailwind utilities;
35 |
--------------------------------------------------------------------------------
/example/src/assets/main.scss:
--------------------------------------------------------------------------------
1 | /** The Inter UI font */
2 | @import url("https://rsms.me/inter/inter.css");
3 |
4 | /** Common styles */
5 | @import "./common/reset";
6 | @import "./common/variables";
7 | @import "./common/global";
8 |
9 | /** Components */
10 | @import "./components/buttons";
11 | @import "./components/titles";
12 | @import "./components/texts";
13 | @import "./components/tables";
14 | @import "./components/prism";
15 | @import "./components/photo-grid";
16 |
17 | /** Layouts */
18 | @import "./layouts/header";
19 | @import "./layouts/content";
20 | @import "./layouts/footer";
21 |
22 | /** Pages */
23 | @import "./pages/home";
24 |
--------------------------------------------------------------------------------
/docs/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const tailwindui = require('@tailwindcss/ui');
2 | const typography = require('@tailwindcss/typography');
3 | const defaultTheme = require('tailwindcss/defaultTheme');
4 |
5 | module.exports = {
6 | purge: [
7 | './layouts/**/*.js',
8 | './components/**/*.js',
9 | './pages/**/*.js',
10 | './pages/**/*.mdx',
11 | ],
12 | theme: {
13 | extend: {
14 | fontFamily: {
15 | sans: [
16 | 'Inter var',
17 | ...defaultTheme.fontFamily.sans,
18 | ],
19 | },
20 | },
21 | },
22 | variants: {},
23 | plugins: [
24 | tailwindui,
25 | typography,
26 | ],
27 | };
28 |
--------------------------------------------------------------------------------
/example/src/assets/components/_texts.scss:
--------------------------------------------------------------------------------
1 | .text {
2 | color: $text-color;
3 | font-size: $text-font-size;
4 | margin: $text-spacing;
5 | line-height: $text-line-height;
6 |
7 | &.text__inherit {
8 | font-size: inherit;
9 | line-height: inherit;
10 | margin-top: 0;
11 | color: inherit;
12 | }
13 |
14 | a {
15 | text-decoration: none;
16 | display: inline-flex;
17 | padding: $text-link-padding;
18 | background-color: $text-link-background;
19 | line-height: inherit;
20 | border-bottom: 1px solid;
21 |
22 | &:hover {
23 | background-color: $text-link-background-hover;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/src/assets/layouts/_footer.scss:
--------------------------------------------------------------------------------
1 | .footer {
2 | background-color: $footer-background;
3 | font-size: $footer-font-size;
4 | color: $footer-color;
5 | padding: $footer-padding;
6 | text-align: center;
7 | /** Sticky footer */
8 | margin-top: auto;
9 |
10 | a {
11 | background: none;
12 | color: $footer-link-color;
13 |
14 | &:hover {
15 | background: none;
16 | }
17 | }
18 |
19 | p {
20 | margin: 0 0 0.5rem;
21 | }
22 |
23 | .links {
24 | padding-top: 0.5em;
25 |
26 | a {
27 | display: inline-flex;
28 | margin: 0 0.35rem;
29 | font-weight: $font-weight-bold;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/docs/components/Sidebar/Sidebar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Navigation, { MenuShape } from '../Navigation';
4 |
5 | const defaultProps = {
6 | menu: null,
7 | };
8 |
9 | const propTypes = {
10 | menu: MenuShape,
11 | };
12 |
13 | const Sidebar = ({
14 | menu,
15 | }) => {
16 | if (!menu) {
17 | return null;
18 | }
19 |
20 | return (
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | Sidebar.defaultProps = defaultProps;
28 | Sidebar.propTypes = propTypes;
29 |
30 | export default Sidebar;
31 |
--------------------------------------------------------------------------------
/docs/pages/docs/help.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Help'
3 | layout: 'docs-page'
4 | ---
5 |
6 | import Link from 'next/link';
7 |
8 | ### Have a question?
9 |
10 | Follow the [installation](docs/installation) guide to get up and running quickly. Please do not use **Github issues** to report personal support requests.
11 |
12 | For any personal contact, please drop me a line to [pepalauisaac@gmail.com](mailto:pepalauisaac@gmail.com;).
13 |
14 | ### Found a bug?
15 |
16 | If you find a bug, please read the [Contribution Guildelines](https://github.com/peterpalau/react-bnb-gallery/blob/master/CONTRIBUTING.md) before you [report the issue](https://github.com/peterpalau/react-bnb-gallery/issues/new).
17 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | collectCoverage: true,
4 | moduleDirectories: ["node_modules", "src"],
5 | collectCoverageFrom : ["src/**/*.{js,jsx,mjs}"],
6 | moduleFileExtensions: ["js", "json", "jsx"],
7 | coverageDirectory: "coverage",
8 | transform: {"^.+\\.js?$": "babel-jest"},
9 | testMatch: ["**/tests/**/*.test.js?(x)"],
10 | setupFilesAfterEnv: ["/tests/setup.js"],
11 | modulePaths: ["/src","/node_modules"],
12 | moduleNameMapper: {
13 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js",
14 | "\\.(css|less|scss)$": "identity-obj-proxy"
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/docs/pages/_document.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable class-methods-use-this */
2 | import React from 'react';
3 |
4 | import Document, {
5 | Html,
6 | Head,
7 | Main,
8 | NextScript,
9 | } from 'next/document';
10 |
11 | class MyDocument extends Document {
12 | static async getInitialProps(ctx) {
13 | const initialProps = await Document.getInitialProps(ctx);
14 | return {
15 | ...initialProps,
16 | };
17 | }
18 |
19 | render() {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | export default MyDocument;
33 |
--------------------------------------------------------------------------------
/example/src/components/Text/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { forbidExtraProps } from 'airbnb-prop-types';
5 | import classnames from 'classnames';
6 |
7 | const propTypes = forbidExtraProps({
8 | className: PropTypes.string,
9 | children: PropTypes.node.isRequired,
10 | inherit: PropTypes.bool,
11 | });
12 |
13 | const defaultProps = {
14 | className: null,
15 | inherit: false,
16 | };
17 |
18 | const Text = ({
19 | className,
20 | children,
21 | inherit,
22 | }) => (
23 |
24 | {children}
25 |
26 | );
27 |
28 | Text.propTypes = propTypes;
29 | Text.defaultProps = defaultProps;
30 |
31 | export default Text;
32 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard",
3 | "rules": {
4 | "no-empty-source": null,
5 | "string-quotes": "double",
6 | "at-rule-no-unknown": [
7 | true,
8 | {
9 | "ignoreAtRules": [
10 | "extend",
11 | "at-root",
12 | "debug",
13 | "warn",
14 | "error",
15 | "if",
16 | "else",
17 | "for",
18 | "each",
19 | "while",
20 | "mixin",
21 | "include",
22 | "content",
23 | "return",
24 | "function",
25 | "tailwind",
26 | "apply",
27 | "responsive",
28 | "variants",
29 | "screen"
30 | ]
31 | }
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/example/src/assets/pages/_home.scss:
--------------------------------------------------------------------------------
1 | .intro {
2 | padding-top: 5em;
3 | padding-bottom: 5em;
4 | display: flex;
5 | height: auto;
6 | flex-direction: column;
7 | justify-content: center;
8 | text-align: center;
9 |
10 | .actions {
11 | display: flex;
12 | justify-content: center;
13 | }
14 |
15 | @media (max-width: 550px) {
16 | padding-top: 10em;
17 | padding-bottom: 6em;
18 |
19 | .actions {
20 | flex-direction: column;
21 |
22 | > div {
23 | margin-right: 0 !important;
24 | margin-left: 0 !important;
25 | padding: 10px 20px;
26 | }
27 |
28 | .button {
29 | width: 100%;
30 | margin-top: 0 !important;
31 | margin-bottom: 0 !important;
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/src/components/Title/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { forbidExtraProps, range } from 'airbnb-prop-types';
5 | import classnames from 'classnames';
6 |
7 | const propTypes = forbidExtraProps({
8 | children: PropTypes.node.isRequired,
9 | level: range(1, 7),
10 | });
11 |
12 | const defaultProps = {
13 | level: 2,
14 | };
15 |
16 | const Title = ({
17 | children,
18 | level,
19 | }) => {
20 | const Level = `h${level}`;
21 | return (
22 |
26 | {children}
27 |
28 | );
29 | };
30 |
31 | Title.propTypes = propTypes;
32 | Title.defaultProps = defaultProps;
33 |
34 | export default Title;
35 |
--------------------------------------------------------------------------------
/example/src/pages/License/License.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Container from '../../components/Container';
4 | import Heading from '../../components/Heading';
5 | import Text from '../../components/Text';
6 |
7 | const License = () => (
8 |
9 | License
10 | react-bnb-gallery is free to use for personal and commercial projects under the MIT license .
11 | Attribution is not required, but greatly appreciated. It does not have to be user-facing and can remain within the code.
12 |
13 | );
14 |
15 | export default License;
16 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "test": {
4 | "presets": [
5 | "@babel/preset-env",
6 | "@babel/preset-react",
7 | "airbnb"
8 | ]
9 | },
10 | "development": {
11 | "presets": [
12 | "@babel/preset-env",
13 | "@babel/preset-react",
14 | "airbnb"
15 | ]
16 | },
17 | "production": {
18 | "presets": [
19 | ["airbnb", {
20 | "removePropTypes": true
21 | }]
22 | ],
23 | }
24 | },
25 | "plugins": [
26 | "@babel/plugin-proposal-class-properties",
27 | "@babel/plugin-proposal-export-default-from",
28 | "@babel/plugin-syntax-dynamic-import",
29 | ["@babel/plugin-transform-object-assign", {
30 | "moduleSpecifier": "object.assign"
31 | }]
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/example/src/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Container from '../Container';
4 | import Text from '../Text';
5 |
6 | const Footer = () => (
7 |
16 | );
17 |
18 | export default Footer;
19 |
--------------------------------------------------------------------------------
/example/src/assets/components/_tables.scss:
--------------------------------------------------------------------------------
1 | .responsive-table {
2 | max-width: 100%;
3 | margin-bottom: 2em;
4 |
5 | @media (max-width: 767px) {
6 | overflow-x: auto;
7 | }
8 | }
9 |
10 | table {
11 | border-collapse: collapse;
12 | width: 100%;
13 | border: $table-border;
14 | margin-bottom: $table-spacing;
15 | background-color: $table-background-color;
16 | font-size: $table-font-size;
17 | line-height: $table-line-height;
18 | color: $table-color;
19 | box-shadow: $table-box-shadow;
20 |
21 | th,
22 | td {
23 | padding: $table-cell-padding;
24 | vertical-align: top;
25 | }
26 |
27 | thead th {
28 | vertical-align: bottom;
29 | }
30 |
31 | td {
32 | border-top: $table-cell-border;
33 | }
34 |
35 | tbody tr:nth-child(odd) {
36 | background-color: $table-odd-background;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/components/SidebarDocs/SidebarDocs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classnames from 'classnames';
4 |
5 | import Sidebar from '../Sidebar';
6 |
7 | import docsPages from '../../data/getDocsPages';
8 |
9 | const defaultProps = {
10 | isOpen: false,
11 | };
12 |
13 | const propTypes = {
14 | isOpen: PropTypes.bool,
15 | };
16 |
17 | const SidebarDocs = ({
18 | isOpen,
19 | }) => (
20 |
26 |
27 |
28 | );
29 |
30 | SidebarDocs.defaultProps = defaultProps;
31 | SidebarDocs.propTypes = propTypes;
32 |
33 | export default SidebarDocs;
34 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: npm-publish
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | npm-publish:
9 | name: npm-publish
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout repository
13 | uses: actions/checkout@master
14 | - name: Set up Node.js
15 | uses: actions/setup-node@master
16 | with:
17 | node-version: 12
18 | - run: yarn
19 | - run: yarn test
20 | - run: yarn build
21 | - name: Publish if version has been updated
22 | uses: pascalgn/npm-publish-action@4f4bf159e299f65d21cd1cbd96fc5d53228036df
23 | with:
24 | tag_name: "v%s"
25 | tag_message: "v%s"
26 | commit_pattern: "^Release (\\S+)"
27 | env:
28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
30 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "export": "yarn build && next export"
10 | },
11 | "dependencies": {
12 | "@mdx-js/loader": "^1.6.6",
13 | "@next/mdx": "^9.4.4",
14 | "@tailwindcss/typography": "^0.1.2",
15 | "@tailwindcss/ui": "^0.3.0",
16 | "autoprefixer": "^9.8.4",
17 | "classnames": "^2.2.6",
18 | "next": "9.4.4",
19 | "netlify-plugin-cache-nextjs": "^1.4.0",
20 | "next-mdx-enhanced": "^3.0.0",
21 | "next-transpile-modules": "^3.3.0",
22 | "postcss-nesting": "^7.0.1",
23 | "prop-types": "^15.7.2",
24 | "react": "16.13.1",
25 | "react-bnb-gallery": "^1.4.4",
26 | "react-dom": "16.13.1",
27 | "tailwindcss": "^1.4.6"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/docs/data/getDocsPages.jsx:
--------------------------------------------------------------------------------
1 | import { frontMatter as installation } from '../pages/docs/installation.mdx';
2 | import { frontMatter as options } from '../pages/docs/options.mdx';
3 | import { frontMatter as license } from '../pages/docs/license.mdx';
4 | import { frontMatter as help } from '../pages/docs/help.mdx';
5 |
6 | export default [
7 | {
8 | slug: 'documentation',
9 | title: 'Documentation',
10 | items: [
11 | {
12 | slug: 'installation',
13 | url: '/docs/installation',
14 | ...installation,
15 | },
16 | {
17 | slug: 'options',
18 | url: '/docs/options',
19 | ...options,
20 | },
21 | ],
22 | },
23 | {
24 | slug: 'license',
25 | url: '/docs/license',
26 | ...license,
27 | },
28 | {
29 | slug: 'help',
30 | url: '/docs/help',
31 | ...help,
32 | },
33 | ];
34 |
--------------------------------------------------------------------------------
/example/src/components/Section/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Container from '../Container';
5 | import Rule from '../Rule';
6 | import Title from '../Title';
7 |
8 | import './component.scss';
9 |
10 | const propTypes = {
11 | section: PropTypes.object.isRequired,
12 | };
13 |
14 | const defaultProps = {
15 | section: null,
16 | };
17 |
18 | const Section = ({
19 | section,
20 | children,
21 | }) => (
22 |
23 |
24 |
25 | {section.title && {section.title} }
26 |
27 | {children}
28 |
29 |
30 |
31 | );
32 |
33 | Section.propTypes = propTypes;
34 | Section.defaultProps = defaultProps;
35 |
36 | export default Section;
37 |
--------------------------------------------------------------------------------
/tests/components/Gallery.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import Gallery from '../../src/components/Gallery';
5 | import Caption from '../../src/components/Caption';
6 |
7 | import photos from '../test-photos';
8 |
9 | describe('Gallery', () => {
10 | describe('#render', () => {
11 | it('renders ', () => {
12 | const wrapper = shallow((
13 |
17 | ));
18 | expect(wrapper.find(Caption)).toHaveLength(1);
19 | });
20 | it(' is not rendered if props.showThumbnails === false', () => {
21 | const wrapper = shallow((
22 |
26 | ));
27 | expect(wrapper.find(Caption)).toHaveLength(0);
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/src/common/galleryPropTypes.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 |
3 | import {
4 | forbidExtraProps,
5 | nonNegativeInteger,
6 | } from './prop-types';
7 |
8 | import SlideDirectionShape from '../shapes/SlideDirectionShape';
9 | import PhotosShape from '../shapes/PhotosShape';
10 |
11 | import defaultPhrases from '../defaultPhrases';
12 | import getPhrasePropTypes from '../utils/getPhrasePropTypes';
13 |
14 | export default forbidExtraProps({
15 | activePhotoIndex: nonNegativeInteger,
16 | activePhotoPressed: PropTypes.func,
17 | direction: SlideDirectionShape,
18 | nextButtonPressed: PropTypes.func,
19 | prevButtonPressed: PropTypes.func,
20 | showThumbnails: PropTypes.bool,
21 | photos: PhotosShape,
22 | preloadSize: nonNegativeInteger,
23 | wrap: PropTypes.bool,
24 | phrases: PropTypes.shape(getPhrasePropTypes(defaultPhrases)),
25 | light: PropTypes.bool,
26 | backgroundColor: PropTypes.string,
27 | });
28 |
--------------------------------------------------------------------------------
/example/src/pages/Examples/section.css:
--------------------------------------------------------------------------------
1 | /* Examples section */
2 | #examples > .container {
3 | max-width: 1200px !important;
4 | }
5 |
6 | #examples pre[class*="language-"] {
7 | padding: 0 !important;
8 | }
9 |
10 | .demos {
11 | display: -ms-flexbox;
12 | display: flex;
13 | -ms-flex-wrap: wrap;
14 | flex-wrap: wrap;
15 | margin-right: -15px;
16 | margin-left: -15px;
17 | }
18 |
19 | .demos .demo {
20 | -ms-flex-preferred-size: 0;
21 | flex-basis: 0;
22 | -ms-flex-positive: 1;
23 | flex-grow: 1;
24 | max-width: 100%;
25 | padding-left: 15px;
26 | padding-right: 15px;
27 | margin-top: 15px;
28 | margin-bottom: 15px;
29 | text-align: left;
30 | display: -ms-flexbox;
31 | display: flex;
32 | -ms-flex-direction: column;
33 | flex-direction: column;
34 | }
35 |
36 | .demos .demo .button {
37 | margin-top: auto;
38 | }
39 |
40 | @media (min-width: 991px) {
41 | .demos .demo {
42 | -ms-flex: 0 0 33.333333%;
43 | flex: 0 0 33.333333%;
44 | max-width: 33.333333%;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/example/src/components/Anchor/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './component.scss';
5 |
6 | const ANCHOR_ICON = 'M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z';
7 |
8 | const propTypes = {
9 | id: PropTypes.string.isRequired
10 | };
11 |
12 | const Anchor = ({
13 | id,
14 | }) => (
15 |
20 |
26 |
27 |
28 |
29 | );
30 |
31 | Anchor.propTypes = propTypes;
32 |
33 | export default Anchor;
34 |
--------------------------------------------------------------------------------
/src/utils/calculateThumbnailsLeftScroll.js:
--------------------------------------------------------------------------------
1 | import calculateThumbnailsContainerDimension from './calculateThumbnailsContainerDimension';
2 |
3 | import {
4 | THUMBNAIL_WIDTH,
5 | THUMBNAIL_OFFSET,
6 | } from '../constants';
7 |
8 | export default function calculateThumbnailsLeftScroll(current, total, bounding) {
9 | const half = (bounding.width / 2) - (THUMBNAIL_WIDTH / 2);
10 | const thumbnailsOffset = ((current * THUMBNAIL_WIDTH) + (current * THUMBNAIL_OFFSET)) - half;
11 | let calculatedScrollLeft = 0;
12 |
13 | if (thumbnailsOffset < 0) {
14 | return calculatedScrollLeft;
15 | }
16 |
17 | const thumbnailsPerRow = bounding.width / (THUMBNAIL_WIDTH + THUMBNAIL_OFFSET);
18 | const thumbnailsHalf = Math.round(thumbnailsPerRow / 2);
19 | const thumbnailsLeft = total - (current + 1);
20 |
21 | if (thumbnailsLeft < thumbnailsHalf) {
22 | calculatedScrollLeft = calculateThumbnailsContainerDimension(total) - bounding.width;
23 | } else {
24 | calculatedScrollLeft = thumbnailsOffset;
25 | }
26 |
27 | return -Math.abs(calculatedScrollLeft);
28 | }
29 |
--------------------------------------------------------------------------------
/docs/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/example/src/assets/common/_global.scss:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: $font-family-sans-serif;
3 | line-height: 1.15;
4 | height: 100%;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | display: flex;
10 | flex-direction: column;
11 | height: 100%;
12 | }
13 |
14 | a {
15 | color: $link-color;
16 | }
17 |
18 | a:hover {
19 | color: $link-hover-color;
20 | }
21 |
22 | code {
23 | background: $code-background-color;
24 | font-family: $code-font-family;
25 | text-align: $code-text-align;
26 | font-weight: $code-font-weight;
27 | white-space: pre;
28 | word-spacing: normal;
29 | word-break: normal;
30 | word-wrap: normal;
31 | line-height: $code-line-height;
32 | tab-size: $code-tab-size;
33 | hyphens: none;
34 |
35 | &.data-type {
36 | background-color: #E6FFFA;
37 | color: #2F855A;
38 | padding: 0.35rem 0.82rem;
39 | font-weight: 400;
40 | border-radius: 10em;
41 | }
42 | }
43 |
44 | #root {
45 | display: flex;
46 | width: 100%;
47 | height: 100%;
48 | flex-direction: column;
49 | }
50 |
51 | .title + .text {
52 | margin-top: 0 !important;
53 | }
54 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const DIRECTION_PREV = 'prev';
2 | export const DIRECTION_NEXT = 'next';
3 |
4 | export const POINTER_TYPE_TOUCH = 'touch';
5 | export const POINTER_TYPE_PEN = 'pen';
6 |
7 | export const FORWARDS = 'forwards';
8 | export const BACKWARDS = 'backwards';
9 |
10 | export const ARROW_LEFT_KEYCODE = 37;
11 | export const ARROW_RIGHT_KEYCODE = 39;
12 | export const ESC_KEYCODE = 27;
13 | export const ENTER_KEYCODE = 13;
14 | export const SPACE_KEYCODE = 32;
15 |
16 | export const TOUCHEVENT_COMPAT_WAIT = 500;
17 | export const SWIPE_THRESHOLD = 40;
18 |
19 | export const THUMBNAIL_WIDTH = 58;
20 | export const THUMBNAIL_HEIGHT = 50;
21 | export const THUMBNAIL_OFFSET = 8;
22 |
23 | export const DEFAULT_OPACITY = 1;
24 | export const DEFAULT_COLOR = 'rgba(0,0,0,1)';
25 | export const NORMAL_COLOR = 'rgba(255,255,255,1)';
26 | export const INVERSE_COLOR = 'rgba(1,1,1,1)';
27 | export const DEFAULT_Z_INDEX = 2000;
28 |
29 | export const defaultPhotoProps = {
30 | photo: undefined,
31 | number: undefined,
32 | caption: undefined,
33 | subcaption: undefined,
34 | thumbnail: undefined,
35 | };
36 |
--------------------------------------------------------------------------------
/example/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Home from './Home';
4 | import GettingStarted from './GettingStarted';
5 | import Options from './Options';
6 | import Examples from './Examples';
7 | import License from './License';
8 | import Help from './Help';
9 |
10 | export default [{
11 | id: 'home',
12 | title: 'Home',
13 | disabled: false,
14 | isDefault: true,
15 | getComponent: () =>
16 | }, {
17 | id: 'getting-started',
18 | title: 'Getting Started',
19 | disabled: false,
20 | isDefault: false,
21 | getComponent: () =>
22 | }, {
23 | id: 'options',
24 | title: 'Options',
25 | disabled: false,
26 | isDefault: false,
27 | getComponent: () =>
28 | }, {
29 | id: 'examples',
30 | title: 'Examples',
31 | disabled: true,
32 | isDefault: false,
33 | getComponent: () =>
34 | }, {
35 | id: 'license',
36 | title: 'License',
37 | disabled: false,
38 | isDefault: false,
39 | getComponent: () =>
40 | }, {
41 | id: 'help',
42 | title: 'Help',
43 | disabled: false,
44 | isDefault: false,
45 | getComponent: () =>
46 | }];
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Pedro Enrique Palau
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/NextButton/NextButton.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Control from '../Control';
5 |
6 | import { forbidExtraProps } from '../../common/prop-types';
7 | import noop from '../../utils/noop';
8 |
9 | const NEXT_ARROW = 'm4.29 1.71a1 1 0 1 1 1.42-1.41l8 8a1 1 0 0 1 0 1.41l-8 8a1 1 0 1 1 -1.42-1.41l7.29-7.29z';
10 |
11 | const propTypes = forbidExtraProps({
12 | onPress: PropTypes.func,
13 | disabled: PropTypes.bool,
14 | light: PropTypes.bool,
15 | });
16 |
17 | const defaultProps = {
18 | onPress: noop,
19 | disabled: false,
20 | light: false,
21 | };
22 |
23 | class NextButton extends PureComponent {
24 | render() {
25 | const {
26 | onPress,
27 | disabled,
28 | light,
29 | } = this.props;
30 |
31 | return (
32 |
39 | );
40 | }
41 | }
42 |
43 | NextButton.propTypes = propTypes;
44 | NextButton.defaultProps = defaultProps;
45 |
46 | export default NextButton;
47 |
--------------------------------------------------------------------------------
/src/components/PrevButton/PrevButton.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Control from '../Control';
5 |
6 | import { forbidExtraProps } from '../../common/prop-types';
7 | import noop from '../../utils/noop';
8 |
9 | const PREV_ARROW = 'm13.7 16.29a1 1 0 1 1 -1.42 1.41l-8-8a1 1 0 0 1 0-1.41l8-8a1 1 0 1 1 1.42 1.41l-7.29 7.29z';
10 |
11 | const propTypes = forbidExtraProps({
12 | onPress: PropTypes.func,
13 | disabled: PropTypes.bool,
14 | light: PropTypes.bool,
15 | });
16 |
17 | const defaultProps = {
18 | onPress: noop,
19 | disabled: false,
20 | light: false,
21 | };
22 |
23 | class PrevButton extends PureComponent {
24 | render() {
25 | const {
26 | onPress,
27 | disabled,
28 | light,
29 | } = this.props;
30 |
31 | return (
32 |
39 | );
40 | }
41 | }
42 |
43 | PrevButton.propTypes = propTypes;
44 | PrevButton.defaultProps = defaultProps;
45 |
46 | export default PrevButton;
47 |
--------------------------------------------------------------------------------
/tests/components/ReactBnbGallery.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jest/expect-expect */
2 | import React from 'react';
3 | import { shallow, mount } from 'enzyme';
4 |
5 | import ReactBnbGallery from '../../src/ReactBnbGallery';
6 | import Gallery from '../../src/components/Gallery';
7 |
8 | import photos from '../test-photos';
9 |
10 | // See https://github.com/focus-trap/focus-trap-react/issues/24#issuecomment-586034017
11 | jest.mock('focus-trap', () => {
12 | const trap = {
13 | activate: () => trap,
14 | deactivate: () => trap,
15 | pause: () => {},
16 | unpause: () => {},
17 | };
18 | return () => trap;
19 | });
20 |
21 | describe('ReactBnbGallery', () => {
22 | describe('#render', () => {
23 | it('unmounts', () => {
24 | const wrapper = mount((
25 |
26 | ));
27 | wrapper.setProps({ show: true });
28 | wrapper.unmount();
29 | });
30 | it('renders ', () => {
31 | const wrapper = shallow((
32 |
36 | )).dive();
37 | expect(wrapper.find(Gallery)).toHaveLength(1);
38 | });
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/docs/pages/_app.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/forbid-prop-types */
2 | /* eslint-disable react/jsx-props-no-spreading */
3 | import React from 'react';
4 | import PropTypes from 'prop-types';
5 |
6 | import Head from 'next/head';
7 |
8 | import 'react-bnb-gallery/dist/style.css';
9 | import '../css/styles.css';
10 |
11 | const defaultProps = {
12 | pageProps: {},
13 | };
14 |
15 | const propTypes = {
16 | Component: PropTypes.any.isRequired,
17 | pageProps: PropTypes.object,
18 | };
19 |
20 | const App = ({
21 | Component,
22 | pageProps,
23 | }) => (
24 | <>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
37 | >
38 | );
39 |
40 | App.defaultProps = defaultProps;
41 | App.propTypes = propTypes;
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://peterpalau.github.io/react-bnb-gallery/",
6 | "keywords": [
7 | "react",
8 | "gallery",
9 | "photos",
10 | "carousel",
11 | "image gallery",
12 | "photo gallery",
13 | "responsive",
14 | "slider",
15 | "component",
16 | "modal gallery"
17 | ],
18 | "dependencies": {
19 | "airbnb-prop-types": "^2.13.2",
20 | "classnames": "^2.2.6",
21 | "node-sass": "^4.14.1",
22 | "prismjs": "^1.23.0",
23 | "prop-types": "^15.6.2",
24 | "react": "^16.8.5",
25 | "react-bnb-gallery": "file:..",
26 | "react-dom": "^16.8.5",
27 | "react-router-dom": "^5.0.1",
28 | "react-scripts": "^3.1.2",
29 | "react-sizeme": "^2.5.2"
30 | },
31 | "optionalDependencies": {
32 | "typescript": "^3.4.5"
33 | },
34 | "scripts": {
35 | "start": "react-scripts start",
36 | "build": "react-scripts build",
37 | "test": "react-scripts test",
38 | "eject": "react-scripts eject"
39 | },
40 | "eslintConfig": {
41 | "extends": "react-app"
42 | },
43 | "browserslist": [
44 | ">0.2%",
45 | "not dead",
46 | "not ie <= 11",
47 | "not op_mini all"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "babel-eslint",
4 | "extends": [
5 | "airbnb",
6 | "plugin:jest/recommended"
7 | ],
8 | "parserOptions": {
9 | "ecmaFeatures": {
10 | "globalReturn": true,
11 | "generators": false,
12 | "objectLiteralDuplicateProperties": false,
13 | "experimentalObjectRestSpread": true,
14 | "jsx": true
15 | },
16 | "ecmaVersion": 2017,
17 | "sourceType": "module"
18 | },
19 | "plugins": [
20 | "import",
21 | "react",
22 | "jsx-a11y",
23 | "jest"
24 | ],
25 | "env": {
26 | "browser": true,
27 | "node": true,
28 | "jest/globals": true
29 | },
30 | "rules": {
31 | "linebreak-style": 0,
32 | "class-methods-use-this": [2, { "exceptMethods": ["processPhoto"] }],
33 | "react/forbid-foreign-prop-types": 2,
34 | "jsx-a11y/click-events-have-key-events": 1,
35 | "jsx-a11y/anchor-is-valid": 0,
36 | "react/no-did-mount-set-state": 0,
37 | "no-restricted-imports": 0,
38 | "react/jsx-one-expression-per-line": 1,
39 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
40 | },
41 | "settings": {
42 | "propWrapperFunctions": [
43 | "forbidExtraProps",
44 | "exact",
45 | "Object.freeze"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on Vercel
27 |
28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/docs/components/Navigation/SubMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Link from 'next/link';
5 |
6 | export const ItemsShape = PropTypes.arrayOf(
7 | PropTypes.shape({
8 | slug: PropTypes.string.isRequired,
9 | data: PropTypes.shape({
10 | title: PropTypes.string.isRequired,
11 | }),
12 | url: PropTypes.string.isRequired,
13 | }),
14 | );
15 |
16 | const defaultProps = {
17 | items: [],
18 | };
19 |
20 | const propTypes = {
21 | items: ItemsShape,
22 | };
23 |
24 | const SubMenu = ({
25 | items,
26 | }) => {
27 | if (items.length === 0) {
28 | return null;
29 | }
30 |
31 | return (
32 |
33 | {
34 | items.map(({
35 | slug,
36 | url,
37 | title,
38 | }) => (
39 |
40 |
41 |
42 | {title}
43 |
44 |
45 |
46 | ))
47 | }
48 |
49 | );
50 | };
51 |
52 | SubMenu.defaultProps = defaultProps;
53 | SubMenu.propTypes = propTypes;
54 |
55 | export default SubMenu;
56 |
--------------------------------------------------------------------------------
/example/src/pages/Help/Help.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Container from '../../components/Container';
4 | import Heading from '../../components/Heading';
5 | import Text from '../../components/Text';
6 | import Title from '../../components/Title';
7 |
8 | const Help = () => (
9 |
10 |
11 |
Help
12 |
Have a question?
13 |
Follow the quick start guide on GitHub to get up and running quickly. Please do not use Github Issues to report personal support requests.
14 |
For any personal contact, please drop me a line to pepalauisaac@gmail.com .
15 |
Found a bug?
16 |
If you find a bug, please read the Contribution Guildelines before you report the issue .
17 |
18 |
19 | );
20 |
21 | export default Help;
22 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import url from '@rollup/plugin-url';
3 | import commonjs from '@rollup/plugin-commonjs';
4 | import external from 'rollup-plugin-peer-deps-external';
5 | import resolve from 'rollup-plugin-node-resolve';
6 | import scss from 'rollup-plugin-scss';
7 | import stylelint from 'rollup-plugin-stylelint';
8 | import svgr from '@svgr/rollup';
9 |
10 | import pkg from './package.json';
11 |
12 | const createConfig = (output) => ({
13 | input: 'src/index.js',
14 | output: Object.assign.call({
15 | sourcemap: true,
16 | }, output),
17 | plugins: [
18 | external({
19 | includeDependencies: true,
20 | }),
21 | scss({
22 | modules: true,
23 | sourcemap: false,
24 | output: 'dist/style.css',
25 | }),
26 | url(),
27 | svgr(),
28 | babel({
29 | babelHelpers: 'runtime',
30 | exclude: 'node_modules/**',
31 | }),
32 | resolve(),
33 | commonjs(),
34 | stylelint({
35 | fix: false,
36 | include: ['src/scss/**.scss'],
37 | syntax: 'scss',
38 | quiet: false,
39 | }),
40 | ],
41 | });
42 |
43 | export default [
44 | createConfig({
45 | file: pkg.main,
46 | format: 'cjs',
47 | exports: 'named',
48 | }),
49 | createConfig({
50 | file: pkg.module,
51 | format: 'esm',
52 | exports: 'named',
53 | }),
54 | ];
55 |
--------------------------------------------------------------------------------
/example/src/assets/common/normalize/_variables.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Variables
3 | //
4 | // You can override the default values by setting the variables in your Sass
5 | // before importing the normalize-scss library.
6 |
7 | // The font size set on the root html element.
8 | $base-font-size: 16px !default;
9 |
10 | // The base line height determines the basic unit of vertical rhythm.
11 | $base-line-height: 24px !default;
12 |
13 | // The length unit in which to output vertical rhythm values.
14 | // Supported values: px, em, rem.
15 | $base-unit: 'em' !default;
16 |
17 | // The default font family.
18 | $base-font-family: null !default;
19 |
20 | // The font sizes for h1-h6.
21 | $h1-font-size: 2 * $base-font-size !default;
22 | $h2-font-size: 1.5 * $base-font-size !default;
23 | $h3-font-size: 1.17 * $base-font-size !default;
24 | $h4-font-size: 1 * $base-font-size !default;
25 | $h5-font-size: 0.83 * $base-font-size !default;
26 | $h6-font-size: 0.67 * $base-font-size !default;
27 |
28 | // The amount lists and blockquotes are indented.
29 | $indent-amount: 40px !default;
30 |
31 | // The following variable controls whether normalize-scss will output
32 | // font-sizes, line-heights and block-level top/bottom margins that form a basic
33 | // vertical rhythm on the page, which differs from the original Normalize.css.
34 | // However, changing any of the variables above will cause
35 | // $normalize-vertical-rhythm to be automatically set to true.
36 | $normalize-vertical-rhythm: false !default;
37 |
--------------------------------------------------------------------------------
/example/src/components/Spacing/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { forbidExtraProps } from 'airbnb-prop-types';
5 |
6 | import { units } from './utils';
7 |
8 | const propTypes = forbidExtraProps({
9 | children: PropTypes.node,
10 | vertical: PropTypes.number,
11 | horizontal: PropTypes.number,
12 | left: PropTypes.number,
13 | right: PropTypes.number,
14 | top: PropTypes.number,
15 | bottom: PropTypes.number,
16 | });
17 |
18 | const defaultProps = {
19 | vertical: 0,
20 | horizontal: 0,
21 | left: 0,
22 | right: 0,
23 | top: 0,
24 | bottom: 0,
25 | };
26 |
27 | const Spacing = ({
28 | children,
29 | vertical,
30 | horizontal,
31 | left,
32 | right,
33 | top,
34 | bottom,
35 | }) => {
36 | let style = {};
37 |
38 | // left margin
39 | if (left || horizontal) {
40 | style.marginLeft = units(left || horizontal);
41 | }
42 |
43 | // right margin
44 | if (right || horizontal) {
45 | style.marginRight = units(right || horizontal);
46 | }
47 |
48 | // top margin
49 | if (top || vertical) {
50 | style.marginTop = units(top || vertical);
51 | }
52 |
53 | // bottom margin
54 | if (bottom || vertical) {
55 | style.marginBottom = units(bottom || vertical);
56 | }
57 |
58 | return (
59 |
60 | {children}
61 |
62 | );
63 | };
64 |
65 | Spacing.propTypes = propTypes;
66 | Spacing.defaultProps = defaultProps;
67 |
68 | export default Spacing;
69 |
--------------------------------------------------------------------------------
/src/components/TogglePhotoList/TogglePhotoList.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classnames from 'classnames';
5 |
6 | import defaultPhrases from '../../defaultPhrases';
7 | import getPhrasePropTypes from '../../utils/getPhrasePropTypes';
8 |
9 | import { forbidExtraProps } from '../../common/prop-types';
10 | import noop from '../../utils/noop';
11 |
12 | const propTypes = forbidExtraProps({
13 | isOpened: PropTypes.bool,
14 | onPress: PropTypes.func,
15 | phrases: PropTypes.shape(getPhrasePropTypes(defaultPhrases)),
16 | });
17 |
18 | const defaultProps = {
19 | isOpened: true,
20 | onPress: noop,
21 | phrases: defaultPhrases,
22 | };
23 |
24 | class TogglePhotoList extends PureComponent {
25 | render() {
26 | const {
27 | isOpened,
28 | onPress,
29 | phrases: {
30 | showPhotoList: showLabel,
31 | hidePhotoList: hideLabel,
32 | },
33 | } = this.props;
34 |
35 | const label = isOpened ? hideLabel : showLabel;
36 |
37 | const className = classnames(
38 | 'gallery-thumbnails--toggle',
39 | isOpened ? 'hide' : 'open',
40 | );
41 |
42 | return (
43 |
48 | {label}
49 |
50 | );
51 | }
52 | }
53 |
54 | TogglePhotoList.propTypes = propTypes;
55 | TogglePhotoList.defaultProps = defaultProps;
56 |
57 | export default TogglePhotoList;
58 |
--------------------------------------------------------------------------------
/example/src/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link } from "react-router-dom";
3 |
4 | import Container from '../Container';
5 | import GitHubButton from '../GitHubButton';
6 |
7 | import sections from '../../pages';
8 |
9 | import { ReactComponent as Logo } from './logo.svg';
10 |
11 | class Header extends Component {
12 | renderMenu() {
13 | const items = sections.map(section => {
14 | if (!section.disabled) {
15 | return (
16 |
17 |
18 | {section.title}
19 |
20 |
21 | );
22 | } else {
23 | return null;
24 | }
25 | });
26 |
27 | return (
28 |
31 | );
32 | }
33 |
34 | render() {
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {this.renderMenu()}
44 |
45 |
46 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | export default Header;
59 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 | import { HashRouter, Route } from 'react-router-dom';
3 |
4 | import pages from './pages';
5 |
6 | import {
7 | Container,
8 | Header,
9 | Footer,
10 | } from './components';
11 |
12 | class App extends Component {
13 | renderRoute({
14 | id,
15 | disabled,
16 | getComponent,
17 | }, isDefault) {
18 | if (!disabled) {
19 | if (isDefault) {
20 | return (
21 |
27 | );
28 | }
29 | else {
30 | return (
31 |
37 | );
38 | }
39 | }
40 | }
41 |
42 | renderRoutes() {
43 | const routes = [];
44 |
45 | pages.forEach((route) => {
46 | const { isDefault } = route;
47 | if (isDefault) {
48 | routes.push(this.renderRoute(route, true));
49 | }
50 | routes.push(this.renderRoute(route));
51 | });
52 |
53 | return routes;
54 | }
55 |
56 | render() {
57 | return (
58 |
59 |
60 |
61 |
62 | {this.renderRoutes()}
63 |
64 |
65 |
66 |
67 | );
68 | }
69 | }
70 |
71 | export default App;
72 |
--------------------------------------------------------------------------------
/docs/pages/docs/license.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'License'
3 | layout: 'docs-page'
4 | ---
5 |
6 | **BnbGallery** is free to use for personal and commercial projects under the [MIT license](https://github.com/peterpalau/react-bnb-gallery/blob/master/LICENSE).
7 |
8 | Attribution is not required, but greatly appreciated.
9 |
10 | It does not have to be user-facing and can remain within the code.
11 |
12 | ```
13 | The MIT License (MIT)
14 |
15 | Copyright (c) Pedro Enrique Palau
16 |
17 | Permission is hereby granted, free of charge, to any person obtaining a copy
18 | of this software and associated documentation files (the "Software"), to deal
19 | in the Software without restriction, including without limitation the rights
20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 | copies of the Software, and to permit persons to whom the Software is
22 | furnished to do so, subject to the following conditions:
23 |
24 | The above copyright notice and this permission notice shall be included in all
25 | copies or substantial portions of the Software.
26 |
27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 | SOFTWARE.
34 | ```
--------------------------------------------------------------------------------
/example/src/components/GitHubButton/GitHubButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { forbidExtraProps } from 'airbnb-prop-types';
5 |
6 | const propTypes = forbidExtraProps({
7 | user: PropTypes.string.isRequired,
8 | repo: PropTypes.string.isRequired,
9 | });
10 |
11 | const GitHubButton = ({
12 | user,
13 | repo,
14 | }) => {
15 | const url = `https://github.com/${user}/${repo}`;
16 | const label = `Star ${user}/${repo} on GitHub`;
17 |
18 | return (
19 |
25 |
32 |
36 |
37 |
38 | )
39 | };
40 |
41 | GitHubButton.propTypes = propTypes;
42 |
43 | export default GitHubButton;
44 |
--------------------------------------------------------------------------------
/src/components/CloseButton/CloseButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { forbidExtraProps } from '../../common/prop-types';
5 | import noop from '../../utils/noop';
6 |
7 | import {
8 | NORMAL_COLOR,
9 | INVERSE_COLOR,
10 | } from '../../constants';
11 |
12 | const CLOSE_PATH = 'm23.25 24c-.19 0-.38-.07-.53-.22l-10.72-10.72-10.72 10.72c-.29.29-.77.29-1.06 0s-.29-.77 0-1.06l10.72-10.72-10.72-10.72c-.29-.29-.29-.77 0-1.06s.77-.29 1.06 0l10.72 10.72 10.72-10.72c.29-.29.77-.29 1.06 0s .29.77 0 1.06l-10.72 10.72 10.72 10.72c.29.29.29.77 0 1.06-.15.15-.34.22-.53.22';
13 |
14 | const buttonStyle = {
15 | height: '2em',
16 | width: '2em',
17 | display: 'block',
18 | fill: NORMAL_COLOR,
19 | };
20 |
21 | const buttonStyleLight = {
22 | fill: INVERSE_COLOR,
23 | };
24 |
25 | const propTypes = forbidExtraProps({
26 | onPress: PropTypes.func,
27 | light: PropTypes.bool,
28 | });
29 |
30 | const defaultProps = {
31 | onPress: noop,
32 | light: false,
33 | };
34 |
35 | const CloseButton = ({
36 | onPress,
37 | light,
38 | }) => (
39 |
45 |
54 |
55 |
56 |
57 | );
58 |
59 | CloseButton.propTypes = propTypes;
60 | CloseButton.defaultProps = defaultProps;
61 |
62 | export default CloseButton;
63 |
--------------------------------------------------------------------------------
/tests/utils.test.js:
--------------------------------------------------------------------------------
1 | import calculateThumbnailsOffset from '../src/utils/calculateThumbnailsOffset';
2 | import calculateThumbnailsContainerDimension from '../src/utils/calculateThumbnailsContainerDimension';
3 |
4 | import {
5 | THUMBNAIL_WIDTH,
6 | } from '../src/constants';
7 |
8 | describe('the calculateThumbnailsOffset function', () => {
9 | it('offset with current 0 and width of 100', () => {
10 | expect(calculateThumbnailsOffset(0, { width: 100 })).toBe(21);
11 | });
12 | it('offset with current 0 and width of 200', () => {
13 | expect(calculateThumbnailsOffset(0, { width: 100 })).toBe(21);
14 | });
15 | it('offset with current 1 and width of 100', () => {
16 | expect(calculateThumbnailsOffset(1, { width: 100 })).toBe(-45);
17 | });
18 | it('offset with current 1 and width of 175', () => {
19 | expect(calculateThumbnailsOffset(1, { width: 175 })).toBe(-7.5);
20 | });
21 | it('offset with current 100 and width of 999999', () => {
22 | expect(calculateThumbnailsOffset(100, { width: 999999 })).toBe(493370.5);
23 | });
24 | });
25 |
26 | describe('the calculateThumbnailsContainerDimension function', () => {
27 | it('dimension with total of 1', () => {
28 | expect(calculateThumbnailsContainerDimension(1)).toBe(THUMBNAIL_WIDTH);
29 | });
30 | it('dimension with total of 10', () => {
31 | expect(calculateThumbnailsContainerDimension(10)).toBe(652);
32 | });
33 | it('dimension with total of 21', () => {
34 | expect(calculateThumbnailsContainerDimension(21)).toBe(1378);
35 | });
36 | it('dimension with total of 999', () => {
37 | expect(calculateThumbnailsContainerDimension(999)).toBe(65926);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/docs/layouts/docs-page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Head from '../components/Head';
4 | import Layout, { LayoutContextConsumer } from '../components/Layout';
5 | import SidebarDocs from '../components/SidebarDocs';
6 |
7 | const Doc = ({
8 | title,
9 | __resourcePath,
10 | }) => ({ children: content }) => (
11 |
12 |
13 |
14 |
15 |
16 | {({ navigationOpened }) => (
17 |
18 | )}
19 |
20 |
21 |
22 |
23 | {title}
24 |
25 |
26 |
27 | {content}
28 |
29 |
41 |
42 |
43 |
44 |
45 | );
46 |
47 | export default Doc;
48 |
--------------------------------------------------------------------------------
/src/components/Thumbnail/Thumbnail.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classnames from 'classnames';
5 |
6 | import Image from '../Image';
7 | import PhotoShape from '../../shapes/PhotoShape';
8 |
9 | import {
10 | forbidExtraProps,
11 | nonNegativeInteger,
12 | } from '../../common/prop-types';
13 | import noop from '../../utils/noop';
14 |
15 | import {
16 | THUMBNAIL_WIDTH,
17 | THUMBNAIL_HEIGHT,
18 | } from '../../constants';
19 |
20 | const thumbnailStyle = {
21 | width: THUMBNAIL_WIDTH,
22 | height: THUMBNAIL_HEIGHT,
23 | };
24 |
25 | const propTypes = forbidExtraProps({
26 | active: PropTypes.bool,
27 | photo: PhotoShape,
28 | onPress: PropTypes.func,
29 | number: nonNegativeInteger,
30 | });
31 |
32 | const defaultProps = {
33 | active: false,
34 | photo: null,
35 | onPress: noop,
36 | number: 0,
37 | };
38 |
39 | class Thumbnail extends PureComponent {
40 | render() {
41 | const {
42 | active,
43 | photo,
44 | onPress,
45 | number,
46 | } = this.props;
47 |
48 | const className = classnames(
49 | 'thumbnail-button',
50 | active && 'active',
51 | );
52 |
53 | return (
54 |
62 |
68 |
69 | );
70 | }
71 | }
72 |
73 | Thumbnail.propTypes = propTypes;
74 | Thumbnail.defaultProps = defaultProps;
75 |
76 | export default Thumbnail;
77 |
--------------------------------------------------------------------------------
/docs/components/Layout/Layout.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import classnames from 'classnames';
4 |
5 | import Header from '../Header';
6 | import Footer from '../Footer';
7 | import Main from '../Main';
8 |
9 | import { LayoutContextProvider } from './context';
10 |
11 | const defaultProps = {
12 | showHeader: true,
13 | showHeaderFixed: false,
14 | showMenuControls: true,
15 | };
16 |
17 | const propTypes = {
18 | showHeader: PropTypes.bool,
19 | showHeaderFixed: PropTypes.bool,
20 | showMenuControls: PropTypes.bool,
21 | children: PropTypes.node.isRequired,
22 | };
23 |
24 | const Layout = ({
25 | showHeader,
26 | showHeaderFixed,
27 | showMenuControls,
28 | children,
29 | }) => {
30 | const [value, setValue] = useState({
31 | navigationOpened: false,
32 | });
33 |
34 | return (
35 |
36 |
42 | {showHeader && (
43 | setValue({
46 | ...value,
47 | navigationOpened: true,
48 | })}
49 | onMenuClose={() => setValue({
50 | ...value,
51 | navigationOpened: false,
52 | })}
53 | showMenuControls={showMenuControls}
54 | fixed={showHeaderFixed}
55 | />
56 | )}
57 |
58 | {children}
59 |
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | Layout.defaultProps = defaultProps;
67 | Layout.propTypes = propTypes;
68 |
69 | export default Layout;
70 |
--------------------------------------------------------------------------------
/docs/components/PhotoGrid/Image.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import PropTypes from 'prop-types';
3 | import classnames from 'classnames';
4 |
5 | const defaultProps = {
6 | width: 100,
7 | height: 100,
8 | alt: 'bnbgallery',
9 | onPress: () => {},
10 | };
11 |
12 | const propTypes = {
13 | src: PropTypes.string.isRequired,
14 | width: PropTypes.number,
15 | height: PropTypes.number,
16 | alt: PropTypes.string,
17 | onPress: PropTypes.func,
18 | };
19 |
20 | const Image = ({
21 | src,
22 | onPress,
23 | width,
24 | height,
25 | alt,
26 | }) => {
27 | const [status, setStatus] = useState({
28 | loading: true,
29 | error: false,
30 | });
31 |
32 | const onClickCallback = useCallback(() => {
33 | onPress(src);
34 | }, [
35 | src,
36 | onPress,
37 | ]);
38 |
39 | return (
40 |
45 |
48 |
49 |
setStatus({ loading: false, error: false })}
60 | onError={() => setStatus({ loading: false, error: true })}
61 | />
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | Image.propTypes = propTypes;
69 | Image.defaultProps = defaultProps;
70 |
71 | export default Image;
72 |
--------------------------------------------------------------------------------
/docs/components/Social/Social.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Social = () => (
4 |
36 | );
37 |
38 | export default Social;
39 |
--------------------------------------------------------------------------------
/example/src/components/Button/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { forbidExtraProps } from 'airbnb-prop-types';
4 | import classnames from 'classnames';
5 |
6 | const propTypes = forbidExtraProps({
7 | children: PropTypes.node.isRequired,
8 | url: PropTypes.string,
9 | customStyle: PropTypes.object,
10 | large: PropTypes.bool,
11 | normal: PropTypes.bool,
12 | onPress: PropTypes.func,
13 | primary: PropTypes.bool,
14 | small: PropTypes.bool,
15 | secondary: PropTypes.bool,
16 | outline: PropTypes.bool,
17 | block: PropTypes.bool,
18 | });
19 |
20 | const defaultProps = {
21 | url: null,
22 | customStyle: null,
23 | large: false,
24 | normal: true,
25 | onPress: () => {},
26 | primary: false,
27 | small: false,
28 | secondary: false,
29 | outline: false,
30 | block: false,
31 | };
32 |
33 | const Button = ({
34 | children,
35 | url,
36 | customStyle,
37 | large,
38 | normal,
39 | onPress,
40 | primary,
41 | small,
42 | secondary,
43 | outline,
44 | block,
45 | }) => {
46 | let ComponentName, props = {};
47 | if (url) {
48 | ComponentName = 'a';
49 | props = { href: url };
50 | } else {
51 | ComponentName = 'button';
52 | }
53 |
54 | return (
55 |
71 | {children}
72 |
73 | );
74 | };
75 |
76 | Button.propTypes = propTypes;
77 | Button.defaultProps = defaultProps;
78 |
79 | export default Button;
80 |
--------------------------------------------------------------------------------
/docs/components/Navigation/Navigation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Link from 'next/link';
5 |
6 | import SubMenu, { ItemsShape } from './SubMenu';
7 |
8 | export const MenuShape = PropTypes.arrayOf(
9 | PropTypes.shape({
10 | slug: PropTypes.string.isRequired,
11 | title: PropTypes.string.isRequired,
12 | url: PropTypes.string,
13 | items: ItemsShape,
14 | }),
15 | );
16 |
17 | const defaultProps = {
18 | menu: [],
19 | };
20 |
21 | const propTypes = {
22 | menu: MenuShape,
23 | };
24 |
25 | const Navigation = ({
26 | menu,
27 | }) => (
28 |
29 |
30 | {
31 | menu.map(({
32 | slug,
33 | title,
34 | url,
35 | items,
36 | }) => {
37 | let content;
38 | if (url) {
39 | content = (
40 |
41 |
42 | {title}
43 |
44 |
45 | );
46 | } else {
47 | content = (
48 |
49 | {title}
50 |
51 | );
52 | }
53 |
54 | return (
55 |
56 | {content}
57 | {items && (
58 |
59 | )}
60 |
61 | );
62 | })
63 | }
64 |
65 |
66 | );
67 |
68 | Navigation.defaultProps = defaultProps;
69 | Navigation.propTypes = propTypes;
70 |
71 | export default Navigation;
72 |
--------------------------------------------------------------------------------
/example/src/assets/layouts/_header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | width: 100%;
3 | background-color: $header-background-color;
4 | padding-top: $header-padding-y;
5 | padding-bottom: $header-padding-y;
6 | border-top: $header-border;
7 |
8 | .header__container {
9 | padding-left: $header-padding-x;
10 | padding-right: $header-padding-x;
11 | max-width: $header-container-max-width;
12 | margin-left: auto;
13 | margin-right: auto;
14 | border-bottom: $header-border-bottom;
15 | }
16 |
17 | .header__content {
18 | display: flex;
19 | align-content: center;
20 |
21 | .header__brand {
22 | display: inline-flex;
23 | align-items: center;
24 |
25 | svg {
26 | width: 32px;
27 | height: auto;
28 | display: block;
29 | }
30 | }
31 |
32 | .navigation {
33 | padding-left: $header-navigation-spacing;
34 | padding-right: $header-navigation-spacing;
35 |
36 | .nav {
37 | width: 100%;
38 | display: flex;
39 | padding-left: 0;
40 | margin-bottom: 0;
41 | margin-top: 0;
42 | list-style: none;
43 | justify-content: center;
44 |
45 | li {
46 | padding-left: 10px;
47 | padding-right: 10px;
48 |
49 | a {
50 | display: block;
51 | padding-top: 20px;
52 | padding-bottom: 20px;
53 | text-decoration: none;
54 | color: #999999;
55 |
56 | &:hover {
57 | color: #111111;
58 | }
59 | }
60 | }
61 | }
62 | }
63 |
64 | .right {
65 | align-items: center;
66 | margin-left: auto;
67 | display: flex;
68 | justify-content: flex-end;
69 | }
70 | }
71 |
72 | @media (max-width: 650px) {
73 | overflow-x: auto;
74 |
75 | .navigation .nav li {
76 | padding-left: 5px;
77 | padding-right: 5px;
78 |
79 | a {
80 | font-size: 15px;
81 | }
82 | }
83 |
84 | .right {
85 | display: none;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/example/src/components/PhotoGrid/PhotoGrid.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { withSize } from 'react-sizeme'
5 |
6 | import Image from './Image';
7 |
8 | import photos from './photos';
9 |
10 | const propTypes = {
11 | photosPerColumn: PropTypes.number,
12 | onPhotoPress: PropTypes.func,
13 | };
14 |
15 | const defaultProps = {
16 | photosPerColumn: 3,
17 | onPhotoPress: () => {},
18 | };
19 |
20 | class PhotoGrid extends PureComponent {
21 | constructor(props) {
22 | super(props);
23 | this.getColumnWidth();
24 | }
25 |
26 | getColumnWidth() {
27 | const {
28 | size,
29 | photosPerColumn,
30 | } = this.props;
31 |
32 | this.columnWidth = size.width / (photos.length / photosPerColumn);
33 | }
34 |
35 | renderColumns() {
36 | let columns = [];
37 | let index = 0;
38 | let current = 1;
39 |
40 | photos.forEach(photo => {
41 | if (!columns[index]) {
42 | columns[index] = [];
43 | }
44 | columns[index].push(
45 | this.renderPhoto(photo)
46 | );
47 | if (current < 3) {
48 | current++;
49 | }
50 | else {
51 | current = 1;
52 | index++;
53 | }
54 | });
55 |
56 | return columns;
57 | }
58 |
59 | renderPhoto(photo) {
60 | const {
61 | onPhotoPress,
62 | } = this.props;
63 |
64 | return (
65 |
71 | );
72 | }
73 |
74 | render() {
75 | const columns = this.renderColumns();
76 |
77 | return (
78 |
79 |
80 | {columns.map((column, index) => (
81 |
82 | {column}
83 |
84 | ))}
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | PhotoGrid.propTypes = propTypes;
92 | PhotoGrid.defaultProps = defaultProps;
93 |
94 | export default withSize()(PhotoGrid);
95 |
--------------------------------------------------------------------------------
/docs/pages/docs/installation.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Installation'
3 | layout: 'docs-page'
4 | ---
5 |
6 |
7 |
8 | 1
9 | Install the library
10 |
11 |
12 |
13 | You can install [react-bnb-gallery](https://www.npmjs.com/package/react-bnb-gallery) from [npm](https://www.npmjs.com/).
14 |
15 | ```bash
16 | # Using npm
17 | npm install react-bnb-gallery
18 |
19 | # Using Yarn
20 | yarn add react-bnb-gallery
21 | ```
22 |
23 |
24 |
25 | 2
26 | Import compiled CSS
27 |
28 |
29 |
30 | Alternatively, you may use the library CSS by simply adding this line to your project’s entry point:
31 |
32 | ```javascript
33 | import 'react-bnb-gallery/dist/style.css'
34 | ```
35 |
36 |
37 |
38 | 3
39 | Start using the library
40 |
41 |
42 |
43 | Following code is simplest usage:
44 |
45 | ```javascript
46 | import React, { useState } from 'react';
47 | import ReactBnbGallery from 'react-bnb-gallery';
48 |
49 | const PHOTOS = [
50 | 'https://images.unsplash.com/photo-1470238660368-09dd17cab0b5',
51 | 'https://images.unsplash.com/photo-1565472604484-fd8b0414aaf3',
52 | 'https://images.unsplash.com/photo-1594240094495-1b9177b5fefc',
53 | ];
54 |
55 | class GalleryExample = () => {
56 | const [isOpen, setIsOpen] = useState(false);
57 |
58 | return (
59 | <>
60 | setIsOpen(true)}>
61 | Open gallery
62 |
63 | setIsOpen(false)}
67 | />
68 | >
69 | );
70 | };
71 | ```
--------------------------------------------------------------------------------
/src/components/Control/Control.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classnames from 'classnames';
5 |
6 | import { forbidExtraProps } from '../../common/prop-types';
7 | import noop from '../../utils/noop';
8 |
9 | import {
10 | NORMAL_COLOR,
11 | INVERSE_COLOR,
12 | } from '../../constants';
13 |
14 | const controlStyle = {
15 | height: '2.8em',
16 | width: '2.8em',
17 | fill: NORMAL_COLOR,
18 | };
19 |
20 | const controlStyleLight = {
21 | fill: INVERSE_COLOR,
22 | };
23 |
24 | const propTypes = forbidExtraProps({
25 | arrow: PropTypes.string,
26 | onPress: PropTypes.func,
27 | label: PropTypes.string,
28 | className: PropTypes.string,
29 | disabled: PropTypes.bool,
30 | light: PropTypes.bool,
31 | });
32 |
33 | const defaultProps = {
34 | arrow: null,
35 | onPress: noop,
36 | label: '',
37 | className: null,
38 | disabled: false,
39 | light: false,
40 | };
41 |
42 | class Control extends React.PureComponent {
43 | constructor(props) {
44 | super(props);
45 | this.onButtonPress = this.onButtonPress.bind(this);
46 | }
47 |
48 | onButtonPress() {
49 | const { onPress } = this.props;
50 | onPress();
51 | return false;
52 | }
53 |
54 | render() {
55 | const {
56 | arrow,
57 | label,
58 | className,
59 | disabled,
60 | light,
61 | } = this.props;
62 |
63 | return (
64 |
71 |
81 |
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | Control.propTypes = propTypes;
92 | Control.defaultProps = defaultProps;
93 |
94 | export default Control;
95 |
--------------------------------------------------------------------------------
/example/src/pages/GettingStarted/GettingStarted.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Container from '../../components/Container';
4 | import Heading from '../../components/Heading';
5 | import Text from '../../components/Text';
6 |
7 | import withPrism from '../../utils/withPrism';
8 |
9 | const installCode = `
10 | /* If you are using NPM */
11 | npm install --save react-bnb-gallery
12 |
13 | /* If you are using Yarn */
14 | yarn add react-bnb-gallery
15 | `;
16 |
17 | const importStyles = `
18 | import 'react-bnb-gallery/dist/style.css'
19 | `;
20 |
21 | const usageCode = `
22 | import React, { useState } from 'react';
23 | import ReactBnbGallery from 'react-bnb-gallery';
24 |
25 | const PHOTOS = [...];
26 |
27 | class GalleryExample = () => {
28 | const [isOpen, setIsOpen] = useState(false);
29 |
30 | return (
31 | <>
32 | setIsOpen(true)}>
33 | Open gallery
34 |
35 | setIsOpen(false)}
39 | />
40 | >
41 | );
42 | };
43 | `;
44 |
45 | const GettingStarted = () => (
46 |
47 | Getting Started
48 | You can install react-bnb-gallery from npm .
49 |
50 |
51 | {installCode}
52 |
53 |
54 | Importing compiled CSS
55 | Alternatively, you may use the library CSS by simply adding this line to your project’s entry point:
56 |
57 |
58 | {importStyles}
59 |
60 |
61 | Following code is simplest usage:
62 |
63 |
64 | {usageCode}
65 |
66 |
67 |
68 | );
69 |
70 | export default withPrism(GettingStarted);
71 |
--------------------------------------------------------------------------------
/example/src/assets/common/normalize/_vertical-rhythm.scss:
--------------------------------------------------------------------------------
1 | //
2 | // Vertical Rhythm
3 | //
4 | // This is the minimal amount of code needed to create vertical rhythm in our
5 | // CSS. If you are looking for a robust solution, look at the excellent Typey
6 | // library. @see https://github.com/jptaranto/typey
7 |
8 | @function normalize-rhythm($value, $relative-to: $base-font-size, $unit: $base-unit) {
9 | @if unit($value) != px {
10 | @error "The normalize vertical-rhythm module only supports px inputs. The typey library is better.";
11 | }
12 | @if $unit == rem {
13 | @return ($value / $base-font-size) * 1rem;
14 | }
15 | @else if $unit == em {
16 | @return ($value / $relative-to) * 1em;
17 | }
18 | @else { // $unit == px
19 | @return $value;
20 | }
21 | }
22 |
23 | @mixin normalize-font-size($value, $relative-to: $base-font-size) {
24 | @if unit($value) != 'px' {
25 | @error "normalize-font-size() only supports px inputs. The typey library is better.";
26 | }
27 | font-size: normalize-rhythm($value, $relative-to);
28 | }
29 |
30 | @mixin normalize-rhythm($property, $values, $relative-to: $base-font-size) {
31 | $value-list: $values;
32 | $sep: space;
33 | @if type-of($values) == 'list' {
34 | $sep: list-separator($values);
35 | }
36 | @else {
37 | $value-list: append((), $values);
38 | }
39 |
40 | $normalized-values: ();
41 | @each $value in $value-list {
42 | @if unitless($value) and $value != 0 {
43 | $value: $value * normalize-rhythm($base-line-height, $relative-to);
44 | }
45 | $normalized-values: append($normalized-values, $value, $sep);
46 | }
47 | #{$property}: $normalized-values;
48 | }
49 |
50 | @mixin normalize-margin($values, $relative-to: $base-font-size) {
51 | @include normalize-rhythm(margin, $values, $relative-to);
52 | }
53 |
54 | @mixin normalize-line-height($font-size, $min-line-padding: 2px) {
55 | $lines: ceil($font-size / $base-line-height);
56 | // If lines are cramped include some extra leading.
57 | @if ($lines * $base-line-height - $font-size) < ($min-line-padding * 2) {
58 | $lines: $lines + 1;
59 | }
60 | @include normalize-rhythm(line-height, $lines, $font-size);
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Friendly, customizable and accessible-ready simple photo gallery based on React.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ------
15 |
16 | ## Install
17 |
18 | You can install [react-bnb-gallery](https://www.npmjs.com/package/react-bnb-gallery) from [npm](https://www.npmjs.com/).
19 |
20 | ```bash
21 | # If you are using npm
22 | npm install --save react-bnb-gallery
23 |
24 | # If you prefer yarn
25 | yarn add react-bnb-gallery
26 | ```
27 |
28 | ## Live Demo
29 |
30 | [https://peterpalau.github.io/react-bnb-gallery/](https://peterpalau.github.io/react-bnb-gallery/)
31 |
32 | 
33 |
34 | ## Documentation
35 |
36 | For full documentation, visit the [demo website](https://peterpalau.github.io/react-bnb-gallery).
37 |
38 | ## License
39 |
40 | This package have been released under the [MIT Licence](https://raw.githubusercontent.com/peterpalau/react-bnb-gallery/master/LICENSE).
41 |
42 | ## Updates
43 |
44 | If you are interested in view the main updates visit our [CHANGELOG.md](https://github.com/peterpalau/react-bnb-gallery/blob/master/CHANGELOG.md).
45 |
46 | ## Contributing
47 |
48 | If you're interested in contributing please read the [contributing docs](https://github.com/peterpalau/react-bnb-gallery/blob/master/CONTRIBUTING.md) **before submitting a pull request**.
49 |
--------------------------------------------------------------------------------
/docs/components/PhotoGrid/PhotoGrid.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Image from './Image';
5 |
6 | import photos from './photos';
7 |
8 | const propTypes = {
9 | photosPerColumn: PropTypes.number,
10 | onPhotoPress: PropTypes.func,
11 | };
12 |
13 | const defaultProps = {
14 | photosPerColumn: 3,
15 | onPhotoPress: () => {},
16 | };
17 |
18 | class PhotoGrid extends PureComponent {
19 | constructor(props) {
20 | super(props);
21 | this.getColumnWidth();
22 | }
23 |
24 | getColumnWidth() {
25 | const { photosPerColumn } = this.props;
26 | this.columnWidth = photos.length / photosPerColumn;
27 | }
28 |
29 | renderColumns() {
30 | let columns = {};
31 | let index = 0;
32 | let current = 1;
33 |
34 | photos.forEach((photo) => {
35 | if (!columns[index]) {
36 | columns = {
37 | ...columns,
38 | [index]: [],
39 | };
40 | }
41 |
42 | columns = {
43 | ...columns,
44 | [index]: [
45 | ...columns[index],
46 | this.renderPhoto(photo),
47 | ],
48 | };
49 |
50 | if (current < 3) {
51 | current += 1;
52 | } else {
53 | current = 1;
54 | index += 1;
55 | }
56 | });
57 |
58 | return columns;
59 | }
60 |
61 | renderPhoto({
62 | src,
63 | width,
64 | height,
65 | }) {
66 | const {
67 | onPhotoPress,
68 | } = this.props;
69 |
70 | return (
71 |
79 | );
80 | }
81 |
82 | render() {
83 | const columns = this.renderColumns();
84 |
85 | return (
86 |
87 |
88 | {Object.keys(columns).map((i) => (
89 |
90 | {columns[i]}
91 |
92 | ))}
93 |
94 |
95 | );
96 | }
97 | }
98 |
99 | PhotoGrid.propTypes = propTypes;
100 | PhotoGrid.defaultProps = defaultProps;
101 |
102 | export default PhotoGrid;
103 |
--------------------------------------------------------------------------------
/docs/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import Link from 'next/link';
3 |
4 | import ReactBnbGallery from 'react-bnb-gallery';
5 |
6 | import Head from '../components/Head';
7 | import Layout from '../components/Layout';
8 | import Logo from '../components/Logo';
9 | import PhotoGrid from '../components/PhotoGrid';
10 |
11 | import photos from '../constants/photos';
12 |
13 | const Home = () => {
14 | const [isOpen, setOpen] = useState(false);
15 |
16 | const toggleGallery = useCallback(() => {
17 | setOpen(!isOpen);
18 | }, [isOpen]);
19 |
20 | return (
21 |
22 |
23 |
24 |
25 |
26 | Friendly, customizable and accessible-ready simple photo gallery based on
27 | React
28 | .
29 |
30 |
47 |
48 |
49 |
50 |
56 |
57 | );
58 | };
59 |
60 | export default Home;
61 |
--------------------------------------------------------------------------------
/example/src/components/Header/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/src/pages/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 |
3 | import ReactBnbGallery from 'react-bnb-gallery';
4 |
5 | import {
6 | Button,
7 | Container,
8 | Spacing,
9 | Text,
10 | Title,
11 | PhotoGrid,
12 | } from '../../components';
13 |
14 | import PHOTOS from '../../photos';
15 |
16 | const buttonCustomStyle = {
17 | marginTop: '16px',
18 | marginBottom: '24px',
19 | };
20 |
21 | const Home = () => {
22 | const [galleryStatus, setGalleryStatus] = useState({
23 | isOpen: false,
24 | currentPhoto: null,
25 | });
26 |
27 | const onPhotoPress = useCallback((url) => {
28 | setGalleryStatus({
29 | isOpen: true,
30 | currentPhoto: url,
31 | });
32 | }, []);
33 |
34 | const onGalleryClose = useCallback(() => {
35 | setGalleryStatus({
36 | isOpen: false,
37 | currentPhoto: null,
38 | });
39 | }, []);
40 |
41 | const isOpen = galleryStatus.isOpen;
42 |
43 | const photosToShow = galleryStatus.currentPhoto || PHOTOS;
44 |
45 | return (
46 | <>
47 |
48 | React photo gallery
49 | Friendly, customizable and accessible-ready simple photo gallery based on React .
50 |
51 |
52 | setGalleryStatus({
54 | isOpen: true,
55 | currentPhoto: null,
56 | })}
57 | customStyle={buttonCustomStyle}
58 | primary
59 | large
60 | >
61 | View demo gallery
62 |
63 |
64 |
65 |
72 | Fork this repository
73 |
74 |
75 |
76 |
77 |
78 |
85 | >
86 | );
87 | };
88 |
89 | export default Home;
90 |
--------------------------------------------------------------------------------
/example/src/components/PhotoGrid/Image.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classnames from 'classnames';
5 |
6 | import { getHeightWithProportion } from './utils';
7 |
8 | const propTypes = {
9 | src: PropTypes.string.isRequired,
10 | width: PropTypes.number,
11 | height: PropTypes.number,
12 | onPress: PropTypes.func,
13 | columnSize: PropTypes.number,
14 | };
15 |
16 | const defaultProps = {
17 | src: null,
18 | width: 100,
19 | height: 100,
20 | onPress: () => {},
21 | columnSize: 100,
22 | };
23 |
24 | class Image extends PureComponent {
25 | constructor(props) {
26 | super(props);
27 | this.state = {
28 | loading: true,
29 | };
30 | this.onLoad = this.onLoad.bind(this);
31 | this.onClick = this.onClick.bind(this);
32 | }
33 |
34 | onClick() {
35 | const {
36 | src,
37 | onPress,
38 | } = this.props;
39 |
40 | onPress(src);
41 | }
42 |
43 | onLoad() {
44 | this.setState({
45 | loading: false,
46 | });
47 | }
48 |
49 | getRelativeHeight() {
50 | const {
51 | width,
52 | height,
53 | columnSize,
54 | } = this.props;
55 |
56 | return getHeightWithProportion(height, width, columnSize) - 8;
57 | }
58 |
59 | render() {
60 | const {
61 | src,
62 | width,
63 | height,
64 | } = this.props;
65 |
66 | const {
67 | loading,
68 | } = this.state;
69 |
70 | const relativeHeight = this.getRelativeHeight();
71 |
72 | return (
73 |
87 |
93 |
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 | Image.propTypes = propTypes;
107 | Image.defaultProps = defaultProps;
108 |
109 | export default Image;
110 |
--------------------------------------------------------------------------------
/docs/constants/photos.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | photo: 'https://images.unsplash.com/photo-1470238660368-09dd17cab0b5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
4 | caption: 'Sunset',
5 | },
6 | {
7 | photo: 'https://images.unsplash.com/photo-1565472604484-fd8b0414aaf3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
8 | caption: 'Venom',
9 | },
10 | {
11 | photo: 'https://images.unsplash.com/photo-1594240094495-1b9177b5fefc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
12 | caption: 'Beach',
13 | },
14 | {
15 | photo: 'https://images.unsplash.com/photo-1593773271567-b13ceb8ce8ac?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1204&q=100',
16 | caption: 'Summer & Earth Colors',
17 | subcation: 'By @cbarbalis',
18 | },
19 | {
20 | photo: 'https://images.unsplash.com/photo-1593200931138-001b51a74284?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
21 | caption: 'Paceful',
22 | },
23 | {
24 | photo: 'https://images.unsplash.com/photo-1593616471742-f31639185abd?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
25 | caption: 'Wonderful View',
26 | },
27 | {
28 | photo: 'https://images.unsplash.com/photo-1593270573246-2dca2f2c1c43?ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&q=100',
29 | caption: 'Quite Night',
30 | },
31 | {
32 | photo: 'https://images.unsplash.com/photo-1592407815059-d10df1905661?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
33 | caption: 'Alone',
34 | },
35 | {
36 | photo: 'https://images.unsplash.com/photo-1592334454447-7d0aa7feda01?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
37 | caption: 'Colors',
38 | },
39 | {
40 | photo: 'https://images.unsplash.com/photo-1591981093673-984cd7de9ca4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
41 | caption: 'Health Green',
42 | },
43 | {
44 | photo: 'https://images.unsplash.com/photo-1591254267182-4211662a0c00?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
45 | caption: 'Pretty Green',
46 | },
47 | {
48 | photo: 'https://images.unsplash.com/photo-1593286880275-cc4aee0fb836?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1024&q=100',
49 | caption: 'Cold Forest',
50 | },
51 | {
52 | photo: 'https://images.unsplash.com/photo-1593616469718-20c25ba3d611?ixlib=rb-1.2.1&auto=format&fit=crop&w=751&q=80',
53 | caption: 'White',
54 | },
55 | ];
56 |
--------------------------------------------------------------------------------
/docs/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classnames from 'classnames';
4 |
5 | import Link from 'next/link';
6 |
7 | import Logo from '../Logo';
8 | import Social from '../Social';
9 |
10 | const defaultProps = {
11 | menuOpened: false,
12 | showMenuControls: true,
13 | fixed: false,
14 | onMenuOpen: () => {},
15 | onMenuClose: () => {},
16 | };
17 |
18 | const propTypes = {
19 | menuOpened: PropTypes.bool,
20 | showMenuControls: PropTypes.bool,
21 | fixed: PropTypes.bool,
22 | onMenuOpen: PropTypes.func,
23 | onMenuClose: PropTypes.func,
24 | };
25 |
26 | const Header = ({
27 | menuOpened,
28 | showMenuControls,
29 | fixed,
30 | onMenuOpen,
31 | onMenuClose,
32 | }) => (
33 |
40 |
41 |
42 |
49 |
50 | {showMenuControls && (
51 |
52 | {!menuOpened && (
53 |
54 |
55 |
56 |
57 |
58 | )}
59 | {menuOpened && (
60 |
61 |
62 |
63 |
64 |
65 | )}
66 |
67 | )}
68 |
69 |
70 |
71 | );
72 |
73 | Header.defaultProps = defaultProps;
74 | Header.propTypes = propTypes;
75 |
76 | export default Header;
77 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 |
6 |
7 | ## 1.4.3
8 |
9 | - [enhancement] Updates dependencies
10 |
11 | ## 1.3.1
12 |
13 | - [enhancement] Creates actions and updates workflow
14 |
15 | ## 1.2.6
16 |
17 | - [enhancement] Updates dependencies
18 | - [enhancement] Integrates with GitHub Packages
19 |
20 | ## 1.2.5
21 |
22 | - [fix] Wrong thumbnails visualization
23 |
24 | ## 1.2.4
25 |
26 | - [enhancement] Updates dependencies
27 | - [enhancement] Updates development dependencies
28 |
29 | ## 1.2.3
30 |
31 | - [fix] Thumbnails list hide/show styles
32 | - [enhancement] More improvements to the gallery styles
33 |
34 | ## 1.2.2
35 |
36 | - [fix] Lot of wrong styles
37 | - [enhancement] Updates images visualization
38 |
39 | ## 1.2.1
40 |
41 | - [fix] Wrong loading spinner style
42 |
43 | ## 1.2.0
44 |
45 | - [fix] Updates multiple dependencies
46 | - [enhancement] Accept path-only images
47 |
48 | ## 1.1.9
49 |
50 | - [fix] Correctly bind the `zIndex` property
51 |
52 | ## 1.1.8
53 |
54 | - [fix] Update typings of the package details
55 | - [enhancement] Folder organization
56 | - [enhancement] Update the visual of the component
57 |
58 | ## 1.1.7
59 |
60 | - [enhancement] Updates multiple dependencies.
61 | - [enhancement] Replace `npm` with `yarn`.
62 | - [enhancement] `Gallery` is exported now as an individual component
63 | - [fix] All the images are loaded at the same time
64 |
65 | ## 1.1.6
66 |
67 | - [enhancement] Using the latest version of some dependencies.
68 | - [enhancement] `react-scripts` version updated for the example showcase.
69 | - [fixed] Some vulnerabilities issues
70 |
71 | ## 1.1.5
72 |
73 | - [fix] Does not work with TypeScript ([#2](https://github.com/peterpalau/react-bnb-gallery/issues/2))
74 | - [fix] Warning `Using "external-helpers" plugin with rollup is deprecated`.
75 |
76 | ## 1.1.4
77 |
78 | - [new] Added `opacity` property to set the opacity level.
79 | - [new] Added `backgroundColor` property to set the background color.
80 | - [new] Added `zIndex` property to stack order relative to other components.
81 |
82 | ## 1.0.4
83 |
84 | - [new] Testing.
85 | - [fix] Code style.
86 |
87 | ## 1.0.3
88 |
89 | - [new] Allow to disable keyboard navigation by the `keyboard` prop.
90 | - [fix] Better prop types validation.
91 |
92 | ## 1.0.2
93 |
94 | - [new] Allow an array of urls instead of objects.
95 | - [fix] Fix incorrect dynamic position offset of active thumbnails.
96 |
97 | ## 1.0.1
98 |
99 | - [new] Keyboard support, for `Left` and `Right` arrows and `Esc` key.
100 |
--------------------------------------------------------------------------------
/src/components/Photo/Photo.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import omit from 'lodash/omit';
5 | import classnames from 'classnames';
6 |
7 | import Image from '../Image';
8 | import PhotoShape from '../../shapes/PhotoShape';
9 |
10 | import {
11 | imagePropTypes,
12 | imageDefaultProps,
13 | } from '../../common';
14 | import { forbidExtraProps } from '../../common/prop-types';
15 | import noop from '../../utils/noop';
16 |
17 | const propTypes = forbidExtraProps({
18 | ...imagePropTypes,
19 | photo: PhotoShape,
20 | onPress: PropTypes.func,
21 | onTouchStart: PropTypes.func,
22 | onTouchMove: PropTypes.func,
23 | onTouchEnd: PropTypes.func,
24 | });
25 |
26 | const defaultProps = {
27 | ...imageDefaultProps,
28 | photo: null,
29 | onPress: noop,
30 | onTouchStart: noop,
31 | onTouchMove: noop,
32 | onTouchEnd: noop,
33 | };
34 |
35 | class Photo extends PureComponent {
36 | constructor(props) {
37 | super(props);
38 | this.onPress = this.onPress.bind(this);
39 | }
40 |
41 | onPress() {
42 | const { onPress } = this.props;
43 | onPress();
44 | }
45 |
46 | renderPhoto() {
47 | const {
48 | photo,
49 | onTouchStart,
50 | onTouchMove,
51 | onTouchEnd,
52 | ...rest
53 | } = this.props;
54 |
55 | if (!photo) {
56 | return null;
57 | }
58 |
59 | const {
60 | onLoad,
61 | onError,
62 | style,
63 | } = omit(rest, [
64 | 'onPress',
65 | ]);
66 |
67 | return (
68 |
76 |
84 |
85 | );
86 | }
87 |
88 | render() {
89 | const className = classnames(
90 | 'gallery-media-photo',
91 | 'gallery-media-photo--block',
92 | 'gallery-media-cover',
93 | );
94 |
95 | const photoRendered = this.renderPhoto();
96 |
97 | return (
98 |
99 |
100 | {photoRendered}
101 |
102 |
103 | );
104 | }
105 | }
106 |
107 | Photo.propTypes = propTypes;
108 | Photo.defaultProps = defaultProps;
109 |
110 | export default Photo;
111 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | export type Props = {
4 | /**
5 | * Default photo to show.
6 | */
7 | activePhotoIndex?: number
8 | /**
9 | * Executed when a photo is pressed.
10 | */
11 | activePhotoPressed?: () => void
12 | /**
13 | * Executed when left key of the keyboard is pressed.
14 | */
15 | leftKeyPressed?: () => void
16 | /**
17 | * Called when next control button is pressed.
18 | */
19 | nextButtonPressed?: () => void
20 | /**
21 | * Called when the modal is going to close.
22 | */
23 | onClose: () => void
24 | /**
25 | * Preload number photos.
26 | */
27 | preloadSize?: number
28 | /**
29 | * Called when previous control button is pressed.
30 | */
31 | prevButtonPressed?: () => void
32 | /**
33 | * Array of photos.
34 | * It can be an array of photos URLs or an array of objects.
35 | */
36 | photos: Photo[] | string[]
37 | /**
38 | * Custom labels object.
39 | */
40 | phrases?: object
41 | /**
42 | * Called when right key of the keyboard is pressed.
43 | */
44 | rightKeyPressed?: () => void
45 | /**
46 | * Shows the modal when initialized.
47 | */
48 | show?: boolean
49 | /**
50 | * Whether the gallery should show thumbnails.
51 | */
52 | showThumbnails?: boolean
53 | /**
54 | * Whether the gallery should react to keyboard events.
55 | */
56 | keyboard?: boolean
57 | /**
58 | * Whether the gallery should cycle continuously or have hard stops.
59 | */
60 | wrap?: boolean
61 | /**
62 | * Sets the opacity level for the component background.
63 | */
64 | opacity?: number
65 | /**
66 | * Sets the background color of the gallery component.
67 | */
68 | backgroundColor?: string
69 | /**
70 | * Specifies the stack order of the component.
71 | */
72 | zIndex?: number
73 | };
74 |
75 | export type Photo = {
76 | /**
77 | * The source (`src`) of the photo.
78 | */
79 | photo: string
80 | /**
81 | * The current number of the photo.
82 | */
83 | number?: number
84 | /**
85 | * Short description of the photo.
86 | */
87 | caption?: string
88 | /**
89 | * Secondary description.
90 | * Can be used to show the photo author or the name or
91 | * the place where it was taken.
92 | */
93 | subcaption?: string
94 | /**
95 | * The url of the photo thumbnail.
96 | * The preferred size for each thumbnail is `100x67`.
97 | */
98 | thumbnail?: string
99 | }
100 |
101 | export type ReactBnbGalleryInterface = React.ComponentClass
102 |
103 | declare const ReactBnbGallery: ReactBnbGalleryInterface
104 |
105 | export default ReactBnbGallery
106 |
--------------------------------------------------------------------------------
/example/src/assets/components/_photo-grid.scss:
--------------------------------------------------------------------------------
1 | .grid-container {
2 | position: relative;
3 | overflow: hidden;
4 |
5 | .grid {
6 | display: flex;
7 | flex-wrap: wrap;
8 | padding-left: $photo-grid-gutter / 2;
9 | padding-right: $photo-grid-gutter / 2;
10 | padding-bottom: $photo-grid-gutter;
11 |
12 | .column {
13 | flex-basis: 0;
14 | flex-grow: 1;
15 | flex: 0 0 $photo-grid-col-max-size;
16 | max-width: $photo-grid-col-max-size;
17 | padding-left: $photo-grid-gutter / 2;
18 | padding-right: $photo-grid-gutter / 2;
19 | display: flex;
20 | flex-direction: column;
21 | justify-content: flex-end;
22 |
23 | .picture {
24 | margin: $photo-grid-picture-spacing;
25 | cursor: pointer;
26 | min-width: 100%;
27 | max-width: 100%;
28 | height: auto;
29 | background-color: $photo-grid-picture-background;
30 | overflow: hidden;
31 | position: relative;
32 |
33 | &:last-child {
34 | margin-bottom: 0;
35 | }
36 |
37 | .bg {
38 | background-position: center center;
39 | background-repeat: no-repeat;
40 | background-size: cover;
41 | position: absolute;
42 | top: 0;
43 | left: 0;
44 | width: 100%;
45 | height: 100%;
46 | transition-delay: 0.82s;
47 | transition: 0.75s ease all;
48 | }
49 |
50 | img {
51 | width: 100%;
52 | height: auto;
53 | display: block;
54 | opacity: 0;
55 | visibility: hidden;
56 | }
57 |
58 | &.loading {
59 | cursor: default;
60 |
61 | .bg,
62 | img {
63 | opacity: 0;
64 | }
65 | }
66 |
67 | &.loaded {
68 | &::before {
69 | content: "";
70 | display: block;
71 | position: absolute;
72 | top: 0;
73 | left: 0;
74 | width: 100%;
75 | height: 100%;
76 | background-color: $photo-grid-picture-background-loaded;
77 | opacity: 0;
78 | z-index: 1;
79 | transition: 0.45s ease all;
80 | }
81 |
82 | &:hover {
83 | &::before {
84 | opacity: 0.5;
85 | }
86 | }
87 |
88 | .bg,
89 | img {
90 | opacity: 1;
91 | z-index: 0;
92 | transition: 0.75s ease all;
93 | transition-delay: 0s;
94 | }
95 |
96 | img {
97 | position: relative;
98 | }
99 |
100 | &:hover {
101 | .bg,
102 | img {
103 | transform: scale(1.02);
104 | }
105 | }
106 | }
107 | }
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/example/src/pages/Examples/Examples.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent, Fragment } from 'react';
2 |
3 | import ReactBnbGallery from 'react-bnb-gallery';
4 |
5 | import Button from '../../components/Button';
6 | import Container from '../../components/Container';
7 | import Text from '../../components/Text';
8 | import Title from '../../components/Title';
9 | import Rule from '../../components/Rule';
10 | import Spacing from '../../components/Spacing';
11 |
12 | import { DEMOS } from './constants';
13 |
14 | import withPrism from '../../utils/withPrism';
15 |
16 | import './section.css';
17 |
18 | class Examples extends PureComponent {
19 | constructor() {
20 | super();
21 | this.state = {
22 | galleryOpened: false,
23 | galleryProps: {},
24 | };
25 | this.onButtonPress = this.onButtonPress.bind(this);
26 | this.closeGallery = this.closeGallery.bind(this);
27 | }
28 |
29 | onButtonPress(config = {}) {
30 | this.setState({
31 | galleryOpened: true,
32 | galleryProps: config,
33 | });
34 | }
35 |
36 | closeGallery() {
37 | this.setState({
38 | galleryOpened: false,
39 | galleryProps: {},
40 | });
41 | }
42 |
43 | render() {
44 | const {
45 | galleryOpened,
46 | galleryProps,
47 | } = this.state;
48 |
49 | return (
50 |
51 |
52 | All these examples can be found in the demos folder of the repository.
53 |
54 | {DEMOS.map(demostration => (
55 |
56 |
57 | {demostration.label}
58 |
59 |
60 |
61 |
62 | {demostration.code}
63 |
64 |
65 |
66 |
67 | this.onButtonPress(demostration.config)}
69 | outline
70 | small
71 | >
72 | Run example
73 |
74 |
75 |
76 | ))}
77 |
78 |
79 |
80 |
81 |
82 | Images from Unsplash Awards 2018
83 |
84 |
85 |
90 |
91 | );
92 | }
93 | }
94 |
95 | export default withPrism(Examples);
96 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/example/src/assets/components/_buttons.scss:
--------------------------------------------------------------------------------
1 | .button {
2 | display: inline-flex;
3 | align-items: center;
4 | border: $btn-border;
5 | border-radius: $btn-border-radius;
6 | box-shadow: $btn-box-shadow;
7 | cursor: pointer;
8 | text-decoration: none;
9 |
10 | &:focus,
11 | &:active {
12 | outline: none;
13 | }
14 |
15 | &.button__block {
16 | display: block;
17 | width: 100%;
18 | }
19 |
20 | &.button__default {
21 | line-height: $btn-line-height;
22 | height: $btn-height;
23 | padding: $btn-padding-y $btn-padding-x;
24 | font-size: $btn-font-size;
25 | font-weight: $btn-font-weight;
26 | border-width: $btn-border-width;
27 | border-radius: $btn-border-radius;
28 | }
29 |
30 | &.button__small {
31 | line-height: $btn-small-line-height;
32 | height: $btn-small-height;
33 | padding: $btn-small-padding-y $btn-small-padding-x;
34 | font-size: $btn-small-font-size;
35 | font-weight: $btn-small-font-weight;
36 | border-width: $btn-small-border-width;
37 | border-radius: $btn-small-border-radius;
38 | }
39 |
40 | &.button__large {
41 | line-height: $btn-large-line-height;
42 | height: $btn-large-height;
43 | padding: $btn-large-padding-y $btn-large-padding-x;
44 | font-size: $btn-large-font-size;
45 | font-weight: $btn-large-font-weight;
46 | border-width: $btn-large-border-width;
47 | border-radius: $btn-large-border-radius;
48 | }
49 |
50 | &.button__outline {
51 | box-shadow: none;
52 | }
53 |
54 | &.button__primary {
55 | color: $btn-primary-color;
56 | background-color: $btn-primary-background;
57 | border-color: $btn-primary-border;
58 |
59 | &:hover {
60 | color: $btn-primary-hover-color;
61 | background-color: $btn-primary-hover-background;
62 | border-color: $btn-primary-hover-border;
63 | }
64 |
65 | &.button__outline {
66 | color: $btn-primary-outline-color;
67 | background-color: $btn-primary-outline-background;
68 | border-color: $btn-primary-outline-border;
69 |
70 | &:hover {
71 | color: $btn-primary-outline-hover-color;
72 | background-color: $btn-primary-outline-hover-background;
73 | border-color: $btn-primary-outline-hover-border;
74 | }
75 | }
76 | }
77 |
78 | &.button__secondary {
79 | color: $btn-secondary-color;
80 | background-color: $btn-secondary-background;
81 | border-color: $btn-secondary-border;
82 |
83 | &:hover {
84 | color: $btn-secondary-hover-color;
85 | background-color: $btn-secondary-hover-background;
86 | border-color: $btn-secondary-hover-border;
87 | }
88 |
89 | &.button__outline {
90 | color: $btn-secondary-outline-color;
91 | background-color: $btn-secondary-outline-background;
92 | border-color: $btn-secondary-outline-border;
93 |
94 | &:hover {
95 | color: $btn-secondary-outline-hover-color;
96 | background-color: $btn-secondary-outline-hover-background;
97 | border-color: $btn-secondary-outline-hover-border;
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/example/src/assets/components/_prism.scss:
--------------------------------------------------------------------------------
1 | code[class*="language-"],
2 | pre[class*="language-"] {
3 | color: $prism-color;
4 | background: none;
5 | text-shadow: $prism-text-shadow;
6 | font-family: $prism-font-family;
7 | text-align: left;
8 | white-space: pre;
9 | word-spacing: normal;
10 | word-break: normal;
11 | word-wrap: normal;
12 | line-height: $prism-line-height;
13 | tab-size: $prism-tab-size;
14 | hyphens: none;
15 | direction: ltr;
16 | }
17 |
18 | :not(pre) > code[class*="language-"],
19 | pre[class*="language-"] {
20 | background: $prism-background;
21 | }
22 |
23 | pre[class*="language-"] {
24 | padding: $prism-block-padding;
25 | margin: $prism-block-margin;
26 | overflow: auto;
27 | border-radius: $prism-block-border-radius;
28 | }
29 |
30 | :not(pre) > code[class*="language-"] {
31 | padding: $prism-code-padding;
32 | border-radius: $prism-code-border-radius;
33 | white-space: normal;
34 | }
35 |
36 | .token.comment,
37 | .token.prolog,
38 | .token.doctype,
39 | .token.cdata {
40 | color: slategray;
41 | }
42 |
43 | .token.punctuation {
44 | color: #f8f8f2;
45 | }
46 |
47 | .namespace {
48 | opacity: 0.7;
49 | }
50 |
51 | .token.property,
52 | .token.tag,
53 | .token.constant,
54 | .token.symbol,
55 | .token.deleted {
56 | color: #f92672;
57 | }
58 |
59 | .token.boolean,
60 | .token.number {
61 | color: #ae81ff;
62 | }
63 |
64 | .token.selector,
65 | .token.attr-name,
66 | .token.string,
67 | .token.char,
68 | .token.builtin,
69 | .token.inserted {
70 | color: #6eba76;
71 | }
72 |
73 | .token.entity,
74 | .token.url,
75 | .language-css .token.string,
76 | .style .token.string,
77 | .token.variable {
78 | color: #f8f8f2;
79 | }
80 |
81 | .token.class-name {
82 | color: #e5c07b;
83 | }
84 |
85 | .token.function {
86 | color: #61afef;
87 | }
88 |
89 | .token.atrule,
90 | .token.attr-value {
91 | color: #e6db74;
92 | }
93 |
94 | .token.operator {
95 | color: #4cb6c2;
96 | }
97 |
98 | .token.keyword {
99 | color: #c678dd;
100 | }
101 |
102 | .token.regex,
103 | .token.important {
104 | color: #fd971f;
105 | }
106 |
107 | .token.important,
108 | .token.bold {
109 | font-weight: bold;
110 | }
111 | .token.italic {
112 | font-style: italic;
113 | }
114 |
115 | .token.entity {
116 | cursor: help;
117 | }
118 |
119 | .command-line-prompt {
120 | border-right: 1px solid #999;
121 | display: block;
122 | float: left;
123 | font-size: 100%;
124 | letter-spacing: -1px;
125 | margin-right: 1em;
126 | pointer-events: none;
127 |
128 | -webkit-user-select: none;
129 | -moz-user-select: none;
130 | -ms-user-select: none;
131 | user-select: none;
132 | }
133 |
134 | .command-line-prompt > span:before {
135 | color: #999;
136 | content: " ";
137 | display: block;
138 | padding-right: 0.8em;
139 | }
140 |
141 | .command-line-prompt > span[data-user]:before {
142 | content: "[" attr(data-user) "@" attr(data-host) "] $";
143 | }
144 |
145 | .command-line-prompt > span[data-user="root"]:before {
146 | content: "[" attr(data-user) "@" attr(data-host) "] #";
147 | }
148 |
149 | .command-line-prompt > span[data-prompt]:before {
150 | content: attr(data-prompt);
151 | }
152 |
--------------------------------------------------------------------------------
/docs/components/PhotoGrid/photos.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | src: 'https://source.unsplash.com/random/502x502',
4 | width: 502,
5 | height: 502,
6 | },
7 | {
8 | src: 'https://source.unsplash.com/random/702x702',
9 | width: 702,
10 | height: 702,
11 | },
12 | {
13 | src: 'https://source.unsplash.com/random/602x802',
14 | width: 602,
15 | height: 802,
16 | },
17 | /*---------------------------------------------------*/
18 | {
19 | src: 'https://source.unsplash.com/random/636x703',
20 | width: 636,
21 | height: 703,
22 | },
23 | {
24 | src: 'https://source.unsplash.com/random/550x425',
25 | width: 550,
26 | height: 425,
27 | },
28 | {
29 | src: 'https://source.unsplash.com/random/550x800',
30 | width: 550,
31 | height: 800,
32 | },
33 | /*---------------------------------------------------*/
34 | {
35 | src: 'https://source.unsplash.com/random/500x500',
36 | width: 500,
37 | height: 500,
38 | },
39 | {
40 | src: 'https://source.unsplash.com/random/801x724',
41 | width: 801,
42 | height: 727,
43 | },
44 | {
45 | src: 'https://source.unsplash.com/random/601x856',
46 | width: 601,
47 | height: 856,
48 | },
49 | /*---------------------------------------------------*/
50 | {
51 | src: 'https://source.unsplash.com/random/701x860',
52 | width: 701,
53 | height: 860,
54 | },
55 | {
56 | src: 'https://source.unsplash.com/random/801x1034',
57 | width: 801,
58 | height: 1034,
59 | },
60 | {
61 | src: 'https://source.unsplash.com/random/801x653',
62 | width: 801,
63 | height: 653,
64 | },
65 | /*---------------------------------------------------*/
66 | {
67 | src: 'https://source.unsplash.com/random/551x399',
68 | width: 551,
69 | height: 399,
70 | },
71 | {
72 | src: 'https://source.unsplash.com/random/501x790',
73 | width: 501,
74 | height: 790,
75 | },
76 | {
77 | src: 'https://source.unsplash.com/random/701x723',
78 | width: 701,
79 | height: 723,
80 | },
81 | /*---------------------------------------------------*/
82 | {
83 | src: 'https://source.unsplash.com/random/801x1037',
84 | width: 801,
85 | height: 1037,
86 | },
87 | {
88 | src: 'https://source.unsplash.com/random/551x437',
89 | width: 551,
90 | height: 437,
91 | },
92 | {
93 | src: 'https://source.unsplash.com/random/701x873',
94 | width: 701,
95 | height: 873,
96 | },
97 | /*---------------------------------------------------*/
98 | {
99 | src: 'https://source.unsplash.com/random/801x897',
100 | width: 801,
101 | height: 897,
102 | },
103 | {
104 | src: 'https://source.unsplash.com/random/551x550',
105 | width: 551,
106 | height: 550,
107 | },
108 | {
109 | src: 'https://source.unsplash.com/random/601x731',
110 | width: 601,
111 | height: 731,
112 | },
113 | /*---------------------------------------------------*/
114 | {
115 | src: 'https://source.unsplash.com/random/801x683',
116 | width: 801,
117 | height: 683,
118 | },
119 | {
120 | src: 'https://source.unsplash.com/random/551x465',
121 | width: 551,
122 | height: 465,
123 | },
124 | {
125 | src: 'https://source.unsplash.com/random/601x984',
126 | width: 601,
127 | height: 984,
128 | },
129 | ];
130 |
--------------------------------------------------------------------------------
/docs/pages/docs/options.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Options'
3 | layout: 'docs-page'
4 | ---
5 |
6 | To customize **bnbgallery** you can use the following properties:
7 |
8 | ### `activePhotoIndex`
9 |
10 | `activePhotoIndex: number`
11 |
12 | Initial photo index to show. Defaults to `0`.
13 |
14 | ### `activePhotoPressed`
15 |
16 | `activePhotoPressed: function`
17 |
18 | Called when a photo is pressed. Defaults to `noop`.
19 |
20 | ### `leftKeyPressed`
21 |
22 | `leftKeyPressed: function`
23 |
24 | Called when left key of the keyboard is pressed. Defaults to `noop`.
25 |
26 | ### `nextButtonPressed`
27 |
28 | `nextButtonPressed: function`
29 |
30 | Called when next control button is pressed. Defaults to `noop`.
31 |
32 | ### `onClose`
33 |
34 | `onClose: function`
35 |
36 | Called when the gallery modal is going to close. Defaults to `noop`.
37 |
38 | ### `preloadSize`
39 |
40 | `preloadSize: number`
41 |
42 | The number of photos to preload on gallery initialization. Defaults to `5`.
43 |
44 | ### `prevButtonPressed`
45 |
46 | `prevButtonPressed: function`
47 |
48 | Called when previous control button is pressed. Defaults to `noop`.
49 |
50 | ### `photos`
51 |
52 | `photos: array`
53 |
54 | List of photos. It can be an array of photos URLs or an array of objects. See the [photo object] props bellow.
55 |
56 | ### `phrases`
57 |
58 | `phrases: object`
59 |
60 | List of phrases. Could be used to translate strings. Defaults to `{...}`.
61 |
62 | ### `rightKeyPressed`
63 |
64 | `rightKeyPressed: function`
65 |
66 | Called when right key of the keyboard is pressed. Defaults to `noop`.
67 |
68 | ### `show`
69 |
70 | `show: function`
71 |
72 | Shows the modal when initialized. Defaults to `noop`.
73 |
74 | ### `showThumbnails`
75 |
76 | `showThumbnails: boolean`
77 |
78 | Whether the gallery should show thumbnails. Defaults to `true`.
79 |
80 | ### `keyboard`
81 |
82 | `keyboard: boolean`
83 |
84 | Whether the gallery should react to keyboard events. Defaults to `true`.
85 |
86 | ### `wrap`
87 |
88 | `wrap: boolean`
89 |
90 | Whether the gallery should cycle continuously or have hard stops. Defaults to `false`.
91 |
92 | ### `opacity`
93 |
94 | `opacity: number`
95 |
96 | Sets the opacity level for the component. Defaults to `1`.
97 |
98 | ### `backgroundColor`
99 |
100 | `backgroundColor: string`
101 |
102 | Sets the background color of the gallery component. Defaults to `#000000`.
103 |
104 | ### `zIndex`
105 |
106 | `zIndex: number`
107 |
108 | Specifies the stack order of the component. Defaults to `2000`.
109 |
110 | ## Photo properties
111 |
112 | This props are passed into `ReactBnbGallery`'s photos property:
113 |
114 | ### `photo`
115 |
116 | `photo: string`
117 |
118 | The src attribute value of the image. Defaults to `undefined`.
119 |
120 | ### `number`
121 |
122 | `number: string`
123 |
124 | The current number of the photo. Defaults to `undefined`.
125 |
126 | ### `caption`
127 |
128 | `caption: string`
129 |
130 | Photo description. Defaults to `undefined`.
131 |
132 | ### `subcaption`
133 |
134 | `subcaption: string`
135 |
136 | Photo secondary description, like the photo author or the name of the place where it was taken. Defaults to `undefined`.
137 |
138 | ### `thumbnail`
139 |
140 | `thumbnail: string`
141 |
142 | The url of the photo thumbnail. The preferred size for each thumbnail is `100x67`. Defaults to `undefined`.
143 |
--------------------------------------------------------------------------------
/example/src/components/PhotoGrid/photos.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | src: 'https://source.unsplash.com/random/502x502',
4 | width: 502,
5 | height: 502,
6 | },
7 | {
8 | src: 'https://source.unsplash.com/random/702x702',
9 | width: 702,
10 | height: 702,
11 | },
12 | {
13 | src: 'https://source.unsplash.com/random/602x802',
14 | width: 602,
15 | height: 802,
16 | },
17 | /*---------------------------------------------------*/
18 | {
19 | src: 'https://source.unsplash.com/random/636x703',
20 | width: 636,
21 | height: 703,
22 | },
23 | {
24 | src: 'https://source.unsplash.com/random/550x425',
25 | width: 550,
26 | height: 425,
27 | },
28 | {
29 | src: 'https://source.unsplash.com/random/550x800',
30 | width: 550,
31 | height: 800,
32 | },
33 | /*---------------------------------------------------*/
34 | {
35 | src: 'https://source.unsplash.com/random/500x500',
36 | width: 500,
37 | height: 500,
38 | },
39 | {
40 | src: 'https://source.unsplash.com/random/801x724',
41 | width: 801,
42 | height: 727,
43 | },
44 | {
45 | src: 'https://source.unsplash.com/random/601x856',
46 | width: 601,
47 | height: 856,
48 | },
49 | /*---------------------------------------------------*/
50 | {
51 | src: 'https://source.unsplash.com/random/701x860',
52 | width: 701,
53 | height: 860,
54 | },
55 | {
56 | src: 'https://source.unsplash.com/random/801x1034',
57 | width: 801,
58 | height: 1034,
59 | },
60 | {
61 | src: 'https://source.unsplash.com/random/801x653',
62 | width: 801,
63 | height: 653,
64 | },
65 | /*---------------------------------------------------*/
66 | {
67 | src: 'https://source.unsplash.com/random/551x399',
68 | width: 551,
69 | height: 399,
70 | },
71 | {
72 | src: 'https://source.unsplash.com/random/501x790',
73 | width: 501,
74 | height: 790,
75 | },
76 | {
77 | src: 'https://source.unsplash.com/random/701x723',
78 | width: 701,
79 | height: 723,
80 | },
81 | /*---------------------------------------------------*/
82 | {
83 | src: 'https://source.unsplash.com/random/801x1037',
84 | width: 801,
85 | height: 1037,
86 | },
87 | {
88 | src: 'https://source.unsplash.com/random/551x437',
89 | width: 551,
90 | height: 437,
91 | },
92 | {
93 | src: 'https://source.unsplash.com/random/701x873',
94 | width: 701,
95 | height: 873,
96 | },
97 | /*---------------------------------------------------*/
98 | {
99 | src: 'https://source.unsplash.com/random/801x897',
100 | width: 801,
101 | height: 897,
102 | },
103 | {
104 | src: 'https://source.unsplash.com/random/551x550',
105 | width: 551,
106 | height: 550,
107 | },
108 | {
109 | src: 'https://source.unsplash.com/random/601x731',
110 | width: 601,
111 | height: 731,
112 | },
113 | /*---------------------------------------------------*/
114 | {
115 | src: 'https://source.unsplash.com/random/801x683',
116 | width: 801,
117 | height: 683,
118 | },
119 | {
120 | src: 'https://source.unsplash.com/random/551x465',
121 | width: 551,
122 | height: 465,
123 | },
124 | {
125 | src: 'https://source.unsplash.com/random/601x984',
126 | width: 601,
127 | height: 984,
128 | },
129 | ];
130 |
--------------------------------------------------------------------------------
/src/components/Image/Image.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classnames from 'classnames';
5 |
6 | import LoadingSpinner from '../LoadingSpinner';
7 |
8 | import {
9 | imagePropTypes,
10 | imageDefaultProps,
11 | } from '../../common';
12 | import { forbidExtraProps } from '../../common/prop-types';
13 |
14 | const propTypes = forbidExtraProps({
15 | ...imagePropTypes,
16 | alt: PropTypes.string.isRequired,
17 | src: PropTypes.string.isRequired,
18 | style: PropTypes.object,
19 | className: PropTypes.oneOfType([
20 | PropTypes.string,
21 | PropTypes.array,
22 | ]),
23 | });
24 |
25 | const defaultProps = {
26 | ...imageDefaultProps,
27 | style: null,
28 | className: null,
29 | alt: '',
30 | };
31 |
32 | const defaultState = {
33 | loading: true,
34 | withError: false,
35 | };
36 |
37 | class Image extends Component {
38 | constructor(props) {
39 | super(props);
40 | const { src } = props;
41 | this.state = {
42 | currentSrc: src,
43 | ...defaultState,
44 | };
45 | this.onLoad = this.onLoad.bind(this);
46 | this.onError = this.onError.bind(this);
47 | }
48 |
49 | static getDerivedStateFromProps(nextProps, prevState) {
50 | const { src } = nextProps;
51 | const { currentSrc } = prevState;
52 |
53 | if (src !== currentSrc) {
54 | return {
55 | currentSrc: src,
56 | ...defaultState,
57 | };
58 | }
59 |
60 | return null;
61 | }
62 |
63 | onLoad() {
64 | const { onLoad } = this.props;
65 |
66 | onLoad();
67 |
68 | this.setState({
69 | loading: false,
70 | withError: false,
71 | });
72 | }
73 |
74 | onError() {
75 | const { onError } = this.props;
76 |
77 | onError();
78 |
79 | this.setState({
80 | loading: false,
81 | withError: true,
82 | });
83 | }
84 |
85 | renderImage() {
86 | const {
87 | alt,
88 | src,
89 | style,
90 | className,
91 | } = this.props;
92 |
93 | const {
94 | loading,
95 | withError,
96 | } = this.state;
97 |
98 | const classNames = [
99 | className,
100 | 'media-image',
101 | ];
102 |
103 | const components = [];
104 |
105 | // the loading spinner
106 | // TODO: make this 'LoadingSpinner' component customizable
107 | if (loading) {
108 | components.push( );
109 | }
110 |
111 | // if no loading, then return the
112 | // picture only if no error ocurred
113 | if (!withError) {
114 | components.push( );
123 | }
124 |
125 | // TODO: show a custom message indicating the
126 | // error ocurred while loading the picture
127 |
128 | return components;
129 | }
130 |
131 | render() {
132 | const {
133 | loading,
134 | } = this.state;
135 |
136 | const wrapperClassNames = [
137 | 'picture',
138 | loading && 'loading',
139 | ];
140 |
141 | // render the picture element
142 | const picture = this.renderImage();
143 |
144 | return (
145 |
146 | {picture}
147 |
148 | );
149 | }
150 | }
151 |
152 | Image.propTypes = propTypes;
153 | Image.defaultProps = defaultProps;
154 |
155 | export default Image;
156 |
--------------------------------------------------------------------------------
/example/src/photos.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | photo: "https://source.unsplash.com/aZjw7xI3QAA/1144x763",
4 | caption: "Viñales, Pinar del Río, Cuba",
5 | subcaption: "Photo by Simon Matzinger on Unsplash",
6 | thumbnail: "https://source.unsplash.com/aZjw7xI3QAA/100x67",
7 | },
8 | {
9 | photo: "https://source.unsplash.com/c77MgFOt7e0/1144x763",
10 | caption: "La Habana, Cuba",
11 | subcaption: "Photo by Gerardo Sanchez on Unsplash",
12 | thumbnail: "https://source.unsplash.com/c77MgFOt7e0/100x67",
13 | },
14 | {
15 | photo: "https://source.unsplash.com/QdBHnkBdu4g/1144x763",
16 | caption: "Woman smoking a tobacco",
17 | subcaption: "Photo by Hannah Cauhepe on Unsplash",
18 | thumbnail: "https://source.unsplash.com/QdBHnkBdu4g/100x67",
19 | },
20 | {
21 | photo: "https://source.unsplash.com/YIHRJrdgJqw/1144x763",
22 | caption: "Malecon, La Habana, Cuba",
23 | subcaption: "Photo by Emanuel Haas on Unsplash",
24 | thumbnail: "https://source.unsplash.com/YIHRJrdgJqw/100x67",
25 | },
26 | {
27 | photo: "https://source.unsplash.com/tC_SbXEhS5Y/1144x763",
28 | caption: "Yellow car parked near painted concrete houses",
29 | subcaption: "Photo by James Garman on Unsplash",
30 | thumbnail: "https://source.unsplash.com/tC_SbXEhS5Y/100x67",
31 | },
32 | {
33 | photo: "https://source.unsplash.com/h9Iq22JJlGk/1144x763",
34 | caption: "Cuban brown coffee beans beside white ceramic mug",
35 | subcaption: "Photo by Janko Ferlič on Unsplash",
36 | thumbnail: "https://source.unsplash.com/h9Iq22JJlGk/100x67",
37 | },
38 | {
39 | photo: "https://source.unsplash.com/6NT7jy6OU9I/1144x763",
40 | caption: "Orange car on asphalt road",
41 | subcaption: "Photo by Stéphan Valentin on Unsplash",
42 | thumbnail: "https://source.unsplash.com/6NT7jy6OU9I/100x67",
43 | },
44 | {
45 | photo: "https://source.unsplash.com/-vWmir7fGRM/1144x763",
46 | caption: "Three assorted-colored vintage car on road in Havana",
47 | subcaption: "Photo by Persnickety Prints on Unsplash",
48 | thumbnail: "https://source.unsplash.com/-vWmir7fGRM/100x67",
49 | },
50 | {
51 | photo: "https://source.unsplash.com/PubtV8UJxB8/1144x763",
52 | caption: "Man walking on walkway while holding his bicycle",
53 | subcaption: "Photo by Melanie Dretvic on Unsplash",
54 | thumbnail: "https://source.unsplash.com/PubtV8UJxB8/100x67",
55 | },
56 | {
57 | photo: "https://source.unsplash.com/fwtXC2sP7Tg/1144x763",
58 | caption: "Blue \"Lada\" parked beside pink and green house",
59 | subcaption: "Photo by Arno Smit on Unsplash",
60 | thumbnail: "https://source.unsplash.com/fwtXC2sP7Tg/100x67",
61 | },
62 | {
63 | photo: "https://source.unsplash.com/8AJN9q9Rxqw/1144x763",
64 | caption: "Girl with a parrot",
65 | subcaption: "Photo by Ricardo IV Tamayo on Unsplash",
66 | thumbnail: "https://source.unsplash.com/8AJN9q9Rxqw/100x67",
67 | },
68 | {
69 | photo: "https://source.unsplash.com/Q6LO0SCx3n0/1144x763",
70 | caption: "Classic yellow car",
71 | subcaption: "Photo by Flo P on Unsplash",
72 | thumbnail: "https://source.unsplash.com/Q6LO0SCx3n0/100x67",
73 | },
74 | {
75 | photo: "https://source.unsplash.com/kfxPVP_7P7U/1144x763",
76 | caption: "Cuban boy pointing his finger",
77 | subcaption: "Photo by Craig Philbrick on Unsplash",
78 | thumbnail: "https://source.unsplash.com/kfxPVP_7P7U/100x67",
79 | },
80 | {
81 | photo: "https://source.unsplash.com/kI00pEcN4bg/1144x763",
82 | caption: "Man on a horse in Viñales, Cuba",
83 | subcaption: "Photo by Flo P on Unsplash",
84 | thumbnail: "https://source.unsplash.com/kI00pEcN4bg/100x67",
85 | }
86 | ];
87 |
--------------------------------------------------------------------------------
/tests/test-photos.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | photo: 'https://source.unsplash.com/aZjw7xI3QAA/1144x763',
4 | caption: 'Viñales, Pinar del Río, Cuba',
5 | subcaption: 'Photo by Simon Matzinger on Unsplash',
6 | thumbnail: 'https://source.unsplash.com/aZjw7xI3QAA/100x67',
7 | },
8 | {
9 | photo: 'https://source.unsplash.com/c77MgFOt7e0/1144x763',
10 | caption: 'La Habana, Cuba',
11 | subcaption: 'Photo by Gerardo Sanchez on Unsplash',
12 | thumbnail: 'https://source.unsplash.com/c77MgFOt7e0/100x67',
13 | },
14 | {
15 | photo: 'https://source.unsplash.com/QdBHnkBdu4g/1144x763',
16 | caption: 'Woman smoking a tobacco',
17 | subcaption: 'Photo by Hannah Cauhepe on Unsplash',
18 | thumbnail: 'https://source.unsplash.com/QdBHnkBdu4g/100x67',
19 | },
20 | {
21 | photo: 'https://source.unsplash.com/YIHRJrdgJqw/1144x763',
22 | caption: 'Malecon, La Habana, Cuba',
23 | subcaption: 'Photo by Emanuel Haas on Unsplash',
24 | thumbnail: 'https://source.unsplash.com/YIHRJrdgJqw/100x67',
25 | },
26 | {
27 | photo: 'https://source.unsplash.com/tC_SbXEhS5Y/1144x763',
28 | caption: 'Yellow car parked near painted concrete houses',
29 | subcaption: 'Photo by James Garman on Unsplash',
30 | thumbnail: 'https://source.unsplash.com/tC_SbXEhS5Y/100x67',
31 | },
32 | {
33 | photo: 'https://source.unsplash.com/h9Iq22JJlGk/1144x763',
34 | caption: 'Cuban brown coffee beans beside white ceramic mug',
35 | subcaption: 'Photo by Janko Ferlič on Unsplash',
36 | thumbnail: 'https://source.unsplash.com/h9Iq22JJlGk/100x67',
37 | },
38 | {
39 | photo: 'https://source.unsplash.com/6NT7jy6OU9I/1144x763',
40 | caption: 'Orange car on asphalt road',
41 | subcaption: 'Photo by Stéphan Valentin on Unsplash',
42 | thumbnail: 'https://source.unsplash.com/6NT7jy6OU9I/100x67',
43 | },
44 | {
45 | photo: 'https://source.unsplash.com/-vWmir7fGRM/1144x763',
46 | caption: 'Three assorted-colored vintage car on road in Havana',
47 | subcaption: 'Photo by Persnickety Prints on Unsplash',
48 | thumbnail: 'https://source.unsplash.com/-vWmir7fGRM/100x67',
49 | },
50 | {
51 | photo: 'https://source.unsplash.com/PubtV8UJxB8/1144x763',
52 | caption: 'Man walking on walkway while holding his bicycle',
53 | subcaption: 'Photo by Melanie Dretvic on Unsplash',
54 | thumbnail: 'https://source.unsplash.com/PubtV8UJxB8/100x67',
55 | },
56 | {
57 | photo: 'https://source.unsplash.com/fwtXC2sP7Tg/1144x763',
58 | caption: 'Blue \'Lada\' parked beside pink and green house',
59 | subcaption: 'Photo by Arno Smit on Unsplash',
60 | thumbnail: 'https://source.unsplash.com/fwtXC2sP7Tg/100x67',
61 | },
62 | {
63 | photo: 'https://source.unsplash.com/8AJN9q9Rxqw/1144x763',
64 | caption: 'Girl with a parrot',
65 | subcaption: 'Photo by Ricardo IV Tamayo on Unsplash',
66 | thumbnail: 'https://source.unsplash.com/8AJN9q9Rxqw/100x67',
67 | },
68 | {
69 | photo: 'https://source.unsplash.com/Q6LO0SCx3n0/1144x763',
70 | caption: 'Classic yellow car',
71 | subcaption: 'Photo by Flo P on Unsplash',
72 | thumbnail: 'https://source.unsplash.com/Q6LO0SCx3n0/100x67',
73 | },
74 | {
75 | photo: 'https://source.unsplash.com/kfxPVP_7P7U/1144x763',
76 | caption: 'Cuban boy pointing his finger',
77 | subcaption: 'Photo by Craig Philbrick on Unsplash',
78 | thumbnail: 'https://source.unsplash.com/kfxPVP_7P7U/100x67',
79 | },
80 | {
81 | photo: 'https://source.unsplash.com/kI00pEcN4bg/1144x763',
82 | caption: 'Man on a horse in Viñales, Cuba',
83 | subcaption: 'Photo by Flo P on Unsplash',
84 | thumbnail: 'https://source.unsplash.com/kI00pEcN4bg/100x67',
85 | },
86 | ];
87 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
18 |
19 | ## Code of Conduct
20 |
21 | ### Our Pledge
22 |
23 | In the interest of fostering an open and welcoming environment, we as
24 | contributors and maintainers pledge to making participation in our project and
25 | our community a harassment-free experience for everyone, regardless of age, body
26 | size, disability, ethnicity, gender identity and expression, level of experience,
27 | nationality, personal appearance, race, religion, or sexual identity and
28 | orientation.
29 |
30 | ### Our Standards
31 |
32 | Examples of behavior that contributes to creating a positive environment
33 | include:
34 |
35 | * Using welcoming and inclusive language
36 | * Being respectful of differing viewpoints and experiences
37 | * Gracefully accepting constructive criticism
38 | * Focusing on what is best for the community
39 | * Showing empathy towards other community members
40 |
41 | Examples of unacceptable behavior by participants include:
42 |
43 | * The use of sexualized language or imagery and unwelcome sexual attention or
44 | advances
45 | * Trolling, insulting/derogatory comments, and personal or political attacks
46 | * Public or private harassment
47 | * Publishing others' private information, such as a physical or electronic
48 | address, without explicit permission
49 | * Other conduct which could reasonably be considered inappropriate in a
50 | professional setting
51 |
52 | ### Our Responsibilities
53 |
54 | Project maintainers are responsible for clarifying the standards of acceptable
55 | behavior and are expected to take appropriate and fair corrective action in
56 | response to any instances of unacceptable behavior.
57 |
58 | Project maintainers have the right and responsibility to remove, edit, or
59 | reject comments, commits, code, wiki edits, issues, and other contributions
60 | that are not aligned to this Code of Conduct, or to ban temporarily or
61 | permanently any contributor for other behaviors that they deem inappropriate,
62 | threatening, offensive, or harmful.
63 |
64 | ### Scope
65 |
66 | This Code of Conduct applies both within project spaces and in public spaces
67 | when an individual is representing the project or its community. Examples of
68 | representing a project or community include using an official project e-mail
69 | address, posting via an official social media account, or acting as an appointed
70 | representative at an online or offline event. Representation of a project may be
71 | further defined and clarified by project maintainers.
72 |
73 | ### Enforcement
74 |
75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
76 | reported by contacting the project team at pepalauisaac@gmail.com. All
77 | complaints will be reviewed and investigated and will result in a response that
78 | is deemed necessary and appropriate to the circumstances. The project team is
79 | obligated to maintain confidentiality with regard to the reporter of an incident.
80 | Further details of specific enforcement policies may be posted separately.
81 |
82 | Project maintainers who do not follow or enforce the Code of Conduct in good
83 | faith may face temporary or permanent repercussions as determined by other
84 | members of the project's leadership.
85 |
86 | ### Attribution
87 |
88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
89 | available at [http://contributor-covenant.org/version/1/4][version]
90 |
91 | [homepage]: http://contributor-covenant.org
92 | [version]: http://contributor-covenant.org/version/1/4/
93 |
--------------------------------------------------------------------------------
/example/src/assets/common/_variables.scss:
--------------------------------------------------------------------------------
1 | /** Colors */
2 | $white: #fff;
3 | $primary: #37a866;
4 | $dark: #111;
5 |
6 | /** Common */
7 | $body-color: $dark;
8 |
9 | /** Typography */
10 | $font-family-sans-serif: "Inter", sans-serif;
11 | $font-family-monospace: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
12 |
13 | $font-size-base: 1rem;
14 | $line-height-base: 1.5;
15 |
16 | $font-weight-normal: 400;
17 | $font-weight-bold: 700;
18 |
19 | $font-size-sm: $font-size-base - 0.1;
20 |
21 | $text-color: $body-color;
22 | $text-font-size: $font-size-base;
23 | $text-line-height: $line-height-base;
24 | $text-link-padding: 0 0.12rem;
25 | $text-link-background: #F7FAFC;
26 | $text-link-background-hover: #C6F6D5;
27 | $text-spacing: 0 0 1rem;
28 |
29 | /** Headings */
30 | $heading-color: $body-color;
31 | $heading-spacing: 0 auto 0.8rem;
32 | $heading-line-height: 1.5;
33 | $heading-font-weight: 700;
34 |
35 | $h1-font-size: 42px;
36 | $h2-font-size: 36px;
37 | $h3-font-size: 28px;
38 | $h4-font-size: 25px;
39 | $h5-font-size: 20px;
40 | $h6-font-size: 15px;
41 |
42 | /** Content */
43 | $container-max-width: 800px;
44 | $container-gutter: 15px;
45 |
46 | /** Links */
47 | $link-color: $primary;
48 | $link-hover-color: $body-color;
49 |
50 | /** Code */
51 | $code-font-family: $font-family-monospace;
52 | $code-background-color: transparent;
53 | $code-text-align: left;
54 | $code-line-height: $line-height-base;
55 | $code-tab-size: 2;
56 | $code-font-weight: $font-weight-normal;
57 |
58 | /** Buttons */
59 | $btn-border-width: 1px;
60 | $btn-border-style: solid;
61 | $btn-border-color: transparent;
62 | $btn-border: $btn-border-width $btn-border-style $btn-border-color;
63 | $btn-border-radius: 2px;
64 | $btn-box-shadow: none;
65 |
66 | $btn-primary-color: $white;
67 | $btn-primary-background: #48bb78;
68 | $btn-primary-border: #48bb78;
69 | $btn-primary-hover-color: $white;
70 | $btn-primary-hover-background: #2f855a;
71 | $btn-primary-hover-border: #2f855a;
72 | $btn-primary-outline-color: #48BB78;
73 | $btn-primary-outline-background: transparent;
74 | $btn-primary-outline-border: #48BB78;
75 | $btn-primary-outline-hover-color: $white;
76 | $btn-primary-outline-hover-background: #48bb78;
77 | $btn-primary-outline-hover-border: #48bb78;
78 |
79 | $btn-secondary-color: #1A202C;
80 | $btn-secondary-background: #EDF2F7;
81 | $btn-secondary-border: #EDF2F7;
82 | $btn-secondary-hover-color: $white;
83 | $btn-secondary-hover-background: #1A202C;
84 | $btn-secondary-hover-border: #1A202C;
85 | $btn-secondary-outline-color: #1A202C;
86 | $btn-secondary-outline-background: transparent;
87 | $btn-secondary-outline-border: #E2E8F0;
88 | $btn-secondary-outline-hover-color: $white;
89 | $btn-secondary-outline-hover-background: #1A202C;
90 | $btn-secondary-outline-hover-border: #1A202C;
91 |
92 | $btn-line-height: 1.6;
93 | $btn-height: 44px;
94 | $btn-padding-y: 0;
95 | $btn-padding-x: 20px;
96 | $btn-font-size: 15px;
97 | $btn-border-width: 2px;
98 | $btn-border-radius: 3px;
99 | $btn-font-weight: $font-weight-bold;
100 |
101 | $btn-small-line-height: 1.6;
102 | $btn-small-height: 36px;
103 | $btn-small-padding-y: 0;
104 | $btn-small-padding-x: 14px;
105 | $btn-small-font-size: 12px;
106 | $btn-small-border-width: 1px;
107 | $btn-small-border-radius: 4px;
108 | $btn-small-font-weight: $font-weight-normal;
109 |
110 | $btn-large-line-height: 1.6;
111 | $btn-large-height: 54px;
112 | $btn-large-padding-y: 0;
113 | $btn-large-padding-x: 24px;
114 | $btn-large-font-size: 17px;
115 | $btn-large-border-width: 2px;
116 | $btn-large-border-radius: 4px;
117 | $btn-large-font-weight: $font-weight-bold;
118 |
119 | /** Header */
120 | $header-background-color: $white;
121 | $header-padding-x: 0;
122 | $header-padding-y: 0.25rem;
123 | $header-navigation-spacing: 1em;
124 | $header-container-max-width: $container-max-width;
125 | $header-border: 4px solid #48BB78;
126 | $header-border-bottom: 1px solid #EDF2F7;
127 |
128 | /** Footer */
129 | $footer-background: #1A202C;
130 | $footer-font-size: $font-size-sm;
131 | $footer-color: #A0AEC0;
132 | $footer-link-color: $white;
133 | $footer-padding: 2em 1em;
134 |
135 | /** Tables */
136 | $table-color: $body-color;
137 | $table-border: none;
138 | $table-odd-background: transparent;
139 | $table-spacing: 1rem;
140 | $table-background-color: $white;
141 | $table-font-size: $font-size-sm;
142 | $table-line-height: $line-height-base;
143 | $table-cell-padding: 0.85rem 2rem 0.85rem 0;
144 | $table-box-shadow: none;
145 | $table-cell-border: 1px solid #E2E8F0;
146 |
147 | /** Prims */
148 | $prism-color: #F7FAFC;
149 | $prism-background: #2D3748;
150 | $prism-font-family: $font-family-monospace;
151 | $prism-line-height: 22px;
152 | $prism-text-shadow: none;
153 | $prism-border-radius: 0.5rem;
154 | $prism-tab-size: 2;
155 | $prism-block-border-radius: $prism-border-radius;
156 | $prism-block-padding: 0 0 1em;
157 | $prism-block-margin: 0.5rem 0 1.5rem;
158 | $prism-code-border-radius: $prism-border-radius;
159 | $prism-code-padding: 0.1em;
160 |
161 | /** Photo Grid */
162 | $photo-grid-gutter: 6px;
163 | $photo-grid-col-max-size: 12%;
164 | $photo-grid-picture-spacing: ($photo-grid-gutter / 2) 0;
165 | $photo-grid-picture-background: #EDF2F7;
166 | $photo-grid-picture-background-loaded: #1A202C;
167 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-bnb-gallery",
3 | "version": "1.4.4",
4 | "description": "Simple photo gallery based on React and Airbnb image gallery",
5 | "author": "Pedro Enrique Palau ",
6 | "homepage": "https://peterpalau.github.io/react-bnb-gallery/",
7 | "keywords": [
8 | "react",
9 | "gallery",
10 | "photos",
11 | "carousel",
12 | "image gallery",
13 | "photo gallery",
14 | "responsive",
15 | "slider",
16 | "component",
17 | "modal gallery",
18 | "images",
19 | "react image gallery",
20 | "react photo gallery",
21 | "react modal gallery",
22 | "react gallery"
23 | ],
24 | "repository": {
25 | "type": "git",
26 | "url": "git@github.com:peterpalau/react-bnb-gallery.git"
27 | },
28 | "license": "MIT",
29 | "main": "dist/index.js",
30 | "module": "dist/index.es.js",
31 | "jsnext:main": "dist/index.es.js",
32 | "typings": "index.d.ts",
33 | "files": [
34 | "dist",
35 | "index.d.ts",
36 | "styles.css"
37 | ],
38 | "bugs": {
39 | "url": "https://github.com/peterpalau/react-bnb-gallery/issues"
40 | },
41 | "scripts": {
42 | "test": "jest --no-cache",
43 | "test:watch": "jest --watch",
44 | "test:coverage": "jest --coverage --colors",
45 | "build": "rollup -c",
46 | "start": "rollup -c -w",
47 | "prepublishOnly": "yarn build",
48 | "predeploy": "yarn && yarn build",
49 | "deploy": "gh-pages -d example/build",
50 | "lint": "yarn lint:scripts && yarn lint:css",
51 | "lint:scripts": "eslint --ext js src tests",
52 | "lint:css": "stylelint \"src/scss/*.{css,sass,scss,sss,less}\"",
53 | "travis": "yarn test",
54 | "preview": "yarn preview:build && yarn preview:deploy",
55 | "preview:build": "yarn build",
56 | "preview:deploy": "cd ./docs && yarn export"
57 | },
58 | "peerDependencies": {
59 | "react": "^15.0.0 || ^16.0.0",
60 | "react-dom": "^15.0.0 || ^16.0.0"
61 | },
62 | "devDependencies": {
63 | "@babel/core": "^7.10.4",
64 | "@babel/plugin-external-helpers": "^7.10.4",
65 | "@babel/plugin-proposal-class-properties": "^7.10.4",
66 | "@babel/plugin-proposal-decorators": "^7.10.4",
67 | "@babel/plugin-proposal-do-expressions": "^7.10.4",
68 | "@babel/plugin-proposal-export-default-from": "^7.10.4",
69 | "@babel/plugin-proposal-export-namespace-from": "^7.10.4",
70 | "@babel/plugin-proposal-function-bind": "^7.10.4",
71 | "@babel/plugin-proposal-function-sent": "^7.10.4",
72 | "@babel/plugin-proposal-json-strings": "^7.10.4",
73 | "@babel/plugin-proposal-logical-assignment-operators": "^7.10.4",
74 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
75 | "@babel/plugin-proposal-numeric-separator": "^7.10.4",
76 | "@babel/plugin-proposal-optional-chaining": "^7.10.4",
77 | "@babel/plugin-proposal-pipeline-operator": "^7.10.4",
78 | "@babel/plugin-proposal-throw-expressions": "^7.10.4",
79 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
80 | "@babel/plugin-syntax-import-meta": "^7.10.4",
81 | "@babel/plugin-transform-object-assign": "^7.10.4",
82 | "@babel/preset-env": "^7.10.4",
83 | "@babel/preset-react": "^7.10.4",
84 | "@babel/runtime": "^7.10.4",
85 | "@rollup/plugin-babel": "^5.0.4",
86 | "@rollup/plugin-commonjs": "^14.0.0",
87 | "@rollup/plugin-url": "^5.0.1",
88 | "@svgr/rollup": "^5.4.0",
89 | "@welldone-software/why-did-you-render": "^4.2.5",
90 | "airbnb-js-shims": "^2.2.1",
91 | "babel-core": "7.0.0-bridge.0",
92 | "babel-eslint": "^10.1.0",
93 | "babel-jest": "^26.1.0",
94 | "babel-preset-airbnb": "^5.0.0",
95 | "cross-env": "^7.0.2",
96 | "enzyme": "^3.11.0",
97 | "enzyme-adapter-react-16": "^1.15.2",
98 | "enzyme-to-json": "^3.5.0",
99 | "eslint": "^7.4.0",
100 | "eslint-config-airbnb": "^18.2.0",
101 | "eslint-plugin-import": "^2.22.0",
102 | "eslint-plugin-jest": "^23.18.0",
103 | "eslint-plugin-jsx-a11y": "^6.3.1",
104 | "eslint-plugin-react": "^7.20.3",
105 | "eslint-plugin-react-hooks": "^4.0.6",
106 | "gh-pages": "^3.1.0",
107 | "identity-obj-proxy": "^3.0.0",
108 | "jest": "^26.1.0",
109 | "jest-environment-enzyme": "^7.0.2",
110 | "jest-enzyme": "^7.0.1",
111 | "prettier": "^2.0.5",
112 | "react": "^16.13.1",
113 | "react-dom": "^16.13.1",
114 | "react-scripts": "^3.4.1",
115 | "react-test-renderer": "^16.13.1",
116 | "regenerator-runtime": "^0.13.5",
117 | "rollup": "^2.21.0",
118 | "rollup-plugin-node-resolve": "^5.2.0",
119 | "rollup-plugin-peer-deps-external": "^2.2.3",
120 | "rollup-plugin-postcss": "^3.1.2",
121 | "rollup-plugin-scss": "^2.5.0",
122 | "rollup-plugin-stylelint": "^1.0.0",
123 | "stylelint": "^13.6.1",
124 | "stylelint-config-standard": "^20.0.0"
125 | },
126 | "dependencies": {
127 | "airbnb-prop-types": "^2.16.0",
128 | "ajv": "^6.12.3",
129 | "classnames": "^2.2.6",
130 | "focus-trap-react": "^7.0.1",
131 | "lodash": "^4.17.19",
132 | "object.assign": "^4.1.0",
133 | "object.values": "^1.1.1",
134 | "prop-types": "^15.7.2",
135 | "react-portal": "^4.2.1"
136 | },
137 | "optionalDependencies": {
138 | "typescript": "^3.6.2"
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/components/Caption/Caption.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import classnames from 'classnames';
5 | import noop from '../../utils/noop';
6 |
7 | import Thumbnail from '../Thumbnail';
8 | import TogglePhotoList from '../TogglePhotoList';
9 |
10 | import calculateThumbnailsContainerDimension from '../../utils/calculateThumbnailsContainerDimension';
11 | import calculateThumbnailsLeftScroll from '../../utils/calculateThumbnailsLeftScroll';
12 |
13 | import defaultPhrases from '../../defaultPhrases';
14 | import getPhrasePropTypes from '../../utils/getPhrasePropTypes';
15 |
16 | import {
17 | forbidExtraProps,
18 | nonNegativeInteger,
19 | } from '../../common/prop-types';
20 |
21 | import PhotosShape from '../../shapes/PhotosShape';
22 |
23 | const propTypes = forbidExtraProps({
24 | showThumbnails: PropTypes.bool,
25 | current: nonNegativeInteger,
26 | photos: PhotosShape,
27 | onPress: PropTypes.func,
28 | phrases: PropTypes.shape(getPhrasePropTypes(defaultPhrases)),
29 | });
30 |
31 | const defaultProps = {
32 | showThumbnails: true,
33 | current: 0,
34 | photos: [],
35 | onPress: noop,
36 | phrases: defaultPhrases,
37 | };
38 |
39 | class Caption extends PureComponent {
40 | constructor(props) {
41 | super(props);
42 | const {
43 | showThumbnails,
44 | photos,
45 | } = this.props;
46 |
47 | this.state = {
48 | showThumbnails,
49 | };
50 |
51 | this.thumbnailsWrapperRef = null;
52 | this.thumbnailsListRef = null;
53 | this.hasMoreThanOnePhoto = photos.length > 1;
54 | this.onThumbnailPress = this.onThumbnailPress.bind(this);
55 | this.setGalleryFigcaptionRef = this.setGalleryFigcaptionRef.bind(this);
56 | this.setGalleryThubmanilsRef = this.setGalleryThubmanilsRef.bind(this);
57 | this.toggleThumbnails = this.toggleThumbnails.bind(this);
58 | }
59 |
60 | componentDidUpdate(prevProps) {
61 | const { current } = this.props;
62 | if (current !== prevProps.current) {
63 | this.setThumbnailsWrapperScrollLeft(current);
64 | }
65 | }
66 |
67 | onThumbnailPress(event) {
68 | const {
69 | onPress,
70 | photos,
71 | } = this.props;
72 | const index = parseInt(event.currentTarget.dataset.photoIndex, 10);
73 | if (index >= 0 && index <= photos.length - 1) {
74 | onPress(index);
75 | }
76 | }
77 |
78 | setThumbnailsWrapperScrollLeft(current) {
79 | const { photos } = this.props;
80 | const bounding = this.thumbnailsWrapperRef.getBoundingClientRect();
81 | const scrollLeft = calculateThumbnailsLeftScroll(current, photos.length, bounding);
82 | this.thumbnailsListRef.style.marginLeft = `${scrollLeft}px`;
83 | }
84 |
85 | getPhotoByIndex(index) {
86 | const { photos } = this.props;
87 | return photos[index];
88 | }
89 |
90 | setGalleryFigcaptionRef(element) {
91 | this.thumbnailsWrapperRef = element;
92 | }
93 |
94 | setGalleryThubmanilsRef(element) {
95 | this.thumbnailsListRef = element;
96 | }
97 |
98 | toggleThumbnails() {
99 | this.setState((prevState) => ({
100 | showThumbnails: !prevState.showThumbnails,
101 | }));
102 | }
103 |
104 | renderThumbnail(photo, index, onPress) {
105 | const { current } = this.props;
106 |
107 | return (
108 |
114 | );
115 | }
116 |
117 | render() {
118 | const {
119 | current,
120 | photos,
121 | phrases,
122 | } = this.props;
123 |
124 | const {
125 | showThumbnails,
126 | } = this.state;
127 |
128 | const className = classnames(
129 | 'gallery-figcaption',
130 | !showThumbnails && 'hide',
131 | );
132 |
133 | const currentPhoto = this.getPhotoByIndex(current);
134 | const captionThumbnailsWrapperWidth = calculateThumbnailsContainerDimension(photos.length);
135 |
136 | return (
137 |
138 |
139 |
140 |
141 |
142 | {currentPhoto.caption && (
143 |
144 | {currentPhoto.caption}
145 |
146 | )}
147 | {currentPhoto.subcaption && (
148 |
149 | {currentPhoto.subcaption}
150 |
151 | )}
152 |
153 | {this.hasMoreThanOnePhoto && (
154 |
155 |
160 |
161 | )}
162 |
163 | {this.hasMoreThanOnePhoto && (
164 |
169 |
175 |
179 | {photos.map((photo, index) => (
180 |
181 | {this.renderThumbnail(photo, index, this.onThumbnailPress)}
182 |
183 | ))}
184 |
185 |
186 |
187 | )}
188 |
189 |
190 |
191 | );
192 | }
193 | }
194 |
195 | Caption.propTypes = propTypes;
196 | Caption.defaultProps = defaultProps;
197 |
198 | export default Caption;
199 |
--------------------------------------------------------------------------------
/src/ReactBnbGallery.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
2 | /* eslint-disable react/no-unused-prop-types */
3 | import React, { Component } from 'react';
4 | import PropTypes from 'prop-types';
5 | import FocusTrap from 'focus-trap-react';
6 | import { Portal } from 'react-portal';
7 |
8 | import omit from 'lodash/omit';
9 | import classnames from 'classnames';
10 |
11 | import Gallery from './components/Gallery';
12 | import CloseButton from './components/CloseButton';
13 |
14 | import opacityValidation from './common/opacityValidation';
15 | import noop from './utils/noop';
16 | import getPhotos from './utils/getPhotos';
17 |
18 | import {
19 | ARROW_LEFT_KEYCODE,
20 | ARROW_RIGHT_KEYCODE,
21 | ESC_KEYCODE,
22 | DEFAULT_OPACITY,
23 | DEFAULT_Z_INDEX,
24 | } from './constants';
25 |
26 | import {
27 | galleryPropTypes,
28 | galleryDefaultProps,
29 | } from './common';
30 |
31 | import {
32 | forbidExtraProps,
33 | nonNegativeInteger,
34 | } from './common/prop-types';
35 |
36 | import './scss/style.scss';
37 |
38 | const propTypes = forbidExtraProps({
39 | ...galleryPropTypes,
40 | leftKeyPressed: PropTypes.func,
41 | onClose: PropTypes.func,
42 | rightKeyPressed: PropTypes.func,
43 | show: PropTypes.bool,
44 | keyboard: PropTypes.bool,
45 | opacity: opacityValidation,
46 | zIndex: nonNegativeInteger,
47 | });
48 |
49 | const defaultProps = {
50 | ...galleryDefaultProps,
51 | leftKeyPressed: noop,
52 | onClose: noop,
53 | rightKeyPressed: noop,
54 | show: false,
55 | keyboard: true,
56 | opacity: DEFAULT_OPACITY,
57 | zIndex: DEFAULT_Z_INDEX,
58 | };
59 |
60 | class ReactBnbGallery extends Component {
61 | constructor(props) {
62 | super(props);
63 | this.state = {
64 | photos: null,
65 | };
66 | this.gallery = React.createRef();
67 | this.close = this.close.bind(this);
68 | this.onKeyDown = this.onKeyDown.bind(this);
69 | }
70 |
71 | static getDerivedStateFromProps(props, state) {
72 | if (props.photos !== state.photos) {
73 | return {
74 | photos: getPhotos(props.photos),
75 | };
76 | }
77 | return null;
78 | }
79 |
80 | onKeyDown(event) {
81 | if (/input|textarea/i.test(event.target.tagName)) {
82 | return;
83 | }
84 |
85 | switch (event.which) {
86 | case ESC_KEYCODE:
87 | event.preventDefault();
88 | this.close();
89 | break;
90 |
91 | case ARROW_LEFT_KEYCODE:
92 | event.preventDefault();
93 | this.gallery.current.prev();
94 | break;
95 |
96 | case ARROW_RIGHT_KEYCODE:
97 | event.preventDefault();
98 | this.gallery.current.next();
99 | break;
100 |
101 | default:
102 | }
103 | }
104 |
105 | getModalOverlayStyles() {
106 | const {
107 | opacity,
108 | backgroundColor,
109 | } = this.props;
110 |
111 | return {
112 | opacity,
113 | backgroundColor,
114 | };
115 | }
116 |
117 | close() {
118 | const { onClose } = this.props;
119 | onClose();
120 | }
121 |
122 | render() {
123 | const {
124 | show,
125 | phrases,
126 | keyboard,
127 | light,
128 | zIndex,
129 | } = this.props;
130 |
131 | const { photos } = this.state;
132 |
133 | if (!show) {
134 | return null; // nothing to return
135 | }
136 |
137 | const {
138 | wrap,
139 | activePhotoIndex,
140 | activePhotoPressed,
141 | direction,
142 | nextButtonPressed,
143 | prevButtonPressed,
144 | showThumbnails,
145 | preloadSize,
146 | } = omit(this.props, [
147 | 'onClose',
148 | 'leftKeyPressed',
149 | 'rightKeyPressed',
150 | 'show',
151 | 'photos',
152 | 'opacity',
153 | 'backgroundColor',
154 | 'zIndex',
155 | 'keyboard',
156 | ]);
157 |
158 | // modal overlay customization styles
159 | const galleryModalOverlayStyles = this.getModalOverlayStyles();
160 |
161 | const modalStyle = {
162 | zIndex,
163 | };
164 |
165 | return (
166 |
167 |
168 |
178 |
182 |
183 |
184 |
185 |
186 |
187 |
191 |
192 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 | );
220 | }
221 | }
222 |
223 | ReactBnbGallery.propTypes = propTypes;
224 | ReactBnbGallery.defaultProps = defaultProps;
225 |
226 | export default ReactBnbGallery;
227 |
--------------------------------------------------------------------------------
/example/src/pages/Examples/constants.js:
--------------------------------------------------------------------------------
1 | import SpanishPhrases from './localization/es';
2 |
3 | const NO_TITLE = '-';
4 |
5 | export const DEMO_PHOTOS = {
6 | earth: [{
7 | photo: 'https://source.unsplash.com/iQRKBNKyRpo',
8 | caption: "Teutoburg Forest, Germany",
9 | subcaption: "Philipp Pilz",
10 | thumbnail: "https://source.unsplash.com/iQRKBNKyRpo/100x67",
11 | }, {
12 | photo: 'https://source.unsplash.com/M-rww53f9A0',
13 | caption: "La Palma, Spain",
14 | subcaption: "Evgeni Tcherkasski",
15 | thumbnail: "https://source.unsplash.com/M-rww53f9A0/100x67",
16 | }, {
17 | photo: 'https://source.unsplash.com/9QF90iLO0q0',
18 | caption: "Planet Earth",
19 | subcaption: "Joshua Fuller",
20 | thumbnail: "https://source.unsplash.com/9QF90iLO0q0/100x67",
21 | }, {
22 | photo: 'https://source.unsplash.com/6xn0wsneuY8',
23 | caption: "White Sands National Monument, United States",
24 | subcaption: "Ben Klea",
25 | thumbnail: "https://source.unsplash.com/6xn0wsneuY8/100x67",
26 | }, {
27 | photo: 'https://source.unsplash.com/UYpsXT7lpyc',
28 | caption: "Unnamed Road, Obukhivs'kyi district, Ukraine",
29 | subcaption: "Anton Sharov",
30 | thumbnail: "https://source.unsplash.com/UYpsXT7lpyc/100x67",
31 | }, {
32 | photo: 'https://source.unsplash.com/mFJu6intdkQ',
33 | caption: "Conversations With Shadows, Cheddar Gorge, England",
34 | subcaption: "Rob Potter",
35 | thumbnail: "https://source.unsplash.com/mFJu6intdkQ/100x67",
36 | }, {
37 | photo: 'https://source.unsplash.com/7cAiUMWPjnY',
38 | caption: "Mývatn, Iceland",
39 | subcaption: "Joshua Earle",
40 | thumbnail: "https://source.unsplash.com/7cAiUMWPjnY/100x67",
41 | }, {
42 | photo: 'https://source.unsplash.com/IHGievc9q70',
43 | caption: "Latvia",
44 | subcaption: "Ralph Blvmberg",
45 | thumbnail: "https://source.unsplash.com/IHGievc9q70/100x67",
46 | }, {
47 | photo: 'https://source.unsplash.com/VivzPEYabew',
48 | caption: NO_TITLE,
49 | subcaption: "Jez Timms",
50 | thumbnail: "https://source.unsplash.com/VivzPEYabew/100x67",
51 | }, {
52 | photo: 'https://source.unsplash.com/nCQeEV8npmE',
53 | caption: "Keyhole Arch, Big Sur, CA, USA",
54 | subcaption: "Aaron Roth",
55 | thumbnail: "https://source.unsplash.com/nCQeEV8npmE/100x67",
56 | }],
57 | entrepreneur: [{
58 | photo: 'https://source.unsplash.com/R3VGPOttrNc',
59 | caption: "Frakcia, Kazan, Russia",
60 | subcaption: "Rashid Sadykov",
61 | thumbnail: "https://source.unsplash.com/R3VGPOttrNc/100x67",
62 | }, {
63 | photo: 'https://source.unsplash.com/wdrvmCAJqW4',
64 | caption: NO_TITLE,
65 | subcaption: "Thit Htoo Zaw",
66 | thumbnail: "https://source.unsplash.com/wdrvmCAJqW4/100x67",
67 | }, {
68 | photo: 'https://source.unsplash.com/4Yv84VgQkRM',
69 | caption: "Lagos, Nigeria",
70 | subcaption: "Prince Akachi",
71 | thumbnail: "https://source.unsplash.com/4Yv84VgQkRM/100x67",
72 | }, {
73 | photo: 'https://source.unsplash.com/WWk2icWFJtk',
74 | caption: "Sydney, Australia",
75 | subcaption: "Simon Rae",
76 | thumbnail: "https://source.unsplash.com/WWk2icWFJtk/100x67",
77 | }, {
78 | photo: 'https://source.unsplash.com/XkKCui44iM0',
79 | caption: NO_TITLE,
80 | subcaption: "Priscilla Du Preez",
81 | thumbnail: "https://source.unsplash.com/XkKCui44iM0/100x67",
82 | }, {
83 | photo: 'https://source.unsplash.com/RZdPw7eIkdk',
84 | caption: NO_TITLE,
85 | subcaption: "Zhu Hongzhi",
86 | thumbnail: "https://source.unsplash.com/RZdPw7eIkdk/100x67",
87 | }, {
88 | photo: 'https://source.unsplash.com/F5ae6ys2Mp8',
89 | caption: "Sydney, Australia",
90 | subcaption: "Trent Szmolnik",
91 | thumbnail: "https://source.unsplash.com/F5ae6ys2Mp8/100x67",
92 | }, {
93 | photo: 'https://source.unsplash.com/-WBYxmW4yuw',
94 | caption: "Timișoara, Romania",
95 | subcaption: "Alexandru Acea",
96 | thumbnail: "https://source.unsplash.com/-WBYxmW4yuw/100x67",
97 | }, {
98 | photo: 'https://source.unsplash.com/_y0tTb95xeY',
99 | caption: NO_TITLE,
100 | subcaption: "Julian Christian Anderson",
101 | thumbnail: "https://source.unsplash.com/_y0tTb95xeY/100x67",
102 | }, {
103 | photo: 'https://source.unsplash.com/C_L61Njf3do',
104 | caption: NO_TITLE,
105 | subcaption: "Kevin Bhagat",
106 | thumbnail: "https://source.unsplash.com/C_L61Njf3do/100x67",
107 | }]
108 | };
109 |
110 | export const DEMOS = [{
111 | name: 'no-configuration',
112 | label: 'Default props',
113 | description: null,
114 | config: {
115 | photos: DEMO_PHOTOS.earth,
116 | },
117 | code: `
118 | {
119 | // This prop is required
120 | photos: [{
121 | photo: ...,
122 | caption: ...,
123 | subcaption: ...,
124 | thumbnail: ...,
125 | }],
126 | }
127 | `,
128 | }, {
129 | name: 'localization',
130 | label: 'Localization (Spanish)',
131 | description: null,
132 | config: {
133 | photos: DEMO_PHOTOS.entrepreneur,
134 | phrases: SpanishPhrases,
135 | showThumbnails: true,
136 | },
137 | code: `
138 | {
139 | photos: [...],
140 | // More phrases comming...
141 | phrases: {
142 | noPhotosProvided: ...,
143 | showPhotoList: ...,
144 | hidePhotoList: ...,
145 | }
146 | }
147 | `,
148 | }, {
149 | name: 'wrap',
150 | label: 'Wrap',
151 | description: null,
152 | config: {
153 | photos: DEMO_PHOTOS.earth,
154 | wrap: true,
155 | },
156 | code: `
157 | {
158 | photos: [...],
159 | // Set to false, if you
160 | // want an infinity slideshow
161 | wrap: true,
162 | }
163 | `,
164 | }, {
165 | name: 'keyboard',
166 | label: 'Keyboard navigation',
167 | description: null,
168 | config: {
169 | photos: DEMO_PHOTOS.earth,
170 | keyboard: true,
171 | },
172 | code: `
173 | {
174 | photos: [...],
175 | // The keyboard navigation
176 | // uses , and
177 | keyboard: true,
178 | }
179 | `,
180 | }, {
181 | name: 'no-thumbnails',
182 | label: 'No thumbnails',
183 | description: null,
184 | config: {
185 | photos: DEMO_PHOTOS.earth,
186 | showThumbnails: false,
187 | },
188 | code: `
189 | {
190 | photos: [...],
191 | // When is false, the thumbnails
192 | // and caption are hidden
193 | showThumbnails: false,
194 | }
195 | `,
196 | }, {
197 | name: 'callbacks',
198 | label: 'Callbacks',
199 | description: null,
200 | config: {
201 | photos: DEMO_PHOTOS.earth,
202 | activePhotoPressed: () => {},
203 | leftKeyPressed: () => {},
204 | nextButtonPressed: () => {},
205 | prevButtonPressed: () => {},
206 | rightKeyPressed: () => {},
207 | },
208 | code: `
209 | {
210 | photos: [...],
211 | // Current photo clicked
212 | activePhotoPressed: () => {},
213 | // key pressed
214 | leftKeyPressed: () => {},
215 | nextButtonPressed: () => {},
216 | // For closing the gallery
217 | onClose: () => {},
218 | prevButtonPressed: () => {},
219 | // key pressed
220 | rightKeyPressed: () => {},
221 | }
222 | `,
223 | }];
224 |
--------------------------------------------------------------------------------