react-time-machine-js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/TimeMachine.d.ts +23 -0
- package/dist/TimeMachine.js +66 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +39 -0
- package/src/TimeMachine.css +105 -0
- package/src/TimeMachine.tsx +147 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# react-time-machine-js
|
|
2
|
+
|
|
3
|
+
A React component package that provides a ready-to-drop-in dev widget for any React app, powered by [time-machine-js](https://www.npmjs.com/package/time-machine-js).
|
|
4
|
+
|
|
5
|
+
It provides a minimal UI to control the global `Date.now()` patch, allowing you to simulate different points in time (flowing or frozen) during development.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install react-time-machine-js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> [!NOTE]
|
|
14
|
+
> `react` and `react-dom` (>=17) are peer dependencies and must be provided by the consumer.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Render the `<TimeMachine />` component anywhere in your app. It is recommended to wrap it in a development guard.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { TimeMachine } from 'react-time-machine-js';
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<div>
|
|
26
|
+
{/* ... your app ... */}
|
|
27
|
+
|
|
28
|
+
{process.env.NODE_ENV === 'development' && <TimeMachine />}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
### Props
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
| :--- | :--- | :--- | :--- |
|
|
40
|
+
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Widget corner position. |
|
|
41
|
+
| `storageKey` | `string` | `'__timeMachine__'` | LocalStorage key for persistence. |
|
|
42
|
+
| `onTravel` | `(timestamp: number, mode: 'flowing' | 'frozen') => void` | - | Callback on activation. |
|
|
43
|
+
| `onReturnToPresent` | `() => void` | - | Callback on reset. |
|
|
44
|
+
|
|
45
|
+
## Behavior
|
|
46
|
+
|
|
47
|
+
- **Auto-Restore**: On mount, the component automatically restores any saved state from `localStorage`.
|
|
48
|
+
- **Auto-Cleanup**: On unmount, the component calls `returnToPresent()` to restore the native `Date.now()`.
|
|
49
|
+
- **Persistence**: Clicking "Activate" saves the state to `localStorage`. Clicking "Reset" clears the saved state.
|
|
50
|
+
- **Sync**: The widget polls the current simulated time every second to keep the status display in sync.
|
|
51
|
+
|
|
52
|
+
## Styling
|
|
53
|
+
|
|
54
|
+
Styles are fully self-contained using inline CSS. No external stylesheets or configuration required. The widget uses a high z-index (`9999`) to stay on top of your application.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './TimeMachine.css';
|
|
3
|
+
export interface TimeMachineProps {
|
|
4
|
+
/**
|
|
5
|
+
* Position of the widget on the screen.
|
|
6
|
+
* @default 'bottom-right'
|
|
7
|
+
*/
|
|
8
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
9
|
+
/**
|
|
10
|
+
* The localStorage key used to persist the time machine state.
|
|
11
|
+
* @default '__timeMachine__'
|
|
12
|
+
*/
|
|
13
|
+
storageKey?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Callback fired when a new time/mode is activated.
|
|
16
|
+
*/
|
|
17
|
+
onTravel?: (timestamp: number, mode: 'flowing' | 'frozen') => void;
|
|
18
|
+
/**
|
|
19
|
+
* Callback fired when the time machine is reset.
|
|
20
|
+
*/
|
|
21
|
+
onReturnToPresent?: () => void;
|
|
22
|
+
}
|
|
23
|
+
export declare const TimeMachine: React.FC<TimeMachineProps>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { travel, returnToPresent, isActive, save, restore, getMode } from 'time-machine-js';
|
|
4
|
+
import './TimeMachine.css';
|
|
5
|
+
let isRestored = false;
|
|
6
|
+
export const TimeMachine = ({ position = 'bottom-right', storageKey = '__timeMachine__', onTravel, onReturnToPresent, }) => {
|
|
7
|
+
if (!isRestored) {
|
|
8
|
+
restore(storageKey);
|
|
9
|
+
isRestored = true;
|
|
10
|
+
}
|
|
11
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
12
|
+
const [active, setActive] = useState(isActive());
|
|
13
|
+
const [displayTime, setDisplayTime] = useState(Date.now());
|
|
14
|
+
const [mode, setMode] = useState('flowing');
|
|
15
|
+
const [inputTime, setInputTime] = useState('');
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const update = () => {
|
|
18
|
+
setActive(isActive());
|
|
19
|
+
setDisplayTime(Date.now());
|
|
20
|
+
};
|
|
21
|
+
const interval = setInterval(update, 1000);
|
|
22
|
+
return () => clearInterval(interval);
|
|
23
|
+
}, []);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
return () => {
|
|
26
|
+
returnToPresent();
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
const handleToggleExpand = () => {
|
|
30
|
+
if (!isExpanded) {
|
|
31
|
+
const now = new Date();
|
|
32
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
33
|
+
const localStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
34
|
+
setInputTime(localStr);
|
|
35
|
+
const currentMode = getMode();
|
|
36
|
+
if (currentMode) {
|
|
37
|
+
setMode(currentMode);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
setIsExpanded(!isExpanded);
|
|
41
|
+
};
|
|
42
|
+
const handleActivate = () => {
|
|
43
|
+
const timestamp = new Date(inputTime).getTime();
|
|
44
|
+
if (isNaN(timestamp))
|
|
45
|
+
return;
|
|
46
|
+
travel(timestamp, mode);
|
|
47
|
+
save(storageKey);
|
|
48
|
+
setActive(true);
|
|
49
|
+
if (onTravel)
|
|
50
|
+
onTravel(timestamp, mode);
|
|
51
|
+
};
|
|
52
|
+
const handleReset = () => {
|
|
53
|
+
returnToPresent();
|
|
54
|
+
localStorage.removeItem(storageKey);
|
|
55
|
+
setActive(false);
|
|
56
|
+
if (onReturnToPresent)
|
|
57
|
+
onReturnToPresent();
|
|
58
|
+
};
|
|
59
|
+
const getStatusText = () => {
|
|
60
|
+
if (!active)
|
|
61
|
+
return '● Real time';
|
|
62
|
+
const dateStr = new Date(displayTime).toLocaleString();
|
|
63
|
+
return `● ${mode.charAt(0).toUpperCase() + mode.slice(1)}: ${dateStr}`;
|
|
64
|
+
};
|
|
65
|
+
return (_jsxs("div", { className: `time-machine-widget position-${position}`, children: [_jsx("div", { className: "time-machine-status-bar", onClick: handleToggleExpand, children: _jsx("span", { className: active ? 'time-machine-status-bar-active' : 'time-machine-status-bar-inactive', children: getStatusText() }) }), _jsxs("div", { className: `time-machine-panel ${!isExpanded ? 'time-machine-panel-hidden' : ''}`, children: [_jsxs("div", { className: "time-machine-input-group", children: [_jsx("label", { children: "Target Date/Time:" }), _jsx("input", { type: "datetime-local", className: "time-machine-input", value: inputTime, onChange: (e) => setInputTime(e.target.value) })] }), _jsxs("div", { className: "time-machine-input-group", children: [_jsx("label", { children: "Mode:" }), _jsxs("div", { className: "time-machine-toggle", children: [_jsx("div", { className: `time-machine-toggle-option ${mode === 'flowing' ? 'time-machine-toggle-option-active' : ''}`, onClick: () => setMode('flowing'), children: "Flowing" }), _jsx("div", { className: `time-machine-toggle-option ${mode === 'frozen' ? 'time-machine-toggle-option-active' : ''}`, onClick: () => setMode('frozen'), children: "Frozen" })] })] }), _jsx("button", { className: "time-machine-button", onClick: handleActivate, children: "Activate" }), active && (_jsx("button", { className: "time-machine-button time-machine-button-reset", onClick: handleReset, children: "Reset to Present" }))] })] }));
|
|
66
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TimeMachine';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TimeMachine';
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-time-machine-js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Ready-to-drop-in React dev widget for time-machine-js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"react",
|
|
14
|
+
"time-machine",
|
|
15
|
+
"testing",
|
|
16
|
+
"development",
|
|
17
|
+
"date-monkey-patch"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/WilliamPinto-Olmos/react-time-machine-js.git"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"time-machine-js": "latest"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": ">=17",
|
|
30
|
+
"react-dom": ">=17"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^18.0.0",
|
|
34
|
+
"@types/react-dom": "^18.0.0",
|
|
35
|
+
"react": "^18.0.0",
|
|
36
|
+
"react-dom": "^18.0.0",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
.time-machine-widget {
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: 9999;
|
|
4
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
5
|
+
font-size: 12px;
|
|
6
|
+
background-color: rgba(0, 0, 0, 0.85);
|
|
7
|
+
color: #fff;
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
14
|
+
transition: all 0.2s ease-in-out;
|
|
15
|
+
user-select: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.time-machine-widget.position-bottom-right { bottom: 20px; right: 20px; }
|
|
19
|
+
.time-machine-widget.position-bottom-left { bottom: 20px; left: 20px; }
|
|
20
|
+
.time-machine-widget.position-top-right { top: 20px; right: 20px; }
|
|
21
|
+
.time-machine-widget.position-top-left { top: 20px; left: 20px; }
|
|
22
|
+
|
|
23
|
+
.time-machine-status-bar {
|
|
24
|
+
padding: 8px 12px;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.time-machine-status-bar-active {
|
|
34
|
+
color: #10b981;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.time-machine-status-bar-inactive {
|
|
38
|
+
color: #6b7280;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.time-machine-panel {
|
|
42
|
+
padding: 12px;
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
gap: 10px;
|
|
46
|
+
border-top: 1px solid rgba(255,255,255,0.1);
|
|
47
|
+
width: 240px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.time-machine-panel-hidden {
|
|
51
|
+
display: none !important;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.time-machine-input-group {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 4px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.time-machine-input {
|
|
61
|
+
background-color: #333;
|
|
62
|
+
border: 1px solid #444;
|
|
63
|
+
color: #fff;
|
|
64
|
+
padding: 4px 8px;
|
|
65
|
+
border-radius: 3px;
|
|
66
|
+
font-size: 12px;
|
|
67
|
+
width: 100%;
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.time-machine-toggle {
|
|
72
|
+
display: flex;
|
|
73
|
+
background-color: #222;
|
|
74
|
+
border-radius: 3px;
|
|
75
|
+
padding: 2px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.time-machine-toggle-option {
|
|
79
|
+
flex: 1;
|
|
80
|
+
padding: 4px;
|
|
81
|
+
text-align: center;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
border-radius: 2px;
|
|
84
|
+
background-color: transparent;
|
|
85
|
+
transition: background 0.2s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.time-machine-toggle-option-active {
|
|
89
|
+
background-color: #444;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.time-machine-button {
|
|
93
|
+
background-color: #3d82f6;
|
|
94
|
+
color: #fff;
|
|
95
|
+
border: none;
|
|
96
|
+
padding: 6px 12px;
|
|
97
|
+
border-radius: 3px;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
font-size: 12px;
|
|
100
|
+
font-weight: bold;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.time-machine-button-reset {
|
|
104
|
+
background-color: #ef4444;
|
|
105
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { travel, returnToPresent, getOffset, isActive, save, restore, TimeMachineMode, getMode } from 'time-machine-js';
|
|
3
|
+
import './TimeMachine.css';
|
|
4
|
+
|
|
5
|
+
export interface TimeMachineProps {
|
|
6
|
+
/**
|
|
7
|
+
* Position of the widget on the screen.
|
|
8
|
+
* @default 'bottom-right'
|
|
9
|
+
*/
|
|
10
|
+
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
11
|
+
/**
|
|
12
|
+
* The localStorage key used to persist the time machine state.
|
|
13
|
+
* @default '__timeMachine__'
|
|
14
|
+
*/
|
|
15
|
+
storageKey?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Callback fired when a new time/mode is activated.
|
|
18
|
+
*/
|
|
19
|
+
onTravel?: (timestamp: number, mode: 'flowing' | 'frozen') => void;
|
|
20
|
+
/**
|
|
21
|
+
* Callback fired when the time machine is reset.
|
|
22
|
+
*/
|
|
23
|
+
onReturnToPresent?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let isRestored = false;
|
|
27
|
+
|
|
28
|
+
export const TimeMachine: React.FC<TimeMachineProps> = ({
|
|
29
|
+
position = 'bottom-right',
|
|
30
|
+
storageKey = '__timeMachine__',
|
|
31
|
+
onTravel,
|
|
32
|
+
onReturnToPresent,
|
|
33
|
+
}) => {
|
|
34
|
+
if (!isRestored) {
|
|
35
|
+
restore(storageKey);
|
|
36
|
+
isRestored = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
40
|
+
const [active, setActive] = useState(isActive());
|
|
41
|
+
const [displayTime, setDisplayTime] = useState(Date.now());
|
|
42
|
+
const [mode, setMode] = useState<TimeMachineMode>('flowing');
|
|
43
|
+
const [inputTime, setInputTime] = useState('');
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const update = () => {
|
|
47
|
+
setActive(isActive());
|
|
48
|
+
setDisplayTime(Date.now());
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const interval = setInterval(update, 1000);
|
|
52
|
+
return () => clearInterval(interval);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
return () => {
|
|
57
|
+
returnToPresent();
|
|
58
|
+
};
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const handleToggleExpand = () => {
|
|
62
|
+
if (!isExpanded) {
|
|
63
|
+
const now = new Date();
|
|
64
|
+
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
65
|
+
const localStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
66
|
+
setInputTime(localStr);
|
|
67
|
+
const currentMode = getMode();
|
|
68
|
+
if (currentMode) {
|
|
69
|
+
setMode(currentMode);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
setIsExpanded(!isExpanded);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleActivate = () => {
|
|
76
|
+
const timestamp = new Date(inputTime).getTime();
|
|
77
|
+
if (isNaN(timestamp)) return;
|
|
78
|
+
|
|
79
|
+
travel(timestamp, mode);
|
|
80
|
+
save(storageKey);
|
|
81
|
+
setActive(true);
|
|
82
|
+
if (onTravel) onTravel(timestamp, mode);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleReset = () => {
|
|
86
|
+
returnToPresent();
|
|
87
|
+
localStorage.removeItem(storageKey);
|
|
88
|
+
setActive(false);
|
|
89
|
+
if (onReturnToPresent) onReturnToPresent();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const getStatusText = () => {
|
|
93
|
+
if (!active) return '● Real time';
|
|
94
|
+
const dateStr = new Date(displayTime).toLocaleString();
|
|
95
|
+
return `● ${mode.charAt(0).toUpperCase() + mode.slice(1)}: ${dateStr}`;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className={`time-machine-widget position-${position}`}>
|
|
100
|
+
<div className="time-machine-status-bar" onClick={handleToggleExpand}>
|
|
101
|
+
<span className={active ? 'time-machine-status-bar-active' : 'time-machine-status-bar-inactive'}>
|
|
102
|
+
{getStatusText()}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div className={`time-machine-panel ${!isExpanded ? 'time-machine-panel-hidden' : ''}`}>
|
|
107
|
+
<div className="time-machine-input-group">
|
|
108
|
+
<label>Target Date/Time:</label>
|
|
109
|
+
<input
|
|
110
|
+
type="datetime-local"
|
|
111
|
+
className="time-machine-input"
|
|
112
|
+
value={inputTime}
|
|
113
|
+
onChange={(e) => setInputTime(e.target.value)}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="time-machine-input-group">
|
|
118
|
+
<label>Mode:</label>
|
|
119
|
+
<div className="time-machine-toggle">
|
|
120
|
+
<div
|
|
121
|
+
className={`time-machine-toggle-option ${mode === 'flowing' ? 'time-machine-toggle-option-active' : ''}`}
|
|
122
|
+
onClick={() => setMode('flowing')}
|
|
123
|
+
>
|
|
124
|
+
Flowing
|
|
125
|
+
</div>
|
|
126
|
+
<div
|
|
127
|
+
className={`time-machine-toggle-option ${mode === 'frozen' ? 'time-machine-toggle-option-active' : ''}`}
|
|
128
|
+
onClick={() => setMode('frozen')}
|
|
129
|
+
>
|
|
130
|
+
Frozen
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<button className="time-machine-button" onClick={handleActivate}>
|
|
136
|
+
Activate
|
|
137
|
+
</button>
|
|
138
|
+
|
|
139
|
+
{active && (
|
|
140
|
+
<button className="time-machine-button time-machine-button-reset" onClick={handleReset}>
|
|
141
|
+
Reset to Present
|
|
142
|
+
</button>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TimeMachine';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"outDir": "dist",
|
|
16
|
+
"jsx": "react-jsx"
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|