@hivemindhq/core 0.4.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.
- package/README.md +224 -0
- package/dist/chunk-2RGM3KJL.js +351 -0
- package/dist/chunk-2RGM3KJL.js.map +1 -0
- package/dist/chunk-E5TTKYNS.js +55 -0
- package/dist/chunk-E5TTKYNS.js.map +1 -0
- package/dist/chunk-ERZSVDIB.js +270 -0
- package/dist/chunk-ERZSVDIB.js.map +1 -0
- package/dist/chunk-H4RMZQ2Z.js +213 -0
- package/dist/chunk-H4RMZQ2Z.js.map +1 -0
- package/dist/chunk-K7EIJSYQ.js +3 -0
- package/dist/chunk-K7EIJSYQ.js.map +1 -0
- package/dist/chunk-OJF67RNM.js +3 -0
- package/dist/chunk-OJF67RNM.js.map +1 -0
- package/dist/chunk-P5E2XNDI.js +2758 -0
- package/dist/chunk-P5E2XNDI.js.map +1 -0
- package/dist/chunk-T2XWV636.js +137 -0
- package/dist/chunk-T2XWV636.js.map +1 -0
- package/dist/chunk-VIRNUAYY.js +11 -0
- package/dist/chunk-VIRNUAYY.js.map +1 -0
- package/dist/components/index.d.ts +277 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/ui/index.d.ts +396 -0
- package/dist/components/ui/index.js +6 -0
- package/dist/components/ui/index.js.map +1 -0
- package/dist/hooks/index.d.ts +27 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/toast-D090UYOq.d.ts +19 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +573 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +107 -0
- package/tailwind.config.js +96 -0
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @revel8/core
|
|
2
|
+
|
|
3
|
+
Shared components, utilities, and types for Revel8 applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides common functionality shared between:
|
|
8
|
+
- **revel8-explorer** - Web application for exploring the Intuition knowledge graph
|
|
9
|
+
- **intuition-extension** - Browser extension for Intuition insights
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Using pnpm (recommended)
|
|
15
|
+
pnpm add @revel8/core
|
|
16
|
+
|
|
17
|
+
# Using npm
|
|
18
|
+
npm install @revel8/core
|
|
19
|
+
|
|
20
|
+
# For local development (link)
|
|
21
|
+
cd revel8-core && pnpm link
|
|
22
|
+
cd ../revel8-explorer && pnpm link @revel8/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Utilities
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { cn, truncateId, ipfsToHttp, formatCompact } from '@revel8/core'
|
|
31
|
+
|
|
32
|
+
// Merge Tailwind classes
|
|
33
|
+
cn('px-4 py-2', isActive && 'bg-blue-500')
|
|
34
|
+
|
|
35
|
+
// Truncate atom/triple IDs for display
|
|
36
|
+
truncateId('0x123456789098765432') // '12345'
|
|
37
|
+
|
|
38
|
+
// Convert IPFS URIs to HTTP
|
|
39
|
+
ipfsToHttp('ipfs://QmHash123') // 'https://ipfs.io/ipfs/QmHash123'
|
|
40
|
+
|
|
41
|
+
// Format large numbers
|
|
42
|
+
formatCompact(1500000) // '1.5M'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Components
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// UI primitives
|
|
49
|
+
import { Button, Card, Tooltip } from '@revel8/core'
|
|
50
|
+
|
|
51
|
+
// Display components
|
|
52
|
+
import { CryptoAmount, AtomIcon, IpfsImage } from '@revel8/core'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### CryptoAmount Component
|
|
56
|
+
|
|
57
|
+
A unified component for displaying cryptocurrency amounts with optional fiat conversion.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import { CryptoAmount, createCryptoAmountRateProps } from '@revel8/core'
|
|
61
|
+
|
|
62
|
+
// Basic usage - crypto on top, fiat below
|
|
63
|
+
<CryptoAmount
|
|
64
|
+
value="1500000000000000000" // 1.5 tokens in wei
|
|
65
|
+
symbol="TRUST"
|
|
66
|
+
exchangeRate={0.42} // 1 TRUST = $0.42
|
|
67
|
+
currencyCode="usd"
|
|
68
|
+
/>
|
|
69
|
+
// Renders:
|
|
70
|
+
// 1.5 TRUST
|
|
71
|
+
// $0.63
|
|
72
|
+
|
|
73
|
+
// Inline variant
|
|
74
|
+
<CryptoAmount
|
|
75
|
+
value="1500000000000000000"
|
|
76
|
+
symbol="TRUST"
|
|
77
|
+
exchangeRate={0.42}
|
|
78
|
+
variant="inline"
|
|
79
|
+
/>
|
|
80
|
+
// Renders: 1.5 TRUST (~$0.63)
|
|
81
|
+
|
|
82
|
+
// Compact variant (for large amounts)
|
|
83
|
+
<CryptoAmount
|
|
84
|
+
value="1500000000000000000000000" // 1.5M tokens
|
|
85
|
+
symbol="TRUST"
|
|
86
|
+
variant="compact"
|
|
87
|
+
/>
|
|
88
|
+
// Renders: 1.5M TRUST
|
|
89
|
+
|
|
90
|
+
// Crypto-only (no fiat)
|
|
91
|
+
<CryptoAmount
|
|
92
|
+
value="1500000000000000000"
|
|
93
|
+
symbol="TRUST"
|
|
94
|
+
variant="crypto-only"
|
|
95
|
+
/>
|
|
96
|
+
// Renders: 1.5 TRUST
|
|
97
|
+
|
|
98
|
+
// Hook wrapper pattern for your app:
|
|
99
|
+
function useCryptoAmountProps() {
|
|
100
|
+
const { data } = useExchangeRates()
|
|
101
|
+
const { selectedCurrency } = useCurrency()
|
|
102
|
+
return createCryptoAmountRateProps(data?.rate, selectedCurrency)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Then in components:
|
|
106
|
+
const rateProps = useCryptoAmountProps()
|
|
107
|
+
<CryptoAmount value={stake} symbol="TRUST" {...rateProps} />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Props:**
|
|
111
|
+
| Prop | Type | Default | Description |
|
|
112
|
+
|------|------|---------|-------------|
|
|
113
|
+
| `value` | `string \| bigint` | required | Raw amount in smallest unit (wei) |
|
|
114
|
+
| `symbol` | `string` | required | Token symbol (e.g., 'TRUST') |
|
|
115
|
+
| `decimals` | `number` | `18` | Token decimals |
|
|
116
|
+
| `exchangeRate` | `number \| null` | - | Fiat conversion rate (hides fiat if missing) |
|
|
117
|
+
| `currencyCode` | `string` | `'USD'` | Fiat currency code for formatting |
|
|
118
|
+
| `variant` | `'default' \| 'inline' \| 'crypto-only' \| 'compact'` | `'default'` | Display variant |
|
|
119
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant |
|
|
120
|
+
| `maxSignificantDigits` | `number` | `10` | Digits before truncation (shows tooltip) |
|
|
121
|
+
|
|
122
|
+
### Tailwind Configuration
|
|
123
|
+
|
|
124
|
+
Extend the shared Tailwind config in your app:
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// tailwind.config.js
|
|
128
|
+
const sharedConfig = require('@revel8/core/tailwind.config')
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
presets: [sharedConfig],
|
|
132
|
+
content: [
|
|
133
|
+
'./src/**/*.{ts,tsx}',
|
|
134
|
+
'./node_modules/@revel8/core/dist/**/*.js',
|
|
135
|
+
],
|
|
136
|
+
// Your app-specific extensions...
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Install dependencies
|
|
144
|
+
pnpm install
|
|
145
|
+
|
|
146
|
+
# Build the package
|
|
147
|
+
pnpm build
|
|
148
|
+
|
|
149
|
+
# Watch mode for development
|
|
150
|
+
pnpm dev
|
|
151
|
+
|
|
152
|
+
# Type check
|
|
153
|
+
pnpm typecheck
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Package Structure
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
revel8-core/
|
|
160
|
+
├── src/
|
|
161
|
+
│ ├── index.ts # Main entry point
|
|
162
|
+
│ ├── components/
|
|
163
|
+
│ │ ├── ui/ # shadcn/ui primitives
|
|
164
|
+
│ │ │ └── index.ts
|
|
165
|
+
│ │ └── display/ # Intuition display components
|
|
166
|
+
│ │ └── index.ts
|
|
167
|
+
│ ├── utils/
|
|
168
|
+
│ │ ├── index.ts
|
|
169
|
+
│ │ ├── cn.ts # Class name utility
|
|
170
|
+
│ │ └── formatting.ts # Formatting helpers
|
|
171
|
+
│ └── types/
|
|
172
|
+
│ └── index.ts # Shared TypeScript types
|
|
173
|
+
├── tailwind.config.js # Shared Tailwind configuration
|
|
174
|
+
├── tsconfig.json
|
|
175
|
+
├── tsup.config.ts # Build configuration
|
|
176
|
+
└── package.json
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Exports
|
|
180
|
+
|
|
181
|
+
The package provides multiple entry points:
|
|
182
|
+
|
|
183
|
+
| Import Path | Contents |
|
|
184
|
+
|-------------|----------|
|
|
185
|
+
| `@revel8/core` | Everything (utils, components, types) |
|
|
186
|
+
| `@revel8/core/utils` | Utility functions only |
|
|
187
|
+
| `@revel8/core/components/ui` | UI primitives |
|
|
188
|
+
| `@revel8/core/components/display` | Display components |
|
|
189
|
+
|
|
190
|
+
## Peer Dependencies
|
|
191
|
+
|
|
192
|
+
This package requires the following peer dependencies:
|
|
193
|
+
|
|
194
|
+
| Package | Version |
|
|
195
|
+
|---------|---------|
|
|
196
|
+
| `react` | ^18.0.0 \|\| ^19.0.0 |
|
|
197
|
+
| `react-dom` | ^18.0.0 \|\| ^19.0.0 |
|
|
198
|
+
| `tailwindcss` | ^3.0.0 \|\| ^4.0.0 (optional) |
|
|
199
|
+
|
|
200
|
+
## Adding New Components
|
|
201
|
+
|
|
202
|
+
1. Create the component in the appropriate directory
|
|
203
|
+
2. Export it from the directory's `index.ts`
|
|
204
|
+
3. Rebuild the package: `pnpm build`
|
|
205
|
+
4. Update consuming apps to use the new component
|
|
206
|
+
|
|
207
|
+
### Migration Checklist
|
|
208
|
+
|
|
209
|
+
When migrating a component from a consuming app:
|
|
210
|
+
|
|
211
|
+
- [ ] Copy component to `src/components/`
|
|
212
|
+
- [ ] Update imports to use relative paths (no `@/` or `~/` aliases)
|
|
213
|
+
- [ ] Ensure it only depends on:
|
|
214
|
+
- Other `@revel8/core` exports
|
|
215
|
+
- Peer dependencies (React, etc.)
|
|
216
|
+
- Direct dependencies listed in package.json
|
|
217
|
+
- [ ] Add export to the appropriate barrel file
|
|
218
|
+
- [ ] Test in both consuming apps
|
|
219
|
+
- [ ] Remove original from consuming apps
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
ISC © Kylan Hurt
|
|
224
|
+
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { strengthenImageUrl, ipfsToHttp, formatCryptoAmount } from './chunk-ERZSVDIB.js';
|
|
2
|
+
import { Tooltip as Tooltip$1, TooltipTrigger, TooltipContent } from './chunk-E5TTKYNS.js';
|
|
3
|
+
import { cn } from './chunk-VIRNUAYY.js';
|
|
4
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import * as React2 from 'react';
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { ResponsiveContainer, BarChart, XAxis, YAxis, Bar, AreaChart, CartesianGrid, Tooltip, Area } from 'recharts';
|
|
8
|
+
|
|
9
|
+
var AtomIcon = ({ ...props }) => {
|
|
10
|
+
return /* @__PURE__ */ jsxs(
|
|
11
|
+
"svg",
|
|
12
|
+
{
|
|
13
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
14
|
+
width: "24",
|
|
15
|
+
height: "24",
|
|
16
|
+
viewBox: "0 0 24 24",
|
|
17
|
+
fill: "none",
|
|
18
|
+
stroke: "rgba(255,255,255,0.6)",
|
|
19
|
+
"stroke-width": "1.5",
|
|
20
|
+
"stroke-linecap": "round",
|
|
21
|
+
"stroke-linejoin": "round",
|
|
22
|
+
className: "lucide lucide-atom",
|
|
23
|
+
...props,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
|
|
26
|
+
/* @__PURE__ */ jsx("path", { d: "M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z" }),
|
|
27
|
+
/* @__PURE__ */ jsx("path", { d: "M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z" })
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
var UnknownImage = ({
|
|
33
|
+
asLink = false,
|
|
34
|
+
src,
|
|
35
|
+
...props
|
|
36
|
+
}) => {
|
|
37
|
+
if (!src) return null;
|
|
38
|
+
const [numberOfErrors, setNumberOfErrors] = useState(0);
|
|
39
|
+
const onImageError = (e, finalSrc2) => {
|
|
40
|
+
setNumberOfErrors(numberOfErrors + 1);
|
|
41
|
+
if (typeof window !== "undefined" && window.__DEV__) {
|
|
42
|
+
console.log("UnknownImage error:", e.currentTarget.src, finalSrc2);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const finalSrc = strengthenImageUrl(src);
|
|
46
|
+
const img = /* @__PURE__ */ jsx("img", { src: finalSrc, onError: (e) => onImageError(e, finalSrc), ...props });
|
|
47
|
+
if (asLink) {
|
|
48
|
+
return /* @__PURE__ */ jsx("a", { href: finalSrc, target: "_blank", rel: "noopener noreferrer", children: img });
|
|
49
|
+
}
|
|
50
|
+
return img;
|
|
51
|
+
};
|
|
52
|
+
function LoadingDots({
|
|
53
|
+
transitionDuration = 300,
|
|
54
|
+
isVisible = true,
|
|
55
|
+
className = ""
|
|
56
|
+
}) {
|
|
57
|
+
return /* @__PURE__ */ jsxs(
|
|
58
|
+
"div",
|
|
59
|
+
{
|
|
60
|
+
className: `flex gap-1 justify-center items-center ${className}`,
|
|
61
|
+
style: {
|
|
62
|
+
opacity: isVisible ? 1 : 0,
|
|
63
|
+
transition: `opacity ${transitionDuration}ms ease-in-out`
|
|
64
|
+
},
|
|
65
|
+
children: [
|
|
66
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
className: "w-2 h-2 bg-gray-600 rounded-full",
|
|
70
|
+
style: {
|
|
71
|
+
animation: "revel8-loading-dots-wave 1.2s infinite ease-in-out",
|
|
72
|
+
animationDelay: `${i * 0.2}s`
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
i
|
|
76
|
+
)),
|
|
77
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
78
|
+
@keyframes revel8-loading-dots-wave {
|
|
79
|
+
0%, 100% {
|
|
80
|
+
transform: translateY(0);
|
|
81
|
+
}
|
|
82
|
+
50% {
|
|
83
|
+
transform: translateY(-6px);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
` })
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
var IpfsImage = ({ src, ...props }) => {
|
|
92
|
+
const finalSrc = ipfsToHttp(src) || src;
|
|
93
|
+
return /* @__PURE__ */ jsx("img", { src: finalSrc, ...props });
|
|
94
|
+
};
|
|
95
|
+
var TriplePositionsTornadoMinGraph = ({
|
|
96
|
+
data,
|
|
97
|
+
forColor = "#31ad31",
|
|
98
|
+
againstColor = "#bc4535",
|
|
99
|
+
className
|
|
100
|
+
}) => {
|
|
101
|
+
if (!data || data.length === 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", className, children: /* @__PURE__ */ jsxs(
|
|
105
|
+
BarChart,
|
|
106
|
+
{
|
|
107
|
+
data,
|
|
108
|
+
stackOffset: "sign",
|
|
109
|
+
layout: "vertical",
|
|
110
|
+
barGap: 0,
|
|
111
|
+
barCategoryGap: 0,
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ jsx(XAxis, { type: "number", hide: true }),
|
|
114
|
+
/* @__PURE__ */ jsx(YAxis, { dataKey: "name", type: "number", reversed: true, hide: true }),
|
|
115
|
+
/* @__PURE__ */ jsx(Bar, { dataKey: "aggregateVaultBalance", fill: forColor, stackId: "stack" }),
|
|
116
|
+
/* @__PURE__ */ jsx(
|
|
117
|
+
Bar,
|
|
118
|
+
{
|
|
119
|
+
dataKey: "aggregateCounterVaultBalance",
|
|
120
|
+
fill: againstColor,
|
|
121
|
+
stackId: "stack"
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
) });
|
|
127
|
+
};
|
|
128
|
+
var TripleAreaChart = ({
|
|
129
|
+
chartData,
|
|
130
|
+
variant = "min",
|
|
131
|
+
vaultColor = "green",
|
|
132
|
+
counterVaultColor = "red"
|
|
133
|
+
}) => {
|
|
134
|
+
const data = [...chartData];
|
|
135
|
+
const maxValue = Math.max(...data.map((d) => d.xValue));
|
|
136
|
+
const minValue = Math.min(...data.map((d) => d.xValue));
|
|
137
|
+
if (maxValue > 0) {
|
|
138
|
+
data.push({
|
|
139
|
+
xValue: maxValue * 1.2,
|
|
140
|
+
vault: maxValue
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (minValue < 0) {
|
|
144
|
+
data.push({
|
|
145
|
+
xValue: minValue * 1.2,
|
|
146
|
+
counterVault: minValue * -1
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
data.sort((a, b) => a.xValue - b.xValue);
|
|
150
|
+
const maxAbsValue = Math.max(...data.map((d) => Math.abs(d.xValue)));
|
|
151
|
+
const domain = [maxAbsValue * -1, maxAbsValue];
|
|
152
|
+
return /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(AreaChart, { data, layout: "vertical", children: [
|
|
153
|
+
variant !== "min" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
154
|
+
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3" }),
|
|
155
|
+
/* @__PURE__ */ jsx(Tooltip, {})
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsx(XAxis, { type: "number", hide: variant === "min" }),
|
|
158
|
+
/* @__PURE__ */ jsx(
|
|
159
|
+
YAxis,
|
|
160
|
+
{
|
|
161
|
+
dataKey: "xValue",
|
|
162
|
+
type: "number",
|
|
163
|
+
reversed: true,
|
|
164
|
+
domain,
|
|
165
|
+
hide: variant === "min"
|
|
166
|
+
}
|
|
167
|
+
),
|
|
168
|
+
/* @__PURE__ */ jsx(
|
|
169
|
+
Area,
|
|
170
|
+
{
|
|
171
|
+
type: "stepBefore",
|
|
172
|
+
dataKey: "vault",
|
|
173
|
+
stroke: vaultColor,
|
|
174
|
+
fill: vaultColor,
|
|
175
|
+
fillOpacity: 0.5
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsx(
|
|
179
|
+
Area,
|
|
180
|
+
{
|
|
181
|
+
type: "stepAfter",
|
|
182
|
+
dataKey: "counterVault",
|
|
183
|
+
stroke: counterVaultColor,
|
|
184
|
+
fill: counterVaultColor,
|
|
185
|
+
fillOpacity: 0.5
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
] }) });
|
|
189
|
+
};
|
|
190
|
+
var ErrorBanner = ({
|
|
191
|
+
error,
|
|
192
|
+
code,
|
|
193
|
+
className
|
|
194
|
+
}) => {
|
|
195
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2 mt-12", className), children: [
|
|
196
|
+
/* @__PURE__ */ jsx("h2", { className: "text-center text-2xl font-bold mb-8", children: code ? `${code} Error` : "Error" }),
|
|
197
|
+
/* @__PURE__ */ jsx("p", { className: "text-center text-lg text-gray-500", children: error })
|
|
198
|
+
] });
|
|
199
|
+
};
|
|
200
|
+
function CryptoAmount({
|
|
201
|
+
value,
|
|
202
|
+
decimals = 18,
|
|
203
|
+
isTokenUnits = false,
|
|
204
|
+
symbol,
|
|
205
|
+
exchangeRate,
|
|
206
|
+
currencyCode = "USD",
|
|
207
|
+
locale,
|
|
208
|
+
variant = "default",
|
|
209
|
+
size = "md",
|
|
210
|
+
maxSignificantDigits = 10,
|
|
211
|
+
maxDecimals = 6,
|
|
212
|
+
showSymbol = true,
|
|
213
|
+
showFiat = true,
|
|
214
|
+
className,
|
|
215
|
+
cryptoClassName,
|
|
216
|
+
fiatClassName
|
|
217
|
+
}) {
|
|
218
|
+
const effectiveLocale = locale || (typeof navigator !== "undefined" ? navigator.language : "en-US");
|
|
219
|
+
const formatted = React2.useMemo(() => {
|
|
220
|
+
if (isTokenUnits) {
|
|
221
|
+
const numericValue = typeof value === "string" ? parseFloat(value) : Number(value);
|
|
222
|
+
const displayValue = numericValue.toString();
|
|
223
|
+
return {
|
|
224
|
+
display: displayValue,
|
|
225
|
+
fullValue: displayValue,
|
|
226
|
+
isTruncated: false,
|
|
227
|
+
numericValue
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const weiValue = typeof value === "number" ? value.toString() : value;
|
|
231
|
+
return formatCryptoAmount(weiValue, decimals, {
|
|
232
|
+
maxSignificantDigits,
|
|
233
|
+
maxDecimals,
|
|
234
|
+
compact: variant === "compact",
|
|
235
|
+
locale: effectiveLocale
|
|
236
|
+
});
|
|
237
|
+
}, [value, decimals, isTokenUnits, maxSignificantDigits, maxDecimals, variant, effectiveLocale]);
|
|
238
|
+
const fiatFormatted = React2.useMemo(() => {
|
|
239
|
+
if (!showFiat || exchangeRate == null || variant === "crypto-only") {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const fiatValue = formatted.numericValue * exchangeRate;
|
|
243
|
+
try {
|
|
244
|
+
return new Intl.NumberFormat(effectiveLocale, {
|
|
245
|
+
style: "currency",
|
|
246
|
+
currency: currencyCode.toUpperCase(),
|
|
247
|
+
minimumFractionDigits: 2,
|
|
248
|
+
maximumFractionDigits: 2
|
|
249
|
+
}).format(fiatValue);
|
|
250
|
+
} catch {
|
|
251
|
+
return `$${fiatValue.toFixed(2)}`;
|
|
252
|
+
}
|
|
253
|
+
}, [formatted.numericValue, exchangeRate, currencyCode, effectiveLocale, showFiat, variant]);
|
|
254
|
+
const sizeStyles = {
|
|
255
|
+
sm: {
|
|
256
|
+
crypto: "text-sm",
|
|
257
|
+
fiat: "text-xs"
|
|
258
|
+
},
|
|
259
|
+
md: {
|
|
260
|
+
crypto: "text-base",
|
|
261
|
+
fiat: "text-sm"
|
|
262
|
+
},
|
|
263
|
+
lg: {
|
|
264
|
+
crypto: "text-lg font-semibold",
|
|
265
|
+
fiat: "text-base"
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const styles = sizeStyles[size];
|
|
269
|
+
const cryptoDisplay = showSymbol ? `${formatted.display} ${symbol}` : formatted.display;
|
|
270
|
+
const fullCryptoDisplay = showSymbol ? `${formatted.fullValue} ${symbol}` : formatted.fullValue;
|
|
271
|
+
const needsTooltip = formatted.isTruncated;
|
|
272
|
+
const cryptoElement = /* @__PURE__ */ jsx(
|
|
273
|
+
"span",
|
|
274
|
+
{
|
|
275
|
+
className: cn(
|
|
276
|
+
styles.crypto,
|
|
277
|
+
"font-medium",
|
|
278
|
+
cryptoClassName
|
|
279
|
+
),
|
|
280
|
+
children: cryptoDisplay
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
const cryptoWithTooltip = needsTooltip ? /* @__PURE__ */ jsxs(Tooltip$1, { delayDuration: 1e3, children: [
|
|
284
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: cryptoElement }),
|
|
285
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: fullCryptoDisplay }) })
|
|
286
|
+
] }) : cryptoElement;
|
|
287
|
+
if (variant === "crypto-only") {
|
|
288
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex items-center", className), children: cryptoWithTooltip });
|
|
289
|
+
}
|
|
290
|
+
if (variant === "fiat-only") {
|
|
291
|
+
if (!fiatFormatted) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
return /* @__PURE__ */ jsxs("span", { className: cn(
|
|
295
|
+
styles.fiat,
|
|
296
|
+
"text-muted-foreground",
|
|
297
|
+
fiatClassName,
|
|
298
|
+
className
|
|
299
|
+
), children: [
|
|
300
|
+
"\u2248 ",
|
|
301
|
+
fiatFormatted
|
|
302
|
+
] });
|
|
303
|
+
}
|
|
304
|
+
if (variant === "inline") {
|
|
305
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1", className), children: [
|
|
306
|
+
cryptoWithTooltip,
|
|
307
|
+
fiatFormatted && /* @__PURE__ */ jsxs("span", { className: cn(
|
|
308
|
+
styles.fiat,
|
|
309
|
+
"text-muted-foreground",
|
|
310
|
+
fiatClassName
|
|
311
|
+
), children: [
|
|
312
|
+
"(~",
|
|
313
|
+
fiatFormatted,
|
|
314
|
+
")"
|
|
315
|
+
] })
|
|
316
|
+
] });
|
|
317
|
+
}
|
|
318
|
+
if (variant === "compact") {
|
|
319
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1", className), children: [
|
|
320
|
+
cryptoWithTooltip,
|
|
321
|
+
fiatFormatted && /* @__PURE__ */ jsxs("span", { className: cn(
|
|
322
|
+
styles.fiat,
|
|
323
|
+
"text-muted-foreground",
|
|
324
|
+
fiatClassName
|
|
325
|
+
), children: [
|
|
326
|
+
"(",
|
|
327
|
+
fiatFormatted,
|
|
328
|
+
")"
|
|
329
|
+
] })
|
|
330
|
+
] });
|
|
331
|
+
}
|
|
332
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
|
|
333
|
+
cryptoWithTooltip,
|
|
334
|
+
fiatFormatted && /* @__PURE__ */ jsx("span", { className: cn(
|
|
335
|
+
styles.fiat,
|
|
336
|
+
"text-muted-foreground",
|
|
337
|
+
fiatClassName
|
|
338
|
+
), children: fiatFormatted })
|
|
339
|
+
] });
|
|
340
|
+
}
|
|
341
|
+
function createCryptoAmountRateProps(exchangeRate, currencyCode = "USD", locale) {
|
|
342
|
+
return {
|
|
343
|
+
exchangeRate: exchangeRate ?? void 0,
|
|
344
|
+
currencyCode,
|
|
345
|
+
locale
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export { AtomIcon, CryptoAmount, ErrorBanner, IpfsImage, LoadingDots, TripleAreaChart, TriplePositionsTornadoMinGraph, UnknownImage, createCryptoAmountRateProps };
|
|
350
|
+
//# sourceMappingURL=chunk-2RGM3KJL.js.map
|
|
351
|
+
//# sourceMappingURL=chunk-2RGM3KJL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/AtomIcon.tsx","../src/components/UnknownImage.tsx","../src/components/LoadingDots.tsx","../src/components/IpfsImage.tsx","../src/components/TriplePositionsTornadoMinGraph.tsx","../src/components/TripleAreaChart.tsx","../src/components/ErrorBanner.tsx","../src/components/CryptoAmount.tsx"],"names":["finalSrc","jsx","jsxs","ResponsiveContainer","Tooltip","XAxis","YAxis"],"mappings":"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","file":"chunk-2RGM3KJL.js","sourcesContent":["export const AtomIcon = ({ ...props }: React.SVGProps<SVGSVGElement>) => {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"rgba(255,255,255,0.6)\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n className=\"lucide lucide-atom\"\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"1\" />\n <path d=\"M20.2 20.2c2.04-2.03.02-7.36-4.5-11.9-4.54-4.52-9.87-6.54-11.9-4.5-2.04 2.03-.02 7.36 4.5 11.9 4.54 4.52 9.87 6.54 11.9 4.5Z\" />\n <path d=\"M15.7 15.7c4.52-4.54 6.54-9.87 4.5-11.9-2.03-2.04-7.36-.02-11.9 4.5-4.52 4.54-6.54 9.87-4.5 11.9 2.03 2.04 7.36.02 11.9-4.5Z\" />\n </svg>\n )\n}\n","import React, { useState } from 'react'\nimport { strengthenImageUrl } from '../utils/formatting'\n\nexport interface UnknownImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {\n /** The image source URL - can be IPFS, HTTP, or raw hash */\n src: string\n /** Whether to wrap the image in an anchor link to the source */\n asLink?: boolean\n}\n\n/**\n * An image component that handles various URL formats including IPFS.\n * Automatically converts IPFS URIs and hashes to gateway URLs.\n * \n * @example\n * <UnknownImage src=\"ipfs://QmHash123\" alt=\"My image\" className=\"w-10 h-10\" />\n */\nexport const UnknownImage = ({\n asLink = false,\n src,\n ...props\n}: UnknownImageProps) => {\n if (!src) return null\n const [numberOfErrors, setNumberOfErrors] = useState(0)\n\n const onImageError = (\n e: React.SyntheticEvent<HTMLImageElement>,\n finalSrc: string\n ) => {\n setNumberOfErrors(numberOfErrors + 1)\n // Only log in development\n if (typeof window !== 'undefined' && (window as any).__DEV__) {\n console.log('UnknownImage error:', e.currentTarget.src, finalSrc)\n }\n }\n\n const finalSrc = strengthenImageUrl(src)\n\n const img = (\n <img src={finalSrc} onError={(e) => onImageError(e, finalSrc)} {...props} />\n )\n\n if (asLink) {\n return (\n <a href={finalSrc} target=\"_blank\" rel=\"noopener noreferrer\">\n {img}\n </a>\n )\n }\n\n return img\n}\n\nexport default UnknownImage\n\n","export interface LoadingDotsProps {\n /** Duration of opacity transition in ms */\n transitionDuration?: number\n /** Whether the dots are visible */\n isVisible?: boolean\n /** Additional CSS classes */\n className?: string\n}\n\n/**\n * Animated loading dots indicator.\n * Shows three dots with a wave animation.\n * \n * @example\n * <LoadingDots isVisible={isLoading} />\n */\nexport function LoadingDots({\n transitionDuration = 300,\n isVisible = true,\n className = '',\n}: LoadingDotsProps) {\n return (\n <div\n className={`flex gap-1 justify-center items-center ${className}`}\n style={{\n opacity: isVisible ? 1 : 0,\n transition: `opacity ${transitionDuration}ms ease-in-out`,\n }}\n >\n {[0, 1, 2].map((i) => (\n <span\n key={i}\n className=\"w-2 h-2 bg-gray-600 rounded-full\"\n style={{\n animation: 'revel8-loading-dots-wave 1.2s infinite ease-in-out',\n animationDelay: `${i * 0.2}s`,\n }}\n />\n ))}\n <style>{`\n @keyframes revel8-loading-dots-wave {\n 0%, 100% {\n transform: translateY(0);\n }\n 50% {\n transform: translateY(-6px);\n }\n }\n `}</style>\n </div>\n )\n}\n\nexport default LoadingDots\n\n","import React from 'react'\nimport { ipfsToHttp } from '../utils/formatting'\n\nexport interface IpfsImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {\n /** The image source - can be an ipfs:// URI or regular URL */\n src: string\n}\n\n/**\n * An image component that automatically converts IPFS URIs to gateway URLs.\n * \n * @example\n * <IpfsImage src=\"ipfs://QmHash123\" alt=\"IPFS image\" />\n */\nexport const IpfsImage = ({ src, ...props }: IpfsImageProps) => {\n const finalSrc = ipfsToHttp(src) || src\n return <img src={finalSrc} {...props} />\n}\n\nexport default IpfsImage\n\n","import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'\n\nexport interface TornadoGraphDataPoint {\n /** The range identifier/name */\n name: number\n /** Vault balance for this range */\n vaultBalance?: number\n /** Counter vault balance for this range */\n counterVaultBalance?: number\n /** Aggregate vault balance (positive side) */\n aggregateVaultBalance: number\n /** Aggregate counter vault balance (negative side) */\n aggregateCounterVaultBalance: number\n}\n\nexport interface TriplePositionsTornadoMinGraphProps {\n /** Pre-formatted data for the tornado chart */\n data: TornadoGraphDataPoint[]\n /** Color for the \"FOR\" (vault) bars */\n forColor?: string\n /** Color for the \"AGAINST\" (counter vault) bars */\n againstColor?: string\n /** Additional className for the container */\n className?: string\n}\n\n/**\n * A minimal tornado/butterfly chart for displaying triple position distributions.\n * Shows FOR positions (green) on the right and AGAINST positions (red) on the left.\n * \n * @example\n * <TriplePositionsTornadoMinGraph \n * data={formattedRanges} \n * forColor=\"#31ad31\" \n * againstColor=\"#bc4535\" \n * />\n */\nexport const TriplePositionsTornadoMinGraph = ({\n data,\n forColor = '#31ad31',\n againstColor = '#bc4535',\n className,\n}: TriplePositionsTornadoMinGraphProps) => {\n if (!data || data.length === 0) {\n return null\n }\n\n return (\n <ResponsiveContainer width=\"100%\" height=\"100%\" className={className}>\n <BarChart\n data={data}\n stackOffset=\"sign\"\n layout=\"vertical\"\n barGap={0}\n barCategoryGap={0}\n >\n <XAxis type=\"number\" hide={true} />\n <YAxis dataKey=\"name\" type=\"number\" reversed hide={true} />\n <Bar dataKey=\"aggregateVaultBalance\" fill={forColor} stackId=\"stack\" />\n <Bar\n dataKey=\"aggregateCounterVaultBalance\"\n fill={againstColor}\n stackId=\"stack\"\n />\n </BarChart>\n </ResponsiveContainer>\n )\n}\n\nexport default TriplePositionsTornadoMinGraph\n\n","import {\n Area,\n AreaChart,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from 'recharts'\n\nexport interface TripleAreaChartDataPoint {\n /** The x-axis value (typically position value) */\n xValue: number\n /** Vault (FOR) position value */\n vault?: number\n /** Counter vault (AGAINST) position value */\n counterVault?: number\n}\n\nexport interface TripleAreaChartProps {\n /** Pre-formatted chart data points */\n chartData: TripleAreaChartDataPoint[]\n /** Display variant - 'min' hides axes/grid, 'max' shows full chart */\n variant?: 'min' | 'max'\n /** Color for vault (FOR) area */\n vaultColor?: string\n /** Color for counter vault (AGAINST) area */\n counterVaultColor?: string\n}\n\n/**\n * An area chart for visualizing triple position distributions.\n * Shows vault (FOR) positions on positive side and counter vault (AGAINST) on negative.\n * \n * @example\n * <TripleAreaChart \n * chartData={formattedPositions} \n * variant=\"min\" \n * />\n */\nexport const TripleAreaChart = ({\n chartData,\n variant = 'min',\n vaultColor = 'green',\n counterVaultColor = 'red',\n}: TripleAreaChartProps) => {\n // Clone data to avoid mutating props\n const data = [...chartData]\n \n const maxValue = Math.max(...data.map((d) => d.xValue))\n const minValue = Math.min(...data.map((d) => d.xValue))\n \n // Add padding points for better visualization\n if (maxValue > 0) {\n data.push({\n xValue: maxValue * 1.2,\n vault: maxValue,\n })\n }\n if (minValue < 0) {\n data.push({\n xValue: minValue * 1.2,\n counterVault: minValue * -1,\n })\n }\n \n data.sort((a, b) => a.xValue - b.xValue)\n const maxAbsValue = Math.max(...data.map((d) => Math.abs(d.xValue)))\n const domain = [maxAbsValue * -1, maxAbsValue]\n\n return (\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={data} layout=\"vertical\">\n {variant !== 'min' && (\n <>\n <CartesianGrid strokeDasharray=\"3 3\" />\n <Tooltip />\n </>\n )}\n <XAxis type=\"number\" hide={variant === 'min'} />\n <YAxis\n dataKey=\"xValue\"\n type=\"number\"\n reversed\n domain={domain}\n hide={variant === 'min'}\n />\n <Area\n type=\"stepBefore\"\n dataKey=\"vault\"\n stroke={vaultColor}\n fill={vaultColor}\n fillOpacity={0.5}\n />\n <Area\n type=\"stepAfter\"\n dataKey=\"counterVault\"\n stroke={counterVaultColor}\n fill={counterVaultColor}\n fillOpacity={0.5}\n />\n </AreaChart>\n </ResponsiveContainer>\n )\n}\n\nexport default TripleAreaChart\n\n","import { cn } from '../utils/cn'\n\nexport interface ErrorBannerProps {\n /** The error message to display */\n error: string\n /** Optional HTTP status code */\n code?: number\n /** Additional CSS classes */\n className?: string\n}\n\n/**\n * A simple error banner component for displaying error messages.\n * Centered layout with large heading and subtitle.\n * \n * @example\n * <ErrorBanner error=\"Page not found\" code={404} />\n */\nexport const ErrorBanner = ({ \n error, \n code, \n className \n}: ErrorBannerProps) => {\n return (\n <div className={cn(\"flex flex-col gap-2 mt-12\", className)}>\n <h2 className=\"text-center text-2xl font-bold mb-8\">\n {code ? `${code} Error` : 'Error'}\n </h2>\n <p className=\"text-center text-lg text-gray-500\">{error}</p>\n </div>\n )\n}\n\nexport default ErrorBanner\n\n","import * as React from 'react'\nimport { cn } from '../utils/cn'\nimport { Tooltip, TooltipTrigger, TooltipContent } from './ui/tooltip'\nimport { formatCryptoAmount } from '../utils/formatting'\n\n/**\n * Display variant for the CryptoAmount component.\n * \n * - `default`: Crypto on top, fiat below (stacked)\n * - `inline`: Single line with fiat in parentheses\n * - `crypto-only`: Just the native token amount\n * - `fiat-only`: Just the fiat equivalent (useful for previews)\n * - `compact`: Abbreviated format for large amounts (1.5M TRUST)\n */\nexport type CryptoAmountVariant = 'default' | 'inline' | 'crypto-only' | 'fiat-only' | 'compact'\n\n/**\n * Size variant for the CryptoAmount component.\n */\nexport type CryptoAmountSize = 'sm' | 'md' | 'lg'\n\nexport interface CryptoAmountProps {\n /** \n * The crypto amount. By default, expects wei/smallest unit (string or bigint).\n * If `isTokenUnits` is true, expects the value already in token units (e.g., 1.5 for 1.5 TRUST).\n */\n value: string | bigint | number\n \n /** Number of decimals for the token (default: 18). Ignored when isTokenUnits is true. */\n decimals?: number\n \n /**\n * When true, the value is already in token units (e.g., 1.5 for 1.5 TRUST).\n * When false (default), the value is in wei/smallest units and will be converted.\n */\n isTokenUnits?: boolean\n \n /** Token symbol to display (e.g., 'TRUST', 'ETH') */\n symbol: string\n \n /**\n * Exchange rate for fiat conversion (1 token = X fiat).\n * If undefined/null, fiat display is hidden.\n */\n exchangeRate?: number | null\n \n /**\n * Fiat currency code for formatting (default: 'USD').\n * Used with Intl.NumberFormat for proper currency display.\n */\n currencyCode?: string\n \n /**\n * Locale for number/currency formatting (default: browser locale or 'en-US')\n */\n locale?: string\n \n /** Display variant (default: 'default') */\n variant?: CryptoAmountVariant\n \n /** Size variant (default: 'md') */\n size?: CryptoAmountSize\n \n /**\n * Maximum significant digits before truncation (default: 10).\n * Amounts exceeding this will show \"...\" with full value in tooltip.\n */\n maxSignificantDigits?: number\n \n /**\n * Maximum decimal places to display (default: 6).\n * Applied after conversion but before significant digit truncation.\n */\n maxDecimals?: number\n \n /** Whether to show the token symbol (default: true) */\n showSymbol?: boolean\n \n /** Whether to show fiat value when exchange rate is available (default: true) */\n showFiat?: boolean\n \n /** Additional CSS classes for the container */\n className?: string\n \n /** Additional CSS classes for the crypto amount text */\n cryptoClassName?: string\n \n /** Additional CSS classes for the fiat amount text */\n fiatClassName?: string\n}\n\n/**\n * A unified component for displaying cryptocurrency amounts with optional fiat conversion.\n * \n * Features:\n * - Crypto-first display (native token amount is primary)\n * - Optional fiat conversion shown as secondary\n * - Truncation with tooltip for large amounts\n * - Multiple display variants (stacked, inline, compact)\n * - Proper number formatting based on locale\n * \n * @example\n * ```tsx\n * // Basic usage - stacked display\n * <CryptoAmount \n * value=\"1500000000000000000\" \n * symbol=\"TRUST\" \n * exchangeRate={0.42} \n * />\n * // Renders:\n * // 1.5 TRUST\n * // $0.63\n * \n * // Inline variant\n * <CryptoAmount \n * value=\"1500000000000000000\" \n * symbol=\"TRUST\" \n * exchangeRate={0.42}\n * variant=\"inline\"\n * />\n * // Renders: 1.5 TRUST (~$0.63)\n * \n * // Compact variant for large amounts\n * <CryptoAmount \n * value=\"1500000000000000000000000\" \n * symbol=\"TRUST\"\n * variant=\"compact\"\n * />\n * // Renders: 1.5M TRUST\n * ```\n */\nexport function CryptoAmount({\n value,\n decimals = 18,\n isTokenUnits = false,\n symbol,\n exchangeRate,\n currencyCode = 'USD',\n locale,\n variant = 'default',\n size = 'md',\n maxSignificantDigits = 10,\n maxDecimals = 6,\n showSymbol = true,\n showFiat = true,\n className,\n cryptoClassName,\n fiatClassName,\n}: CryptoAmountProps) {\n // Determine locale\n const effectiveLocale = locale || (typeof navigator !== 'undefined' ? navigator.language : 'en-US')\n \n // Format the crypto amount\n const formatted = React.useMemo(() => {\n // If value is already in token units, create a simple formatted result\n if (isTokenUnits) {\n const numericValue = typeof value === 'string' ? parseFloat(value) : Number(value)\n const displayValue = numericValue.toString()\n return {\n display: displayValue,\n fullValue: displayValue,\n isTruncated: false,\n numericValue,\n }\n }\n \n // Otherwise, convert from wei\n // Ensure value is string or bigint for formatCryptoAmount\n const weiValue = typeof value === 'number' ? value.toString() : value\n return formatCryptoAmount(weiValue, decimals, {\n maxSignificantDigits,\n maxDecimals,\n compact: variant === 'compact',\n locale: effectiveLocale,\n })\n }, [value, decimals, isTokenUnits, maxSignificantDigits, maxDecimals, variant, effectiveLocale])\n \n // Calculate fiat value if exchange rate is available\n const fiatFormatted = React.useMemo(() => {\n if (!showFiat || exchangeRate == null || variant === 'crypto-only') {\n return null\n }\n \n const fiatValue = formatted.numericValue * exchangeRate\n \n try {\n return new Intl.NumberFormat(effectiveLocale, {\n style: 'currency',\n currency: currencyCode.toUpperCase(),\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(fiatValue)\n } catch {\n // Fallback if currency code is invalid\n return `$${fiatValue.toFixed(2)}`\n }\n }, [formatted.numericValue, exchangeRate, currencyCode, effectiveLocale, showFiat, variant])\n \n // Size-based styling\n const sizeStyles = {\n sm: {\n crypto: 'text-sm',\n fiat: 'text-xs',\n },\n md: {\n crypto: 'text-base',\n fiat: 'text-sm',\n },\n lg: {\n crypto: 'text-lg font-semibold',\n fiat: 'text-base',\n },\n }\n \n const styles = sizeStyles[size]\n \n // Build the crypto display string\n const cryptoDisplay = showSymbol \n ? `${formatted.display} ${symbol}`\n : formatted.display\n \n // Full value for tooltip (when truncated)\n const fullCryptoDisplay = showSymbol\n ? `${formatted.fullValue} ${symbol}`\n : formatted.fullValue\n \n // Determine if we need a tooltip\n const needsTooltip = formatted.isTruncated\n \n // Render the crypto amount (with or without tooltip)\n const cryptoElement = (\n <span \n className={cn(\n styles.crypto,\n 'font-medium',\n cryptoClassName\n )}\n >\n {cryptoDisplay}\n </span>\n )\n \n const cryptoWithTooltip = needsTooltip ? (\n <Tooltip delayDuration={1000}>\n <TooltipTrigger asChild>\n {cryptoElement}\n </TooltipTrigger>\n <TooltipContent>\n <span className=\"font-mono text-xs\">{fullCryptoDisplay}</span>\n </TooltipContent>\n </Tooltip>\n ) : cryptoElement\n \n // Render based on variant\n if (variant === 'crypto-only') {\n return (\n <div className={cn('flex items-center', className)}>\n {cryptoWithTooltip}\n </div>\n )\n }\n \n if (variant === 'fiat-only') {\n // Only show fiat, return null if no exchange rate available\n if (!fiatFormatted) {\n return null\n }\n return (\n <span className={cn(\n styles.fiat,\n 'text-muted-foreground',\n fiatClassName,\n className\n )}>\n ≈ {fiatFormatted}\n </span>\n )\n }\n \n if (variant === 'inline') {\n return (\n <div className={cn('flex items-center gap-1', className)}>\n {cryptoWithTooltip}\n {fiatFormatted && (\n <span className={cn(\n styles.fiat,\n 'text-muted-foreground',\n fiatClassName\n )}>\n (~{fiatFormatted})\n </span>\n )}\n </div>\n )\n }\n \n if (variant === 'compact') {\n return (\n <div className={cn('flex items-center gap-1', className)}>\n {cryptoWithTooltip}\n {fiatFormatted && (\n <span className={cn(\n styles.fiat,\n 'text-muted-foreground',\n fiatClassName\n )}>\n ({fiatFormatted})\n </span>\n )}\n </div>\n )\n }\n \n // Default: stacked layout\n return (\n <div className={cn('flex flex-col', className)}>\n {cryptoWithTooltip}\n {fiatFormatted && (\n <span className={cn(\n styles.fiat,\n 'text-muted-foreground',\n fiatClassName\n )}>\n {fiatFormatted}\n </span>\n )}\n </div>\n )\n}\n\nexport default CryptoAmount\n\n// ============================================================================\n// Helper utilities for creating CryptoAmount props\n// ============================================================================\n\n/**\n * Props subset for creating a CryptoAmount from an exchange rate context.\n * Use this type when building a hook wrapper in your app.\n */\nexport type CryptoAmountRateProps = Pick<\n CryptoAmountProps,\n 'exchangeRate' | 'currencyCode' | 'locale'\n>\n\n/**\n * Creates CryptoAmountRateProps from an exchange rate and currency settings.\n * This is a helper for building hook wrappers in consuming applications.\n * \n * @param exchangeRate - The current exchange rate (1 token = X fiat), or null/undefined\n * @param currencyCode - The fiat currency code (e.g., 'USD', 'EUR')\n * @param locale - Optional locale for formatting\n * @returns Props object to spread onto CryptoAmount\n * \n * @example\n * ```tsx\n * // In your app's hook wrapper:\n * export function useCryptoAmountProps() {\n * const { data } = useExchangeRates()\n * const { selectedCurrency } = useCurrency()\n * \n * return createCryptoAmountRateProps(\n * data?.rate,\n * selectedCurrency,\n * navigator.language\n * )\n * }\n * \n * // Then in components:\n * const rateProps = useCryptoAmountProps()\n * <CryptoAmount value=\"1000000000000000000\" symbol=\"TRUST\" {...rateProps} />\n * ```\n */\nexport function createCryptoAmountRateProps(\n exchangeRate: number | null | undefined,\n currencyCode: string = 'USD',\n locale?: string\n): CryptoAmountRateProps {\n return {\n exchangeRate: exchangeRate ?? undefined,\n currencyCode,\n locale,\n }\n}\n\n"]}
|