feat: adds search input with animation and minimized state

This commit is contained in:
marcelines 2023-06-06 15:02:17 +01:00
parent b6f24857c1
commit e59396e0cf
No known key found for this signature in database
GPG Key ID: 56B1E53E2A3F43C7
3 changed files with 177 additions and 80 deletions

View File

@ -1,11 +1,13 @@
import { useState } from 'react'
import { useRef, useState } from 'react'
import { Avatar, Button, Tag, Text } from '@status-im/components'
import { OpenIcon } from '@status-im/icons'
import { Avatar, Button, Input, Tag, Text } from '@status-im/components'
import { OpenIcon, SearchIcon } from '@status-im/icons'
import Link from 'next/link'
import { FilterAuthor } from './filters/filter-author'
import type { TextInput } from 'react-native'
const issues = [
{
id: 5154,
@ -93,6 +95,10 @@ const authors = [
export const TableIssues = () => {
const [activeTab, setActiveTab] = useState<'open' | 'closed'>('open')
const [issuesSearchText, setIssuesSearchText] = useState('')
const inputRef = useRef<TextInput>(null)
const [isMinimized, setIsMinimized] = useState(true)
return (
<div className="border-neutral-10 overflow-hidden rounded-2xl border">
<div className="bg-neutral-5 border-neutral-10 flex border-b p-3">
@ -119,6 +125,19 @@ export const TableIssues = () => {
</div>
<div className="flex-1">
<div className="flex items-center justify-end">
<div className="flex px-1">
<Input
placeholder="Find Author"
icon={<SearchIcon size={20} />}
size={32}
value={issuesSearchText}
onChangeText={setIssuesSearchText}
onHandleMinimized={() => setIsMinimized(!isMinimized)}
minimized={isMinimized}
direction="rtl"
ref={inputRef}
/>
</div>
<FilterAuthor authors={authors} />
<div className="pr-3">
<Button size={32} variant="ghost">

View File

@ -1,10 +1,11 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { SearchIcon } from '@status-im/icons'
import { Input } from './input'
import type { Meta, StoryObj } from '@storybook/react'
import type { TextInput } from 'react-native'
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
const meta: Meta<typeof Input> = {
@ -52,6 +53,33 @@ const InputSearch = () => {
)
}
const InputSearchMinimzed = () => {
const [value, setValue] = useState('')
const [isMinimized, setIsMinimized] = useState(true)
const inputRef = useRef<TextInput>(null)
return (
<>
<Input
onHandleMinimized={() => setIsMinimized(!isMinimized)}
placeholder="Please type something..."
value={value}
onChangeText={setValue}
icon={<SearchIcon size={20} />}
onClear={() => setValue('')}
size={32}
minimized={isMinimized}
ref={inputRef}
/>
</>
)
}
export const Minimized: Story = {
render: () => <InputSearchMinimzed />,
}
export const CompleteExample: Story = {
render: () => <InputSearch />,
}

View File

@ -6,7 +6,6 @@ import { focusableInputHOC } from '@tamagui/focusable'
import { TextInput } from 'react-native'
import { Button } from '../button'
import { IconButton } from '../icon-button'
import { Text } from '../text'
import type { GetProps } from '@tamagui/core'
@ -16,6 +15,118 @@ setupReactNative({
TextInput,
})
type Props = GetProps<typeof InputFrame> & {
button?: {
label: string
onPress: () => void
}
endLabel?: string
icon?: React.ReactElement
label?: string
onClear?: () => void
onHandleMinimized?: (isMinimized: boolean) => void
size?: 40 | 32
error?: boolean
minimized?: boolean
direction?: 'ltr' | 'rtl'
}
const _Input = (props: Props, ref: Ref<TextInput>) => {
const {
button,
color = '$neutral-50',
endLabel,
error,
icon,
label,
onClear,
size = 40,
minimized,
placeholder,
value,
direction = 'ltr',
onHandleMinimized,
...rest
} = props
return (
<Stack flexDirection={direction === 'ltr' ? 'row' : 'row-reverse'}>
{Boolean(label || endLabel) && (
<Stack flexDirection="row" justifyContent="space-between" pb={8}>
{label && (
<Text size={13} color="$neutral-50" weight="medium">
{label}
</Text>
)}
{endLabel && (
<Text size={13} color="$neutral-50">
{endLabel}
</Text>
)}
</Stack>
)}
<InputContainer
size={size}
error={error}
minimized={minimized}
onPress={event => {
event.stopPropagation()
event.preventDefault()
if (onHandleMinimized && minimized) {
onHandleMinimized(false)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore ref is not inferred correctly here
ref?.current?.focus()
}
}}
>
{icon ? (
<Stack flexShrink={0}>{cloneElement(icon, { color })}</Stack>
) : null}
<InputBase
value={value}
placeholder={minimized && !value ? '' : placeholder}
flex={1}
ref={ref}
onBlur={() => {
if (!value && onHandleMinimized && !minimized) {
onHandleMinimized?.(true)
}
}}
{...rest}
/>
<Stack flexDirection="row" alignItems="center">
{Boolean(onClear) && !!value && (
<Stack
role="button"
accessibilityRole="button"
pr={4}
onPress={onClear}
cursor="pointer"
hoverStyle={{ opacity: 0.6 }}
animation="fast"
>
<ClearIcon size={20} />
</Stack>
)}
{button && (
<Button onPress={button.onPress} variant="outline" size={24}>
{button.label}
</Button>
)}
</Stack>
</InputContainer>
</Stack>
)
}
const Input = forwardRef(_Input)
export { Input }
export type { Props as InputProps }
const InputFrame = styled(
TextInput,
{
@ -46,6 +157,8 @@ const InputFrame = styled(
}
)
const InputBase = focusableInputHOC(InputFrame)
const InputContainer = styled(Stack, {
name: 'InputContainer',
tag: 'div',
@ -54,11 +167,12 @@ const InputContainer = styled(Stack, {
gap: 8,
borderWidth: 1,
borderColor: '$neutral-20',
borderColor: '$neutral-30',
paddingHorizontal: 12,
animation: 'fast',
animation: 'medium',
width: '100%',
hoverStyle: {
borderColor: '$neutral-40',
@ -85,6 +199,15 @@ const InputContainer = styled(Stack, {
borderRadius: '$10',
},
},
minimized: {
true: {
width: 32,
paddingHorizontal: 0,
paddingLeft: 5,
cursor: 'pointer',
},
},
error: {
true: {
borderColor: '$danger-50-opa-40',
@ -99,76 +222,3 @@ const InputContainer = styled(Stack, {
},
} as const,
})
type Props = GetProps<typeof InputFrame> & {
button?: {
label: string
onPress: () => void
}
endLabel?: string
icon?: React.ReactElement
label?: string
onClear?: () => void
size?: 40 | 32
error?: boolean
}
const InputBase = focusableInputHOC(InputFrame)
const _Input = (props: Props, ref: Ref<HTMLDivElement>) => {
const {
button,
color = '$neutral-50',
endLabel,
error,
icon,
label,
onClear,
size = 40,
value,
...rest
} = props
return (
<Stack>
{Boolean(label || endLabel) && (
<Stack flexDirection="row" justifyContent="space-between" pb={8}>
{label && (
<Text size={13} color="$neutral-50" weight="medium">
{label}
</Text>
)}
{endLabel && (
<Text size={13} color="$neutral-50">
{endLabel}
</Text>
)}
</Stack>
)}
<InputContainer size={size} ref={ref} error={error}>
{icon ? cloneElement(icon, { color }) : null}
<InputBase value={value} {...rest} flex={1} />
<Stack flexDirection="row" alignItems="center">
{Boolean(onClear) && !!value && (
<Stack pr={4}>
<IconButton
variant="ghost"
icon={<ClearIcon size={20} />}
onPress={onClear}
/>
</Stack>
)}
{button && (
<Button onPress={button.onPress} variant="outline" size={24}>
{button.label}
</Button>
)}
</Stack>
</InputContainer>
</Stack>
)
}
const Input = forwardRef(_Input)
export { Input }
export type { Props as InputProps }