# Choice menus (/start/choices)



<CalloutContainer type="info">
  <CalloutTitle>
    UI screen
  </CalloutTitle>

  <CalloutDescription>
    You can find an example of the choice menu UI screen in the <DynamicLink href="/start/interface-examples#choice-menu">interface examples</DynamicLink> section.
  </CalloutDescription>
</CalloutContainer>

<CalloutContainer type="info">
  <CalloutTitle>
    *ink*
  </CalloutTitle>

  <CalloutDescription>
    You can use this method with the *ink* syntax. See more <DynamicLink href="/ink/choices">here</DynamicLink>.
  </CalloutDescription>
</CalloutContainer>

In visual novels, choice menus allow the player to make decisions that affect the story.

In Pixi’VN, you can prompt the player to make a choice. Each choice can either start a `label` or close the choice menu.

## Require the player to make a choice [#require-the-player-to-make-a-choice]

To require the player to make a choice, set [`narration.choices`](/jsdoc/pixi-vn/index/interfaces/NarrationManagerInterface#choices) to an array of [`StoredChoiceInterface`](/jsdoc/pixi-vn/index/type-aliases/StoredChoiceInterface). To create a [`StoredChoiceInterface`](/jsdoc/pixi-vn/index/type-aliases/StoredChoiceInterface) object, use:

* [`newChoiceOption`](/jsdoc/pixi-vn/index/functions/newChoiceOption)
* [`newCloseChoiceOption`](/jsdoc/pixi-vn/index/functions/newCloseChoiceOption)

```ts title="content/labels/start.label.ts"
import {
    newChoiceOption,
    newCloseChoiceOption,
    narration,
    newLabel,
} from "@drincs/pixi-vn";

export const startLabel = newLabel("start", [
    async () => {
        narration.dialogue = "Choose a fruit:";
        narration.choices = [
            // [!code focus]
            newChoiceOption("Orange", orangeLabel, {}), // by default, the label will be called with "call" // [!code focus]
            newChoiceOption("Banana", bananaLabel, {}, { type: "jump" }), // [!code focus]
            newChoiceOption(
                // [!code focus]
                "Apple", // [!code focus]
                appleLabel, // [!code focus]
                { quantity: 5 }, // [!code focus]
                { type: "call" }, // [!code focus]
            ), // [!code focus]
            newCloseChoiceOption("Cancel"), // [!code focus]
        ]; // [!code focus]
    },
    () => {
        narration.dialogue = "Restart";
    },
    async (props) => await narration.jump("start", props),
]);
```

<PixiVnExample path="narration/choice-menus" />

## Get [#get]

To get the current choice menu, use [`narration.choices.list`](/jsdoc/pixi-vn/index/interfaces/NarrationChoicesInterface#list). This returns an array of [`StoredChoiceInterface`](/jsdoc/pixi-vn/index/type-aliases/StoredChoiceInterface).

```ts
const menuOptions: StoredChoiceInterface[] = narration.choices.list;
```

## Request [#request]

To select a choice, use [`narration.choices.select`](/jsdoc/pixi-vn/index/interfaces/NarrationChoicesInterface#select).

```ts
const item = narration.choices.list![0]; // get the first item

narration.choices
    .select(item, {
        // Add StepLabelProps here
        navigate: navigate, // example
        // And the props to pass to the label
        ...item.props,
    })
    .then(() => {
        // ...
    })
    .catch((e) => {
        // ...
    });
```

## Remove [#remove]

To clear the choice options, set `narration.choices = undefined`.

```ts
narration.choices = undefined;
```

## Other features [#other-features]

<Accordions>
  <Accordion title="Choices already made" id="choices-already-made">
    To get the choices already made in the current `step`, use [`narration.queries.alreadyCurrentStepMadeChoices`](/jsdoc/pixi-vn/index/interfaces/NarrationQueriesInterface#alreadycurrentstepmadechoices).

    <CodeBlockTabs defaultValue="hooks/useQueryInterface.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="hooks/useQueryInterface.ts">
          hooks/useQueryInterface.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="components/menus/choice-menus.tsx">
          components/menus/choice-menus.tsx
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="hooks/useQueryInterface.ts">
        ```ts
        import { INTERFACE_DATA_USE_QUERY_KEY } from "@/constants";
        import { narration } from "@drincs/pixi-vn";
        import { useQuery } from "@tanstack/react-query";

        const CHOICE_MENU_OPTIONS_USE_QUERY_KEY = "choice_menu_options_use_query_key";
        export function useQueryChoiceMenuOptions() {
            return useQuery({
                queryKey: [
                    INTERFACE_DATA_USE_QUERY_KEY,
                    CHOICE_MENU_OPTIONS_USE_QUERY_KEY,
                ],
                queryFn: async () =>
                    narration.choices.list?.map((option) => ({
                        ...option,
                        text:
                            typeof option.text === "string"
                                ? option.text
                                : option.text.map((text) => text).join(" "),
                        alreadyChosen: // [!code ++]
                            narration.queries.alreadyCurrentStepMadeChoices?.find( // [!code ++]
                                (index) => index === option.choiceIndex, // [!code ++]
                            ) !== undefined, // [!code ++]
                    })) || [],
            });
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="components/menus/choice-menus.tsx">
        ```tsx
        import { Button } from "@/components/ui/button";
        import { useNarrationFunctions } from "@/lib/hooks/narration-hooks";
        import { useQueryChoiceMenuOptions } from "@/lib/query/narration-query";
        import { CornerDownLeft } from "lucide-react";

        export function ChoiceMenu() {
            const { data: menu = [] } = useQueryChoiceMenuOptions();
            const { selectChoice } = useNarrationFunctions();

            return (
                <div
                    className="flex flex-col items-center justify-center gap-2 w-full h-full overflow-auto"
                    role="menu"
                >
                    {menu.map((item) => (
                        <div key={`choice-${item.choiceIndex}`}>
                            <Button onClick={() => selectChoice(item)}>
                                {item.alreadyChosen ? "✓ " : ""} // [!code ++]
                                {item.type === "close" && <CornerDownLeft />}
                                {item.text}
                            </Button>
                        </div>
                    ))}
                </div>
            );
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <PixiVnExample path="narration/choices-already-made" />
  </Accordion>

  <Accordion title="Custom class" id="custom-class">
    You can customize a choice menu option by adding properties to the [`ChoiceInterface`](/jsdoc/pixi-vn/index/interfaces/ChoiceInterface) interface. For example, add an `icon` property to display an icon.

    Override the [`ChoiceInterface`](/jsdoc/pixi-vn/index/interfaces/ChoiceInterface) interface in your `.d.ts` file:

    <CodeBlockTabs defaultValue="pixi-vn.d.ts">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="pixi-vn.d.ts">
          pixi-vn.d.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="content/labels/start.label.ts">
          content/labels/start.label.ts
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="components/menus/choice-menus.tsx">
          components/menus/choice-menus.tsx
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="pixi-vn.d.ts">
        ```ts
        declare module "@drincs/pixi-vn" {
            interface ChoiceInterface {
                icon?: string;
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="content/labels/start.label.ts">
        ```ts
        narration.choices = [
            newChoiceOption("Orange", orangeLabel, {}, { icon: "orange.png" }),
            newChoiceOption("Banana", bananaLabel, {}, { icon: "banana.png" }),
            newChoiceOption("Apple", appleLabel, {}, { icon: "apple.png" }),
        ];
        ```
      </CodeBlockTab>

      <CodeBlockTab value="components/menus/choice-menus.tsx">
        ```tsx
        import { Button } from "@/components/ui/button";
        import { useNarrationFunctions } from "@/lib/hooks/narration-hooks";
        import { useQueryChoiceMenuOptions } from "@/lib/query/narration-query";
        import { CornerDownLeft } from "lucide-react";

        export function ChoiceMenu() {
            const { data: menu = [] } = useQueryChoiceMenuOptions();
            const { selectChoice } = useNarrationFunctions();

            return (
                <div
                    className="flex flex-col items-center justify-center gap-2 w-full h-full overflow-auto"
                    role="menu"
                >
                    {menu.map((item) => (
                        <div key={`choice-${item.choiceIndex}`}>
                            {choice.icon && <img src={choice.icon} alt={choice.text} />} // [!code ++]
                            <Button onClick={() => selectChoice(item)}>
                                {item.type === "close" && <CornerDownLeft />}
                                {item.text}
                            </Button>
                        </div>
                    ))}
                </div>
            );
        }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>
  </Accordion>
</Accordions>
