├── .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 | 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 | 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 | 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 |
      29 | {items} 30 |
    31 | ); 32 | } 33 | 34 | render() { 35 | return ( 36 |
    37 | 38 |
    39 |
    40 | 41 |
    42 | 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 |