@luxfi/ui 7.4.11 → 7.4.13
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/dist/alert.cjs +68 -46
- package/dist/alert.js +69 -46
- package/dist/avatar.cjs +49 -47
- package/dist/avatar.js +52 -49
- package/dist/badge.cjs +82 -49
- package/dist/badge.js +83 -50
- package/dist/checkbox.cjs +42 -75
- package/dist/checkbox.js +43 -76
- package/dist/chrome.cjs +3 -7
- package/dist/chrome.js +3 -7
- package/dist/close-button.cjs +3 -7
- package/dist/close-button.js +3 -7
- package/dist/dialog.cjs +3 -7
- package/dist/dialog.js +3 -7
- package/dist/drawer.cjs +3 -7
- package/dist/drawer.js +3 -7
- package/dist/empty-state.cjs +13 -13
- package/dist/empty-state.js +13 -13
- package/dist/expiration-selector.cjs +58 -63
- package/dist/expiration-selector.js +58 -63
- package/dist/greeks-display.cjs +48 -39
- package/dist/greeks-display.js +48 -39
- package/dist/index.cjs +1053 -888
- package/dist/index.js +1054 -889
- package/dist/input-group.cjs +36 -19
- package/dist/input-group.js +37 -19
- package/dist/option-chain.cjs +89 -67
- package/dist/option-chain.js +89 -67
- package/dist/option-position.cjs +88 -85
- package/dist/option-position.js +88 -85
- package/dist/pin-input.cjs +30 -29
- package/dist/pin-input.js +30 -29
- package/dist/pnl-diagram.cjs +20 -20
- package/dist/pnl-diagram.js +20 -20
- package/dist/popover.cjs +3 -7
- package/dist/popover.js +3 -7
- package/dist/progress-circle.cjs +40 -22
- package/dist/progress-circle.d.cts +5 -5
- package/dist/progress-circle.d.ts +5 -5
- package/dist/progress-circle.js +41 -22
- package/dist/progress.cjs +15 -22
- package/dist/progress.d.cts +6 -6
- package/dist/progress.d.ts +6 -6
- package/dist/progress.js +15 -22
- package/dist/radio.cjs +35 -68
- package/dist/radio.d.cts +18 -18
- package/dist/radio.d.ts +18 -18
- package/dist/radio.js +36 -69
- package/dist/rating.cjs +15 -17
- package/dist/rating.js +15 -17
- package/dist/slider.cjs +33 -50
- package/dist/slider.js +34 -51
- package/dist/strategy-builder.cjs +194 -115
- package/dist/strategy-builder.js +194 -115
- package/dist/switch.cjs +48 -55
- package/dist/switch.js +49 -56
- package/dist/tag.cjs +115 -69
- package/dist/tag.js +116 -69
- package/dist/tooltip.cjs +28 -17
- package/dist/tooltip.js +30 -18
- package/package.json +1 -1
- package/src/alert.tsx +78 -45
- package/src/avatar.tsx +54 -38
- package/src/badge.tsx +65 -44
- package/src/checkbox.tsx +70 -89
- package/src/close-button.tsx +3 -8
- package/src/empty-state.tsx +21 -17
- package/src/expiration-selector.tsx +84 -66
- package/src/greeks-display.tsx +59 -51
- package/src/input-group.tsx +38 -24
- package/src/option-chain.tsx +154 -104
- package/src/option-position.tsx +143 -114
- package/src/pin-input.tsx +37 -29
- package/src/pnl-diagram.tsx +36 -28
- package/src/progress-circle.tsx +52 -28
- package/src/progress.tsx +17 -21
- package/src/radio.tsx +56 -76
- package/src/rating.tsx +22 -20
- package/src/slider.tsx +43 -45
- package/src/strategy-builder.tsx +260 -162
- package/src/switch.tsx +63 -64
- package/src/tag.tsx +89 -51
- package/src/tooltip.tsx +21 -13
- package/tokens.css +18 -0
package/src/option-chain.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { Text, View } from '@hanzo/gui';
|
|
3
4
|
import * as React from 'react';
|
|
4
5
|
|
|
5
|
-
import { cn } from './utils';
|
|
6
|
-
|
|
7
6
|
// ---------------------------------------------------------------------------
|
|
8
7
|
// Types
|
|
9
8
|
// ---------------------------------------------------------------------------
|
|
@@ -57,6 +56,12 @@ function fmtPct(n: number | undefined | null): string {
|
|
|
57
56
|
return `${(n * 100).toFixed(1)}%`;
|
|
58
57
|
}
|
|
59
58
|
|
|
59
|
+
// A cell's own text is centred/right/left via flex, not text-align: the host
|
|
60
|
+
// is a <td> (unstyled, so it keeps native table-cell layout) and the content
|
|
61
|
+
// is a gui Text, which has no cascading effect from a parent's CSS text-align.
|
|
62
|
+
const justify = (align: 'left' | 'right' | 'center'): 'flex-start' | 'flex-end' | 'center' =>
|
|
63
|
+
align === 'right' ? 'flex-end' : align === 'left' ? 'flex-start' : 'center';
|
|
64
|
+
|
|
60
65
|
// ---------------------------------------------------------------------------
|
|
61
66
|
// QuoteCell — clickable cell in the option chain
|
|
62
67
|
// ---------------------------------------------------------------------------
|
|
@@ -71,19 +76,64 @@ interface QuoteCellProps {
|
|
|
71
76
|
|
|
72
77
|
function QuoteCell({ value, highlight, align = 'right', onClick, className }: QuoteCellProps) {
|
|
73
78
|
return (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)}
|
|
83
|
-
|
|
79
|
+
<View
|
|
80
|
+
unstyled
|
|
81
|
+
render={ <td/> }
|
|
82
|
+
onClick={ onClick }
|
|
83
|
+
paddingHorizontal={ 6 }
|
|
84
|
+
paddingVertical={ 4 }
|
|
85
|
+
cursor={ onClick ? 'pointer' : undefined }
|
|
86
|
+
transition="quick"
|
|
87
|
+
hoverStyle={ onClick ? ({ backgroundColor: 'var(--color-popover-item-hover)' } as never) : undefined }
|
|
88
|
+
style={ { display: 'flex', alignItems: 'center', justifyContent: justify(align) } as never }
|
|
89
|
+
className={ className }
|
|
84
90
|
>
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
<Text
|
|
92
|
+
fontSize={ 12 }
|
|
93
|
+
style={ { fontFamily: 'monospace', whiteSpace: 'nowrap' } as never }
|
|
94
|
+
fontVariant={ ['tabular-nums'] as never }
|
|
95
|
+
color={ (highlight ? 'var(--color-text-primary)' : 'var(--color-text-secondary)') as never }
|
|
96
|
+
>
|
|
97
|
+
{ value }
|
|
98
|
+
</Text>
|
|
99
|
+
</View>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// HeadCell — <th> in either header row
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
interface HeadCellProps {
|
|
108
|
+
readonly children?: React.ReactNode;
|
|
109
|
+
readonly colSpan?: number;
|
|
110
|
+
readonly align?: 'left' | 'right' | 'center';
|
|
111
|
+
readonly size?: 'group' | 'column';
|
|
112
|
+
readonly color?: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function HeadCell({ children, colSpan, align = 'center', size = 'column', color }: HeadCellProps) {
|
|
116
|
+
return (
|
|
117
|
+
<View
|
|
118
|
+
unstyled
|
|
119
|
+
render={ colSpan ? <th colSpan={ colSpan }/> : <th/> }
|
|
120
|
+
paddingHorizontal={ size === 'group' ? 8 : 6 }
|
|
121
|
+
paddingVertical={ size === 'group' ? 6 : 4 }
|
|
122
|
+
style={ { display: 'flex', alignItems: 'center', justifyContent: justify(align) } as never }
|
|
123
|
+
>
|
|
124
|
+
{ children != null && (
|
|
125
|
+
<Text
|
|
126
|
+
fontSize={ 10 }
|
|
127
|
+
fontWeight={ size === 'group' ? '600' : '500' }
|
|
128
|
+
textTransform="uppercase"
|
|
129
|
+
letterSpacing={ 0.5 }
|
|
130
|
+
style={ { whiteSpace: 'nowrap' } as never }
|
|
131
|
+
color={ (color ?? 'var(--color-text-secondary)') as never }
|
|
132
|
+
>
|
|
133
|
+
{ children }
|
|
134
|
+
</Text>
|
|
135
|
+
) }
|
|
136
|
+
</View>
|
|
87
137
|
);
|
|
88
138
|
}
|
|
89
139
|
|
|
@@ -133,58 +183,41 @@ export const OptionChain = React.forwardRef<HTMLDivElement, OptionChainProps>(
|
|
|
133
183
|
);
|
|
134
184
|
|
|
135
185
|
return (
|
|
136
|
-
<
|
|
137
|
-
ref={ref}
|
|
138
|
-
|
|
139
|
-
|
|
186
|
+
<View
|
|
187
|
+
ref={ ref as never }
|
|
188
|
+
unstyled
|
|
189
|
+
width="100%"
|
|
190
|
+
overflowX="auto"
|
|
191
|
+
className={ className }
|
|
192
|
+
{ ...(rest as object) }
|
|
140
193
|
>
|
|
141
|
-
<table
|
|
142
|
-
<thead>
|
|
143
|
-
<tr
|
|
194
|
+
<View unstyled render={ <table/> } width="100%" style={ { borderCollapse: 'collapse' } as never }>
|
|
195
|
+
<View unstyled render={ <thead/> }>
|
|
196
|
+
<View unstyled render={ <tr/> } borderBottomWidth={ 1 } borderColor={ 'var(--color-border-divider)' as never }>
|
|
144
197
|
{/* Call headers */}
|
|
145
|
-
<
|
|
146
|
-
colSpan={CALL_HEADERS.length}
|
|
147
|
-
className="px-2 py-1.5 text-center text-[10px] font-semibold uppercase tracking-wider text-emerald-400"
|
|
148
|
-
>
|
|
149
|
-
Calls
|
|
150
|
-
</th>
|
|
198
|
+
<HeadCell colSpan={ CALL_HEADERS.length } size="group" color="var(--color-status-good)">Calls</HeadCell>
|
|
151
199
|
{/* Strike header */}
|
|
152
|
-
<
|
|
153
|
-
Strike
|
|
154
|
-
</th>
|
|
200
|
+
<HeadCell size="group">Strike</HeadCell>
|
|
155
201
|
{/* Put headers */}
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
className="px-1.5 py-1 text-right text-[10px] font-medium uppercase tracking-wider text-zinc-500"
|
|
168
|
-
>
|
|
169
|
-
{h}
|
|
170
|
-
</th>
|
|
171
|
-
))}
|
|
172
|
-
<th className="px-2 py-1 text-center text-[10px] font-medium uppercase tracking-wider text-zinc-500" />
|
|
173
|
-
{PUT_HEADERS.map((h) => (
|
|
174
|
-
<th
|
|
175
|
-
key={`put-${h}`}
|
|
176
|
-
className={cn(
|
|
177
|
-
'px-1.5 py-1 text-[10px] font-medium uppercase tracking-wider text-zinc-500',
|
|
178
|
-
h === 'Bid' || h === 'Ask' || h === 'Last' ? 'text-left' : 'text-right',
|
|
179
|
-
)}
|
|
202
|
+
<HeadCell colSpan={ PUT_HEADERS.length } size="group" color="var(--color-status-bad)">Puts</HeadCell>
|
|
203
|
+
</View>
|
|
204
|
+
<View unstyled render={ <tr/> } borderBottomWidth={ 1 } borderColor={ 'var(--color-popover-item-hover)' as never }>
|
|
205
|
+
{ CALL_HEADERS.map((h) => (
|
|
206
|
+
<HeadCell key={ `call-${ h }` } align="right">{ h }</HeadCell>
|
|
207
|
+
)) }
|
|
208
|
+
<HeadCell/>
|
|
209
|
+
{ PUT_HEADERS.map((h) => (
|
|
210
|
+
<HeadCell
|
|
211
|
+
key={ `put-${ h }` }
|
|
212
|
+
align={ h === 'Bid' || h === 'Ask' || h === 'Last' ? 'left' : 'right' }
|
|
180
213
|
>
|
|
181
|
-
{h}
|
|
182
|
-
</
|
|
183
|
-
))}
|
|
184
|
-
</
|
|
185
|
-
</
|
|
186
|
-
<tbody>
|
|
187
|
-
{strikes.map((row) => {
|
|
214
|
+
{ h }
|
|
215
|
+
</HeadCell>
|
|
216
|
+
)) }
|
|
217
|
+
</View>
|
|
218
|
+
</View>
|
|
219
|
+
<View unstyled render={ <tbody/> }>
|
|
220
|
+
{ strikes.map((row) => {
|
|
188
221
|
const isATM = Math.abs(row.strike - spotPrice) <=
|
|
189
222
|
(strikes.length > 1
|
|
190
223
|
? Math.abs((strikes[1]?.strike ?? 0) - (strikes[0]?.strike ?? 0)) / 2
|
|
@@ -194,68 +227,85 @@ export const OptionChain = React.forwardRef<HTMLDivElement, OptionChainProps>(
|
|
|
194
227
|
const c = row.call;
|
|
195
228
|
const p = row.put;
|
|
196
229
|
|
|
230
|
+
const rowBg = isATM
|
|
231
|
+
? 'var(--secondary)'
|
|
232
|
+
: callITM
|
|
233
|
+
? 'color-mix(in srgb, var(--color-status-good) 12%, transparent)'
|
|
234
|
+
: putITM
|
|
235
|
+
? 'color-mix(in srgb, var(--color-status-bad) 12%, transparent)'
|
|
236
|
+
: 'transparent';
|
|
237
|
+
|
|
197
238
|
return (
|
|
198
|
-
<
|
|
199
|
-
key={row.strike}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
)}
|
|
239
|
+
<View
|
|
240
|
+
key={ row.strike }
|
|
241
|
+
unstyled
|
|
242
|
+
render={ <tr/> }
|
|
243
|
+
borderBottomWidth={ 1 }
|
|
244
|
+
borderColor={ 'color-mix(in srgb, var(--color-border-divider) 50%, transparent)' as never }
|
|
245
|
+
transition="quick"
|
|
246
|
+
backgroundColor={ rowBg as never }
|
|
247
|
+
hoverStyle={ isATM || callITM || putITM ? undefined : ({ backgroundColor: 'var(--color-popover-item-hover)' } as never) }
|
|
207
248
|
>
|
|
208
249
|
{/* Call side */}
|
|
209
250
|
<QuoteCell
|
|
210
|
-
value={fmt(c?.bid)}
|
|
211
|
-
highlight={!!c}
|
|
212
|
-
onClick={() => handleClick(c, 'call', row.strike)}
|
|
251
|
+
value={ fmt(c?.bid) }
|
|
252
|
+
highlight={ !!c }
|
|
253
|
+
onClick={ () => handleClick(c, 'call', row.strike) }
|
|
213
254
|
/>
|
|
214
255
|
<QuoteCell
|
|
215
|
-
value={fmt(c?.ask)}
|
|
216
|
-
highlight={!!c}
|
|
217
|
-
onClick={() => handleClick(c, 'call', row.strike)}
|
|
256
|
+
value={ fmt(c?.ask) }
|
|
257
|
+
highlight={ !!c }
|
|
258
|
+
onClick={ () => handleClick(c, 'call', row.strike) }
|
|
218
259
|
/>
|
|
219
|
-
<QuoteCell value={fmt(c?.last)} highlight={!!c}
|
|
220
|
-
<QuoteCell value={fmtInt(c?.volume)}
|
|
221
|
-
<QuoteCell value={fmtInt(c?.openInterest)}
|
|
222
|
-
<QuoteCell value={fmtPct(c?.iv)}
|
|
223
|
-
<QuoteCell value={fmt(c?.delta, 3)}
|
|
260
|
+
<QuoteCell value={ fmt(c?.last) } highlight={ !!c }/>
|
|
261
|
+
<QuoteCell value={ fmtInt(c?.volume) }/>
|
|
262
|
+
<QuoteCell value={ fmtInt(c?.openInterest) }/>
|
|
263
|
+
<QuoteCell value={ fmtPct(c?.iv) }/>
|
|
264
|
+
<QuoteCell value={ fmt(c?.delta, 3) }/>
|
|
224
265
|
|
|
225
266
|
{/* Strike */}
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
267
|
+
<View
|
|
268
|
+
unstyled
|
|
269
|
+
render={ <td/> }
|
|
270
|
+
paddingHorizontal={ 8 }
|
|
271
|
+
paddingVertical={ 4 }
|
|
272
|
+
style={ { display: 'flex', alignItems: 'center', justifyContent: 'center' } as never }
|
|
231
273
|
>
|
|
232
|
-
|
|
233
|
-
|
|
274
|
+
<Text
|
|
275
|
+
fontSize={ 12 }
|
|
276
|
+
fontWeight="600"
|
|
277
|
+
style={ { fontFamily: 'monospace' } as never }
|
|
278
|
+
fontVariant={ ['tabular-nums'] as never }
|
|
279
|
+
color={ (isATM ? 'var(--foreground)' : 'var(--muted-foreground)') as never }
|
|
280
|
+
>
|
|
281
|
+
{ fmt(row.strike) }
|
|
282
|
+
</Text>
|
|
283
|
+
</View>
|
|
234
284
|
|
|
235
285
|
{/* Put side (mirrored order) */}
|
|
236
|
-
<QuoteCell value={fmt(p?.delta, 3)} align="right"
|
|
237
|
-
<QuoteCell value={fmtPct(p?.iv)} align="right"
|
|
238
|
-
<QuoteCell value={fmtInt(p?.openInterest)} align="right"
|
|
239
|
-
<QuoteCell value={fmtInt(p?.volume)} align="right"
|
|
240
|
-
<QuoteCell value={fmt(p?.last)} align="left" highlight={!!p}
|
|
286
|
+
<QuoteCell value={ fmt(p?.delta, 3) } align="right"/>
|
|
287
|
+
<QuoteCell value={ fmtPct(p?.iv) } align="right"/>
|
|
288
|
+
<QuoteCell value={ fmtInt(p?.openInterest) } align="right"/>
|
|
289
|
+
<QuoteCell value={ fmtInt(p?.volume) } align="right"/>
|
|
290
|
+
<QuoteCell value={ fmt(p?.last) } align="left" highlight={ !!p }/>
|
|
241
291
|
<QuoteCell
|
|
242
|
-
value={fmt(p?.ask)}
|
|
292
|
+
value={ fmt(p?.ask) }
|
|
243
293
|
align="left"
|
|
244
|
-
highlight={!!p}
|
|
245
|
-
onClick={() => handleClick(p, 'put', row.strike)}
|
|
294
|
+
highlight={ !!p }
|
|
295
|
+
onClick={ () => handleClick(p, 'put', row.strike) }
|
|
246
296
|
/>
|
|
247
297
|
<QuoteCell
|
|
248
|
-
value={fmt(p?.bid)}
|
|
298
|
+
value={ fmt(p?.bid) }
|
|
249
299
|
align="left"
|
|
250
|
-
highlight={!!p}
|
|
251
|
-
onClick={() => handleClick(p, 'put', row.strike)}
|
|
300
|
+
highlight={ !!p }
|
|
301
|
+
onClick={ () => handleClick(p, 'put', row.strike) }
|
|
252
302
|
/>
|
|
253
|
-
</
|
|
303
|
+
</View>
|
|
254
304
|
);
|
|
255
|
-
})}
|
|
256
|
-
</
|
|
257
|
-
</
|
|
258
|
-
</
|
|
305
|
+
}) }
|
|
306
|
+
</View>
|
|
307
|
+
</View>
|
|
308
|
+
</View>
|
|
259
309
|
);
|
|
260
310
|
},
|
|
261
311
|
);
|
package/src/option-position.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
3
4
|
import * as React from 'react';
|
|
4
5
|
|
|
5
|
-
import { cn } from './utils';
|
|
6
|
-
|
|
7
6
|
// ---------------------------------------------------------------------------
|
|
8
7
|
// Types
|
|
9
8
|
// ---------------------------------------------------------------------------
|
|
@@ -43,6 +42,8 @@ function formatDate(date: string): string {
|
|
|
43
42
|
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
const MONO = { fontFamily: 'monospace' } as never;
|
|
46
|
+
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
48
|
// OptionPositionCard
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
@@ -61,119 +62,149 @@ export const OptionPositionCard = React.forwardRef<HTMLDivElement, OptionPositio
|
|
|
61
62
|
const isLong = position.quantity > 0;
|
|
62
63
|
const isCall = position.type === 'call';
|
|
63
64
|
const isProfitable = position.pnl >= 0;
|
|
65
|
+
const pnlColor = (isProfitable ? 'var(--color-status-good)' : 'var(--color-status-bad)') as never;
|
|
64
66
|
|
|
65
67
|
return (
|
|
66
|
-
<
|
|
67
|
-
ref={ref}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)}
|
|
72
|
-
{
|
|
68
|
+
<YStack
|
|
69
|
+
ref={ ref as never }
|
|
70
|
+
gap={ 8 }
|
|
71
|
+
borderRadius={ 8 }
|
|
72
|
+
borderWidth={ 1 }
|
|
73
|
+
borderColor={ 'var(--color-border-divider)' as never }
|
|
74
|
+
backgroundColor={ 'var(--card)' as never }
|
|
75
|
+
padding={ 12 }
|
|
76
|
+
className={ className }
|
|
77
|
+
{ ...(rest as object) }
|
|
73
78
|
>
|
|
74
79
|
{/* Header row: symbol badge + quantity */}
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
{/* Symbol badge */}
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
<XStack alignItems="center" justifyContent="space-between">
|
|
81
|
+
<XStack alignItems="center" gap={ 8 }>
|
|
82
|
+
{/* Symbol badge. Call vs put is a rank difference, not a hue difference. */}
|
|
83
|
+
<XStack
|
|
84
|
+
alignItems="center"
|
|
85
|
+
gap={ 4 }
|
|
86
|
+
borderRadius={ 4 }
|
|
87
|
+
paddingHorizontal={ 6 }
|
|
88
|
+
paddingVertical={ 2 }
|
|
89
|
+
backgroundColor={ (isCall ? 'var(--secondary)' : 'var(--muted)') as never }
|
|
90
|
+
>
|
|
91
|
+
<Text fontSize={ 12 } fontWeight="600" color={ (isCall ? 'var(--foreground)' : 'var(--muted-foreground)') as never }>
|
|
92
|
+
{ position.underlying }
|
|
93
|
+
</Text>
|
|
94
|
+
<Text fontSize={ 12 } fontWeight="600" style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ (isCall ? 'var(--foreground)' : 'var(--muted-foreground)') as never }>
|
|
95
|
+
{ position.strike }
|
|
96
|
+
</Text>
|
|
97
|
+
<Text fontSize={ 12 } fontWeight="600" textTransform="uppercase" color={ (isCall ? 'var(--foreground)' : 'var(--muted-foreground)') as never }>
|
|
98
|
+
{ position.type === 'call' ? 'C' : 'P' }
|
|
99
|
+
</Text>
|
|
100
|
+
<Text fontSize={ 12 } fontWeight="400" color={ 'var(--color-text-secondary)' as never }>
|
|
101
|
+
{ formatDate(position.expiration) }
|
|
102
|
+
</Text>
|
|
103
|
+
</XStack>
|
|
104
|
+
</XStack>
|
|
89
105
|
|
|
90
106
|
{/* Quantity */}
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
</
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{isProfitable ? '+' : ''}{position.pnl.toFixed(2)}
|
|
121
|
-
</
|
|
122
|
-
<
|
|
123
|
-
'
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<GreekPill label=
|
|
135
|
-
<GreekPill label=
|
|
136
|
-
|
|
137
|
-
<GreekPill label={'\u03BD'} value={position.greeks.vega} color="text-emerald-400" />
|
|
138
|
-
</div>
|
|
107
|
+
<Text
|
|
108
|
+
fontSize={ 12 }
|
|
109
|
+
fontWeight="600"
|
|
110
|
+
style={ MONO }
|
|
111
|
+
fontVariant={ ['tabular-nums'] as never }
|
|
112
|
+
color={ (isLong ? 'var(--color-status-good)' : 'var(--color-status-bad)') as never }
|
|
113
|
+
>
|
|
114
|
+
{ isLong ? '+' : '' }{ position.quantity }
|
|
115
|
+
</Text>
|
|
116
|
+
</XStack>
|
|
117
|
+
|
|
118
|
+
{/* Values row (3 equal columns) */}
|
|
119
|
+
<XStack gap={ 12 }>
|
|
120
|
+
<YStack flex={ 1 } gap={ 2 }>
|
|
121
|
+
<Text fontSize={ 10 } textTransform="uppercase" letterSpacing={ 0.5 } color={ 'var(--color-text-secondary)' as never }>Avg Cost</Text>
|
|
122
|
+
<Text fontSize={ 12 } style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ 'var(--color-text-primary)' as never }>
|
|
123
|
+
${ position.avgCost.toFixed(2) }
|
|
124
|
+
</Text>
|
|
125
|
+
</YStack>
|
|
126
|
+
<YStack flex={ 1 } gap={ 2 }>
|
|
127
|
+
<Text fontSize={ 10 } textTransform="uppercase" letterSpacing={ 0.5 } color={ 'var(--color-text-secondary)' as never }>Value</Text>
|
|
128
|
+
<Text fontSize={ 12 } style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ 'var(--color-text-primary)' as never }>
|
|
129
|
+
${ position.currentValue.toFixed(2) }
|
|
130
|
+
</Text>
|
|
131
|
+
</YStack>
|
|
132
|
+
<YStack flex={ 1 } gap={ 2 } alignItems="flex-end">
|
|
133
|
+
<Text fontSize={ 10 } textTransform="uppercase" letterSpacing={ 0.5 } color={ 'var(--color-text-secondary)' as never }>P/L</Text>
|
|
134
|
+
<XStack alignItems="center" gap={ 4 }>
|
|
135
|
+
<Text fontSize={ 12 } fontWeight="600" style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ pnlColor }>
|
|
136
|
+
{ isProfitable ? '+' : '' }{ position.pnl.toFixed(2) }
|
|
137
|
+
</Text>
|
|
138
|
+
<Text fontSize={ 10 } style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ pnlColor } opacity={ 0.7 }>
|
|
139
|
+
({ isProfitable ? '+' : '' }{ position.pnlPercent.toFixed(1) }%)
|
|
140
|
+
</Text>
|
|
141
|
+
</XStack>
|
|
142
|
+
</YStack>
|
|
143
|
+
</XStack>
|
|
144
|
+
|
|
145
|
+
{/* Greeks row — same achromatic ramp as <GreeksDisplay>: a greek is
|
|
146
|
+
identified by its symbol, never by a hue. */}
|
|
147
|
+
<XStack alignItems="center" gap={ 12 } borderTopWidth={ 1 } borderColor={ 'var(--color-border-divider)' as never } paddingTop={ 8 }>
|
|
148
|
+
<GreekPill label="Δ" value={ position.greeks.delta } color="var(--foreground)"/>
|
|
149
|
+
<GreekPill label="Γ" value={ position.greeks.gamma } color="var(--chart-1)"/>
|
|
150
|
+
<GreekPill label="Θ" value={ position.greeks.theta } color="var(--chart-4)"/>
|
|
151
|
+
<GreekPill label="ν" value={ position.greeks.vega } color="var(--chart-2)"/>
|
|
152
|
+
</XStack>
|
|
139
153
|
|
|
140
154
|
{/* Action buttons */}
|
|
141
|
-
{(onClose || onExercise || onRoll) && (
|
|
142
|
-
<
|
|
143
|
-
{onClose && (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
Close
|
|
150
|
-
</button>
|
|
151
|
-
)}
|
|
152
|
-
{onExercise && (
|
|
153
|
-
<button
|
|
154
|
-
type="button"
|
|
155
|
-
onClick={() => onExercise(position)}
|
|
156
|
-
className="flex-1 rounded-md bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-300 hover:bg-zinc-700 hover:text-zinc-100 transition-colors"
|
|
157
|
-
>
|
|
158
|
-
Exercise
|
|
159
|
-
</button>
|
|
160
|
-
)}
|
|
161
|
-
{onRoll && (
|
|
162
|
-
<button
|
|
163
|
-
type="button"
|
|
164
|
-
onClick={() => onRoll(position)}
|
|
165
|
-
className="flex-1 rounded-md bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-300 hover:bg-zinc-700 hover:text-zinc-100 transition-colors"
|
|
166
|
-
>
|
|
167
|
-
Roll
|
|
168
|
-
</button>
|
|
169
|
-
)}
|
|
170
|
-
</div>
|
|
171
|
-
)}
|
|
172
|
-
</div>
|
|
155
|
+
{ (onClose || onExercise || onRoll) && (
|
|
156
|
+
<XStack alignItems="center" gap={ 8 } borderTopWidth={ 1 } borderColor={ 'var(--color-border-divider)' as never } paddingTop={ 8 }>
|
|
157
|
+
{ onClose && <ActionButton onClick={ () => onClose(position) }>Close</ActionButton> }
|
|
158
|
+
{ onExercise && <ActionButton onClick={ () => onExercise(position) }>Exercise</ActionButton> }
|
|
159
|
+
{ onRoll && <ActionButton onClick={ () => onRoll(position) }>Roll</ActionButton> }
|
|
160
|
+
</XStack>
|
|
161
|
+
) }
|
|
162
|
+
</YStack>
|
|
173
163
|
);
|
|
174
164
|
},
|
|
175
165
|
);
|
|
176
166
|
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// ActionButton (internal) — Close / Exercise / Roll. Same resting/hover pair
|
|
169
|
+
// button.tsx's "subtle" variant uses: hover repaints the text, not the fill.
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
interface ActionButtonProps {
|
|
173
|
+
readonly onClick: () => void;
|
|
174
|
+
readonly children: React.ReactNode;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function ActionButton({ onClick, children }: ActionButtonProps) {
|
|
178
|
+
return (
|
|
179
|
+
<XStack
|
|
180
|
+
unstyled
|
|
181
|
+
render={ <button type="button"/> }
|
|
182
|
+
onClick={ onClick }
|
|
183
|
+
flex={ 1 }
|
|
184
|
+
alignItems="center"
|
|
185
|
+
justifyContent="center"
|
|
186
|
+
borderRadius={ 6 }
|
|
187
|
+
borderWidth={ 0 }
|
|
188
|
+
paddingHorizontal={ 8 }
|
|
189
|
+
paddingVertical={ 4 }
|
|
190
|
+
backgroundColor={ 'var(--color-button-subtle-bg)' as never }
|
|
191
|
+
cursor="pointer"
|
|
192
|
+
>
|
|
193
|
+
{/* `hoverStyle` lives on the Text, not the button frame — Stacks carry
|
|
194
|
+
no `color` (same reason they carry no `fontSize`), so the hover
|
|
195
|
+
re-tint has to be the text's own state, not the button's. */}
|
|
196
|
+
<Text
|
|
197
|
+
fontSize={ 12 }
|
|
198
|
+
fontWeight="500"
|
|
199
|
+
color={ 'var(--color-button-subtle-fg)' as never }
|
|
200
|
+
hoverStyle={ { color: 'var(--color-hover)' } as never }
|
|
201
|
+
>
|
|
202
|
+
{ children }
|
|
203
|
+
</Text>
|
|
204
|
+
</XStack>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
177
208
|
// ---------------------------------------------------------------------------
|
|
178
209
|
// GreekPill (internal)
|
|
179
210
|
// ---------------------------------------------------------------------------
|
|
@@ -181,21 +212,19 @@ export const OptionPositionCard = React.forwardRef<HTMLDivElement, OptionPositio
|
|
|
181
212
|
interface GreekPillProps {
|
|
182
213
|
readonly label: string;
|
|
183
214
|
readonly value: number;
|
|
215
|
+
/** A resolved `var(--…)` token — this component is package-private. */
|
|
184
216
|
readonly color: string;
|
|
185
217
|
}
|
|
186
218
|
|
|
187
219
|
function GreekPill({ label, value, color }: GreekPillProps) {
|
|
188
220
|
return (
|
|
189
|
-
<
|
|
190
|
-
<
|
|
191
|
-
{label}
|
|
192
|
-
</
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{value.toFixed(4)}
|
|
198
|
-
</span>
|
|
199
|
-
</div>
|
|
221
|
+
<XStack alignItems="center" gap={ 4 }>
|
|
222
|
+
<Text fontSize={ 10 } fontWeight="600" color={ color as never }>
|
|
223
|
+
{ label }
|
|
224
|
+
</Text>
|
|
225
|
+
<Text fontSize={ 11 } style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ (value < 0 ? 'var(--color-status-bad)' : 'var(--color-text-secondary)') as never }>
|
|
226
|
+
{ value.toFixed(4) }
|
|
227
|
+
</Text>
|
|
228
|
+
</XStack>
|
|
200
229
|
);
|
|
201
230
|
}
|