├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── DatePicker.js ├── DayPicker.js ├── MonthPicker.js ├── YearPicker.js ├── index.css ├── index.js ├── logo.svg └── serviceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | *.swp 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-awesome-datepicker 2 | Rethinking the date picker UI 3 | 4 | Making date selection work for humans 5 | 6 | Based on this article by Jason Ford 7 | https://uxdesign.cc/rethinking-the-date-picker-ui-99b9dcb303ad 8 | 9 | 10 | ## Usage 11 | ```html 12 | 13 | ``` 14 | 15 | ## Demo 16 | https://rajasegar.github.io/react-awesome-datepicker-demo/ 17 | 18 | ## Things to do 19 | - Publish to npm 20 | - I18n 21 | - Leap year 22 | - Default Options 23 | - Tests 24 | - Storybook 25 | - And much more... 26 | 27 | ## Credits 28 | Jason Ford from UX Collective 29 | https://uxdesign.cc/rethinking-the-date-picker-ui-99b9dcb303ad 30 | 31 | 32 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awesome-datepicker", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "date-fns": "^1.29.0", 7 | "react": "^16.5.2", 8 | "react-dom": "^16.5.2", 9 | "react-scripts": "2.0.4", 10 | "react-transition-group": "^2.5.0", 11 | "styled-components": "^3.4.9", 12 | "styled-transition-group": "^1.0.0" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject", 19 | "predeploy": "yarn build", 20 | "deploy": "gh-pages -d build" 21 | }, 22 | "eslintConfig": { 23 | "extends": "react-app" 24 | }, 25 | "browserslist": [ 26 | ">0.2%", 27 | "not dead", 28 | "not ie <= 11", 29 | "not op_mini all" 30 | ], 31 | "homepage": "https://rajasegar.github.io/react-awesome-datepicker-demo", 32 | "devDependencies": { 33 | "gh-pages": "^2.0.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajasegar/react-awesome-datepicker-demo/c00c4ca5b92cee82db62f0650ca846371fe9789e/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React Awesome Datepicker 23 | 24 | 25 | 28 |

Rethinking the date picker UI with React.js

29 |

Making date selection work for humans

30 |
31 |

Based on this article by Jason Ford

32 |

Github

33 | 34 | 35 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #eee; 3 | padding: 2em; 4 | text-align:center; 5 | 6 | } 7 | .App { 8 | text-align: center; 9 | min-height: 400px; 10 | } 11 | 12 | .App-logo { 13 | animation: App-logo-spin infinite 20s linear; 14 | height: 40vmin; 15 | } 16 | 17 | .App-header { 18 | background-color: #282c34; 19 | min-height: 100vh; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | font-size: calc(10px + 2vmin); 25 | color: white; 26 | } 27 | 28 | .App-link { 29 | color: #61dafb; 30 | } 31 | 32 | @keyframes App-logo-spin { 33 | from { 34 | transform: rotate(0deg); 35 | } 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import "./App.css"; 3 | import DatePicker from "./DatePicker"; 4 | 5 | class App extends Component { 6 | render() { 7 | const showToday = false; 8 | return ( 9 |
10 | 11 |
12 | ); 13 | } 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/DatePicker.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import DayPicker from "./DayPicker"; 3 | import MonthPicker from "./MonthPicker"; 4 | import YearPicker from "./YearPicker"; 5 | import styled from "styled-components"; 6 | import transition from "styled-transition-group"; 7 | //import { format } from "date-fns"; 8 | 9 | const months = [ 10 | "January", 11 | "February", 12 | "March", 13 | "April", 14 | "May", 15 | "June", 16 | "July", 17 | "August", 18 | "September", 19 | "October", 20 | "November", 21 | "December" 22 | ]; 23 | 24 | const Wrapper = styled.div` 25 | background: white; 26 | max-width: 300px; 27 | margin: 1em auto; 28 | max-height: 300px; 29 | overflow: hidden; 30 | border-radius: 4px; 31 | border: 1px solid #ddd; 32 | `; 33 | 34 | const TriggerInput = styled.input` 35 | border: none; 36 | font-weight: bold; 37 | font-size: 1em; 38 | cursor: pointer; 39 | color: #555; 40 | width: 100%; 41 | background: none; 42 | text-align: center; 43 | `; 44 | 45 | const TriggerWrapper = styled.div` 46 | padding: 10px; 47 | `; 48 | 49 | const Dropdown = transition.div.attrs({ 50 | unmountOnExit: true, 51 | timeout: 100 52 | })` 53 | border-top: 1px solid #ccc; 54 | min-height: 271px; 55 | &:enter { opacity: 0.01;} 56 | &:enter-active { 57 | opacity: 1; 58 | transition: opacity 200ms ease-in-out; 59 | } 60 | &:exit { opacity: 1; } 61 | &:exit-active { 62 | opacity: 0.01; 63 | transition: opacity 1ms ease-in; 64 | } 65 | `; 66 | 67 | class DatePicker extends Component { 68 | constructor(props) { 69 | super(props); 70 | this.state = { 71 | showDayPicker: false, 72 | showMonthPicker: false, 73 | showYearPicker: false, 74 | showDropdown: false, 75 | formattedDate: "DD/MM/YYYY" 76 | }; 77 | 78 | this.dateInput = React.createRef(); 79 | this.monthInput = React.createRef(); 80 | this.yearInput = React.createRef(); 81 | 82 | this.renderDayPicker = this.renderDayPicker.bind(this); 83 | this.renderMonthPicker = this.renderMonthPicker.bind(this); 84 | this.renderYearPicker = this.renderYearPicker.bind(this); 85 | this.onDatePicked = this.onDatePicked.bind(this); 86 | this.onDateChange = this.onDateChange.bind(this); 87 | 88 | this.resetDate = this.resetDate.bind(this); 89 | } 90 | 91 | componentDidMount() { 92 | this.resetDate(); 93 | } 94 | 95 | resetDate() { 96 | const { showToday } = this.props; 97 | let today = new Date(); 98 | let _date = showToday ? today.getDate() : "DD"; 99 | let _month = showToday ? today.getMonth() + 1 : "MM"; 100 | let _year = showToday ? today.getFullYear() : "YYYY"; 101 | 102 | this.setState({ 103 | date: _date, 104 | month: _month, 105 | year: _year, 106 | formattedDate: `${_date}/${_month}/${_year}` 107 | }); 108 | } 109 | 110 | renderDayPicker() { 111 | this.setState({ 112 | showDayPicker: true, 113 | showDropdown: true 114 | }); 115 | //this.resetDate(); 116 | this.dateInput.current.select(); 117 | } 118 | 119 | renderMonthPicker(d) { 120 | const _date = d.toString().padStart(2, "0"); 121 | const { month, year } = this.state; 122 | 123 | this.setState({ 124 | showDayPicker: false, 125 | showMonthPicker: true, 126 | date: _date, 127 | formattedDate: `${_date}/${month}/${year}` 128 | }); 129 | } 130 | 131 | renderYearPicker(m) { 132 | const _month = m.toString().padStart(2, "0"); 133 | const { date, year } = this.state; 134 | this.setState({ 135 | showMonthPicker: false, 136 | showYearPicker: true, 137 | month: _month, 138 | formattedDate: `${date}/${_month}/${year}` 139 | }); 140 | } 141 | 142 | onDatePicked(y) { 143 | const { date, month } = this.state; 144 | this.setState({ 145 | showDropdown: false, 146 | showYearPicker: false, 147 | year: y, 148 | formattedDate: `${date}/${month}/${y}` 149 | }); 150 | 151 | /* 152 | 153 | const locales = { 154 | en: require("date-fns/locale/en"), 155 | eo: require("date-fns/locale/eo"), 156 | ru: require("date-fns/locale/ru"), 157 | fr: require("date-fns/locale/fr") 158 | }; 159 | 160 | console.log(year, month, date); 161 | window.__localeId__ = "fr"; 162 | console.log( 163 | format(new Date(2018, month, date), "DD MMMM, YYYY", { 164 | locale: locales[window.__localeId__] 165 | }) 166 | ); 167 | */ 168 | } 169 | 170 | onDateChange(e) { 171 | const _value = e.target.value; 172 | 173 | const [_date, _month, _year] = _value.split("/"); 174 | //console.log(_date); 175 | //console.log(_month); 176 | //console.log(_year); 177 | 178 | const showMonth = _value.indexOf("/") >= 1; 179 | const showYear = _value.lastIndexOf("/") >= 3; 180 | if (showMonth) { 181 | this.renderMonthPicker(_date); 182 | } 183 | if (showYear) { 184 | this.setState({ 185 | showMonthPicker: false, 186 | showYearPicker: true, 187 | month: _month 188 | }); 189 | } 190 | 191 | if (_year && _year.length === 4) { 192 | this.setState({ 193 | showDropdown: false, 194 | showYearPicker: false 195 | }); 196 | } 197 | 198 | this.setState({ formattedDate: _value }); 199 | } 200 | 201 | render() { 202 | const { 203 | showDayPicker, 204 | showMonthPicker, 205 | showYearPicker, 206 | showDropdown, 207 | date, 208 | formattedDate 209 | } = this.state; 210 | return ( 211 | 212 | 213 | 219 | 220 | 221 | {showDayPicker && } 222 | {showMonthPicker && ( 223 | 224 | )} 225 | {showYearPicker && } 226 | 227 | 228 | ); 229 | } 230 | } 231 | 232 | export default DatePicker; 233 | -------------------------------------------------------------------------------- /src/DayPicker.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Wrapper = styled.div` 5 | display: grid; 6 | grid-template-columns: repeat(6, 1fr); 7 | grid-column-gap: 5px; 8 | grid-row-gap: 5px; 9 | max-width: 250px; 10 | max-height: 217px; 11 | margin: 0 auto; 12 | `; 13 | 14 | const Caption = styled.p` 15 | color: #555; 16 | font-size: 0.75em; 17 | `; 18 | const DayButton = styled.button` 19 | background: none; 20 | border: ${props => (props.currentDate ? "1px solid black" : "none")}; 21 | cursor: pointer; 22 | padding: 4px; 23 | text-align: center; 24 | width: 32px; 25 | border-radius: 4px; 26 | height: 32px; 27 | margin: 0 auto; 28 | 29 | &:focus, 30 | &:active, 31 | &:hover { 32 | background: #ff7494; 33 | color: white; 34 | } 35 | `; 36 | 37 | class DatePicker extends Component { 38 | constructor(props) { 39 | super(props); 40 | this.pickDate = this.pickDate.bind(this); 41 | } 42 | 43 | pickDate(e) { 44 | this.props.onDatePicked(e.target.textContent); 45 | } 46 | 47 | render() { 48 | const days = []; 49 | const currentDate = new Date().getDate(); 50 | for (let i = 0; i < 31; i++) days.push(i + 1); 51 | return ( 52 |
53 | Select a day 54 | 55 | {days.map(d => ( 56 | 61 | {d} 62 | 63 | ))} 64 | 65 |
66 | ); 67 | } 68 | } 69 | 70 | export default DatePicker; 71 | -------------------------------------------------------------------------------- /src/MonthPicker.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import styled from "styled-components"; 3 | import transition from "styled-transition-group"; 4 | import { format } from "date-fns"; 5 | 6 | const locales = { 7 | es: require("date-fns/locale/es"), 8 | fr: require("date-fns/locale/fr") 9 | }; 10 | 11 | const Wrapper = styled.div` 12 | display: grid; 13 | grid-template-columns: repeat(3, 1fr); 14 | grid-column-gap: 5px; 15 | grid-row-gap: 5px; 16 | max-width: 250px; 17 | min-height: 167px; 18 | max-height: 167px; 19 | margin: 25px auto; 20 | `; 21 | 22 | const Caption = styled.p` 23 | color: #555; 24 | font-size: 0.75em; 25 | `; 26 | const MonthButton = styled.button` 27 | background: none; 28 | border: ${props => (props.currentMonth ? "1px solid black" : "none")}; 29 | cursor: pointer; 30 | padding: 8px; 31 | text-align: center; 32 | border-radius: 4px; 33 | margin: 0 auto; 34 | 35 | &:focus, 36 | &:active, 37 | &:hover { 38 | background: #ff7494; 39 | color: white; 40 | } 41 | 42 | &:disabled { 43 | cursor: not-allowed; 44 | user-select: none; 45 | } 46 | `; 47 | 48 | const Transition = transition.div.attrs({ 49 | unmountOnExit: true, 50 | timeout: 1000 51 | })` 52 | &:enter { 53 | transform: translateX(300px); 54 | } 55 | &:enter-active { 56 | transform: translateX(0px); 57 | transition: transform 400ms ease-in-out; 58 | } 59 | &:exit { 60 | transform: translateX(0px); 61 | } 62 | &:exit-active { 63 | transform: translateX(300px); 64 | transition: transoform 400ms ease-in; 65 | } 66 | `; 67 | 68 | class DatePicker extends Component { 69 | constructor(props) { 70 | super(props); 71 | this.pickMonth = this.pickMonth.bind(this); 72 | this.state = { 73 | show: false 74 | }; 75 | } 76 | 77 | componentDidMount() { 78 | setTimeout(() => { 79 | this.setState({ show: true }); 80 | }); 81 | } 82 | 83 | pickMonth(e) { 84 | this.props.onMonthPicked(e.target.dataset.month); 85 | } 86 | 87 | render() { 88 | const months = [ 89 | "January", 90 | "February", 91 | "March", 92 | "April", 93 | "May", 94 | "June", 95 | "July", 96 | "August", 97 | "September", 98 | "October", 99 | "November", 100 | "December" 101 | ]; 102 | 103 | const monthWith30days = [ 104 | "February", 105 | "April", 106 | "June", 107 | "September", 108 | "November" 109 | ]; 110 | 111 | const { date } = this.props; 112 | const { show } = this.state; 113 | const today = new Date(); 114 | return ( 115 | 116 | Select month 117 | 118 | {months.map((m, index) => ( 119 | 137 | ))} 138 | 139 | 140 | ); 141 | } 142 | } 143 | 144 | export default DatePicker; 145 | -------------------------------------------------------------------------------- /src/YearPicker.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import styled from "styled-components"; 3 | import transition from "styled-transition-group"; 4 | 5 | const Wrapper = styled.div` 6 | display: grid; 7 | grid-template-columns: repeat(5, 1fr); 8 | grid-column-gap: 5px; 9 | grid-row-gap: 5px; 10 | max-width: 250px; 11 | max-height: 175px; 12 | overflow-y: scroll; 13 | margin: 0 auto; 14 | `; 15 | 16 | const Caption = styled.p` 17 | color: #555; 18 | font-size: 0.75em; 19 | margin-bottom: 0; 20 | `; 21 | 22 | const YearButton = styled.button` 23 | background: none; 24 | border: ${props => (props.currentYear ? "1px solid black" : "none")}; 25 | cursor: pointer; 26 | padding: 4px; 27 | text-align: center; 28 | border-radius: 4px; 29 | margin: 0 auto; 30 | 31 | &:focus, 32 | &:active, 33 | &:hover { 34 | background: #ff7494; 35 | color: white; 36 | } 37 | `; 38 | 39 | const Pointer = styled.button` 40 | margin: 0 auto; 41 | cursor: pointer; 42 | border: none; 43 | background: none; 44 | `; 45 | 46 | const Transition = transition.div.attrs({ 47 | unmountOnExit: true, 48 | timeout: 1000 49 | })` 50 | &:enter { 51 | transform: translateX(300px); 52 | } 53 | &:enter-active { 54 | transform: translateX(0px); 55 | transition: transform 400ms ease-in-out; 56 | } 57 | 58 | `; 59 | 60 | class DatePicker extends Component { 61 | constructor(props) { 62 | super(props); 63 | this.pickYear = this.pickYear.bind(this); 64 | this.state = { 65 | show: false 66 | }; 67 | 68 | this.wrapper = React.createRef(); 69 | } 70 | 71 | componentDidMount() { 72 | setTimeout(() => { 73 | this.setState({ show: true }); 74 | // need to revisit this 75 | this.wrapper.current.scrollTop = 188; 76 | }); 77 | } 78 | 79 | pickYear(e) { 80 | this.setState({ show: false }); 81 | this.props.onYearPicked(e.target.textContent); 82 | } 83 | 84 | render() { 85 | let today = new Date(); 86 | let currentYear = today.getFullYear(); 87 | let minYear = currentYear - 50; 88 | let maxYear = currentYear + 50; 89 | const years = []; 90 | for (let i = minYear; i < maxYear; i++) years.push(i); 91 | const { show } = this.state; 92 | return ( 93 | 94 | Select year 95 | (this.wrapper.current.scrollTop -= 21)} 98 | > 99 | ▲ 100 | 101 | 102 | {years.map(y => ( 103 | 108 | {y} 109 | 110 | ))} 111 | 112 | (this.wrapper.current.scrollTop += 21)} 115 | > 116 | ▼ 117 | 118 | 119 | ); 120 | } 121 | } 122 | 123 | export default DatePicker; 124 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export function register(config) { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Let's check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl, config); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl, config); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl, config) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | 70 | // Execute callback 71 | if (config.onUpdate) { 72 | config.onUpdate(registration); 73 | } 74 | } else { 75 | // At this point, everything has been precached. 76 | // It's the perfect time to display a 77 | // "Content is cached for offline use." message. 78 | console.log('Content is cached for offline use.'); 79 | 80 | // Execute callback 81 | if (config.onSuccess) { 82 | config.onSuccess(registration); 83 | } 84 | } 85 | } 86 | }; 87 | }; 88 | }) 89 | .catch(error => { 90 | console.error('Error during service worker registration:', error); 91 | }); 92 | } 93 | 94 | function checkValidServiceWorker(swUrl, config) { 95 | // Check if the service worker can be found. If it can't reload the page. 96 | fetch(swUrl) 97 | .then(response => { 98 | // Ensure service worker exists, and that we really are getting a JS file. 99 | if ( 100 | response.status === 404 || 101 | response.headers.get('content-type').indexOf('javascript') === -1 102 | ) { 103 | // No service worker found. Probably a different app. Reload the page. 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister().then(() => { 106 | window.location.reload(); 107 | }); 108 | }); 109 | } else { 110 | // Service worker found. Proceed as normal. 111 | registerValidSW(swUrl, config); 112 | } 113 | }) 114 | .catch(() => { 115 | console.log( 116 | 'No internet connection found. App is running in offline mode.' 117 | ); 118 | }); 119 | } 120 | 121 | export function unregister() { 122 | if ('serviceWorker' in navigator) { 123 | navigator.serviceWorker.ready.then(registration => { 124 | registration.unregister(); 125 | }); 126 | } 127 | } 128 | --------------------------------------------------------------------------------