chore: add Storybook stories for MyForm component

This commit is contained in:
2026-09-19 16:59:28 +03:30
parent 107624d4f2
commit 12f8b70c2b
3 changed files with 308 additions and 3 deletions
+3 -3
View File
@@ -1,11 +1,11 @@
import "./App.css";
import { MyForm } from "./stories/myForm";
function App() {
return (
<>
<section>
<text>TanStackForm</text>
</section>
<MyForm></MyForm>
</>
);
}
+119
View File
@@ -0,0 +1,119 @@
import type { Meta, StoryObj } from "@storybook/react";
import { within, userEvent, expect, fn } from "storybook/test";
import { MyForm } from "./myForm";
const meta: Meta<typeof MyForm> = {
title: "Components/MyForm",
component: MyForm,
parameters: {
layout: "centered",
},
args: {
onSubmit: fn(),
},
};
export default meta;
type Story = StoryObj<typeof MyForm>;
export const Default: Story = {};
export const ValidationErrors: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const nameInput = canvas.getByLabelText(/Full Name/i);
await userEvent.type(nameInput, "12345");
const ageInput = canvas.getByLabelText(/Age/i);
await userEvent.type(ageInput, "15");
const passwordInput = canvas.getByLabelText(/Password/i);
await userEvent.type(passwordInput, "123");
await userEvent.tab();
await expect(
canvas.getByText(/Please enter letters only/i),
).toBeInTheDocument();
await expect(
canvas.getByText(/Age must be at least 18/i),
).toBeInTheDocument();
await expect(
canvas.getByText(/Password must be at least 8 characters long/i),
).toBeInTheDocument();
},
};
export const SuccessfulSubmit: Story = {
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const nameInput = canvas.getByLabelText(/Full Name/i);
await userEvent.type(nameInput, "Somayeh Ghanavati");
const ageInput = canvas.getByLabelText(/Age/i);
await userEvent.type(ageInput, "37");
const passwordInput = canvas.getByLabelText(/Password/i);
await userEvent.type(passwordInput, "password123");
const submitButton = canvas.getByRole("button", { name: /Sign Up/i });
await expect(submitButton).toBeEnabled();
await userEvent.click(submitButton);
},
};
export const FillAndFixForm: Story = {
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const nameInput = canvas.getByLabelText(/Full Name/i);
const ageInput = canvas.getByLabelText(/Age/i);
const passwordInput = canvas.getByLabelText(/Password/i);
const submitButton = canvas.getByRole("button", { name: /Sign Up/i });
await userEvent.type(nameInput, "1234");
await userEvent.type(ageInput, "10");
await userEvent.type(passwordInput, "12");
await userEvent.tab();
await expect(
canvas.queryByText(/Please enter letters only/i),
).toBeInTheDocument();
await expect(
canvas.queryByText(/Age must be at least 18/),
).toBeInTheDocument();
await expect(
canvas.queryByText(/Password must be at least 8 characters long/),
).toBeInTheDocument();
await expect(submitButton).toBeDisabled();
await userEvent.clear(nameInput);
await userEvent.type(nameInput, "Somayeh Ghanavati");
await userEvent.clear(ageInput);
await userEvent.type(ageInput, "36");
await userEvent.clear(passwordInput);
await userEvent.type(passwordInput, "123456789");
await expect(
canvas.queryByText(/Please enter letters only/i),
).not.toBeInTheDocument();
await expect(
canvas.queryByText(/Age must be at least 18/),
).not.toBeInTheDocument();
await expect(
canvas.queryByText(/Password must be at least 8 characters long/),
).not.toBeInTheDocument();
await expect(submitButton).toBeEnabled();
await userEvent.click(submitButton);
},
};
+186
View File
@@ -0,0 +1,186 @@
import React from "react";
import { useForm } from "@tanstack/react-form";
export function MyForm() {
const form = useForm({
defaultValues: {
username: "",
age: "",
password: "",
},
onSubmit: async ({ value }) => {
console.log("Form submitted successfully:", value);
alert(JSON.stringify(value, null, 2));
},
});
return (
<div
style={{
maxWidth: "400px",
margin: "20px auto",
fontFamily: "sans-serif",
}}
>
<h2>Registration Form (TanStack Form)</h2>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<form.Field
name="username"
validators={{
onChange: ({ value }) => {
if (!value) return "Full name is required";
if (!/^[a-zA-Za-z\s]+$/.test(value)) {
return "Please enter letters only";
}
return undefined;
},
}}
>
{(field) => (
<div style={{ marginBottom: "15px" }}>
<label
htmlFor={field.name}
style={{ display: "block", marginBottom: "5px" }}
>
Full Name (Letters only):
</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
style={{
width: "100%",
padding: "8px",
boxSizing: "border-box",
}}
/>
{field.state.meta.errors.length > 0 && (
<span style={{ color: "red", fontSize: "12px" }}>
{field.state.meta.errors.join(", ")}
</span>
)}
</div>
)}
</form.Field>
<form.Field
name="age"
validators={{
onChange: ({ value }) => {
if (!value) return "Age is required";
if (isNaN(Number(value))) {
return "Please enter numbers only";
}
if (Number(value) < 18) {
return "Age must be at least 18";
}
return undefined;
},
}}
>
{(field) => (
<div style={{ marginBottom: "15px" }}>
<label
htmlFor={field.name}
style={{ display: "block", marginBottom: "5px" }}
>
Age (Numbers only):
</label>
<input
id={field.name}
name={field.name}
type="number"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
style={{
width: "100%",
padding: "8px",
boxSizing: "border-box",
}}
/>
{field.state.meta.errors.length > 0 && (
<span style={{ color: "red", fontSize: "12px" }}>
{field.state.meta.errors.join(", ")}
</span>
)}
</div>
)}
</form.Field>
<form.Field
name="password"
validators={{
onChange: ({ value }) => {
if (!value) return "Password is required";
if (value.length < 8) {
return "Password must be at least 8 characters long";
}
return undefined;
},
}}
>
{(field) => (
<div style={{ marginBottom: "15px" }}>
<label
htmlFor={field.name}
style={{ display: "block", marginBottom: "5px" }}
>
Password:
</label>
<input
id={field.name}
name={field.name}
type="password"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
style={{
width: "100%",
padding: "8px",
boxSizing: "border-box",
}}
/>
{field.state.meta.errors.length > 0 && (
<span style={{ color: "red", fontSize: "12px" }}>
{field.state.meta.errors.join(", ")}
</span>
)}
</div>
)}
</form.Field>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<button
type="submit"
disabled={!canSubmit}
style={{
width: "100%",
padding: "10px",
backgroundColor: canSubmit ? "#007bff" : "#ccc",
color: "white",
border: "none",
borderRadius: "4px",
cursor: canSubmit ? "pointer" : "not-allowed",
}}
>
{isSubmitting ? "Submitting..." : "Sign Up"}
</button>
)}
</form.Subscribe>
</form>
</div>
);
}