Skip to content

Commit

Permalink
feat(input): add clear function
Browse files Browse the repository at this point in the history
  • Loading branch information
andresz1 committed Jul 14, 2023
1 parent 2471dea commit 838b177
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 10 deletions.
15 changes: 13 additions & 2 deletions packages/components/input/src/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useFormFieldControl } from '@spark-ui/form-field'
import { Slot } from '@spark-ui/slot'
import { ComponentPropsWithoutRef, forwardRef } from 'react'
import { ChangeEventHandler, ComponentPropsWithoutRef, forwardRef } from 'react'

import { inputStyles } from './Input.styles'
import { useInputGroup } from './InputGroupContext'
Expand All @@ -10,7 +10,7 @@ export interface InputProps extends ComponentPropsWithoutRef<'input'> {
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, asChild, ...others }, ref) => {
({ className, asChild, onChange, ...others }, ref) => {
const field = useFormFieldControl()
const group = useInputGroup()

Expand All @@ -20,6 +20,16 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
const Component = asChild ? Slot : 'input'
const state = field.state ?? group.state

const handleChange: ChangeEventHandler<HTMLInputElement> = event => {
if (onChange) {
onChange(event)
}

if (group.onChange) {
group.onChange(event.target.value)
}
}

return (
<Component
ref={ref}
Expand All @@ -37,6 +47,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
})}
disabled={group.disabled}
required={isRequired}
onChange={handleChange}
aria-describedby={description}
aria-invalid={isInvalid}
{...others}
Expand Down
13 changes: 9 additions & 4 deletions packages/components/input/src/InputClearButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import { Icon } from '@spark-ui/icon'
import { IconButton } from '@spark-ui/icon-button'
import { DeleteOutline } from '@spark-ui/icons/dist/icons/DeleteOutline'
import { cx } from 'class-variance-authority'
import { forwardRef } from 'react'
import { forwardRef, MouseEventHandler } from 'react'

import { useInputGroup } from './InputGroupContext'

export interface InputClearButtonProps {
'aria-label': string
}

export const InputClearButton = forwardRef<HTMLDivElement, InputClearButtonProps>(
({ 'aria-label': ariaLabel, ...others }, ref) => {
const group = useInputGroup()
// const isControlled = true

// const handleClear = () => {
Expand All @@ -20,6 +23,10 @@ export const InputClearButton = forwardRef<HTMLDivElement, InputClearButtonProps
// }
// }

const handleClick: MouseEventHandler<HTMLButtonElement> = () => {
group.onClear()
}

return (
<div ref={ref} className={cx('pointer-events-auto')} {...others}>
<IconButton
Expand All @@ -28,9 +35,7 @@ export const InputClearButton = forwardRef<HTMLDivElement, InputClearButtonProps
design="ghost"
size="sm"
aria-label={ariaLabel}
onClick={e => {
e.preventDefault()
}}
onClick={handleClick}
>
<Icon size="sm">
<DeleteOutline />
Expand Down
25 changes: 21 additions & 4 deletions packages/components/input/src/InputGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/* eslint-disable complexity */

import { useFormFieldControl } from '@spark-ui/form-field'
import { useCombinedState } from '@spark-ui/use-combined-state'
import { cva } from 'class-variance-authority'
import {
Children,
cloneElement,
ComponentPropsWithoutRef,
FC,
forwardRef,
isValidElement,
PropsWithChildren,
ReactElement,
useMemo,
useRef,
} from 'react'

import { InputGroupContext } from './InputGroupContext'
Expand Down Expand Up @@ -46,21 +51,32 @@ export const InputGroup = forwardRef<HTMLDivElement, PropsWithChildren<InputGrou
const clearButton = findElement('InputGroup.ClearButton')
const trailingIcon = state ? <InputStateIndicator /> : findElement('InputGroup.TrailingIcon')
const trailingAddon = findElement('InputGroup.TrailingAddon')
const inputRef = useRef<HTMLInputElement>(null!)
const [value, onChange] = useCombinedState(input?.props?.value, input?.props?.defaultValue)

const hasLeadingAddon = !!leadingAddon
const hasTrailingAddon = !!trailingAddon
const hasLeadingIcon = !!leadingIcon
const hasTrailingIcon = !!trailingIcon || !!state
const hasClearButton = value && !!clearButton

const contextValue = useMemo(() => {
const onClear = () => {
inputRef.current.value = ''
inputRef.current.focus()
onChange('')
}

return {
state,
disabled: !!disabled,
hasLeadingIcon,
hasTrailingIcon,
hasLeadingAddon,
hasTrailingAddon,
hasClearButton: !!clearButton,
hasClearButton,
onChange,
onClear,
}
}, [
state,
Expand All @@ -69,7 +85,8 @@ export const InputGroup = forwardRef<HTMLDivElement, PropsWithChildren<InputGrou
hasTrailingIcon,
hasLeadingAddon,
hasTrailingAddon,
clearButton,
hasClearButton,
onChange,
])

return (
Expand All @@ -78,10 +95,10 @@ export const InputGroup = forwardRef<HTMLDivElement, PropsWithChildren<InputGrou
{hasLeadingAddon && leadingAddon}

<div className="relative w-full">
{input}
{input && cloneElement(input, { ref: inputRef })}
{leadingIcon}
<div className="pointer-events-none absolute right-lg top-1/2 flex -translate-y-1/2 items-center gap-md">
{clearButton}
{hasClearButton && clearButton}
{trailingIcon}
</div>
</div>
Expand Down

0 comments on commit 838b177

Please sign in to comment.