@takaro/lib-components 0.0.14 → 0.0.15

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.0.14",
3
+ "version": "0.0.15",
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",
@@ -31,16 +31,33 @@
31
31
  "@rjsf/core": "5.20.0",
32
32
  "@rjsf/utils": "5.20.0",
33
33
  "@rjsf/validator-ajv8": "5.20.0",
34
+ "@sentry/react": "8.32.0",
34
35
  "@tanstack/react-table": "8.20.5",
35
36
  "@tanstack/react-router": "1.58.15",
36
37
  "@types/luxon": "3.4.2",
38
+ "@visx/axis": "3.10.1",
39
+ "@visx/brush": "3.10.4",
40
+ "@visx/curve": "3.3.0",
41
+ "@visx/event": "3.3.0",
42
+ "@visx/geo": "3.5.0",
43
+ "@visx/gradient": "3.3.0",
44
+ "@visx/grid": "3.5.0",
45
+ "@visx/group": "3.3.0",
46
+ "@visx/heatmap": "3.3.0",
47
+ "@visx/mock-data": "3.3.0",
48
+ "@visx/pattern": "3.3.0",
49
+ "@visx/responsive": "3.10.2",
50
+ "@visx/scale": "3.5.0",
51
+ "@visx/shape": "3.5.0",
52
+ "@visx/tooltip": "3.3.0",
53
+ "@visx/vendor": "3.5.0",
54
+ "@visx/zoom": "^3.3.0",
37
55
  "framer-motion": "11.9.0",
38
56
  "luxon": "3.5.0",
39
57
  "notistack": "3.0.1",
40
58
  "polished": "4.3.1",
41
59
  "react": "18.3.1",
42
60
  "react-dnd": "16.0.1",
43
- "@sentry/react": "8.32.0",
44
61
  "react-dnd-html5-backend": "16.0.1",
45
62
  "react-dom": "18.3.1",
46
63
  "react-hook-form": "7.53.0",
@@ -50,23 +67,7 @@
50
67
  "react-window": "1.8.10",
51
68
  "simplebar-react": "3.2.6",
52
69
  "styled-components": "5.3.11",
53
- "web-vitals": "4.2.3",
54
- "@visx/scale": "3.5.0",
55
- "@visx/responsive": "3.10.2",
56
- "@visx/group": "3.3.0",
57
- "@visx/heatmap": "3.3.0",
58
- "@visx/tooltip": "3.3.0",
59
- "@visx/event": "3.3.0",
60
- "@visx/shape": "3.5.0",
61
- "@visx/grid": "3.5.0",
62
- "@visx/vendor": "3.5.0",
63
- "@visx/brush": "3.10.4",
64
- "@visx/axis": "3.10.1",
65
- "@visx/pattern": "3.3.0",
66
- "@visx/curve": "3.3.0",
67
- "@visx/gradient": "3.3.0",
68
- "@visx/mock-data": "3.3.0",
69
- "@visx/geo": "3.5.0",
70
- "topojson-client": "3.1.0"
70
+ "topojson-client": "3.1.0",
71
+ "web-vitals": "4.2.3"
71
72
  }
72
73
  }
@@ -23,12 +23,6 @@ const Wrapper = styled.div`
23
23
  width: 500px;
24
24
  `;
25
25
 
26
- const Header = styled.div`
27
- display: flex;
28
- align-items: center;
29
- justify-content: space-between;
30
- `;
31
-
32
26
  export const Default: StoryFn<AreaChartProps<AppleStock>> = (args) => {
33
27
  const getDate = (d: AppleStock) => new Date(d.date);
34
28
  const getStockValue = (d: AppleStock) => d.close;
@@ -57,7 +51,7 @@ function generateData() {
57
51
  const data: PingData[] = [];
58
52
  for (let i = 0; i < 100; i++) {
59
53
  const timestamp = faker.date.between({ from: '2021-01-01T00:00:00Z', to: '2021-01-31T23:59:59Z' }).toISOString();
60
- const latency = faker.number.float({ min: 0, max: 70, precision: 1 });
54
+ const latency = faker.number.float({ min: 0, max: 70, fractionDigits: 1 });
61
55
  data.push({ timestamp, latency });
62
56
  }
63
57
  data.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
@@ -75,8 +69,7 @@ export const PingExample: StoryFn = () => {
75
69
  return (
76
70
  <div style={{ width: 800, height: 300 }}>
77
71
  <Card variant="outline">
78
- <Header>
79
- <h2 style={{ marginBottom: '10px' }}>Ping latency</h2>
72
+ <Card.Title label="Ping latency">
80
73
  <Dropdown>
81
74
  <Dropdown.Trigger asChild>
82
75
  <IconButton icon={<MenuIcon />} ariaLabel="open menu" />
@@ -87,17 +80,19 @@ export const PingExample: StoryFn = () => {
87
80
  <Dropdown.Menu.Item onClick={() => {}} label="Option 3" />
88
81
  </Dropdown.Menu>
89
82
  </Dropdown>
90
- </Header>
91
- <div style={{ height: '500px' }}>
92
- <AreaChart<PingData>
93
- name="Ping"
94
- xAccessor={getDate}
95
- yAccessor={getLatency}
96
- tooltipAccessor={tooltipAccessor}
97
- data={generateData()}
98
- showBrush={false}
99
- />
100
- </div>
83
+ </Card.Title>
84
+ <Card.Body>
85
+ <div style={{ height: '500px' }}>
86
+ <AreaChart<PingData>
87
+ name="Ping"
88
+ xAccessor={getDate}
89
+ yAccessor={getLatency}
90
+ tooltipAccessor={tooltipAccessor}
91
+ data={generateData()}
92
+ showBrush={false}
93
+ />
94
+ </div>
95
+ </Card.Body>
101
96
  </Card>
102
97
  </div>
103
98
  );
@@ -10,8 +10,7 @@ interface Shape {
10
10
  name: string;
11
11
  }
12
12
 
13
- // 3 letter country codes
14
- const data: Shape[] = [
13
+ const alpha3: Shape[] = [
15
14
  { code: 'BEL', amount: 10, name: 'Belgium' },
16
15
  { code: 'CAN', amount: 20, name: 'Canada' },
17
16
  { code: 'DEU', amount: 30, name: 'Germany' },
@@ -27,19 +26,22 @@ const data: Shape[] = [
27
26
  export default {
28
27
  title: 'Charts/GeoMercator',
29
28
  component: GeoMercator,
29
+ args: {
30
+ showZoomControls: false,
31
+ allowZoomAndDrag: false,
32
+ },
30
33
  } as Meta<GeoMercatorProps<Shape>>;
31
34
 
32
35
  const Wrapper = styled.div`
33
- width: 90vw;
34
- height: 90vh;
36
+ width: fit-content;
35
37
  `;
36
38
 
37
39
  const Inner = styled.div`
38
- width: 90vw;
39
- height: 90vh;
40
+ width: 800px;
41
+ height: 800px;
40
42
  `;
41
43
 
42
- export const Default: StoryFn<GeoMercatorProps<Shape>> = () => {
44
+ export const Default: StoryFn<GeoMercatorProps<Shape>> = (args) => {
43
45
  const getCountry = (d: Shape) => d.code;
44
46
  const getAmount = (d: Shape) => Number(d.amount);
45
47
  const tooltipAccessor = (d: Shape) => {
@@ -48,17 +50,21 @@ export const Default: StoryFn<GeoMercatorProps<Shape>> = () => {
48
50
 
49
51
  return (
50
52
  <Wrapper>
51
- <Card variant="outline">
52
- <Inner>
53
- <h2>Map</h2>
54
- <GeoMercator<Shape>
55
- name="geo-mercator"
56
- xAccessor={getCountry}
57
- yAccessor={getAmount}
58
- tooltipAccessor={tooltipAccessor}
59
- data={data}
60
- />
61
- </Inner>
53
+ <Card>
54
+ <Card.Title label="Map" />
55
+ <Card.Body>
56
+ <Inner>
57
+ <GeoMercator<Shape>
58
+ name="geo-mercator"
59
+ xAccessor={getCountry}
60
+ yAccessor={getAmount}
61
+ tooltipAccessor={tooltipAccessor}
62
+ data={alpha3}
63
+ showZoomControls={args.showZoomControls}
64
+ allowZoomAndDrag={args.allowZoomAndDrag}
65
+ />
66
+ </Inner>
67
+ </Card.Body>
62
68
  </Card>
63
69
  </Wrapper>
64
70
  );
@@ -1,14 +1,17 @@
1
1
  import * as topojson from 'topojson-client';
2
2
  import topology from './world.json';
3
3
  import { Graticule, Mercator } from '@visx/geo';
4
+ import { scaleLinear } from '@visx/scale';
4
5
  import { ParentSize } from '@visx/responsive';
5
6
 
6
7
  import { getDefaultTooltipStyles, InnerChartProps, Margin } from '../util';
7
8
  import { useTheme } from '../../../hooks';
8
9
  import { useTooltip, Tooltip } from '@visx/tooltip';
10
+ import { Zoom } from '@visx/zoom';
9
11
  import { useCallback } from 'react';
10
12
  import { localPoint } from '@visx/event';
11
- import { shade } from 'polished';
13
+ import { ZoomControls } from '../ZoomControls';
14
+ import { alpha2ToAlpha3 } from './iso3166-alpha2-to-alpha3';
12
15
 
13
16
  interface FeatureShape {
14
17
  type: 'Feature';
@@ -28,11 +31,14 @@ export interface GeoMercatorProps<T> {
28
31
  name: string;
29
32
  data: T[];
30
33
  margin?: Margin;
31
- xAccessor: (d: T) => string; // country: string
32
- yAccessor: (d: T) => number; // amount: number (used for color)
34
+ xAccessor: (d: T) => string;
35
+ yAccessor: (d: T) => number;
33
36
  tooltipAccessor?: (d: T) => string;
37
+ showZoomControls?: boolean;
38
+ allowZoomAndDrag?: boolean;
34
39
  }
35
40
 
41
+ type InnerGeoMercator<T> = InnerChartProps & GeoMercatorProps<T>;
36
42
  const defaultMargin = { top: 0, right: 0, bottom: 0, left: 0 };
37
43
  export const GeoMercator = <T,>({
38
44
  data,
@@ -41,46 +47,51 @@ export const GeoMercator = <T,>({
41
47
  name,
42
48
  margin = defaultMargin,
43
49
  tooltipAccessor,
50
+ showZoomControls = false,
51
+ allowZoomAndDrag = false,
44
52
  }: GeoMercatorProps<T>) => {
45
53
  return (
46
- <>
47
- <ParentSize>
48
- {(parent) => (
49
- <Chart<T>
50
- name={name}
51
- data={data}
52
- width={parent.width}
53
- height={parent.height}
54
- margin={margin}
55
- yAccessor={yAccessor}
56
- xAccessor={xAccessor}
57
- tooltipAccessor={tooltipAccessor}
58
- />
59
- )}
60
- </ParentSize>
61
- </>
54
+ <ParentSize>
55
+ {(parent) => (
56
+ <Chart<T>
57
+ name={name}
58
+ data={data}
59
+ width={parent.width}
60
+ height={parent.height}
61
+ margin={margin}
62
+ yAccessor={yAccessor}
63
+ xAccessor={xAccessor}
64
+ allowZoomAndDrag={allowZoomAndDrag}
65
+ showZoomControls={showZoomControls}
66
+ tooltipAccessor={tooltipAccessor}
67
+ />
68
+ )}
69
+ </ParentSize>
62
70
  );
63
71
  };
64
72
 
65
- type InnerGeoMercator<T> = InnerChartProps & GeoMercatorProps<T>;
66
-
67
73
  const Chart = <T,>({
68
74
  width,
75
+ height,
69
76
  yAccessor,
70
77
  xAccessor,
71
78
  data,
72
79
  tooltipAccessor,
73
80
  name,
74
- height,
75
- }: // margin = defaultMargin,
76
- // tooltipAccessor
77
- InnerGeoMercator<T>) => {
81
+ allowZoomAndDrag,
82
+ showZoomControls,
83
+ }: InnerGeoMercator<T>) => {
78
84
  const theme = useTheme();
79
85
  const { hideTooltip, showTooltip, tooltipData, tooltipLeft = 0, tooltipTop = 0 } = useTooltip<T>();
80
86
 
81
- const centerX = width / 2;
82
- const centerY = height / 2;
83
- const scale = (width / 630) * 100;
87
+ const centerX = width / 2 + 50;
88
+ const centerY = height / 2 + 150;
89
+ const scale = (width / 1000) * 100;
90
+
91
+ const colorScale = scaleLinear({
92
+ domain: [Math.min(...data.map((d) => yAccessor(d))), Math.max(...data.map((d) => yAccessor(d)))],
93
+ range: [theme.colors.backgroundAlt, theme.colors.primary],
94
+ });
84
95
 
85
96
  const handleTooltip = useCallback(
86
97
  (event: React.TouchEvent<SVGPathElement> | React.MouseEvent<SVGPathElement>, countryData: T | undefined) => {
@@ -89,40 +100,131 @@ InnerGeoMercator<T>) => {
89
100
  showTooltip({
90
101
  tooltipData: countryData,
91
102
  tooltipLeft: eventSvgCoords?.x,
92
- tooltipTop: eventSvgCoords?.y,
103
+ tooltipTop: eventSvgCoords ? eventSvgCoords.y - 38 : undefined,
93
104
  });
94
105
  },
95
- [yAccessor, xAccessor, width, height],
106
+ [showTooltip],
96
107
  );
97
108
 
98
- return width < 10 ? null : (
99
- <>
100
- <svg id={name} name={name} width={width} height={height}>
101
- <rect x={0} y={0} width={width} height={height} fill={theme.colors.background} rx={14} />
102
- <Mercator<FeatureShape> data={world.features} scale={scale} translate={[centerX, centerY + 50]}>
103
- {(mercator) => (
104
- <g>
105
- <Graticule graticule={(g) => mercator.path(g) || ''} stroke="rgba(33,33,33,0.05)" />
106
- {mercator.features.map(({ feature, path }, i) => {
107
- const countryData = data.find((d) => xAccessor(d) === feature.id);
108
- return (
109
- <path
110
- key={`map-feature-${i}`}
111
- d={path || ''}
112
- stroke={countryData ? theme.colors.primary : theme.colors.backgroundAccent}
113
- strokeWidth={countryData ? 1 : 0.5}
114
- fill={countryData ? shade(0.5, theme.colors.primary) : theme.colors.backgroundAlt}
115
- onMouseLeave={hideTooltip}
116
- onMouseMove={(e) => handleTooltip(e, countryData)}
117
- onTouchStart={(e) => handleTooltip(e, countryData)}
118
- onTouchMove={(e) => handleTooltip(e, countryData)}
119
- />
120
- );
121
- })}
122
- </g>
109
+ const renderMap = (zoom?: {
110
+ containerRef: React.RefObject<SVGSVGElement>;
111
+ isDragging: boolean;
112
+ dragStart: any;
113
+ dragMove: any;
114
+ dragEnd: any;
115
+ transformMatrix: { scaleX: number; translateX: number; translateY: number };
116
+ }) => (
117
+ <svg
118
+ id={name}
119
+ name={name}
120
+ width={width}
121
+ height={height}
122
+ ref={zoom?.containerRef}
123
+ style={{
124
+ touchAction: 'none',
125
+ cursor: allowZoomAndDrag && zoom ? (zoom.isDragging ? 'grabbing' : 'grab') : 'default',
126
+ }}
127
+ >
128
+ <rect x={0} y={0} width={width} height={height} fill={theme.colors.background} rx={10} />
129
+ <Mercator<FeatureShape>
130
+ data={world.features}
131
+ scale={zoom?.transformMatrix.scaleX || scale}
132
+ translate={[zoom?.transformMatrix.translateX || centerX, zoom?.transformMatrix.translateY || centerY]}
133
+ >
134
+ {(mercator) => (
135
+ <g>
136
+ <Graticule graticule={(g) => mercator.path(g) || ''} stroke="rgba(33,33,33,0.05)" />
137
+ {mercator.features.map(({ feature, path }, i) => {
138
+ const countryData = data.find((d) => {
139
+ // If alpha2 , try to convert it to aplha3.
140
+ // Since world.json only has support for alpha3.
141
+ if (xAccessor(d).length === 2) {
142
+ return alpha2ToAlpha3[xAccessor(d)] === feature.id;
143
+ }
144
+ return xAccessor(d) === feature.id;
145
+ });
146
+
147
+ const fillColor = countryData ? colorScale(yAccessor(countryData)) : theme.colors.backgroundAlt;
148
+
149
+ return (
150
+ <path
151
+ key={`map-feature-${i}`}
152
+ d={path || ''}
153
+ stroke={theme.colors.backgroundAccent}
154
+ strokeWidth={0.35}
155
+ fill={fillColor}
156
+ onMouseLeave={hideTooltip}
157
+ onMouseMove={(e) => handleTooltip(e, countryData)}
158
+ onTouchStart={(e) => handleTooltip(e, countryData)}
159
+ onTouchMove={(e) => handleTooltip(e, countryData)}
160
+ />
161
+ );
162
+ })}
163
+ </g>
164
+ )}
165
+ </Mercator>
166
+ {allowZoomAndDrag && zoom && (
167
+ <rect
168
+ x={0}
169
+ y={0}
170
+ width={width}
171
+ height={height}
172
+ rx={14}
173
+ fill="transparent"
174
+ onTouchStart={zoom.dragStart}
175
+ onTouchMove={zoom.dragMove}
176
+ onTouchEnd={zoom.dragEnd}
177
+ onMouseDown={zoom.dragStart}
178
+ onMouseMove={zoom.dragMove}
179
+ onMouseUp={zoom.dragEnd}
180
+ onMouseLeave={() => {
181
+ if (zoom.isDragging) zoom.dragEnd();
182
+ }}
183
+ />
184
+ )}
185
+ </svg>
186
+ );
187
+
188
+ return allowZoomAndDrag ? (
189
+ <Zoom<SVGSVGElement>
190
+ width={width}
191
+ height={height}
192
+ scaleXMin={100}
193
+ scaleXMax={1000}
194
+ scaleYMin={100}
195
+ scaleYMax={1000}
196
+ initialTransformMatrix={{
197
+ scaleX: scale,
198
+ scaleY: scale,
199
+ translateX: centerX,
200
+ translateY: centerY,
201
+ skewX: 0,
202
+ skewY: 0,
203
+ }}
204
+ >
205
+ {(zoom) => (
206
+ <div style={{ position: 'relative' }}>
207
+ {renderMap(zoom)}
208
+ {showZoomControls && <ZoomControls zoom={zoom} />}
209
+ {tooltipData && (
210
+ <Tooltip
211
+ top={tooltipTop}
212
+ left={tooltipLeft}
213
+ style={{
214
+ ...getDefaultTooltipStyles(theme),
215
+ textAlign: 'center',
216
+ transform: 'translate(-50%)',
217
+ }}
218
+ >
219
+ {tooltipAccessor ? tooltipAccessor(tooltipData) : `${xAccessor(tooltipData)}: ${yAccessor(tooltipData)}`}
220
+ </Tooltip>
123
221
  )}
124
- </Mercator>
125
- </svg>
222
+ </div>
223
+ )}
224
+ </Zoom>
225
+ ) : (
226
+ <div style={{ position: 'relative' }}>
227
+ {renderMap()}
126
228
  {tooltipData && (
127
229
  <Tooltip
128
230
  top={tooltipTop}
@@ -136,6 +238,6 @@ InnerGeoMercator<T>) => {
136
238
  {tooltipAccessor ? tooltipAccessor(tooltipData) : `${xAccessor(tooltipData)}: ${yAccessor(tooltipData)}`}
137
239
  </Tooltip>
138
240
  )}
139
- </>
241
+ </div>
140
242
  );
141
243
  };