Skip to content

Commit

Permalink
feat: add timer machine (chakra-ui#1508)
Browse files Browse the repository at this point in the history
* feat: add timer machine

* chore: simplify samples

* chore: add anatomy

* chore: update anatomy

* refactor: timer machine

* docs: add changeset

---------

Co-authored-by: Segun Adebayo <[email protected]>
  • Loading branch information
2 people authored and colinlienard committed Jun 18, 2024
1 parent 8fc1dc2 commit d951e04
Show file tree
Hide file tree
Showing 23 changed files with 3,199 additions and 1,702 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-radios-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zag-js/timer": minor
---

Initial release
78 changes: 78 additions & 0 deletions .xstate/timer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"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: {
"hasReachedTarget": 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: "idle",
cond: "hasReachedTarget",
actions: ["invokeOnComplete"]
}, {
actions: ["updateTime", "invokeOnTick"]
}],
RESET: {
actions: "resetTime"
}
}
},
paused: {
on: {
RESUME: "running",
RESET: {
target: "idle",
actions: "resetTime"
}
}
}
}
}, {
actions: {
updateContext: assign((context, event) => {
return {
[event.contextKey]: true
};
})
},
guards: {
"hasReachedTarget": ctx => ctx["hasReachedTarget"]
}
});
2 changes: 1 addition & 1 deletion e2e/time-picker.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TimePickerModel } from "./models/time-picker.model"

let I: TimePickerModel

test.describe("timepicker", () => {
test.describe.skip("timepicker", () => {
test.beforeEach(async ({ page }) => {
I = new TimePickerModel(page)
await I.goto()
Expand Down
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
48 changes: 48 additions & 0 deletions examples/next-ts/pages/timer-countdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { normalizeProps, useMachine } from "@zag-js/react"
import * as timer from "@zag-js/timer"
import { useId } from "react"
import { StateVisualizer } from "../components/state-visualizer"
import { Toolbar } from "../components/toolbar"

export default function Page() {
const [state, send] = useMachine(
timer.machine({
id: useId(),
countdown: true,
autoStart: true,
startMs: timer.parse({ day: 2, second: 10 }),
onComplete() {
console.log("Timer completed")
},
}),
)

const api = timer.connect(state, send, normalizeProps)

return (
<>
<main className="timer">
<div {...api.rootProps}>
<div {...api.getSegmentProps({ type: "day" })}>{api.segments.day}</div>
<div {...api.separatorProps}>:</div>
<div {...api.getSegmentProps({ type: "hour" })}>{api.segments.hour}</div>
<div {...api.separatorProps}>:</div>
<div {...api.getSegmentProps({ type: "minute" })}>{api.segments.minute}</div>
<div {...api.separatorProps}>:</div>
<div {...api.getSegmentProps({ type: "second" })}>{api.segments.second}</div>
</div>

<div style={{ display: "flex", gap: "4px" }}>
<button onClick={api.start}>START</button>
<button onClick={api.pause}>PAUSE</button>
<button onClick={api.resume}>RESUME</button>
<button onClick={api.reset}>RESET</button>
</div>
</main>

<Toolbar controls={null} viz>
<StateVisualizer state={state} />
</Toolbar>
</>
)
}
45 changes: 45 additions & 0 deletions examples/next-ts/pages/timer-stopwatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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"

export default function Page() {
const [state, send] = useMachine(
timer.machine({
id: useId(),
autoStart: true,
// startMs: timer.parse({ day: 2, second: 10 }),
// targetMs: timer.parse({ day: 2, second: 20 }),
}),
)

const api = timer.connect(state, send, normalizeProps)

return (
<>
<main className="timer">
<div {...api.rootProps}>
<div {...api.getSegmentProps({ type: "day" })}>{api.segments.day}</div>
<div {...api.separatorProps}>:</div>
<div {...api.getSegmentProps({ type: "hour" })}>{api.segments.hour}</div>
<div {...api.separatorProps}>:</div>
<div {...api.getSegmentProps({ type: "minute" })}>{api.segments.minute}</div>
<div {...api.separatorProps}>:</div>
<div {...api.getSegmentProps({ type: "second" })}>{api.segments.second}</div>
</div>

<div style={{ display: "flex", gap: "4px" }}>
<button onClick={api.start}>START</button>
<button onClick={api.pause}>PAUSE</button>
<button onClick={api.resume}>RESUME</button>
<button onClick={api.reset}>RESET</button>
</div>
</main>

<Toolbar controls={null} viz>
<StateVisualizer state={state} />
</Toolbar>
</>
)
}
49 changes: 0 additions & 49 deletions examples/solid-ts/src/pages/timer.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions examples/solid-ts/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ export const routes: RouteDefinition[] = [
{ path: "/number-input", component: lazy(() => import("./pages/number-input")) },
{ path: "/pagination", component: lazy(() => import("./pages/pagination")) },
{ path: "/pin-input", component: lazy(() => import("./pages/pin-input")) },
// { path: "/popper", component: lazy(()=>import("./pages/popper")) },
{ path: "/popover", component: lazy(() => import("./pages/popover")) },
// { path: "/nested-popover", component: lazy(()=>import("./pages/nested-popover")) },
{ path: "/radio-group", component: lazy(() => import("./pages/radio-group")) },
{ path: "/range-slider", component: lazy(() => import("./pages/range-slider")) },
{ path: "/rating-group", component: lazy(() => import("./pages/rating-group")) },
Expand Down
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"
}
14 changes: 14 additions & 0 deletions packages/machines/timer/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export { anatomy } from "./timer.anatomy"
export { connect } from "./timer.connect"
export { machine } from "./timer.machine"
export * from "./timer.parse"
export * from "./timer.props"
export type {
MachineApi as Api,
UserDefinedContext as Context,
MachineState,
SegmentProps,
SegmentType,
TickDetails,
TimeSegments,
} from "./timer.types"
4 changes: 4 additions & 0 deletions packages/machines/timer/src/timer.anatomy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createAnatomy } from "@zag-js/anatomy"

export const anatomy = createAnatomy("timer").parts("root", "segment", "separator")
export const parts = anatomy.build()
43 changes: 43 additions & 0 deletions packages/machines/timer/src/timer.connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { NormalizeProps, PropTypes } from "@zag-js/types"
import type { MachineApi, State, Send } from "./timer.types"
import { parts } from "./timer.anatomy"

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")

return {
running,
paused,
segments: state.context.segments,
progressPercent: state.context.progressPercent,
start() {
send("START")
},
pause() {
send("PAUSE")
},
resume() {
send("RESUME")
},
reset() {
send("RESET")
},
restart() {
send("RESTART")
},
rootProps: normalize.element({
...parts.root.attrs,
}),
getSegmentProps(props) {
return normalize.element({
...parts.segment.attrs,
"data-type": props.type,
})
},
separatorProps: normalize.element({
role: "separator",
...parts.separator.attrs,
}),
}
}
Loading

0 comments on commit d951e04

Please sign in to comment.