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

Update species lozenges (#760)

- Extracted a dedicated SpeciesLozenge component out of the SelectedSpecies component for better reusability
   (SpeciesLozenge has a theme prop; knows how to present the data)
- Updated visual styles of the lozenges
parent ae08cae1
Pipeline #288725 passed with stages
in 5 minutes and 13 seconds
......@@ -17,7 +17,6 @@
import React, { useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import noop from 'lodash/noop';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { AppName } from 'src/global/globalConfig';
......@@ -28,7 +27,7 @@ import { addSelectedSpecies } from 'src/content/app/tools/blast/state/blast-form
import { getSelectedSpeciesIds } from 'src/content/app/tools/blast/state/blast-form/blastFormSelectors';
import AppBar from 'src/shared/components/app-bar/AppBar';
import { SelectedSpecies } from 'src/shared/components/selected-species';
import { SpeciesLozenge } from 'src/shared/components/selected-species';
import SpeciesTabsWrapper from 'src/shared/components/species-tabs-wrapper/SpeciesTabsWrapper';
import type { CommittedItem } from 'src/content/app/species-selector/types/species-search';
......@@ -63,20 +62,16 @@ const BlastAppBar = (props: Props) => {
};
const enabledSpecies = speciesList.map((species, index) => (
<SelectedSpecies
<SpeciesLozenge
key={index}
theme="blue"
species={species}
onClick={() => speciesLozengeClick(species)}
/>
));
const disabledSpecies = speciesList.map((species, index) => (
<SelectedSpecies
key={index}
isActive={true}
species={{ ...species, isEnabled: false }}
onClick={noop}
/>
<SpeciesLozenge key={index} theme="grey" species={species} />
));
const speciesTabs = view === 'blast-form' ? enabledSpecies : disabledSpecies;
......
@import 'src/styles/common';
@import 'src/styles/mixins';
.species {
@include lozenge;
border: 1px solid $blue;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: $white;
margin-bottom: 12px;
&:not(:last-child) {
margin-right: 15px;
}
}
.nameActive,
.assemblyActive {
color: $white;
}
.inUseActive {
background-color: $black;
border: 1px solid $black;
cursor: default;
}
.inUseInactive {
background-color: $blue;
border: 1px solid $blue;
}
.notInUseActive {
background-color: $grey;
border: 1px solid $grey;
cursor: auto;
}
.notInUseInactive {
background-color: $ice-blue;
border: 1px solid $ice-blue;
color: $blue;
}
......@@ -49,31 +49,31 @@ describe('<SelectedSpecies />', () => {
it('has correct classes when active and enabled', () => {
const { container } = renderSelectedSpecies(minimalProps);
const lozenge = container.firstChild as HTMLElement;
expect(lozenge.classList.contains('inUseActive')).toBe(true);
expect(lozenge.classList.contains('themeBlack')).toBe(true);
});
it('has correct classes when active and not enabled', () => {
const props = set('species.isEnabled', false, minimalProps);
const { container } = renderSelectedSpecies(props);
const lozenge = container.firstChild as HTMLElement;
expect(lozenge.classList.contains('notInUseActive')).toBe(true);
expect(lozenge.classList.contains('themeGrey')).toBe(true);
});
it('has correct classes when inactive and enabled', () => {
const props = set('isActive', false, minimalProps);
const { container } = renderSelectedSpecies(props);
const lozenge = container.firstChild as HTMLElement;
expect(lozenge.classList.contains('inUseInactive')).toBe(true);
expect(lozenge.classList.contains('themeBlue')).toBe(true);
});
it('has correct classes when inactive and disabled', () => {
it('has correct classes when inactive and not enabled', () => {
const props = merge(minimalProps, {
isActive: false,
species: { isEnabled: false }
});
const { container } = renderSelectedSpecies(props);
const lozenge = container.firstChild as HTMLElement;
expect(lozenge.classList.contains('notInUseInactive')).toBe(true);
expect(lozenge.classList.contains('themeIceBlue')).toBe(true);
});
});
......
......@@ -15,75 +15,60 @@
*/
import React from 'react';
import classNames from 'classnames';
import SelectedSpeciesContent from './SelectedSpeciesContent';
import SpeciesLozenge from './SpeciesLozenge';
import styles from './SelectedSpecies.scss';
import { CommittedItem } from 'src/content/app/species-selector/types/species-search';
import type { CommittedItem } from 'src/content/app/species-selector/types/species-search';
export type Props = {
species: CommittedItem;
isActive: boolean;
isActive?: boolean;
onClick: (genomeId: string) => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
className?: string;
};
const chooseClassName = (props: Props) => {
const SelectedSpecies = (props: Props) => {
return (
<SpeciesLozenge
species={props.species}
className={props.className}
onMouseEnter={props.onMouseEnter}
onMouseLeave={props.onMouseLeave}
{...getSpeciesLozengeProps(props)}
/>
);
};
const getSpeciesLozengeProps = (props: Props) => {
const {
isActive,
species: { isEnabled }
isActive = false,
species: { isEnabled },
onClick
} = props;
// TODO: add invalid (red) species when we start having them
if (isActive && isEnabled) {
return styles.inUseActive;
return {
theme: 'black'
} as const;
} else if (isActive && !isEnabled) {
return styles.notInUseActive;
return {
theme: 'grey'
} as const;
} else if (!isActive && isEnabled) {
return styles.inUseInactive;
return {
theme: 'blue',
onClick
} as const;
} else {
return styles.notInUseInactive;
return {
theme: 'ice-blue',
onClick
} as const;
}
};
const SelectedSpecies = (props: Props) => {
const handleMouseEnter = () => {
props.onMouseEnter && props.onMouseEnter();
};
const handleMouseLeave = () => {
props.onMouseLeave && props.onMouseLeave();
};
const handleClick = () => {
if (!props.isActive) {
props.onClick(props.species.genome_id);
}
};
const className = classNames(
styles.species,
chooseClassName(props),
props.className
);
return (
<div
className={className}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
>
<SelectedSpeciesContent species={props.species} />
</div>
);
};
SelectedSpecies.defaultProps = {
isActive: false
};
export default SelectedSpecies;
@import 'src/styles/common';
@import 'src/styles/mixins';
.species {
display: inline-flex;
align-items: center;
height: 28px;
padding: 0 20px;
border-width: 1px;
border-radius: 20px;
border-style: solid;
overflow: hidden;
user-select: none;
}
.themeBlue {
border-color: var(--species-lozenge-bg-color, #{$blue});
background-color: var(--species-lozenge-bg-color, #{$blue});
color: var(--species-lozenge-text-color, #{$white});
}
.themeBlack {
border-color: var(--species-lozenge-bg-color, #{$black});
background-color: var(--species-lozenge-bg-color, #{$black});
color: var(--species-lozenge-text-color, #{$white});
}
.themeIceBlue {
border-color: var(--species-lozenge-bg-color, #{$ice-blue});
background-color: var(--species-lozenge-bg-color, #{$ice-blue});
color: var(--species-lozenge-text-color, #{$blue});
}
.themeGrey {
border-color: var(--species-lozenge-bg-color, #{$grey});
background-color: var(--species-lozenge-bg-color, #{$grey});
color: var(--species-lozenge-text-color, #{$white});
}
.themeRed {
border-color: var(--species-lozenge-bg-color, #{$red});
background-color: var(--species-lozenge-bg-color, #{$red});
color: var(--species-lozenge-text-color, #{$white});
}
.clickable {
cursor: pointer;
}
.inner {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1;
}
.name {
font-size: 15px;
font-weight: $bold;
margin-right: 10px;
}
.assembly {
font-size: 11px;
}
......@@ -16,40 +16,61 @@
import React from 'react';
import classNames from 'classnames';
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import { getDisplayName } from './selectedSpeciesHelpers';
import { CommittedItem } from 'src/content/app/species-selector/types/species-search';
import type { CommittedItem } from 'src/content/app/species-selector/types/species-search';
import styles from './selected-species-common.scss';
import styles from './SpeciesLozenge.scss';
type Props = {
type SpeciesLozengeTheme = 'blue' | 'black' | 'ice-blue' | 'grey' | 'red';
export type Props = {
species: CommittedItem;
classNames?: {
name?: string;
assembly?: string;
};
theme: SpeciesLozengeTheme;
className?: string;
onClick?: (genomeId: string) => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
};
const SelectedSpeciesContent = (props: Props) => {
const displayName = getDisplayName(props.species);
const SpeciesLozenge = (props: Props) => {
const handleMouseEnter = () => {
props?.onMouseEnter?.();
};
const nameClasses = classNames(
styles.name,
props.classNames && props.classNames.name
);
const handleMouseLeave = () => {
props?.onMouseLeave?.();
};
const handleClick = () => {
props?.onClick?.(props.species.genome_id);
};
const displayName = getDisplayName(props.species);
const assemblyClasses = classNames(
styles.assembly,
props.classNames && props.classNames.assembly
const componentClasses = classNames(
styles.species,
styles[`theme${upperFirst(camelCase(props.theme))}`],
{ [styles.clickable]: Boolean(props.onClick) },
props.className
);
return (
<>
<span className={nameClasses}>{displayName}</span>
<span className={assemblyClasses}>{props.species.assembly_name}</span>
</>
<div
className={componentClasses}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
>
<div className={styles.inner}>
<span className={styles.name}>{displayName}</span>
<span className={styles.assembly}>{props.species.assembly_name}</span>
</div>
</div>
);
};
export default SelectedSpeciesContent;
export default SpeciesLozenge;
......@@ -15,3 +15,4 @@
*/
export { default as SelectedSpecies } from './SelectedSpecies';
export { default as SpeciesLozenge } from './SpeciesLozenge';
@import 'src/styles/common';
$selectedSpeciesBorderRadius: 20px;
$selectedSpeciesPadding: 6px 20px;
$deleteButtonPadding: 8px 20px 8px 28px;
@mixin selected-species-common {
display: inline-block;
padding: $selectedSpeciesPadding;
border-radius: $selectedSpeciesBorderRadius;
line-height: 1;
user-select: none;
margin-bottom: 4px;
&:not(:last-child) {
margin-right: 12px;
}
}
.name {
font-size: 14px;
font-weight: $bold;
margin-right: 0.5rem;
}
.assembly {
font-size: 11px;
}
......@@ -16,10 +16,10 @@
import { CommittedItem } from 'src/content/app/species-selector/types/species-search';
const SPECIES_NAME_SIZE = 14;
const SPECIES_NAME_SIZE = 15;
const ASSEMBLY_NAME_SIZE = 11;
const PADDING_SIZE = 20;
const SPACE_BETWEEN = 7;
const SPACE_BETWEEN = 10;
const BORDER_WIDTH = 1;
export const getDisplayName = (species: CommittedItem) =>
......
.multiLineSpeciesWrapper{
.multiLineSpeciesWrapper {
display: flex;
flex-wrap: wrap;
column-gap: 15px;
row-gap: 12px;
}
.linkWrapper {
......
@import 'src/styles/common';
.wrapper {
display: flex;
flex-direction: column;
row-gap: 1.5rem;
padding: 40px;
}
.innerWrapper {
display: flex;
align-items: center;
column-gap: 1.5rem;
}
......@@ -16,7 +16,7 @@
import React from 'react';
import SelectedSpecies from 'src/shared/components/selected-species/SelectedSpecies';
import SpeciesLozenge from 'src/shared/components/selected-species/SpeciesLozenge';
import speciesData from '../species-tabs-wrapper/speciesData';
......@@ -32,33 +32,41 @@ type StoryArgs = {
};
export const SelectedSpeciesStory = (args: StoryArgs) => {
const enabledSpecies = speciesData[0];
const disabledSpecies = {
...enabledSpecies,
isEnabled: false
};
const species = speciesData[0];
return (
<div className={styles.wrapper}>
<SelectedSpecies
species={enabledSpecies}
onClick={args.onClick}
></SelectedSpecies>
<SelectedSpecies
species={enabledSpecies}
isActive={true}
onClick={args.onClick}
></SelectedSpecies>
<SelectedSpecies
species={disabledSpecies}
onClick={args.onClick}
></SelectedSpecies>
<SelectedSpecies
species={disabledSpecies}
isActive={true}
onClick={args.onClick}
></SelectedSpecies>
</div>
<>
<div className={styles.wrapper}>
<div className={styles.innerWrapper}>
<SpeciesLozenge theme="blue" species={species} />
<span>blue theme</span>
</div>
<div className={styles.innerWrapper}>
<SpeciesLozenge theme="black" species={species} />
<span>black theme</span>
</div>
<div className={styles.innerWrapper}>
<SpeciesLozenge theme="ice-blue" species={species} />
<span>ice-blue theme</span>
</div>
<div className={styles.innerWrapper}>
<SpeciesLozenge theme="grey" species={species} />
<span>grey theme</span>
</div>
<div className={styles.innerWrapper}>
<SpeciesLozenge theme="red" species={species} />
<span>red theme</span>
</div>
</div>
<p>A clickable lozenge will change the cursor to hover, like so:</p>
<div>
<SpeciesLozenge theme="blue" species={species} onClick={args.onClick} />
</div>
</>
);
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment