@tomaszjarosz/react-visualizers 0.2.13 → 0.2.14

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/README.md CHANGED
@@ -1,9 +1,21 @@
1
1
  # @tomaszjarosz/react-visualizers
2
2
 
3
- Interactive algorithm and data structure visualizers for React.
3
+ Interactive algorithm and data structure visualizers for React. Perfect for learning, teaching, and interview preparation.
4
4
 
5
- [![Storybook](https://img.shields.io/badge/Storybook-Live%20Demo-ff4785?logo=storybook&logoColor=white)](https://6934a2d9e17d1e509a92c935-rdicbxowdr.chromatic.com/)
6
5
  [![npm version](https://img.shields.io/npm/v/@tomaszjarosz/react-visualizers)](https://www.npmjs.com/package/@tomaszjarosz/react-visualizers)
6
+ [![Storybook](https://img.shields.io/badge/Storybook-Live%20Demo-ff4785?logo=storybook&logoColor=white)](https://6934a2d9e17d1e509a92c935-rdicbxowdr.chromatic.com/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## Features
10
+
11
+ - **36 interactive visualizers** covering algorithms, data structures, and distributed systems
12
+ - **Interview Mode** with built-in questions, scoring, hints, and explanations
13
+ - **Step-by-step animations** with playback controls (play, pause, step forward/back)
14
+ - **Code highlighting** showing the current line being executed
15
+ - **Keyboard shortcuts** for efficient navigation
16
+ - **URL state persistence** for sharing specific states
17
+ - **Fully typed** with TypeScript
18
+ - **Zero runtime dependencies** (only `lucide-react` for icons)
7
19
 
8
20
  ## Live Demo
9
21
 
@@ -13,78 +25,157 @@ Interactive algorithm and data structure visualizers for React.
13
25
 
14
26
  ```bash
15
27
  npm install @tomaszjarosz/react-visualizers
28
+ # or
29
+ pnpm add @tomaszjarosz/react-visualizers
30
+ # or
31
+ yarn add @tomaszjarosz/react-visualizers
32
+ ```
33
+
34
+ **Important:** Import the CSS file in your application entry point:
35
+
36
+ ```tsx
37
+ import '@tomaszjarosz/react-visualizers/styles.css';
16
38
  ```
17
39
 
18
40
  ## Requirements
19
41
 
20
42
  - React 17+
21
- - Tailwind CSS 4+
43
+ - The library includes compiled Tailwind CSS, no additional setup needed
44
+
45
+ ## Quick Start
46
+
47
+ ```tsx
48
+ import { HashMapVisualizer } from '@tomaszjarosz/react-visualizers';
49
+ import '@tomaszjarosz/react-visualizers/styles.css';
50
+
51
+ function App() {
52
+ return <HashMapVisualizer showControls showCode />;
53
+ }
54
+ ```
22
55
 
23
56
  ## Available Visualizers
24
57
 
25
- ### Algorithms
58
+ ### Algorithms (6)
59
+
26
60
  | Visualizer | Description |
27
61
  |------------|-------------|
28
62
  | `BinarySearchVisualizer` | Binary search with O(log n) visualization |
29
- | `SortingVisualizer` | Step-by-step sorting algorithm |
30
- | `SortingComparisonVisualizer` | Side-by-side algorithm comparison |
31
- | `GraphVisualizer` | DFS/BFS graph traversal |
32
- | `DijkstraVisualizer` | Shortest path algorithm |
33
- | `DPVisualizer` | Dynamic programming table |
63
+ | `SortingVisualizer` | Bubble, Selection, Insertion, Quick, Merge sort |
64
+ | `SortingComparisonVisualizer` | Side-by-side algorithm race |
65
+ | `GraphVisualizer` | DFS and BFS traversal |
66
+ | `DijkstraVisualizer` | Shortest path with distance relaxation |
67
+ | `DPVisualizer` | Dynamic programming table (Fibonacci) |
68
+
69
+ ### Data Structures (10)
34
70
 
35
- ### Data Structures
36
71
  | Visualizer | Description |
37
72
  |------------|-------------|
38
- | `ArrayListVisualizer` | Dynamic array with resizing |
39
- | `LinkedListVisualizer` | Node-based list operations |
40
- | `HashMapVisualizer` | Hash table with buckets |
41
- | `HashTableVisualizer` | Hash function internals |
42
- | `ArrayDequeVisualizer` | Circular buffer deque |
43
- | `PriorityQueueVisualizer` | Min-heap operations |
44
- | `TreeSetVisualizer` | Red-Black tree (BST) |
45
- | `LinkedHashMapVisualizer` | Hash + insertion order |
46
- | `EnumSetVisualizer` | Bit vector set |
47
-
48
- ### Concurrency
73
+ | `ArrayListVisualizer` | Dynamic array with amortized resizing |
74
+ | `LinkedListVisualizer` | Singly linked list operations |
75
+ | `HashMapVisualizer` | Hash table with separate chaining |
76
+ | `HashTableVisualizer` | Hash function internals and collisions |
77
+ | `ArrayDequeVisualizer` | Circular buffer double-ended queue |
78
+ | `PriorityQueueVisualizer` | Binary min-heap operations |
79
+ | `TreeSetVisualizer` | Red-Black tree (balanced BST) |
80
+ | `LinkedHashMapVisualizer` | HashMap + insertion order linked list |
81
+ | `EnumSetVisualizer` | Bit vector implementation |
82
+ | `ListComparisonVisualizer` | ArrayList vs LinkedList performance |
83
+
84
+ ### Concurrency (4)
85
+
49
86
  | Visualizer | Description |
50
87
  |------------|-------------|
51
- | `BlockingQueueVisualizer` | Producer-consumer pattern |
52
- | `ConcurrentHashMapVisualizer` | Segment-based locking |
53
- | `CopyOnWriteVisualizer` | Copy-on-write pattern |
88
+ | `BlockingQueueVisualizer` | Producer-consumer with blocking |
89
+ | `ConcurrentHashMapVisualizer` | Segment-based concurrent access |
90
+ | `CopyOnWriteVisualizer` | Copy-on-write pattern for reads |
54
91
  | `ImmutableCollectionsVisualizer` | Java 9+ immutable collections |
55
92
 
56
- ### Other
93
+ ### Advanced Data Structures (2)
94
+
95
+ | Visualizer | Description |
96
+ |------------|-------------|
97
+ | `BloomFilterVisualizer` | Probabilistic set membership |
98
+ | `BTreeVisualizer` | B-Tree with node splitting |
99
+
100
+ ### Distributed Systems (2)
101
+
57
102
  | Visualizer | Description |
58
103
  |------------|-------------|
59
- | `GCVisualizer` | JVM garbage collection |
60
- | `SQLJoinVisualizer` | SQL JOIN operations |
104
+ | `ConsistentHashingVisualizer` | Hash ring with virtual nodes |
105
+ | `RaftVisualizer` | Raft consensus algorithm |
61
106
 
62
- ## Usage
107
+ ### Other (2)
108
+
109
+ | Visualizer | Description |
110
+ |------------|-------------|
111
+ | `GCVisualizer` | JVM generational garbage collection |
112
+ | `SQLJoinVisualizer` | SQL JOIN operations (INNER, LEFT, RIGHT, FULL) |
113
+
114
+ ### Interview Mode (10)
115
+
116
+ Interview visualizers include built-in questions with multiple choice answers, hints, and detailed explanations. Perfect for interview preparation.
117
+
118
+ | Visualizer | Topics |
119
+ |------------|--------|
120
+ | `HashMapInterviewVisualizer` | Hashing, collisions, load factor, rehashing |
121
+ | `TreeSetInterviewVisualizer` | BST properties, Red-Black balancing, rotations |
122
+ | `SortingInterviewVisualizer` | Time complexity, stability, space complexity |
123
+ | `GraphInterviewVisualizer` | DFS vs BFS, cycle detection, topological sort |
124
+ | `DijkstraInterviewVisualizer` | Shortest path, negative weights, relaxation |
125
+ | `DPInterviewVisualizer` | Overlapping subproblems, memoization |
126
+ | `BloomFilterInterviewVisualizer` | False positives, hash functions, bit arrays |
127
+ | `BTreeInterviewVisualizer` | Node splitting, disk-based storage, order |
128
+ | `ConsistentHashingInterviewVisualizer` | Virtual nodes, rebalancing, hot spots |
129
+ | `RaftInterviewVisualizer` | Leader election, log replication, safety |
130
+
131
+ ## Interview Mode Usage
63
132
 
64
133
  ```tsx
65
- import { BinarySearchVisualizer, HashMapVisualizer } from '@tomaszjarosz/react-visualizers';
134
+ import { HashMapInterviewVisualizer } from '@tomaszjarosz/react-visualizers';
66
135
 
67
- function App() {
136
+ function InterviewPrep() {
68
137
  return (
69
- <div>
70
- <BinarySearchVisualizer showControls={true} showCode={true} />
71
- <HashMapVisualizer showControls={true} showCode={true} />
72
- </div>
138
+ <HashMapInterviewVisualizer
139
+ showControls
140
+ showCode
141
+ onComplete={(session) => {
142
+ console.log(`Score: ${session.results.filter(r => r.isCorrect).length}/${session.results.length}`);
143
+ }}
144
+ />
73
145
  );
74
146
  }
75
147
  ```
76
148
 
77
- ### Props
149
+ ## Props
78
150
 
79
- All visualizers accept these props:
151
+ All visualizers accept these common props:
80
152
 
81
153
  | Prop | Type | Default | Description |
82
154
  |------|------|---------|-------------|
83
155
  | `showControls` | `boolean` | `true` | Show playback controls |
84
- | `showCode` | `boolean` | `true` | Show code panel |
156
+ | `showCode` | `boolean` | `true` | Show code panel with highlighting |
85
157
  | `className` | `string` | `''` | Additional CSS classes |
86
158
 
87
- ## Shared Components
159
+ Interview visualizers also accept:
160
+
161
+ | Prop | Type | Default | Description |
162
+ |------|------|---------|-------------|
163
+ | `shuffleQuestions` | `boolean` | `false` | Randomize question order |
164
+ | `onComplete` | `(session) => void` | - | Callback when all questions answered |
165
+
166
+ ## Keyboard Shortcuts
167
+
168
+ | Key | Action |
169
+ |-----|--------|
170
+ | `Space` | Play/Pause |
171
+ | `→` | Next step |
172
+ | `←` | Previous step |
173
+ | `R` | Reset |
174
+ | `?` | Show help |
175
+
176
+ ## Building Custom Visualizers
177
+
178
+ The library exports shared components and hooks for building your own visualizers:
88
179
 
89
180
  ```tsx
90
181
  import {
@@ -93,13 +184,83 @@ import {
93
184
  Legend,
94
185
  StatusPanel,
95
186
  VisualizationArea,
96
- HelpPanel
187
+ useVisualizerPlayback,
188
+ useInterviewMode,
189
+ } from '@tomaszjarosz/react-visualizers';
190
+
191
+ function CustomVisualizer() {
192
+ const { currentStep, isPlaying, play, pause, next, prev, reset } = useVisualizerPlayback({
193
+ totalSteps: 10,
194
+ intervalMs: 500,
195
+ });
196
+
197
+ return (
198
+ <div>
199
+ <VisualizationArea>
200
+ {/* Your visualization */}
201
+ </VisualizationArea>
202
+ <ControlPanel
203
+ isPlaying={isPlaying}
204
+ onPlay={play}
205
+ onPause={pause}
206
+ onNext={next}
207
+ onPrev={prev}
208
+ onReset={reset}
209
+ currentStep={currentStep}
210
+ totalSteps={10}
211
+ />
212
+ </div>
213
+ );
214
+ }
215
+ ```
216
+
217
+ ### Available Hooks
218
+
219
+ | Hook | Description |
220
+ |------|-------------|
221
+ | `useVisualizerPlayback` | Step-through animation control with play/pause |
222
+ | `useUrlState` | Persist visualizer state in URL for sharing |
223
+ | `useInterviewMode` | Interview session with questions and scoring |
224
+
225
+ ### Available Components
226
+
227
+ | Component | Description |
228
+ |-----------|-------------|
229
+ | `ControlPanel` | Play, pause, step, reset buttons |
230
+ | `CodePanel` | Syntax-highlighted code with line highlighting |
231
+ | `Legend` | Color legend for visualization |
232
+ | `StatusPanel` | Current step description |
233
+ | `VisualizationArea` | Container with consistent styling |
234
+ | `HelpPanel` | Keyboard shortcuts overlay |
235
+ | `ArrayInput` | Custom array input for sorting visualizers |
236
+ | `StepHistory` | List of executed steps |
237
+ | `InterviewModePanel` | Question display with options and scoring |
238
+
239
+ ## TypeScript
240
+
241
+ The library is fully typed. All types are exported:
242
+
243
+ ```tsx
244
+ import type {
245
+ ControlPanelProps,
246
+ LegendItem,
247
+ InterviewQuestion,
248
+ InterviewSession,
249
+ UseVisualizerPlaybackOptions,
250
+ UseInterviewModeReturn,
97
251
  } from '@tomaszjarosz/react-visualizers';
98
252
  ```
99
253
 
100
254
  ## Development
101
255
 
102
256
  ```bash
257
+ # Clone the repository
258
+ git clone https://github.com/tomaszjarosz/tomaszjarosz-ui.git
259
+ cd tomaszjarosz-ui/packages/react-visualizers
260
+
261
+ # Install dependencies
262
+ pnpm install
263
+
103
264
  # Run Storybook locally
104
265
  pnpm run storybook
105
266
 
@@ -110,6 +271,14 @@ pnpm run build
110
271
  pnpm run typecheck
111
272
  ```
112
273
 
274
+ ## Contributing
275
+
276
+ Contributions are welcome! Please read the contributing guidelines before submitting a PR.
277
+
113
278
  ## License
114
279
 
115
- MIT
280
+ MIT License - see [LICENSE](LICENSE) for details.
281
+
282
+ ## Author
283
+
284
+ **Tomasz Jarosz** - [tomaszjarosz.dev](https://tomaszjarosz.dev)
package/dist/index.cjs CHANGED
@@ -1637,11 +1637,46 @@ const ALGORITHM_NAMES$1 = {
1637
1637
  merge: "MergeSort"
1638
1638
  };
1639
1639
  const ALGORITHM_COMPLEXITIES$1 = {
1640
- bubble: { time: "O(n²)", space: "O(1)" },
1641
- selection: { time: "O(n²)", space: "O(1)" },
1642
- insertion: { time: "O(n²)", space: "O(1)" },
1643
- quick: { time: "O(n log n)", space: "O(log n)" },
1644
- merge: { time: "O(n log n)", space: "O(n)" }
1640
+ bubble: {
1641
+ time: "O(n²)",
1642
+ best: "O(n)",
1643
+ average: "O(n²)",
1644
+ worst: "O(n²)",
1645
+ space: "O(1)",
1646
+ stable: true
1647
+ },
1648
+ selection: {
1649
+ time: "O(n²)",
1650
+ best: "O(n²)",
1651
+ average: "O(n²)",
1652
+ worst: "O(n²)",
1653
+ space: "O(1)",
1654
+ stable: false
1655
+ },
1656
+ insertion: {
1657
+ time: "O(n²)",
1658
+ best: "O(n)",
1659
+ average: "O(n²)",
1660
+ worst: "O(n²)",
1661
+ space: "O(1)",
1662
+ stable: true
1663
+ },
1664
+ quick: {
1665
+ time: "O(n log n)",
1666
+ best: "O(n log n)",
1667
+ average: "O(n log n)",
1668
+ worst: "O(n²)",
1669
+ space: "O(log n)",
1670
+ stable: false
1671
+ },
1672
+ merge: {
1673
+ time: "O(n log n)",
1674
+ best: "O(n log n)",
1675
+ average: "O(n log n)",
1676
+ worst: "O(n log n)",
1677
+ space: "O(n)",
1678
+ stable: true
1679
+ }
1645
1680
  };
1646
1681
  const ALGORITHM_CODE$1 = {
1647
1682
  bubble: [
@@ -2642,6 +2677,7 @@ const SortingVisualizerComponent = ({
2642
2677
  const [customArray, setCustomArray] = React.useState(null);
2643
2678
  const [historyCollapsed, setHistoryCollapsed] = React.useState(true);
2644
2679
  const [urlStateLoaded, setUrlStateLoaded] = React.useState(false);
2680
+ const [showComplexityTable, setShowComplexityTable] = React.useState(false);
2645
2681
  const playingRef = React.useRef(false);
2646
2682
  const timeoutRef = React.useRef(null);
2647
2683
  const VISUALIZER_ID = "sorting-visualizer";
@@ -2946,6 +2982,63 @@ const SortingVisualizerComponent = ({
2946
2982
  /* @__PURE__ */ jsxRuntime.jsx(HelpPanel, {})
2947
2983
  ] })
2948
2984
  ] }) }),
2985
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pb-2", children: [
2986
+ /* @__PURE__ */ jsxRuntime.jsxs(
2987
+ "button",
2988
+ {
2989
+ onClick: () => setShowComplexityTable(!showComplexityTable),
2990
+ className: "flex items-center gap-2 text-sm text-indigo-600 hover:text-indigo-800 font-medium transition-colors",
2991
+ children: [
2992
+ /* @__PURE__ */ jsxRuntime.jsx(
2993
+ "svg",
2994
+ {
2995
+ className: `w-4 h-4 transition-transform ${showComplexityTable ? "rotate-90" : ""}`,
2996
+ fill: "none",
2997
+ viewBox: "0 0 24 24",
2998
+ stroke: "currentColor",
2999
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" })
3000
+ }
3001
+ ),
3002
+ "Algorithm Complexity Comparison"
3003
+ ]
3004
+ }
3005
+ ),
3006
+ showComplexityTable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 p-4 bg-gradient-to-r from-indigo-50 to-blue-50 rounded-xl border border-indigo-200", children: [
3007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-sm", children: [
3008
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-left text-indigo-900", children: [
3009
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "pb-2 pr-4 font-semibold", children: "Algorithm" }),
3010
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "pb-2 px-3 font-semibold text-center", children: "Best" }),
3011
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "pb-2 px-3 font-semibold text-center", children: "Average" }),
3012
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "pb-2 px-3 font-semibold text-center", children: "Worst" }),
3013
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "pb-2 px-3 font-semibold text-center", children: "Space" }),
3014
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "pb-2 pl-3 font-semibold text-center", children: "Stable" })
3015
+ ] }) }),
3016
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "text-gray-700", children: Object.keys(ALGORITHM_NAMES$1).map((alg) => {
3017
+ const comp = ALGORITHM_COMPLEXITIES$1[alg];
3018
+ const isCurrentAlgorithm = alg === algorithm;
3019
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3020
+ "tr",
3021
+ {
3022
+ className: `border-t border-indigo-100 ${isCurrentAlgorithm ? "bg-indigo-100/50 font-medium" : ""}`,
3023
+ children: [
3024
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 pr-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: isCurrentAlgorithm ? "text-indigo-700" : "", children: ALGORITHM_NAMES$1[alg] }) }),
3025
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2 py-0.5 rounded text-xs ${comp.best.includes("log") ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"}`, children: comp.best }) }),
3026
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2 py-0.5 rounded text-xs ${comp.average.includes("log") ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"}`, children: comp.average }) }),
3027
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2 py-0.5 rounded text-xs ${comp.worst.includes("log") ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"}`, children: comp.worst }) }),
3028
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 px-3 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: `px-2 py-0.5 rounded text-xs ${comp.space === "O(1)" ? "bg-green-100 text-green-700" : "bg-purple-100 text-purple-700"}`, children: comp.space }) }),
3029
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "py-2 pl-3 text-center", children: comp.stable ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-green-600", children: "✓" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-400", children: "✗" }) })
3030
+ ]
3031
+ },
3032
+ alg
3033
+ );
3034
+ }) })
3035
+ ] }) }),
3036
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-3 text-xs text-indigo-600", children: [
3037
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Stable:" }),
3038
+ " A sorting algorithm is stable if elements with equal keys maintain their relative order."
3039
+ ] })
3040
+ ] })
3041
+ ] }),
2949
3042
  showControls && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3 bg-gray-50 border-t border-gray-200", children: [
2950
3043
  /* @__PURE__ */ jsxRuntime.jsx(
2951
3044
  ControlPanel,
@@ -17460,15 +17553,75 @@ const SQLJoinVisualizerComponent = ({
17460
17553
  }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[10px] text-gray-400 text-center py-2", children: "No results yet" })
17461
17554
  ] })
17462
17555
  ] }),
17463
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 p-3 bg-blue-50 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-blue-700", children: [
17464
- /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
17465
- joinType.toUpperCase(),
17466
- " JOIN:"
17556
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 p-3 bg-gradient-to-r from-cyan-50 to-blue-50 rounded-lg border border-cyan-200", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
17557
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 100 60", className: "w-24 h-14 flex-shrink-0", children: [
17558
+ /* @__PURE__ */ jsxRuntime.jsx(
17559
+ "circle",
17560
+ {
17561
+ cx: "35",
17562
+ cy: "30",
17563
+ r: "22",
17564
+ fill: joinType === "left" || joinType === "full" ? "#06b6d4" : joinType === "inner" ? "transparent" : "#e5e7eb",
17565
+ fillOpacity: joinType === "left" || joinType === "full" ? 0.6 : joinType === "inner" ? 0 : 0.5,
17566
+ stroke: "#0891b2",
17567
+ strokeWidth: "2"
17568
+ }
17569
+ ),
17570
+ /* @__PURE__ */ jsxRuntime.jsx(
17571
+ "circle",
17572
+ {
17573
+ cx: "65",
17574
+ cy: "30",
17575
+ r: "22",
17576
+ fill: joinType === "right" || joinType === "full" ? "#06b6d4" : joinType === "inner" ? "transparent" : "#e5e7eb",
17577
+ fillOpacity: joinType === "right" || joinType === "full" ? 0.6 : joinType === "inner" ? 0 : 0.5,
17578
+ stroke: "#0891b2",
17579
+ strokeWidth: "2"
17580
+ }
17581
+ ),
17582
+ /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: "leftClip", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "35", cy: "30", r: "22" }) }),
17583
+ /* @__PURE__ */ jsxRuntime.jsx(
17584
+ "circle",
17585
+ {
17586
+ cx: "65",
17587
+ cy: "30",
17588
+ r: "22",
17589
+ fill: "#22c55e",
17590
+ fillOpacity: "0.7",
17591
+ clipPath: "url(#leftClip)"
17592
+ }
17593
+ ),
17594
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: "22", y: "33", fontSize: "8", fill: "#0e7490", fontWeight: "bold", children: "L" }),
17595
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: "74", y: "33", fontSize: "8", fill: "#0e7490", fontWeight: "bold", children: "R" })
17467
17596
  ] }),
17468
- joinType === "inner" && " Returns only rows with matches in BOTH tables.",
17469
- joinType === "left" && " Returns ALL rows from left + matching rows from right (NULL if no match).",
17470
- joinType === "right" && " Returns ALL rows from right + matching rows from left (NULL if no match).",
17471
- joinType === "full" && " Returns ALL rows from BOTH tables (NULL where no match exists)."
17597
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
17598
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm font-semibold text-cyan-800 mb-1", children: [
17599
+ joinType.toUpperCase(),
17600
+ " JOIN"
17601
+ ] }),
17602
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-cyan-700", children: [
17603
+ joinType === "inner" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17604
+ "Returns only rows with matches in ",
17605
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "both" }),
17606
+ " tables (green intersection)."
17607
+ ] }),
17608
+ joinType === "left" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17609
+ "Returns ",
17610
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "all" }),
17611
+ " rows from left table + matching rows from right. NULL if no match."
17612
+ ] }),
17613
+ joinType === "right" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17614
+ "Returns ",
17615
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "all" }),
17616
+ " rows from right table + matching rows from left. NULL if no match."
17617
+ ] }),
17618
+ joinType === "full" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
17619
+ "Returns ",
17620
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "all" }),
17621
+ " rows from both tables. NULL where no match exists."
17622
+ ] })
17623
+ ] })
17624
+ ] })
17472
17625
  ] }) }),
17473
17626
  /* @__PURE__ */ jsxRuntime.jsx(
17474
17627
  StatusPanel,