Skip to content
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

Merged
merged 22 commits into from
Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
08f00bf
feat(theme): WIP update Input themes
karrui Jun 16, 2021
765c5d4
wip(design-system): add initial Input component and stories
karrui Jun 16, 2021
3b56a61
feat(Input): update invalid field border width
karrui Jun 16, 2021
12a69cf
feat: add success state for input and story
karrui Jun 24, 2021
57b542f
ref: add JSDoc and inline success icon component
karrui Jun 24, 2021
4512c81
ref: update typings of theme for ease of referencing inports
karrui Jun 24, 2021
ca87189
chore: add react-textarea-autosize package
karrui Jun 24, 2021
24d8651
feat: add Textarea theme extending from Input styling
karrui Jun 24, 2021
ec96f99
feat: add Textarea component
karrui Jun 24, 2021
15bc516
feat(story): add Textarea stories
karrui Jun 24, 2021
bf6312d
feat: add Textarea's index.ts
karrui Jun 24, 2021
54b64c1
feat: add `id` param to Input component
karrui Jun 28, 2021
5b844e5
style: update input theme to accept isSuccess and isPrefilled as props
karrui Jun 30, 2021
85b7c4b
feat: allow prefill and success states for textarea
karrui Jun 30, 2021
661966b
fix: add placeholder to remove a11y warning on input field
karrui Jun 30, 2021
0c6439d
feat: move success icon theming to theme file
karrui Jun 30, 2021
44cd381
fix: remove extra theming props before passing into DOM
karrui Jun 30, 2021
02f088b
fix: correctly export InputProps type
karrui Jun 30, 2021
52ba76b
fix(Textarea): remove extra theming props before passing into DOM
karrui Jun 30, 2021
b746b08
Merge branch 'form-v2/develop' into form-v2/input-component
karrui Jul 5, 2021
673cb11
Merge branch 'form-v2/develop' into form-v2/input-component
karrui Jul 7, 2021
58a269e
fix(form-v2): dependabot config
karrui Jul 7, 2021
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
26 changes: 13 additions & 13 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ updates:
prefix-development: 'chore'
include: 'scope'
- package-ecosystem: 'npm' # See documentation for possible values
directory: '/frontend' # Location of package manifests
schedule:
interval: 'daily'
time: '01:00'
timezone: 'Asia/Singapore'
commit-message:
prefix: 'fix'
prefix-development: 'chore'
include: 'scope'
labels:
- "react"
- "dependencies"
target-branch: "form-v2/develop"
directory: '/frontend' # Location of package manifests
schedule:
interval: 'daily'
time: '01:00'
timezone: 'Asia/Singapore'
commit-message:
prefix: 'fix'
prefix-development: 'chore'
include: 'scope'
labels:
- 'react'
- 'dependencies'
target-branch: 'form-v2/develop'
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ tmp/

# Tests
# =======
coverage/
coverage/
frontend/.eslintcache
9 changes: 2 additions & 7 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"react-hook-form": "^7.10.1",
"react-icons": "^3.0.0",
"react-router-dom": "^5.2.0",
"react-textarea-autosize": "^8.3.3",
"typescript": "^4.3.5",
"web-vitals": "^0.2.2"
},
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/assets/icons/BxsCheckCircle.tsx
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>
)
}
})
100 changes: 100 additions & 0 deletions frontend/src/components/Input/Input.stories.tsx
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,
}
56 changes: 56 additions & 0 deletions frontend/src/components/Input/Input.tsx
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} />
}
Comment on lines +33 to +35
Copy link
Contributor

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?

Copy link
Contributor Author

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 for InputRightElement 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


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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 Input in success state and compose it as part of an InputGroup only to discover that it's actually not.

)
})

/**
* 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'
2 changes: 2 additions & 0 deletions frontend/src/components/Input/index.ts
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'
101 changes: 101 additions & 0 deletions frontend/src/components/Textarea/Textarea.stories.tsx
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,
}
Loading