From a26ed6ce7fe359edf09daf61f4ce7eb6b7dded48 Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Mon, 1 Apr 2024 17:45:41 +0100 Subject: [PATCH 1/7] refactor: menu prefer explicit state for options --- .changeset/strong-dragons-smile.md | 9 ++ .xstate/menu.js | 21 ++-- examples/next-ts/pages/context-menu.tsx | 8 +- examples/next-ts/pages/menu-options.tsx | 55 ++++++--- examples/next-ts/pages/menu.tsx | 8 +- examples/next-ts/pages/nested-menu.tsx | 10 +- examples/nuxt-ts/pages/context-menu.vue | 8 +- examples/nuxt-ts/pages/menu-options.vue | 55 +++++---- examples/nuxt-ts/pages/menu.vue | 8 +- examples/solid-ts/src/pages/context-menu.tsx | 8 +- examples/solid-ts/src/pages/menu-options.tsx | 77 +++++++----- examples/solid-ts/src/pages/menu.tsx | 8 +- examples/solid-ts/src/pages/nested-menu.tsx | 10 +- .../svelte-ts/src/routes/context-menu.svelte | 8 +- examples/vue-ts/src/pages/context-menu.tsx | 8 +- examples/vue-ts/src/pages/menu-options.tsx | 67 ++++++---- examples/vue-ts/src/pages/menu.tsx | 8 +- examples/vue-ts/src/pages/nested-menu.tsx | 17 +-- packages/machines/menu/src/menu.connect.ts | 29 ++--- packages/machines/menu/src/menu.dom.ts | 8 +- packages/machines/menu/src/menu.machine.ts | 115 +++++++----------- packages/machines/menu/src/menu.types.ts | 45 +++---- shared/src/data.ts | 42 +++---- starters/react/components/menu.tsx | 2 +- website/components/machines/context-menu.tsx | 2 +- website/components/machines/menu.tsx | 3 +- website/components/machines/nested-menu.tsx | 4 +- website/data/components/menu.mdx | 19 +-- .../data/snippets/react/menu/context-menu.mdx | 16 +-- .../data/snippets/react/menu/nested-menu.mdx | 14 +-- .../data/snippets/react/menu/option-items.mdx | 62 ++++++---- website/data/snippets/react/menu/usage.mdx | 13 +- .../data/snippets/solid/menu/context-menu.mdx | 15 +-- .../data/snippets/solid/menu/nested-menu.mdx | 24 ++-- .../data/snippets/solid/menu/option-items.mdx | 83 ++++++++----- website/data/snippets/solid/menu/usage.mdx | 12 +- .../snippets/vue-jsx/menu/context-menu.mdx | 15 +-- .../snippets/vue-jsx/menu/nested-menu.mdx | 22 ++-- .../snippets/vue-jsx/menu/option-items.mdx | 79 ++++++------ website/data/snippets/vue-jsx/menu/usage.mdx | 12 +- .../snippets/vue-sfc/menu/context-menu.mdx | 8 +- .../snippets/vue-sfc/menu/nested-menu.mdx | 14 +-- .../snippets/vue-sfc/menu/option-items.mdx | 75 +++++++----- website/data/snippets/vue-sfc/menu/usage.mdx | 10 +- website/data/snippets/website/snippet.mdx | 7 +- 45 files changed, 590 insertions(+), 553 deletions(-) create mode 100644 .changeset/strong-dragons-smile.md diff --git a/.changeset/strong-dragons-smile.md b/.changeset/strong-dragons-smile.md new file mode 100644 index 0000000000..cdb850cd5a --- /dev/null +++ b/.changeset/strong-dragons-smile.md @@ -0,0 +1,9 @@ +--- +"@zag-js/menu": minor +--- + +> Breaking changes to the menu component + +- Removed `value` and `onValueChange` in favor of using explicit state to manage option items. +- Prefer `value` over `id` in `getItemProps` and `getOptionItemProps` for consistency with other machine. +- `onSelect` now provides `value` not `id` in its details. diff --git a/.xstate/menu.js b/.xstate/menu.js index 006e177e84..66a8200246 100644 --- a/.xstate/menu.js +++ b/.xstate/menu.js @@ -79,11 +79,8 @@ const fetchMachine = createMachine({ target: "closed", actions: "invokeOnClose" }], - RESTORE_FOCUS: { - actions: "restoreFocus" - }, - "VALUE.SET": { - actions: ["setOptionValue", "invokeOnValueChange"] + "HIGHLIGHTED.RESTORE": { + actions: "restoreHighlightedItem" }, "HIGHLIGHTED.SET": { actions: "setHighlightedItem" @@ -190,14 +187,14 @@ const fetchMachine = createMachine({ actions: ["invokeOnClose"] }, { target: "closed", - actions: ["focusParentMenu", "restoreParentFocus", "invokeOnClose"] + actions: ["focusParentMenu", "restoreParentHiglightedItem", "invokeOnClose"] }] }, on: { "CONTROLLED.OPEN": "open", "CONTROLLED.CLOSE": { target: "closed", - actions: ["focusParentMenu", "restoreParentFocus"] + actions: ["focusParentMenu", "restoreParentHiglightedItem"] }, // don't invoke on open here since the menu is still open (we're only keeping it open) MENU_POINTERENTER: { @@ -209,7 +206,7 @@ const fetchMachine = createMachine({ actions: "invokeOnClose" }, { target: "closed", - actions: ["focusParentMenu", "restoreParentFocus"] + actions: ["focusParentMenu", "restoreParentHiglightedItem"] }] } }, @@ -338,7 +335,7 @@ const fetchMachine = createMachine({ cond: "!suspendPointer", actions: ["highlightItem", "focusMenu"] }, { - actions: "setHoveredItem" + actions: "setLastHighlightedItem" }], ITEM_POINTERLEAVE: { cond: "!suspendPointer && !isTriggerItem", @@ -348,16 +345,16 @@ const fetchMachine = createMachine({ // == grouped == { cond: "!isTriggerItemHighlighted && !isHighlightedItemEditable && closeOnSelect && isOpenControlled", - actions: ["invokeOnSelect", "changeOptionValue", "invokeOnValueChange", "closeRootMenu", "invokeOnClose"] + actions: ["invokeOnSelect", "setOptionState", "closeRootMenu", "invokeOnClose"] }, { cond: "!isTriggerItemHighlighted && !isHighlightedItemEditable && closeOnSelect", target: "closed", - actions: ["invokeOnSelect", "changeOptionValue", "invokeOnValueChange", "closeRootMenu", "invokeOnClose"] + actions: ["invokeOnSelect", "setOptionState", "closeRootMenu", "invokeOnClose"] }, // { cond: "!isTriggerItemHighlighted && !isHighlightedItemEditable", - actions: ["invokeOnSelect", "changeOptionValue", "invokeOnValueChange"] + actions: ["invokeOnSelect", "setOptionState"] }, { actions: "highlightItem" }], diff --git a/examples/next-ts/pages/context-menu.tsx b/examples/next-ts/pages/context-menu.tsx index e367f3e94c..f40c7a4f52 100644 --- a/examples/next-ts/pages/context-menu.tsx +++ b/examples/next-ts/pages/context-menu.tsx @@ -21,10 +21,10 @@ export default function Page() {
    -
  • Edit
  • -
  • Duplicate
  • -
  • Delete
  • -
  • Export...
  • +
  • Edit
  • +
  • Duplicate
  • +
  • Delete
  • +
  • Export...
diff --git a/examples/next-ts/pages/menu-options.tsx b/examples/next-ts/pages/menu-options.tsx index 3556fb79c4..051d2c21dc 100644 --- a/examples/next-ts/pages/menu-options.tsx +++ b/examples/next-ts/pages/menu-options.tsx @@ -1,7 +1,7 @@ import * as menu from "@zag-js/menu" import { normalizeProps, Portal, useMachine } from "@zag-js/react" -import { menuOptionData as data, menuControls } from "@zag-js/shared" -import { useId } from "react" +import { menuOptionData, menuControls } from "@zag-js/shared" +import { useId, useState } from "react" import { StateVisualizer } from "../components/state-visualizer" import { Toolbar } from "../components/toolbar" import { useControls } from "../hooks/use-controls" @@ -9,17 +9,34 @@ import { useControls } from "../hooks/use-controls" export default function Page() { const controls = useControls(menuControls) - const [state, send] = useMachine( - menu.machine({ - id: useId(), - value: { order: "", type: [] }, - onValueChange: console.log, - }), - { context: controls.context }, - ) + const [order, setOrder] = useState("") + const [type, setType] = useState([]) + + const [state, send] = useMachine(menu.machine({ id: useId() }), { + context: controls.context, + }) const api = menu.connect(state, send, normalizeProps) + const radios = menuOptionData.order.map((item) => ({ + type: "radio" as const, + name: "order", + value: item.value, + label: item.label, + checked: order === item.value, + onCheckedChange: (checked: boolean) => setOrder(checked ? item.value : ""), + })) + + const checkboxes = menuOptionData.type.map((item) => ({ + type: "checkbox" as const, + name: "type", + value: item.value, + label: item.label, + checked: type.includes(item.value), + onCheckedChange: (checked: boolean) => + setType((prev) => (checked ? [...prev, item.value] : prev.filter((x) => x !== item.value))), + })) + return ( <>
@@ -31,22 +48,20 @@ export default function Page() {
- {data.order.map((item) => { - const opts = { type: "radio", name: "order", value: item.id } as const + {radios.map((item) => { return ( -
- - {item.label} +
+ + {item.label}
) })}
- {data.type.map((item) => { - const opts = { type: "checkbox", name: "type", value: item.id } as const + {checkboxes.map((item) => { return ( -
- - {item.label} +
+ + {item.label}
) })} diff --git a/examples/next-ts/pages/menu.tsx b/examples/next-ts/pages/menu.tsx index b456972b55..7c076ade9d 100644 --- a/examples/next-ts/pages/menu.tsx +++ b/examples/next-ts/pages/menu.tsx @@ -24,10 +24,10 @@ export default function Page() {
    -
  • Edit
  • -
  • Duplicate
  • -
  • Delete
  • -
  • Export...
  • +
  • Edit
  • +
  • Duplicate
  • +
  • Delete
  • +
  • Export...
diff --git a/examples/next-ts/pages/nested-menu.tsx b/examples/next-ts/pages/nested-menu.tsx index 0220433590..5f759f1499 100644 --- a/examples/next-ts/pages/nested-menu.tsx +++ b/examples/next-ts/pages/nested-menu.tsx @@ -47,9 +47,9 @@ export default function Page() {
    {level1.map((item) => { - const props = item.trigger ? triggerItemProps : root.getItemProps({ id: item.id }) + const props = item.trigger ? triggerItemProps : root.getItemProps({ value: item.value }) return ( -
  • +
  • {item.label}
  • ) @@ -62,9 +62,9 @@ export default function Page() {
      {level2.map((item) => { - const props = item.trigger ? triggerItem2Props : sub.getItemProps({ id: item.id }) + const props = item.trigger ? triggerItem2Props : sub.getItemProps({ value: item.value }) return ( -
    • +
    • {item.label}
    • ) @@ -77,7 +77,7 @@ export default function Page() {
        {level3.map((item) => ( -
      • +
      • {item.label}
      • ))} diff --git a/examples/nuxt-ts/pages/context-menu.vue b/examples/nuxt-ts/pages/context-menu.vue index 798089b9cc..d4cf833a53 100644 --- a/examples/nuxt-ts/pages/context-menu.vue +++ b/examples/nuxt-ts/pages/context-menu.vue @@ -18,10 +18,10 @@ const api = computed(() => menu.connect(state.value, send, normalizeProps))
          -
        • Edit
        • -
        • Duplicate
        • -
        • Delete
        • -
        • Export...
        • +
        • Edit
        • +
        • Duplicate
        • +
        • Delete
        • +
        • Export...
        diff --git a/examples/nuxt-ts/pages/menu-options.vue b/examples/nuxt-ts/pages/menu-options.vue index e4ed6c1ebc..fef96cbbd2 100644 --- a/examples/nuxt-ts/pages/menu-options.vue +++ b/examples/nuxt-ts/pages/menu-options.vue @@ -1,32 +1,41 @@