Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 16 additions & 27 deletions libs/ui/src/molecules/form-textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ReactNode } from "react"
import { ErrorText } from "../atoms/error-text"
import { ExtraText } from "../atoms/extra-text"
import { Label } from "../atoms/label"
import { StatusText } from "../atoms/status-text"
import { Textarea, type TextareaProps } from "../atoms/textarea"

type ValidateStatus = "default" | "error" | "success" | "warning"
Expand All @@ -11,22 +10,18 @@ interface FormTextareaRawProps extends TextareaProps {
label: ReactNode
validateStatus?: ValidateStatus
helpText?: ReactNode
extraText?: ReactNode
}

export function FormTextareaRaw({
id,
label,
validateStatus = "default",
helpText,
extraText,
size = "md",
required,
disabled,
...props
}: FormTextareaRawProps) {
const extraTextId = extraText ? `${id}-extra` : undefined

return (
<div className="flex flex-col gap-form-field-gap">
<Label disabled={disabled} htmlFor={id} required={required} size={size}>
Expand All @@ -41,43 +36,37 @@ export function FormTextareaRaw({
{...props}
/>

{/* Status message */}
{helpText}

{extraText && (
<ExtraText id={extraTextId} size={size}>
{extraText}
</ExtraText>
)}
</div>
)
}

type FormTextareaProps = FormTextareaRawProps & {
showHelpTextIcon?: boolean
}

export function FormTextarea({
helpText,
id,
validateStatus,
size,
validateStatus = "default",
showHelpTextIcon = validateStatus !== "default",
size = "md",
...props
}: FormTextareaRawProps) {
const helpTextId = helpText ? `${id}-helper` : undefined

}: FormTextareaProps) {
return (
<FormTextareaRaw
helpText={
validateStatus === "error" ? (
<ErrorText id={helpTextId} showIcon size={size}>
{helpText}
</ErrorText>
) : (
<ExtraText id={helpTextId} size={size}>
helpText &&
<StatusText
status={validateStatus}
showIcon={showHelpTextIcon}
size={size}
>
{helpText}
</ExtraText>
)
</StatusText>
}
id={id}
size={size}
validateStatus={validateStatus}
{...props}
/>
)
Expand Down
90 changes: 87 additions & 3 deletions libs/ui/stories/molecules/form-textarea.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const meta: Meta<typeof FormTextarea> = {
options: ['default', 'error', 'success', 'warning'],
description: 'Validation state that affects textarea style and helper text',
},
showHelpTextIcon: {
control: 'boolean',
description: 'Whether to show an icon with the help text',
},
resize: {
control: 'select',
options: ['none', 'both', 'x', 'y'],
Expand Down Expand Up @@ -92,6 +96,16 @@ export const AllVariants: Story = {
</VariantGroup>

<VariantGroup title="Validation States">
<div className="w-80">
<FormTextarea
id="default-textarea"
label="Default state"
placeholder="Enter text..."
validateStatus="default"
helpText="This is default help text"
rows={3}
/>
</div>
<div className="w-80">
<FormTextarea
id="success-textarea"
Expand Down Expand Up @@ -137,10 +151,9 @@ export const AllVariants: Story = {
<div className="w-80">
<FormTextarea
id="extra-textarea"
label="With helper and extra text"
label="With validation message"
placeholder="Enter comment..."
helpText="Please be respectful"
extraText="Maximum 500 characters"
validateStatus="default"
rows={3}
/>
Expand Down Expand Up @@ -251,6 +264,78 @@ export const AllVariants: Story = {
),
}

// Validation States - Dedicated story showing all 4 validation states
export const ValidationStates: Story = {
render: () => (
<div className="flex flex-col gap-4 w-80">
<FormTextarea
id="default-validation"
label="Default State"
placeholder="Enter your message..."
validateStatus="default"
helpText="This is default help text"
rows={3}
/>
<FormTextarea
id="error-validation"
label="Error State"
placeholder="Enter your message..."
validateStatus="error"
helpText="Message is too short (min 10 characters)"
rows={3}
/>
<FormTextarea
id="success-validation"
label="Success State"
placeholder="Enter your message..."
validateStatus="success"
helpText="Message saved successfully"
rows={3}
/>
<FormTextarea
id="warning-validation"
label="Warning State"
placeholder="Enter your message..."
validateStatus="warning"
helpText="Message contains sensitive information"
rows={3}
/>
</div>
),
}

// Sizes - Dedicated story showing all size variants
export const Sizes: Story = {
render: () => (
<div className="flex flex-col gap-4 w-80">
<FormTextarea
id="small-size"
label="Small Size"
placeholder="Enter text..."
size="sm"
helpText="Small textarea variant"
rows={3}
/>
<FormTextarea
id="medium-size"
label="Medium Size (Default)"
placeholder="Enter text..."
size="md"
helpText="Medium textarea variant"
rows={3}
/>
<FormTextarea
id="large-size"
label="Large Size"
placeholder="Enter text..."
size="lg"
helpText="Large textarea variant"
rows={3}
/>
</div>
),
}

// Interactive character count example
export const InteractiveCharacterCount: Story = {
render: () => {
Expand Down Expand Up @@ -361,7 +446,6 @@ export const ContactForm: Story = {
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
rows={6}
helpText="Please provide as much detail as possible"
extraText="We typically respond within 24 hours"
/>

<FormTextarea
Expand Down
Loading