Skip to content

Commit

Permalink
feat: add timer machine
Browse files Browse the repository at this point in the history
  • Loading branch information
anubra266 committed May 21, 2024
1 parent 1a7f0bf commit 613bac0
Show file tree
Hide file tree
Showing 16 changed files with 10,794 additions and 11,770 deletions.
86 changes: 86 additions & 0 deletions .xstate/timer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use strict";

var _xstate = require("xstate");
const {
actions,
createMachine,
assign
} = _xstate;
const {
choose
} = actions;
const fetchMachine = createMachine({
id: "timer",
initial: ctx.autostart ? "running" : "idle",
context: {
"isCountdownComplete": false
},
on: {
RESTART: {
target: "running",
actions: "resetTime"
}
},
on: {
UPDATE_CONTEXT: {
actions: "updateContext"
}
},
states: {
idle: {
on: {
START: "running",
RESET: {
actions: "resetTime"
}
}
},
running: {
invoke: {
src: "interval",
id: "interval"
},
on: {
PAUSE: "paused",
TICK: [{
target: "completed",
cond: "isCountdownComplete",
actions: ["invokeOnComplete"]
}, {
actions: ["updateTime", "invokeOnTick"]
}],
RESET: {
actions: "resetTime"
}
}
},
paused: {
on: {
RESUME: "running",
RESET: {
target: "idle",
actions: "resetTime"
}
}
},
completed: {
on: {
RESET: {
target: "idle",
actions: "resetTime"
}
}
}
}
}, {
actions: {
updateContext: assign((context, event) => {
return {
[event.contextKey]: true
};
})
},
guards: {
"isCountdownComplete": ctx => ctx["isCountdownComplete"]
}
});
77 changes: 77 additions & 0 deletions examples/next-ts/components/flip-timer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from "react"

export function FlipTimer({ reverse, time }: any) {
return (
<div className="container">
<Digit label="days" value={time.day} reverse={reverse} />
<Digit label="hours" value={time.hour} reverse={reverse} />
<Digit label="mins" value={time.minute} reverse={reverse} />
<Digit label="secs" value={time.second} reverse={reverse} />
</div>
)
}

function Digit({ label, value, reverse }: any) {
const [digit, setDigit] = React.useState(value)
const [flipped, setFlipped] = React.useState(false)

const next = nextDigit(label, digit, reverse)

React.useEffect(() => {
if (digit !== value) {
setFlipped(true)
} else {
setFlipped(false)
}
}, [value, digit])

const handleTransitionEnd = (): void => {
setDigit(value)
setFlipped(false)
}

return (
<div className="wrapper">
<div className="block">
<div className="next">{next}</div>
<div className="current">{digit}</div>
<div data-flipped={flipped ? "" : undefined} className="flip" onTransitionEnd={handleTransitionEnd}>
<div className="face face_front">{digit}</div>
<div className="face face_back">{next}</div>
</div>
</div>
<span>{label}</span>
</div>
)
}

function nextDigit(label: string, digit: number, reverse?: boolean) {
const ranges = {
days: 366,
hours: 24,
mins: 60,
secs: 60,
ms: 1000,
} as any

const maxVal = ranges[label]

let nextDigit
if (reverse) {
// Counting backwards
if (digit === 0) {
nextDigit = maxVal - 1
} else {
nextDigit = digit - 1
}
} else {
// Counting forwards
if (digit === maxVal - 1) {
nextDigit = 0
} else {
nextDigit = digit + 1
}
}

return nextDigit
}
1 change: 1 addition & 0 deletions examples/next-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@zag-js/tags-input": "workspace:*",
"@zag-js/text-selection": "workspace:*",
"@zag-js/time-picker": "workspace:*",
"@zag-js/timer": "workspace:*",
"@zag-js/toast": "workspace:*",
"@zag-js/toggle-group": "workspace:*",
"@zag-js/tooltip": "workspace:*",
Expand Down
66 changes: 66 additions & 0 deletions examples/next-ts/pages/timer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as timer from "@zag-js/timer"
import { useMachine, normalizeProps } from "@zag-js/react"
import { useId } from "react"
import { StateVisualizer } from "../components/state-visualizer"
import { Toolbar } from "../components/toolbar"
import { FlipTimer } from "../components/flip-timer"

export default function Page() {
const [stopwatchstate, stopwatchsend] = useMachine(
timer.machine({
id: useId(),
mode: "stopwatch",
autostart: true,
}),
)

const stopwatch = timer.connect(stopwatchstate, stopwatchsend, normalizeProps)

const [countdownstate, countdownsend] = useMachine(
timer.machine({
id: useId(),
mode: "countdown",
duration: 466_153_000,
autostart: true,
min: -5000,
}),
)

const countdown = timer.connect(countdownstate, countdownsend, normalizeProps)

return (
<>
<main className="timer">
<div>
<h1> Stopwatch</h1>

<FlipTimer time={stopwatch.countTimeUnits} />

<h2>paused: {stopwatch.paused.toString()}</h2>
<button onClick={stopwatch.start}>START</button>
<button onClick={stopwatch.pause}>PAUSE</button>
<button onClick={stopwatch.resume}>RESUME</button>
<button onClick={stopwatch.reset}>RESET</button>
<button onClick={stopwatch.restart}>RESTART</button>
</div>

<div>
<h1> Countdown</h1>

<FlipTimer reverse time={countdown.countTimeUnits} />

<h2>completed: {countdown.completed.toString()}</h2>
<button onClick={countdown.start}>START</button>
<button onClick={countdown.pause}>PAUSE</button>
<button onClick={countdown.resume}>RESUME</button>
<button onClick={countdown.reset}>RESET</button>
</div>
</main>

<Toolbar controls={null} viz>
<StateVisualizer state={stopwatch} label="Stopwatch" />
<StateVisualizer state={countdown} label="Countdown" />
</Toolbar>
</>
)
}
1 change: 1 addition & 0 deletions packages/machines/timer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @zag-js/checkbox
19 changes: 19 additions & 0 deletions packages/machines/timer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# @zag-js/checkbox

Core logic for the checkbox widget implemented as a state machine

## **Installation**

```sh
yarn add @zag-js/timer
# or
npm i @zag-js/timer
```

## Contribution

Yes please! See the [contributing guidelines](https://github.com/chakra-ui/zag/blob/main/CONTRIBUTING.md) for details.

## Licence

This project is licensed under the terms of the [MIT license](https://github.com/chakra-ui/zag/blob/main/LICENSE).
48 changes: 48 additions & 0 deletions packages/machines/timer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@zag-js/timer",
"version": "0.50.0",
"description": "Core logic for the timer widget implemented as a state machine",
"keywords": [
"js",
"machine",
"xstate",
"statechart",
"component",
"chakra-ui",
"timer"
],
"author": "Abraham Aremu <[email protected]>",
"homepage": "https://github.com/chakra-ui/zag#readme",
"license": "MIT",
"repository": "https://github.com/chakra-ui/zag/tree/main/packages/timer",
"sideEffects": false,
"files": [
"dist",
"src"
],
"scripts": {
"build": "tsup",
"test": "jest --config ../../jest.config.js --rootDir tests",
"lint": "eslint src --ext .ts,.tsx",
"test-ci": "pnpm test --ci --runInBand -u",
"test-watch": "pnpm test --watch",
"prepack": "clean-package",
"postpack": "clean-package restore"
},
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/chakra-ui/zag/issues"
},
"dependencies": {
"@zag-js/core": "workspace:*",
"@zag-js/types": "workspace:*",
"@zag-js/utils": "workspace:*"
},
"devDependencies": {
"clean-package": "2.2.0"
},
"clean-package": "../../../clean-package.config.json",
"main": "src/index.ts"
}
3 changes: 3 additions & 0 deletions packages/machines/timer/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { connect } from "./timer.connect"
export { machine } from "./timer.machine"
export type { MachineApi as Api, UserDefinedContext as Context } from "./timer.types"
37 changes: 37 additions & 0 deletions packages/machines/timer/src/timer.connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { NormalizeProps, PropTypes } from "@zag-js/types"
import type { MachineApi, State, Send } from "./timer.types"

export function connect<T extends PropTypes>(state: State, send: Send, normalize: NormalizeProps<T>): MachineApi<T> {
const running = state.matches("running")
const paused = state.matches("paused")
const completed = state.matches("completed")

const duration = state.context.duration

const count = state.context.count
const countTimeUnits = state.context.countTimeUnits

return {
running,
paused,
completed,
duration,
count,
countTimeUnits,
start() {
send("START")
},
pause() {
send("PAUSE")
},
resume() {
send("RESUME")
},
reset() {
send("RESET")
},
restart() {
send("RESTART")
},
}
}
Loading

0 comments on commit 613bac0

Please sign in to comment.