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

fix: Transfers responsibility of InputText's main attributes and callbacks to the renderer #163

Merged
merged 5 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- priceCurrency field on SEO meta data ([#161](https://github.com/vtex-sites/nextjs.store/pull/161))

- Transfers responsibility of `InputText`'s main attributes and callbacks to the renderer ([#163](https://github.com/vtex-sites/nextjs.store/pull/163))

## [22.26.0.beta] - 2022-07-01

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function RegionInput({ closeModal }: Props) {
const inputRef = useRef<HTMLInputElement>(null)
const { setSession, isValidating, ...session } = useSession()
const [errorMessage, setErrorMessage] = useState<string>('')
const [input, setInput] = useState<string>('')

const handleSubmit = async () => {
const value = inputRef.current?.value
Expand Down Expand Up @@ -43,10 +44,16 @@ function RegionInput({ closeModal }: Props) {
<InputText
inputRef={inputRef}
id="postal-code-input"
errorMessage={errorMessage}
error={errorMessage}
label="Zip Code"
actionable
value={input}
onInput={(e) => {
errorMessage !== '' && setErrorMessage('')
setInput(e.currentTarget.value)
}}
onSubmit={handleSubmit}
onClear={() => setInput('')}
/>
</div>
)
Expand Down
41 changes: 32 additions & 9 deletions src/components/ui/InputText/InputText.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useState } from 'react'

import type { InputTextProps } from '.'
import InputText from '.'

Expand All @@ -13,6 +15,9 @@ const story = {
id: {
type: { name: 'string', required: true },
},
actionable: {
type: { name: 'boolean' },
},
},
}

Expand All @@ -22,38 +27,56 @@ const Template = ({ ...args }: InputTextProps) => (
</div>
)

const TemplateActionable = ({ ...args }: InputTextProps) => {
const [input, setInput] = useState('')
const [error, setError] = useState<string>()

return (
<div style={{ width: 400 }}>
<InputText
actionable
error={error}
value={input}
onSubmit={() => setError('Invalid Postal Code')}
onClear={() => {
setError(undefined)
setInput('')
}}
onChange={(e) => {
error && setError(undefined)
setInput(e.target.value)
}}
{...args}
/>
</div>
)
}

export const Default = Template.bind({})
export const HasError = Template.bind({})
export const Actionable = Template.bind({})
export const Actionable = TemplateActionable.bind({})
export const Disabled = Template.bind({})

Default.args = {
id: 'default-input-text',
label: 'Email',
errorMessage: 'Please, add a valid email',
disabled: false,
}

HasError.args = {
id: 'error-input-text',
label: 'Email',
value: 'invalid@mail',
errorMessage: 'Please, add a valid email',
disabled: false,
error: 'Please, add a valid email',
}

Actionable.args = {
id: 'actionable-input-text',
label: 'Postal Code',
actionable: true,
errorMessage: 'Invalid Postal Code',
disabled: false,
}

Disabled.args = {
id: 'disabled-input-text',
label: 'Email',
errorMessage: 'Please, add a valid email',
disabled: true,
}

Expand Down
75 changes: 31 additions & 44 deletions src/components/ui/InputText/InputText.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Input as UIInput, Label as UILabel } from '@faststore/ui'
import type { MutableRefObject } from 'react'
import { useEffect, useState } from 'react'
import type { InputProps } from '@faststore/ui'

import Button from 'src/components/ui/Button'
Expand All @@ -19,7 +18,7 @@ type DefaultProps = {
/**
* The error message is displayed when an error occurs.
*/
errorMessage?: string
error?: string
/**
* Component's ref.
*/
Expand All @@ -31,99 +30,87 @@ type DefaultProps = {
}

type ActionableInputText =
| {
actionable?: never
onSubmit?: never
onClear?: never
buttonActionText?: string
}
| {
/**
* Adds a Button to the component.
*/
actionable: true
/**
* Callback function when button is clicked.
* Callback function when button is clicked. Required for actionable input.
*/
onSubmit: (value: string) => void
onSubmit: () => void
/**
* Callback function when clear button is clicked. Required for actionable input.
*/
onClear: () => void
/**
* The text displayed on the Button. Suggestion: maximum 9 characters.
*/
buttonActionText?: string
}
| {
actionable?: false
onSubmit?: never
buttonActionText?: string
}

export type InputTextProps = DefaultProps &
Omit<InputProps, 'disabled'> &
Omit<InputProps, 'disabled' | 'onSubmit'> &
ActionableInputText

const InputText = ({
id,
label,
type = 'text',
errorMessage,
error,
actionable,
buttonActionText = 'Apply',
onSubmit,
onClear,
placeholder = ' ', // initializes with an empty space to style float label using `placeholder-shown`
value,
inputRef,
disabled,
value,
...otherProps
}: InputTextProps) => {
const [inputValue, setInputValue] = useState<string>((value as string) ?? '')
const [hasError, setHasError] = useState<boolean>(!!errorMessage)

useEffect(() => {
errorMessage && setHasError(true)
}, [errorMessage])

const onClear = () => {
setInputValue('')
inputRef?.current?.focus()
}
const shouldDisplayError = !disabled && error && error !== ''
const shouldDisplayButton = actionable && !disabled && value !== ''

return (
<div
data-fs-input-text
data-fs-input-text-error={hasError && inputValue !== ''}
data-fs-input-text-actionable={actionable}
data-fs-input-text-error={error && error !== ''}
>
<UIInput
type={type}
id={id}
type={type}
value={value}
ref={inputRef}
placeholder={placeholder}
value={inputValue}
disabled={disabled}
onInput={(e) => {
hasError && setHasError(false)
setInputValue(e.currentTarget.value)
}}
placeholder={placeholder}
{...otherProps}
/>
<UILabel htmlFor={id}>{label}</UILabel>

{actionable &&
!disabled &&
inputValue !== '' &&
(hasError ? (
{shouldDisplayButton &&
(error ? (
<ButtonIcon
data-fs-button-size="small"
aria-label="Clear Field"
icon={<Icon name="XCircle" width={20} height={20} />}
onClick={onClear}
onClick={() => {
onClear?.()
inputRef?.current?.focus()
}}
/>
) : (
<Button
variant="tertiary"
onClick={() => onSubmit(inputValue)}
size="small"
>
<Button variant="tertiary" size="small" onClick={onSubmit}>
{buttonActionText}
</Button>
))}
{hasError && !disabled && inputValue !== '' && (
<span data-fs-input-text-message>{errorMessage}</span>
)}
{shouldDisplayError && <span data-fs-input-text-message>{error}</span>}
</div>
)
}
Expand Down