Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add country flags to Per country page #187

Merged
merged 11 commits into from
Jul 7, 2021
2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@
"classnames": "2.2.6",
"connected-next-router": "3.1.0",
"core-js": "3.6.5",
"country-flag-icons": "1.2.10",
"css.escape": "1.5.1",
"fast-copy": "2.1.0",
"history": "5.0.0",
"immer": "7.0.8",
"immutable": "3.8.2",
"is-absolute-url": "3.0.3",
"iso-3166-1-alpha-2": "1.0.0",
"lodash": "4.17.20",
"luxon": "1.25.0",
"marked": "1.1.1",
Expand Down
45 changes: 45 additions & 0 deletions web/src/components/Common/CountryFlag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { SVGProps } from 'react'

import styled from 'styled-components'
import iso3311a2 from 'iso-3166-1-alpha-2'
import Flags from 'country-flag-icons/react/3x2'

export const missingCountryCodes: Record<string, string> = {
'Bonaire': 'BQ',
'Curacao': 'CW',
'North Macedonia': 'MK',
'Russia': 'RU',
'Sint Maarten': 'SX',
'South Korea': 'KR',
'USA': 'US',
}

export const FlagWrapper = styled.div<{ $countryCode?: string }>`
height: calc(1em + 2px);
width: calc(1.5em + 2px);
border: 1px solid #ced4da;
display: flex;
> * {
width: 100%;
height: 100%;
}
`

export interface CountryFlagProps extends SVGProps<SVGSVGElement> {
country: string
withFallback?: boolean
}

export function CountryFlag({ country, withFallback = false }: CountryFlagProps) {
const countryCode = missingCountryCodes[country] ?? iso3311a2.getCode(country) ?? '?'
const Flag = Flags[countryCode]

const fallback = withFallback ? <FlagWrapper $countryCode={countryCode} /> : null
return Flag ? (
<FlagWrapper>
<Flag />
</FlagWrapper>
) : (
fallback
)
}
12 changes: 10 additions & 2 deletions web/src/components/CountryDistribution/CountryDistributionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {

import { CountryDistributionPlotCard } from './CountryDistributionPlotCard'
import { CountryDistributionDatum } from './CountryDistributionPlot'
import { CountryFlag } from '../Common/CountryFlag'

export interface ClusterState {
[key: string]: { enabled: boolean }
Expand Down Expand Up @@ -83,6 +84,7 @@ export function CountryDistributionPage() {
}, [clustersState, countriesState])

const regionsTitle = useMemo(() => (currentRegion === 'World' ? 'Countries' : 'Regions'), [currentRegion])
const iconComponent = useMemo(() => (currentRegion === 'World' ? CountryFlag : undefined), [currentRegion])

const { withCountriesFiltered } =
/* prettier-ignore */
Expand All @@ -96,10 +98,15 @@ export function CountryDistributionPage() {
() =>
withClustersFiltered.map(({ country, distribution }) => (
<ColCustom key={country} md={12} lg={6} xl={6} xxl={4}>
<CountryDistributionPlotCard country={country} distribution={distribution} cluster_names={enabledClusters} />
<CountryDistributionPlotCard
country={country}
distribution={distribution}
cluster_names={enabledClusters}
Icon={iconComponent}
/>
</ColCustom>
)),
[enabledClusters, withClustersFiltered],
[enabledClusters, withClustersFiltered, iconComponent],
)

const handleClusterCheckedChange = useCallback(
Expand Down Expand Up @@ -187,6 +194,7 @@ export function CountryDistributionPage() {
regionsTitle={regionsTitle}
enabledFilters={enabledFilters}
clustersCollapsedByDefault={false}
Icon={iconComponent}
onClusterFilterChange={handleClusterCheckedChange}
onClusterFilterSelectAll={handleClusterSelectAll}
onClusterFilterDeselectAll={handleClusterDeselectAll}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
/* eslint-disable camelcase */
import React from 'react'

import { Card, CardBody, CardHeader, Col, Row } from 'reactstrap'
import { ColoredCircle } from 'src/components/Common/ColoredCircle'
import styled from 'styled-components'

import { PlotCardTitle } from 'src/components/Common/PlotCardTitle'
import { getCountryColor } from 'src/io/getCountryColor'
import { CountryFlagProps } from 'src/components/Common/CountryFlag'
import {
CountryDistributionDatum,
CountryDistributionPlot,
} from 'src/components/CountryDistribution/CountryDistributionPlot'

const FlagAlignment = styled.span`
display: flex;
align-items: center;
> * + * {
margin-left: 0.5em;
}
`
export interface CountryDistributionPlotCardProps {
country: string
distribution: CountryDistributionDatum[]
cluster_names: string[]
Icon?: React.ComponentType<CountryFlagProps>
}

export function CountryDistributionPlotCard({
country,
distribution,
cluster_names,
Icon,
}: CountryDistributionPlotCardProps) {
return (
<Card className="m-2">
<CardHeader className="d-flex flex-sm-column">
<PlotCardTitle>
<ColoredCircle $color={getCountryColor(country)} $size={20} />
<span>{country}</span>
<FlagAlignment>
{Icon && <Icon country={country} />}
<span>{country}</span>
</FlagAlignment>
</PlotCardTitle>
</CardHeader>

Expand Down
47 changes: 35 additions & 12 deletions web/src/components/DistributionSidebar/CountryFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Label,
Row,
} from 'reactstrap'
import { ColoredCircle } from 'src/components/Common/ColoredCircle'
import { CountryFlagProps } from 'src/components/Common/CountryFlag'
import styled from 'styled-components'

import type { CountryState } from 'src/components/CountryDistribution/CountryDistributionPage'
Expand All @@ -31,32 +31,52 @@ export const Form = styled(FormBase)`
flex-wrap: wrap;
`

const FlagAlignment = styled.span`
display: inline-flex;
align-items: center;
margin-left: 0.25em;
> * + * {
margin-left: 0.5em;
}
`

export interface CountryFilterCheckboxProps {
country: string
enabled: boolean
withIcons?: boolean
Icon?: React.ComponentType<CountryFlagProps>
onFilterChange(country: string): void
}

export function CountryFilterCheckbox({ country, enabled, withIcons, onFilterChange }: CountryFilterCheckboxProps) {
export function CountryFilterCheckbox({
country,
enabled,
withIcons,
Icon,
onFilterChange,
}: CountryFilterCheckboxProps) {
const onChange = useCallback(() => onFilterChange(country), [country, onFilterChange])

return (
<FormGroup check>
<Label htmlFor={CSS.escape(country)} check>
<Input id={CSS.escape(country)} type="checkbox" checked={enabled} onChange={onChange} />
{withIcons ? (
<ColoredCircle $color={getCountryColor(country)} $size={14} />
<FlagAlignment>
{Icon && <Icon country={country} withFallback />}
<span>{country}</span>
</FlagAlignment>
) : (
<ColoredHorizontalLineIcon
width={theme.plot.country.legend.lineIcon.width}
height={theme.plot.country.legend.lineIcon.height}
stroke={getCountryColor(country)}
strokeWidth={theme.plot.country.legend.lineIcon.thickness}
strokeDasharray={getCountryStrokeDashArray(country)}
/>
<>
<ColoredHorizontalLineIcon
width={theme.plot.country.legend.lineIcon.width}
height={theme.plot.country.legend.lineIcon.height}
stroke={getCountryColor(country)}
strokeWidth={theme.plot.country.legend.lineIcon.thickness}
strokeDasharray={getCountryStrokeDashArray(country)}
/>
<span className="ml-2">{country}</span>
</>
)}
<span className="ml-2">{country}</span>
</Label>
</FormGroup>
)
Expand All @@ -67,6 +87,7 @@ export interface CountryFiltersProps {
regionsTitle: string
collapsed: boolean
withIcons?: boolean
Icon?: React.ComponentType<CountryFlagProps>
onFilterChange(country: string): void
onFilterSelectAll(): void
onFilterDeselectAll(): void
Expand All @@ -78,6 +99,7 @@ export function CountryFilters({
regionsTitle,
collapsed,
withIcons,
Icon,
onFilterSelectAll,
onFilterDeselectAll,
onFilterChange,
Expand Down Expand Up @@ -111,6 +133,7 @@ export function CountryFilters({
country={country}
enabled={enabled}
withIcons={withIcons}
Icon={Icon}
onFilterChange={onFilterChange}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { get } from 'lodash'
import React, { useState, useMemo } from 'react'

import { Col, Row } from 'reactstrap'

import type { ClusterState, CountryState } from 'src/components/CountryDistribution/CountryDistributionPage'
import { getClusterNames } from 'src/io/getClusters'
import { ClusterFilters } from './ClusterFilters'
import { CountryFilters } from './CountryFilters'

import { CountryFlagProps } from '../Common/CountryFlag'

const clusterNames = getClusterNames()

export function sortClusters(clusters?: ClusterState): ClusterState | undefined {
Expand All @@ -32,6 +33,7 @@ export interface DistributionSidebarProps {
clustersCollapsedByDefault?: boolean
coutriesCollapsedByDefault?: boolean
enabledFilters: string[]
Icon?: React.ComponentType<CountryFlagProps>
onClusterFilterChange(cluster: string): void
onClusterFilterSelectAll(): void
onClusterFilterDeselectAll(): void
Expand All @@ -47,6 +49,7 @@ export function DistributionSidebar({
clustersCollapsedByDefault = true,
coutriesCollapsedByDefault = true,
enabledFilters,
Icon,
onClusterFilterChange,
onClusterFilterSelectAll,
onClusterFilterDeselectAll,
Expand Down Expand Up @@ -78,6 +81,7 @@ export function DistributionSidebar({
key="country-filters"
regionsTitle={regionsTitle}
withIcons
Icon={Icon}
countries={countries}
onFilterChange={onCountryFilterChange}
onFilterSelectAll={onCountryFilterSelectAll}
Expand Down Expand Up @@ -110,6 +114,7 @@ export function DistributionSidebar({
onCountryFilterDeselectAll,
onCountryFilterSelectAll,
regionsTitle,
Icon,
],
)

Expand Down
5 changes: 5 additions & 0 deletions web/src/types/country-flag-icons/react/3x2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { FC, SVGProps } from 'react'

declare const Flags: Record<string, FC<SVGProps<SVGSVGElement>>>

export default Flags
15 changes: 15 additions & 0 deletions web/src/types/iso-3166-1-alpha-2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export declare class Iso31661a2 {
getCountry(code: string): string | undefined

getCode(country: string): string | undefined

getCountries(): string[]

getCodes(): string[]

getData(): Record<string, string>
}

const iso31661a2: Iso31661a2

export default iso31661a2
17 changes: 17 additions & 0 deletions web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4294,6 +4294,11 @@ cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"

country-flag-icons@1.2.10:
version "1.2.10"
resolved "https://registry.yarnpkg.com/country-flag-icons/-/country-flag-icons-1.2.10.tgz#c60fdf25883abacd28fbbf3842b920890f944591"
integrity sha512-nG+kGe4wVU9M+EsLUhP4buSuNdBH0leTm0Fv6RToXxO9BbbxUKV9VUq+9AcztnW7nEnweK7WYdtJsfyNLmQugQ==

create-ecdh@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
Expand Down Expand Up @@ -7333,6 +7338,13 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=

iso-3166-1-alpha-2@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/iso-3166-1-alpha-2/-/iso-3166-1-alpha-2-1.0.0.tgz#bc9e0bb94e584df5468a932997a28552e26f97ac"
integrity sha1-vJ4LuU5YTfVGipMpl6KFUuJvl6w=
dependencies:
mout "^0.11.0"

isobject@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
Expand Down Expand Up @@ -8796,6 +8808,11 @@ moment@2.27.0:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==

mout@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/mout/-/mout-0.11.1.tgz#ba3611df5f0e5b1ffbfd01166b8f02d1f5fa2b99"
integrity sha1-ujYR318OWx/7/QEWa48C0fX6K5k=

move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
Expand Down