@hivemindhq/core 0.4.0 → 0.5.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.
Files changed (85) hide show
  1. package/README.md +16 -16
  2. package/dist/{chunk-2RGM3KJL.js → chunk-K2544PJ5.js} +42 -20
  3. package/dist/chunk-K2544PJ5.js.map +1 -0
  4. package/dist/{chunk-P5E2XNDI.js → chunk-K4XDMY2V.js} +3 -3
  5. package/dist/{chunk-P5E2XNDI.js.map → chunk-K4XDMY2V.js.map} +1 -1
  6. package/dist/{chunk-ERZSVDIB.js → chunk-RW4JXOAM.js} +11 -3
  7. package/dist/chunk-RW4JXOAM.js.map +1 -0
  8. package/dist/chunk-VU3OPG32.js +907 -0
  9. package/dist/chunk-VU3OPG32.js.map +1 -0
  10. package/dist/components/index.d.ts +28 -3
  11. package/dist/components/index.js +2 -2
  12. package/dist/components/ui/index.js +2 -2
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +6 -6
  15. package/dist/utils/index.d.ts +312 -6
  16. package/dist/utils/index.js +2 -2
  17. package/package.json +15 -11
  18. package/src/components/AtomIcon.tsx +21 -0
  19. package/src/components/CryptoAmount.tsx +447 -0
  20. package/src/components/ErrorBanner.tsx +35 -0
  21. package/src/components/IpfsImage.tsx +21 -0
  22. package/src/components/LoadingDots.tsx +55 -0
  23. package/src/components/TripleAreaChart.tsx +108 -0
  24. package/src/components/TriplePositionsTornadoMinGraph.tsx +71 -0
  25. package/src/components/UnknownImage.tsx +55 -0
  26. package/src/components/index.ts +24 -0
  27. package/src/components/ui/alert.tsx +59 -0
  28. package/src/components/ui/avatar.tsx +47 -0
  29. package/src/components/ui/badge.tsx +35 -0
  30. package/src/components/ui/breadcrumb.tsx +108 -0
  31. package/src/components/ui/button.tsx +56 -0
  32. package/src/components/ui/card.tsx +75 -0
  33. package/src/components/ui/carousel.tsx +239 -0
  34. package/src/components/ui/chart.tsx +350 -0
  35. package/src/components/ui/checkbox.tsx +28 -0
  36. package/src/components/ui/collapsible.tsx +10 -0
  37. package/src/components/ui/command.tsx +177 -0
  38. package/src/components/ui/dialog.tsx +119 -0
  39. package/src/components/ui/dropdown-menu.tsx +202 -0
  40. package/src/components/ui/form.tsx +175 -0
  41. package/src/components/ui/index.ts +183 -0
  42. package/src/components/ui/input.tsx +21 -0
  43. package/src/components/ui/label.tsx +25 -0
  44. package/src/components/ui/loader.tsx +20 -0
  45. package/src/components/ui/pagination.tsx +104 -0
  46. package/src/components/ui/popover.tsx +45 -0
  47. package/src/components/ui/progress.tsx +25 -0
  48. package/src/components/ui/radio-group.tsx +42 -0
  49. package/src/components/ui/scroll-area.tsx +45 -0
  50. package/src/components/ui/select.tsx +178 -0
  51. package/src/components/ui/separator.tsx +28 -0
  52. package/src/components/ui/sheet.tsx +139 -0
  53. package/src/components/ui/sidebar.tsx +723 -0
  54. package/src/components/ui/skeleton.tsx +15 -0
  55. package/src/components/ui/sonner.tsx +27 -0
  56. package/src/components/ui/spinner.tsx +67 -0
  57. package/src/components/ui/switch.tsx +26 -0
  58. package/src/components/ui/table.tsx +113 -0
  59. package/src/components/ui/tabs.tsx +63 -0
  60. package/src/components/ui/textarea.tsx +21 -0
  61. package/src/components/ui/toast.tsx +146 -0
  62. package/src/components/ui/toaster.tsx +33 -0
  63. package/src/components/ui/toggle-group.tsx +58 -0
  64. package/src/components/ui/toggle.tsx +44 -0
  65. package/src/components/ui/tooltip.tsx +61 -0
  66. package/src/hooks/index.ts +7 -0
  67. package/src/hooks/use-mobile.ts +20 -0
  68. package/src/hooks/use-toast.ts +190 -0
  69. package/src/index.ts +25 -0
  70. package/src/types/index.ts +17 -0
  71. package/src/utils/atom-label-detection.ts +689 -0
  72. package/src/utils/atom.ts +279 -0
  73. package/src/utils/cn.ts +18 -0
  74. package/src/utils/formatting.ts +624 -0
  75. package/src/utils/index.ts +11 -0
  76. package/src/utils/multivault-errors.ts +581 -0
  77. package/src/utils/search/formatting.tsx +95 -0
  78. package/src/utils/search/index.ts +28 -0
  79. package/src/utils/search/ranking.ts +203 -0
  80. package/src/utils/search/types.ts +114 -0
  81. package/tailwind.config.js +3 -3
  82. package/dist/chunk-2RGM3KJL.js.map +0 -1
  83. package/dist/chunk-ERZSVDIB.js.map +0 -1
  84. package/dist/chunk-H4RMZQ2Z.js +0 -213
  85. package/dist/chunk-H4RMZQ2Z.js.map +0 -1
@@ -0,0 +1,447 @@
1
+ import * as React from 'react'
2
+ import { cn } from '../utils/cn'
3
+ import { Tooltip, TooltipTrigger, TooltipContent } from './ui/tooltip'
4
+ import { formatCryptoAmount } from '../utils/formatting'
5
+
6
+ /**
7
+ * Display variant for the CryptoAmount component.
8
+ *
9
+ * - `default`: Crypto on top, fiat below (stacked)
10
+ * - `inline`: Single line with fiat in parentheses
11
+ * - `crypto-only`: Just the native token amount
12
+ * - `fiat-only`: Just the fiat equivalent (useful for previews)
13
+ * - `compact`: Abbreviated format for large amounts (1.5M TRUST)
14
+ */
15
+ export type CryptoAmountVariant = 'default' | 'inline' | 'crypto-only' | 'fiat-only' | 'compact'
16
+
17
+ /**
18
+ * Size variant for the CryptoAmount component.
19
+ */
20
+ export type CryptoAmountSize = 'sm' | 'md' | 'lg'
21
+
22
+ /**
23
+ * Controls where the secondary currency (fiat for crypto display, crypto for fiat display) appears.
24
+ *
25
+ * - `inline`: Show alongside primary currency (default behavior)
26
+ * - `tooltip`: Show only in tooltip on hover (saves space)
27
+ * - `hidden`: Don't show secondary currency at all
28
+ */
29
+ export type SecondaryDisplayMode = 'inline' | 'tooltip' | 'hidden'
30
+
31
+ export interface CryptoAmountProps {
32
+ /**
33
+ * The crypto amount. By default, expects wei/smallest unit (string or bigint).
34
+ * If `isTokenUnits` is true, expects the value already in token units (e.g., 1.5 for 1.5 TRUST).
35
+ */
36
+ value: string | bigint | number
37
+
38
+ /** Number of decimals for the token (default: 18). Ignored when isTokenUnits is true. */
39
+ decimals?: number
40
+
41
+ /**
42
+ * When true, the value is already in token units (e.g., 1.5 for 1.5 TRUST).
43
+ * When false (default), the value is in wei/smallest units and will be converted.
44
+ */
45
+ isTokenUnits?: boolean
46
+
47
+ /** Token symbol to display (e.g., 'TRUST', 'ETH') */
48
+ symbol: string
49
+
50
+ /**
51
+ * Exchange rate for fiat conversion (1 token = X fiat).
52
+ * If undefined/null, fiat display is hidden.
53
+ */
54
+ exchangeRate?: number | null
55
+
56
+ /**
57
+ * Fiat currency code for formatting (default: 'USD').
58
+ * Used with Intl.NumberFormat for proper currency display.
59
+ */
60
+ currencyCode?: string
61
+
62
+ /**
63
+ * Locale for number/currency formatting (default: browser locale or 'en-US')
64
+ */
65
+ locale?: string
66
+
67
+ /** Display variant (default: 'default') */
68
+ variant?: CryptoAmountVariant
69
+
70
+ /** Size variant (default: 'md') */
71
+ size?: CryptoAmountSize
72
+
73
+ /**
74
+ * Maximum significant digits before truncation (default: 10).
75
+ * Amounts exceeding this will show "..." with full value in tooltip.
76
+ */
77
+ maxSignificantDigits?: number
78
+
79
+ /**
80
+ * Maximum decimal places to display (default: 6).
81
+ * Applied after conversion but before significant digit truncation.
82
+ */
83
+ maxDecimals?: number
84
+
85
+ /** Whether to show the token symbol (default: true) */
86
+ showSymbol?: boolean
87
+
88
+ /** Whether to show fiat value when exchange rate is available (default: true) */
89
+ showFiat?: boolean
90
+
91
+ /**
92
+ * Where to display the secondary currency (fiat when showing crypto, crypto when showing fiat).
93
+ * - `inline`: Show alongside primary (default behavior)
94
+ * - `tooltip`: Show in tooltip on hover (saves space in dense UIs)
95
+ * - `hidden`: Don't show at all
96
+ * @default 'inline'
97
+ */
98
+ secondaryDisplay?: SecondaryDisplayMode
99
+
100
+ /** Additional CSS classes for the container */
101
+ className?: string
102
+
103
+ /** Additional CSS classes for the crypto amount text */
104
+ cryptoClassName?: string
105
+
106
+ /** Additional CSS classes for the fiat amount text */
107
+ fiatClassName?: string
108
+ }
109
+
110
+ /**
111
+ * A unified component for displaying cryptocurrency amounts with optional fiat conversion.
112
+ *
113
+ * Features:
114
+ * - Crypto-first display (native token amount is primary)
115
+ * - Optional fiat conversion shown as secondary (inline or in tooltip)
116
+ * - Truncation with tooltip for large amounts
117
+ * - Multiple display variants (stacked, inline, compact)
118
+ * - Proper number formatting based on locale
119
+ *
120
+ * @example
121
+ * ```tsx
122
+ * // Basic usage - stacked display
123
+ * <CryptoAmount
124
+ * value="1500000000000000000"
125
+ * symbol="TRUST"
126
+ * exchangeRate={0.42}
127
+ * />
128
+ * // Renders:
129
+ * // 1.5 TRUST
130
+ * // $0.63
131
+ *
132
+ * // Inline variant
133
+ * <CryptoAmount
134
+ * value="1500000000000000000"
135
+ * symbol="TRUST"
136
+ * exchangeRate={0.42}
137
+ * variant="inline"
138
+ * />
139
+ * // Renders: 1.5 TRUST (~$0.63)
140
+ *
141
+ * // Space-saving: show crypto only, fiat in tooltip
142
+ * <CryptoAmount
143
+ * value="1500000000000000000"
144
+ * symbol="TRUST"
145
+ * exchangeRate={0.42}
146
+ * secondaryDisplay="tooltip"
147
+ * />
148
+ * // Renders: 1.5 TRUST (hover shows: 1.5 TRUST + ≈ $0.63)
149
+ *
150
+ * // Compact variant for large amounts
151
+ * <CryptoAmount
152
+ * value="1500000000000000000000000"
153
+ * symbol="TRUST"
154
+ * variant="compact"
155
+ * />
156
+ * // Renders: 1.5M TRUST
157
+ * ```
158
+ */
159
+ export function CryptoAmount({
160
+ value,
161
+ decimals = 18,
162
+ isTokenUnits = false,
163
+ symbol,
164
+ exchangeRate,
165
+ currencyCode = 'USD',
166
+ locale,
167
+ variant = 'default',
168
+ size = 'md',
169
+ maxSignificantDigits = 10,
170
+ maxDecimals = 6,
171
+ showSymbol = true,
172
+ showFiat = true,
173
+ secondaryDisplay = 'inline',
174
+ className,
175
+ cryptoClassName,
176
+ fiatClassName,
177
+ }: CryptoAmountProps) {
178
+ // Determine locale
179
+ const effectiveLocale = locale || (typeof navigator !== 'undefined' ? navigator.language : 'en-US')
180
+
181
+ // Format the crypto amount
182
+ const formatted = React.useMemo(() => {
183
+ // If value is already in token units, create a simple formatted result
184
+ if (isTokenUnits) {
185
+ const numericValue = typeof value === 'string' ? parseFloat(value) : Number(value)
186
+ const fullValue = numericValue.toString()
187
+
188
+ // Apply maxDecimals truncation for display
189
+ let displayValue = fullValue
190
+ if (maxDecimals !== undefined) {
191
+ // Round to maxDecimals places
192
+ const factor = Math.pow(10, maxDecimals)
193
+ const truncated = Math.floor(numericValue * factor) / factor
194
+ displayValue = truncated.toLocaleString(effectiveLocale, {
195
+ minimumFractionDigits: 0,
196
+ maximumFractionDigits: maxDecimals,
197
+ })
198
+ }
199
+
200
+ const isTruncated = displayValue !== fullValue
201
+ return {
202
+ display: displayValue,
203
+ fullValue,
204
+ isTruncated,
205
+ numericValue,
206
+ }
207
+ }
208
+
209
+ // Otherwise, convert from wei
210
+ // Ensure value is string or bigint for formatCryptoAmount
211
+ const weiValue = typeof value === 'number' ? value.toString() : value
212
+ return formatCryptoAmount(weiValue, decimals, {
213
+ maxSignificantDigits,
214
+ maxDecimals,
215
+ compact: variant === 'compact',
216
+ locale: effectiveLocale,
217
+ })
218
+ }, [value, decimals, isTokenUnits, maxSignificantDigits, maxDecimals, variant, effectiveLocale])
219
+
220
+ // Calculate fiat value if exchange rate is available
221
+ // Note: We calculate even when secondaryDisplay='tooltip' because we need it for the tooltip
222
+ const fiatFormatted = React.useMemo(() => {
223
+ if (!showFiat || exchangeRate == null || variant === 'crypto-only' || secondaryDisplay === 'hidden') {
224
+ return null
225
+ }
226
+
227
+ const fiatValue = formatted.numericValue * exchangeRate
228
+
229
+ try {
230
+ return new Intl.NumberFormat(effectiveLocale, {
231
+ style: 'currency',
232
+ currency: currencyCode.toUpperCase(),
233
+ minimumFractionDigits: 2,
234
+ maximumFractionDigits: 2,
235
+ }).format(fiatValue)
236
+ } catch {
237
+ // Fallback if currency code is invalid
238
+ return null
239
+ }
240
+ }, [formatted.numericValue, exchangeRate, currencyCode, effectiveLocale, showFiat, variant, secondaryDisplay])
241
+
242
+ // Determine if we should show fiat inline (vs in tooltip)
243
+ const showFiatInline = fiatFormatted && secondaryDisplay === 'inline'
244
+
245
+ // Size-based styling
246
+ const sizeStyles = {
247
+ sm: {
248
+ crypto: 'text-sm',
249
+ fiat: 'text-xs',
250
+ },
251
+ md: {
252
+ crypto: 'text-base',
253
+ fiat: 'text-sm',
254
+ },
255
+ lg: {
256
+ crypto: 'text-lg font-semibold',
257
+ fiat: 'text-base',
258
+ },
259
+ }
260
+
261
+ const styles = sizeStyles[size]
262
+
263
+ // Build the crypto display string
264
+ const cryptoDisplay = showSymbol
265
+ ? `${formatted.display} ${symbol}`
266
+ : formatted.display
267
+
268
+ // Full value for tooltip (when truncated)
269
+ const fullCryptoDisplay = showSymbol
270
+ ? `${formatted.fullValue} ${symbol}`
271
+ : formatted.fullValue
272
+
273
+ // Determine if we need a tooltip
274
+ // Show tooltip if truncated OR if secondary currency should be in tooltip
275
+ const showSecondaryInTooltip = secondaryDisplay === 'tooltip' && fiatFormatted
276
+ const needsTooltip = formatted.isTruncated || showSecondaryInTooltip
277
+
278
+ // Render the crypto amount (with or without tooltip)
279
+ const cryptoElement = (
280
+ <span
281
+ className={cn(
282
+ styles.crypto,
283
+ 'font-medium',
284
+ // Add cursor hint when tooltip contains extra info
285
+ // showSecondaryInTooltip && 'cursor-help',
286
+ cryptoClassName
287
+ )}
288
+ >
289
+ {cryptoDisplay}
290
+ </span>
291
+ )
292
+
293
+ // Build tooltip content - show full crypto always, add fiat if in tooltip mode
294
+ // Note: Tooltip uses bg-primary, so we use text-primary-foreground with opacity
295
+ // rather than text-muted-foreground which is designed for page backgrounds
296
+ const tooltipContent = (
297
+ <div className="flex flex-col gap-0.5">
298
+ <span className="font-mono text-xs">{fullCryptoDisplay}</span>
299
+ {showSecondaryInTooltip && (
300
+ <span className="text-xs text-primary-foreground/70">≈ {fiatFormatted}</span>
301
+ )}
302
+ </div>
303
+ )
304
+
305
+ const cryptoWithTooltip = needsTooltip ? (
306
+ <Tooltip delayDuration={showSecondaryInTooltip ? 300 : 1000}>
307
+ <TooltipTrigger asChild>
308
+ {cryptoElement}
309
+ </TooltipTrigger>
310
+ <TooltipContent>
311
+ {tooltipContent}
312
+ </TooltipContent>
313
+ </Tooltip>
314
+ ) : cryptoElement
315
+
316
+ // Render based on variant
317
+ if (variant === 'crypto-only') {
318
+ return (
319
+ <div className={cn('flex items-center', className)}>
320
+ {cryptoWithTooltip}
321
+ </div>
322
+ )
323
+ }
324
+
325
+ if (variant === 'fiat-only') {
326
+ // Only show fiat, return null if no exchange rate available
327
+ if (!fiatFormatted) {
328
+ return null
329
+ }
330
+ return (
331
+ <span className={cn(
332
+ styles.fiat,
333
+ 'text-muted-foreground',
334
+ fiatClassName,
335
+ className
336
+ )}>
337
+ ≈ {fiatFormatted}
338
+ </span>
339
+ )
340
+ }
341
+
342
+ if (variant === 'inline') {
343
+ return (
344
+ <div className={cn('flex items-center gap-1', className)}>
345
+ {cryptoWithTooltip}
346
+ {showFiatInline && (
347
+ <span className={cn(
348
+ styles.fiat,
349
+ 'text-muted-foreground',
350
+ fiatClassName
351
+ )}>
352
+ (~{fiatFormatted})
353
+ </span>
354
+ )}
355
+ </div>
356
+ )
357
+ }
358
+
359
+ if (variant === 'compact') {
360
+ return (
361
+ <div className={cn('flex items-center gap-1', className)}>
362
+ {cryptoWithTooltip}
363
+ {showFiatInline && (
364
+ <span className={cn(
365
+ styles.fiat,
366
+ 'text-muted-foreground',
367
+ fiatClassName
368
+ )}>
369
+ ({fiatFormatted})
370
+ </span>
371
+ )}
372
+ </div>
373
+ )
374
+ }
375
+
376
+ // Default: stacked layout
377
+ return (
378
+ <div className={cn('flex flex-col', className)}>
379
+ {cryptoWithTooltip}
380
+ {showFiatInline && (
381
+ <span className={cn(
382
+ styles.fiat,
383
+ 'text-muted-foreground',
384
+ fiatClassName
385
+ )}>
386
+ {fiatFormatted}
387
+ </span>
388
+ )}
389
+ </div>
390
+ )
391
+ }
392
+
393
+ export default CryptoAmount
394
+
395
+ // ============================================================================
396
+ // Helper utilities for creating CryptoAmount props
397
+ // ============================================================================
398
+
399
+ /**
400
+ * Props subset for creating a CryptoAmount from an exchange rate context.
401
+ * Use this type when building a hook wrapper in your app.
402
+ */
403
+ export type CryptoAmountRateProps = Pick<
404
+ CryptoAmountProps,
405
+ 'exchangeRate' | 'currencyCode' | 'locale'
406
+ >
407
+
408
+ /**
409
+ * Creates CryptoAmountRateProps from an exchange rate and currency settings.
410
+ * This is a helper for building hook wrappers in consuming applications.
411
+ *
412
+ * @param exchangeRate - The current exchange rate (1 token = X fiat), or null/undefined
413
+ * @param currencyCode - The fiat currency code (e.g., 'USD', 'EUR')
414
+ * @param locale - Optional locale for formatting
415
+ * @returns Props object to spread onto CryptoAmount
416
+ *
417
+ * @example
418
+ * ```tsx
419
+ * // In your app's hook wrapper:
420
+ * export function useCryptoAmountProps() {
421
+ * const { data } = useExchangeRates()
422
+ * const { selectedCurrency } = useCurrency()
423
+ *
424
+ * return createCryptoAmountRateProps(
425
+ * data?.rate,
426
+ * selectedCurrency,
427
+ * navigator.language
428
+ * )
429
+ * }
430
+ *
431
+ * // Then in components:
432
+ * const rateProps = useCryptoAmountProps()
433
+ * <CryptoAmount value="1000000000000000000" symbol="TRUST" {...rateProps} />
434
+ * ```
435
+ */
436
+ export function createCryptoAmountRateProps(
437
+ exchangeRate: number | null | undefined,
438
+ currencyCode: string = 'USD',
439
+ locale?: string
440
+ ): CryptoAmountRateProps {
441
+ return {
442
+ exchangeRate: exchangeRate ?? undefined,
443
+ currencyCode,
444
+ locale,
445
+ }
446
+ }
447
+
@@ -0,0 +1,35 @@
1
+ import { cn } from '../utils/cn'
2
+
3
+ export interface ErrorBannerProps {
4
+ /** The error message to display */
5
+ error: string
6
+ /** Optional HTTP status code */
7
+ code?: number
8
+ /** Additional CSS classes */
9
+ className?: string
10
+ }
11
+
12
+ /**
13
+ * A simple error banner component for displaying error messages.
14
+ * Centered layout with large heading and subtitle.
15
+ *
16
+ * @example
17
+ * <ErrorBanner error="Page not found" code={404} />
18
+ */
19
+ export const ErrorBanner = ({
20
+ error,
21
+ code,
22
+ className
23
+ }: ErrorBannerProps) => {
24
+ return (
25
+ <div className={cn("flex flex-col gap-2 mt-12", className)}>
26
+ <h2 className="text-center text-2xl font-bold mb-8">
27
+ {code ? `${code} Error` : 'Error'}
28
+ </h2>
29
+ <p className="text-center text-lg text-gray-500">{error}</p>
30
+ </div>
31
+ )
32
+ }
33
+
34
+ export default ErrorBanner
35
+
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+ import { ipfsToHttp } from '../utils/formatting'
3
+
4
+ export interface IpfsImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
5
+ /** The image source - can be an ipfs:// URI or regular URL */
6
+ src: string
7
+ }
8
+
9
+ /**
10
+ * An image component that automatically converts IPFS URIs to gateway URLs.
11
+ *
12
+ * @example
13
+ * <IpfsImage src="ipfs://QmHash123" alt="IPFS image" />
14
+ */
15
+ export const IpfsImage = ({ src, ...props }: IpfsImageProps) => {
16
+ const finalSrc = ipfsToHttp(src) || src
17
+ return <img src={finalSrc} {...props} />
18
+ }
19
+
20
+ export default IpfsImage
21
+
@@ -0,0 +1,55 @@
1
+ export interface LoadingDotsProps {
2
+ /** Duration of opacity transition in ms */
3
+ transitionDuration?: number
4
+ /** Whether the dots are visible */
5
+ isVisible?: boolean
6
+ /** Additional CSS classes */
7
+ className?: string
8
+ }
9
+
10
+ /**
11
+ * Animated loading dots indicator.
12
+ * Shows three dots with a wave animation.
13
+ *
14
+ * @example
15
+ * <LoadingDots isVisible={isLoading} />
16
+ */
17
+ export function LoadingDots({
18
+ transitionDuration = 300,
19
+ isVisible = true,
20
+ className = '',
21
+ }: LoadingDotsProps) {
22
+ return (
23
+ <div
24
+ className={`flex gap-1 justify-center items-center ${className}`}
25
+ style={{
26
+ opacity: isVisible ? 1 : 0,
27
+ transition: `opacity ${transitionDuration}ms ease-in-out`,
28
+ }}
29
+ >
30
+ {[0, 1, 2].map((i) => (
31
+ <span
32
+ key={i}
33
+ className="w-2 h-2 bg-gray-600 rounded-full"
34
+ style={{
35
+ animation: 'hivemind-loading-dots-wave 1.2s infinite ease-in-out',
36
+ animationDelay: `${i * 0.2}s`,
37
+ }}
38
+ />
39
+ ))}
40
+ <style>{`
41
+ @keyframes hivemind-loading-dots-wave {
42
+ 0%, 100% {
43
+ transform: translateY(0);
44
+ }
45
+ 50% {
46
+ transform: translateY(-6px);
47
+ }
48
+ }
49
+ `}</style>
50
+ </div>
51
+ )
52
+ }
53
+
54
+ export default LoadingDots
55
+
@@ -0,0 +1,108 @@
1
+ import {
2
+ Area,
3
+ AreaChart,
4
+ CartesianGrid,
5
+ ResponsiveContainer,
6
+ Tooltip,
7
+ XAxis,
8
+ YAxis,
9
+ } from 'recharts'
10
+
11
+ export interface TripleAreaChartDataPoint {
12
+ /** The x-axis value (typically position value) */
13
+ xValue: number
14
+ /** Vault (FOR) position value */
15
+ vault?: number
16
+ /** Counter vault (AGAINST) position value */
17
+ counterVault?: number
18
+ }
19
+
20
+ export interface TripleAreaChartProps {
21
+ /** Pre-formatted chart data points */
22
+ chartData: TripleAreaChartDataPoint[]
23
+ /** Display variant - 'min' hides axes/grid, 'max' shows full chart */
24
+ variant?: 'min' | 'max'
25
+ /** Color for vault (FOR) area */
26
+ vaultColor?: string
27
+ /** Color for counter vault (AGAINST) area */
28
+ counterVaultColor?: string
29
+ }
30
+
31
+ /**
32
+ * An area chart for visualizing triple position distributions.
33
+ * Shows vault (FOR) positions on positive side and counter vault (AGAINST) on negative.
34
+ *
35
+ * @example
36
+ * <TripleAreaChart
37
+ * chartData={formattedPositions}
38
+ * variant="min"
39
+ * />
40
+ */
41
+ export const TripleAreaChart = ({
42
+ chartData,
43
+ variant = 'min',
44
+ vaultColor = 'green',
45
+ counterVaultColor = 'red',
46
+ }: TripleAreaChartProps) => {
47
+ // Clone data to avoid mutating props
48
+ const data = [...chartData]
49
+
50
+ const maxValue = Math.max(...data.map((d) => d.xValue))
51
+ const minValue = Math.min(...data.map((d) => d.xValue))
52
+
53
+ // Add padding points for better visualization
54
+ if (maxValue > 0) {
55
+ data.push({
56
+ xValue: maxValue * 1.2,
57
+ vault: maxValue,
58
+ })
59
+ }
60
+ if (minValue < 0) {
61
+ data.push({
62
+ xValue: minValue * 1.2,
63
+ counterVault: minValue * -1,
64
+ })
65
+ }
66
+
67
+ data.sort((a, b) => a.xValue - b.xValue)
68
+ const maxAbsValue = Math.max(...data.map((d) => Math.abs(d.xValue)))
69
+ const domain = [maxAbsValue * -1, maxAbsValue]
70
+
71
+ return (
72
+ <ResponsiveContainer width="100%" height="100%">
73
+ <AreaChart data={data} layout="vertical">
74
+ {variant !== 'min' && (
75
+ <>
76
+ <CartesianGrid strokeDasharray="3 3" />
77
+ <Tooltip />
78
+ </>
79
+ )}
80
+ <XAxis type="number" hide={variant === 'min'} />
81
+ <YAxis
82
+ dataKey="xValue"
83
+ type="number"
84
+ reversed
85
+ domain={domain}
86
+ hide={variant === 'min'}
87
+ />
88
+ <Area
89
+ type="stepBefore"
90
+ dataKey="vault"
91
+ stroke={vaultColor}
92
+ fill={vaultColor}
93
+ fillOpacity={0.5}
94
+ />
95
+ <Area
96
+ type="stepAfter"
97
+ dataKey="counterVault"
98
+ stroke={counterVaultColor}
99
+ fill={counterVaultColor}
100
+ fillOpacity={0.5}
101
+ />
102
+ </AreaChart>
103
+ </ResponsiveContainer>
104
+ )
105
+ }
106
+
107
+ export default TripleAreaChart
108
+