├── .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 | 30 && monthWith30days.includes(m)) ||
124 | (date === "30" && m === "February")
125 | }
126 | data-month={index + 1}
127 | currentMonth={index === today.getMonth()}
128 | >
129 | {format(
130 | new Date(today.getFullYear(), months.indexOf(m), date),
131 | "MMMM",
132 | {
133 | locale: locales["en"]
134 | }
135 | )}
136 |
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 |
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 |
--------------------------------------------------------------------------------