├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .storybook └── main.js ├── .stylelintrc.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── examples ├── home │ ├── App.js │ └── index.js ├── image-carousel │ ├── App.js │ ├── index.html │ └── index.js ├── index.html ├── multi-rows-carousel │ ├── App.js │ ├── index.html │ └── index.js ├── news-carousel │ ├── App.js │ ├── index.html │ ├── index.js │ └── mockNewsList.json ├── product-carousel │ ├── App.js │ ├── index.html │ ├── index.js │ └── mockProductList.json ├── responsive-carousel │ ├── App.js │ ├── index.html │ └── index.js ├── simple-sample │ ├── App.js │ ├── index.html │ └── index.js ├── testimonial-carousel │ ├── App.js │ ├── index.html │ ├── index.js │ └── mockTestimonialList.json ├── tour-carousel │ ├── App.js │ ├── cities.json │ ├── index.html │ └── index.js └── webpack.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── app.js ├── components │ ├── ArrowButton.js │ ├── Carousel.js │ └── Dot.js ├── hooks │ └── responsiveLayoutHook.js └── utils │ └── resizeListener.js └── stories └── Carousel.stories.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["babel-plugin-styled-components"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | end_of_line = lf 4 | insert_final_newline = true 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": ["eslint:recommended", "plugin:react/recommended"], 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": 2018, 17 | "sourceType": "module" 18 | }, 19 | "plugins": ["react", "react-hooks"], 20 | "rules": { 21 | "react/prop-types": 0, 22 | "react-hooks/rules-of-hooks": "error", 23 | "react-hooks/exhaustive-deps": "warn" 24 | }, 25 | "settings": { 26 | "react": { 27 | "version": "detect" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "endOfLine": "lf" 5 | } 6 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../stories/**/*.stories.js'], 3 | addons: [ 4 | '@storybook/addon-actions', 5 | '@storybook/addon-links', 6 | '@storybook/addon-viewport/register', 7 | '@storybook/addon-knobs/register' 8 | ], 9 | webpackFinal: async config => { 10 | // do mutation to the config 11 | 12 | return config 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "processors": ["stylelint-processor-styled-components"], 3 | "extends": [ 4 | "stylelint-config-standard", 5 | "stylelint-config-styled-components", 6 | "stylelint-config-prettier" 7 | ], 8 | "rules": { 9 | "declaration-colon-newline-after": null 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at z3388638@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 YY Chang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM](https://nodei.co/npm/react-grid-carousel.png?compact=true)](https://nodei.co/npm/react-grid-carousel/) 2 | [![GitHub license](https://img.shields.io/github/license/x3388638/react-grid-carousel)](https://github.com/x3388638/react-grid-carousel/blob/master/LICENSE) [![npm version](https://img.shields.io/npm/v/react-grid-carousel)](https://www.npmjs.com/package/react-grid-carousel) [![Open Source](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://opensource.org/) 3 | 4 |

React Grid Carousel

5 |

React responsive carousel component w/ grid layout
to easily create a carousel like photo gallery, shopping product card or anything you want

6 | 7 |

8 | 9 | 10 | 11 |

12 | 13 | ## Features 14 | 15 | - RWD 16 | - Multiple items 17 | - Multiple rows 18 | - Infinite loop 19 | - Support any component as a item to put into grid 20 | - Show/hide dots 21 | - Show/hide arrow buttons 22 | - Autoplay 23 | - Enable/Disable `scroll-snap` for each item on mobile device 24 | - Customized layout (cols & rows) for different breakpoint 25 | - Customized arrow button 26 | - Customized dots 27 | - Support SSR 28 | 29 | ## Install 30 | 31 | ```bash 32 | $ npm install react-grid-carousel --save 33 | ``` 34 | 35 | ## Usage 36 | 37 | Just import the `Carousel` component from `react-grid-carousel` 38 | and put your item into `Carousel.Item` 39 | 40 | ```javascript 41 | import React from 'react' 42 | import Carousel from 'react-grid-carousel' 43 | 44 | const Gallery = () => { 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {/* anything you want to show in the grid */} 58 | 59 | {/* ... */} 60 | 61 | ) 62 | } 63 | ``` 64 | 65 | ## Props 66 | 67 | | Prop | Type | Default | Description | 68 | | ------------------------------------- | ---------------- | --------- | ----------------------------------------------------------------------------------- | 69 | | cols | Number | 1 | Column amount rendered per page | 70 | | rows | Number | 1 | Row amount rendered per page | 71 | | gap | Number \| String | 10 | Margin (grid-gap) for each item/grid in px or %, passed Number will turn to px unit | 72 | | loop | Boolean | false | Infinite loop or not | 73 | | scrollSnap | Boolean | true | `true` for applying `scroll-snap` to items on mobile | 74 | | hideArrow | Boolean | false | Show/hide the arrow prev/next buttons | 75 | | showDots | Boolean | false | Show dots indicate the current page on desktop mode | 76 | | autoplay | Number | | Autoplay timeout in ms; `undefined` for autoplay disabled | 77 | | dotColorActive | String | '#795548' | Valid css color value for active dot | 78 | | dotColorInactive | String | '#ccc' | Valid css color value for inactive dot | 79 | | [responsiveLayout](#responsiveLayout) | Array | | Customized cols & rows on different viewport size | 80 | | mobileBreakpoint | Number | 767 | The breakpoint(px) to switch to default mobile layout | 81 | | arrowLeft | Element | | Customized left arrow button | 82 | | arrowRight | Element | | Customized left arrow button | 83 | | [dot](#dot) | Element | | Customized dot component with prop `isActive` | 84 | | containerStyle | Object | | Style object for carousel container | 85 | 86 | ### responsiveLayout 87 | 88 | Array of layout settings for each breakbpoint 89 | 90 | #### Setting options 91 | 92 | - `breakpoint`: Number; Requried; Equals to `max-width` used in media query, in px unit 93 | - `cols`: Number; Column amount in specific breakpoint 94 | - `rows`: Number; Row amount in specific breakpoint 95 | - `gap`: Number | String; Gap size in specific breakpoint 96 | - `loop`: Boolean; Infinite loop in specific breakpoint 97 | - `autoplay`: Number; autoplay timeout(ms) in specific breakpoint; `undefined` for autoplay disabled 98 | 99 | e.g. 100 | 101 | ``` 102 | [ 103 | { 104 | breakpoint: 800, 105 | cols: 3, 106 | rows: 1, 107 | gap: 10, 108 | loop: true, 109 | autoplay: 1000 110 | } 111 | ] 112 | ``` 113 | 114 | ### dot 115 | 116 | #### Example 117 | 118 | ```javascript 119 | // your custom dot component with prop `isActive` 120 | const MyDot = ({ isActive }) => ( 121 | 129 | ) 130 | 131 | // set custom dot 132 | 133 | ``` 134 | 135 | ## Example 136 | 137 | Storybook (Don't forget to try on different viewport size) 138 | 139 | ```bash 140 | $ git clone https://github.com/x3388638/react-grid-carousel 141 | $ cd react-grid-carousel 142 | $ npm ci 143 | $ npm run storybook 144 | ``` 145 | 146 | Use case in real world 147 | 148 | ```bash 149 | # clone & install packages 150 | $ npm run dev 151 | # open localhost:8080 152 | ``` 153 | 154 | or visit https://react-grid-carousel.now.sh/#use-case-in-real-world 155 | 156 | ## LICENSE 157 | 158 | MIT 159 | -------------------------------------------------------------------------------- /examples/home/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | 5 | const randomImgUrl = 'https://picsum.photos/{x}/{y}?random=' 6 | 7 | const CustomBtn = styled.div` 8 | position: absolute; 9 | display: inline-flex; 10 | align-items: center; 11 | justify-content: center; 12 | height: 40px; 13 | width: 40px; 14 | font-size: 20px; 15 | color: red; 16 | opacity: 0.6; 17 | cursor: pointer; 18 | top: 50%; 19 | z-index: 10; 20 | transition: all 0.25s; 21 | transform: ${({ type }) => 22 | `translateY(-50%) ${type === 'left' ? 'rotate(180deg)' : ''}`}; 23 | left: ${({ type }) => (type === 'left' ? '20px' : 'initial')}; 24 | right: ${({ type }) => (type === 'right' ? '20px' : 'initial')}; 25 | 26 | &:hover { 27 | background: red; 28 | color: #fff; 29 | opacity: 0.5; 30 | } 31 | ` 32 | 33 | const CustomDot = styled.span` 34 | display: inline-block; 35 | height: 8px; 36 | width: ${({ isActive }) => (isActive ? '16px' : '8px')}; 37 | opacity: ${({ isActive }) => (isActive ? '0.8' : '0.5')}; 38 | border-radius: 8px; 39 | background: red; 40 | transition: all 0.2s; 41 | ` 42 | 43 | const App = () => { 44 | const [isHover, setIshover] = useState(false) 45 | 46 | const handleHover = useCallback(() => { 47 | setIshover(state => !state) 48 | }, []) 49 | 50 | return ( 51 |
52 |

Single column

53 | 54 | {[...Array(4)].map((_, i) => ( 55 | 56 | 60 | 61 | ))} 62 | 63 |

Multiple columns

64 | 65 | {[...Array(15)].map((_, i) => ( 66 | 67 | 71 | 72 | ))} 73 | 74 |

Multiple cols + multiple rows

75 | 81 | {[...Array(18)].map((_, i) => ( 82 | 83 | 87 | 88 | ))} 89 | 90 |

91 | Show/hide arrow buttons and dots w/ infinite loop 92 |

93 |
94 | 101 | {[...Array(9)].map((_, i) => ( 102 | 103 | 109 | 110 | ))} 111 | 112 |
113 |

Autoplay w/ customized arrow buttons and dots

114 | ➜} 122 | arrowRight={} 123 | dot={CustomDot} 124 | > 125 | {[...Array(20)].map((_, i) => ( 126 | 127 | 131 | 132 | ))} 133 | 134 |

135 | Customized layout for RWD (max-width: 1000px/750px/500px) 136 |

137 |
138 | responsiveLayout settings 139 |
{`[
140 |   { breakpoint: 1000, cols: 3 },
141 |   { breakpoint: 750, cols: 2, rows: 1, gap: 5 },
142 |   { breakpoint: 499, autoplay: 2000, loop: true }
143 | ]`}
144 |
145 | 156 | {[...Array(20)].map((_, i) => ( 157 | 158 | 162 | 163 | ))} 164 | 165 | 166 |
167 | *Photo source:{' '} 168 | 173 | https://picsum.photos/ 174 | 175 |
176 |
177 | ) 178 | } 179 | 180 | export default App 181 | -------------------------------------------------------------------------------- /examples/home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/image-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | 5 | const images = [ 6 | 'https://a0.muscache.com/im/pictures/37e211bb-6ef8-44b6-8022-7427e7a241a5.jpg?aki_policy=large', 7 | 'https://a0.muscache.com/im/pictures/c571ab10-d896-4095-b4be-4e57aa8b43cd.jpg?aki_policy=large', 8 | 'https://a0.muscache.com/im/pictures/54b3eadc-e503-41da-a9fe-ba10d20d9aa6.jpg?aki_policy=large', 9 | 'https://a0.muscache.com/im/pictures/b2d713c1-1304-4363-a34f-19fc3c94bcd5.jpg?aki_policy=large', 10 | 'https://a0.muscache.com/im/pictures/6514fab6-a3c4-47b3-8f11-a736d6d3ff77.jpg?aki_policy=large' 11 | ] 12 | 13 | const Container = styled.div` 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | min-height: 100%; 18 | width: 100%; 19 | ` 20 | 21 | const HotelRow = styled.div` 22 | max-width: 800px; 23 | border-top: 1px solid #ebebeb; 24 | border-bottom: 1px solid #ebebeb; 25 | padding: 20px 0; 26 | margin: 30px auto; 27 | display: flex; 28 | position: relative; 29 | 30 | @media screen and (max-width: 767px) { 31 | padding: 20px 10px; 32 | flex-direction: column; 33 | } 34 | ` 35 | 36 | const CarouselWapeer = styled.div` 37 | width: 340px; 38 | 39 | @media screen and (max-width: 767px) { 40 | margin-bottom: 20px; 41 | width: 100%; 42 | } 43 | ` 44 | 45 | const Title = styled.div` 46 | font-size: 20px; 47 | ` 48 | 49 | const Desc = styled.div` 50 | margin-top: 20px; 51 | font-size: 14px; 52 | color: gray; 53 | 54 | @media screen and (max-width: 767px) { 55 | margin-bottom: 50px; 56 | } 57 | ` 58 | const Price = styled.div` 59 | position: absolute; 60 | bottom: 20px; 61 | right: 20px; 62 | 63 | span { 64 | font-weight: bold; 65 | font-size: 18px; 66 | } 67 | ` 68 | 69 | const Code = styled.pre` 70 | max-width: 800px; 71 | margin: 0 auto; 72 | background: floralwhite; 73 | padding: 20px; 74 | box-sizing: border-box; 75 | overflow: auto; 76 | ` 77 | 78 | const Reference = styled.div` 79 | margin: 50px auto; 80 | width: 100%; 81 | max-width: 800px; 82 | border-top: 1px solid #666; 83 | 84 | img { 85 | width: 100%; 86 | } 87 | ` 88 | 89 | const App = () => { 90 | const [isHover, setIsHover] = useState(false) 91 | 92 | const handleHover = useCallback(() => { 93 | setIsHover(state => !state) 94 | }, []) 95 | 96 | return ( 97 | 98 |

99 | Use{' '} 100 | 105 |  react-grid-carousel  106 | {' '} 107 | to build image carousel 108 |

109 | 110 | 111 | 112 | {images.map((img, i) => ( 113 | 114 | 115 | 116 | ))} 117 | 118 | 119 |
120 | Sunny, Modern room in East Village! 121 | 122 | 1 guest · 1 bedroom · 1 bed · 1 shared bath 123 |
124 | Wifi · Kitchen · Heating · Air conditioning 125 |
126 | 127 | $1,952 TWD / night 128 | 129 |
130 |
131 | {` 132 | {images.map((img, i) => ( 133 | 134 | 135 | 136 | ))} 137 | `} 138 | 139 |

140 | Image carousel on{' '} 141 | 146 | Airbnb 147 | 148 |

149 | 150 | 151 | 152 |
153 |
154 | ) 155 | } 156 | 157 | export default App 158 | -------------------------------------------------------------------------------- /examples/image-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Image carousel 7 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/image-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Grid Carousel 8 | 10 | 12 | 19 | 22 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 57 |
58 |

59 | $ npm i 60 |  react-grid-carousel  61 | --save 62 |

63 |

64 | React resposive carousel component w/ grid layout to easily create a carousel like photo gallery, shopping product 65 | card or anything you want 66 |

67 | GitHub 69 |
70 |
71 |

Example

72 |
73 |

Use case in real world

74 | 86 |

Features

87 |
    88 |
  • RWD
  • 89 |
  • Multiple items
  • 90 |
  • Multiple rows
  • 91 |
  • Infinite loop
  • 92 |
  • Support any component as a item to put into grid
  • 93 |
  • Show/hide dots
  • 94 |
  • Show/hide arrow buttons
  • 95 |
  • Auto play
  • 96 |
  • Enable/Disable `scroll-snap` for each item on mobile device
  • 97 |
  • Customized layout (cols & rows) for different breakpoint
  • 98 |
  • Customized arrow button
  • 99 |
  • Support SSR
  • 100 |
101 |

Install

102 |
$ npm install react-grid-carousel --save
103 |

Usage

104 |

105 | import React from 'react'
106 | import Carousel from 'react-grid-carousel'
107 | 
108 | const Gallery = () => {
109 |   return (
110 |     <Carousel cols={2} rows={1} gap={10} loop>
111 |       <Carousel.Item>
112 |         <img width="100%" src="https://picsum.photos/800/600?random=1" />
113 |       </Carousel.Item>
114 |       <Carousel.Item>
115 |         <img width="100%" src="https://picsum.photos/800/600?random=2" />
116 |       </Carousel.Item>
117 |       <Carousel.Item>
118 |         <img width="100%" src="https://picsum.photos/800/600?random=3" />
119 |       </Carousel.Item>
120 |       <Carousel.Item>
121 |         {/* anything you want to show in the grid */}
122 |       </Carousel.Item>
123 |       {/* ... */}
124 |     </Carousel>
125 |   )
126 | }
127 |

Docs

128 | https://github.com/x3388638/react-grid-carousel 130 |
131 | © 2020 YY 132 |
133 |
134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /examples/multi-rows-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | 5 | const brandLogo = 'https://fakeimg.pl/320x180/?text=Brand%20logo%20' 6 | 7 | const Container = styled.div` 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | min-height: 100%; 13 | background: #f5f5f5; 14 | ` 15 | 16 | const Row = styled.div` 17 | max-width: 1200px; 18 | margin: 0 auto; 19 | ` 20 | 21 | const Logo = styled.div` 22 | height: 110px; 23 | background-image: ${({ img }) => `url(${img})`}; 24 | background-size: cover; 25 | background-position: center; 26 | background-repeat: no-repeat; 27 | cursor: pointer; 28 | 29 | &:hover { 30 | border: 1px solid #a9a9a9; 31 | width: calc(100% - 2px); 32 | height: 108px; 33 | } 34 | ` 35 | 36 | const More = styled.div` 37 | height: 100%; 38 | width: 100%; 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | color: red; 43 | font-weight: bold; 44 | cursor: pointer; 45 | background: #fff; 46 | 47 | &:hover { 48 | border: 1px solid #a9a9a9; 49 | width: calc(100% - 2px); 50 | height: 108px; 51 | } 52 | ` 53 | 54 | const Code = styled.pre` 55 | max-width: 1200px; 56 | margin: 15px auto; 57 | background: #fff; 58 | padding: 20px; 59 | box-sizing: border-box; 60 | overflow: auto; 61 | ` 62 | 63 | const Reference = styled.div` 64 | margin: 50px auto; 65 | width: 100%; 66 | max-width: 1200px; 67 | border-top: 1px solid #666; 68 | 69 | img { 70 | width: 100%; 71 | } 72 | ` 73 | 74 | const App = () => { 75 | return ( 76 | 77 |

78 | Use{' '} 79 | 84 |  react-grid-carousel  85 | {' '} 86 | to build multiple rows carousel 87 |

88 | 89 | 95 | {[...Array(23)].map((_, i) => ( 96 | 97 | 98 | 99 | ))} 100 | 101 | See All > 102 | 103 | 104 | 105 | {` 111 | {[...Array(23)].map((_, i) => ( 112 | 113 | 114 | 115 | ))} 116 | 117 | See All > 118 | 119 | `} 120 | 121 |

122 | Multiple rows carousel on{' '} 123 | 124 | Shopee 125 | 126 |

127 | 128 | 129 | 130 |
131 |
132 | ) 133 | } 134 | 135 | export default App 136 | -------------------------------------------------------------------------------- /examples/multi-rows-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Multiple rows carousel 8 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/multi-rows-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/news-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | import newsList from './mockNewsList.json' 5 | 6 | const Container = styled.div` 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | min-height: 100%; 11 | width: 100%; 12 | max-width: 500px; 13 | background: #f3f3f3; 14 | ` 15 | 16 | const Notice = styled.div` 17 | margin: 10px; 18 | color: lightcoral; 19 | ` 20 | 21 | const Item = styled.div` 22 | position: relative; 23 | height: 205px; 24 | background-image: ${({ img }) => `url(${img})`}; 25 | background-position: center; 26 | background-size: cover; 27 | background-repeat: no-repeat; 28 | border-radius: 8px; 29 | ` 30 | 31 | const Index = styled.div` 32 | position: absolute; 33 | background: #232323bf; 34 | color: #ffffffc9; 35 | padding: 2px 8px; 36 | font-size: 12px; 37 | border-radius: 20px; 38 | top: 5px; 39 | right: 5px; 40 | ` 41 | 42 | const Detail = styled.div` 43 | position: absolute; 44 | bottom: 0; 45 | color: #fff; 46 | padding: 15px; 47 | width: 100%; 48 | box-sizing: border-box; 49 | background: linear-gradient(0deg, black, transparent); 50 | padding-top: 120px; 51 | border-radius: 8px; 52 | ` 53 | 54 | const Title = styled.div` 55 | font-size: 20px; 56 | font-weight: bold; 57 | ` 58 | const Comment = styled.div` 59 | font-size: 16px; 60 | font-weight: bold; 61 | ` 62 | 63 | const Code = styled.pre` 64 | background: #fff; 65 | padding: 20px; 66 | font-size: 12px; 67 | overflow: auto; 68 | ` 69 | 70 | const Reference = styled.div` 71 | margin: 50px auto; 72 | border-top: 1px solid #666; 73 | ` 74 | 75 | const App = () => { 76 | return ( 77 | 78 |

79 | Use{' '} 80 | 85 |  react-grid-carousel  86 | {' '} 87 | to build news carousel 88 |

89 | Notice: You should try this demo on mobile viewport size 90 | 91 | {newsList.map(({ imageSrc, title, comment }, i) => ( 92 | 93 | 94 | 95 | {i + 1}/{newsList.length} 96 | 97 | 98 | {title} 99 | {!!comment && {comment.count} comments} 100 | 101 | 102 | 103 | ))} 104 | 105 | {` 106 | {newsList.map(({ imageSrc, title, comment }, i) => ( 107 | 108 | 109 | 110 | {i + 1}/{newsList.length} 111 | 112 | 113 | {title} 114 | {!!comment && {comment.count} comments} 115 | 116 | 117 | 118 | ))} 119 | `} 120 | 121 |

122 | News carousel on{' '} 123 | 128 | Yahoo! mobile home page 129 | 130 |

131 |
134 | 143 |
144 |
145 |
146 | ) 147 | } 148 | 149 | export default App 150 | -------------------------------------------------------------------------------- /examples/news-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | News carousel 7 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/news-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/news-carousel/mockNewsList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "comment": { 4 | "detail": "兩岸全面斷航⋯⋯", 5 | "nickname": "reen ho", 6 | "profile": "https://s.yimg.com/gq/1770/40147157126_80fc42_o.jpg", 7 | "count": 32 8 | }, 9 | "imageSrc": "https://s1.yimg.com/uu/api/res/1.2/k54NZ.sirRkPkEgUpmOM1w--/YXBwaWQ9eXRhY2h5b247Y2g9NDQ5O2N3PTgwMDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://media.zenfs.com/en/cna.com.tw/d031a8727dc4d6f296c3a4fe8ff523d9", 10 | "imageWebpSrc": "https://s2.yimg.com/lo/api/res/1.2/Tcq8S8O2BfJ_REWDxUq8Ag--/YXBwaWQ9eXR3ZnBhZ2U-/https://s1.yimg.com/uu/api/res/1.2/k54NZ.sirRkPkEgUpmOM1w--/YXBwaWQ9eXRhY2h5b247Y2g9NDQ5O2N3PTgwMDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://media.zenfs.com/en/cna.com.tw/d031a8727dc4d6f296c3a4fe8ff523d9.cf.webp", 11 | "link": "https://tw.news.yahoo.com/%E6%AD%A6%E6%BC%A2%E8%82%BA%E7%82%8E%E5%8F%B0%E7%81%A3%E6%96%B0%E5%A2%9E2%E4%BE%8B-%E6%94%BF%E5%BA%9C%E6%B1%BA%E5%AE%9A3%E6%8E%AA%E6%96%BD-114243142.html", 12 | "followLink": false, 13 | "title": "新增2例入台 政府要求湖北團快離境", 14 | "type": "story", 15 | "uuid": "f68fb997-aa08-3716-9c21-0c575fdb52d2" 16 | }, 17 | { 18 | "comment": { 19 | "detail": "人類夕鶴,中國幫了大忙", 20 | "nickname": "無忌", 21 | "profile": "https://s.yimg.com/gq/1756/40147164326_afa8df_o.jpg", 22 | "count": 8 23 | }, 24 | "imageSrc": "https://s.yimg.com/uu/api/res/1.2/CpM3siULXbwr6mbJulW82Q--/YXBwaWQ9eXRhY2h5b247Y2g9MzU4O2N3PTY0MDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/http://media.zenfs.com/it-IT/video/funweek_25/8491ae2f81ed8ae5c22457ea624e617d", 25 | "imageWebpSrc": "https://s.yimg.com/lo/api/res/1.2/4U6HVA8Nl_GNkVlhuFtOOQ--/YXBwaWQ9eXR3ZnBhZ2U-/https://s.yimg.com/uu/api/res/1.2/CpM3siULXbwr6mbJulW82Q--/YXBwaWQ9eXRhY2h5b247Y2g9MzU4O2N3PTY0MDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/http://media.zenfs.com/it-IT/video/funweek_25/8491ae2f81ed8ae5c22457ea624e617d.cf.webp", 26 | "link": "https://tw.news.yahoo.com/%E6%9C%AB%E6%97%A5%E9%90%98%E8%B7%9D%E5%8D%88%E5%A4%9C%E5%8F%AA%E5%89%A9100%E7%A7%92-73%E5%B9%B4%E4%BE%86%E4%BA%BA%E9%A1%9E%E6%9C%80%E6%8E%A5%E8%BF%91%E6%BB%85%E4%BA%A1-064131854.html", 27 | "followLink": false, 28 | "title": "只剩100秒 73年來人類最接近滅亡", 29 | "type": "story", 30 | "uuid": "799ea104-b8bd-333b-bf4e-7eb9e04f9986" 31 | }, 32 | { 33 | "comment": { 34 | "detail": "人們賦予錢太大的力量了,以致於人性在錢的面前醜態百出⋯⋯", 35 | "nickname": "無名", 36 | "profile": "https://s.yimg.com/gq/1756/40147164326_afa8df_o.jpg", 37 | "count": 7 38 | }, 39 | "imageSrc": "https://s3.yimg.com/uu/api/res/1.2/u14p5sDcBUvG1RgqL5nOtw--/YXBwaWQ9eXRhY2h5b247Y2g9ODgwO2N3PTEyMDA7ZHg9MDtkeT0wO2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/http://media.zenfs.com/zh-Hant-TW/homerun/bcc.com.tw/89bfa53e5be1c18dd1d164ec024a652a", 40 | "imageWebpSrc": "https://s.yimg.com/lo/api/res/1.2/apFS8ELEkmRyrrjBqcdzcg--/YXBwaWQ9eXR3ZnBhZ2U-/https://s3.yimg.com/uu/api/res/1.2/u14p5sDcBUvG1RgqL5nOtw--/YXBwaWQ9eXRhY2h5b247Y2g9ODgwO2N3PTEyMDA7ZHg9MDtkeT0wO2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/http://media.zenfs.com/zh-Hant-TW/homerun/bcc.com.tw/89bfa53e5be1c18dd1d164ec024a652a.cf.webp", 41 | "link": "https://tw.news.yahoo.com/video/%E7%B4%AB%E5%8D%97%E5%AE%AE%E5%88%9D-%E7%99%BC%E9%8C%A2%E6%AF%8D-%E6%8E%92%E9%9A%8A%E9%A0%AD%E9%A6%994%E5%A4%A9%E5%89%8D%E5%88%B0-%E5%BB%9F%E6%96%B9%E5%82%9910%E8%90%AC%E6%9E%9A-060235929.html", 42 | "followLink": false, 43 | "title": "大動員!搶紫南宮錢母長龍無限延伸", 44 | "type": "video", 45 | "uuid": "a0729e59-5c45-3ed5-b011-8466e1dea037" 46 | }, 47 | { 48 | "comment": { 49 | "detail": "武漢人過這個年很淦吧!物價飆升七倍還被封城,這不叫過年,這叫坐牢。", 50 | "nickname": "jack", 51 | "profile": "https://s.yimg.com/ag/images/1735/55563583803_ec658c_192sq.jpg", 52 | "count": 20 53 | }, 54 | "imageSrc": "https://s1.yimg.com/uu/api/res/1.2/jhy5BICj.98lFA4a3TuRHg--/YXBwaWQ9eXRhY2h5b247Y2g9MjgxMjtjdz01MDAwO2R4PTA7ZHk9MzcyO2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-01/e05f7e90-3e72-11ea-99df-fe4bb0892449", 55 | "imageWebpSrc": "https://s1.yimg.com/lo/api/res/1.2/lu9GYaPGoifPPmGDJxUfog--/YXBwaWQ9eXR3ZnBhZ2U-/https://s1.yimg.com/uu/api/res/1.2/jhy5BICj.98lFA4a3TuRHg--/YXBwaWQ9eXRhY2h5b247Y2g9MjgxMjtjdz01MDAwO2R4PTA7ZHk9MzcyO2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-images/2020-01/e05f7e90-3e72-11ea-99df-fe4bb0892449.cf.webp", 56 | "link": "https://tw.news.yahoo.com/%E6%9C%AC%E6%97%A5-yahoo%E7%84%A6%E9%BB%9E%E9%99%B8%E5%AE%A2%E6%96%B7%E7%82%8A%E5%BE%8C-%E5%A2%BE%E4%B8%81%E5%81%B7%E7%AC%91-%E5%8D%97%E6%8A%95%E5%BF%83%E5%AF%92-073457271.html", 57 | "followLink": false, 58 | "title": "台確診病例納中國⋯我促世衛更正", 59 | "type": "story", 60 | "uuid": "81b59f1a-a9a3-3174-994b-a3f88b3e4a42" 61 | }, 62 | { 63 | "comment": null, 64 | "imageSrc": "https://s.yimg.com/uu/api/res/1.2/PAqwZH6vOKrP2FQmLjXruw--/YXBwaWQ9eXRhY2h5b247Y2g9MjM5O2N3PTQyNDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://s.yimg.com/os/creatr-images/GLB/2017-08-14/93c7edd0-80c7-11e7-9413-0740cae646c9_iStock_000003018523XSmall.jpg", 65 | "imageWebpSrc": "https://s1.yimg.com/lo/api/res/1.2/xQ9vkz9eLCXMqc83A3PpVw--/YXBwaWQ9eXR3ZnBhZ2U-/https://s.yimg.com/uu/api/res/1.2/PAqwZH6vOKrP2FQmLjXruw--/YXBwaWQ9eXRhY2h5b247Y2g9MjM5O2N3PTQyNDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://s.yimg.com/os/creatr-images/GLB/2017-08-14/93c7edd0-80c7-11e7-9413-0740cae646c9_iStock_000003018523XSmall.jpg.cf.webp", 66 | "link": "https://tw.travel.yahoo.com/news/%E9%81%8E%E5%B9%B4%E5%A1%9E%E8%BB%8A%E4%B8%8D%E7%84%A1%E8%81%8A%E8%B6%85%E5%AF%A6%E7%94%A8%E8%BB%8A%E4%B8%8A%E7%8E%A9%E6%A8%82%E6%B3%95%E5%AF%B6%E9%99%AA%E4%BD%A0%E6%AE%BA%E6%99%82%E9%96%93-031130985.html", 67 | "followLink": false, 68 | "title": "塞車必備救星 少了它全都沒得玩", 69 | "type": "story", 70 | "uuid": "94ae1c54-3c80-30f3-8247-89a3c7c37ad7" 71 | }, 72 | { 73 | "comment": { 74 | "detail": "我回想起,SARS時,大陸官員在WHO會議上對台灣採"誰理你們"的態度。", 75 | "nickname": "K", 76 | "profile": "https://s.yimg.com/gq/1777/40147150166_5af2d8_o.jpg", 77 | "count": 52 78 | }, 79 | "imageSrc": "https://s1.yimg.com/uu/api/res/1.2/DRvrU3w.g3UsWkfVULSBrQ--/YXBwaWQ9eXRhY2h5b247Y2g9MTQwMDtjdz0yMDgxO2R4PTA7ZHk9MDtmaT11bGNyb3A7aD0zMDA7dz04MzA7/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-01/a94aee40-3e99-11ea-adff-6fe3b6204f7a", 80 | "imageWebpSrc": "https://s1.yimg.com/lo/api/res/1.2/EZakeXqzzKJKwy.FdQv6ww--/YXBwaWQ9eXR3ZnBhZ2U-/https://s1.yimg.com/uu/api/res/1.2/DRvrU3w.g3UsWkfVULSBrQ--/YXBwaWQ9eXRhY2h5b247Y2g9MTQwMDtjdz0yMDgxO2R4PTA7ZHk9MDtmaT11bGNyb3A7aD0zMDA7dz04MzA7/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-01/a94aee40-3e99-11ea-adff-6fe3b6204f7a.cf.webp", 81 | "link": "https://tw.news.yahoo.com/video/%E9%86%AB%E9%99%A2%E5%A1%9E%E7%88%86-%E6%97%A5%E8%BC%AA4%E7%8F%AD-%E6%AD%A6%E6%BC%A2%E9%86%AB%E7%94%9F-%E7%B4%AF%E5%B4%A9-%E9%A3%86%E9%AB%98%E5%B1%A4-055108107.html", 82 | "followLink": false, 83 | "title": "武漢醫護加班崩潰 通話怒飆高層", 84 | "type": "video", 85 | "uuid": "ff98be35-85bb-3057-8463-aba771bb7c80" 86 | }, 87 | { 88 | "comment": { 89 | "detail": "大家都乖乖待在家裡頭打電動就好 打麻將還要四個人", 90 | "nickname": "一切有為法,如夢幻泡影,如露亦如電,應作如是觀", 91 | "profile": "https://s.yimg.com/gq/1756/40147164326_afa8df_o.jpg", 92 | "count": 21 93 | }, 94 | "imageSrc": "https://s3.yimg.com/uu/api/res/1.2/ylz.rsqLKBn0BN0dbI0Gzg--/YXBwaWQ9eXRhY2h5b247Y2g9NDUyO2N3PTgwNDtkeD0yMjtkeT0wO2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/https://media.zenfs.com/zh-TW/ctwant_com_582/fd1d3c82f58e6b29fa3d006bb6585ca4", 95 | "imageWebpSrc": "https://s.yimg.com/lo/api/res/1.2/KSw.ejIBjY6I0HUiUhMGtw--/YXBwaWQ9eXR3ZnBhZ2U-/https://s3.yimg.com/uu/api/res/1.2/ylz.rsqLKBn0BN0dbI0Gzg--/YXBwaWQ9eXRhY2h5b247Y2g9NDUyO2N3PTgwNDtkeD0yMjtkeT0wO2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/https://media.zenfs.com/zh-TW/ctwant_com_582/fd1d3c82f58e6b29fa3d006bb6585ca4.cf.webp", 96 | "link": "https://tw.news.yahoo.com/sars%E9%83%BD%E6%B2%92%E9%96%89%E9%A4%A8-%E5%8C%97%E4%BA%AC%E6%95%85%E5%AE%AE%E9%A6%96%E6%AC%A1%E9%96%89%E9%A4%A8-050100955.html", 97 | "followLink": false, 98 | "title": "抗SARS都沒這樣 北京故宮罕見閉館", 99 | "type": "story", 100 | "uuid": "a30fdf39-24b5-3e2f-a521-359798018641" 101 | }, 102 | { 103 | "comment": { 104 | "detail": "今年的年好冷喔!(冷清)為啥呢?", 105 | "nickname": "Test", 106 | "profile": "https://s.yimg.com/gq/1746/29332403839_af81b0_o.jpg", 107 | "count": 21 108 | }, 109 | "imageSrc": "https://s.yimg.com/uu/api/res/1.2/4Hy95YzMHxlNtRVtAGe.cw--/YXBwaWQ9eXRhY2h5b247Y2g9MzI5O2N3PTYwMDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-01/b3509480-3e80-11ea-bdb5-0696ab814b35", 110 | "imageWebpSrc": "https://s.yimg.com/lo/api/res/1.2/P8N22WJCoE8W49rXPBJQEg--/YXBwaWQ9eXR3ZnBhZ2U-/https://s.yimg.com/uu/api/res/1.2/4Hy95YzMHxlNtRVtAGe.cw--/YXBwaWQ9eXRhY2h5b247Y2g9MzI5O2N3PTYwMDtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-01/b3509480-3e80-11ea-bdb5-0696ab814b35.cf.webp", 111 | "link": "https://tw.news.yahoo.com/video/%E5%BD%B0%E5%8C%9629%E5%B9%B4%E8%9B%8B%E9%A4%85%E8%80%81%E5%BA%97%E5%B0%87%E6%AD%87%E6%A5%AD-%E6%B0%91%E7%9C%BE%E5%96%8A-%E5%8F%AF%E4%BB%A5%E4%B8%8D%E8%A6%81%E9%97%9C%E5%97%8E-063411210.html", 112 | "followLink": false, 113 | "title": "「可以別關嗎」29年古早味老店收了", 114 | "type": "video", 115 | "uuid": "e4850219-e355-32e9-8909-daec8fc546c9" 116 | }, 117 | { 118 | "comment": { 119 | "detail": "官方慣性隱匿疫情、粉飾太平 , 疫情早就擴散 , 現在封城已經枉然了 ! \n尤其是 , 還拉著全世界一起下水 !", 120 | "nickname": "oldman", 121 | "profile": "https://s.yimg.com/gq/1721/38837678613_ebe081_o.jpg", 122 | "count": 219 123 | }, 124 | "imageSrc": "https://s3.yimg.com/uu/api/res/1.2/k5xq5OKkcbjrOo9Eez0WSQ--/YXBwaWQ9eXRhY2h5b247Y2g9MjQ5O2N3PTQ0MjtkeD0wO2R5PTg0O2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/https://media.zenfs.com/ko/setn.com.tw/393c38a4b03d142a8db782af4b3814e5", 125 | "imageWebpSrc": "https://s1.yimg.com/lo/api/res/1.2/cts5cokLnFfbiZSczuurhA--/YXBwaWQ9eXR3ZnBhZ2U-/https://s3.yimg.com/uu/api/res/1.2/k5xq5OKkcbjrOo9Eez0WSQ--/YXBwaWQ9eXRhY2h5b247Y2g9MjQ5O2N3PTQ0MjtkeD0wO2R5PTg0O2ZpPXVsY3JvcDtoPTMwMDt3PTgzMDs-/https://media.zenfs.com/ko/setn.com.tw/393c38a4b03d142a8db782af4b3814e5.cf.webp", 126 | "link": "https://tw.news.yahoo.com/%E6%AD%A6%E6%BC%A2%E5%B0%81%E5%9F%8E%E5%BE%8C%E7%9A%84%E5%B8%82%E6%B0%91%E7%94%9F%E6%B4%BB-%E8%B6%85%E5%B8%82-%E6%8E%83%E8%80%8C%E7%A9%BA-%E9%86%AB%E9%99%A2%E4%BA%BA%E6%BB%BF%E7%82%BA%E6%82%A3-%E5%83%8F%E6%98%AF-023001964.html", 127 | "followLink": false, 128 | "title": "武漢封城後⋯「像惡靈古堡一樣」", 129 | "type": "story", 130 | "uuid": "1a964599-c1ae-3eef-a851-8b73a4d31dca" 131 | }, 132 | { 133 | "comment": { 134 | "detail": "有病", 135 | "nickname": "無忌", 136 | "profile": "https://s.yimg.com/gq/1756/40147164326_afa8df_o.jpg", 137 | "count": 2 138 | }, 139 | "imageSrc": "https://s3.yimg.com/uu/api/res/1.2/BaaVFBVgnixXG_48t6aWBQ--/YXBwaWQ9eXRhY2h5b247Y2g9NjAyO2N3PTkxMjtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-01/34d9fa80-3e83-11ea-b6ff-6c02b46a4428", 140 | "imageWebpSrc": "https://s2.yimg.com/lo/api/res/1.2/X7S9WVSnxiliowbDq7hTOw--/YXBwaWQ9eXR3ZnBhZ2U-/https://s3.yimg.com/uu/api/res/1.2/BaaVFBVgnixXG_48t6aWBQ--/YXBwaWQ9eXRhY2h5b247Y2g9NjAyO2N3PTkxMjtkeD0wO2R5PTA7Zmk9dWxjcm9wO2g9MzAwO3c9ODMwOw--/https://media-mbst-pub-ue1.s3.amazonaws.com/creatr-uploaded-images/2020-01/34d9fa80-3e83-11ea-b6ff-6c02b46a4428.cf.webp", 141 | "link": "https://tw.tv.yahoo.com/%E4%B8%89%E7%94%9F%E4%B8%89%E4%B8%96%E6%9E%95%E4%B8%8A%E6%9B%B8-%E7%8D%A8%E5%AE%B6%E9%A0%90%E5%91%8A-081051301.html", 142 | "followLink": false, 143 | "title": "愛而不得三情斷 虐戀只求被記得", 144 | "type": "video", 145 | "uuid": "12628b04-cb30-3921-a68f-c839705466fa" 146 | } 147 | ] 148 | -------------------------------------------------------------------------------- /examples/product-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | import products from './mockProductList.json' 5 | 6 | const Body = styled.div` 7 | background: #f3f3f3; 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | min-height: 100%; 12 | width: 100%; 13 | ` 14 | 15 | const CarouselContainer = styled.div` 16 | padding: 20px 0; 17 | ` 18 | 19 | const Row = styled.div` 20 | max-width: 1000px; 21 | margin: 10px auto; 22 | border-radius: 8px; 23 | background: #fff; 24 | 25 | @media screen and (max-width: 767px) { 26 | margin: 10px; 27 | } 28 | ` 29 | 30 | const RowHead = styled.div` 31 | padding: 20px; 32 | font-size: 18px; 33 | font-weight: bold; 34 | border-bottom: 1px solid #eee; 35 | ` 36 | 37 | const Card = styled.div` 38 | position: relative; 39 | 40 | img { 41 | width: 100%; 42 | height: 180px; 43 | object-fit: cover; 44 | border-radius: 8px; 45 | } 46 | 47 | span:first-of-type { 48 | color: red; 49 | font-size: 16px; 50 | font-weight: bold; 51 | } 52 | 53 | span:last-of-type { 54 | color: gray; 55 | font-size: 12px; 56 | text-decoration-line: line-through; 57 | margin-left: 10px; 58 | } 59 | 60 | @media screen and (max-width: 767px) { 61 | background: #f3f3f3; 62 | border: 1px solid #f3f3f3; 63 | } 64 | ` 65 | 66 | const Title = styled.div` 67 | font-size: 14px; 68 | line-height: 16px; 69 | height: 32px; 70 | overflow: hidden; 71 | margin-bottom: 5px; 72 | ` 73 | 74 | const Mask = styled.div` 75 | opacity: 0; 76 | height: 100%; 77 | width: 100%; 78 | cursor: pointer; 79 | background: #0000000a; 80 | position: absolute; 81 | border-radius: 8px; 82 | top: 0; 83 | left: 0; 84 | 85 | &:hover { 86 | opacity: 1; 87 | } 88 | ` 89 | 90 | const Code = styled.pre` 91 | max-width: 1000px; 92 | margin: 10px auto; 93 | background: #fff; 94 | padding: 20px; 95 | box-sizing: border-box; 96 | ` 97 | 98 | const Reference = styled.div` 99 | margin: 50px auto; 100 | width: 100%; 101 | max-width: 1000px; 102 | border-top: 1px solid #666; 103 | 104 | img { 105 | width: 100%; 106 | } 107 | ` 108 | 109 | const App = () => { 110 | return ( 111 | 112 |

113 | Use{' '} 114 | 119 |  react-grid-carousel  120 | {' '} 121 | to build product carousel 122 |

123 | 124 | 每日好康 125 | 126 | 127 | {products.map((val, i) => ( 128 | 129 | 130 | 131 |
132 | {val.title} 133 | {val.specialPrice} 134 | {val.oriPrice} 135 |
136 | 137 |
138 |
139 | ))} 140 |
141 |
142 |
143 | 144 | {` 145 | {products.map((val, i) => ( 146 | 147 | 148 | 149 |
150 | {val.title} 151 | {val.specialPrice} 152 | {val.oriPrice} 153 |
154 | 155 |
156 |
157 | ))} 158 |
`} 159 |
160 | 161 |

162 | Product carousel on{' '} 163 | 168 | Yahoo! Shopping 169 | 170 |

171 | 172 | 177 | 178 | 179 |
180 | 181 | ) 182 | } 183 | 184 | export default App 185 | -------------------------------------------------------------------------------- /examples/product-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Product carousel 7 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/product-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/product-carousel/mockProductList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "img": "https://s.yimg.com/zp/MerchandiseImages/38D5C91EBF-Product-20678834.jpg", 4 | "title": "美國iRobot Roomba960智慧吸塵+wifi掃地機器人(總代理保固1+1年)", 5 | "specialPrice": "$17999", 6 | "oriPrice": "$34999" 7 | }, 8 | { 9 | "img": "https://s.yimg.com/zp/MerchandiseImages/339BEEDBF1-Gd-8615522.jpg", 10 | "title": "過年換新機 iPhone 11 新機熱賣搶購中", 11 | "specialPrice": "$23688", 12 | "oriPrice": "$24900" 13 | }, 14 | { 15 | "img": "https://s.yimg.com/zp/MerchandiseImages/AD34BB3446-Gd-7448332.jpg", 16 | "title": "天天喝好水 礦泉水箱購送到家35折起", 17 | "specialPrice": "$115", 18 | "oriPrice": "$200" 19 | }, 20 | { 21 | "img": "https://s.yimg.com/zp/MerchandiseImages/39216872AC-Gd-8778203.jpg", 22 | "title": "濕巾x衛生棉 滿599折100", 23 | "specialPrice": "折100", 24 | "oriPrice": "$1199" 25 | }, 26 | { 27 | "img": "https://s.yimg.com/zp/MerchandiseImages/789778D0E9-Gd-8129802.jpg", 28 | "title": "(24H到貨) LONGCHAMP 經典系列$990起", 29 | "specialPrice": "$990起", 30 | "oriPrice": "$18500" 31 | }, 32 | { 33 | "img": "https://s.yimg.com/zp/MerchandiseImages/B810F61664-Gd-8735341.jpg", 34 | "title": "熱銷印表機單日95折下殺", 35 | "specialPrice": "95折", 36 | "oriPrice": "" 37 | }, 38 | { 39 | "img": "https://s.yimg.com/zp/MerchandiseImages/E290A65F00-SP-7909134.jpg", 40 | "title": "BLUE WAY 鼠不盡的好康新品6折起,單筆滿額再折100", 41 | "specialPrice": "$590up", 42 | "oriPrice": "$1380" 43 | }, 44 | { 45 | "img": "https://s.yimg.com/zp/MerchandiseImages/FAD93DEE00-SP-7594913.jpg", 46 | "title": "【Philips 飛利浦】LIGHTING LEVER 酷恒LED檯燈 72007 銀色", 47 | "specialPrice": "$799起", 48 | "oriPrice": "$1690" 49 | }, 50 | { 51 | "img": "https://s.yimg.com/zp/MerchandiseImages/4A5E9BD839-Gd-7821787.jpg", 52 | "title": "包包快速到貨83折!過年不打烊~結帳享折扣!", 53 | "specialPrice": "83折", 54 | "oriPrice": "$2380" 55 | }, 56 | { 57 | "img": "https://s.yimg.com/zp/MerchandiseImages/68A11DC878-SP-7915980.jpg", 58 | "title": "思薇爾 冬季新春福袋2折起,新年挑戰最低價任選89起", 59 | "specialPrice": "2折起", 60 | "oriPrice": "$1480" 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /examples/responsive-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | 5 | const Container = styled.div` 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | min-height: 100%; 10 | width: 100%; 11 | ` 12 | 13 | const Row = styled.div` 14 | max-width: 1100px; 15 | padding: 0 50px; 16 | margin: 50px auto; 17 | 18 | @media screen and (max-width: 670px) { 19 | padding: 0; 20 | } 21 | ` 22 | 23 | const ArrowBtn = styled.span` 24 | display: inline-block; 25 | position: absolute; 26 | top: 50%; 27 | right: ${({ type }) => (type === 'right' ? '-40px' : 'unset')}; 28 | left: ${({ type }) => (type === 'left' ? '-40px' : 'unset')}; 29 | width: 45px; 30 | height: 45px; 31 | background: #fff; 32 | border-radius: 50%; 33 | box-shadow: 0 3px 15px rgba(0, 0, 0, 0.15); 34 | cursor: pointer; 35 | 36 | &::after { 37 | content: ''; 38 | display: inline-block; 39 | position: absolute; 40 | top: 50%; 41 | left: 50%; 42 | transform: ${({ type }) => 43 | type === 'right' 44 | ? 'translate(-75%, -50%) rotate(45deg)' 45 | : 'translate(-25%, -50%) rotate(-135deg)'}; 46 | width: 10px; 47 | height: 10px; 48 | border-top: 2px solid #666; 49 | border-right: 2px solid #666; 50 | } 51 | 52 | &:hover::after { 53 | border-color: #333; 54 | } 55 | ` 56 | 57 | const Card = styled.div` 58 | margin: 2px; 59 | border-radius: 6px; 60 | border: 1px solid #eaeaea; 61 | overflow: hidden; 62 | cursor: pointer; 63 | transition: box-shadow 0.25s; 64 | 65 | :hover { 66 | box-shadow: 0 0 2px 0 #00000063; 67 | } 68 | ` 69 | 70 | const Img = styled.div` 71 | height: 160px; 72 | margin-bottom: 4px; 73 | background-image: ${({ img }) => `url(${img})`}; 74 | background-position: center; 75 | background-repeat: no-repeat; 76 | background-size: cover; 77 | ` 78 | 79 | const Title = styled.div` 80 | margin: 0 10px 10px; 81 | font-size: 15px; 82 | font-weight: bold; 83 | ` 84 | 85 | const Star = styled.div` 86 | float: left; 87 | margin: 10px; 88 | color: #26bec9; 89 | font-size: 15px; 90 | ` 91 | 92 | const Price = styled.div` 93 | font-size: 12px; 94 | font-weight: 400; 95 | color: #999; 96 | float: right; 97 | margin: 10px; 98 | 99 | span { 100 | font-size: 15px; 101 | color: #26bec9; 102 | } 103 | ` 104 | 105 | const Code = styled.pre` 106 | max-width: 1100px; 107 | margin: 15px auto; 108 | background: lightblue; 109 | padding: 20px; 110 | box-sizing: border-box; 111 | overflow: auto; 112 | ` 113 | 114 | const Reference = styled.div` 115 | margin: 50px auto; 116 | width: 100%; 117 | max-width: 1100px; 118 | border-top: 1px solid #666; 119 | 120 | img { 121 | width: 100%; 122 | } 123 | ` 124 | 125 | const App = () => ( 126 | 127 |

128 | Use{' '} 129 | 134 |  react-grid-carousel  135 | {' '} 136 | to build responsive carousel 137 |

138 | 139 |

144 | Hit The Slopes 145 |

146 | } 162 | arrowLeft={} 163 | > 164 | {[...Array(8)].map((_, i) => ( 165 | 166 | 167 | 168 | 169 | Day Tour From Tokyo: Tambara Ski Park & Strawberry Picking 170 | 171 | ★★★★★ 172 | 173 | TWD 2,500 174 | 175 | 176 | 177 | ))} 178 | 179 |
180 | {`} 196 | arrowLeft={} 197 | > 198 | {[...Array(8)].map((_, i) => ( 199 | 200 | 201 | 202 | 203 | Day Tour From Tokyo: Tambara Ski Park & Strawberry Picking 204 | 205 | ★★★★★ 206 | 207 | TWD 2,500 208 | 209 | 210 | 211 | ))} 212 | `} 213 | 214 |

215 | Responsive carousel on{' '} 216 | 221 | KKday 222 | 223 |

224 | 228 | 229 | 230 |
231 |
232 | ) 233 | 234 | export default App 235 | -------------------------------------------------------------------------------- /examples/responsive-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Responsive carousel 8 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/responsive-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/simple-sample/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Carousel from '../../dist/bundle' 3 | import styled from 'styled-components' 4 | 5 | const randomImageUrl = 'https://picsum.photos/800/600?random=' 6 | 7 | const Item = styled.div` 8 | background-image: ${({ img }) => `url(${img})`}; 9 | background-position: center; 10 | background-size: cover; 11 | background-repeat: no-repeat; 12 | width: 100%; 13 | height: 200px; 14 | ` 15 | 16 | const App = () => { 17 | const [cols, setCols] = useState(3) 18 | const [rows, setRows] = useState(1) 19 | const [gap, setGap] = useState(10) 20 | const [pages, setPages] = useState(2) 21 | 22 | return ( 23 |
24 |

Simple sample

25 |
26 | Cols:{' '} 27 | { 33 | setCols(+e.target.value) 34 | }} 35 | />{' '} 36 | {cols} 37 |
38 |
39 | Rows:{' '} 40 | { 46 | setRows(+e.target.value) 47 | }} 48 | />{' '} 49 | {rows} 50 |
51 |
52 | Pages:{' '} 53 | { 59 | setPages(+e.target.value) 60 | }} 61 | />{' '} 62 | {pages} 63 |
64 |
65 | Gap:{' '} 66 | { 72 | setGap(+e.target.value) 73 | }} 74 | />{' '} 75 | {gap} 76 |
77 |
78 | 79 | {[...Array(cols * rows * pages)].map((_, i) => ( 80 | 81 | 82 | 83 | ))} 84 | 85 | Photo by{' '} 86 | 91 | https://picsum.photos/ 92 | 93 |
94 | ) 95 | } 96 | 97 | export default App 98 | -------------------------------------------------------------------------------- /examples/simple-sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple sample 7 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/simple-sample/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/testimonial-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | import testimonials from './mockTestimonialList.json' 5 | 6 | const Container = styled.div` 7 | background: #f7f8fa; 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | min-height: 100%; 13 | ` 14 | 15 | const Card = styled.div` 16 | background: #fff; 17 | border-radius: 3px; 18 | box-shadow: 0 1px 3px 0 rgba(20, 23, 28, 0.15); 19 | height: 100%; 20 | min-height: 240px; 21 | padding: 32px; 22 | margin: 5px; 23 | ` 24 | const User = styled.div` 25 | display: flex; 26 | ` 27 | const Avatar = styled.div` 28 | width: 64px; 29 | height: 64px; 30 | background: rgb(104, 111, 122); 31 | border-radius: 50%; 32 | align-items: center; 33 | justify-content: center; 34 | color: #fff; 35 | display: flex; 36 | font-weight: bold; 37 | margin-right: 10px; 38 | ` 39 | 40 | const Username = styled.div` 41 | display: flex; 42 | align-items: center; 43 | ` 44 | 45 | const Text = styled.div` 46 | margin-top: 20px; 47 | font-size: 16px; 48 | color: #333; 49 | ` 50 | 51 | const Code = styled.pre` 52 | max-width: 1300px; 53 | margin: 15px auto; 54 | background: #fff; 55 | padding: 20px; 56 | box-sizing: border-box; 57 | overflow: auto; 58 | ` 59 | 60 | const Reference = styled.div` 61 | margin: 50px auto; 62 | width: 100%; 63 | max-width: 1300px; 64 | border-top: 1px solid #666; 65 | 66 | img { 67 | width: 100%; 68 | } 69 | ` 70 | 71 | const App = () => { 72 | return ( 73 | 74 |

75 | Use{' '} 76 | 81 |  react-grid-carousel  82 | {' '} 83 | to build testimonial carousel 84 |

85 | 89 | {testimonials.map(({ name, text }, i) => ( 90 | 91 | 92 | 93 | {name[0]} 94 | {name} 95 | 96 | {text} 97 | 98 | 99 | ))} 100 | 101 | {` 105 | {testimonials.map(({ name, text }, i) => ( 106 | 107 | 108 | 109 | {name[0]} 110 | {name} 111 | 112 | {text} 113 | 114 | 115 | ))} 116 | `} 117 | 118 |

119 | Testimonial carousel on{' '} 120 | 125 | Udemy 126 | 127 |

128 | 129 | 130 | 131 |
132 |
133 | ) 134 | } 135 | 136 | export default App 137 | -------------------------------------------------------------------------------- /examples/testimonial-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Testimonial carousel 7 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/testimonial-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/testimonial-carousel/mockTestimonialList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Borivoje", 4 | "text": "Udemy is a life saver. I don't have the time or money for a college education. My goal is to become a freelance web developer, and thanks to Udemy, I'm really close." 5 | }, 6 | { 7 | "name": "Dipesh", 8 | "text": "I believe in lifelong learning and Udemy is a great place to learn from experts. I've learned a lot and recommend it to all my friends." 9 | }, 10 | { 11 | "name": "Kathy", 12 | "text": "My children and I LOVE Udemy! The courses are fantastic and the instructors are so fun and knowledgeable. I only wish we found it sooner." 13 | }, 14 | { 15 | "name": "Zulaika", 16 | "text": "I work in project management and joined Udemy because I get great courses for less. The instructors are fantastic, interesting, and helpful. I plan to use Udemy for a long time!" 17 | }, 18 | { 19 | "name": "Marco", 20 | "text": "Thank you Udemy! You've renewed my passion for learning and my dream of becoming a web developer." 21 | }, 22 | { 23 | "name": "Justin", 24 | "text": "The best part about Udemy is the selection. You can find a course for anything you want to learn!" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /examples/tour-carousel/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Carousel from '../../dist/bundle' 4 | import cities from './cities.json' 5 | 6 | const Container = styled.div` 7 | background: #f5f5f5; 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | min-height: 100%; 12 | width: 100%; 13 | ` 14 | 15 | const Row = styled.div` 16 | max-width: 1200px; 17 | margin: 50px auto; 18 | ` 19 | 20 | const ArrowBtn = styled.span` 21 | display: inline-block; 22 | position: absolute; 23 | top: 50%; 24 | right: ${({ type }) => (type === 'right' ? '8px' : 'unset')}; 25 | left: ${({ type }) => (type === 'left' ? '8px' : 'unset')}; 26 | transform: ${({ type }) => 27 | `translateY(-50%) rotate(${type === 'right' ? '45deg' : '-135deg'})`}; 28 | width: 16px; 29 | height: 16px; 30 | cursor: pointer; 31 | border-top: 2px solid #888; 32 | border-right: 2px solid #888; 33 | 34 | &:hover { 35 | border-color: #333; 36 | } 37 | ` 38 | 39 | const City = styled.div` 40 | background-image: ${({ img }) => `url(${img})`}; 41 | background-size: cover; 42 | background-position: center; 43 | background-repeat: no-repeat; 44 | height: 220px; 45 | line-height: 220px; 46 | font-size: 24px; 47 | font-weight: bold; 48 | color: #fff; 49 | text-align: center; 50 | margin: 7px; 51 | box-shadow: 0 0 2px 0 #666; 52 | border-radius: 3px; 53 | cursor: pointer; 54 | transition: transform 0.25s, box-shadow 0.25s; 55 | 56 | &:hover { 57 | transform: translateY(-5px); 58 | box-shadow: 0 5px 5px 0 #666; 59 | } 60 | ` 61 | 62 | const Code = styled.pre` 63 | max-width: 1200px; 64 | margin: 15px auto; 65 | background: #fff; 66 | padding: 20px; 67 | box-sizing: border-box; 68 | overflow: auto; 69 | ` 70 | 71 | const Reference = styled.div` 72 | margin: 50px auto; 73 | width: 100%; 74 | max-width: 1200px; 75 | border-top: 1px solid #666; 76 | 77 | img { 78 | width: 100%; 79 | } 80 | ` 81 | 82 | const App = () => ( 83 | 84 |

85 | Use{' '} 86 | 91 |  react-grid-carousel  92 | {' '} 93 | to build tour carousel 94 |

95 | 96 |

101 | TOP DESTINATIONS 102 |

103 | } 108 | arrowRight={} 109 | > 110 | {cities.map((city, i) => ( 111 | 112 | {city.name} 113 | 114 | ))} 115 | 116 |
117 | {`} 122 | arrowRight={} 123 | > 124 | {cities.map((city, i) => ( 125 | 126 | {city.name} 127 | 128 | ))} 129 | `} 130 | 131 |

132 | Tour carousel on{' '} 133 | 138 | KLOOK 139 | 140 |

141 | 142 | 143 | 144 |
145 |
146 | ) 147 | 148 | export default App 149 | -------------------------------------------------------------------------------- /examples/tour-carousel/cities.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Hong Kong", 4 | "img": "https://res.klook.com/image/upload/cities/jrfyzvgzvhs1iylduuhj.jpg" 5 | }, 6 | { 7 | "name": "Macau", 8 | "img": "https://res.klook.com/image/upload/cities/c1cklkyp6ms02tougufx.jpg" 9 | }, 10 | { 11 | "name": "Singapore", 12 | "img": "https://res.klook.com/image/upload/cities/jv8g79dw3j5fmi9qozoa.jpg" 13 | }, 14 | { 15 | "name": "Seoul", 16 | "img": "https://res.klook.com/image/upload/cities/hlx7brfs0u9k2unjhmr0.jpg" 17 | }, 18 | { 19 | "name": "Beijing", 20 | "img": "https://res.klook.com/image/upload/cities/br3mng95h81qi71dqpvk.jpg" 21 | }, 22 | { 23 | "name": "Tokyo", 24 | "img": "https://res.klook.com/image/upload/cities/xvp6saafvo0aeykw3di0.jpg" 25 | }, 26 | { 27 | "name": "Osaka", 28 | "img": "https://res.klook.com/image/upload/cities/migfkd37vdt8xhuenn0s.jpg" 29 | }, 30 | { 31 | "name": "JR Pass", 32 | "img": "https://res.klook.com/image/upload/cities/vplgtbmehzxlzvdbnpby.jpg" 33 | }, 34 | { 35 | "name": "Okinawa & Ishigaki", 36 | "img": "https://res.klook.com/image/upload/cities/e8fnw35p6zgusq218foj.jpg" 37 | }, 38 | { 39 | "name": "Taipei", 40 | "img": "https://res.klook.com/image/upload/cities/i5itbqsg2alwruhqkgvx.jpg" 41 | }, 42 | { 43 | "name": "Bangkok", 44 | "img": "https://res.klook.com/image/upload/cities/wbulbfghbwsuvelgvibn.jpg" 45 | }, 46 | { 47 | "name": "Phuket", 48 | "img": "https://res.klook.com/image/upload/cities/ibudlvrsfnlck3sgounc.jpg" 49 | }, 50 | { 51 | "name": "Bali", 52 | "img": "https://res.klook.com/image/upload/cities/zrvzvz42wh91resimuyz.jpg" 53 | }, 54 | { 55 | "name": "London", 56 | "img": "https://res.klook.com/image/upload/cities/xkeic9zojhtxffeovjtl.jpg" 57 | }, 58 | { 59 | "name": "Paris", 60 | "img": "https://res.klook.com/image/upload/cities/bqi3forinxbwjuknp04v.jpg" 61 | }, 62 | { 63 | "name": "New York", 64 | "img": "https://res.klook.com/image/upload/cities/liw377az16sxmp9a6ylg.jpg" 65 | } 66 | ] 67 | -------------------------------------------------------------------------------- /examples/tour-carousel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tour carousel 8 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/tour-carousel/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 4 | 5 | function isDirectory(dir) { 6 | return fs.lstatSync(dir).isDirectory() 7 | } 8 | 9 | module.exports = { 10 | mode: 'development', 11 | entry: fs.readdirSync(__dirname).reduce((entries, dir) => { 12 | if (dir !== 'dist' && isDirectory(path.join(__dirname, dir))) { 13 | entries[dir] = path.join(__dirname, dir, 'index.js') 14 | } 15 | 16 | return entries 17 | }, {}), 18 | output: { 19 | path: path.resolve(__dirname, 'dist'), 20 | filename: '[name].js', 21 | chunkFilename: '[id].chunk.js' 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: 'babel-loader' 30 | } 31 | } 32 | ] 33 | }, 34 | plugins: [ 35 | new CleanWebpackPlugin({ 36 | verbose: true 37 | }) 38 | ], 39 | devServer: { 40 | contentBase: __dirname, 41 | publicPath: '/dist/', 42 | compress: true, 43 | hot: true, 44 | inline: true, 45 | host: '0.0.0.0' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-grid-carousel", 3 | "version": "1.0.1", 4 | "description": "React resposive carousel component w/ grid layout", 5 | "homepage": "https://react-grid-carousel.now.sh/", 6 | "keywords": [ 7 | "react", 8 | "carousel", 9 | "slider", 10 | "gallery", 11 | "image", 12 | "grid", 13 | "responsive", 14 | "react-component", 15 | "react-carousel", 16 | "react-slider", 17 | "react-image", 18 | "react-grid" 19 | ], 20 | "repository": { 21 | "url": "git@github.com:x3388638/react-grid-carousel.git", 22 | "type": "git" 23 | }, 24 | "main": "dist/bundle.js", 25 | "scripts": { 26 | "dev": "npm run build && webpack-dev-server --config examples/webpack.config.js", 27 | "build": "rollup -c", 28 | "prettier:check": "prettier --check './**/*.{js,json,css}' && echo \"✅ Prettier validated\"", 29 | "prettier:write": "prettier --write './**/*.{js,json,css}'", 30 | "stylelint": "stylelint './{src,examples,stories}/**/*.js' && echo \"✅ Stylelint validated\"", 31 | "lint": "eslint './**/*.js'", 32 | "lint:fix": "eslint './**/*.js' --fix", 33 | "storybook": "start-storybook -p 6006", 34 | "build-storybook": "build-storybook", 35 | "deploy:now": "webpack --config examples/webpack.config.js && now examples/ -n react-grid-carousel --prod" 36 | }, 37 | "author": "YY", 38 | "license": "MIT", 39 | "dependencies": { 40 | "lodash.debounce": "^4.0.8", 41 | "prop-types": "^15.7.2", 42 | "smoothscroll-polyfill": "^0.4.4", 43 | "styled-components": "^4.4.1" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.8.3", 47 | "@babel/preset-env": "^7.8.3", 48 | "@babel/preset-react": "^7.8.3", 49 | "@storybook/addon-actions": "^5.3.8", 50 | "@storybook/addon-knobs": "^5.3.8", 51 | "@storybook/addon-links": "^5.3.8", 52 | "@storybook/addon-viewport": "^5.3.8", 53 | "@storybook/addons": "^5.3.8", 54 | "@storybook/react": "^5.3.8", 55 | "babel-loader": "^8.0.6", 56 | "babel-plugin-styled-components": "^1.10.6", 57 | "clean-webpack-plugin": "^3.0.0", 58 | "eslint": "^6.8.0", 59 | "eslint-plugin-react": "^7.18.0", 60 | "eslint-plugin-react-hooks": "^2.3.0", 61 | "husky": "^4.0.10", 62 | "lint-staged": "^10.0.1", 63 | "prettier": "^1.19.1", 64 | "react": "^16.12.0", 65 | "react-dom": "^16.12.0", 66 | "rollup": "^1.29.1", 67 | "rollup-plugin-babel": "^4.3.3", 68 | "stylelint": "^13.2.0", 69 | "stylelint-config-prettier": "^8.0.1", 70 | "stylelint-config-standard": "^20.0.0", 71 | "stylelint-config-styled-components": "^0.1.1", 72 | "stylelint-processor-styled-components": "^1.10.0", 73 | "webpack": "^4.41.5", 74 | "webpack-cli": "^3.3.10", 75 | "webpack-dev-server": "^3.10.1" 76 | }, 77 | "peerDependencies": { 78 | "react": "^16.12.0", 79 | "react-dom": "^16.12.0" 80 | }, 81 | "lint-staged": { 82 | "*.{js,json,css}": [ 83 | "npm run prettier:check" 84 | ], 85 | "*.js": [ 86 | "npm run stylelint", 87 | "npm run lint" 88 | ] 89 | }, 90 | "husky": { 91 | "hooks": { 92 | "pre-commit": "lint-staged" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel' 2 | import pkg from './package.json' 3 | 4 | export default [ 5 | { 6 | input: 'src/app.js', 7 | plugins: [ 8 | babel({ 9 | exclude: 'node_modules/**' 10 | }) 11 | ], 12 | output: { 13 | file: pkg.main, 14 | format: 'cjs' 15 | }, 16 | external: [ 17 | ...Object.keys(pkg.dependencies), 18 | ...Object.keys(pkg.peerDependencies) 19 | ] 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import Carousel from './components/Carousel' 2 | 3 | export default Carousel 4 | -------------------------------------------------------------------------------- /src/components/ArrowButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const ButtonWrapper = styled.div` 6 | @media screen and (max-width: ${({ mobileBreakpoint }) => 7 | mobileBreakpoint}px) { 8 | display: none; 9 | } 10 | ` 11 | 12 | const Button = styled.span` 13 | position: absolute; 14 | top: calc(50% - 17.5px); 15 | height: 35px; 16 | width: 35px; 17 | background: #fff; 18 | border-radius: 50%; 19 | box-shadow: 0 0 5px 0 #0009; 20 | z-index: 10; 21 | cursor: pointer; 22 | font-size: 10px; 23 | opacity: 0.6; 24 | transition: opacity 0.25s; 25 | left: ${({ type }) => (type === 'prev' ? '5px' : 'initial')}; 26 | right: ${({ type }) => (type === 'next' ? '5px' : 'initial')}; 27 | 28 | &:hover { 29 | opacity: 1; 30 | } 31 | 32 | &::before { 33 | content: ''; 34 | height: 10px; 35 | width: 10px; 36 | background: transparent; 37 | border-top: 2px solid #000; 38 | border-right: 2px solid #000; 39 | display: inline-block; 40 | position: absolute; 41 | top: 50%; 42 | left: 50%; 43 | transform: ${({ type }) => 44 | type === 'prev' 45 | ? 'translate(-25%, -50%) rotate(-135deg)' 46 | : 'translate(-75%, -50%) rotate(45deg)'}; 47 | } 48 | ` 49 | 50 | const ArrowButton = ({ 51 | type, 52 | mobileBreakpoint = 1, 53 | hidden = false, 54 | CustomBtn, 55 | onClick 56 | }) => ( 57 |