-
Notifications
You must be signed in to change notification settings - Fork 610
feat: new form input component #2910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 hidden or 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 hidden or 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="frodo@shire.me" | ||
| /> | ||
| </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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.