@timekeeper-countdown/react 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/README.md +236 -0
  2. package/package.json +4 -3
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # @timekeeper-countdown/react
2
+
3
+ React hook for the Timekeeper Countdown engine. This package provides an idiomatic way to manage countdown timers in React components while keeping the underlying engine framework-agnostic.
4
+
5
+ - Single hook: `useCountdown(initialSeconds, options?)`.
6
+ - Snapshot-first API that keeps your UI in sync with timer state.
7
+ - Zero runtime dependencies besides React.
8
+ - Works in React 17+ (including React 18 concurrent mode).
9
+ - Ships modern ESM output with type definitions.
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @timekeeper-countdown/react
17
+ ```
18
+
19
+ Peer dependencies (must already exist in your application):
20
+
21
+ - `react` ≥ 17
22
+ - `@timekeeper-countdown/core` is installed automatically as a dependency.
23
+
24
+ ---
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import { useCountdown } from '@timekeeper-countdown/react';
30
+ import { formatTime } from '@timekeeper-countdown/core/format';
31
+
32
+ export function CountdownCard() {
33
+ const countdown = useCountdown(5 * 60, {
34
+ autoStart: true,
35
+ });
36
+
37
+ const clock = formatTime(countdown.snapshot);
38
+
39
+ return (
40
+ <section>
41
+ <h2>
42
+ {clock.minutes}:{clock.seconds}
43
+ </h2>
44
+ <p>Status: {countdown.state}</p>
45
+ <button onClick={countdown.pause} disabled={!countdown.isRunning}>
46
+ Pause
47
+ </button>
48
+ <button onClick={countdown.resume} disabled={countdown.isRunning}>
49
+ Resume
50
+ </button>
51
+ <button onClick={() => countdown.reset(5 * 60)}>Restart</button>
52
+ </section>
53
+ );
54
+ }
55
+ ```
56
+
57
+ Each hook instance owns a dedicated engine. When the component unmounts the engine is destroyed automatically.
58
+
59
+ ---
60
+
61
+ ## Hook Signature
62
+
63
+ ```ts
64
+ const result = useCountdown(initialSeconds: number, options?: UseCountdownOptions);
65
+ ```
66
+
67
+ ### Options
68
+
69
+ ```ts
70
+ interface UseCountdownOptions extends Omit<CountdownEngineOptions, 'onSnapshot' | 'onStateChange' | 'onError'> {
71
+ autoStart?: boolean; // Start automatically on mount (default false)
72
+ onSnapshot?: CountdownEngineOptions['onSnapshot'];
73
+ onStateChange?: CountdownEngineOptions['onStateChange'];
74
+ onError?: CountdownEngineOptions['onError'];
75
+ }
76
+ ```
77
+
78
+ - `tickIntervalMs`: Polling interval. Default `100`.
79
+ - `timeProvider`: Function or engine time provider (see core README) for deterministic timing or shared clocks.
80
+ - `autoStart`: If `true`, the hook starts immediately after mounting.
81
+
82
+ ### Return Value
83
+
84
+ ```ts
85
+ interface UseCountdownResult {
86
+ snapshot: CountdownSnapshot;
87
+ state: TimerState;
88
+ totalSeconds: number;
89
+ parts: CountdownSnapshot['parts'];
90
+ isRunning: boolean;
91
+ isCompleted: boolean;
92
+
93
+ start(): boolean;
94
+ pause(): boolean;
95
+ resume(): boolean;
96
+ reset(nextInitialSeconds?: number): boolean;
97
+ stop(): boolean;
98
+ setSeconds(value: number): void;
99
+ }
100
+ ```
101
+
102
+ - All control methods mirror the engine and return `false` for invalid transitions.
103
+ - `snapshot` is stable per render; derive memoised values with `useMemo` if needed.
104
+ - `totalSeconds`, `parts`, `isRunning`, and `isCompleted` are re-exposed for convenience.
105
+
106
+ ---
107
+
108
+ ## Formatting Helpers
109
+
110
+ Import helpers directly from the core package to render zero-padded strings:
111
+
112
+ ```tsx
113
+ import { formatTime } from '@timekeeper-countdown/core/format';
114
+
115
+ const clock = formatTime(countdown.snapshot);
116
+ ```
117
+
118
+ `formatTime` returns `{ minutes: string; seconds: string }`. Additional helpers include `formatMinutes`, `formatHours`, `formatDays`, etc.
119
+
120
+ ---
121
+
122
+ ## Integrating with Custom Time Providers
123
+
124
+ You can plug a fake or shared time provider using the utilities bundled with the core package:
125
+
126
+ ```tsx
127
+ import { createFakeTimeProvider, toTimeProvider } from '@timekeeper-countdown/core/testing-utils';
128
+
129
+ const fake = createFakeTimeProvider({ startMs: 0 });
130
+
131
+ function InspectableTimer() {
132
+ const countdown = useCountdown(30, {
133
+ autoStart: true,
134
+ timeProvider: toTimeProvider(fake),
135
+ tickIntervalMs: 10,
136
+ });
137
+
138
+ return (
139
+ <div>
140
+ <span>{countdown.totalSeconds}s</span>
141
+ <button onClick={() => fake.advance(1000)}>Advance 1s</button>
142
+ </div>
143
+ );
144
+ }
145
+ ```
146
+
147
+ This decouples the timer from the real clock and enables deterministic testing or synchronized timers.
148
+
149
+ ---
150
+
151
+ ## Testing
152
+
153
+ `@testing-library/react` and Vitest/RTL work seamlessly with the hook. Inject the fake provider to avoid relying on real timers:
154
+
155
+ ```tsx
156
+ import { render, screen } from '@testing-library/react';
157
+ import userEvent from '@testing-library/user-event';
158
+ import { createFakeTimeProvider, toTimeProvider } from '@timekeeper-countdown/core/testing-utils';
159
+ import { useCountdown } from '@timekeeper-countdown/react';
160
+
161
+ function TestComponent() {
162
+ const fake = useMemo(() => createFakeTimeProvider({ startMs: 0 }), []);
163
+ const countdown = useCountdown(5, {
164
+ autoStart: true,
165
+ timeProvider: toTimeProvider(fake),
166
+ });
167
+
168
+ return (
169
+ <div>
170
+ <output>{countdown.totalSeconds}</output>
171
+ <button onClick={() => fake.advance(1000)}>Advance</button>
172
+ </div>
173
+ );
174
+ }
175
+
176
+ it('counts down when the fake clock advances', async () => {
177
+ render(<TestComponent />);
178
+ await userEvent.click(screen.getByRole('button', { name: /advance/i }));
179
+ expect(screen.getByText('4')).toBeInTheDocument();
180
+ });
181
+ ```
182
+
183
+ The hook handles cleanup automatically, so tests do not need to destroy the engine manually.
184
+
185
+ ---
186
+
187
+ ## Multiple Timers
188
+
189
+ Each hook call is independent. Compose them to build complex flows:
190
+
191
+ ```tsx
192
+ function MultiStageTimer() {
193
+ const focus = useCountdown(25 * 60, { autoStart: true });
194
+ const breakTimer = useCountdown(5 * 60);
195
+
196
+ useEffect(() => {
197
+ if (focus.isCompleted) {
198
+ breakTimer.reset();
199
+ breakTimer.start();
200
+ }
201
+ }, [focus.isCompleted, breakTimer]);
202
+
203
+ return (
204
+ <div>
205
+ <TimerCard title="Focus" countdown={focus} />
206
+ <TimerCard title="Break" countdown={breakTimer} />
207
+ </div>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Related Packages
215
+
216
+ - Engine & utilities: [`@timekeeper-countdown/core`](https://www.npmjs.com/package/@timekeeper-countdown/core)
217
+ - Documentation site (guides & roadmap): <https://eagle-head.github.io/timekeeper-countdown/>
218
+ - GitHub repository: <https://github.com/eagle-head/timekeeper-countdown>
219
+
220
+ ---
221
+
222
+ ## Contributing
223
+
224
+ Bug reports and pull requests are welcome. Please read the [repository guidelines](https://github.com/eagle-head/timekeeper-countdown/blob/main/AGENTS.md) and check your work with:
225
+
226
+ ```bash
227
+ npm run lint --workspaces
228
+ npm run test --workspaces
229
+ npm run typecheck --workspaces
230
+ ```
231
+
232
+ ---
233
+
234
+ ## License
235
+
236
+ MIT © [Eduardo Kohn](https://www.linkedin.com/in/eduardo-kohn-56817b195/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekeeper-countdown/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "React adapter for the Timekeeper Countdown engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,7 +13,8 @@
13
13
  }
14
14
  },
15
15
  "files": [
16
- "dist"
16
+ "dist",
17
+ "README.md"
17
18
  ],
18
19
  "scripts": {
19
20
  "build": "tsup",
@@ -32,7 +33,7 @@
32
33
  "react": ">=17.0.0"
33
34
  },
34
35
  "dependencies": {
35
- "@timekeeper-countdown/core": "^0.1.0"
36
+ "@timekeeper-countdown/core": "^0.1.1"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@testing-library/jest-dom": "^6.4.2",