import { expect, userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { CalendarRegularIcon } from "alouette-icons/phosphor-icons/Calendar"; import { type ReactNode, useState } from "react"; import { Button } from "../actions/Button"; import { DecisionBar } from "../actions/DecisionBar"; import { Modal } from "../containers/Modal"; import { Paragraph, Text } from "../primitives/Text"; import { Story, accentsWithoutNeutral } from "../story-components/Story"; import { Badge } from "./Badge"; import { EditableItem, type EditableItemProps } from "./EditableItem"; type ThisStory = StoryObj; /** * EditableItem owns no editor, so the stories supply one: `onEdit` opens a * plain Modal. FormEditableItem packages this same composition for a form. */ function EditableItemDemo({ label = "Display name", summary = Ada Lovelace, editAriaLabel = "Edit display name", children, ...itemProps }: Partial): ReactNode { const [editing, setEditing] = useState(false); function close(): void { setEditing(false); } return ( { setEditing(true); }} > {children} } onDismiss={close} /> } onClose={close} > Your editor goes here. ); } export default { title: "alouette/Data/EditableItem", component: EditableItem, parameters: { componentSubtitle: "A labelled value with an edit affordance. Owns no editor — pair it with FormEditableItem, or wire onEdit to your own Modal.", }, argTypes: { label: { control: "text" }, details: { control: "text" }, editAriaLabel: { control: "text" }, variant: { control: "select", options: ["tonal", "filled", "soft"], }, accent: { control: "select", options: accentsWithoutNeutral }, disabled: { control: "boolean" }, }, } satisfies Meta; export const EditableItemPreviewStory: ThisStory = { name: "EditableItem Preview", parameters: { layout: "padded", chromatic: { disableSnapshot: true }, }, args: { label: "Display name", editAriaLabel: "Edit display name", }, render: (args) => , }; export const EditableItemVariantsStory: ThisStory = { name: "EditableItem Variants", render: () => ( Europe/Paris} editAriaLabel="Edit time zone" /> Mathematician and writer, chiefly known for her work on the Analytical Engine. 12 Aug 2026} editAriaLabel="Edit renewal date" editIcon={} /> {accentsWithoutNeutral.map((accent) => ( {accent}} editAriaLabel={`Edit ${accent}`} /> ))} ), }; // Renders the press count so the play function can assert onEdit fired without // reaching into a spy. function EditCounter(props: Partial): ReactNode { const [count, setCount] = useState(0); return ( {`edited ${count}`}} {...props} onEdit={() => { setCount(count + 1); }} /> ); } export const EditableItemTestsStory: ThisStory = { name: "EditableItem Tests", render: () => ( locked 0} /> ), play: async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.click(canvas.getByLabelText("Edit display name")); await expect(canvas.getByText("edited 1")).toBeInTheDocument(); // A disabled item exposes the same accessible name but never fires. const lockedButton = canvas.getByLabelText("Edit account id"); await expect(lockedButton).toBeDisabled(); await userEvent.click(lockedButton, { pointerEventsCheck: 0 }); await expect(canvas.getByText("locked 0")).toBeInTheDocument(); }, };