feat: add remaining filters and some polishment with breakpoint table issues component

This commit is contained in:
marcelines 2023-06-07 13:54:54 +01:00
parent dc138f8715
commit ddcddbac35
No known key found for this signature in database
GPG Key ID: 56B1E53E2A3F43C7
11 changed files with 306 additions and 60 deletions

View File

@ -37,7 +37,7 @@ const SideBar = (props: Props) => {
return (
<div className="border-neutral-10 border-r p-5">
<aside className=" sticky top-5 min-w-[320px]">
<aside className=" sticky top-5 w-[280px]">
<Accordion.Root
type="single"
collapsible

View File

@ -11,8 +11,12 @@ function isColorTokens(
const ColorCircle = ({
color: colorFromProps,
opacity = 40,
size = 16,
}: {
color?: ColorTokens | `#${string}`
color: ColorTokens | `#${string}`
opacity?: number
size?: number
}) => {
if (!colorFromProps) {
return null
@ -32,9 +36,9 @@ const ColorCircle = ({
<div
className="rounded-full"
style={{
width: 16,
height: 16,
backgroundColor: `color-mix(in srgb, ${color} ${40}%, transparent)`,
width: size,
height: size,
backgroundColor: `color-mix(in srgb, ${color} ${opacity}%, transparent)`,
border: `1px solid ${color}`,
}}
/>

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import { cloneElement, useState } from 'react'
import { Avatar, Button, Input, Text } from '@status-im/components'
import { DropdownMenu } from '@status-im/components/src/dropdown-menu'
@ -8,16 +8,39 @@ import { ColorCircle } from './components/color-circle'
import type { ColorTokens } from '@tamagui/core'
type Props = {
data: {
type Data = {
id: number
name: string
avatar?: string
avatar?: string | React.ReactElement
color?: ColorTokens | `#${string}`
}[]
}
type Props = {
data: Data[]
label: string
noPadding?: boolean
}
const isAvatar = (value: unknown): value is string => {
return typeof value === 'string' && value !== null
}
const RenderIcon = (props: Data) => {
if (props.color) {
return <ColorCircle color={props.color} />
}
if (!props.avatar) {
return <></>
}
if (isAvatar(props.avatar)) {
return <Avatar src={props.avatar} size={16} name={props.name} type="user" />
}
return cloneElement(props.avatar) || <></>
}
const FilterWithCheckboxes = (props: Props) => {
const { data, label, noPadding } = props
@ -32,7 +55,7 @@ const FilterWithCheckboxes = (props: Props) => {
const [isOpen, setIsOpen] = useState(false)
return (
<div className={noPadding ? '' : 'pr-3'}>
<div className={noPadding ? '' : 'pr-2'}>
<DropdownMenu onOpenChange={() => setIsOpen(!isOpen)}>
<Button
size={32}
@ -63,18 +86,7 @@ const FilterWithCheckboxes = (props: Props) => {
return (
<DropdownMenu.CheckboxItem
key={filtered.id}
icon={
filtered.color ? (
<ColorCircle color={filtered.color} />
) : (
<Avatar
name={filtered.name}
src={filtered.avatar}
size={16}
type="user"
/>
)
}
icon={<RenderIcon {...filtered} />}
label={filtered.name}
checked={selectedValues.includes(filtered.id)}
onSelect={() => {

View File

@ -1,2 +1,3 @@
export { FilterWithCheckboxes } from './filter-with-checkboxes'
export { Sort } from './sort'
export { Tabs } from './tabs'

View File

@ -0,0 +1,73 @@
import { useState } from 'react'
import { IconButton, Text } from '@status-im/components'
import { DropdownMenu } from '@status-im/components/src/dropdown-menu'
import { SortIcon } from '@status-im/icons'
type Data = {
id: number
name: string
}
type Props = {
data: Data[]
noPadding?: boolean
}
const Sort = (props: Props) => {
const { data, noPadding } = props
const [selectedValue, setSelectedValue] = useState<number>()
return (
<div className={noPadding ? '' : 'pr-2'}>
<DropdownMenu>
<div className="relative">
<IconButton icon={<SortIcon size={20} />} variant="outline" />
</div>
<DropdownMenu.Content sideOffset={10} align="end">
<div className="p-2">
<Text size={13} color="$neutral-80">
Sort by
</Text>
</div>
{data.map(option => {
return (
<DropdownMenu.Item
key={option.id}
label={option.name}
onSelect={() => {
setSelectedValue(option.id)
}}
selected={selectedValue === option.id}
/>
)
})}
{data.length === 0 && (
<div className="flex flex-col items-center justify-center p-2 py-1">
<img
className="pb-3 invert"
alt="No results"
src={'/assets/filters/empty.png'}
width={80}
height={80}
/>
<div className="pb-[2px]">
<Text size={15} weight="semibold">
No options found
</Text>
</div>
<div className="text-center">
<Text size={13}>We didn&apos;t find any results</Text>
</div>
</div>
)}
</DropdownMenu.Content>
</DropdownMenu>
</div>
)
}
export { Sort }
export type { Props as SortProps }

View File

@ -9,14 +9,14 @@ const Tabs = (): JSX.Element => {
return (
<div className="flex">
<div className="flex items-center pr-3">
<div className="flex items-center pr-4">
<button
className={`flex cursor-pointer flex-row items-center transition-colors ${
isOpen ? 'text-neutral-100' : 'text-neutral-50'
}`}
onClick={() => setActiveTab('open')}
>
<OpenIcon size={16} color={isOpen ? '$neutral-100' : '$neutral-50'} />
<OpenIcon size={20} color={isOpen ? '$neutral-100' : '$neutral-50'} />
<span className="pl-1 text-[15px]">784 Open</span>
</button>
</div>
@ -28,7 +28,7 @@ const Tabs = (): JSX.Element => {
onClick={() => setActiveTab('closed')}
>
<DoneIcon
size={16}
size={20}
color={!isOpen ? '$neutral-100' : '$neutral-50'}
/>
<span className="pl-1 text-[15px]">1012 Closed</span>

View File

@ -1,12 +1,15 @@
import { useRef, useState } from 'react'
import { Avatar, Button, Input, Tag, Text } from '@status-im/components'
import { SearchIcon } from '@status-im/icons'
import { ProfileIcon, SearchIcon } from '@status-im/icons'
import Link from 'next/link'
import { FilterWithCheckboxes, Tabs } from './filters'
import { useCurrentBreakpoint } from '@/hooks/use-current-breakpoint'
import { FilterWithCheckboxes, Sort, Tabs } from './filters'
import type { FilterWithCheckboxesProps } from './filters/filter-with-checkboxes'
import type { SortProps } from './filters/sort'
import type { TextInput } from 'react-native'
const issues = [
@ -121,18 +124,113 @@ const labels: FilterWithCheckboxesProps['data'] = [
},
]
export const TableIssues = () => {
const assignees: FilterWithCheckboxesProps['data'] = [
{
id: 1,
name: 'Unassigned',
avatar: <ProfileIcon size={16} />,
},
{
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',
},
]
const repositories: FilterWithCheckboxesProps['data'] = [
{
id: 1,
name: 'status-mobile',
},
{
id: 2,
name: 'status-desktop',
},
{
id: 3,
name: 'status-web',
},
{
id: 4,
name: 'status-go',
},
{
id: 5,
name: 'nwaku',
},
{
id: 6,
name: 'go-waku',
},
{
id: 7,
name: 'js-waku',
},
{
id: 8,
name: 'nimbus-eth2',
},
{
id: 9,
name: 'help.status.im',
},
]
const sortOptions: SortProps['data'] = [
{
id: 1,
name: 'Default',
},
{
id: 2,
name: 'Alphabetical',
},
{
id: 3,
name: 'Creation date',
},
{
id: 4,
name: 'Updated',
},
{
id: 5,
name: 'Completion',
},
]
const TableIssues = () => {
const [issuesSearchText, setIssuesSearchText] = useState('')
const inputRef = useRef<TextInput>(null)
const [isMinimized, setIsMinimized] = useState(true)
const currentBreakpoint = useCurrentBreakpoint()
return (
<div className="border-neutral-10 overflow-hidden rounded-2xl border">
<div className="bg-neutral-5 border-neutral-10 flex border-b p-3">
<div className="flex w-full flex-col 2xl:flex-row 2xl:justify-between">
<Tabs />
<div className="flex-1">
<div className="flex items-center justify-end">
<div className="pr-2">
<div className="flex items-center 2xl:justify-end">
<div className="flex w-full justify-between pr-2 pt-4 2xl:justify-end 2xl:pt-0">
<div className="flex">
<div className="pr-2 transition-all">
<Input
placeholder="Find Author"
icon={<SearchIcon size={20} />}
@ -141,13 +239,20 @@ export const TableIssues = () => {
onChangeText={setIssuesSearchText}
onHandleMinimized={() => setIsMinimized(!isMinimized)}
minimized={isMinimized}
direction="rtl"
direction={currentBreakpoint === '2xl' ? 'rtl' : 'ltr'}
ref={inputRef}
/>
</div>
<FilterWithCheckboxes data={authors} label="Author" />
<FilterWithCheckboxes data={epics} label="Epics" />
<FilterWithCheckboxes data={labels} label="Label" noPadding />
<FilterWithCheckboxes data={labels} label="Labels" />
<FilterWithCheckboxes data={assignees} label="Assignee" />
<FilterWithCheckboxes data={repositories} label="Repos" />
</div>
<Sort data={sortOptions} noPadding />
</div>
</div>
</div>
</div>
</div>
@ -197,3 +302,5 @@ export const TableIssues = () => {
</div>
)
}
export { TableIssues }

View File

@ -0,0 +1,43 @@
import { useEffect, useState } from 'react'
import defaultTheme from 'tailwindcss/defaultTheme'
// If we had custom breakpoints, we could use this to get the current breakpoint but we will use the default breakpoints for now
// import resolveConfig from 'tailwindcss/resolveConfig'
// import tailwindConfig from '../../tailwind.config'
// const fullConfig = resolveConfig(tailwindConfig)
export function useCurrentBreakpoint() {
const [currentBreakpoint, setCurrentBreakpoint] = useState('')
useEffect(() => {
const handleResize = () => {
const screenWidth = window.innerWidth
const breakpoints = defaultTheme.screens
? Object.entries(defaultTheme.screens)
: []
for (let i = breakpoints.length - 1; i >= 0; i--) {
const [breakpoint, minWidth] = breakpoints[i]
const convertedMinWidth = parseInt(minWidth, 10)
if (screenWidth >= convertedMinWidth) {
setCurrentBreakpoint(breakpoint)
break
}
}
}
handleResize()
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return currentBreakpoint || 'sm' // Default breakpoint if none matches or during SSR
}

View File

@ -9,6 +9,7 @@ import {
Root,
Trigger,
} from '@radix-ui/react-dropdown-menu'
import { CheckIcon } from '@status-im/icons'
import { Stack, styled } from '@tamagui/core'
import { Checkbox } from '../selectors'
@ -34,7 +35,7 @@ const DropdownMenu = (props: Props) => {
}
interface DropdownMenuItemProps {
icon: React.ReactElement
icon?: React.ReactElement
label: string
onSelect: () => void
selected?: boolean
@ -42,23 +43,26 @@ interface DropdownMenuItemProps {
}
const MenuItem = (props: DropdownMenuItemProps) => {
const { icon, label, onSelect, danger } = props
const { icon, label, onSelect, danger, selected } = props
const iconColor = danger ? '$danger-50' : '$neutral-50'
const textColor = danger ? '$danger-50' : '$neutral-100'
return (
<ItemBase onSelect={onSelect}>
{cloneElement(icon, { color: iconColor })}
<Stack flexDirection="row" gap={8} alignItems="center">
{icon && cloneElement(icon, { color: iconColor })}
<Text size={15} weight="medium" color={textColor}>
{label}
</Text>
</Stack>
{selected && <CheckIcon size={20} color={iconColor} />}
</ItemBase>
)
}
interface DropdownMenuCheckboxItemProps {
icon: React.ReactElement
icon?: React.ReactElement
label: string
onSelect: () => void
checked?: boolean
@ -79,7 +83,7 @@ const DropdownMenuCheckboxItem = forwardRef<
return (
<ItemBaseCheckbox {...props} ref={forwardedRef} onSelect={handleSelect}>
<Stack flexDirection="row" gap={8} alignItems="center">
{cloneElement(icon)}
{icon && cloneElement(icon)}
<Text size={15} weight="medium" color="$neutral-100">
{label}
</Text>
@ -109,6 +113,8 @@ const ItemBase = styled(DropdownMenuItem, {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
height: 32,
paddingHorizontal: 8,
borderRadius: '$10',

View File

@ -93,8 +93,8 @@ const Base = styled(Stack, {
outline: {
backgroundColor: 'transparent',
borderColor: '$neutral-20',
hoverStyle: { borderColor: '$neutral-30' },
borderColor: '$neutral-30',
hoverStyle: { borderColor: '$neutral-40' },
pressStyle: {
borderColor: '$neutral-20',
backgroundColor: '$neutral-10',

View File

@ -171,7 +171,7 @@ const InputContainer = styled(Stack, {
paddingHorizontal: 12,
animation: 'medium',
animation: 'fast',
width: '100%',
hoverStyle: {