Skip to content

Commit

Permalink
feat: floating panel machine (chakra-ui#1363)
Browse files Browse the repository at this point in the history
* chore: initial scafold

* chore: update

* chore: improve set up

* chore: update styles

* chore: rm portal for now

* chore: update transitions

* feat: implement dragging

* chore: get drag and resize to work

* feat: support min and max size

* chore: preserve

* chore: update preserve

* feat: complete features

* refactor: stage types

* feat: track boundary element

* feat: complete v1

* style: fix

* feat: update examples

* feat: keyboard support

* docs: add changeset
  • Loading branch information
segunadebayo authored and colinlienard committed Jun 18, 2024
1 parent c690354 commit 52a261e
Show file tree
Hide file tree
Showing 84 changed files with 2,205 additions and 510 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-buses-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zag-js/floating-panel": minor
---

Introduce new floating panel machine for draggable and resizable panels
128 changes: 128 additions & 0 deletions .xstate/floating-panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"use strict";

var _xstate = require("xstate");
const {
actions,
createMachine,
assign
} = _xstate;
const {
choose
} = actions;
const fetchMachine = createMachine({
id: "floating-panel",
initial: ctx.open ? "open" : "closed",
context: {
"!isMaximized": false,
"!isMinimized": false,
"closeOnEsc": false
},
on: {
UPDATE_CONTEXT: {
actions: "updateContext"
}
},
states: {
closed: {
tags: ["closed"],
on: {
OPEN: {
target: "open",
actions: ["invokeOnOpen", "setPositionStyle", "setSizeStyle"]
}
}
},
open: {
tags: ["open"],
activities: ["trackBoundaryRect"],
on: {
DRAG_START: {
cond: "!isMaximized",
target: "open.dragging",
actions: ["setPrevPosition"]
},
RESIZE_START: {
cond: "!isMinimized",
target: "open.resizing",
actions: ["setPrevSize"]
},
CLOSE: {
target: "closed",
actions: ["invokeOnClose", "resetRect"]
},
ESCAPE: {
cond: "closeOnEsc",
target: "closed",
actions: ["invokeOnClose", "resetRect"]
},
MINIMIZE: {
actions: ["setMinimized", "invokeOnMinimize"]
},
MAXIMIZE: {
actions: ["setMaximized", "invokeOnMaximize"]
},
RESTORE: {
actions: ["setRestored"]
},
MOVE: {
actions: ["setPositionFromKeybord"]
}
}
},
"open.dragging": {
tags: ["open"],
activities: ["trackPointerMove", "trackBoundaryRect"],
exit: ["clearPrevPosition"],
on: {
DRAG: {
actions: ["setPosition"]
},
DRAG_END: {
target: "open",
actions: ["invokeOnDragEnd"]
},
CLOSE: {
target: "closed",
actions: ["invokeOnClose", "resetRect"]
},
ESCAPE: {
target: "open"
}
}
},
"open.resizing": {
tags: ["open"],
activities: ["trackPointerMove", "trackBoundaryRect"],
exit: ["clearPrevSize"],
on: {
DRAG: {
actions: ["setSize"]
},
DRAG_END: {
target: "open",
actions: ["invokeOnResizeEnd"]
},
CLOSE: {
target: "closed",
actions: ["invokeOnClose", "resetRect"]
},
ESCAPE: {
target: "open"
}
}
}
}
}, {
actions: {
updateContext: assign((context, event) => {
return {
[event.contextKey]: true
};
})
},
guards: {
"!isMaximized": ctx => ctx["!isMaximized"],
"!isMinimized": ctx => ctx["!isMinimized"],
"closeOnEsc": ctx => ctx["closeOnEsc"]
}
});
3 changes: 2 additions & 1 deletion examples/next-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@zag-js/element-size": "workspace:*",
"@zag-js/file-upload": "workspace:*",
"@zag-js/file-utils": "workspace:*",
"@zag-js/floating-panel": "workspace:*",
"@zag-js/focus-scope": "workspace:*",
"@zag-js/focus-visible": "workspace:*",
"@zag-js/form-utils": "workspace:*",
Expand Down Expand Up @@ -96,4 +97,4 @@
"typescript": "5.4.2"
},
"license": "MIT"
}
}
67 changes: 67 additions & 0 deletions examples/next-ts/pages/floating-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as floatingPanel from "@zag-js/floating-panel"
import { normalizeProps, useMachine } from "@zag-js/react"
import { floatingPanelControls } from "@zag-js/shared"
import { ArrowDownLeft, Maximize2, Minus, XIcon } from "lucide-react"
import { useId } from "react"
import { StateVisualizer } from "../components/state-visualizer"
import { Toolbar } from "../components/toolbar"
import { useControls } from "../hooks/use-controls"

export default function Page() {
const controls = useControls(floatingPanelControls)

const [state, send] = useMachine(floatingPanel.machine({ id: useId() }), {
context: controls.context,
})

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

return (
<>
<main className="floating-panel">
<div>
<button {...api.triggerProps}>Toggle Panel</button>
<div {...api.positionerProps}>
<div {...api.contentProps}>
<div {...api.dragTriggerProps}>
<div {...api.headerProps}>
<p {...api.titleProps}>Floating Panel</p>
<div data-scope="floating-panel" data-part="trigger-group">
<button {...api.minimizeTriggerProps}>
<Minus />
</button>
<button {...api.maximizeTriggerProps}>
<Maximize2 />
</button>
<button {...api.restoreTriggerProps}>
<ArrowDownLeft />
</button>
<button {...api.closeTriggerProps}>
<XIcon />
</button>
</div>
</div>
</div>
<div {...api.bodyProps}>
<p>Some content</p>
</div>

<div {...api.getResizeTriggerProps({ axis: "n" })} />
<div {...api.getResizeTriggerProps({ axis: "e" })} />
<div {...api.getResizeTriggerProps({ axis: "w" })} />
<div {...api.getResizeTriggerProps({ axis: "s" })} />
<div {...api.getResizeTriggerProps({ axis: "ne" })} />
<div {...api.getResizeTriggerProps({ axis: "se" })} />
<div {...api.getResizeTriggerProps({ axis: "sw" })} />
<div {...api.getResizeTriggerProps({ axis: "nw" })} />
</div>
</div>
</div>
</main>

<Toolbar controls={controls.ui}>
<StateVisualizer state={state} />
</Toolbar>
</>
)
}
1 change: 1 addition & 0 deletions examples/nuxt-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@zag-js/element-size": "workspace:*",
"@zag-js/file-upload": "workspace:*",
"@zag-js/file-utils": "workspace:*",
"@zag-js/floating-panel": "workspace:*",
"@zag-js/focus-scope": "workspace:*",
"@zag-js/focus-visible": "workspace:*",
"@zag-js/form-utils": "workspace:*",
Expand Down
63 changes: 63 additions & 0 deletions examples/nuxt-ts/pages/floating-panel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup lang="ts">
import * as floatingPanel from "@zag-js/floating-panel"
import { floatingPanelControls } from "@zag-js/shared"
import { normalizeProps, useMachine } from "@zag-js/vue"
const controls = useControls(floatingPanelControls)
const [state, send] = useMachine(floatingPanel.machine({ id: "1" }), {
context: controls.context,
})
const api = computed(() => floatingPanel.connect(state.value, send, normalizeProps))
</script>

<template>
<main class="floating-panel">
<div>
<button v-bind="api.triggerProps">Toggle Panel</button>
<div v-bind="api.positionerProps">
<div v-bind="api.contentProps">
<div v-bind="api.dragTriggerProps">
<div v-bind="api.headerProps">
<p v-bind="api.titleProps">Floating Panel</p>
<div data-scope="floating-panel" data-part="trigger-group">
<button v-bind="api.minimizeTriggerProps">
<Minus />
</button>
<button v-bind="api.maximizeTriggerProps">
<Maximize2 />
</button>
<button v-bind="api.restoreTriggerProps">
<ArrowDownLeft />
</button>
<button v-bind="api.closeTriggerProps">
<XIcon />
</button>
</div>
</div>
</div>
<div v-bind="api.bodyProps">
<p>Some content</p>
</div>

<div v-bind="api.getResizeTriggerProps({ axis: 'n' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 'e' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 'w' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 's' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 'ne' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 'se' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 'sw' })" />
<div v-bind="api.getResizeTriggerProps({ axis: 'nw' })" />
</div>
</div>
</div>
</main>

<Toolbar>
<StateVisualizer :state="state" />
<template #controls>
<Controls :control="controls" />
</template>
</Toolbar>
</template>
3 changes: 2 additions & 1 deletion examples/preact-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@zag-js/element-size": "workspace:*",
"@zag-js/file-upload": "workspace:*",
"@zag-js/file-utils": "workspace:*",
"@zag-js/floating-panel": "workspace:*",
"@zag-js/focus-scope": "workspace:*",
"@zag-js/focus-visible": "workspace:*",
"@zag-js/form-utils": "workspace:*",
Expand Down Expand Up @@ -89,4 +90,4 @@
"typescript": "5.4.2",
"vite": "5.1.6"
}
}
}
3 changes: 2 additions & 1 deletion examples/solid-ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@zag-js/element-size": "workspace:*",
"@zag-js/file-upload": "workspace:*",
"@zag-js/file-utils": "workspace:*",
"@zag-js/floating-panel": "workspace:*",
"@zag-js/focus-scope": "workspace:*",
"@zag-js/focus-visible": "workspace:*",
"@zag-js/form-utils": "workspace:*",
Expand Down Expand Up @@ -91,4 +92,4 @@
"lucide-solid": "0.359.0",
"solid-js": "1.8.15"
}
}
}
67 changes: 67 additions & 0 deletions examples/solid-ts/src/pages/floating-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as floatingPanel from "@zag-js/floating-panel"
import { floatingPanelControls } from "@zag-js/shared"
import { normalizeProps, useMachine } from "@zag-js/solid"
import { ArrowDownLeft, Maximize2, Minus, XIcon } from "lucide-solid"
import { createMemo, createUniqueId } from "solid-js"
import { StateVisualizer } from "../components/state-visualizer"
import { Toolbar } from "../components/toolbar"
import { useControls } from "../hooks/use-controls"

export default function Page() {
const controls = useControls(floatingPanelControls)

const [state, send] = useMachine(floatingPanel.machine({ id: createUniqueId() }), {
context: controls.context,
})

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

return (
<>
<main class="floating-panel">
<div>
<button {...api().triggerProps}>Toggle Panel</button>
<div {...api().positionerProps}>
<div {...api().contentProps}>
<div {...api().dragTriggerProps}>
<div {...api().headerProps}>
<p {...api().titleProps}>Floating Panel</p>
<div data-scope="floating-panel" data-part="trigger-group">
<button {...api().minimizeTriggerProps}>
<Minus />
</button>
<button {...api().maximizeTriggerProps}>
<Maximize2 />
</button>
<button {...api().restoreTriggerProps}>
<ArrowDownLeft />
</button>
<button {...api().closeTriggerProps}>
<XIcon />
</button>
</div>
</div>
</div>
<div {...api().bodyProps}>
<p>Some content</p>
</div>

<div {...api().getResizeTriggerProps({ axis: "n" })} />
<div {...api().getResizeTriggerProps({ axis: "e" })} />
<div {...api().getResizeTriggerProps({ axis: "w" })} />
<div {...api().getResizeTriggerProps({ axis: "s" })} />
<div {...api().getResizeTriggerProps({ axis: "ne" })} />
<div {...api().getResizeTriggerProps({ axis: "se" })} />
<div {...api().getResizeTriggerProps({ axis: "sw" })} />
<div {...api().getResizeTriggerProps({ axis: "nw" })} />
</div>
</div>
</div>
</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: "/floating-panel", component: lazy(() => import("./pages/floating-panel")) },
{ path: "/tour", component: lazy(() => import("./pages/tour")) },
{ path: "/collapsible", component: lazy(() => import("./pages/collapsible")) },
{ path: "/clipboard", component: lazy(() => import("./pages/clipboard")) },
Expand Down
Loading

0 comments on commit 52a261e

Please sign in to comment.