Hierarchical Combobox (Flat)
import { effect, eventApi, guard, setup, storeApi as createStoreApi } from "matchina";import { createHSM } from "matchina/hsm";import { createComboboxStore } from "../store";
export function createFlatComboboxMachine() { const store = createComboboxStore(); const storeApi = createStoreApi(store); const machine = createHSM({ initial: "Inactive", states: { Inactive: { on: { focus: "Active", }, }, // Define the Child Machine Active: { initial: "Empty", states: { Empty: { on: { type: "Suggesting", select: "Empty", } }, Suggesting: { data: (text: string) => text, on: { select: "Empty", }, }, }, // Child machine transitions on: { blur: "^Inactive", type: "Suggesting", }, }, }, } as const);
setup(machine)( guard((ev) => ev.match({ type: () => true, _: () => true, }) ), effect((ev) => ev.match( { focus: storeApi.clear, select: storeApi.selectHighlighted, blur: storeApi.clear, type: storeApi.setInput, }, false ) ) );
return Object.assign(machine, { model: store, ...storeApi, ...eventApi(machine), select: () => { storeApi.selectHighlighted(); machine.send("select"); }, });}
export type FlatComboboxMachine = ReturnType<typeof createFlatComboboxMachine>;import { useRef, useEffect } from "react";import { effect, setup } from "matchina";import { useMachine } from "matchina/react";import { ComboboxView } from "../../combobox-common/ComboboxView";import type { FlatComboboxMachine } from "./machine";
interface ComboboxViewFlatProps { machine: FlatComboboxMachine;}
export function ComboboxViewFlat({ machine }: ComboboxViewFlatProps) { useMachine(machine); useMachine(machine.model); const { input, selectedTags, suggestions, highlightedIndex } = machine.model.getState(); const searchRef = useRef<HTMLInputElement>(null);
const state = machine.getState(); const isActive = state.key !== "Inactive"; useEffect(() => setup(machine)( effect((ev: any) => { if (ev.type === "focus") searchRef.current?.focus(); if (ev.type === "blur") searchRef.current?.blur(); }) ), [machine] );
return ( <ComboboxView stateKey={state.key} isActive={isActive} input={input} selectedTags={selectedTags} suggestions={suggestions} highlightedIndex={highlightedIndex} searchRef={searchRef} onFocus={() => machine.send("focus")} onBlur={() => machine.send("blur")} onType={(v) => machine.send("type", v)} onSelect={(i) => { machine.setHighlighted(i); machine.select(); }} onAdd={(tag) => machine.addTag(tag)} onRemove={(tag) => machine.removeTag(tag)} onHighlight={(dir) => machine.highlight(dir)} /> );}import { useMemo, useState } from "react";import { createFlatComboboxMachine } from "./flattened/machine";import { createComboboxMachine } from "./nested/machine";import { ComboboxViewNested } from "./nested/ComboboxView";import { ComboboxViewFlat } from "./flattened/ComboboxView";import { MachineVisualizer } from "@components/MachineVisualizer";
type Mode = "flat" | "nested";
export default function HSMComboboxIndex() { const [mode, setMode] = useState<Mode>("flat");
// Re-create machine when mode changes const { flatMachine, hierarchicalMachine } = useMemo(() => { return { flatMachine: createFlatComboboxMachine(), hierarchicalMachine: createComboboxMachine(), }; }, [mode]);
const machine = mode === "flat" ? flatMachine : hierarchicalMachine;
return ( <div className="space-y-6"> {/* Mode Toggle - Sticky below header */} <div className="flex justify-center mb-6 sticky top-16 z-10 py-2 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm -mx-4 px-4"> <div className="inline-flex bg-gray-100 dark:bg-gray-800 p-1 rounded-lg"> <button onClick={() => setMode("flat")} className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${ mode === "flat" ? "bg-white dark:bg-gray-700 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" }`} > Flattened </button> <button onClick={() => setMode("nested")} className={`px-4 py-2 rounded text-sm font-medium transition-colors ${ mode === "nested" ? "bg-white dark:bg-gray-700 text-blue-600 dark:text-blue-400 shadow-sm" : "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" }`} > Nested (Hierarchical) </button> </div> </div>
{mode === "flat" ? ( <ComboboxViewFlat machine={flatMachine} /> ) : ( <ComboboxViewNested machine={hierarchicalMachine} /> )}
<MachineVisualizer key={mode} // Force re-mount of visualizer when mode changes machine={machine} title={`HSM Combobox (${mode === "flat" ? "Flattened" : "Nested"})`} interactive={true} layout="stacked" /> </div> );}