-
Notifications
You must be signed in to change notification settings - Fork 87
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
feat(design-system): add Input, Textarea component and stories #2246
Changes from all commits
08f00bf
765c5d4
3b56a61
12a69cf
57b542f
4512c81
ca87189
24d8651
ec96f99
15bc516
bf6312d
54b64c1
5b844e5
85b7c4b
661966b
0c6439d
44cd381
02f088b
52ba76b
b746b08
673cb11
58a269e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,4 +62,5 @@ tmp/ | |
|
||
# Tests | ||
# ======= | ||
coverage/ | ||
coverage/ | ||
frontend/.eslintcache |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,19 @@ | ||
// Retrieved from https://reactsvgicons.com/boxicons | ||
import { forwardRef } from 'react' | ||
|
||
export const BxsCheckCircle = ( | ||
props: React.SVGProps<SVGSVGElement>, | ||
): JSX.Element => { | ||
export const BxsCheckCircle = forwardRef< | ||
SVGSVGElement, | ||
React.SVGProps<SVGSVGElement> | ||
>((props, ref) => { | ||
return ( | ||
<svg | ||
viewBox="0 0 24 24" | ||
fill="currentColor" | ||
height="1em" | ||
width="1em" | ||
{...props} | ||
ref={ref} | ||
> | ||
<path d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zm-1.999 14.413l-3.713-3.705L7.7 11.292l2.299 2.295 5.294-5.294 1.414 1.414-6.706 6.706z" /> | ||
</svg> | ||
) | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { useForm } from 'react-hook-form' | ||
import { | ||
FormControl, | ||
FormErrorMessage, | ||
FormLabel, | ||
} from '@chakra-ui/form-control' | ||
import { Meta, Story } from '@storybook/react' | ||
|
||
import Button from '../Button' | ||
|
||
import { Input, InputProps } from './Input' | ||
|
||
export default { | ||
title: 'Components/Input', | ||
component: Input, | ||
decorators: [], | ||
} as Meta | ||
|
||
const Template: Story<InputProps> = (args) => <Input {...args} /> | ||
export const Default = Template.bind({}) | ||
Default.args = { | ||
placeholder: 'Test placeholder', | ||
} | ||
|
||
export const Prefilled = Template.bind({}) | ||
Prefilled.args = { | ||
placeholder: 'Test placeholder', | ||
defaultValue: 'Prefilled field', | ||
isPrefilled: true, | ||
} | ||
|
||
export const Error = Template.bind({}) | ||
Error.args = { | ||
isInvalid: true, | ||
placeholder: 'Test placeholder', | ||
defaultValue: 'Field error', | ||
} | ||
|
||
export const Success = Template.bind({}) | ||
Success.args = { | ||
isInvalid: false, | ||
isSuccess: true, | ||
placeholder: 'Test placeholder', | ||
defaultValue: 'Field success', | ||
} | ||
export const Disabled = Template.bind({}) | ||
Disabled.args = { | ||
defaultValue: 'Some text', | ||
placeholder: 'Test placeholder', | ||
isDisabled: true, | ||
} | ||
|
||
export const Playground: Story = ({ | ||
name, | ||
label, | ||
isDisabled, | ||
isRequired, | ||
...args | ||
}) => { | ||
const { | ||
handleSubmit, | ||
register, | ||
formState: { errors }, | ||
} = useForm() | ||
const onSubmit = (data: unknown) => alert(JSON.stringify(data)) | ||
|
||
return ( | ||
<form onSubmit={handleSubmit(onSubmit)} noValidate> | ||
<FormControl | ||
isRequired={isRequired} | ||
isDisabled={isDisabled} | ||
isInvalid={!!errors[name]} | ||
mb={6} | ||
> | ||
<FormLabel htmlFor={name}>{label}</FormLabel> | ||
<Input | ||
{...args} | ||
{...register(name, { | ||
required: isRequired | ||
? { value: true, message: 'Required field' } | ||
: false, | ||
})} | ||
/> | ||
<FormErrorMessage> | ||
{errors[name] && errors[name].message} | ||
</FormErrorMessage> | ||
</FormControl> | ||
<Button variant="solid" type="submit"> | ||
Submit | ||
</Button> | ||
</form> | ||
) | ||
} | ||
Playground.args = { | ||
name: 'Test playground input', | ||
label: 'Field label', | ||
placeholder: 'Fill in this field', | ||
isRequired: true, | ||
isDisabled: false, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { | ||
forwardRef, | ||
Icon, | ||
Input as ChakraInput, | ||
InputGroup, | ||
InputProps as ChakraInputProps, | ||
InputRightElement, | ||
useMultiStyleConfig, | ||
} from '@chakra-ui/react' | ||
import { omit } from '@chakra-ui/utils' | ||
|
||
import { BxsCheckCircle } from '~assets/icons/BxsCheckCircle' | ||
|
||
export interface InputProps extends ChakraInputProps { | ||
/** | ||
* Whether the input is in a prefilled state. | ||
*/ | ||
isPrefilled?: boolean | ||
/** | ||
* Whether the input is in a success state. | ||
*/ | ||
isSuccess?: boolean | ||
} | ||
|
||
export const Input = forwardRef<InputProps, 'input'>((props, ref) => { | ||
const inputStyles = useMultiStyleConfig('Input', props) | ||
|
||
// Omit extra props so they will not be passed into the DOM and trigger | ||
// React warnings. | ||
const inputProps = omit(props, ['isSuccess', 'isPrefilled']) | ||
|
||
// Return normal input component if not success state. | ||
if (!props.isSuccess) { | ||
return <ChakraInput ref={ref} {...inputProps} sx={inputStyles.field} /> | ||
} | ||
|
||
return ( | ||
// InputGroup is required for InputRightElement to retrieve the correct | ||
// style props. Will crash if not included. | ||
<InputGroup> | ||
<ChakraInput ref={ref} {...inputProps} sx={inputStyles.field} /> | ||
<InputRightElement sx={inputStyles.success}> | ||
<Icon as={BxsCheckCircle} /> | ||
</InputRightElement> | ||
</InputGroup> | ||
Comment on lines
+40
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could i clarify on why we chose to do this? Testing on chakra's website seems to suggest that there are styling conflicts and consumers of our component might mistakenly think that this is a base |
||
) | ||
}) | ||
|
||
/** | ||
* This is used in by Chakra's `InputGroup` component to remove border radii | ||
* when paired with `InputLeftAddon` or `InputRightAddon`. | ||
* | ||
* See https://github.com/chakra-ui/chakra-ui/blob/main/packages/input/src/input.tsx#L70 and | ||
* https://github.com/chakra-ui/chakra-ui/blob/main/packages/input/src/input-group.tsx#L58. | ||
*/ | ||
Input.id = 'Input' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export type { InputProps } from './Input' | ||
export { Input as default } from './Input' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { useForm } from 'react-hook-form' | ||
import { | ||
FormControl, | ||
FormErrorMessage, | ||
FormLabel, | ||
} from '@chakra-ui/form-control' | ||
import { Meta, Story } from '@storybook/react' | ||
|
||
import Button from '../Button' | ||
|
||
import { Textarea, TextareaProps } from './Textarea' | ||
|
||
export default { | ||
title: 'Components/Textarea', | ||
component: Textarea, | ||
decorators: [], | ||
} as Meta | ||
|
||
const Template: Story<TextareaProps> = (args) => <Textarea {...args} /> | ||
export const Default = Template.bind({}) | ||
Default.args = { | ||
placeholder: 'Test placeholder', | ||
} | ||
|
||
export const Prefilled = Template.bind({}) | ||
Prefilled.args = { | ||
placeholder: 'Test placeholder', | ||
defaultValue: 'Prefilled field', | ||
isPrefilled: true, | ||
} | ||
|
||
export const Error = Template.bind({}) | ||
Error.args = { | ||
isInvalid: true, | ||
placeholder: 'Test placeholder', | ||
defaultValue: 'Field error', | ||
} | ||
|
||
export const Success = Template.bind({}) | ||
Success.args = { | ||
isInvalid: false, | ||
isSuccess: true, | ||
placeholder: 'Test placeholder', | ||
defaultValue: 'Field success', | ||
} | ||
|
||
export const Disabled = Template.bind({}) | ||
Disabled.args = { | ||
defaultValue: 'Some text', | ||
placeholder: 'Test placeholder', | ||
isDisabled: true, | ||
} | ||
|
||
export const Playground: Story = ({ | ||
name, | ||
label, | ||
isDisabled, | ||
isRequired, | ||
...args | ||
}) => { | ||
const { | ||
handleSubmit, | ||
register, | ||
formState: { errors }, | ||
} = useForm() | ||
const onSubmit = (data: unknown) => alert(JSON.stringify(data)) | ||
|
||
return ( | ||
<form onSubmit={handleSubmit(onSubmit)} noValidate> | ||
<FormControl | ||
isRequired={isRequired} | ||
isDisabled={isDisabled} | ||
isInvalid={!!errors[name]} | ||
mb={6} | ||
> | ||
<FormLabel htmlFor={name}>{label}</FormLabel> | ||
<Textarea | ||
{...args} | ||
{...register(name, { | ||
required: isRequired | ||
? { value: true, message: 'Required field' } | ||
: false, | ||
})} | ||
/> | ||
<FormErrorMessage> | ||
{errors[name] && errors[name].message} | ||
</FormErrorMessage> | ||
</FormControl> | ||
<Button variant="solid" type="submit"> | ||
Submit | ||
</Button> | ||
</form> | ||
) | ||
} | ||
Playground.args = { | ||
name: 'Test playground Textarea', | ||
label: 'Field label', | ||
placeholder: 'Fill in this field', | ||
isRequired: true, | ||
isDisabled: false, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry why are we doing this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per the comments in the non-success case,
InputGroup
is required forInputRightElement
to retrieve the correct style props and will crash if not included.However, since we just display the normal input on success, we do not need that special requirement and can just return the input as per normal