mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-25 14:05:03 +00:00
feat(ui): implemented form control components
This commit is contained in:
@ -0,0 +1,40 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
// Be careful on dep cycle
|
||||
import { Input } from '../Input/Input';
|
||||
import { FormControl } from './FormControl';
|
||||
|
||||
const meta: Meta<typeof FormControl> = {
|
||||
title: 'Components/FormControl',
|
||||
component: FormControl,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof FormControl>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Basic: Story = {
|
||||
args: {
|
||||
children: <Input />,
|
||||
label: 'Email',
|
||||
id: 'email',
|
||||
helperText: 'Type something..'
|
||||
}
|
||||
};
|
||||
|
||||
export const RequiredInput: Story = {
|
||||
args: {
|
||||
...Basic.args,
|
||||
isRequired: true
|
||||
}
|
||||
};
|
||||
|
||||
export const ErrorInput: Story = {
|
||||
args: {
|
||||
...Basic.args,
|
||||
errorText: 'Some random error',
|
||||
isError: true
|
||||
}
|
||||
};
|
72
frontend/src/components/v2/FormControl/FormControl.tsx
Normal file
72
frontend/src/components/v2/FormControl/FormControl.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { cloneElement, ReactNode } from 'react';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as Label from '@radix-ui/react-label';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type FormLabelProps = {
|
||||
id?: string;
|
||||
isRequired?: boolean;
|
||||
label?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => (
|
||||
<Label.Root className="text-mineshaft-300 text-sm font-medium block mb-1 ml-0.5" htmlFor={id}>
|
||||
{label}
|
||||
{isRequired && <span className="text-red ml-1">*</span>}
|
||||
</Label.Root>
|
||||
);
|
||||
|
||||
export type FormHelperTextProps = {
|
||||
isError?: boolean;
|
||||
text?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormHelperText = ({ isError, text }: FormHelperTextProps) => (
|
||||
<div
|
||||
className={twMerge(
|
||||
'text-xs flex items-center opacity-90 text-mineshaft-300 mt-2',
|
||||
isError && 'text-red-600'
|
||||
)}
|
||||
>
|
||||
{isError && (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} size="sm" className="mr-1" />
|
||||
</span>
|
||||
)}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export type FormControlProps = {
|
||||
id?: string;
|
||||
isRequired?: boolean;
|
||||
isError?: boolean;
|
||||
label?: ReactNode;
|
||||
helperText?: ReactNode;
|
||||
errorText?: ReactNode;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export const FormControl = ({
|
||||
children,
|
||||
isRequired,
|
||||
label,
|
||||
helperText,
|
||||
errorText,
|
||||
id,
|
||||
isError
|
||||
}: FormControlProps): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
{typeof label === 'string' ? (
|
||||
<FormLabel label={label} isRequired={isRequired} id={id} />
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
{cloneElement(children, { isRequired, 'data-required': isRequired, isError })}
|
||||
{!isError && helperText && <FormHelperText isError={isError} text={helperText} />}
|
||||
{isError && errorText && <FormHelperText isError={isError} text={errorText} />}
|
||||
</div>
|
||||
);
|
||||
};
|
2
frontend/src/components/v2/FormControl/index.tsx
Normal file
2
frontend/src/components/v2/FormControl/index.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
export type { FormControlProps, FormHelperTextProps, FormLabelProps } from './FormControl';
|
||||
export { FormControl, FormHelperText, FormLabel } from './FormControl';
|
@ -1 +1,3 @@
|
||||
export * from './Button';
|
||||
export * from './FormControl';
|
||||
export * from './Input';
|
||||
|
Reference in New Issue
Block a user