Unverified Commit 0ebb4ac3 authored by Andrey Azov's avatar Andrey Azov Committed by GitHub
Browse files

Add a Chevron component (#515)

Also, add a ShowHide component, which consists of a label associated with a chevron
parent 924e02f3
Pipeline #175665 passed with stages
in 6 minutes and 46 seconds
......@@ -11,13 +11,9 @@
width: 16px;
}
.expandBtn {
margin-left: 10px;
img {
height: 12px;
width: 12px;
}
.chevron {
margin-left: 8px;
height: 6px;
}
&:hover,
......
......@@ -136,7 +136,7 @@ describe('<TrackPanelListItem />', () => {
it('toggles the expanded/collapsed state of the track when clicked on the expand button', async () => {
const { container } = wrapInRedux();
const expandButton = container.querySelector('.expandBtn') as HTMLElement;
const expandButton = container.querySelector('.chevron') as HTMLElement;
userEvent.click(expandButton);
......
......@@ -23,9 +23,6 @@ import { RootState } from 'src/store';
import analyticsTracking from 'src/services/analytics-service';
import browserMessagingService from 'src/content/app/browser/browser-messaging-service';
import ImageButton from 'src/shared/components/image-button/ImageButton';
import VisibilityIcon from 'src/shared/components/visibility-icon/VisibilityIcon';
import {
TrackItemColour,
TrackItemColourKey,
......@@ -57,8 +54,10 @@ import {
getBrowserActiveEnsObjectId
} from '../../browserSelectors';
import chevronDownIcon from 'static/img/shared/chevron-down.svg';
import chevronUpIcon from 'static/img/shared/chevron-up.svg';
import ImageButton from 'src/shared/components/image-button/ImageButton';
import Chevron from 'src/shared/components/chevron/Chevron';
import VisibilityIcon from 'src/shared/components/visibility-icon/VisibilityIcon';
import { ReactComponent as Ellipsis } from 'static/img/track-panel/ellipsis.svg';
import { DrawerView } from 'src/content/app/browser/drawer/drawerState';
......@@ -225,12 +224,11 @@ export const TrackPanelListItem = (props: TrackPanelListItemProps) => {
</span>
)}
{track.child_tracks && (
<button onClick={toggleExpand} className={styles.expandBtn}>
<img
src={isCollapsed ? chevronDownIcon : chevronUpIcon}
alt={isCollapsed ? 'expand' : 'collapse'}
/>
</button>
<Chevron
onClick={toggleExpand}
direction={isCollapsed ? 'down' : 'up'}
classNames={{ svg: styles.chevron }}
/>
)}
</label>
<div className={styles.ellipsisHolder}>
......
......@@ -76,7 +76,9 @@ $backgroundColor: $black;
top: 0;
}
.filterLabelWrapper {
.filterLabelWrapper {
display: flex;
justify-content: flex-end;
padding-right: 20px;
padding-bottom: 9px;
padding-top: 6px;
......@@ -118,14 +120,3 @@ $backgroundColor: $black;
top: -2px;
}
}
.chevron {
margin-left: 10px;
margin-bottom: -3px;
height: 12px;
width: 12px;
}
.chevronUp {
transform: rotate(180deg);
}
......@@ -57,12 +57,11 @@ import GeneRelationships from 'src/content/app/entity-viewer/gene-view/component
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import { CircleLoader } from 'src/shared/components/loader/Loader';
import { TicksAndScale } from 'src/content/app/entity-viewer/gene-view/components/base-pairs-ruler/BasePairsRuler';
import ShowHide from 'src/shared/components/show-hide/ShowHide';
import { FullGene } from 'src/shared/types/thoas/gene';
import { SortingRule } from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSlice';
import { ReactComponent as ChevronDown } from 'static/img/shared/chevron-down.svg';
import styles from './GeneView.scss';
type Gene = Pick<FullGene, 'symbol'> &
......@@ -268,19 +267,11 @@ const GeneViewWithData = (props: GeneViewWithDataProps) => {
>
{props.gene.transcripts.length > 5 && (
<div className={styles.filterLabelWrapper}>
<div
className={classNames([styles.filterLabel], {
[styles.openFilterLabel]: isFilterOpen
})}
<ShowHide
onClick={toggleFilter}
>
{filterLabel}
<ChevronDown
className={classNames([styles.chevron], {
[styles.chevronUp]: isFilterOpen
})}
/>
</div>
isExpanded={isFilterOpen}
label={filterLabel}
/>
</div>
)}
</div>
......
......@@ -57,8 +57,6 @@
.downloadLink {
grid-area: downloadLink;
color: $blue;
cursor: pointer;
padding-top: 6px;
}
......@@ -67,17 +65,6 @@
padding-top: 6px;
}
.chevron {
margin-left: 10px;
margin-bottom: -3px;
height: 12px;
width: 12px;
transition: transform linear 0.2s;
&Up {
transform: rotate(-180deg);
}
}
.viewInApp {
padding-top: 12px;
}
......@@ -35,6 +35,7 @@ import { buildFocusIdForUrl } from 'src/shared/state/ens-object/ensObjectHelpers
import { InstantDownloadTranscript } from 'src/shared/components/instant-download';
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import ShowHide from 'src/shared/components/show-hide/ShowHide';
import { toggleTranscriptDownload } from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSlice';
import { clearExpandedProteins } from 'src/content/app/entity-viewer/state/gene-view/proteins/geneViewProteinsSlice';
......@@ -48,8 +49,6 @@ import { View } from 'src/content/app/entity-viewer/state/gene-view/view/geneVie
import transcriptsListStyles from '../DefaultTranscriptsList.scss';
import styles from './TranscriptsListItemInfo.scss';
import { ReactComponent as ChevronDown } from 'static/img/shared/chevron-down.svg';
type Gene = Pick<FullGene, 'unversioned_stable_id' | 'stable_id'>;
type Transcript = Pick<
FullTranscript,
......@@ -139,10 +138,6 @@ export const TranscriptsListItemInfo = (
return urlFor.browser({ genomeId: genomeId, focus: focusIdForUrl });
};
const chevronClassForDownload = classNames(styles.chevron, {
[styles.chevronUp]: props.expandDownload
});
return (
<div className={mainStyles}>
<div className={transcriptsListStyles.left}></div>
......@@ -177,13 +172,12 @@ export const TranscriptsListItemInfo = (
<div className={styles.moreInformation}>More information</div>
<div
className={styles.downloadLink}
<ShowHide
onClick={() => props.toggleTranscriptDownload(transcript.stable_id)}
>
Download
<ChevronDown className={chevronClassForDownload} />
</div>
label="Download"
isExpanded={props.expandDownload}
classNames={{ wrapper: styles.downloadLink }}
/>
{props.expandDownload && renderInstantDownload({ ...props, genomeId })}
</div>
<div className={transcriptsListStyles.right}>
......
......@@ -39,13 +39,8 @@
padding: 0px;
width: auto;
padding-right: 60px;
display: inline-block;
grid-template-columns: max-content max-content;
margin-left: 20px;
&:before,
&:after {
margin-top: -10px;
}
}
.xrefAccordionItem:not(:first-child) {
......
......@@ -73,13 +73,6 @@ $content-width: $gene_image_width;
.chevron {
margin-left: 6px;
margin-bottom: -1px;
height: 12px;
width: 12px;
transition: transform linear 0.2s;
&Up {
transform: rotate(-180deg);
}
}
.xrefCountChevronOpen {
......
......@@ -18,7 +18,6 @@ import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import set from 'lodash/fp/set';
import { Pick2 } from 'ts-multipick';
import classNames from 'classnames';
import { CircleLoader } from 'src/shared/components/loader/Loader';
import ProteinDomainImage from 'src/content/app/entity-viewer/gene-view/components/protein-domain-image/ProteinDomainImage';
......@@ -26,7 +25,7 @@ import ProteinImage from 'src/content/app/entity-viewer/gene-view/components/pro
import ProteinFeaturesCount from 'src/content/app/entity-viewer/gene-view/components/protein-features-count/ProteinFeaturesCount';
import ExternalReference from 'src/shared/components/external-reference/ExternalReference';
import InstantDownloadProtein from 'src/shared/components/instant-download/instant-download-protein/InstantDownloadProtein';
import { ReactComponent as ChevronDown } from 'static/img/shared/chevron-down.svg';
import Chevron from 'src/shared/components/chevron/Chevron';
import {
ExternalSource,
......@@ -101,23 +100,18 @@ const ProteinsListItemInfo = (props: Props) => {
const params: { [key: string]: string } = useParams();
const { genomeId } = params;
const [
transcriptWithProteinDomains,
setTranscriptWithProteinDomains
] = useState<TranscriptWithProteinDomains | null>(null);
const [transcriptWithProteinDomains, setTranscriptWithProteinDomains] =
useState<TranscriptWithProteinDomains | null>(null);
const [proteinSummaryStats, setProteinSummaryStats] = useState<
ProteinStats | null | undefined
>();
const [proteinSummaryStats, setProteinSummaryStats] =
useState<ProteinStats | null | undefined>();
const [domainsLoadingState, setDomainsLoadingState] = useState<LoadingState>(
LoadingState.LOADING
);
const [
summaryStatsLoadingState,
setSummaryStatsLoadingState
] = useState<LoadingState>(LoadingState.LOADING);
const [summaryStatsLoadingState, setSummaryStatsLoadingState] =
useState<LoadingState>(LoadingState.LOADING);
const proteinId =
transcript.product_generating_contexts[0].product.unversioned_stable_id;
......@@ -296,10 +290,6 @@ export const ProteinExternalReferenceGroup = (
setXrefGroupOpen(!isXrefGroupOpen);
};
const chevronClasses = classNames(styles.chevron, {
[styles.chevronUp]: isXrefGroupOpen
});
if (xrefs.length > 3) {
const displayXref = xrefs[0];
return (
......@@ -318,7 +308,11 @@ export const ProteinExternalReferenceGroup = (
}
>
+ {xrefs.length - 1}
<ChevronDown className={chevronClasses} />
<Chevron
direction={isXrefGroupOpen ? 'up' : 'down'}
animate={true}
classNames={{ svg: styles.chevron }}
/>
</span>
</div>
</div>
......
......@@ -5,6 +5,12 @@ $submenuSidePadding: 30px;
.helpMenu {
grid-row: menu;
position: relative;
// increase specificity through nesting in order to be able to override child's styles
.chevron {
height: 7px;
fill: $blue;
}
}
.menuBar {
......@@ -67,8 +73,3 @@ $submenuSidePadding: 30px;
.submenuItem:hover {
background: $ice-blue;
}
.chevron {
height: 14px;
fill: $blue;
}
......@@ -20,7 +20,7 @@ import { useDispatch } from 'react-redux';
import { push } from 'connected-react-router';
import { Link } from 'react-router-dom';
import { ReactComponent as Chevron } from 'static/img/shared/chevron-right.svg';
import Chevron from 'src/shared/components/chevron/Chevron';
import {
Menu as MenuType,
......@@ -129,7 +129,7 @@ const Submenu = (props: SubmenuProps) => {
{item.type === 'collection' ? (
<>
{item.name}
<Chevron className={styles.chevron} />
<Chevron direction="right" classNames={{ svg: styles.chevron }} />
</>
) : (
item.name
......
......@@ -18,9 +18,12 @@ import React from 'react';
import noop from 'lodash/noop';
import classNames from 'classnames';
import { Consumer as ItemConsumer, ItemContext } from './ItemContext';
import Chevron from 'src/shared/components/chevron/Chevron';
import { InjectedButtonAttributes } from '../helpers/AccordionStore';
import { DivAttributes } from '../helpers/types';
import { Consumer as ItemConsumer, ItemContext } from './ItemContext';
import defaultStyles from '../css/Accordion.scss';
......@@ -36,6 +39,7 @@ export const AccordionItemButton = (props: Props) => {
extendDefaultStyles,
toggleExpanded,
disabled,
children,
...rest
} = props;
......@@ -55,7 +59,17 @@ export const AccordionItemButton = (props: Props) => {
className={styles}
onClick={disabled ? noop : toggleExpanded}
data-accordion-component="AccordionItemButton"
/>
>
<div>{children}</div>
{!disabled && (
<div>
<Chevron
direction={rest['aria-expanded'] ? 'up' : 'down'}
animate={true}
/>
</div>
)}
</div>
);
};
......
......@@ -10,6 +10,9 @@
}
.accordionButtonDefault {
display: grid;
grid-template-columns: 1fr auto;
column-gap: 0.6rem;
color: #444;
cursor: pointer;
padding: 18px;
......@@ -19,39 +22,11 @@
position: relative;
}
.accordionButtonDefault:after,
.accordionButtonDefault:before {
display: inline-block;
position: absolute;
margin-top: 10px;
content: '';
height: 2px;
width: 10px;
margin-right: 12px;
transition: transform 0.2s linear, top 0.2s linear;
background-color: $blue;
}
.accordionButtonDisabled {
display: block;
background-color: $light-grey;
color: $grey;
cursor: default;
&:after,
&:before {
display: none;
}
}
.accordionButtonDefault:before {
right: 21px;
top: 20px;
transform: rotate(45deg);
}
.accordionButtonDefault:after {
right: 15px;
top: 20px;
transform: rotate(-45deg);
}
.accordionButtonDefault[aria-expanded='true']::before,
......
......@@ -11,6 +11,13 @@
padding: 8px 20px;
min-height: 80px;
width: 100%;
// nesting to increase specificity to override child's styles
.chevron {
margin-left: 5px;
height: 7px;
fill: $grey;
}
}
.appBarTop {
......@@ -38,15 +45,9 @@
margin-left: 2em;
text-align: right;
a {
span {
color: $black;
font-size: 12px;
font-weight: $light;
}
img {
height: 14px;
margin-left: 5px;
width: 14px;
}
}
......@@ -16,7 +16,8 @@
import React from 'react';
import chevronRightIcon from 'static/img/shared/chevron-right-grey.svg';
import Chevron from 'src/shared/components/chevron/Chevron';
import styles from './AppBar.scss';
type AppBarProps = {
......@@ -37,9 +38,10 @@ export const AppBar = (props: AppBarProps) => (
export const HelpAndDocumentation = () => {
return (
<div className={styles.helpLink}>
<a className="inactive">
Help &amp; documentation <img src={chevronRightIcon} alt="" />
</a>
<span>
Help &amp; documentation
<Chevron direction="right" classNames={{ svg: styles.chevron }} />
</span>
</div>
);
};
......
@import 'src/styles/common';
.chevron {
fill: $blue;
height: 8px;
user-select: none;
}
.chevron_animated {
transition: transform 0.3s ease-in-out;
}
.chevron_up {
transform: rotate(-180deg);
}
.chevron_left {
transform: rotate(90deg);
}
.chevron_right {
transform: rotate(-90deg);
}
/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Chevron from './Chevron';
describe('<Chevron />', () => {
describe('default', () => {
it('renders correctly', () => {
const { container, rerender } = render(<Chevron direction="down" />);
const chevron = container.firstChild as HTMLElement;
expect(chevron.tagName.toLowerCase()).toBe('svg');
expect(chevron.classList.contains('chevron')).toBe(true);
rerender(<Chevron direction="up" />);
expect(chevron.classList.contains('chevron_up')).toBe(true);
rerender(<Chevron direction="left" />);
expect(chevron.classList.contains('chevron_left')).toBe(true);
rerender(<Chevron direction="right" />);
expect(chevron.classList.contains('chevron_right')).toBe(true);
rerender(<Chevron direction="down" animate={true} />);
expect(chevron.classList.contains('chevron_animated')).toBe(true);
});
});