@takaro/lib-components 0.1.5 → 0.2.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takaro/lib-components",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
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",
@@ -3,7 +3,7 @@
3
3
  exports[`Should render <Button/> 1`] = `
4
4
  <div>
5
5
  <button
6
- class="sc-cKXybt hgqaEX"
6
+ class="sc-djTQaJ ikqgQo"
7
7
  color="primary"
8
8
  tabindex="0"
9
9
  type="button"
@@ -4,7 +4,7 @@ exports[`Should render <IconButton /> 1`] = `
4
4
  <div>
5
5
  <button
6
6
  aria-label="test"
7
- class="sc-cgjDci duTlkQ"
7
+ class="sc-dBFDNq dwHZij"
8
8
  color="primary"
9
9
  type="button"
10
10
  >
@@ -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: 16px;
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>{flag}</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> = ({ id, placeholder, copyText = 'copied' }) => {
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
+ };
@@ -3,3 +3,6 @@ export type { CardProps } from './Card';
3
3
 
4
4
  export { Divider } from './Divider';
5
5
  export type { DividerProps } from './Divider';
6
+
7
+ export { Flag } from './Flag';
8
+ export type { FlagProps } from './Flag';