@mcp-shark/mcp-shark 1.5.13 → 1.7.2

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.
Files changed (158) hide show
  1. package/README.md +482 -56
  2. package/bin/mcp-shark.js +146 -52
  3. package/core/cli/AutoFixEngine.js +93 -0
  4. package/core/cli/ConfigScanner.js +193 -0
  5. package/core/cli/DataLoader.js +200 -0
  6. package/core/cli/DeclarativeRuleEngine.js +363 -0
  7. package/core/cli/DoctorCommand.js +218 -0
  8. package/core/cli/FixHandlers.js +222 -0
  9. package/core/cli/HtmlReportGenerator.js +203 -0
  10. package/core/cli/IdeConfigPaths.js +175 -0
  11. package/core/cli/ListCommand.js +255 -0
  12. package/core/cli/LockCommand.js +164 -0
  13. package/core/cli/LockDiffEngine.js +152 -0
  14. package/core/cli/RuleRegistryConfig.js +131 -0
  15. package/core/cli/ScanCommand.js +244 -0
  16. package/core/cli/ScanService.js +200 -0
  17. package/core/cli/SecretDetector.js +92 -0
  18. package/core/cli/SharkScoreCalculator.js +109 -0
  19. package/core/cli/ToolClassifications.js +51 -0
  20. package/core/cli/ToxicFlowAnalyzer.js +212 -0
  21. package/core/cli/UpdateCommand.js +188 -0
  22. package/core/cli/WalkthroughGenerator.js +195 -0
  23. package/core/cli/WatchCommand.js +129 -0
  24. package/core/cli/YamlRuleEngine.js +197 -0
  25. package/core/cli/data/rule-packs/aauth-visibility.json +117 -0
  26. package/core/cli/data/rule-packs/agentic-security-2026.json +180 -0
  27. package/core/cli/data/rule-packs/general-security.json +173 -0
  28. package/core/cli/data/rule-packs/owasp-mcp-2026.json +244 -0
  29. package/core/cli/data/rule-packs/toxic-flow-heuristics.json +21 -0
  30. package/core/cli/data/rule-sources.json +5 -0
  31. package/core/cli/data/secret-patterns.json +18 -0
  32. package/core/cli/data/tool-classifications.json +111 -0
  33. package/core/cli/data/toxic-flow-rules.json +47 -0
  34. package/core/cli/index.js +23 -0
  35. package/core/cli/output/Banner.js +52 -0
  36. package/core/cli/output/Formatter.js +183 -0
  37. package/core/cli/output/JsonFormatter.js +106 -0
  38. package/core/cli/output/index.js +16 -0
  39. package/core/cli/secureRegistryFetch.js +157 -0
  40. package/core/cli/symbols.js +16 -0
  41. package/core/configs/environment.js +3 -1
  42. package/core/configs/index.js +3 -64
  43. package/core/container/DependencyContainer.js +4 -1
  44. package/core/mcp-server/index.js +4 -1
  45. package/core/mcp-server/server/external/all.js +10 -3
  46. package/core/mcp-server/server/external/config.js +62 -5
  47. package/core/models/RequestFilters.js +3 -0
  48. package/core/repositories/PacketRepository.js +16 -0
  49. package/core/services/AuditService.js +2 -0
  50. package/core/services/ConfigService.js +9 -1
  51. package/core/services/ConfigTransformService.js +34 -2
  52. package/core/services/RequestService.js +58 -5
  53. package/core/services/ServerManagementService.js +59 -4
  54. package/core/services/security/StaticRulesService.js +69 -13
  55. package/core/services/security/TrafficAnalysisService.js +19 -1
  56. package/core/services/security/TrafficToxicFlowService.js +154 -0
  57. package/core/services/security/aauthGraph.js +199 -0
  58. package/core/services/security/aauthParser.js +274 -0
  59. package/core/services/security/aauthSelfTest.js +346 -0
  60. package/core/services/security/index.js +2 -1
  61. package/core/services/security/rules/index.js +25 -59
  62. package/core/services/security/rules/scans/configPermissions.js +91 -0
  63. package/core/services/security/rules/scans/duplicateToolNames.js +85 -0
  64. package/core/services/security/rules/scans/insecureTransport.js +148 -0
  65. package/core/services/security/rules/scans/missingContainment.js +123 -0
  66. package/core/services/security/rules/scans/shellEnvInjection.js +101 -0
  67. package/core/services/security/rules/scans/unsafeDefaults.js +99 -0
  68. package/core/services/security/toolsListFromTrafficParser.js +70 -0
  69. package/core/tui/App.js +144 -0
  70. package/core/tui/FindingsPanel.js +115 -0
  71. package/core/tui/FixPanel.js +132 -0
  72. package/core/tui/Header.js +51 -0
  73. package/core/tui/HelpBar.js +42 -0
  74. package/core/tui/ServersPanel.js +109 -0
  75. package/core/tui/ToxicFlowsPanel.js +100 -0
  76. package/core/tui/h.js +8 -0
  77. package/core/tui/index.js +11 -0
  78. package/core/tui/render.js +22 -0
  79. package/package.json +24 -16
  80. package/ui/dist/assets/index-D6zDrtMV.js +81 -0
  81. package/ui/dist/index.html +1 -1
  82. package/ui/server/controllers/AauthController.js +279 -0
  83. package/ui/server/controllers/RequestController.js +12 -1
  84. package/ui/server/controllers/SecurityFindingsController.js +46 -1
  85. package/ui/server/routes/aauth.js +18 -0
  86. package/ui/server/routes/requests.js +8 -1
  87. package/ui/server/routes/security.js +5 -1
  88. package/ui/server/setup.js +224 -6
  89. package/ui/server/swagger/paths/components.js +55 -0
  90. package/ui/server/swagger/paths/securityTrafficFlows.js +59 -0
  91. package/ui/server/swagger/paths.js +2 -2
  92. package/ui/server/swagger/swagger.js +5 -2
  93. package/ui/server.js +1 -1
  94. package/ui/src/App.jsx +26 -52
  95. package/ui/src/PacketFilters.jsx +31 -1
  96. package/ui/src/PacketList.jsx +2 -2
  97. package/ui/src/Security.jsx +10 -0
  98. package/ui/src/TabNavigation.jsx +8 -0
  99. package/ui/src/components/AAuthBadge.jsx +92 -0
  100. package/ui/src/components/AauthExplorer/AauthExplorerGraph.jsx +231 -0
  101. package/ui/src/components/AauthExplorer/AauthExplorerView.jsx +387 -0
  102. package/ui/src/components/AauthExplorer/NodeDetailPanel.jsx +272 -0
  103. package/ui/src/components/App/ActionMenu.jsx +4 -31
  104. package/ui/src/components/App/ApiDocsButton.jsx +0 -1
  105. package/ui/src/components/App/ShutdownButton.jsx +0 -1
  106. package/ui/src/components/App/useAppState.js +19 -26
  107. package/ui/src/components/DetailsTab/AAuthIdentitySection.jsx +119 -0
  108. package/ui/src/components/DetailsTab/RequestDetailsSection.jsx +2 -0
  109. package/ui/src/components/DetailsTab/ResponseDetailsSection.jsx +2 -0
  110. package/ui/src/components/DetectedPathsList.jsx +1 -5
  111. package/ui/src/components/FileInput.jsx +0 -1
  112. package/ui/src/components/PacketFilters/AAuthPostureFilter.jsx +81 -0
  113. package/ui/src/components/RequestRow/RequestRowMain.jsx +7 -1
  114. package/ui/src/components/Security/AAuthPosturePanel.jsx +360 -0
  115. package/ui/src/components/Security/ScannerContent.jsx +33 -1
  116. package/ui/src/components/Security/TrafficToxicFlowsPanel.jsx +253 -0
  117. package/ui/src/components/Security/securityApi.js +15 -0
  118. package/ui/src/components/Security/useSecurity.js +60 -3
  119. package/ui/src/components/ServerControl.jsx +0 -1
  120. package/ui/src/components/TabNavigation/DesktopTabs.jsx +0 -11
  121. package/ui/src/components/TabNavigationIcons.jsx +5 -0
  122. package/ui/src/components/ViewModeTabs.jsx +0 -1
  123. package/ui/src/utils/animations.js +26 -9
  124. package/core/services/security/rules/scans/agentic01GoalHijack.js +0 -130
  125. package/core/services/security/rules/scans/agentic02ToolMisuse.js +0 -129
  126. package/core/services/security/rules/scans/agentic03IdentityAbuse.js +0 -130
  127. package/core/services/security/rules/scans/agentic04SupplyChain.js +0 -130
  128. package/core/services/security/rules/scans/agentic06MemoryPoisoning.js +0 -130
  129. package/core/services/security/rules/scans/agentic07InsecureCommunication.js +0 -135
  130. package/core/services/security/rules/scans/agentic08CascadingFailures.js +0 -135
  131. package/core/services/security/rules/scans/agentic09TrustExploitation.js +0 -135
  132. package/core/services/security/rules/scans/agentic10RogueAgent.js +0 -130
  133. package/core/services/security/rules/scans/hardcodedSecrets.js +0 -130
  134. package/core/services/security/rules/scans/mcp01TokenMismanagement.js +0 -127
  135. package/core/services/security/rules/scans/mcp02ScopeCreep.js +0 -130
  136. package/core/services/security/rules/scans/mcp03ToolPoisoning.js +0 -132
  137. package/core/services/security/rules/scans/mcp04SupplyChain.js +0 -131
  138. package/core/services/security/rules/scans/mcp06PromptInjection.js +0 -200
  139. package/core/services/security/rules/scans/mcp07InsufficientAuth.js +0 -130
  140. package/core/services/security/rules/scans/mcp08LackAudit.js +0 -129
  141. package/core/services/security/rules/scans/mcp09ShadowServers.js +0 -129
  142. package/core/services/security/rules/scans/mcp10ContextInjection.js +0 -130
  143. package/ui/dist/assets/index-CiCSDYf-.js +0 -97
  144. package/ui/server/routes/help.js +0 -44
  145. package/ui/server/swagger/paths/help.js +0 -82
  146. package/ui/src/HelpGuide/HelpGuideContent.jsx +0 -118
  147. package/ui/src/HelpGuide/HelpGuideFooter.jsx +0 -59
  148. package/ui/src/HelpGuide/HelpGuideHeader.jsx +0 -57
  149. package/ui/src/HelpGuide.jsx +0 -78
  150. package/ui/src/IntroTour.jsx +0 -154
  151. package/ui/src/components/App/HelpButton.jsx +0 -90
  152. package/ui/src/components/TourOverlay.jsx +0 -117
  153. package/ui/src/components/TourTooltip/TourTooltipButtons.jsx +0 -120
  154. package/ui/src/components/TourTooltip/TourTooltipHeader.jsx +0 -71
  155. package/ui/src/components/TourTooltip/TourTooltipIcons.jsx +0 -54
  156. package/ui/src/components/TourTooltip/useTooltipPosition.js +0 -135
  157. package/ui/src/components/TourTooltip.jsx +0 -91
  158. package/ui/src/config/tourSteps.jsx +0 -140
@@ -1,117 +0,0 @@
1
- function TourOverlay({ elementRect, _onClick }) {
2
- if (!elementRect) {
3
- return (
4
- <div
5
- style={{
6
- position: 'absolute',
7
- top: 0,
8
- left: 0,
9
- right: 0,
10
- bottom: 0,
11
- background: 'rgba(0, 0, 0, 0.8)',
12
- }}
13
- />
14
- );
15
- }
16
-
17
- const highlightPadding = 8;
18
- const highlightBorderWidth = 3;
19
-
20
- return (
21
- <>
22
- {elementRect.top > 0 && (
23
- <div
24
- style={{
25
- position: 'fixed',
26
- top: 0,
27
- left: 0,
28
- right: 0,
29
- height: `${elementRect.top - highlightPadding}px`,
30
- background: 'rgba(0, 0, 0, 0.8)',
31
- zIndex: 9999,
32
- }}
33
- />
34
- )}
35
- {elementRect.bottom < window.innerHeight && (
36
- <div
37
- style={{
38
- position: 'fixed',
39
- bottom: 0,
40
- left: 0,
41
- right: 0,
42
- height: `${window.innerHeight - elementRect.bottom - highlightPadding}px`,
43
- background: 'rgba(0, 0, 0, 0.8)',
44
- zIndex: 9999,
45
- }}
46
- />
47
- )}
48
- {elementRect.left > 0 && (
49
- <div
50
- style={{
51
- position: 'fixed',
52
- top: `${Math.max(0, elementRect.top - highlightPadding)}px`,
53
- left: 0,
54
- width: `${elementRect.left - highlightPadding}px`,
55
- height: `${elementRect.height + highlightPadding * 2}px`,
56
- background: 'rgba(0, 0, 0, 0.8)',
57
- zIndex: 9999,
58
- }}
59
- />
60
- )}
61
- {elementRect.right < window.innerWidth && (
62
- <div
63
- style={{
64
- position: 'fixed',
65
- top: `${Math.max(0, elementRect.top - highlightPadding)}px`,
66
- right: 0,
67
- width: `${window.innerWidth - elementRect.right - highlightPadding}px`,
68
- height: `${elementRect.height + highlightPadding * 2}px`,
69
- background: 'rgba(0, 0, 0, 0.8)',
70
- zIndex: 9999,
71
- }}
72
- />
73
- )}
74
- <div
75
- style={{
76
- position: 'fixed',
77
- left: elementRect.left - highlightPadding - highlightBorderWidth,
78
- top: elementRect.top - highlightPadding - highlightBorderWidth,
79
- width: elementRect.width + (highlightPadding + highlightBorderWidth) * 2,
80
- height: elementRect.height + (highlightPadding + highlightBorderWidth) * 2,
81
- border: `${highlightBorderWidth}px solid #4ec9b0`,
82
- borderRadius: '8px',
83
- boxShadow:
84
- '0 0 0 2px rgba(78, 201, 176, 0.3), ' +
85
- '0 0 20px rgba(78, 201, 176, 0.5), ' +
86
- '0 0 40px rgba(78, 201, 176, 0.3), ' +
87
- 'inset 0 0 20px rgba(78, 201, 176, 0.1)',
88
- zIndex: 10000,
89
- pointerEvents: 'none',
90
- animation: 'pulse 2s ease-in-out infinite',
91
- }}
92
- />
93
- <style>
94
- {`
95
- @keyframes pulse {
96
- 0%, 100% {
97
- box-shadow:
98
- 0 0 0 2px rgba(78, 201, 176, 0.3),
99
- 0 0 20px rgba(78, 201, 176, 0.5),
100
- 0 0 40px rgba(78, 201, 176, 0.3),
101
- inset 0 0 20px rgba(78, 201, 176, 0.1);
102
- }
103
- 50% {
104
- box-shadow:
105
- 0 0 0 3px rgba(78, 201, 176, 0.4),
106
- 0 0 30px rgba(78, 201, 176, 0.7),
107
- 0 0 60px rgba(78, 201, 176, 0.5),
108
- inset 0 0 30px rgba(78, 201, 176, 0.2);
109
- }
110
- }
111
- `}
112
- </style>
113
- </>
114
- );
115
- }
116
-
117
- export default TourOverlay;
@@ -1,120 +0,0 @@
1
- import { colors, fonts } from '../../theme';
2
- import { ChevronLeft, ChevronRight } from './TourTooltipIcons';
3
-
4
- export default function TourTooltipButtons({
5
- currentStep,
6
- totalSteps,
7
- onNext,
8
- onPrevious,
9
- onSkip,
10
- }) {
11
- return (
12
- <div
13
- style={{
14
- display: 'flex',
15
- alignItems: 'center',
16
- justifyContent: 'space-between',
17
- gap: '12px',
18
- pointerEvents: 'none',
19
- }}
20
- >
21
- <button
22
- type="button"
23
- onClick={(e) => {
24
- e.stopPropagation();
25
- onSkip();
26
- }}
27
- onMouseDown={(e) => e.stopPropagation()}
28
- style={{
29
- background: 'transparent',
30
- border: `1px solid ${colors.borderMedium}`,
31
- color: colors.textSecondary,
32
- padding: '8px 16px',
33
- borderRadius: '8px',
34
- cursor: 'pointer',
35
- fontSize: '13px',
36
- fontFamily: fonts.body,
37
- flex: 1,
38
- pointerEvents: 'auto',
39
- }}
40
- onMouseEnter={(e) => {
41
- e.currentTarget.style.background = colors.bgHover;
42
- e.currentTarget.style.color = colors.textPrimary;
43
- e.currentTarget.style.borderColor = colors.borderMedium;
44
- }}
45
- onMouseLeave={(e) => {
46
- e.currentTarget.style.background = 'transparent';
47
- e.currentTarget.style.color = colors.textSecondary;
48
- e.currentTarget.style.borderColor = colors.borderMedium;
49
- }}
50
- >
51
- Skip Tour
52
- </button>
53
- <div style={{ display: 'flex', gap: '8px', pointerEvents: 'auto' }}>
54
- {currentStep > 0 && (
55
- <button
56
- type="button"
57
- onClick={(e) => {
58
- e.stopPropagation();
59
- onPrevious();
60
- }}
61
- onMouseDown={(e) => e.stopPropagation()}
62
- style={{
63
- background: colors.buttonSecondary,
64
- border: `1px solid ${colors.borderMedium}`,
65
- color: colors.textPrimary,
66
- padding: '8px 12px',
67
- borderRadius: '8px',
68
- cursor: 'pointer',
69
- fontSize: '13px',
70
- fontFamily: fonts.body,
71
- display: 'flex',
72
- alignItems: 'center',
73
- gap: '4px',
74
- }}
75
- onMouseEnter={(e) => {
76
- e.currentTarget.style.background = colors.buttonSecondaryHover;
77
- }}
78
- onMouseLeave={(e) => {
79
- e.currentTarget.style.background = colors.buttonSecondary;
80
- }}
81
- >
82
- <ChevronLeft size={14} />
83
- Previous
84
- </button>
85
- )}
86
- <button
87
- type="button"
88
- onClick={(e) => {
89
- e.stopPropagation();
90
- onNext();
91
- }}
92
- onMouseDown={(e) => e.stopPropagation()}
93
- style={{
94
- background: colors.buttonPrimary,
95
- border: 'none',
96
- color: colors.textInverse,
97
- padding: '8px 16px',
98
- borderRadius: '8px',
99
- cursor: 'pointer',
100
- fontSize: '13px',
101
- fontFamily: fonts.body,
102
- display: 'flex',
103
- alignItems: 'center',
104
- gap: '4px',
105
- fontWeight: '500',
106
- }}
107
- onMouseEnter={(e) => {
108
- e.currentTarget.style.background = colors.buttonPrimaryHover;
109
- }}
110
- onMouseLeave={(e) => {
111
- e.currentTarget.style.background = colors.buttonPrimary;
112
- }}
113
- >
114
- {currentStep === totalSteps - 1 ? 'Finish' : 'Next'}
115
- {currentStep < totalSteps - 1 && <ChevronRight size={14} />}
116
- </button>
117
- </div>
118
- </div>
119
- );
120
- }
@@ -1,71 +0,0 @@
1
- import { colors, fonts } from '../../theme';
2
- import { CloseIcon } from './TourTooltipIcons';
3
-
4
- export default function TourTooltipHeader({ step, currentStep, totalSteps, isDragging, onSkip }) {
5
- return (
6
- <div
7
- style={{
8
- display: 'flex',
9
- alignItems: 'flex-start',
10
- justifyContent: 'space-between',
11
- marginBottom: '12px',
12
- }}
13
- >
14
- <div style={{ flex: 1, pointerEvents: 'none' }}>
15
- <h3
16
- style={{
17
- margin: 0,
18
- color: colors.accentBlue,
19
- fontSize: '16px',
20
- fontWeight: '600',
21
- fontFamily: fonts.body,
22
- }}
23
- >
24
- {step.title}
25
- </h3>
26
- <p
27
- style={{
28
- margin: '4px 0 0 0',
29
- color: colors.textTertiary,
30
- fontSize: '12px',
31
- fontFamily: fonts.body,
32
- }}
33
- >
34
- Step {currentStep + 1} of {totalSteps}
35
- {isDragging && <span style={{ marginLeft: '8px', fontSize: '11px' }}>• Dragging</span>}
36
- </p>
37
- </div>
38
- <button
39
- type="button"
40
- onClick={(e) => {
41
- e.stopPropagation();
42
- onSkip();
43
- }}
44
- onMouseDown={(e) => e.stopPropagation()}
45
- style={{
46
- background: 'transparent',
47
- border: 'none',
48
- color: colors.textTertiary,
49
- cursor: 'pointer',
50
- padding: '4px',
51
- display: 'flex',
52
- alignItems: 'center',
53
- borderRadius: '8px',
54
- marginLeft: '12px',
55
- pointerEvents: 'auto',
56
- }}
57
- onMouseEnter={(e) => {
58
- e.currentTarget.style.background = colors.bgHover;
59
- e.currentTarget.style.color = colors.textPrimary;
60
- }}
61
- onMouseLeave={(e) => {
62
- e.currentTarget.style.background = 'transparent';
63
- e.currentTarget.style.color = colors.textTertiary;
64
- }}
65
- title="Skip tour"
66
- >
67
- <CloseIcon size={18} />
68
- </button>
69
- </div>
70
- );
71
- }
@@ -1,54 +0,0 @@
1
- export const CloseIcon = ({ size = 16, color = 'currentColor' }) => (
2
- <svg
3
- width={size}
4
- height={size}
5
- viewBox="0 0 24 24"
6
- fill="none"
7
- stroke={color}
8
- strokeWidth="2"
9
- strokeLinecap="round"
10
- strokeLinejoin="round"
11
- role="img"
12
- aria-label="Close icon"
13
- >
14
- <title>Close icon</title>
15
- <line x1="18" y1="6" x2="6" y2="18" />
16
- <line x1="6" y1="6" x2="18" y2="18" />
17
- </svg>
18
- );
19
-
20
- export const ChevronRight = ({ size = 16, color = 'currentColor' }) => (
21
- <svg
22
- width={size}
23
- height={size}
24
- viewBox="0 0 24 24"
25
- fill="none"
26
- stroke={color}
27
- strokeWidth="2"
28
- strokeLinecap="round"
29
- strokeLinejoin="round"
30
- role="img"
31
- aria-label="Chevron right icon"
32
- >
33
- <title>Chevron right icon</title>
34
- <polyline points="9 18 15 12 9 6" />
35
- </svg>
36
- );
37
-
38
- export const ChevronLeft = ({ size = 16, color = 'currentColor' }) => (
39
- <svg
40
- width={size}
41
- height={size}
42
- viewBox="0 0 24 24"
43
- fill="none"
44
- stroke={color}
45
- strokeWidth="2"
46
- strokeLinecap="round"
47
- strokeLinejoin="round"
48
- role="img"
49
- aria-label="Chevron left icon"
50
- >
51
- <title>Chevron left icon</title>
52
- <polyline points="15 18 9 12 15 6" />
53
- </svg>
54
- );
@@ -1,135 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
-
3
- export function useTooltipPosition(elementRect, step, _currentStep) {
4
- const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
5
- const [isDragging, setIsDragging] = useState(false);
6
- const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
7
-
8
- useEffect(() => {
9
- setTooltipPosition({ x: 0, y: 0 });
10
- }, []);
11
-
12
- const handleMouseDown = (e, tooltipRef) => {
13
- e.preventDefault();
14
- e.stopPropagation();
15
- if (tooltipRef.current) {
16
- const rect = tooltipRef.current.getBoundingClientRect();
17
- setDragOffset({
18
- x: e.clientX - rect.left,
19
- y: e.clientY - rect.top,
20
- });
21
- setIsDragging(true);
22
- }
23
- };
24
-
25
- useEffect(() => {
26
- if (!isDragging) {
27
- return;
28
- }
29
-
30
- const handleMouseMove = (e) => {
31
- setTooltipPosition({
32
- x: e.clientX - dragOffset.x,
33
- y: e.clientY - dragOffset.y,
34
- });
35
- };
36
-
37
- const handleMouseUp = () => {
38
- setIsDragging(false);
39
- };
40
-
41
- window.addEventListener('mousemove', handleMouseMove);
42
- window.addEventListener('mouseup', handleMouseUp);
43
-
44
- return () => {
45
- window.removeEventListener('mousemove', handleMouseMove);
46
- window.removeEventListener('mouseup', handleMouseUp);
47
- };
48
- }, [isDragging, dragOffset]);
49
-
50
- const calculatePosition = () => {
51
- if (!elementRect) {
52
- return { left: 0, top: 0, transform: 'none' };
53
- }
54
-
55
- const tooltipWidth = 350;
56
- const tooltipHeight = 200;
57
- const spacing = 20;
58
-
59
- if (tooltipPosition.x !== 0 || tooltipPosition.y !== 0) {
60
- const left = Math.max(10, Math.min(tooltipPosition.x, window.innerWidth - tooltipWidth - 10));
61
- const top = Math.max(
62
- 10,
63
- Math.min(tooltipPosition.y, window.innerHeight - tooltipHeight - 10)
64
- );
65
- return { left, top, transform: 'none' };
66
- }
67
-
68
- const position = step.position || 'bottom';
69
-
70
- const calculatePosition = (position, elementRect, tooltipWidth, tooltipHeight, spacing) => {
71
- if (position === 'left') {
72
- const baseLeft = elementRect.left - tooltipWidth - spacing;
73
- const left = baseLeft < 10 ? elementRect.right + spacing : baseLeft;
74
- return {
75
- left,
76
- top: elementRect.top + elementRect.height / 2,
77
- transform: 'translateY(-50%)',
78
- };
79
- }
80
-
81
- if (position === 'right') {
82
- const baseLeft = elementRect.right + spacing;
83
- const left =
84
- baseLeft + tooltipWidth > window.innerWidth - 10
85
- ? elementRect.left - tooltipWidth - spacing
86
- : baseLeft;
87
- return {
88
- left,
89
- top: elementRect.top + elementRect.height / 2,
90
- transform: 'translateY(-50%)',
91
- };
92
- }
93
-
94
- if (position === 'top') {
95
- const baseTop = elementRect.top - tooltipHeight - spacing;
96
- const top = baseTop < 10 ? elementRect.bottom + spacing : baseTop;
97
- return {
98
- left: elementRect.left + elementRect.width / 2,
99
- top,
100
- transform: 'translate(-50%, 0)',
101
- };
102
- }
103
-
104
- // bottom (default)
105
- const baseTop = elementRect.bottom + spacing;
106
- const top =
107
- baseTop + tooltipHeight > window.innerHeight - 10
108
- ? elementRect.top - tooltipHeight - spacing
109
- : baseTop;
110
- return {
111
- left: elementRect.left + elementRect.width / 2,
112
- top,
113
- transform: 'translate(-50%, 0)',
114
- };
115
- };
116
-
117
- const rawPosition = calculatePosition(
118
- position,
119
- elementRect,
120
- tooltipWidth,
121
- tooltipHeight,
122
- spacing
123
- );
124
- const left = Math.max(10, Math.min(rawPosition.left, window.innerWidth - tooltipWidth - 10));
125
- const top = Math.max(10, Math.min(rawPosition.top, window.innerHeight - tooltipHeight - 10));
126
-
127
- return { left, top, transform: rawPosition.transform };
128
- };
129
-
130
- return {
131
- position: calculatePosition(),
132
- isDragging,
133
- handleMouseDown,
134
- };
135
- }
@@ -1,91 +0,0 @@
1
- import { useRef } from 'react';
2
- import { colors, fonts } from '../theme';
3
- import TourTooltipButtons from './TourTooltip/TourTooltipButtons';
4
- import TourTooltipHeader from './TourTooltip/TourTooltipHeader';
5
- import { useTooltipPosition } from './TourTooltip/useTooltipPosition';
6
-
7
- function TourTooltip({ elementRect, step, currentStep, totalSteps, onNext, onPrevious, onSkip }) {
8
- const tooltipRef = useRef(null);
9
- const { position, isDragging, handleMouseDown } = useTooltipPosition(
10
- elementRect,
11
- step,
12
- currentStep
13
- );
14
-
15
- if (!elementRect) {
16
- return null;
17
- }
18
-
19
- return (
20
- <button
21
- type="button"
22
- ref={tooltipRef}
23
- onMouseDown={(e) => handleMouseDown(e, tooltipRef)}
24
- onKeyDown={(e) => {
25
- if (e.key === 'Enter' || e.key === ' ') {
26
- e.preventDefault();
27
- handleMouseDown(e, tooltipRef);
28
- }
29
- }}
30
- aria-label="Draggable tooltip"
31
- style={{
32
- position: 'fixed',
33
- left: `${position.left}px`,
34
- top: `${position.top}px`,
35
- transform: position.transform,
36
- background: colors.bgCard,
37
- border: `2px solid ${colors.accentBlue}`,
38
- borderRadius: '12px',
39
- padding: '20px',
40
- maxWidth: '350px',
41
- minWidth: '300px',
42
- boxShadow: `0 4px 20px ${colors.shadowLg}`,
43
- zIndex: 10001,
44
- pointerEvents: 'auto',
45
- cursor: isDragging ? 'grabbing' : 'grab',
46
- userSelect: 'none',
47
- }}
48
- onClick={(e) => {
49
- const target = e.target;
50
- if (
51
- target.tagName !== 'BUTTON' &&
52
- target.tagName !== 'INPUT' &&
53
- !target.closest('button')
54
- ) {
55
- e.stopPropagation();
56
- }
57
- }}
58
- >
59
- <TourTooltipHeader
60
- step={step}
61
- currentStep={currentStep}
62
- totalSteps={totalSteps}
63
- isDragging={isDragging}
64
- onSkip={onSkip}
65
- />
66
-
67
- <div
68
- style={{
69
- color: colors.textPrimary,
70
- fontSize: '14px',
71
- lineHeight: '1.6',
72
- marginBottom: '20px',
73
- pointerEvents: 'none',
74
- fontFamily: fonts.body,
75
- }}
76
- >
77
- {step.content}
78
- </div>
79
-
80
- <TourTooltipButtons
81
- currentStep={currentStep}
82
- totalSteps={totalSteps}
83
- onNext={onNext}
84
- onPrevious={onPrevious}
85
- onSkip={onSkip}
86
- />
87
- </button>
88
- );
89
- }
90
-
91
- export default TourTooltip;