feat: add remaining filters and some polishment with breakpoint table issues component
This commit is contained in:
parent
dc138f8715
commit
ddcddbac35
|
@ -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
|
||||
|
|
|
@ -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}`,
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -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 Data = {
|
||||
id: number
|
||||
name: string
|
||||
avatar?: string | React.ReactElement
|
||||
color?: ColorTokens | `#${string}`
|
||||
}
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
id: number
|
||||
name: string
|
||||
avatar?: string
|
||||
color?: ColorTokens | `#${string}`
|
||||
}[]
|
||||
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={() => {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { FilterWithCheckboxes } from './filter-with-checkboxes'
|
||||
export { Sort } from './sort'
|
||||
export { Tabs } from './tabs'
|
||||
|
|
|
@ -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't find any results</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Sort }
|
||||
export type { Props as SortProps }
|
|
@ -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>
|
||||
|
|
|
@ -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,33 +124,135 @@ 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">
|
||||
<Tabs />
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="pr-2">
|
||||
<Input
|
||||
placeholder="Find Author"
|
||||
icon={<SearchIcon size={20} />}
|
||||
size={32}
|
||||
value={issuesSearchText}
|
||||
onChangeText={setIssuesSearchText}
|
||||
onHandleMinimized={() => setIsMinimized(!isMinimized)}
|
||||
minimized={isMinimized}
|
||||
direction="rtl"
|
||||
ref={inputRef}
|
||||
/>
|
||||
<div className="flex w-full flex-col 2xl:flex-row 2xl:justify-between">
|
||||
<Tabs />
|
||||
<div className="flex-1">
|
||||
<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} />}
|
||||
size={32}
|
||||
value={issuesSearchText}
|
||||
onChangeText={setIssuesSearchText}
|
||||
onHandleMinimized={() => setIsMinimized(!isMinimized)}
|
||||
minimized={isMinimized}
|
||||
direction={currentBreakpoint === '2xl' ? 'rtl' : 'ltr'}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FilterWithCheckboxes data={authors} label="Author" />
|
||||
<FilterWithCheckboxes data={epics} label="Epics" />
|
||||
<FilterWithCheckboxes data={labels} label="Labels" />
|
||||
<FilterWithCheckboxes data={assignees} label="Assignee" />
|
||||
<FilterWithCheckboxes data={repositories} label="Repos" />
|
||||
</div>
|
||||
<Sort data={sortOptions} noPadding />
|
||||
</div>
|
||||
</div>
|
||||
<FilterWithCheckboxes data={authors} label="Author" />
|
||||
<FilterWithCheckboxes data={epics} label="Epics" />
|
||||
<FilterWithCheckboxes data={labels} label="Label" noPadding />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -197,3 +302,5 @@ export const TableIssues = () => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { TableIssues }
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 })}
|
||||
<Text size={15} weight="medium" color={textColor}>
|
||||
{label}
|
||||
</Text>
|
||||
<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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -171,7 +171,7 @@ const InputContainer = styled(Stack, {
|
|||
|
||||
paddingHorizontal: 12,
|
||||
|
||||
animation: 'medium',
|
||||
animation: 'fast',
|
||||
width: '100%',
|
||||
|
||||
hoverStyle: {
|
||||
|
|
Loading…
Reference in New Issue