Skip to content

Commit

Permalink
feat: add filter/sort functionality (#4)
Browse files Browse the repository at this point in the history
* feat: add filter/sort functionality

fix: minor changes

refactor: structures of the filters component

fix: minor changes

fix: lock file

* fix: minor changes

* fix: minor changes

* feat: style adjustment

* fix: styles for filter date

* refactor: structure

* fix: minor changes

* feat: add todos

* fix: minor changes

* fix: minor changes

* fix: minor changes

* feat: structure optimization

* feat: add comments

* feat: add more options for filters

* feat: add states for repo list

* feat: add filtering for calendar is_between

* feat: add text type filter

* feat: add number type filter

* fix: minor changes

* chore: add small adjustments

feat: add auto opening of the filter when selected

feat: filter improvements (#8)

* refactor: component BranchSelector

* fix: updated common layout and navbar

* fix: fixes for updated common layout and navbar

* feat: add filter/sort functionality (#4)

* feat: add filter/sort functionality

fix: minor changes

refactor: structures of the filters component

fix: minor changes

fix: lock file

* fix: minor changes

* fix: minor changes

* feat: style adjustment

* fix: styles for filter date

* refactor: structure

* fix: minor changes

* feat: add todos

* fix: minor changes

* fix: minor changes

* fix: minor changes

* feat: structure optimization

* feat: add comments

* feat: add more options for filters

* feat: add states for repo list

* feat: add filtering for calendar is_between

* feat: add text type filter

* feat: add number type filter

* fix: minor changes

* chore: add small adjustments

* refactor: using types

* feat: add an additional filter call button

* fix: minor changes

* feat: add save logic

* fix: minor changes

* feat: small improvements in saving filters

* feat: expanding the functionality of the calendar

* feat: override the save logic

* chore: add small adjustments

* fix: minor changes

* fix: minor changes

* fix: minor changes

---------

Co-authored-by: Alex Zemlyakov <[email protected]>

fix: conflicts with old branch selector component
  • Loading branch information
andrewgolovanov committed Nov 21, 2024
1 parent 13acaab commit 77931d7
Show file tree
Hide file tree
Showing 34 changed files with 7,318 additions and 6,717 deletions.
2 changes: 2 additions & 0 deletions packages/canary/src/components/calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as React from 'react'
import { DayPicker } from 'react-day-picker'
import type { DateRange } from 'react-day-picker'

import { buttonVariants } from '@/components/button'
import { cn } from '@/lib/utils'
import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons'

export type CalendarProps = React.ComponentProps<typeof DayPicker>
export type CalendarDateRange = DateRange

function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
Expand Down
18 changes: 10 additions & 8 deletions packages/canary/src/components/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as React from 'react'

import { cn } from '@/lib/utils'
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons'
import { ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons'

import { Icon } from './icon'

const DropdownMenu = DropdownMenuPrimitive.Root

Expand All @@ -15,7 +17,7 @@ const DropdownMenuTrigger = React.forwardRef<
<DropdownMenuPrimitive.Trigger
ref={ref}
className={cn(
'[&>svg.chevron-down]:duration-100 [&>svg.chevron-down]:ease-in-out [&>svg.chevron-down]:data-[state=open]:rotate-180',
'ring-offset-background outline-none focus:ring-2 focus:ring-offset-2 [&>svg.chevron-down]:duration-100 [&>svg.chevron-down]:ease-in-out [&>svg.chevron-down]:data-[state=open]:rotate-180',
{ 'flex cursor-pointer items-center border-l border-inherit px-2.5 py-0.5 outline-none': insideSplitButton },
className
)}
Expand Down Expand Up @@ -99,7 +101,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'text-foreground-2 focus:bg-background-4 focus:text-primary relative flex cursor-default select-none items-center rounded-sm px-2 py-[7px] text-sm outline-none transition-colors',
'text-foreground-8 focus:bg-background-4 focus:text-primary relative flex cursor-pointer select-none items-center rounded-sm px-2 py-[7px] text-sm outline-none transition-colors',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
Expand All @@ -116,18 +118,18 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'focus:bg-accent focus:text-accent-foreground group relative flex cursor-pointer select-none items-center rounded-sm py-2 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
<span className="border-borders-9 group-data-[state=checked]:border-icons-2 absolute left-2 flex h-4 w-4 items-center justify-center rounded-sm border">
<DropdownMenuPrimitive.ItemIndicator className="bg-icons-2 flex h-full w-full items-center justify-center">
<Icon className="text-icons-5 h-[7px]" name="checkbox" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
<span className="text-14 text-foreground-8">{children}</span>
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
Expand Down
6 changes: 3 additions & 3 deletions packages/canary/src/components/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export interface BaseInputProps extends React.InputHTMLAttributes<HTMLInputEleme
}

const BaseInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, isExtended, ...props }, ref) => {
const commonClassName = 'bg-transparent px-3 py-1 text-foreground-1'
const commonClassName = 'bg-transparent text-foreground-1 px-2.5 py-1'
const specificClassNames = isExtended
? 'border-none grow focus-visible:outline-none'
: 'flex h-9 w-full rounded border border-input text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-foreground-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'
: 'flex h-8 w-full rounded border border-border-2 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-foreground-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50'

return <input type={type} className={cn(commonClassName, specificClassNames, className)} ref={ref} {...props} />
return <input className={cn(commonClassName, specificClassNames, className)} type={type} ref={ref} {...props} />
})
BaseInput.displayName = 'BaseInput'

Expand Down
6 changes: 3 additions & 3 deletions packages/canary/src/components/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ function Item({ icon, text, description, active, submenuItem, className }: ItemP
/>
<div className="z-10 col-start-1 row-span-full flex items-center">
{icon ? (
<div className="sub-menu-icon-bg relative flex size-8 place-content-center place-items-center rounded border border-borders-1 bg-background-2">
<div className="sub-menu-icon-bg border-borders-1 bg-background-2 relative flex size-8 place-content-center place-items-center rounded border">
<Icon
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-foreground-3"
className="text-foreground-3 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
name="sub-menu-ellipse"
size={18}
/>
Expand Down Expand Up @@ -194,7 +194,7 @@ function Item({ icon, text, description, active, submenuItem, className }: ItemP

function Footer({ children }: { children: React.ReactNode }) {
return (
<div className="sticky bottom-0 z-20 grid h-[72px] items-center border-t border-borders-5 px-4">{children}</div>
<div className="border-borders-5 sticky bottom-0 z-20 grid h-[72px] items-center border-t px-4">{children}</div>
)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/canary/src/components/search-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ const Root = ({

return (
<div className={cn('relative', width === 'full' ? 'w-full' : 'w-96', className)}>
<Icon name="search" size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-icons-1" />
<Icon name="search" size={12} className="text-icons-1 absolute left-2.5 top-1/2 -translate-y-1/2" />
{hasShortcut && !!shortcutLetter && (
<div className="absolute right-1.5 top-1/2 flex h-5 -translate-y-1/2 cursor-pointer items-center gap-0.5 rounded-sm border bg-background-3 px-1 text-foreground-2 duration-100 ease-in-out">
<div className="bg-background-3 text-foreground-2 absolute right-1.5 top-1/2 flex h-5 -translate-y-1/2 cursor-pointer items-center gap-0.5 rounded-sm border px-1 duration-100 ease-in-out">
<Icon name="apple-shortcut" size={12} />
<Text size={0} className="text-inherit">
{shortcutLetter}
Expand Down
3 changes: 3 additions & 0 deletions packages/canary/src/icons/checkbox.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion packages/canary/src/icons/chevron-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/canary/src/icons/chevron-fill-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions packages/canary/src/icons/circle-arrow-top.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions packages/canary/src/icons/circle-arrows-updown.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/canary/src/icons/grid-dots.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions packages/canary/src/icons/more-dots-fill-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 2 additions & 3 deletions packages/canary/src/icons/plus.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 9 additions & 11 deletions packages/canary/src/icons/trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
},
"homepage": "https://github.com/harness/canary",
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@git-diff-view/react": "^0.0.16",
"@git-diff-view/shiki": "^0.0.16",
"@harnessio/canary": "workspace:*",
Expand All @@ -59,6 +62,7 @@
"@uiw/react-markdown-preview": "^5.1.1",
"caret-xy": "^2.0.3",
"clipboard-copy": "^3.1.0",
"date-fns": "^3.6.0",
"diff2html": "3.4.22",
"fast-diff": "^1.3.0",
"highlight.js": "^11.9.0",
Expand Down
169 changes: 169 additions & 0 deletions packages/playground/src/components/filters/filter-trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { FilterOption, SortOption } from './types'
import { UseFiltersReturn } from './use-filters'

import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Icon,
Input
} from '@harnessio/canary'

interface BaseFilterTriggerProps {
type: 'filter' | 'sort'
options: FilterOption[] | SortOption[]
customLabel?: React.ReactNode | string
hideCount?: boolean
dropdownAlign?: 'start' | 'end'
}

interface FilterTriggerFilterProps extends BaseFilterTriggerProps {
type: 'filter'
options: FilterOption[]
activeFilters: UseFiltersReturn['activeFilters']
onChange: UseFiltersReturn['handleFilterChange']
onReset?: UseFiltersReturn['handleResetFilters']
searchQueries: UseFiltersReturn['searchQueries']
onSearchChange: UseFiltersReturn['handleSearchChange']
}

interface FilterTriggerSortProps extends BaseFilterTriggerProps {
type: 'sort'
options: SortOption[]
activeFilters: UseFiltersReturn['activeSorts']
onChange: UseFiltersReturn['handleSortChange']
onReset: UseFiltersReturn['handleResetSorts']
searchQueries: UseFiltersReturn['searchQueries']
onSearchChange: UseFiltersReturn['handleSearchChange']
}

type FilterTriggerProps = FilterTriggerFilterProps | FilterTriggerSortProps

const LABELS = {
filter: {
defaultLabel: 'Filter',
inputPlaceholder: 'Filter by...',
buttonLabel: 'Reset filters'
},
sort: {
defaultLabel: 'Sort',
inputPlaceholder: 'Sort by...',
buttonLabel: 'Reset sort'
}
}

const FilterTrigger = ({
type,
activeFilters,
customLabel,
hideCount,
dropdownAlign = 'end',
onChange,
onReset,
searchQueries,
onSearchChange,
options
}: FilterTriggerProps) => {
const { defaultLabel, inputPlaceholder, buttonLabel } = LABELS[type]
const displayLabel = customLabel || defaultLabel

const isFilterOption = (option: FilterOption | SortOption): option is FilterOption => {
return 'type' in option && (option as FilterOption).type !== undefined
}

const isSortOption = (option: FilterOption | SortOption): option is SortOption => {
return !('type' in option)
}

const onChangeOption = (option: FilterOption | SortOption) => {
if (type === 'filter') {
if (isFilterOption(option)) {
onChange(
{
type: option.value
},
option.conditions?.[0].value
)
}
} else {
if (isSortOption(option)) {
onChange({
type: option.value,
direction: 'desc'
})
}
}
}

const filteredBySearchOptions = options.filter(
option => !searchQueries.menu[type] || option.label.toLowerCase().includes(searchQueries.menu[type].toLowerCase())
)

return (
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center gap-x-1.5">
<span className="text-foreground-2 hover:text-foreground-1 text-14 flex items-center gap-x-1">
{displayLabel}
{!hideCount && activeFilters.length > 0 && (
<span className="text-foreground-2 bg-background-2 text-11 border-borders-5 flex h-[18px] min-w-[17px] items-center justify-center rounded border px-1">
{activeFilters.length}
</span>
)}
</span>
{!customLabel && <Icon className="chevron-down text-icons-4" name="chevron-fill-down" size={6} />}
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[224px] p-0" align={dropdownAlign}>
<div className="border-borders-4 relative flex items-center justify-between border-b px-3 py-2.5">
<Input
type="text"
placeholder={inputPlaceholder}
value={searchQueries.menu[type] || ''}
onChange={e => onSearchChange(type, e.target.value, 'menu')}
onKeyDown={e => e.stopPropagation()}
onClick={e => e.preventDefault()}
/>

{searchQueries.menu[type] && (
<div className="absolute right-3">
<button
className="text-foreground-4 hover:text-foreground-1 flex p-1.5 transition-colors duration-200"
onClick={e => {
e.preventDefault()
onSearchChange(type, '', 'menu')
}}>
<Icon className="rotate-45" name="plus" size={12} />
</button>
</div>
)}
</div>

<div className="p-1">
{filteredBySearchOptions.map(option => (
<DropdownMenuItem key={option.value} onSelect={() => onChangeOption(option)}>
{option.label}
</DropdownMenuItem>
))}

{filteredBySearchOptions.length === 0 && (
<div className="flex items-center justify-center p-4">
<span className="text-foreground-2 text-14 leading-none">No results</span>
</div>
)}
</div>

{onReset && (
<div className="border-borders-4 border-t p-1">
<DropdownMenuItem asChild>
<button className="w-full font-medium" onClick={onReset}>
{buttonLabel}
</button>
</DropdownMenuItem>
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
)
}

export default FilterTrigger
Loading

0 comments on commit 77931d7

Please sign in to comment.