Skip to content

Commit

Permalink
chore #237 - Extract out Tabs and Filter component from web-app-manag…
Browse files Browse the repository at this point in the history
…er (#238)
  • Loading branch information
sam-dassana committed Mar 2, 2021
1 parent f335a6e commit ae835a1
Show file tree
Hide file tree
Showing 17 changed files with 1,121 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dassana-io/web-components",
"version": "0.9.3",
"version": "0.9.4",
"publishConfig": {
"registry": "https://npm.pkg.github.com/dassana-io"
},
Expand Down
46 changes: 46 additions & 0 deletions src/__mocks__/filter_mock_data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { FilterOptions } from '../components/Filters'

export const mockDynamicFilterOptions = [
{
id: '5391d028-d5fe-4c33-a4c9-8719191aaeb1',
name: 'Ingestion Dev Account'
},
{
id: '5391d028-d5fe-4c33-a4c9-8719191aaeb8',
name: 'Ingestion Prod Account'
}
]

export const mockFilterOptions: FilterOptions = [
{
filterKey: 'name',
options: [
'Dev account',
'Prod account 1',
'Prod account 2',
'Test account'
],
staticFilter: false
},
{
filterKey: 'service',
options: [
'EC2',
'S3',
'RDS',
'Amazon Keyspaces (for Apache Cassandra)',
'EKS'
],
staticFilter: true
},
{
filterKey: 'model',
options: ['instance', 'bucket', 'table'],
staticFilter: true
},
{
filterKey: 'region',
options: ['us-west-1', 'us-east-2', 'ap-south-1'],
staticFilter: true
}
]
2 changes: 2 additions & 0 deletions src/__mocks__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './filter_mock_data'
export * from './table_mock_data'
38 changes: 37 additions & 1 deletion src/__snapshots__/storybook.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2825,6 +2825,42 @@ exports[`Storyshots Skeleton Default 1`] = `
</div>
`;

exports[`Storyshots Tabs Default 1`] = `
<div
className="light storyWrapper-0-1-2"
>
<div
className=""
>
<div
className="tabsList-0-1-138"
>
<li
className="tab-0-1-140 activeTab-0-1-139"
onClick={[Function]}
>
Foo
</li>
<li
className="tab-0-1-140"
onClick={[Function]}
>
Bar
</li>
</div>
<div
className="tabPane-0-1-137"
>
<div
className="tabPane-0-1-141"
>
Foo content text
</div>
</div>
</div>
</div>
`;

exports[`Storyshots Tag Colored 1`] = `
<div
className="light storyWrapper-0-1-2"
Expand Down Expand Up @@ -2974,7 +3010,7 @@ exports[`Storyshots TimeInput Error 1`] = `
className="decorator-0-1-115"
>
<div
className="ant-picker error-0-1-138"
className="ant-picker error-0-1-143"
onMouseUp={[Function]}
>
<div
Expand Down
108 changes: 108 additions & 0 deletions src/components/Filters/FilterPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Button } from 'components/Button'
import { faFilter } from '@fortawesome/free-solid-svg-icons'
import FilterUnit from './FilterUnit'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Link } from 'components/Link'
import { Popover } from 'components/Popover'
import { SelectOption } from 'components/Select'
import { usePopoverStyles } from './utils'
import { FiltersList, OnSearchWrapper, ProcessedFilters } from './types'
import { IconButton, IconSizes } from 'components/IconButton'
import React, { FC } from 'react'

interface FilterPopoverProps {
allFilters: ProcessedFilters
closePopover: () => void
dynamicOptions?: SelectOption[]
dynamicSearchVal: string
filtersList: FiltersList
onVisibleChange: (visible: boolean) => void
onClickAddFilter: () => void
onDelete: (selectedId: string) => void
onFilterChange: (selectedId: string, selection: string | string[]) => void
onSearchWrapper: OnSearchWrapper
pending: boolean
setFiltersList: React.Dispatch<React.SetStateAction<FiltersList>>
visible?: boolean
}

export const FilterPopover: FC<FilterPopoverProps> = ({
allFilters,
closePopover,
dynamicOptions,
dynamicSearchVal,
filtersList,
onVisibleChange,
onClickAddFilter,
onDelete,
onFilterChange,
onSearchWrapper,
pending,
setFiltersList,
visible = false
}: FilterPopoverProps) => {
const classes = usePopoverStyles()

return (
<Popover
classes={[classes.popover]}
content={
<div className={classes.popoverContent}>
<IconButton
classes={[classes.closeButton]}
onClick={closePopover}
size={IconSizes.sm}
/>
<div className={classes.popoverControls}>
<div className={classes.popoverControlsChild}>
<FontAwesomeIcon icon={faFilter} />
</div>
<Button
classes={[classes.popoverControlsChild]}
disabled={
filtersList.length >=
Object.keys(allFilters).length
}
onClick={onClickAddFilter}
>
+ Add Filter
</Button>
{filtersList.length > 0 && (
<Link
classes={[classes.popoverControlsChild]}
onClick={() => setFiltersList([])}
>
Clear Filters
</Link>
)}
</div>
<div className={classes.filtersList}>
{filtersList.map((filterItem, i) => (
<FilterUnit
allFilters={allFilters}
dynamicOptions={dynamicOptions}
dynamicSearchVal={dynamicSearchVal}
filtersList={filtersList}
id={filterItem.id}
key={filterItem.id}
onDelete={onDelete}
onFilterChange={onFilterChange}
onSearchWrapper={onSearchWrapper}
pending={pending}
selectedFilterKey={filtersList[i].selectedKey}
/>
))}
</div>
</div>
}
onVisibleChange={onVisibleChange}
placement='bottomLeft'
popupContainerSelector='#filters-popup-wrapper'
popupTriggerClasses={[classes.popoverTrigger]}
trigger='click'
visible={visible}
>
<i />
</Popover>
)
}
135 changes: 135 additions & 0 deletions src/components/Filters/FilterUnit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { faEquals } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconButton } from 'components/IconButton'
import uniq from 'lodash/uniq'
import {
FilterOption,
FiltersList,
OnSearchWrapper,
ProcessedFilters
} from './types'
import {
formatFilterOptions,
getFilterKeysOptions,
useFilterUnitStyles
} from './utils'
import { MultiSelect, Select, SelectOption } from 'components/Select'
import React, { FC } from 'react'

interface FilterUnitProps {
id: string
allFilters: ProcessedFilters
dynamicOptions?: SelectOption[]
dynamicSearchVal: string
onDelete: (selectedId: string) => void
onFilterChange: (selectedId: string, selection: string | string[]) => void
filtersList: FiltersList
onSearchWrapper: OnSearchWrapper
pending: boolean
selectedFilterKey?: string
}

const FilterUnit: FC<FilterUnitProps> = ({
id,
allFilters = {},
dynamicOptions,
dynamicSearchVal,
filtersList = [],
onDelete,
onFilterChange,
onSearchWrapper,
pending,
selectedFilterKey = ''
}: FilterUnitProps) => {
const classes = useFilterUnitStyles()

const renderKey = () => (
<Select
disabled={!!selectedFilterKey}
matchSelectedContentWidth={125}
onChange={selectedValue =>
onFilterChange(id, (selectedValue as unknown) as string)
}
options={formatFilterOptions(
getFilterKeysOptions(allFilters, filtersList)
)}
placeholder='Select Value'
showSearch
/>
)

const renderValues = () => {
const filterOption: FilterOption = allFilters[selectedFilterKey]

let options: SelectOption[] = []
let dynamicFilterProps = {}

if (selectedFilterKey && filterOption.options) {
// if filter is static, options will be the opts that BE initially gave
if (filterOption.staticFilter) {
options = formatFilterOptions(filterOption.options)
} else {
// if filter is dynamic & state is pending, data is still being fetched so options will be empty []. So only get options if status isn't pending
if (!pending) {
// if dynamic opts don't exist, options will be same as for static with the opts that BE initially gave
if (!dynamicOptions) {
options = formatFilterOptions(filterOption.options)
} else {
// if you send empty string to BE (e.g. after typing something and clearing it), it'll send back an empty [] but if there's no search val, we want to display the list of options that BE initially gave so only show the dynamic opts if search val exists
if (dynamicSearchVal) options = dynamicOptions
// if there is no search val but dynamic options exist - along with options that BE initially gave, make sure to add the selected values(if they exist)
else {
const filtersListItem = filtersList.find(
item => item.selectedKey === selectedFilterKey
)

let selectedVals: string[] = []

if (
filtersListItem &&
filtersListItem.selectedValues
)
selectedVals = filtersListItem.selectedValues

options = formatFilterOptions(
uniq([...filterOption.options, ...selectedVals])
)
}
}
}

// these dynamic filter props should be there for all dynamic filters
dynamicFilterProps = {
onSearch: onSearchWrapper(selectedFilterKey),
pending,
searchPlaceholder: 'This one hits BE...'
}
}
}

return (
<MultiSelect
disabled={!options.length && !pending}
matchSelectedContentWidth={225}
maxTagCount={5}
onChange={(values: string[]) => onFilterChange(id, values)}
options={options}
placeholder='Select field'
searchPlaceholder='Search'
showSearch
{...dynamicFilterProps}
/>
)
}

return (
<div className={classes.container}>
<div className={classes.singleSelectContainer}>{renderKey()}</div>
<FontAwesomeIcon icon={faEquals} size='xs' />
<div className={classes.multiSelectContainer}>{renderValues()}</div>
<IconButton onClick={() => onDelete(id)} />
</div>
)
}

export default FilterUnit
Loading

0 comments on commit ae835a1

Please sign in to comment.