@cyber-harbour/ui 1.0.66 → 1.0.68

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": "@cyber-harbour/ui",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,24 @@
1
+ import { SVGProps } from 'react';
2
+
3
+ interface RotateLeftIconProps extends SVGProps<SVGSVGElement> {
4
+ fill?: string;
5
+ }
6
+
7
+ export const RotateLeftIcon = ({ fill = 'currentColor', ...props }: RotateLeftIconProps) => {
8
+ return (
9
+ <svg viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M8.37229 15.6666C4.90562 15.6666 2.09229 12.8466 2.09229 9.38659C2.09229 8.13993 2.45895 6.93326 3.15229 5.89326C3.30562 5.66659 3.61895 5.59993 3.84562 5.75326C4.07229 5.9066 4.13895 6.21993 3.98562 6.44659C3.40562 7.31326 3.09895 8.33326 3.09895 9.37993C3.09895 12.2933 5.46562 14.6599 8.37895 14.6599C11.2923 14.6599 13.659 12.2933 13.659 9.37993C13.659 6.46659 11.2856 4.09993 8.37229 4.09993C7.75895 4.09993 7.15895 4.18659 6.59229 4.35993C6.32562 4.43993 6.04562 4.29326 5.96562 4.02659C5.88562 3.75993 6.03229 3.47993 6.29895 3.39993C6.96562 3.19993 7.65895 3.09326 8.37229 3.09326C11.839 3.09326 14.6523 5.91326 14.6523 9.37326C14.6523 12.8333 11.839 15.6666 8.37229 15.6666Z"
12
+ fill={fill}
13
+ />
14
+ <path
15
+ d="M5.61881 4.54658C5.50548 4.54658 5.38548 4.50658 5.29215 4.42658C5.07881 4.23992 5.05881 3.92658 5.23881 3.71992L7.16548 1.50658C7.34548 1.29992 7.66548 1.27325 7.87215 1.45992C8.07881 1.63992 8.09881 1.95992 7.91881 2.16658L5.99215 4.37325C5.89215 4.48658 5.75215 4.54658 5.61881 4.54658Z"
16
+ fill={fill}
17
+ />
18
+ <path
19
+ d="M7.86545 6.18659C7.76545 6.18659 7.65879 6.15325 7.57212 6.09325L5.31879 4.44659C5.09879 4.28659 5.05212 3.97325 5.21212 3.75325C5.37212 3.52659 5.68545 3.47992 5.91212 3.63992L8.15879 5.27992C8.37879 5.43992 8.43212 5.75325 8.26545 5.97992C8.17212 6.11992 8.01879 6.18659 7.86545 6.18659Z"
20
+ fill={fill}
21
+ />
22
+ </svg>
23
+ );
24
+ };
@@ -0,0 +1,24 @@
1
+ import { SVGProps } from 'react';
2
+
3
+ interface RotateRightIconProps extends SVGProps<SVGSVGElement> {
4
+ fill?: string;
5
+ }
6
+
7
+ export const RotateRightIcon = ({ fill = 'currentColor', ...props }: RotateRightIconProps) => {
8
+ return (
9
+ <svg viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
10
+ <path
11
+ d="M8.37228 15.6668C4.90562 15.6668 2.09229 12.8468 2.09229 9.38676C2.09229 5.92676 4.90562 3.1001 8.37228 3.1001C9.08562 3.1001 9.77895 3.2001 10.4456 3.40676C10.7123 3.48676 10.859 3.76676 10.779 4.03343C10.699 4.3001 10.419 4.44676 10.1523 4.36676C9.58562 4.19343 8.98562 4.1001 8.37228 4.1001C5.45895 4.1001 3.09229 6.46676 3.09229 9.3801C3.09229 12.2934 5.45895 14.6601 8.37228 14.6601C11.2856 14.6601 13.6523 12.2934 13.6523 9.3801C13.6523 8.32676 13.3456 7.31343 12.7656 6.44676C12.6123 6.2201 12.6723 5.90676 12.9056 5.75343C13.1323 5.6001 13.4456 5.6601 13.599 5.89343C14.2923 6.92676 14.659 8.13343 14.659 9.38676C14.6523 12.8468 11.839 15.6668 8.37228 15.6668Z"
12
+ fill={fill}
13
+ />
14
+ <path
15
+ d="M11.1252 4.54664C10.9852 4.54664 10.8452 4.48664 10.7452 4.37331L8.81854 2.15998C8.63854 1.95331 8.65854 1.63331 8.86521 1.45331C9.07188 1.27331 9.39188 1.29331 9.57188 1.49998L11.4985 3.71331C11.6785 3.91998 11.6585 4.23998 11.4519 4.41998C11.3652 4.50664 11.2452 4.54664 11.1252 4.54664Z"
16
+ fill={fill}
17
+ />
18
+ <path
19
+ d="M8.87904 6.18696C8.72571 6.18696 8.57237 6.11363 8.47237 5.9803C8.31237 5.7603 8.35904 5.44696 8.57904 5.2803L10.8257 3.6403C11.0457 3.47363 11.359 3.52696 11.5257 3.74696C11.6924 3.96696 11.639 4.2803 11.419 4.44696L9.17237 6.09363C9.08571 6.1603 8.98571 6.18696 8.87904 6.18696Z"
20
+ fill={fill}
21
+ />
22
+ </svg>
23
+ );
24
+ };
@@ -62,3 +62,5 @@ export { PencilIcon } from './PencilIcon';
62
62
  export { UserInCircleIcon } from './UserInCircle';
63
63
  export { FlashIcon } from './FlashIcon';
64
64
  export { FolderInfoIcon } from './FolderInfoIcon';
65
+ export { RotateLeftIcon } from './RotateLeftIcon';
66
+ export { RotateRightIcon } from './RotateRightIcon';
@@ -0,0 +1,92 @@
1
+ import { createPortal } from 'react-dom';
2
+ import { styled } from 'styled-components';
3
+ import { useBodyScrollLock } from '../../utils';
4
+ import { createComponent, createStyledComponent, generatePropertySpaceStyle, StyledFabricComponent } from '../../Theme';
5
+
6
+ type Position = 'start' | 'center' | 'end';
7
+
8
+ export type OverlayProps = {
9
+ children: any;
10
+ onOutsideClick?: () => void;
11
+ append?: any;
12
+ appendPosition?: Position;
13
+ prepend?: any;
14
+ prependPosition?: Position;
15
+ };
16
+
17
+ export const Overlay = createComponent<OverlayProps>(
18
+ ({ children, onOutsideClick, prepend, append, prependPosition, appendPosition, ...props }) => {
19
+ useBodyScrollLock(true);
20
+ return createPortal(
21
+ <Container
22
+ {...props}
23
+ onClick={(e) => {
24
+ if (e.target === e.currentTarget && onOutsideClick) {
25
+ onOutsideClick();
26
+ }
27
+ }}
28
+ >
29
+ {!!prepend && <PrependWrapper $position={prependPosition}>{prepend}</PrependWrapper>}
30
+ <StyledWrapper>{children}</StyledWrapper>
31
+ {!!append && <AppendWrapper $position={appendPosition}>{append}</AppendWrapper>}
32
+ </Container>,
33
+ document.body
34
+ );
35
+ }
36
+ );
37
+
38
+ const PrependWrapper = styled.div<{ $position?: Position }>(
39
+ ({ theme, $position = 'end' }) => `
40
+ position: absolute;
41
+ top: ${theme.overlay.gap};
42
+ z-index: ${theme.zIndex.backdrop};
43
+ ${
44
+ $position === 'end'
45
+ ? `right: ${theme.overlay.gap};`
46
+ : $position === 'start'
47
+ ? `left: ${theme.overlay.gap};`
48
+ : 'left: 50%; transform: translateX(-50%);'
49
+ }
50
+ `
51
+ );
52
+
53
+ const AppendWrapper = styled.div<{ $position?: Position }>(
54
+ ({ theme, $position = 'center' }) => `
55
+ position: absolute;
56
+ bottom: ${theme.overlay.gap};
57
+ z-index: ${theme.zIndex.backdrop};
58
+ ${
59
+ $position === 'end'
60
+ ? `right: ${theme.overlay.gap};`
61
+ : $position === 'start'
62
+ ? `left: ${theme.overlay.gap};`
63
+ : 'left: 50%; transform: translateX(-50%);'
64
+ }
65
+ `
66
+ );
67
+
68
+ const StyledWrapper = styled.div`
69
+ max-width: 100%;
70
+ max-height: 100%;
71
+ overflow: hidden;
72
+ `;
73
+
74
+ const Container = createStyledComponent(
75
+ styled.div<StyledFabricComponent>(
76
+ ({ theme, py = theme.overlay.paddingBlock }) => `
77
+ position: fixed;
78
+ ${generatePropertySpaceStyle(theme, 'padding-block', py)};
79
+ top: 0;
80
+ bottom: 0;
81
+ left: 0;
82
+ right: 0;
83
+ min-width: 0;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ background: ${theme.overlay.background};
88
+ z-index: ${theme.zIndex.backdrop};
89
+
90
+ `
91
+ )
92
+ );
@@ -0,0 +1 @@
1
+ export * from './Overlay';
package/src/Core/index.ts CHANGED
@@ -23,3 +23,4 @@ export * from './Switch';
23
23
  export * from './Drawer';
24
24
  export * from './Tooltip';
25
25
  export * from './ContentLoader';
26
+ export * from './Overlay';
@@ -1181,6 +1181,49 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1181
1181
  [getNodeAtPosition]
1182
1182
  );
1183
1183
 
1184
+ const getHoveredIndex = useCallback(
1185
+ (x: number, y: number) => {
1186
+ let hoveredIndex = null;
1187
+ if (canvasRef.current && stateRef.current && buttonImages.length > 0) {
1188
+ const buttonRadius = (config.nodeSizeBase * config.nodeAreaFactor) / 2;
1189
+ const rect = canvasRef.current.getBoundingClientRect();
1190
+
1191
+ // Масштабуємо координати відносно розміру відображення полотна
1192
+ const canvasScaleX = canvasRef.current.width / rect.width;
1193
+ const canvasScaleY = canvasRef.current.height / rect.height;
1194
+
1195
+ // Масштабовані координати миші в системі координат полотна
1196
+ const scaledMouseX = x * canvasScaleX;
1197
+ const scaledMouseY = y * canvasScaleY;
1198
+
1199
+ // Застосовуємо поточну трансформацію для отримання світових координат
1200
+ const worldX = (scaledMouseX - stateRef.current.transform.x) / stateRef.current.transform.k;
1201
+ const worldY = (scaledMouseY - stateRef.current.transform.y) / stateRef.current.transform.k;
1202
+
1203
+ // Node position
1204
+ const nodeX = stateRef.current.selectedNode?.x || 0;
1205
+ const nodeY = stateRef.current.selectedNode?.y || 0;
1206
+
1207
+ // Обчислюємо кількість кнопок та їхні сектори
1208
+ const buttonCount = Math.min(buttonImages.length, 8);
1209
+ const sectorAngle = Math.min((Math.PI * 2) / buttonCount, Math.PI);
1210
+
1211
+ // Перевіряємо, чи вказівник миші знаходиться над будь-яким сектором кнопок
1212
+ for (let i = 0; i < buttonCount; i++) {
1213
+ const startAngle = i * sectorAngle;
1214
+ const endAngle = (i + 1) * sectorAngle;
1215
+
1216
+ if (isPointInButtonSector(worldX, worldY, nodeX, nodeY, buttonRadius, startAngle, endAngle)) {
1217
+ hoveredIndex = i;
1218
+ break;
1219
+ }
1220
+ }
1221
+ }
1222
+ return hoveredIndex;
1223
+ },
1224
+ [buttonImages]
1225
+ );
1226
+
1184
1227
  // Обробка руху миші для перетягування та наведення
1185
1228
  const handleMouseMove = useCallback(
1186
1229
  (event: React.MouseEvent<HTMLCanvasElement>) => {
@@ -1274,40 +1317,7 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1274
1317
 
1275
1318
  // Логіка виявлення наведення на кнопки
1276
1319
  if (stateRef.current.selectedNode && canvasRef.current && buttonImages.length > 0) {
1277
- const buttonRadius = (config.nodeSizeBase * config.nodeAreaFactor) / 2;
1278
-
1279
- // Масштабуємо координати відносно розміру відображення полотна
1280
- const canvasScaleX = canvasRef.current.width / rect.width;
1281
- const canvasScaleY = canvasRef.current.height / rect.height;
1282
-
1283
- // Масштабовані координати миші в системі координат полотна
1284
- const scaledMouseX = x * canvasScaleX;
1285
- const scaledMouseY = y * canvasScaleY;
1286
-
1287
- // Застосовуємо поточну трансформацію для отримання світових координат
1288
- const worldX = (scaledMouseX - stateRef.current.transform.x) / stateRef.current.transform.k;
1289
- const worldY = (scaledMouseY - stateRef.current.transform.y) / stateRef.current.transform.k;
1290
-
1291
- // Node position
1292
- const nodeX = stateRef.current.selectedNode.x || 0;
1293
- const nodeY = stateRef.current.selectedNode.y || 0;
1294
-
1295
- // Обчислюємо кількість кнопок та їхні сектори
1296
- const buttonCount = Math.min(buttonImages.length, 8);
1297
- const sectorAngle = Math.min((Math.PI * 2) / buttonCount, Math.PI);
1298
-
1299
- let hoveredIndex = null;
1300
-
1301
- // Перевіряємо, чи вказівник миші знаходиться над будь-яким сектором кнопок
1302
- for (let i = 0; i < buttonCount; i++) {
1303
- const startAngle = i * sectorAngle;
1304
- const endAngle = (i + 1) * sectorAngle;
1305
-
1306
- if (isPointInButtonSector(worldX, worldY, nodeX, nodeY, buttonRadius, startAngle, endAngle)) {
1307
- hoveredIndex = i;
1308
- break;
1309
- }
1310
- }
1320
+ const hoveredIndex = getHoveredIndex(x, y);
1311
1321
  if (hoveredIndex !== null) hoveredNode = stateRef.current.selectedNode; // Set hoveredNode to selectedNode for further processing
1312
1322
  if (hoveredIndex !== stateRef.current.hoveredButtonIndex) {
1313
1323
  shouldRender = true; // Only render if hovered button index has changed
@@ -1371,6 +1381,7 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1371
1381
  handleLinkHover,
1372
1382
  renderCanvas2D,
1373
1383
  isPointInButtonSector,
1384
+ getHoveredIndex,
1374
1385
  ]
1375
1386
  );
1376
1387
 
@@ -1396,7 +1407,9 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1396
1407
  if (rect) {
1397
1408
  const x = event.clientX - rect.left;
1398
1409
  const y = event.clientY - rect.top;
1399
-
1410
+ if (buttonImages.length > 0 && stateRef.current.hoveredButtonIndex === null) {
1411
+ stateRef.current.hoveredButtonIndex = getHoveredIndex(x, y);
1412
+ }
1400
1413
  // Спочатку перевіряємо, чи ми клікаємо на кнопку обраного вузла
1401
1414
  let isButtonClick = false;
1402
1415
  if (
@@ -1409,6 +1422,7 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1409
1422
  if (button && button.onClick) {
1410
1423
  button.onClick(stateRef.current.selectedNode);
1411
1424
  isButtonClick = true;
1425
+ stateRef.current.hoveredButtonIndex = null;
1412
1426
  }
1413
1427
  }
1414
1428
 
@@ -1465,7 +1479,16 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1465
1479
 
1466
1480
  renderCanvas2D();
1467
1481
  },
1468
- [buttons, renderCanvas2D, handleNodeClick, handleBackgroundClick, getLinkAtPosition, handleLinkClick]
1482
+ [
1483
+ buttons,
1484
+ renderCanvas2D,
1485
+ handleNodeClick,
1486
+ handleBackgroundClick,
1487
+ getLinkAtPosition,
1488
+ handleLinkClick,
1489
+ getHoveredIndex,
1490
+ buttonImages,
1491
+ ]
1469
1492
  );
1470
1493
 
1471
1494
  // Обробляємо подію колеса миші для масштабування
@@ -1509,6 +1532,47 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1509
1532
  [renderCanvas2D]
1510
1533
  );
1511
1534
 
1535
+ //Обробка подій на тачскрін девайсах
1536
+ const convertTouchToMouseEvent = (
1537
+ e: TouchEvent | React.TouchEvent<HTMLCanvasElement>,
1538
+ callback: (event: React.MouseEvent<HTMLCanvasElement>) => void
1539
+ ) => {
1540
+ const touch = e.touches[0];
1541
+ if (touch) {
1542
+ // Create a synthetic React.MouseEvent-like object
1543
+ const syntheticEvent = {
1544
+ clientX: touch.clientX,
1545
+ clientY: touch.clientY,
1546
+ } as React.MouseEvent<HTMLCanvasElement>;
1547
+ callback(syntheticEvent);
1548
+ }
1549
+ };
1550
+ const handleTouchStart = useCallback(
1551
+ (e: React.TouchEvent<HTMLCanvasElement>) => {
1552
+ convertTouchToMouseEvent(e, handleMouseDown);
1553
+ },
1554
+ [handleMouseDown]
1555
+ );
1556
+
1557
+ const handleTouchEnd = useCallback(
1558
+ (e: TouchEvent) => {
1559
+ if (stateRef.current.mustBeStoppedPropagation) {
1560
+ e.preventDefault();
1561
+ e.stopPropagation();
1562
+ }
1563
+ stateRef.current.mustBeStoppedPropagation = false;
1564
+ convertTouchToMouseEvent(e, handleMouseUp);
1565
+ },
1566
+ [handleMouseUp]
1567
+ );
1568
+
1569
+ const handleTouchMove = useCallback(
1570
+ (e: React.TouchEvent<HTMLCanvasElement>) => {
1571
+ convertTouchToMouseEvent(e, handleMouseMove);
1572
+ },
1573
+ [handleMouseMove]
1574
+ );
1575
+
1512
1576
  useImperativeHandle(
1513
1577
  ref,
1514
1578
  () => ({
@@ -1647,12 +1711,14 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1647
1711
 
1648
1712
  // Add event listener with passive: false to allow preventDefault
1649
1713
  canvas.addEventListener('wheel', handleWheel, { passive: false });
1714
+ canvas.addEventListener('touchend', handleTouchEnd, { passive: false });
1650
1715
 
1651
1716
  // Clean up when component unmounts
1652
1717
  return () => {
1653
1718
  canvas.removeEventListener('wheel', handleWheel);
1719
+ canvas.removeEventListener('touchend', handleTouchEnd);
1654
1720
  };
1655
- }, [handleWheel]);
1721
+ }, [handleWheel, handleTouchEnd]);
1656
1722
 
1657
1723
  return (
1658
1724
  <Wrapper>
@@ -1665,6 +1731,8 @@ export const Graph2D: any = forwardRef<Graph2DRef, Graph2DProps>(
1665
1731
  onMouseUp={handleMouseUp}
1666
1732
  onMouseLeave={handleMouseUp}
1667
1733
  onClick={handleClick}
1734
+ onTouchStart={handleTouchStart}
1735
+ onTouchMove={handleTouchMove}
1668
1736
  />
1669
1737
  </Wrapper>
1670
1738
  );
@@ -922,8 +922,8 @@ export const darkThemePx: Theme = {
922
922
  },
923
923
  color: {
924
924
  icon: '#FDC700',
925
- text: '#894B00',
926
- background: '#FFFBE0',
925
+ text: '#FFEDD8',
926
+ background: '#68550E',
927
927
  },
928
928
  },
929
929
  label: {
@@ -985,6 +985,11 @@ export const darkThemePx: Theme = {
985
985
  height: 26,
986
986
  iconSize: 8,
987
987
  },
988
+ overlay: {
989
+ paddingBlock: 40,
990
+ gap: 6,
991
+ background: 'rgba(0, 0, 0, 0.5)',
992
+ },
988
993
  };
989
994
 
990
995
  export const darkTheme = convertPaletteToRem(darkThemePx, darkThemePx.baseSize) as DefaultTheme;
@@ -984,6 +984,11 @@ export const lightThemePx: Theme = {
984
984
  height: 26,
985
985
  iconSize: 8,
986
986
  },
987
+ overlay: {
988
+ paddingBlock: 40,
989
+ gap: 6,
990
+ background: 'rgba(16, 16, 16, 0.1)',
991
+ },
987
992
  };
988
993
 
989
994
  export const lightTheme = convertPaletteToRem(lightThemePx, lightThemePx.baseSize) as DefaultTheme;
@@ -328,6 +328,11 @@ export type Theme = {
328
328
  height: string | number;
329
329
  iconSize: string | number;
330
330
  };
331
+ overlay: {
332
+ paddingBlock: string | number;
333
+ background: string;
334
+ gap: string | number;
335
+ };
331
336
  };
332
337
 
333
338
  //TODO check and refactoring