-
Notifications
You must be signed in to change notification settings - Fork 543
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new form input component (#2910)
- Loading branch information
1 parent
9395fb7
commit 5f0994d
Showing
7 changed files
with
337 additions
and
8 deletions.
There are no files selected for viewing
116 changes: 116 additions & 0 deletions
116
apps/engineering/content/design/components/form-input.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
--- | ||
title: FormInput | ||
description: A form input component with built-in label, description, and error handling capabilities. | ||
--- | ||
import { | ||
DefaultFormInputVariant, | ||
RequiredFormInputVariant, | ||
SuccessFormInputVariant, | ||
WarningFormInputVariant, | ||
ErrorFormInputVariant, | ||
DisabledFormInputVariant, | ||
DefaultValueFormInputVariant, | ||
ReadonlyFormInputVariant, | ||
ComplexFormInputVariant | ||
} from "./form/form-input.variants" | ||
|
||
# FormInput | ||
A comprehensive form input component that combines labels, descriptions, and validation states. Perfect for creating accessible, user-friendly forms with proper labeling and helpful context. | ||
|
||
## Default | ||
The default FormInput includes a label and optional description text, providing clear context for users. | ||
|
||
<DefaultFormInputVariant /> | ||
|
||
## Input States | ||
|
||
### Required Field | ||
Use the required prop to indicate mandatory fields. This automatically adds an asterisk (*) to the label. | ||
|
||
<RequiredFormInputVariant /> | ||
|
||
### Success State | ||
Indicates successful validation or acceptance of input value. The success icon and text provide positive feedback. | ||
|
||
<SuccessFormInputVariant /> | ||
|
||
### Warning State | ||
Used for potentially problematic inputs that don't prevent form submission. Includes a warning icon and explanatory text. | ||
|
||
<WarningFormInputVariant /> | ||
|
||
### Error State | ||
Shows validation errors or other issues that need user attention. Features prominent error styling and message. | ||
|
||
<ErrorFormInputVariant /> | ||
|
||
### Disabled State | ||
Apply when the field should be non-interactive, such as during form submission or based on other field values. | ||
|
||
<DisabledFormInputVariant /> | ||
|
||
### With Default Value | ||
Pre-populated input with an initial value that users can modify. | ||
|
||
<DefaultValueFormInputVariant /> | ||
|
||
### Read-only State | ||
For displaying non-editable information while maintaining form layout consistency. | ||
|
||
<ReadonlyFormInputVariant /> | ||
|
||
## Complex Usage | ||
Example of a FormInput with multiple props configured for a specific use case. | ||
|
||
<ComplexFormInputVariant /> | ||
|
||
## Props | ||
The FormInput component extends the standard Input component props with additional form-specific properties: | ||
|
||
<AutoTypeTable | ||
name="FormInputProps" | ||
type={`import { InputProps } from "../input" | ||
import { ReactNode } from "react" | ||
export interface FormInputProps extends InputProps { | ||
/** Text label for the input field */ | ||
label?: string; | ||
/** Helper text providing additional context */ | ||
description?: string; | ||
/** Whether the field is required */ | ||
required?: boolean; | ||
/** Error message to display when validation fails */ | ||
error?: string; | ||
/** ID for the input element, auto-generated if not provided */ | ||
id?: string; | ||
/** Additional class names to apply to the fieldset wrapper */ | ||
className?: string; | ||
/** Visual state variant passed to the underlying Input component */ | ||
variant?: 'default' | 'success' | 'warning' | 'error'; | ||
}`} | ||
/> | ||
|
||
## Accessibility | ||
FormInput is built with accessibility in mind: | ||
- Labels are properly associated with inputs using htmlFor/id | ||
- Error messages are announced to screen readers using role="alert" | ||
- Required fields are marked both visually and via aria-required | ||
- Helper text is linked to inputs using aria-describedby | ||
- Error states are indicated using aria-invalid | ||
|
||
## Best Practices | ||
When using the FormInput component: | ||
- Always provide clear, concise labels | ||
- Use description text to provide additional context when needed | ||
- Keep error messages specific and actionable | ||
- Use required fields sparingly and logically | ||
- Group related FormInputs using fieldset and legend when appropriate | ||
- Consider the mobile experience when writing labels and descriptions | ||
- Maintain consistent validation patterns across your form | ||
- Use appropriate input types (email, tel, etc.) for better mobile keyboards | ||
- Consider character/word limits in descriptions and error messages | ||
- Test with screen readers to ensure accessibility | ||
|
||
## Layout Guidelines | ||
- Labels should be clear and concise | ||
- Error messages should appear immediately below the input | ||
- Description text should be helpful but not too lengthy |
131 changes: 131 additions & 0 deletions
131
apps/engineering/content/design/components/form/form-input.variants.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { RenderComponentWithSnippet } from "@/app/components/render"; | ||
import { FormInput } from "@unkey/ui"; | ||
|
||
export const DefaultFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Username" | ||
description="Choose a unique username for your account" | ||
placeholder="e.g. gandalf_grey" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Required field variant | ||
export const RequiredFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Email Address" | ||
description="We'll send your confirmation email here" | ||
required | ||
placeholder="[email protected]" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Success variant | ||
export const SuccessFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="API Key" | ||
description="Your API key has been verified" | ||
variant="success" | ||
defaultValue="sk_live_middleearth123" | ||
placeholder="Enter your API key" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Warning variant | ||
export const WarningFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Password" | ||
description="Your password is about to expire" | ||
variant="warning" | ||
type="password" | ||
placeholder="Enter your password" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Error variant | ||
export const ErrorFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Repository Name" | ||
error="A repository with this name already exists" | ||
placeholder="my-awesome-project" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Disabled variant | ||
export const DisabledFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Organization ID" | ||
description="Contact admin to change organization ID" | ||
disabled | ||
defaultValue="org_fellowship123" | ||
placeholder="Organization ID" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// With default value | ||
export const DefaultValueFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Project Name" | ||
description="Name of your new project" | ||
defaultValue="The Fellowship Project" | ||
placeholder="Enter project name" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Readonly variant | ||
export const ReadonlyFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Generated Token" | ||
description="Copy this token for your records" | ||
readOnly | ||
defaultValue="tkn_1ring2rulethemall" | ||
placeholder="Your token will appear here" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; | ||
|
||
// Complex example with multiple props | ||
export const ComplexFormInputVariant = () => { | ||
return ( | ||
<RenderComponentWithSnippet> | ||
<FormInput | ||
label="Webhook URL" | ||
description="Enter the URL where we'll send event notifications" | ||
required | ||
placeholder="https://api.yourdomain.com/webhooks" | ||
className="max-w-lg" | ||
id="webhook-url-input" | ||
/> | ||
</RenderComponentWithSnippet> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import { CircleInfo, TriangleWarning2 } from "@unkey/icons"; | ||
import * as React from "react"; | ||
import { cn } from "../../lib/utils"; | ||
import { Input, type InputProps } from "../input"; | ||
|
||
export interface FormInputProps extends InputProps { | ||
label?: string; | ||
description?: string; | ||
required?: boolean; | ||
error?: string; | ||
} | ||
|
||
export const FormInput = React.forwardRef<HTMLInputElement, FormInputProps>( | ||
({ label, description, error, required, id, className, variant, ...props }, ref) => { | ||
const inputVariant = error ? "error" : variant; | ||
|
||
const inputId = id || React.useId(); | ||
const descriptionId = `${inputId}-helper`; | ||
const errorId = `${inputId}-error`; | ||
|
||
return ( | ||
<fieldset className={cn("flex flex-col gap-1.5 border-0 m-0 p-0", className)}> | ||
{label && ( | ||
<label | ||
id={`${inputId}-label`} | ||
htmlFor={inputId} | ||
className="text-gray-11 text-[13px] flex items-center" | ||
> | ||
{label} | ||
{required && ( | ||
<span className="text-error-9 ml-1" aria-label="required field"> | ||
* | ||
</span> | ||
)} | ||
</label> | ||
)} | ||
|
||
<Input | ||
ref={ref} | ||
id={inputId} | ||
variant={inputVariant} | ||
aria-describedby={error ? errorId : description ? descriptionId : undefined} | ||
aria-invalid={!!error} | ||
aria-required={required} | ||
{...props} | ||
/> | ||
|
||
{(description || error) && ( | ||
<div className="text-[13px] leading-5"> | ||
{error ? ( | ||
<div id={errorId} role="alert" className="text-error-11 flex gap-2 items-center"> | ||
<TriangleWarning2 aria-hidden="true" /> | ||
{error} | ||
</div> | ||
) : description ? ( | ||
<output | ||
id={descriptionId} | ||
className={cn( | ||
"text-gray-9 flex gap-2 items-center", | ||
variant === "success" | ||
? "text-success-11" | ||
: variant === "warning" | ||
? "text-warning-11" | ||
: "", | ||
)} | ||
> | ||
{variant === "warning" ? ( | ||
<TriangleWarning2 size="md-regular" aria-hidden="true" /> | ||
) : ( | ||
<CircleInfo size="md-regular" aria-hidden="true" /> | ||
)} | ||
<span>{description}</span> | ||
</output> | ||
) : null} | ||
</div> | ||
)} | ||
</fieldset> | ||
); | ||
}, | ||
); | ||
|
||
FormInput.displayName = "FormInput"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./form-input"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters