@takaro/lib-components 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/actions/Button/__snapshots__/Button.test.tsx.snap +1 -1
- package/src/components/actions/IconButton/__snapshots__/IconButton.test.tsx.snap +1 -1
- package/src/components/charts/GeoMercator/index.tsx +3 -17
- package/src/components/data/CopyId/index.tsx +73 -3
- package/src/components/visual/Flag/index.tsx +61 -0
- package/src/components/visual/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takaro/lib-components",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Takaro UI is a simple and customizable component library to build React apps faster within the Takaro eco system",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -13,19 +13,7 @@ import { useCallback } from 'react';
|
|
|
13
13
|
import { localPoint } from '@visx/event';
|
|
14
14
|
import { ZoomControls } from '../ZoomControls';
|
|
15
15
|
import { alpha2ToAlpha3 } from './iso3166-alpha2-to-alpha3';
|
|
16
|
-
|
|
17
|
-
const getCountryFlag = (countryCode: string): string => {
|
|
18
|
-
if (!countryCode || countryCode.length !== 2) {
|
|
19
|
-
return '';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const codePoints = countryCode
|
|
23
|
-
.toUpperCase()
|
|
24
|
-
.split('')
|
|
25
|
-
.map((char) => 127397 + char.charCodeAt(0));
|
|
26
|
-
|
|
27
|
-
return String.fromCodePoint(...codePoints);
|
|
28
|
-
};
|
|
16
|
+
import { Flag } from '../../visual/Flag';
|
|
29
17
|
|
|
30
18
|
const alpha3ToAlpha2: Record<string, string> = Object.entries(alpha2ToAlpha3).reduce(
|
|
31
19
|
(acc, [alpha2, alpha3]) => {
|
|
@@ -92,9 +80,8 @@ const CountryCell = styled.div`
|
|
|
92
80
|
`;
|
|
93
81
|
|
|
94
82
|
const FlagCell = styled(CountryCell)`
|
|
95
|
-
width:
|
|
83
|
+
width: 20px;
|
|
96
84
|
text-align: center;
|
|
97
|
-
font-size: 10px;
|
|
98
85
|
padding-right: 4px;
|
|
99
86
|
`;
|
|
100
87
|
|
|
@@ -151,13 +138,12 @@ const CountrySidebar = <T,>({ data, xAccessor, yAccessor }: CountrySidebarProps<
|
|
|
151
138
|
const countryCode = xAccessor(item);
|
|
152
139
|
const playerCount = yAccessor(item);
|
|
153
140
|
const alpha2Code = countryCode.length === 3 ? alpha3ToAlpha2[countryCode] : countryCode;
|
|
154
|
-
const flag = getCountryFlag(alpha2Code);
|
|
155
141
|
// Prefer 2-letter country codes for display if available
|
|
156
142
|
const displayCode = alpha2Code || countryCode;
|
|
157
143
|
|
|
158
144
|
return (
|
|
159
145
|
<CountryRow key={`${countryCode}-${index}`}>
|
|
160
|
-
<FlagCell>{
|
|
146
|
+
<FlagCell>{alpha2Code && <Flag countryCode={alpha2Code} size={1} />}</FlagCell>
|
|
161
147
|
<CountryCell2>{displayCode}</CountryCell2>
|
|
162
148
|
<CountCell>{playerCount}</CountCell>
|
|
163
149
|
</CountryRow>
|
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
import { FC, useEffect, useState } from 'react';
|
|
2
|
-
import { Chip } from '../../../components';
|
|
3
|
-
import { AiFillCopy as CopyIcon, AiOutlineCheck as CheckmarkIcon } from 'react-icons/ai';
|
|
2
|
+
import { Chip, Tooltip } from '../../../components';
|
|
3
|
+
import { AiFillCopy as CopyIcon, AiOutlineCheck as CheckmarkIcon, AiOutlineLink as LinkIcon } from 'react-icons/ai';
|
|
4
|
+
import { styled } from '../../../styled';
|
|
5
|
+
|
|
6
|
+
const Container = styled.div`
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
gap: ${({ theme }) => theme.spacing['0_5']};
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
const LinkButton = styled.button`
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
background-color: ${({ theme }) => theme.colors.backgroundAlt};
|
|
17
|
+
border: 1px solid ${({ theme }) => theme.colors.backgroundAccent};
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
padding: ${({ theme }) => theme.spacing['0_5']};
|
|
20
|
+
border-radius: ${({ theme }) => theme.borderRadius.small};
|
|
21
|
+
transition: all 0.2s ease;
|
|
22
|
+
|
|
23
|
+
&:hover {
|
|
24
|
+
background-color: ${({ theme }) => theme.colors.primary};
|
|
25
|
+
border-color: ${({ theme }) => theme.colors.primary};
|
|
26
|
+
|
|
27
|
+
svg {
|
|
28
|
+
color: ${({ theme }) => theme.colors.white};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
svg {
|
|
33
|
+
width: 16px;
|
|
34
|
+
height: 16px;
|
|
35
|
+
color: ${({ theme }) => theme.colors.primary};
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
4
38
|
|
|
5
39
|
export interface CopyIdProps {
|
|
6
40
|
id?: string;
|
|
7
41
|
placeholder?: string;
|
|
8
42
|
copyText?: string;
|
|
43
|
+
externalLink?: string;
|
|
44
|
+
externalLinkTooltip?: string;
|
|
9
45
|
}
|
|
10
46
|
|
|
11
|
-
export const CopyId: FC<CopyIdProps> = ({
|
|
47
|
+
export const CopyId: FC<CopyIdProps> = ({
|
|
48
|
+
id,
|
|
49
|
+
placeholder,
|
|
50
|
+
copyText = 'copied',
|
|
51
|
+
externalLink,
|
|
52
|
+
externalLinkTooltip = 'Open external link',
|
|
53
|
+
}) => {
|
|
12
54
|
const [copied, setCopied] = useState<boolean>(false);
|
|
13
55
|
|
|
14
56
|
function handleCopy(text: string) {
|
|
@@ -27,6 +69,34 @@ export const CopyId: FC<CopyIdProps> = ({ id, placeholder, copyText = 'copied' }
|
|
|
27
69
|
|
|
28
70
|
if (!id) return <></>;
|
|
29
71
|
|
|
72
|
+
if (externalLink) {
|
|
73
|
+
return (
|
|
74
|
+
<Container>
|
|
75
|
+
<Chip
|
|
76
|
+
icon={copied ? <CheckmarkIcon /> : <CopyIcon />}
|
|
77
|
+
onClick={() => handleCopy(id)}
|
|
78
|
+
variant="outline"
|
|
79
|
+
label={copied ? copyText : placeholder ? placeholder : id}
|
|
80
|
+
color="backgroundAccent"
|
|
81
|
+
/>
|
|
82
|
+
<Tooltip>
|
|
83
|
+
<Tooltip.Trigger asChild>
|
|
84
|
+
<LinkButton
|
|
85
|
+
onClick={(e) => {
|
|
86
|
+
e.stopPropagation();
|
|
87
|
+
window.open(externalLink, '_blank', 'noopener,noreferrer');
|
|
88
|
+
}}
|
|
89
|
+
aria-label={externalLinkTooltip}
|
|
90
|
+
>
|
|
91
|
+
<LinkIcon />
|
|
92
|
+
</LinkButton>
|
|
93
|
+
</Tooltip.Trigger>
|
|
94
|
+
<Tooltip.Content>{externalLinkTooltip}</Tooltip.Content>
|
|
95
|
+
</Tooltip>
|
|
96
|
+
</Container>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
30
100
|
return (
|
|
31
101
|
<Chip
|
|
32
102
|
icon={copied ? <CheckmarkIcon /> : <CopyIcon />}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { styled } from '../../../styled';
|
|
2
|
+
import { FC, ImgHTMLAttributes, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const FlagImg = styled.img`
|
|
5
|
+
width: 1rem;
|
|
6
|
+
height: 1rem;
|
|
7
|
+
object-fit: cover;
|
|
8
|
+
border-radius: 2px;
|
|
9
|
+
`;
|
|
10
|
+
|
|
11
|
+
const FallbackContainer = styled.div<{ size: number }>`
|
|
12
|
+
width: ${({ size }) => `${size}rem`};
|
|
13
|
+
height: ${({ size }) => `${size}rem`};
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
background-color: ${({ theme }) => theme.colors.backgroundAlt};
|
|
18
|
+
border-radius: 2px;
|
|
19
|
+
font-size: ${({ size }) => `${size * 0.5}rem`};
|
|
20
|
+
color: ${({ theme }) => theme.colors.textAlt};
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export interface FlagProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt'> {
|
|
25
|
+
/** 2-letter ISO country code (e.g., 'US', 'FR', 'JP') */
|
|
26
|
+
countryCode: string;
|
|
27
|
+
/** Optional country name for better accessibility */
|
|
28
|
+
countryName?: string;
|
|
29
|
+
/** Size of the flag in rem units */
|
|
30
|
+
size?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Flag: FC<FlagProps> = ({ countryCode, countryName, size = 1, style, ...props }) => {
|
|
34
|
+
const [imageError, setImageError] = useState(false);
|
|
35
|
+
|
|
36
|
+
if (!countryCode || countryCode.length !== 2) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const uppercaseCode = countryCode.toUpperCase();
|
|
41
|
+
const flagUrl = `https://purecatamphetamine.github.io/country-flag-icons/3x2/${uppercaseCode}.svg`;
|
|
42
|
+
const altText = countryName ? `Flag of ${countryName}` : `Flag of ${uppercaseCode}`;
|
|
43
|
+
|
|
44
|
+
if (imageError) {
|
|
45
|
+
return <FallbackContainer size={size}>{uppercaseCode}</FallbackContainer>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<FlagImg
|
|
50
|
+
src={flagUrl}
|
|
51
|
+
alt={altText}
|
|
52
|
+
onError={() => setImageError(true)}
|
|
53
|
+
style={{
|
|
54
|
+
width: `${size}rem`,
|
|
55
|
+
height: `${size}rem`,
|
|
56
|
+
...style,
|
|
57
|
+
}}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|