@@ -76,7 +76,7 @@ export default class Facet extends React.PureComponent<Props> { | |||
renderItem = (value: string) => { | |||
const active = this.props.values.includes(value); | |||
const stat = this.getStat(value); | |||
const { renderName = defaultRenderName } = this.props; | |||
const { renderName = defaultRenderName, renderTextName = defaultRenderName } = this.props; | |||
return ( | |||
<FacetItem | |||
@@ -87,7 +87,7 @@ export default class Facet extends React.PureComponent<Props> { | |||
name={renderName(value)} | |||
onClick={this.handleItemClick} | |||
stat={stat && formatMeasure(stat, 'SHORT_INT')} | |||
tooltip={this.props.values.length === 1 && !active} | |||
tooltip={renderTextName(value)} | |||
value={value} | |||
/> | |||
); |
@@ -88,6 +88,11 @@ export default class ProfileFacet extends React.PureComponent<Props> { | |||
} | |||
}; | |||
getTooltip = (profile: Profile) => { | |||
const base = `${profile.name} ${profile.languageName}`; | |||
return profile.isBuiltIn ? `${base} (${translate('quality_profiles.built_in')})` : base; | |||
}; | |||
renderName = (profile: Profile) => ( | |||
<> | |||
{profile.name} | |||
@@ -138,6 +143,7 @@ export default class ProfileFacet extends React.PureComponent<Props> { | |||
name={this.renderName(profile)} | |||
onClick={this.handleItemClick} | |||
stat={this.renderActivation(profile)} | |||
tooltip={this.getTooltip(profile)} | |||
value={profile.key} | |||
/> | |||
); |
@@ -36,9 +36,32 @@ | |||
padding: 4px 6px; | |||
} | |||
.facet .domain-measures-leak { | |||
padding: 3px 8px; | |||
margin: -5px -5px; | |||
.search-navigator-facet .domain-measures-leak { | |||
height: var(--controlHeight); | |||
line-height: var(--controlHeight); | |||
padding: 0 var(--gridSize); | |||
margin-top: -1px; | |||
margin-right: calc(-0.75 * var(--gridSize) - 1px); | |||
border-radius: 2px; | |||
box-sizing: border-box; | |||
} | |||
.search-navigator-facet:hover .domain-measures-leak, | |||
.search-navigator-facet.active .domain-measures-leak { | |||
height: calc(var(--controlHeight) - 2px); | |||
margin-top: 0; | |||
margin-right: calc(-0.75 * var(--gridSize)); | |||
border-top: none; | |||
border-bottom: none; | |||
border-right: none; | |||
border-top-left-radius: 0; | |||
border-bottom-left-radius: 0; | |||
} | |||
.search-navigator-facet.active .domain-measures-leak { | |||
border-left: none; | |||
border-top-left-radius: 0; | |||
border-bottom-left-radius: 0; | |||
} | |||
.domain-measures-leak-header { |
@@ -29,6 +29,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import Avatar from '../../../components/ui/Avatar'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
export interface Props { | |||
assigned: boolean; | |||
@@ -90,25 +91,28 @@ export default class AssigneeFacet extends React.PureComponent<Props> { | |||
return assignee === '' ? !this.props.assigned : this.props.assignees.includes(assignee); | |||
} | |||
getAssigneeName(assignee: string) { | |||
getAssigneeNameAndTooltip(assignee: string) { | |||
if (assignee === '') { | |||
return translate('unassigned'); | |||
return { name: translate('unassigned'), tooltip: translate('unassigned') }; | |||
} else { | |||
const { referencedUsers } = this.props; | |||
if (referencedUsers[assignee]) { | |||
return ( | |||
<span> | |||
<Avatar | |||
className="little-spacer-right" | |||
hash={referencedUsers[assignee].avatar} | |||
name={referencedUsers[assignee].name} | |||
size={16} | |||
/> | |||
{referencedUsers[assignee].name} | |||
</span> | |||
); | |||
return { | |||
name: ( | |||
<span> | |||
<Avatar | |||
className="little-spacer-right" | |||
hash={referencedUsers[assignee].avatar} | |||
name={referencedUsers[assignee].name} | |||
size={16} | |||
/> | |||
{referencedUsers[assignee].name} | |||
</span> | |||
), | |||
tooltip: referencedUsers[assignee].name | |||
}; | |||
} else { | |||
return assignee; | |||
return { name: assignee, tooltip: assignee }; | |||
} | |||
} | |||
} | |||
@@ -145,6 +149,22 @@ export default class AssigneeFacet extends React.PureComponent<Props> { | |||
); | |||
}; | |||
renderListItem(assignee: string) { | |||
const { name, tooltip } = this.getAssigneeNameAndTooltip(assignee); | |||
return ( | |||
<FacetItem | |||
active={this.isAssigneeActive(assignee)} | |||
key={assignee} | |||
loading={this.props.loading} | |||
name={name} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(assignee))} | |||
tooltip={tooltip} | |||
value={assignee} | |||
/> | |||
); | |||
} | |||
renderList() { | |||
const { stats } = this.props; | |||
@@ -161,20 +181,7 @@ export default class AssigneeFacet extends React.PureComponent<Props> { | |||
); | |||
return ( | |||
<FacetItemsList> | |||
{assignees.map(assignee => ( | |||
<FacetItem | |||
active={this.isAssigneeActive(assignee)} | |||
key={assignee} | |||
loading={this.props.loading} | |||
name={this.getAssigneeName(assignee)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(assignee))} | |||
tooltip={this.props.assignees.length === 1 && !this.isAssigneeActive(assignee)} | |||
value={assignee} | |||
/> | |||
))} | |||
</FacetItemsList> | |||
<FacetItemsList>{assignees.map(assignee => this.renderListItem(assignee))}</FacetItemsList> | |||
); | |||
} | |||
@@ -193,6 +200,7 @@ export default class AssigneeFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const { assignees, stats = {} } = this.props; | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -204,8 +212,13 @@ export default class AssigneeFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && this.renderFooter()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
{this.renderFooter()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={assignees.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -26,6 +26,7 @@ import FacetItem from '../../../components/facet/FacetItem'; | |||
import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -90,7 +91,7 @@ export default class AuthorFacet extends React.PureComponent<Props> { | |||
name={author} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(author))} | |||
tooltip={this.props.authors.length === 1 && !this.props.authors.includes(author)} | |||
tooltip={author} | |||
value={author} | |||
/> | |||
))} | |||
@@ -99,6 +100,7 @@ export default class AuthorFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const { authors, stats = {} } = this.props; | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -110,7 +112,12 @@ export default class AuthorFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={authors.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -229,6 +229,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { | |||
loading={this.props.loading} | |||
name={translate('issues.facet.createdAt.all')} | |||
onClick={this.handlePeriodClick} | |||
tooltip={translate('issues.facet.createdAt.all')} | |||
value="" | |||
/> | |||
{component ? ( | |||
@@ -237,6 +238,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { | |||
loading={this.props.loading} | |||
name={translate('issues.new_code')} | |||
onClick={this.handleLeakPeriodClick} | |||
tooltip={translate('issues.leak_period')} | |||
value="" | |||
/> | |||
) : ( | |||
@@ -246,6 +248,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { | |||
loading={this.props.loading} | |||
name={translate('issues.facet.createdAt.last_week')} | |||
onClick={this.handlePeriodClick} | |||
tooltip={translate('issues.facet.createdAt.last_week')} | |||
value="1w" | |||
/> | |||
<FacetItem | |||
@@ -253,6 +256,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { | |||
loading={this.props.loading} | |||
name={translate('issues.facet.createdAt.last_month')} | |||
onClick={this.handlePeriodClick} | |||
tooltip={translate('issues.facet.createdAt.last_month')} | |||
value="1m" | |||
/> | |||
<FacetItem | |||
@@ -260,6 +264,7 @@ export default class CreationDateFacet extends React.PureComponent<Props> { | |||
loading={this.props.loading} | |||
name={translate('issues.facet.createdAt.last_year')} | |||
onClick={this.handlePeriodClick} | |||
tooltip={translate('issues.facet.createdAt.last_year')} | |||
value="1y" | |||
/> | |||
</> |
@@ -28,6 +28,7 @@ import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { collapsePath } from '../../../helpers/path'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -93,7 +94,8 @@ export default class DirectoryFacet extends React.PureComponent<Props> { | |||
return null; | |||
} | |||
const directories = sortBy(Object.keys(stats), key => -stats[key]); | |||
// sort directories first by counts, then by path | |||
const directories = sortBy(Object.keys(stats), key => -stats[key], d => d); | |||
return ( | |||
<FacetItemsList> | |||
@@ -105,9 +107,7 @@ export default class DirectoryFacet extends React.PureComponent<Props> { | |||
name={this.renderName(directory)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(directory))} | |||
tooltip={ | |||
this.props.directories.length === 1 && !this.props.directories.includes(directory) | |||
} | |||
tooltip={directory} | |||
value={directory} | |||
/> | |||
))} | |||
@@ -116,7 +116,8 @@ export default class DirectoryFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const values = this.props.directories.map(dir => collapsePath(dir)); | |||
const { directories, stats = {} } = this.props; | |||
const values = directories.map(dir => collapsePath(dir)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -128,7 +129,15 @@ export default class DirectoryFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
<MultipleSelectionHint | |||
options={Object.keys(stats).length} | |||
values={directories.length} | |||
/> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -28,6 +28,7 @@ import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { collapsePath } from '../../../helpers/path'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -108,7 +109,7 @@ export default class FileFacet extends React.PureComponent<Props> { | |||
name={this.renderName(file)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(file))} | |||
tooltip={this.props.files.length === 1 && !this.props.files.includes(file)} | |||
tooltip={this.getFileName(file)} | |||
value={file} | |||
/> | |||
))} | |||
@@ -117,7 +118,8 @@ export default class FileFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const values = this.props.files.map(file => this.getFileName(file)); | |||
const { files, stats = {} } = this.props; | |||
const values = files.map(file => this.getFileName(file)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -129,7 +131,12 @@ export default class FileFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={files.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -27,6 +27,7 @@ import FacetItem from '../../../components/facet/FacetItem'; | |||
import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -102,7 +103,7 @@ export default class LanguageFacet extends React.PureComponent<Props> { | |||
name={this.getLanguageName(language)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(language))} | |||
tooltip={this.props.languages.length === 1 && !this.props.languages.includes(language)} | |||
tooltip={this.getLanguageName(language)} | |||
value={language} | |||
/> | |||
))} | |||
@@ -121,6 +122,7 @@ export default class LanguageFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const { languages, stats = {} } = this.props; | |||
const values = this.props.languages.map(language => this.getLanguageName(language)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
@@ -133,8 +135,13 @@ export default class LanguageFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && this.renderFooter()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
{this.renderFooter()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={languages.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -27,6 +27,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -106,7 +107,7 @@ export default class ModuleFacet extends React.PureComponent<Props> { | |||
name={this.renderName(module)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(module))} | |||
tooltip={this.props.modules.length === 1 && !this.props.modules.includes(module)} | |||
tooltip={this.getModuleName(module)} | |||
value={module} | |||
/> | |||
))} | |||
@@ -115,7 +116,8 @@ export default class ModuleFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const values = this.props.modules.map(module => this.getModuleName(module)); | |||
const { modules, stats = {} } = this.props; | |||
const values = modules.map(module => this.getModuleName(module)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -127,7 +129,12 @@ export default class ModuleFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={modules.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -31,6 +31,7 @@ import Organization from '../../../components/shared/Organization'; | |||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
component: Component | undefined; | |||
@@ -114,22 +115,33 @@ export default class ProjectFacet extends React.PureComponent<Props> { | |||
return referencedComponents[project] ? referencedComponents[project].name : project; | |||
} | |||
renderName(project: string) { | |||
getProjectNameAndTooltip(project: string) { | |||
const { organization, referencedComponents } = this.props; | |||
return referencedComponents[project] ? ( | |||
<span> | |||
<QualifierIcon className="little-spacer-right" qualifier="TRK" /> | |||
{!organization && ( | |||
<Organization link={false} organizationKey={referencedComponents[project].organization} /> | |||
)} | |||
{referencedComponents[project].name} | |||
</span> | |||
) : ( | |||
<span> | |||
<QualifierIcon className="little-spacer-right" qualifier="TRK" /> | |||
{project} | |||
</span> | |||
); | |||
return referencedComponents[project] | |||
? { | |||
name: ( | |||
<span> | |||
<QualifierIcon className="little-spacer-right" qualifier="TRK" /> | |||
{!organization && ( | |||
<Organization | |||
link={false} | |||
organizationKey={referencedComponents[project].organization} | |||
/> | |||
)} | |||
{referencedComponents[project].name} | |||
</span> | |||
), | |||
tooltip: referencedComponents[project].name | |||
} | |||
: { | |||
name: ( | |||
<span> | |||
<QualifierIcon className="little-spacer-right" qualifier="TRK" /> | |||
{project} | |||
</span> | |||
), | |||
tooltip: project | |||
}; | |||
} | |||
renderOption = (option: { label: string; organization: string }) => { | |||
@@ -141,6 +153,22 @@ export default class ProjectFacet extends React.PureComponent<Props> { | |||
); | |||
}; | |||
renderListItem(project: string) { | |||
const { name, tooltip } = this.getProjectNameAndTooltip(project); | |||
return ( | |||
<FacetItem | |||
active={this.props.projects.includes(project)} | |||
key={project} | |||
loading={this.props.loading} | |||
name={name} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(project))} | |||
tooltip={tooltip} | |||
value={project} | |||
/> | |||
); | |||
} | |||
renderList() { | |||
const { stats } = this.props; | |||
@@ -150,22 +178,7 @@ export default class ProjectFacet extends React.PureComponent<Props> { | |||
const projects = sortBy(Object.keys(stats), key => -stats[key]); | |||
return ( | |||
<FacetItemsList> | |||
{projects.map(project => ( | |||
<FacetItem | |||
active={this.props.projects.includes(project)} | |||
key={project} | |||
loading={this.props.loading} | |||
name={this.renderName(project)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(project))} | |||
tooltip={this.props.projects.length === 1 && !this.props.projects.includes(project)} | |||
value={project} | |||
/> | |||
))} | |||
</FacetItemsList> | |||
); | |||
return <FacetItemsList>{projects.map(project => this.renderListItem(project))}</FacetItemsList>; | |||
} | |||
renderFooter() { | |||
@@ -184,6 +197,7 @@ export default class ProjectFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const { projects, stats = {} } = this.props; | |||
const values = this.props.projects.map(project => this.getProjectName(project)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
@@ -195,8 +209,13 @@ export default class ProjectFacet extends React.PureComponent<Props> { | |||
values={values} | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && this.renderFooter()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
{this.renderFooter()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={projects.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -26,6 +26,7 @@ import FacetItem from '../../../components/facet/FacetItem'; | |||
import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -38,6 +39,8 @@ interface Props { | |||
stats: { [x: string]: number } | undefined; | |||
} | |||
const RESOLUTIONS = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED']; | |||
export default class ResolutionFacet extends React.PureComponent<Props> { | |||
property = 'resolutions'; | |||
@@ -101,19 +104,15 @@ export default class ResolutionFacet extends React.PureComponent<Props> { | |||
name={this.getFacetItemName(resolution)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(stat)} | |||
tooltip={ | |||
this.props.resolutions.length === 1 && | |||
resolution !== '' && | |||
!this.props.resolutions.includes(resolution) | |||
} | |||
tooltip={this.getFacetItemName(resolution)} | |||
value={resolution} | |||
/> | |||
); | |||
}; | |||
render() { | |||
const resolutions = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED']; | |||
const values = this.props.resolutions.map(resolution => this.getFacetItemName(resolution)); | |||
const { resolutions, stats = {} } = this.props; | |||
const values = resolutions.map(resolution => this.getFacetItemName(resolution)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
@@ -127,7 +126,15 @@ export default class ResolutionFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && <FacetItemsList>{resolutions.map(this.renderItem)}</FacetItemsList>} | |||
{this.props.open && ( | |||
<> | |||
<FacetItemsList>{RESOLUTIONS.map(this.renderItem)}</FacetItemsList> | |||
<MultipleSelectionHint | |||
options={Object.keys(stats).length} | |||
values={resolutions.length} | |||
/> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -28,6 +28,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import FacetFooter from '../../../components/facet/FacetFooter'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -78,6 +79,7 @@ export default class RuleFacet extends React.PureComponent<Props> { | |||
languages: languages.length ? languages.join() : undefined, | |||
organization, | |||
q: query, | |||
// eslint-disable-next-line camelcase | |||
include_external: true | |||
}).then(response => | |||
response.rules.map(rule => ({ label: `(${rule.langName}) ${rule.name}`, value: rule.key })) | |||
@@ -118,7 +120,7 @@ export default class RuleFacet extends React.PureComponent<Props> { | |||
name={this.getRuleName(rule)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(rule))} | |||
tooltip={this.props.rules.length === 1 && !this.props.rules.includes(rule)} | |||
tooltip={this.getRuleName(rule)} | |||
value={rule} | |||
/> | |||
))} | |||
@@ -135,7 +137,8 @@ export default class RuleFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const values = this.props.rules.map(rule => this.getRuleName(rule)); | |||
const { rules, stats = {} } = this.props; | |||
const values = rules.map(rule => this.getRuleName(rule)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -147,8 +150,13 @@ export default class RuleFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && this.renderFooter()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
{this.renderFooter()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={rules.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -27,6 +27,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import SeverityHelper from '../../../components/shared/SeverityHelper'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -38,6 +39,8 @@ interface Props { | |||
stats: { [x: string]: number } | undefined; | |||
} | |||
const SEVERITIES = ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR']; | |||
export default class SeverityFacet extends React.PureComponent<Props> { | |||
property = 'severities'; | |||
@@ -86,15 +89,15 @@ export default class SeverityFacet extends React.PureComponent<Props> { | |||
name={<SeverityHelper severity={severity} />} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(stat)} | |||
tooltip={this.props.severities.length === 1 && !this.props.severities.includes(severity)} | |||
tooltip={translate('severity', severity)} | |||
value={severity} | |||
/> | |||
); | |||
}; | |||
render() { | |||
const severities = ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR']; | |||
const values = this.props.severities.map(severity => translate('severity', severity)); | |||
const { severities, stats = {} } = this.props; | |||
const values = severities.map(severity => translate('severity', severity)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
@@ -107,7 +110,12 @@ export default class SeverityFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && <FacetItemsList>{severities.map(this.renderItem)}</FacetItemsList>} | |||
{this.props.open && ( | |||
<> | |||
<FacetItemsList>{SEVERITIES.map(this.renderItem)}</FacetItemsList> | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={severities.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -33,6 +33,7 @@ import { | |||
Standards | |||
} from '../../securityReports/utils'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
export interface Props { | |||
cwe: string[]; | |||
@@ -57,6 +58,9 @@ interface State { | |||
standards: Standards; | |||
} | |||
type StatsProp = 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats'; | |||
type ValuesProp = 'owaspTop10' | 'sansTop25' | 'cwe'; | |||
export default class StandardFacet extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
property = STANDARDS; | |||
@@ -131,11 +135,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
this.props.onChange({ [this.property]: [], owaspTop10: [], sansTop25: [], cwe: [] }); | |||
}; | |||
handleItemClick = ( | |||
prop: 'owaspTop10' | 'sansTop25' | 'cwe', | |||
itemValue: string, | |||
multiple: boolean | |||
) => { | |||
handleItemClick = (prop: ValuesProp, itemValue: string, multiple: boolean) => { | |||
const items = this.props[prop]; | |||
if (multiple) { | |||
const newValue = sortBy( | |||
@@ -166,8 +166,8 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
}; | |||
renderList = ( | |||
statsProp: 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats', | |||
valuesProp: 'owaspTop10' | 'cwe' | 'sansTop25', | |||
statsProp: StatsProp, | |||
valuesProp: ValuesProp, | |||
renderName: (standards: Standards, category: string) => string, | |||
onClick: (x: string, multiple?: boolean) => void | |||
) => { | |||
@@ -202,7 +202,7 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
name={renderName(this.state.standards, category)} | |||
onClick={onClick} | |||
stat={formatFacetStat(getStat(category))} | |||
tooltip={values.length === 1 && !values.includes(category)} | |||
tooltip={renderName(this.state.standards, category)} | |||
value={category} | |||
/> | |||
))} | |||
@@ -210,6 +210,12 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
); | |||
}; | |||
renderHint = (statsProp: StatsProp, valuesProp: ValuesProp) => { | |||
const stats = this.props[statsProp] || {}; | |||
const values = this.props[valuesProp]; | |||
return <MultipleSelectionHint options={Object.keys(stats).length} values={values.length} />; | |||
}; | |||
renderOwaspTop10List() { | |||
return this.renderList( | |||
'owaspTop10Stats', | |||
@@ -219,6 +225,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
); | |||
} | |||
renderOwaspTop10Hint() { | |||
return this.renderHint('owaspTop10Stats', 'owaspTop10'); | |||
} | |||
renderCWEList() { | |||
return this.renderList('cweStats', 'cwe', renderCWECategory, this.handleCWEItemClick); | |||
} | |||
@@ -243,6 +253,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
); | |||
} | |||
renderCWEHint() { | |||
return this.renderHint('cweStats', 'cwe'); | |||
} | |||
renderSansTop25List() { | |||
return this.renderList( | |||
'sansTop25Stats', | |||
@@ -252,6 +266,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
); | |||
} | |||
renderSansTop25Hint() { | |||
return this.renderHint('sansTop25Stats', 'sansTop25'); | |||
} | |||
renderSubFacets() { | |||
return ( | |||
<> | |||
@@ -265,7 +283,12 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
)} | |||
/> | |||
<DeferredSpinner loading={this.props.fetchingOwaspTop10} /> | |||
{this.props.owaspTop10Open && this.renderOwaspTop10List()} | |||
{this.props.owaspTop10Open && ( | |||
<> | |||
{this.renderOwaspTop10List()} | |||
{this.renderOwaspTop10Hint()} | |||
</> | |||
)} | |||
</FacetBox> | |||
<FacetBox className="is-inner" property="sansTop25"> | |||
<FacetHeader | |||
@@ -277,7 +300,12 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
)} | |||
/> | |||
<DeferredSpinner loading={this.props.fetchingSansTop25} /> | |||
{this.props.sansTop25Open && this.renderSansTop25List()} | |||
{this.props.sansTop25Open && ( | |||
<> | |||
{this.renderSansTop25List()} | |||
{this.renderSansTop25Hint()} | |||
</> | |||
)} | |||
</FacetBox> | |||
<FacetBox className="is-inner" property="cwe"> | |||
<FacetHeader | |||
@@ -287,8 +315,13 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
values={this.props.cwe.map(item => renderCWECategory(this.state.standards, item))} | |||
/> | |||
<DeferredSpinner loading={this.props.fetchingCwe} /> | |||
{this.props.cweOpen && this.renderCWEList()} | |||
{this.props.cweOpen && this.renderCWESearch()} | |||
{this.props.cweOpen && ( | |||
<> | |||
{this.renderCWEList()} | |||
{this.renderCWESearch()} | |||
{this.renderCWEHint()} | |||
</> | |||
)} | |||
</FacetBox> | |||
</> | |||
); |
@@ -27,6 +27,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import StatusHelper from '../../../components/shared/StatusHelper'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -38,6 +39,8 @@ interface Props { | |||
statuses: string[]; | |||
} | |||
const STATUSES = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED']; | |||
export default class StatusFacet extends React.PureComponent<Props> { | |||
property = 'statuses'; | |||
@@ -86,15 +89,15 @@ export default class StatusFacet extends React.PureComponent<Props> { | |||
name={<StatusHelper resolution={undefined} status={status} />} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(stat)} | |||
tooltip={this.props.statuses.length === 1 && !this.props.statuses.includes(status)} | |||
tooltip={translate('issue.status', status)} | |||
value={status} | |||
/> | |||
); | |||
}; | |||
render() { | |||
const statuses = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED']; | |||
const values = this.props.statuses.map(status => translate('issue.status', status)); | |||
const { statuses, stats = {} } = this.props; | |||
const values = statuses.map(status => translate('issue.status', status)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
@@ -107,7 +110,12 @@ export default class StatusFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && <FacetItemsList>{statuses.map(this.renderItem)}</FacetItemsList>} | |||
{this.props.open && ( | |||
<> | |||
<FacetItemsList>{STATUSES.map(this.renderItem)}</FacetItemsList> | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={statuses.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -31,6 +31,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import TagsIcon from '../../../components/icons-components/TagsIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
component: Component | undefined; | |||
@@ -118,7 +119,7 @@ export default class TagFacet extends React.PureComponent<Props> { | |||
name={this.renderTag(tag)} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(this.getStat(tag))} | |||
tooltip={this.props.tags.length === 1 && !this.props.tags.includes(tag)} | |||
tooltip={tag} | |||
value={tag} | |||
/> | |||
))} | |||
@@ -135,6 +136,7 @@ export default class TagFacet extends React.PureComponent<Props> { | |||
} | |||
render() { | |||
const { tags, stats = {} } = this.props; | |||
return ( | |||
<FacetBox property={this.property}> | |||
<FacetHeader | |||
@@ -146,8 +148,13 @@ export default class TagFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && this.renderList()} | |||
{this.props.open && this.renderFooter()} | |||
{this.props.open && ( | |||
<> | |||
{this.renderList()} | |||
{this.renderFooter()} | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={tags.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -27,6 +27,7 @@ import FacetItemsList from '../../../components/facet/FacetItemsList'; | |||
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
interface Props { | |||
fetching: boolean; | |||
@@ -38,6 +39,8 @@ interface Props { | |||
types: string[]; | |||
} | |||
const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT']; | |||
export default class TypeFacet extends React.PureComponent<Props> { | |||
property = 'types'; | |||
@@ -99,15 +102,15 @@ export default class TypeFacet extends React.PureComponent<Props> { | |||
} | |||
onClick={this.handleItemClick} | |||
stat={formatFacetStat(stat)} | |||
tooltip={this.props.types.length === 1 && !this.props.types.includes(type)} | |||
tooltip={translate('issue.type', type)} | |||
value={type} | |||
/> | |||
); | |||
}; | |||
render() { | |||
const types = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT']; | |||
const values = this.props.types.map(type => translate('issue.type', type)); | |||
const { types, stats = {} } = this.props; | |||
const values = types.map(type => translate('issue.type', type)); | |||
return ( | |||
<FacetBox property={this.property}> | |||
@@ -121,7 +124,12 @@ export default class TypeFacet extends React.PureComponent<Props> { | |||
/> | |||
<DeferredSpinner loading={this.props.fetching} /> | |||
{this.props.open && <FacetItemsList>{types.map(this.renderItem)}</FacetItemsList>} | |||
{this.props.open && ( | |||
<> | |||
<FacetItemsList>{TYPES.map(this.renderItem)}</FacetItemsList> | |||
<MultipleSelectionHint options={Object.keys(stats).length} values={types.length} /> | |||
</> | |||
)} | |||
</FacetBox> | |||
); | |||
} |
@@ -15,70 +15,76 @@ exports[`should render 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
loading={false} | |||
name="unassigned" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={false} | |||
value="" | |||
<React.Fragment> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
loading={false} | |||
name="unassigned" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip="unassigned" | |||
value="" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name={ | |||
<span> | |||
<Connect(Avatar) | |||
className="little-spacer-right" | |||
hash="avatart-foo" | |||
name="name-foo" | |||
size={16} | |||
/> | |||
name-foo | |||
</span> | |||
} | |||
onClick={[Function]} | |||
stat="13" | |||
tooltip="name-foo" | |||
value="foo" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="bar" | |||
loading={false} | |||
name="bar" | |||
onClick={[Function]} | |||
stat="7" | |||
tooltip="bar" | |||
value="bar" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="baz" | |||
loading={false} | |||
name="baz" | |||
onClick={[Function]} | |||
stat="6" | |||
tooltip="baz" | |||
value="baz" | |||
/> | |||
</FacetItemsList> | |||
<FacetFooter | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name={ | |||
<span> | |||
<Connect(Avatar) | |||
className="little-spacer-right" | |||
hash="avatart-foo" | |||
name="name-foo" | |||
size={16} | |||
/> | |||
name-foo | |||
</span> | |||
} | |||
onClick={[Function]} | |||
stat="13" | |||
tooltip={false} | |||
value="foo" | |||
<MultipleSelectionHint | |||
options={4} | |||
values={0} | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="bar" | |||
loading={false} | |||
name="bar" | |||
onClick={[Function]} | |||
stat="7" | |||
tooltip={false} | |||
value="bar" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="baz" | |||
loading={false} | |||
name="baz" | |||
onClick={[Function]} | |||
stat="6" | |||
tooltip={false} | |||
value="baz" | |||
/> | |||
</FacetItemsList> | |||
<FacetFooter | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
/> | |||
</React.Fragment> | |||
</FacetBox> | |||
`; | |||
@@ -109,6 +115,12 @@ exports[`should render without stats 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<React.Fragment> | |||
<MultipleSelectionHint | |||
options={0} | |||
values={0} | |||
/> | |||
</React.Fragment> | |||
</FacetBox> | |||
`; | |||
@@ -131,70 +143,76 @@ exports[`should select unassigned 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
loading={false} | |||
name="unassigned" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={false} | |||
value="" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name={ | |||
<span> | |||
<Connect(Avatar) | |||
className="little-spacer-right" | |||
hash="avatart-foo" | |||
name="name-foo" | |||
size={16} | |||
/> | |||
name-foo | |||
</span> | |||
} | |||
onClick={[Function]} | |||
stat="13" | |||
tooltip={false} | |||
value="foo" | |||
<React.Fragment> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
loading={false} | |||
name="unassigned" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip="unassigned" | |||
value="" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name={ | |||
<span> | |||
<Connect(Avatar) | |||
className="little-spacer-right" | |||
hash="avatart-foo" | |||
name="name-foo" | |||
size={16} | |||
/> | |||
name-foo | |||
</span> | |||
} | |||
onClick={[Function]} | |||
stat="13" | |||
tooltip="name-foo" | |||
value="foo" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="bar" | |||
loading={false} | |||
name="bar" | |||
onClick={[Function]} | |||
stat="7" | |||
tooltip="bar" | |||
value="bar" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="baz" | |||
loading={false} | |||
name="baz" | |||
onClick={[Function]} | |||
stat="6" | |||
tooltip="baz" | |||
value="baz" | |||
/> | |||
</FacetItemsList> | |||
<FacetFooter | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="bar" | |||
loading={false} | |||
name="bar" | |||
onClick={[Function]} | |||
stat="7" | |||
tooltip={false} | |||
value="bar" | |||
<MultipleSelectionHint | |||
options={4} | |||
values={0} | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="baz" | |||
loading={false} | |||
name="baz" | |||
onClick={[Function]} | |||
stat="6" | |||
tooltip={false} | |||
value="baz" | |||
/> | |||
</FacetItemsList> | |||
<FacetFooter | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
/> | |||
</React.Fragment> | |||
</FacetBox> | |||
`; | |||
@@ -217,69 +235,75 @@ exports[`should select user 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
loading={false} | |||
name="unassigned" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={true} | |||
value="" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name={ | |||
<span> | |||
<Connect(Avatar) | |||
className="little-spacer-right" | |||
hash="avatart-foo" | |||
name="name-foo" | |||
size={16} | |||
/> | |||
name-foo | |||
</span> | |||
} | |||
onClick={[Function]} | |||
stat="13" | |||
tooltip={false} | |||
value="foo" | |||
<React.Fragment> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
loading={false} | |||
name="unassigned" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip="unassigned" | |||
value="" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name={ | |||
<span> | |||
<Connect(Avatar) | |||
className="little-spacer-right" | |||
hash="avatart-foo" | |||
name="name-foo" | |||
size={16} | |||
/> | |||
name-foo | |||
</span> | |||
} | |||
onClick={[Function]} | |||
stat="13" | |||
tooltip="name-foo" | |||
value="foo" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="bar" | |||
loading={false} | |||
name="bar" | |||
onClick={[Function]} | |||
stat="7" | |||
tooltip="bar" | |||
value="bar" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="baz" | |||
loading={false} | |||
name="baz" | |||
onClick={[Function]} | |||
stat="6" | |||
tooltip="baz" | |||
value="baz" | |||
/> | |||
</FacetItemsList> | |||
<FacetFooter | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="bar" | |||
loading={false} | |||
name="bar" | |||
onClick={[Function]} | |||
stat="7" | |||
tooltip={true} | |||
value="bar" | |||
<MultipleSelectionHint | |||
options={4} | |||
values={1} | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="baz" | |||
loading={false} | |||
name="baz" | |||
onClick={[Function]} | |||
stat="6" | |||
tooltip={true} | |||
value="baz" | |||
/> | |||
</FacetItemsList> | |||
<FacetFooter | |||
onSearch={[Function]} | |||
onSelect={[Function]} | |||
renderOption={[Function]} | |||
/> | |||
</React.Fragment> | |||
</FacetBox> | |||
`; |
@@ -29,11 +29,17 @@ exports[`should render empty sub-facet 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<div | |||
className="search-navigator-facet-empty little-spacer-top" | |||
> | |||
no_results | |||
</div> | |||
<React.Fragment> | |||
<div | |||
className="search-navigator-facet-empty little-spacer-top" | |||
> | |||
no_results | |||
</div> | |||
<MultipleSelectionHint | |||
options={0} | |||
values={0} | |||
/> | |||
</React.Fragment> | |||
</FacetBox> | |||
`; | |||
@@ -73,32 +79,38 @@ exports[`should render sub-facets 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="a1" | |||
loading={false} | |||
name="A1 - a1 title" | |||
onClick={[Function]} | |||
stat="15" | |||
tooltip={true} | |||
value="a1" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="a3" | |||
loading={false} | |||
name="A3" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={false} | |||
value="a3" | |||
<React.Fragment> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="a1" | |||
loading={false} | |||
name="A1 - a1 title" | |||
onClick={[Function]} | |||
stat="15" | |||
tooltip="A1 - a1 title" | |||
value="a1" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="a3" | |||
loading={false} | |||
name="A3" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip="A3" | |||
value="a3" | |||
/> | |||
</FacetItemsList> | |||
<MultipleSelectionHint | |||
options={2} | |||
values={1} | |||
/> | |||
</FacetItemsList> | |||
</React.Fragment> | |||
</FacetBox> | |||
<FacetBox | |||
className="is-inner" | |||
@@ -118,32 +130,38 @@ exports[`should render sub-facets 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name="foo" | |||
onClick={[Function]} | |||
stat="12" | |||
tooltip={true} | |||
value="foo" | |||
<React.Fragment> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="foo" | |||
loading={false} | |||
name="foo" | |||
onClick={[Function]} | |||
stat="12" | |||
tooltip="foo" | |||
value="foo" | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="risky-resource" | |||
loading={false} | |||
name="Risky Resource Management" | |||
onClick={[Function]} | |||
stat="10" | |||
tooltip="Risky Resource Management" | |||
value="risky-resource" | |||
/> | |||
</FacetItemsList> | |||
<MultipleSelectionHint | |||
options={2} | |||
values={1} | |||
/> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="risky-resource" | |||
loading={false} | |||
name="Risky Resource Management" | |||
onClick={[Function]} | |||
stat="10" | |||
tooltip={false} | |||
value="risky-resource" | |||
/> | |||
</FacetItemsList> | |||
</React.Fragment> | |||
</FacetBox> | |||
<FacetBox | |||
className="is-inner" | |||
@@ -163,56 +181,62 @@ exports[`should render sub-facets 1`] = ` | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="42" | |||
loading={false} | |||
name="CWE-42 - cwe-42 title" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip={false} | |||
value="42" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="173" | |||
loading={false} | |||
name="CWE-173" | |||
onClick={[Function]} | |||
stat="3" | |||
tooltip={true} | |||
value="173" | |||
/> | |||
</FacetItemsList> | |||
<div | |||
className="search-navigator-facet-footer" | |||
> | |||
<Select | |||
className="input-super-large" | |||
clearable={false} | |||
noResultsText="select2.noMatches" | |||
onChange={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "CWE-42 - cwe-42 title", | |||
"value": "42", | |||
}, | |||
Object { | |||
"label": "Unknown CWE", | |||
"value": "unknown", | |||
}, | |||
] | |||
} | |||
placeholder="search.search_for_cwe" | |||
searchable={true} | |||
<React.Fragment> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="42" | |||
loading={false} | |||
name="CWE-42 - cwe-42 title" | |||
onClick={[Function]} | |||
stat="5" | |||
tooltip="CWE-42 - cwe-42 title" | |||
value="42" | |||
/> | |||
<FacetItem | |||
active={false} | |||
disabled={false} | |||
halfWidth={false} | |||
key="173" | |||
loading={false} | |||
name="CWE-173" | |||
onClick={[Function]} | |||
stat="3" | |||
tooltip="CWE-173" | |||
value="173" | |||
/> | |||
</FacetItemsList> | |||
<div | |||
className="search-navigator-facet-footer" | |||
> | |||
<Select | |||
className="input-super-large" | |||
clearable={false} | |||
noResultsText="select2.noMatches" | |||
onChange={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "CWE-42 - cwe-42 title", | |||
"value": "42", | |||
}, | |||
Object { | |||
"label": "Unknown CWE", | |||
"value": "unknown", | |||
}, | |||
] | |||
} | |||
placeholder="search.search_for_cwe" | |||
searchable={true} | |||
/> | |||
</div> | |||
<MultipleSelectionHint | |||
options={2} | |||
values={1} | |||
/> | |||
</div> | |||
</React.Fragment> | |||
</FacetBox> | |||
</React.Fragment> | |||
</FacetBox> |
@@ -19,9 +19,6 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { isOnMac } from './utils'; | |||
import Tooltip from '../controls/Tooltip'; | |||
import { translate } from '../../helpers/l10n'; | |||
export interface Props { | |||
active?: boolean; | |||
@@ -32,7 +29,8 @@ export interface Props { | |||
name: React.ReactNode; | |||
onClick: (x: string, multiple?: boolean) => void; | |||
stat?: React.ReactNode; | |||
tooltip?: boolean; | |||
/** Textual version of `name` */ | |||
tooltip: string; | |||
value: string; | |||
} | |||
@@ -50,42 +48,31 @@ export default class FacetItem extends React.PureComponent<Props> { | |||
}; | |||
render() { | |||
const className = classNames('facet', 'search-navigator-facet', this.props.className, { | |||
const { name } = this.props; | |||
const className = classNames('search-navigator-facet', this.props.className, { | |||
active: this.props.active, | |||
'search-navigator-facet-half': this.props.halfWidth | |||
}); | |||
const overlay = | |||
this.props.tooltip && !this.props.disabled | |||
? translate( | |||
isOnMac() | |||
? 'shortcuts.section.global.facets.multiselection.mac' | |||
: 'shortcuts.section.global.facets.multiselection' | |||
) | |||
: undefined; | |||
return ( | |||
<Tooltip overlay={overlay} placement="right"> | |||
{this.props.disabled ? ( | |||
<span className={className} data-facet={this.props.value}> | |||
<span className="facet-name">{this.props.name}</span> | |||
{this.props.stat != null && ( | |||
<span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span> | |||
)} | |||
</span> | |||
) : ( | |||
<a | |||
className={className} | |||
data-facet={this.props.value} | |||
href="#" | |||
onClick={this.handleClick}> | |||
<span className="facet-name">{this.props.name}</span> | |||
{this.props.stat != null && ( | |||
<span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span> | |||
)} | |||
</a> | |||
return this.props.disabled ? ( | |||
<span className={className} data-facet={this.props.value}> | |||
<span className="facet-name">{name}</span> | |||
{this.props.stat != null && ( | |||
<span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span> | |||
)} | |||
</span> | |||
) : ( | |||
<a | |||
className={className} | |||
data-facet={this.props.value} | |||
href="#" | |||
onClick={this.handleClick} | |||
title={this.props.tooltip}> | |||
<span className="facet-name">{name}</span> | |||
{this.props.stat != null && ( | |||
<span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span> | |||
)} | |||
</Tooltip> | |||
</a> | |||
); | |||
} | |||
} |
@@ -17,7 +17,19 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.multiple-selection-hint { | |||
margin-top: var(--gridSize); | |||
margin-bottom: var(--gridSize); | |||
text-align: center; | |||
} | |||
export function isOnMac() { | |||
return navigator.userAgent.indexOf('Mac OS X') !== -1; | |||
.multiple-selection-hint-inner { | |||
display: inline-block; | |||
height: var(--controlHeight); | |||
line-height: var(--controlHeight); | |||
border-radius: var(--controlHeight); | |||
background-color: var(--barBorderColor); | |||
text-align: center; | |||
padding: 0 var(--gridSize); | |||
font-size: var(--smallFontSize); | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { translate } from '../../helpers/l10n'; | |||
import './MultipleSelectionHint.css'; | |||
interface Props { | |||
options: number; | |||
values: number; | |||
} | |||
export default function MultipleSelectionHint({ options, values }: Props) { | |||
// do not render if nothing is selected or there are less than 2 possible options | |||
if (values === 0 || options < 2) { | |||
return null; | |||
} | |||
return ( | |||
<div className="multiple-selection-hint"> | |||
<div className="multiple-selection-hint-inner"> | |||
{translate( | |||
isOnMac() | |||
? 'shortcuts.section.global.facets.multiselection.mac' | |||
: 'shortcuts.section.global.facets.multiselection' | |||
)} | |||
</div> | |||
</div> | |||
); | |||
} | |||
function isOnMac() { | |||
return navigator.userAgent.indexOf('Mac OS X') !== -1; | |||
} |
@@ -55,6 +55,14 @@ it('should call onClick', () => { | |||
function renderFacetItem(props?: Partial<Props>) { | |||
return shallow( | |||
<FacetItem active={false} name="foo" onClick={jest.fn()} stat={null} value="bar" {...props} /> | |||
<FacetItem | |||
active={false} | |||
name="foo" | |||
onClick={jest.fn()} | |||
stat={null} | |||
tooltip="foo" | |||
value="bar" | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import MultipleSelectionHint from '../MultipleSelectionHint'; | |||
it('should render for mac', () => { | |||
Object.defineProperty(navigator, 'userAgent', { | |||
configurable: true, | |||
value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4)' | |||
}); | |||
expect(shallow(<MultipleSelectionHint options={3} values={1} />)).toMatchSnapshot(); | |||
}); | |||
it('should render for windows', () => { | |||
Object.defineProperty(navigator, 'userAgent', { | |||
configurable: true, | |||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' | |||
}); | |||
expect(shallow(<MultipleSelectionHint options={3} values={1} />)).toMatchSnapshot(); | |||
}); | |||
it('should not render when there is not selection', () => { | |||
expect(shallow(<MultipleSelectionHint options={3} values={0} />).type()).toBe(null); | |||
}); | |||
it('should not render when there are not enough options', () => { | |||
expect(shallow(<MultipleSelectionHint options={1} values={1} />).type()).toBe(null); | |||
}); |
@@ -1,118 +1,99 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should loading stat 1`] = ` | |||
<Tooltip | |||
placement="right" | |||
<a | |||
className="search-navigator-facet" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
title="foo" | |||
> | |||
<a | |||
className="facet search-navigator-facet" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
<span | |||
className="facet-name" | |||
> | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
</a> | |||
</Tooltip> | |||
foo | |||
</span> | |||
</a> | |||
`; | |||
exports[`should render active 1`] = ` | |||
<Tooltip | |||
placement="right" | |||
<a | |||
className="search-navigator-facet active" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
title="foo" | |||
> | |||
<a | |||
className="facet search-navigator-facet active" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
<span | |||
className="facet-name" | |||
> | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
</a> | |||
</Tooltip> | |||
foo | |||
</span> | |||
</a> | |||
`; | |||
exports[`should render disabled 1`] = ` | |||
<Tooltip | |||
placement="right" | |||
<span | |||
className="search-navigator-facet" | |||
data-facet="bar" | |||
> | |||
<span | |||
className="facet search-navigator-facet" | |||
data-facet="bar" | |||
className="facet-name" | |||
> | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
foo | |||
</span> | |||
</Tooltip> | |||
</span> | |||
`; | |||
exports[`should render half width 1`] = ` | |||
<Tooltip | |||
placement="right" | |||
<a | |||
className="search-navigator-facet search-navigator-facet-half" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
title="foo" | |||
> | |||
<a | |||
className="facet search-navigator-facet search-navigator-facet-half" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
<span | |||
className="facet-name" | |||
> | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
</a> | |||
</Tooltip> | |||
foo | |||
</span> | |||
</a> | |||
`; | |||
exports[`should render inactive 1`] = ` | |||
<Tooltip | |||
placement="right" | |||
<a | |||
className="search-navigator-facet" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
title="foo" | |||
> | |||
<a | |||
className="facet search-navigator-facet" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
<span | |||
className="facet-name" | |||
> | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
</a> | |||
</Tooltip> | |||
foo | |||
</span> | |||
</a> | |||
`; | |||
exports[`should render stat 1`] = ` | |||
<Tooltip | |||
placement="right" | |||
<a | |||
className="search-navigator-facet" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
title="foo" | |||
> | |||
<a | |||
className="facet search-navigator-facet" | |||
data-facet="bar" | |||
href="#" | |||
onClick={[Function]} | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
<span | |||
className="facet-stat" | |||
> | |||
<span | |||
className="facet-name" | |||
> | |||
foo | |||
</span> | |||
<span | |||
className="facet-stat" | |||
> | |||
13 | |||
</span> | |||
</a> | |||
</Tooltip> | |||
13 | |||
</span> | |||
</a> | |||
`; |
@@ -0,0 +1,25 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render for mac 1`] = ` | |||
<div | |||
className="multiple-selection-hint" | |||
> | |||
<div | |||
className="multiple-selection-hint-inner" | |||
> | |||
shortcuts.section.global.facets.multiselection.mac | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render for windows 1`] = ` | |||
<div | |||
className="multiple-selection-hint" | |||
> | |||
<div | |||
className="multiple-selection-hint-inner" | |||
> | |||
shortcuts.section.global.facets.multiselection | |||
</div> | |||
</div> | |||
`; |
@@ -92,17 +92,17 @@ | |||
.search-navigator-facet { | |||
position: relative; | |||
display: inline-block; | |||
vertical-align: middle; | |||
display: inline-flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
width: 100%; | |||
height: var(--controlHeight); | |||
margin: 0 0 1px 0; | |||
padding: 4px 6px; | |||
border: none; | |||
padding: 0 calc(0.75 * var(--gridSize)); | |||
border: 1px solid transparent; | |||
border-radius: 2px; | |||
box-sizing: border-box; | |||
white-space: normal; | |||
overflow: hidden; | |||
font-size: 0; | |||
opacity: 0.3; | |||
cursor: not-allowed; | |||
transition: none; | |||
@@ -118,15 +118,9 @@ a.search-navigator-facet .facet-name { | |||
} | |||
a.search-navigator-facet:hover, | |||
a.search-navigator-facet:focus { | |||
border: 1px solid var(--blue); | |||
padding: 3px 5px; | |||
} | |||
a.search-navigator-facet:hover .facet-stat, | |||
a.search-navigator-facet:focus .facet-stat { | |||
top: -1px; | |||
right: -1px; | |||
a.search-navigator-facet:focus, | |||
.search-navigator-facet.active { | |||
border-color: var(--blue); | |||
} | |||
.search-navigator-facet.facet-category { | |||
@@ -139,38 +133,25 @@ a.search-navigator-facet:focus .facet-stat { | |||
} | |||
.search-navigator-facet .facet-name { | |||
flex: 1 1 auto; | |||
min-width: 0; | |||
line-height: 16px; | |||
background-color: var(--barBackgroundColor); | |||
padding: 1px 0; /* needed to fit small ratings and levels */ | |||
color: var(--secondFontColor); | |||
font-size: var(--smallFontSize); | |||
white-space: nowrap; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
.search-navigator-facet .facet-stat { | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
margin-left: 5px; | |||
padding: 5px 5px; | |||
background-color: var(--barBackgroundColor); | |||
display: flex; | |||
align-items: center; | |||
margin-left: var(--gridSize); | |||
color: var(--secondFontColor); | |||
font-size: var(--smallFontSize); | |||
} | |||
.search-navigator-facet .facet-stat:before { | |||
content: ' '; | |||
position: absolute; | |||
top: 0; | |||
bottom: 0; | |||
right: 100%; | |||
width: 10px; | |||
background-image: linear-gradient( | |||
to right, | |||
rgba(243, 243, 243, 0), | |||
var(--barBackgroundColor) 75% | |||
); | |||
} | |||
.search-navigator-facet .facet-toggle { | |||
display: none; | |||
float: left; | |||
@@ -210,27 +191,10 @@ a.search-navigator-facet:focus .facet-stat { | |||
} | |||
.search-navigator-facet.active { | |||
border: 1px solid var(--blue); | |||
padding: 3px 5px; | |||
background-color: var(--lightBlue); | |||
text-decoration: none; | |||
} | |||
.search-navigator-facet.active .facet-name { | |||
background-color: var(--lightBlue); | |||
} | |||
.search-navigator-facet.active .facet-stat { | |||
border-color: var(--blue); | |||
background-color: var(--lightBlue); | |||
top: -1px; | |||
right: -1px; | |||
} | |||
.search-navigator-facet.active .facet-stat:before { | |||
background-image: linear-gradient(to right, rgba(202, 227, 242, 0), var(--lightBlue) 75%); | |||
} | |||
.search-navigator-facet.active .facet-toggle { | |||
display: inline; | |||
} | |||
@@ -269,7 +233,7 @@ a.search-navigator-facet:focus .facet-stat { | |||
.search-navigator-facet-highlight-under-container .search-navigator-facet:hover, | |||
.search-navigator-facet-highlight-under-container .search-navigator-facet.active { | |||
border-bottom: none; | |||
padding-bottom: 4px; | |||
padding-bottom: 1px; | |||
border-radius: 2px 2px 0 0; | |||
} | |||
@@ -279,38 +243,25 @@ a.search-navigator-facet:focus .facet-stat { | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet { | |||
padding-left: 5px; | |||
padding-right: 5px; | |||
border-left: 1px solid var(--blue); | |||
border-right: 1px solid var(--blue); | |||
border-top: none; | |||
border-bottom: none; | |||
border-left-color: var(--blue); | |||
border-right-color: var(--blue); | |||
border-radius: 0; | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet:hover | |||
~ .search-navigator-facet | |||
.facet-stat, | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet | |||
.facet-stat { | |||
right: -1px; | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet:hover | |||
~ .search-navigator-facet:last-of-type, | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:last-of-type { | |||
padding-bottom: 3px; | |||
border-bottom: 1px solid var(--blue); | |||
border-radius: 0 0 2px 2px; | |||
} | |||
.search-navigator-facet-highlight-under-container .search-navigator-facet:hover:last-of-type, | |||
.search-navigator-facet-highlight-under-container .search-navigator-facet.active:last-of-type { | |||
padding-bottom: 3px; | |||
border-bottom: 1px solid var(--blue); | |||
border-radius: 2px; | |||
} | |||
@@ -322,28 +273,6 @@ a.search-navigator-facet:focus .facet-stat { | |||
text-decoration: none; | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet | |||
.facet-name { | |||
background-color: var(--lightBlue); | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet | |||
.facet-stat { | |||
border-color: var(--blue); | |||
background-color: var(--lightBlue); | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet | |||
.facet-stat:before { | |||
background-image: linear-gradient(to right, rgba(202, 227, 242, 0), var(--lightBlue) 75%); | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet | |||
@@ -355,48 +284,20 @@ a.search-navigator-facet:focus .facet-stat { | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover, | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
~ .search-navigator-facet { | |||
background-color: #a1cde8; | |||
text-decoration: none; | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
.facet-name, | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
~ .search-navigator-facet | |||
.facet-name { | |||
background-color: #a1cde8; | |||
.search-navigator-facet:hover | |||
~ .search-navigator-facet.active { | |||
border-top: 1px solid var(--blue); | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
.facet-stat, | |||
~ .search-navigator-facet:hover, | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
~ .search-navigator-facet | |||
.facet-stat { | |||
border-color: var(--blue); | |||
~ .search-navigator-facet { | |||
background-color: #a1cde8; | |||
} | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
.facet-stat:before, | |||
.search-navigator-facet-highlight-under-container | |||
.search-navigator-facet.active | |||
~ .search-navigator-facet:hover | |||
~ .search-navigator-facet | |||
.facet-stat:before { | |||
background-image: linear-gradient(to right, rgba(161, 205, 232, 0), #a1cde8 75%); | |||
text-decoration: none; | |||
} | |||
.search-navigator-facet-highlight-under-container |
@@ -935,7 +935,7 @@ shortcuts.section.global=Global | |||
shortcuts.section.global.search=quickly open search bar | |||
shortcuts.section.global.shortcuts=open this window | |||
shortcuts.section.global.facets.multiselection=Ctrl + click to add to selection | |||
shortcuts.section.global.facets.multiselection.mac=Cmd + click to add to selection | |||
shortcuts.section.global.facets.multiselection.mac=\u2318 + click to add to selection | |||
shortcuts.section.issues=Issues Page | |||
shortcuts.section.issues.navigate_between_issues=navigate between issues |