feat: adds all input variants and stars building filters for table issues
This commit is contained in:
parent
8ec5bf3bc5
commit
20f9f11fbf
|
@ -1,41 +1,182 @@
|
|||
import { Avatar, Button, Tag, Text } from '@status-im/components'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Avatar, Button, Input, Tag, Text } from '@status-im/components'
|
||||
import { DropdownMenu } from '@status-im/components/src/dropdown-menu'
|
||||
import { DropdownIcon, OpenIcon, SearchIcon } from '@status-im/icons'
|
||||
import Link from 'next/link'
|
||||
|
||||
const issues = [
|
||||
{
|
||||
id: 5154,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
status: 'open',
|
||||
},
|
||||
{
|
||||
id: 5155,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
status: 'open',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
status: 'open',
|
||||
},
|
||||
{
|
||||
id: 4324,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
status: 'open',
|
||||
},
|
||||
{
|
||||
id: 876,
|
||||
id: 134,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'Open',
|
||||
status: 'closed',
|
||||
},
|
||||
{
|
||||
id: 999,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'closed',
|
||||
},
|
||||
{
|
||||
id: 873,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'open',
|
||||
},
|
||||
{
|
||||
id: 123,
|
||||
title: 'Add support for encrypted communities',
|
||||
status: 'open',
|
||||
},
|
||||
]
|
||||
|
||||
const authors = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Tobias',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Arnold',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Alisher',
|
||||
avatar:
|
||||
'https://images.unsplash.com/photo-1518020382113-a7e8fc38eac9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=500&h=500&q=80',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'marcelines',
|
||||
avatar: 'https://avatars.githubusercontent.com/u/29401404?v=4',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'prichodko',
|
||||
avatar: 'https://avatars.githubusercontent.com/u/14926950?v=4',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'felicio',
|
||||
avatar: 'https://avatars.githubusercontent.com/u/13265126?v=4',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'jkbktl',
|
||||
avatar: 'https://avatars.githubusercontent.com/u/520927?v=4',
|
||||
},
|
||||
]
|
||||
|
||||
export const TableIssues = () => {
|
||||
const [activeTab, setActiveTab] = useState<'open' | 'closed'>('open')
|
||||
|
||||
const [authorFilterText, setAuthorFilterText] = useState('')
|
||||
const filteredAuthors = authors.filter(author =>
|
||||
author.name.toLowerCase().includes(authorFilterText.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="border-neutral-10 overflow-hidden rounded-2xl border">
|
||||
<div className="bg-neutral-5 border-neutral-10 border-b p-3">
|
||||
<Text size={15} weight="medium">
|
||||
<div className="bg-neutral-5 border-neutral-10 flex border-b p-3">
|
||||
<div className="flex">
|
||||
<div className="pr-3">
|
||||
<Button
|
||||
size={32}
|
||||
variant={activeTab === 'open' ? 'darkGrey' : 'grey'}
|
||||
onPress={() => setActiveTab('open')}
|
||||
icon={<OpenIcon size={20} />}
|
||||
>
|
||||
784 Open
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="pr-3">
|
||||
<Button
|
||||
size={32}
|
||||
variant={activeTab === 'closed' ? 'darkGrey' : 'grey'}
|
||||
onPress={() => setActiveTab('closed')}
|
||||
>
|
||||
1012 closed
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="pr-3">
|
||||
<DropdownMenu>
|
||||
<Button
|
||||
size={32}
|
||||
variant="outline"
|
||||
iconAfter={<DropdownIcon size={20} />}
|
||||
>
|
||||
Author
|
||||
</Button>
|
||||
<DropdownMenu.Content sideOffset={10} align="end">
|
||||
<div className="p-2 px-1">
|
||||
<Input
|
||||
placeholder="Find Author"
|
||||
icon={<SearchIcon size={20} />}
|
||||
size={32}
|
||||
value={authorFilterText}
|
||||
onChangeText={setAuthorFilterText}
|
||||
/>
|
||||
</div>
|
||||
{filteredAuthors.map(author => (
|
||||
<DropdownMenu.Item
|
||||
key={author.id}
|
||||
icon={
|
||||
<Avatar
|
||||
name={author.name}
|
||||
src={author.avatar}
|
||||
size={16}
|
||||
type="user"
|
||||
/>
|
||||
}
|
||||
label={author.name}
|
||||
onSelect={() => alert('Author: ' + author.name)}
|
||||
/>
|
||||
))}
|
||||
{filteredAuthors.length === 0 && (
|
||||
<div className="p-2 py-1">
|
||||
<Text size={13}> No authors found</Text>
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="pr-3">
|
||||
<Button size={32} variant="ghost">
|
||||
Filter
|
||||
</Button>
|
||||
</div>
|
||||
<div className="pr-3">
|
||||
<Button size={32} variant="ghost">
|
||||
Sort
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divide-neutral-10 divide-y">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { cloneElement, forwardRef } from 'react'
|
||||
|
||||
import { Stack, styled } from 'tamagui'
|
||||
import { Stack, styled } from '@tamagui/core'
|
||||
|
||||
import { usePressableColors } from '../hooks/use-pressable-colors'
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { SearchIcon } from '@status-im/icons'
|
||||
|
||||
import { Input } from './input'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
@ -18,4 +22,46 @@ export const Primary: Story = {
|
|||
},
|
||||
}
|
||||
|
||||
const InputSearch = () => {
|
||||
const [value, setValue] = useState('')
|
||||
|
||||
// limit input to 100 characters just for demo purposes
|
||||
useEffect(() => {
|
||||
if (value.length > 100) {
|
||||
setValue(value.slice(0, 100))
|
||||
}
|
||||
}, [value])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Please type something..."
|
||||
value={value}
|
||||
onChangeText={setValue}
|
||||
icon={<SearchIcon size={20} />}
|
||||
onClear={() => setValue('')}
|
||||
label="Search"
|
||||
endLabel={`${value.length}/100`}
|
||||
size={40}
|
||||
button={{
|
||||
label: 'Confirm',
|
||||
onPress: () => alert('Confirmed!'),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const CompleteExample: Story = {
|
||||
render: () => <InputSearch />,
|
||||
}
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
placeholder: 'Type something...',
|
||||
error: true,
|
||||
// children: 'Click me',
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
|
|
@ -1,62 +1,174 @@
|
|||
import { setupReactNative, styled } from '@tamagui/core'
|
||||
import { cloneElement, forwardRef } from 'react'
|
||||
|
||||
import { ClearIcon } from '@status-im/icons'
|
||||
import { setupReactNative, Stack, styled } from '@tamagui/core'
|
||||
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'
|
||||
import type { Ref } from 'react'
|
||||
|
||||
setupReactNative({
|
||||
TextInput,
|
||||
})
|
||||
|
||||
export const InputFrame = styled(
|
||||
const InputFrame = styled(
|
||||
TextInput,
|
||||
{
|
||||
tag: 'input',
|
||||
name: 'Input',
|
||||
|
||||
borderWidth: 1,
|
||||
outlineWidth: 0,
|
||||
borderColor: 'rgba(0, 200, 0, 1)',
|
||||
borderColor: '$neutral-20',
|
||||
|
||||
paddingHorizontal: 30,
|
||||
color: 'hsla(218, 51%, 7%, 1)',
|
||||
color: '$neutral-100',
|
||||
placeholderTextColor: '$placeHolderColor',
|
||||
|
||||
backgroundColor: 'transparent',
|
||||
|
||||
height: 32,
|
||||
borderRadius: '$12',
|
||||
|
||||
animation: 'fast',
|
||||
|
||||
// this fixes a flex bug where it overflows container
|
||||
minWidth: 0,
|
||||
|
||||
hoverStyle: {
|
||||
borderColor: '$beigeHover',
|
||||
},
|
||||
|
||||
focusStyle: {
|
||||
borderColor: '$blueHover',
|
||||
},
|
||||
|
||||
variants: {
|
||||
blurred: {
|
||||
true: {
|
||||
placeholderTextColor: '$placeHolderColorBlurred',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
defaultVariants: {
|
||||
blurred: '$false',
|
||||
},
|
||||
} as const,
|
||||
},
|
||||
{
|
||||
isInput: true,
|
||||
}
|
||||
)
|
||||
|
||||
export type InputProps = GetProps<typeof InputFrame>
|
||||
const InputContainer = styled(Stack, {
|
||||
name: 'InputContainer',
|
||||
tag: 'div',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
|
||||
export const Input = focusableInputHOC(InputFrame)
|
||||
borderWidth: 1,
|
||||
borderColor: '$neutral-20',
|
||||
|
||||
paddingHorizontal: 12,
|
||||
|
||||
animation: 'fast',
|
||||
|
||||
hoverStyle: {
|
||||
borderColor: '$neutral-40',
|
||||
},
|
||||
|
||||
focusStyle: {
|
||||
borderColor: '$neutral-40',
|
||||
},
|
||||
|
||||
pressStyle: {
|
||||
borderColor: '$neutral-40',
|
||||
},
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
40: {
|
||||
height: 40,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: '$12',
|
||||
},
|
||||
32: {
|
||||
height: 32,
|
||||
paddingHorizontal: 8,
|
||||
borderRadius: '$10',
|
||||
},
|
||||
},
|
||||
error: {
|
||||
true: {
|
||||
borderColor: '$danger-50-opa-40',
|
||||
},
|
||||
},
|
||||
|
||||
disabled: {
|
||||
true: {
|
||||
opacity: 0.3,
|
||||
cursor: 'default',
|
||||
},
|
||||
},
|
||||
} 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 }
|
||||
|
|
Loading…
Reference in New Issue