Skip to content

Commit

Permalink
feat: add signature pad machine (chakra-ui#1399)
Browse files Browse the repository at this point in the history
* chore: initial design

* chore: wip

* chore: add more examples

* chore: add svelte example

* chore: update

* chore: rename parts

* docs: add changeset
  • Loading branch information
segunadebayo authored and colinlienard committed Jun 18, 2024
1 parent 009eda5 commit 9816eec
Show file tree
Hide file tree
Showing 36 changed files with 1,495 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-readers-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zag-js/signature-pad": minor
---

[NEW] Signature Pad machine to allow capturing user signatures
57 changes: 57 additions & 0 deletions .xstate/signature-pad.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use strict";

var _xstate = require("xstate");
const {
actions,
createMachine,
assign
} = _xstate;
const {
choose
} = actions;
const fetchMachine = createMachine({
id: "signature-pad",
initial: "idle",
context: {},
on: {
CLEAR: {
actions: ["clearPoints", "invokeOnDrawEnd", "focusCanvasEl"]
}
},
on: {
UPDATE_CONTEXT: {
actions: "updateContext"
}
},
states: {
idle: {
on: {
POINTER_DOWN: {
target: "drawing",
actions: ["addPoint"]
}
}
},
drawing: {
activities: ["trackPointerMove"],
on: {
POINTER_MOVE: {
actions: ["addPoint", "invokeOnDraw"]
},
POINTER_UP: {
target: "idle",
actions: ["endStroke", "invokeOnDrawEnd"]
}
}
}
}
}, {
actions: {
updateContext: assign((context, event) => {
return {
[event.contextKey]: true
};
})
},
guards: {}
});
1 change: 1 addition & 0 deletions examples/next-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@zag-js/remove-scroll": "workspace:*",
"@zag-js/select": "workspace:*",
"@zag-js/shared": "workspace:*",
"@zag-js/signature-pad": "workspace:*",
"@zag-js/slider": "workspace:*",
"@zag-js/splitter": "workspace:*",
"@zag-js/store": "workspace:*",
Expand Down
69 changes: 69 additions & 0 deletions examples/next-ts/pages/signature-pad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { normalizeProps, useMachine } from "@zag-js/react"
import { signaturePadControls } from "@zag-js/shared"
import * as signaturePad from "@zag-js/signature-pad"
import { RotateCcw } from "lucide-react"
import { useId, useState } from "react"
import { StateVisualizer } from "../components/state-visualizer"
import { Toolbar } from "../components/toolbar"
import { useControls } from "../hooks/use-controls"

export default function Page() {
const [url, setUrl] = useState("")

const controls = useControls(signaturePadControls)

const [state, send] = useMachine(
signaturePad.machine({
id: useId(),
onDrawEnd(details) {
details.getDataUrl("image/png").then(setUrl)
},
drawing: {
fill: "red",
size: 4,
simulatePressure: true,
},
}),
{ context: controls.context },
)

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

return (
<>
<main className="signature-pad">
<div {...api.rootProps}>
<label {...api.labelProps}>Signature Pad</label>

<div {...api.controlProps}>
<svg {...api.segmentProps}>
{api.paths.map((path, i) => (
<path key={i} {...api.getSegmentPathProps({ path })} />
))}
{api.currentPath && <path {...api.getSegmentPathProps({ path: api.currentPath })} />}
</svg>

<div {...api.separatorProps} />
</div>

<button {...api.clearTriggerProps}>
<RotateCcw />
</button>
</div>

<button
onClick={() => {
api.getDataUrl("image/png").then(setUrl)
}}
>
Show Image
</button>
{url && <img data-part="preview" alt="signature" src={url} />}
</main>

<Toolbar controls={controls.ui}>
<StateVisualizer state={state} omit={["points"]} />
</Toolbar>
</>
)
}
1 change: 1 addition & 0 deletions examples/nuxt-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@zag-js/remove-scroll": "workspace:*",
"@zag-js/select": "workspace:*",
"@zag-js/shared": "workspace:*",
"@zag-js/signature-pad": "workspace:*",
"@zag-js/slider": "workspace:*",
"@zag-js/splitter": "workspace:*",
"@zag-js/store": "workspace:*",
Expand Down
54 changes: 54 additions & 0 deletions examples/nuxt-ts/pages/signature-pad.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script setup lang="ts">
import * as signaturePad from "@zag-js/signature-pad"
import { signaturePadControls } from "@zag-js/shared"
import { normalizeProps, useMachine } from "@zag-js/vue"
const url = ref("")
const setUrl = (v: string) => (url.value = v)
const controls = useControls(signaturePadControls)
const [state, send] = useMachine(signaturePad.machine({ id: "1" }), {
context: controls.context,
})
const api = computed(() => signaturePad.connect(state.value, send, normalizeProps))
</script>

<template>
<main className="signature-pad">
<div v-bind="api.rootProps">
<label v-bind="api.labelProps">Signature Pad</label>

<div v-bind="api.controlProps">
<svg v-bind="api.segmentProps">
<path v-for="path of api.paths" key="{i}" v-bind="api.getSegmentPathProps({ path })" />
<path v-if="api.currentPath" v-bind="api.getSegmentPathProps({ path: api.currentPath })" />
</svg>
<div v-bind="api.separatorProps" />
</div>

<button v-bind="api.clearTriggerProps">
<RotateCcw />
</button>
</div>

<button
@click="
() => {
api.getDataUrl('image/png').then(setUrl)
}
"
>
Show Image
</button>
<img v-if="url" data-part="preview" alt="signature" :src="url" />
</main>

<Toolbar>
<StateVisualizer :state="state" />
<template #controls>
<Controls :control="controls" />
</template>
</Toolbar>
</template>
1 change: 1 addition & 0 deletions examples/preact-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"@zag-js/remove-scroll": "workspace:*",
"@zag-js/select": "workspace:*",
"@zag-js/shared": "workspace:*",
"@zag-js/signature-pad": "workspace:*",
"@zag-js/slider": "workspace:*",
"@zag-js/splitter": "workspace:*",
"@zag-js/store": "workspace:*",
Expand Down
1 change: 1 addition & 0 deletions examples/solid-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@zag-js/remove-scroll": "workspace:*",
"@zag-js/select": "workspace:*",
"@zag-js/shared": "workspace:*",
"@zag-js/signature-pad": "workspace:*",
"@zag-js/slider": "workspace:*",
"@zag-js/solid": "workspace:*",
"@zag-js/splitter": "workspace:*",
Expand Down
68 changes: 68 additions & 0 deletions examples/solid-ts/src/pages/signature-pad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { signaturePadControls } from "@zag-js/shared"
import * as signaturePad from "@zag-js/signature-pad"
import { normalizeProps, useMachine } from "@zag-js/solid"
import { For, Show, createMemo, createSignal, createUniqueId } from "solid-js"
import { StateVisualizer } from "../components/state-visualizer"
import { Toolbar } from "../components/toolbar"
import { useControls } from "../hooks/use-controls"
import { RotateCcw } from "lucide-solid"

export default function Page() {
const [url, setUrl] = createSignal("")

const controls = useControls(signaturePadControls)

const [state, send] = useMachine(
signaturePad.machine({
id: createUniqueId(),
onDrawEnd(details) {
details.getDataUrl("image/png").then(setUrl)
},
}),
{
context: controls.context,
},
)

const api = createMemo(() => signaturePad.connect(state, send, normalizeProps))

return (
<>
<main class="signature-pad">
<div {...api().rootProps}>
<label {...api().labelProps}>Signature Pad</label>

<div {...api().controlProps}>
<svg {...api().segmentProps}>
<For each={api().paths}>{(path) => <path {...api().getSegmentPathProps({ path })} />}</For>
<Show when={api().currentPath}>
{(path) => <path {...api().getSegmentPathProps({ path: path() })} />}
</Show>
</svg>
<div {...api().separatorProps} />
</div>

<button {...api().clearTriggerProps}>
<RotateCcw />
</button>
</div>

<button
onClick={() => {
api().getDataUrl("image/png").then(setUrl)
}}
>
Show Image
</button>

<Show when={url()}>
<img data-part="preview" alt="signature" src={url()} />
</Show>
</main>

<Toolbar controls={controls.ui}>
<StateVisualizer state={state} />
</Toolbar>
</>
)
}
1 change: 1 addition & 0 deletions examples/solid-ts/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { lazy } from "solid-js"
import Home from "./pages/home"

export const routes: RouteDefinition[] = [
{ path: "/signature-pad", component: lazy(() => import("./pages/signature-pad")) },
{ path: "/floating-panel", component: lazy(() => import("./pages/floating-panel")) },
{ path: "/tour", component: lazy(() => import("./pages/tour")) },
{ path: "/collapsible", component: lazy(() => import("./pages/collapsible")) },
Expand Down
3 changes: 2 additions & 1 deletion examples/svelte-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@zag-js/remove-scroll": "workspace:*",
"@zag-js/select": "workspace:*",
"@zag-js/shared": "workspace:*",
"@zag-js/signature-pad": "workspace:*",
"@zag-js/slider": "workspace:*",
"@zag-js/splitter": "workspace:*",
"@zag-js/store": "workspace:*",
Expand Down Expand Up @@ -95,4 +96,4 @@
"vite": "5.2.7",
"vite-tsconfig-paths": "4.3.2"
}
}
}
2 changes: 2 additions & 0 deletions examples/svelte-ts/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import PinInput from "./routes/pin-input.svelte"
import Progress from "./routes/progress.svelte"
import Select from "./routes/select.svelte"
import SignaturePad from "./routes/signature-pad.svelte"
import Slider from "./routes/slider.svelte"
import Tabs from "./routes/tabs.svelte"
import TagsInput from "./routes/tags-input.svelte"
Expand Down Expand Up @@ -44,6 +45,7 @@
{ path: "/progress", component: Progress },
{ path: "/tabs", component: Tabs },
{ path: "/number-input", component: NumberInput },
{ path: "/signature-pad", component: SignaturePad },
]
</script>

Expand Down
Loading

0 comments on commit 9816eec

Please sign in to comment.