Skip to content

Commit

Permalink
🎉 (entity selector) highlight user location
Browse files Browse the repository at this point in the history
  • Loading branch information
sophiamersmann committed Apr 11, 2024
1 parent 3200095 commit f546933
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@ export class LabeledSwitch extends React.Component<{
{tooltip && (
<Tippy
content={tooltip}
theme="settings"
theme="grapher-explanation"
placement="top"
// arrow={false}
maxWidth={338}
>
<FontAwesomeIcon icon={faInfoCircle} />
Expand Down
15 changes: 0 additions & 15 deletions packages/@ourworldindata/grapher/src/controls/SettingsMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,6 @@ nav.controlsRow .chart-controls .settings-menu {
height: 13px;
padding: 0 0.333em;
}

// the tooltip triggered by hovering the circle-i
@at-root .tippy-box[data-theme="settings"] {
background: white;
color: $dark-text;
font: 400 14px/1.5 $sans-serif-font-stack;
box-shadow: 0px 4px 40px 0px rgba(0, 0, 0, 0.15);

.tippy-content {
padding: $indent;
}
.tippy-arrow {
color: white;
}
}
}

.labeled-switch .labeled-switch-subtitle,
Expand Down
19 changes: 19 additions & 0 deletions packages/@ourworldindata/grapher/src/core/grapher.scss
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,25 @@ $zindex-controls-drawer: 150;
z-index: $zindex-Tooltip;
}

// white background tooltip for longer explanations
// (the `--short` version has a little less padding)
.tippy-box[data-theme="grapher-explanation"],
.tippy-box[data-theme="grapher-explanation--short"] {
background: white;
color: $dark-text;
font: 400 14px/1.5 $sans-serif-font-stack;
box-shadow: 0px 4px 40px 0px rgba(0, 0, 0, 0.15);

.tippy-arrow {
color: white;
}
}
.tippy-box[data-theme="grapher-explanation"] {
.tippy-content {
padding: 15px;
}
}

.markdown-text-wrap__line {
display: block;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,22 @@
background: #ebeef2;
z-index: -1;
}

.label-with-location-icon {
display: flex;
align-items: center;

svg {
margin-left: 8px;
font-size: 0.9em;
color: #a1a1a1;

// hide focus outline when clicked
&:focus:not(:focus-visible) {
outline: none;
}
}
}
}

.animated-entity {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ import {
isFiniteWithGuard,
CoreValueType,
clamp,
sortBy,
last,
getUserCountryInformation,
regions,
sortBy,
Tippy,
} from "@ourworldindata/utils"
import { Checkbox } from "@ourworldindata/components"
import { FuzzySearch } from "../controls/FuzzySearch"
import {
faCircleXmark,
faMagnifyingGlass,
faLocationArrow,
} from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome/index.js"
import { SelectionArray } from "../selection/SelectionArray"
Expand All @@ -41,6 +45,7 @@ import { scaleLinear, type ScaleLinear } from "d3-scale"
export interface EntitySelectorState {
searchInput: string
sortConfig: SortConfig
localEntityNames?: string[]
mostRecentlySelectedEntityName?: string
}

Expand All @@ -62,7 +67,7 @@ interface SortConfig {
order: SortOrder
}

type SearchableEntity = { name: string } & Record<
type SearchableEntity = { name: string; local?: boolean } & Record<
Slug,
CoreValueType | undefined
>
Expand Down Expand Up @@ -92,6 +97,8 @@ export class EntitySelector extends React.Component<{
}

componentDidMount(): void {
void this.populateLocalEntities()

if (this.props.autoFocus && !isTouchDevice())
this.searchField.current?.focus()

Expand Down Expand Up @@ -133,6 +140,32 @@ export class EntitySelector extends React.Component<{
}
}

@action.bound async populateLocalEntities(): Promise<void> {
try {
const localCountryInfo = await getUserCountryInformation()
if (!localCountryInfo) return

const userEntityCodes = [
localCountryInfo.code,
...(localCountryInfo.regions ?? []),
]

const userRegions = regions.filter((region) =>
userEntityCodes.includes(region.code)
)

const sortedUserRegions = sortBy(userRegions, (region) =>
userEntityCodes.indexOf(region.code)
)

const localEntityNames = sortedUserRegions.map(
(region) => region.name
)

if (localEntityNames) this.set({ localEntityNames })
} catch (err) {}
}

private clearSearchInput(): void {
this.set({ searchInput: "" })
}
Expand Down Expand Up @@ -178,6 +211,10 @@ export class EntitySelector extends React.Component<{
)
}

@computed private get localEntityNames(): string[] | undefined {
return this.manager.entitySelectorState.localEntityNames
}

@computed private get table(): OwidTable {
return this.manager.tableForSelection
}
Expand Down Expand Up @@ -254,6 +291,11 @@ export class EntitySelector extends React.Component<{
return this.availableEntityNames.map((entityName) => {
const searchableEntity: SearchableEntity = { name: entityName }

if (this.localEntityNames) {
searchableEntity.local =
this.localEntityNames.includes(entityName)
}

for (const column of this.sortColumns) {
const rows = column.owidRowsByEntityName.get(entityName) ?? []
const sortedRows = sortBy(rows, (row) => row.time)
Expand All @@ -264,20 +306,45 @@ export class EntitySelector extends React.Component<{
})
}

private sortEntities(entities: SearchableEntity[]): SearchableEntity[] {
private sortEntities(
entities: SearchableEntity[],
options: { sortLocalsToTop: boolean } = { sortLocalsToTop: true }
): SearchableEntity[] {
const { sortConfig } = this

const shouldBeSortedByName = this.hasSlugName(sortConfig)

// sort by name
if (shouldBeSortedByName) {
// sort by name, ignoring local entities
if (shouldBeSortedByName && !options.sortLocalsToTop) {
return orderBy(
entities,
(entity: SearchableEntity) => entity.name,
sortConfig.order
)
}

// sort by name, with local entities at the top
if (shouldBeSortedByName && options.sortLocalsToTop) {
const [localEntities, otherEntities] = partition(
entities,
(entity: SearchableEntity) => entity.local
)

const sortedLocalEntities = sortBy(
localEntities,
(entity: SearchableEntity) =>
this.localEntityNames?.indexOf(entity.name)
)

const sortedOtherEntities = orderBy(
otherEntities,
(entity: SearchableEntity) => entity.name,
sortConfig.order
)

return [...sortedLocalEntities, ...sortedOtherEntities]
}

// sort by number column, with missing values at the end
const [withValues, withoutValues] = partition(
entities,
Expand Down Expand Up @@ -335,7 +402,7 @@ export class EntitySelector extends React.Component<{
)

return {
selected: this.sortEntities(selected),
selected: this.sortEntities(selected, { sortLocalsToTop: false }),
unselected: this.sortEntities(unselected),
}
}
Expand Down Expand Up @@ -467,6 +534,7 @@ export class EntitySelector extends React.Component<{
checked={this.isEntitySelected(entity)}
bar={this.getBarConfigForEntity(entity)}
onChange={() => this.onChange(entity.name)}
local={entity.local}
/>
</li>
))}
Expand All @@ -485,6 +553,7 @@ export class EntitySelector extends React.Component<{
checked={this.isEntitySelected(entity)}
bar={this.getBarConfigForEntity(entity)}
onChange={() => this.onChange(entity.name)}
local={entity.local}
/>
</li>
))}
Expand Down Expand Up @@ -568,6 +637,7 @@ export class EntitySelector extends React.Component<{
onChange={() =>
this.onChange(entity.name)
}
local={entity.local}
/>
</li>
</Flipped>
Expand Down Expand Up @@ -608,6 +678,7 @@ export class EntitySelector extends React.Component<{
onChange={() =>
this.onChange(entity.name)
}
local={entity.local}
/>
</li>
</Flipped>
Expand Down Expand Up @@ -684,18 +755,35 @@ function SelectableEntity({
type,
bar,
onChange,
local,
}: {
name: React.ReactNode
checked: boolean
type: "checkbox" | "radio"
bar?: BarConfig
onChange: () => void
local?: boolean
}) {
const Input = {
checkbox: Checkbox,
radio: RadioButton,
}[type]

const label = local ? (
<span className="label-with-location-icon">
{name}
<Tippy
content="Your current location"
theme="grapher-explanation--short"
placement="top"
>
<FontAwesomeIcon icon={faLocationArrow} />
</Tippy>
</span>
) : (
name
)

return (
<div
className="selectable-entity"
Expand All @@ -709,7 +797,7 @@ function SelectableEntity({
{bar && bar.width !== undefined && (
<div className="bar" style={{ width: `${bar.width * 100}%` }} />
)}
<Input label={name} checked={checked} onChange={onChange} />
<Input label={label} checked={checked} onChange={onChange} />
{bar && (
<span className="value grapher_label-1-medium">
{bar.formattedValue}
Expand Down

0 comments on commit f546933

Please sign in to comment.