Skip to content

Commit

Permalink
HP-555 Feat/disco tag dropdown viewer (#927)
Browse files Browse the repository at this point in the history
* Sketch out tag viewer

* Update dropdown style

* [WIP] Sketch out idea for moving search bar

* feat: searchable tags

* feat: reset btn, tag orders, and css update

* fix: tag deselection

* chore: round dropbox

* fix: integration tests

Co-authored-by: Michael Ingram <mpingram@uchicago.edu>
  • Loading branch information
mfshao and mpingram committed Sep 29, 2021
1 parent a47cde9 commit 0e70108
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 22 deletions.
11 changes: 10 additions & 1 deletion docs/portal_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,16 @@ Below is an example, with inline comments describing what each JSON block config
},
"search": {
"searchBar": {
"enabled": true
"enabled": true,
"inputSubtitle": "Search Bar", // optional, subtitle of search bar
"placeholder": "Search studies by keyword", // optional, placeholder text of search input
"searchableTextFields": ["study", "age", "publication"] // optional, list of properties in data to make searchable
// if not present, only fields visible in the table will be searchable
},
"tagSearchDropdown": { // optional, config section for searchable tags
"enabled": true,
"collapseOnDefault": false, // optional, whether the searchable tag panel is collapsed when loading, default value is "true"
"collapsibleButtonText": "Study Characteristics" // optional, display text for the searchable tag panel collapse control button, default value is "Tag Panel"
}
},
"advSearchFilters": {
Expand Down
43 changes: 40 additions & 3 deletions src/Discovery/Discovery.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@
height: 90%;
}

.discovery-header__dropdown-tags-container {
flex: 3 1 60%;
height: 90%;
}

.discovery-header__tags-header {
padding-left: 50px;
cursor: default;
Expand Down Expand Up @@ -142,7 +147,7 @@
.discovery-tag {
cursor: pointer;
line-height: 18px;
font-size: 10px;
font-size: 12px;
box-sizing: border-box;
border-radius: 5px;
border-width: 2px;
Expand Down Expand Up @@ -183,15 +188,15 @@
overflow-y: auto;
}

.discovery-search-container {
.discovery-search-container__standalone {
margin-bottom: 10px;
}

.discovery-search {
border-color: var(--g3-color__base-blue);
border-width: 1px;
border-radius: 7px;
max-width: 500px;
max-width: 800px;
}

.discovery-input-subtitle {
Expand Down Expand Up @@ -339,6 +344,38 @@
border-color: unset;
}

.discovery-header__dropdown-tags-control-button {
color: var(--g3-color__base-blue);
border-color: var(--g3-color__base-blue);
min-width: 150px;
margin-left: 8px;
height: 40px;
border-width: 1px;
border-radius: 7px;
}

.discovery-header__dropdown-tags-control-panel {
width: 100%;
padding-right: 16px;
padding-left: 16px;
display: flex;
justify-content: space-between;
align-items: baseline;
}

.discovery-header__dropdown-tags-display-panel .ant-collapse-header {
display: none;
}

.discovery-header__dropdown-tags-search {
width: 50%;
}

.discovery-header__dropdown-tags .ant-select-show-search.ant-select:not(.ant-select-customize-input) .ant-select-selector {
border-width: 1px;
border-radius: 7px;
}

.discovery-modal {
box-sizing: border-box;
}
Expand Down
94 changes: 82 additions & 12 deletions src/Discovery/Discovery.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import React, { useState, useEffect } from 'react';
import * as JsSearch from 'js-search';
import { Tag, Popover } from 'antd';
import {
UnlockOutlined, ClockCircleOutlined, DashOutlined,
Tag, Popover, Space, Collapse, Button,
} from 'antd';
import {
UnlockOutlined, ClockCircleOutlined, DashOutlined, UpOutlined, DownOutlined, UndoOutlined,
} from '@ant-design/icons';
import { DiscoveryConfig } from './DiscoveryConfig';
import './Discovery.css';
import DiscoverySummary from './DiscoverySummary';
import DiscoveryTagViewer from './DiscoveryTagViewer';
import DiscoveryDropdownTagViewer from './DiscoveryDropdownTagViewer';
import DiscoveryListView from './DiscoveryListView';
import DiscoveryDetails from './DiscoveryDetails';
import DiscoveryAdvancedSearchPanel from './DiscoveryAdvancedSearchPanel';
Expand All @@ -24,6 +27,8 @@ export enum AccessLevel {
NOT_AVAILABLE = 4,
}

const { Panel } = Collapse;

const ARBORIST_READ_PRIV = 'read';

const getTagColor = (tagCategory: string, config: DiscoveryConfig): string => {
Expand Down Expand Up @@ -199,6 +204,12 @@ const Discovery: React.FunctionComponent<Props> = (props: Props) => {
const [discoveryActionStatusMessage, setDiscoveryActionStatusMessage] = useState({
url: '', message: '', title: '', active: false,
});
const [searchableTagCollapsed, setSearchableTagCollapsed] = useState(
config.features.search.tagSearchDropdown
&& config.features.search.tagSearchDropdown.enabled
&& (config.features.search.tagSearchDropdown.collapseOnDefault
|| config.features.search.tagSearchDropdown.collapseOnDefault === undefined),
);

const handleSearchChange = (ev) => {
const { value } = ev.currentTarget;
Expand Down Expand Up @@ -476,6 +487,14 @@ const Discovery: React.FunctionComponent<Props> = (props: Props) => {
config,
);

const enableSearchBar = props.config.features.search
&& props.config.features.search.searchBar
&& props.config.features.search.searchBar.enabled;

const enableSearchableTags = props.config.features.search
&& props.config.features.search.tagSearchDropdown
&& props.config.features.search.tagSearchDropdown.enabled;

// Disabling noninteractive-tabindex rule because the span tooltip must be focusable as per https://www.w3.org/TR/2017/REC-wai-aria-1.1-20171214/#tooltip
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
return (
Expand All @@ -491,21 +510,72 @@ const Discovery: React.FunctionComponent<Props> = (props: Props) => {
visibleResources={visibleResources}
config={config}
/>
<DiscoveryTagViewer
config={config}
studies={props.studies}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
{(enableSearchableTags) ? (
<div className='discovery-header__dropdown-tags-container' id='discovery-tag-filters'>
<Space direction='vertical' style={{ width: '100%' }}>
<div className='discovery-header__dropdown-tags-control-panel'>
{(enableSearchBar)
&& (
<div className='discovery-search-container discovery-header__dropdown-tags-search'>
<DiscoveryMDSSearch
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
inputSubtitle={config.features.search.searchBar.inputSubtitle}
/>
</div>
)}
<div className='discovery-header__dropdown-tags-buttons'>
<Button
type='default'
className={'discovery-header__dropdown-tags-control-button'}
disabled={Object.keys(selectedTags).length === 0}
onClick={() => { setSelectedTags({}); }}
icon={<UndoOutlined />}
>
{'Reset Selection'}
</Button>
<Button
type='default'
className={'discovery-header__dropdown-tags-control-button'}
onClick={() => { setSearchableTagCollapsed(!searchableTagCollapsed); }}
icon={(searchableTagCollapsed) ? <DownOutlined /> : <UpOutlined />}
>
{`${props.config.features.search.tagSearchDropdown.collapsibleButtonText || 'Tag Panel'}`}
</Button>
</div>
</div>
<div className='discovery-header__dropdown-tags-display-panel'>
<Collapse activeKey={(searchableTagCollapsed) ? '' : '1'} ghost>
<Panel header='This is panel header 1' key='1'>
<div className='discovery-header__dropdown-tags'>
<DiscoveryDropdownTagViewer
config={config}
studies={props.studies}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
</div>
</Panel>
</Collapse>
</div>
</Space>
</div>
) : (
<DiscoveryTagViewer
config={config}
studies={props.studies}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
)}
</div>

<div className='discovery-studies-container'>
{/* Free-form text search box */}
{ (props.config.features.search
&& props.config.features.search.searchBar
&& props.config.features.search.searchBar.enabled)
{ (enableSearchBar && !enableSearchableTags
)
&& (
<div className='discovery-search-container'>
<div className='discovery-search-container discovery-search-container__standalone'>
<DiscoveryMDSSearch
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
Expand Down
6 changes: 5 additions & 1 deletion src/Discovery/DiscoveryConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ export interface DiscoveryConfig {
enabled: boolean,
inputSubtitle?: string,
placeholder?: string
// searchTags: boolean, // not supported, consider removing
searchableTextFields?: string[] // list of properties in data to make searchable.
// if not present, only fields visible in the table
// will be searchable.
},
tagSearchDropdown?: {
enabled: boolean,
collapsibleButtonText?: string
collapseOnDefault?: boolean
}
},
authorization: {
Expand Down
137 changes: 137 additions & 0 deletions src/Discovery/DiscoveryDropdownTagViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from 'react';
import _ from 'lodash';
import {
Select, Row, Col, Tag,
} from 'antd';
import { DiscoveryConfig } from './DiscoveryConfig';

const { Option } = Select;

interface DiscoveryTagViewerProps {
config: DiscoveryConfig
studies?: {__accessible: boolean, [any: string]: any}[]
selectedTags: any
setSelectedTags: any
}

const DiscoveryDropdownTagViewer: React.FunctionComponent<DiscoveryTagViewerProps> = (props: DiscoveryTagViewerProps) => {
// getTagsInCategory returns a list of the unique tags in studies which belong
// to the specified category.
const getTagsInCategory = (category: any, displayName: string, studies: any[] | null):React.ReactNode => {
if (!studies) {
return <React.Fragment />;
}
const tagMap = {};
studies.forEach((study) => {
const tagField = props.config.minimalFieldMapping.tagsListFieldName;
study[tagField].forEach((tag) => {
if (tag.category === category.name) {
tagMap[tag.name] = 1;
}
});
});
const tagArray = Object.keys(tagMap).sort((a, b) => a.localeCompare(b));
// get selected tags which value is not 'undefined'
const trulySelectedTags = Object.keys(props.selectedTags).reduce((acc, el) => {
if (props.selectedTags[el] !== undefined) acc.push(el);
return acc;
}, []);
const valueArray = _.intersection(tagArray, trulySelectedTags);

return (
<Select
mode='multiple'
showSearch
showArrow
allowClear
maxTagCount={3}
style={{ width: '100%' }}
id={`discovery-tag-column--${category.name}`}
placeholder={displayName}
value={valueArray}
onSelect={(tag: string) => {
props.setSelectedTags({
...props.selectedTags,
[tag]: props.selectedTags[tag] ? undefined : true,
});
}}
onDeselect={(tag: string) => {
props.setSelectedTags({
...props.selectedTags,
[tag]: props.selectedTags[tag] ? undefined : true,
});
}}
>
{ tagArray.map((tag) => (
<Option
key={category.name + tag}
value={tag}
>
<Tag
key={category.name + tag}
role='button'
tabIndex={0}
aria-pressed={props.selectedTags[tag] ? 'true' : 'false'}
className={`discovery-header__tag-btn discovery-tag ${(props.selectedTags[tag]) ? 'discovery-tag--selected' : ''}`}
aria-label={tag}
style={{
backgroundColor: props.selectedTags[tag] ? category.color : 'initial',
borderColor: category.color,
}}
>
{tag}
</Tag>
</Option>
),
)}
</Select>
);
};

return (
<Row
gutter={[16, 8]}
justify='space-between'
>
{
props.config.tagCategories.map((category) => {
if (category.display === false) {
return null;
}

let categoryDisplayName = category.displayName;
if (!categoryDisplayName) {
// Capitalize category name
const categoryWords = category.name.split('_').map((x) => x.toLowerCase());
categoryWords[0] = categoryWords[0].charAt(0).toUpperCase()
+ categoryWords[0].slice(1);
categoryDisplayName = categoryWords.join(' ');
}

const tags = getTagsInCategory(category, categoryDisplayName, props.studies);

return (
<Col
className='discovery-header__tag-group'
key={category.name}
xs={24}
sm={24}
md={12}
lg={12}
xl={12}
xxl={12}
>
{ tags }
</Col>
);
})
}
</Row>
);
};

DiscoveryDropdownTagViewer.defaultProps = {
studies: null,
};

export default DiscoveryDropdownTagViewer;
Loading

0 comments on commit 0e70108

Please sign in to comment.