From c42838d8c63cc4cbda710388db00938b846d7cb4 Mon Sep 17 00:00:00 2001 From: Joel234-png Date: Mon, 28 Sep 2026 13:25:02 +0100 Subject: [PATCH] Add Storybook stories for input, label, select, and fee-estimate-dialog components Add comprehensive input.stories.tsx covering default, disabled, invalid (aria-invalid), file, email, password, and number input variants with value examples and state comparison showcase. Add label.stories.tsx demonstrating default label, label with input pairing, disabled state, required field indicator with asterisk, error states with messages, helper text, form field groups, and label styling variations (font size, weight, case). Add select.stories.tsx with stories for default placeholder state, selected-value state, disabled state, many options (scrolling), token selection use case, recurrence selection from batch create, error states with validation, and multiple selects side-by-side matching real UI patterns. Add fee-estimate-dialog.stories.tsx showing normal-fee and high-fee-warning states with multiple scenarios: large transaction normal fees, small transaction high fee ratio, very high fees, minimal fees, and side-by-side comparison of normal vs warning states to demonstrate visual differences. Closes #724 Closes #725 Closes #726 Closes #723 --- components/ui/fee-estimate-dialog.stories.tsx | 205 ++++++++++++++++ components/ui/input.stories.tsx | 117 ++++++++++ components/ui/label.stories.tsx | 125 ++++++++++ components/ui/select.stories.tsx | 220 ++++++++++++++++++ 4 files changed, 667 insertions(+) create mode 100644 components/ui/fee-estimate-dialog.stories.tsx create mode 100644 components/ui/input.stories.tsx create mode 100644 components/ui/label.stories.tsx create mode 100644 components/ui/select.stories.tsx diff --git a/components/ui/fee-estimate-dialog.stories.tsx b/components/ui/fee-estimate-dialog.stories.tsx new file mode 100644 index 0000000..00d8639 --- /dev/null +++ b/components/ui/fee-estimate-dialog.stories.tsx @@ -0,0 +1,205 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { FeeEstimateDialog } from "./fee-estimate-dialog" +import { Button } from "./button" +import React from "react" + +const meta = { + title: "UI/FeeEstimateDialog", + component: FeeEstimateDialog, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Normal fee estimate (no warning) +export const NormalFee: Story = { + render: () => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) + }, +} + +// High fee warning state +export const HighFeeWarning: Story = { + render: () => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) + }, +} + +// Large transaction with normal fees +export const LargeTransaction: Story = { + render: () => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) + }, +} + +// Small transaction with high relative fees +export const SmallTransactionHighFeeRatio: Story = { + render: () => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) + }, +} + +// Very high fees scenario +export const VeryHighFees: Story = { + render: () => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) + }, +} + +// Minimal fees +export const MinimalFees: Story = { + render: () => { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) + }, +} + +// Comparison: Normal vs High Fee Side by Side (in separate components) +export const StateComparison: Story = { + render: () => { + const [normalOpen, setNormalOpen] = React.useState(false) + const [highOpen, setHighOpen] = React.useState(false) + + return ( +
+
+

Normal Fee

+ + +
+
+

High Fee Warning

+ + +
+
+ ) + }, +} diff --git a/components/ui/input.stories.tsx b/components/ui/input.stories.tsx new file mode 100644 index 0000000..5885afc --- /dev/null +++ b/components/ui/input.stories.tsx @@ -0,0 +1,117 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { Input } from "./input" + +const meta = { + title: "UI/Input", + component: Input, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + disabled: { control: "boolean" }, + type: { control: "select", options: ["text", "email", "password", "number", "file"] }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +// Default input state +export const Default: Story = { + args: { + placeholder: "Enter text...", + type: "text", + }, +} + +// Disabled input +export const Disabled: Story = { + args: { + placeholder: "Disabled input", + disabled: true, + type: "text", + }, +} + +// Invalid state with aria-invalid +export const Invalid: Story = { + args: { + placeholder: "Invalid input", + "aria-invalid": true, + type: "text", + }, +} + +// File input type +export const FileInput: Story = { + args: { + type: "file", + }, +} + +// Email input +export const EmailInput: Story = { + args: { + type: "email", + placeholder: "your@email.com", + }, +} + +// Password input +export const PasswordInput: Story = { + args: { + type: "password", + placeholder: "Enter password...", + }, +} + +// Number input +export const NumberInput: Story = { + args: { + type: "number", + placeholder: "0", + }, +} + +// With value +export const WithValue: Story = { + args: { + type: "text", + value: "Sample input value", + disabled: false, + }, +} + +// Large field +export const LargeField: Story = { + args: { + type: "text", + placeholder: "Large input field", + className: "w-96", + }, +} + +// Comparison of states +export const StateComparison: Story = { + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ ), +} diff --git a/components/ui/label.stories.tsx b/components/ui/label.stories.tsx new file mode 100644 index 0000000..66e0e58 --- /dev/null +++ b/components/ui/label.stories.tsx @@ -0,0 +1,125 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { Label } from "./label" +import { Input } from "./input" + +const meta = { + title: "UI/Label", + component: Label, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Basic label +export const Default: Story = { + args: { + children: "Email Address", + htmlFor: "email-input", + }, +} + +// Label with input (common pattern) +export const WithInput: Story = { + render: () => ( +
+ + +
+ ), +} + +// Disabled state +export const DisabledState: Story = { + render: () => ( +
+ + +
+ ), +} + +// Required field indicator +export const RequiredField: Story = { + render: () => ( +
+ + +
+ ), +} + +// Label with error message +export const WithError: Story = { + render: () => ( +
+ + +

Username already taken

+
+ ), +} + +// Label with helper text +export const WithHelperText: Story = { + render: () => ( +
+ + +

Minimum 8 characters

+
+ ), +} + +// Form field group +export const FormFieldGroup: Story = { + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ), +} + +// Label styling variations +export const StylingVariations: Story = { + render: () => ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ), +} diff --git a/components/ui/select.stories.tsx b/components/ui/select.stories.tsx new file mode 100644 index 0000000..3f4dbec --- /dev/null +++ b/components/ui/select.stories.tsx @@ -0,0 +1,220 @@ +import type { Meta, StoryObj } from "@storybook/react" +import { Select } from "./select" +import React from "react" + +const meta = { + title: "UI/Select", + component: Select, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta + +export default meta +type Story = StoryObj + +// Default select with placeholder +export const Default: Story = { + render: () => { + const [value, setValue] = React.useState("") + const items = [ + { value: "option1", label: "Option 1" }, + { value: "option2", label: "Option 2" }, + { value: "option3", label: "Option 3" }, + ] + + return ( +
+ setValue(newValue)} + items={items} + /> +
+ ) + }, +} + +// Disabled select +export const Disabled: Story = { + render: () => { + const items = [ + { value: "option1", label: "Option 1" }, + { value: "option2", label: "Option 2" }, + { value: "option3", label: "Option 3" }, + ] + + return ( +
+ setValue(newValue)} + placeholder="Select from many options" + items={items} + /> +
+ ) + }, +} + +// Token selection (common use case) +export const TokenSelection: Story = { + render: () => { + const [value, setValue] = React.useState("") + const tokens = [ + { value: "usdc", label: "USDC" }, + { value: "usdt", label: "USDT" }, + { value: "eur", label: "EUR" }, + { value: "xlm", label: "XLM" }, + ] + + return ( +
+ + setValue(newValue)} + placeholder="Select recurrence" + items={recurrenceOptions} + /> +
+ ) + }, +} + +// Select with error state +export const WithError: Story = { + render: () => { + const [value, setValue] = React.useState("") + const items = [ + { value: "option1", label: "Option 1" }, + { value: "option2", label: "Option 2" }, + ] + + return ( +
+ + +
+
+ +