@loafmarkets/ui 0.0.4 → 0.0.6
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/index.d.mts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +168 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +168 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -282,6 +282,7 @@ function HousePositionSlider({
|
|
|
282
282
|
pendingOrders = [],
|
|
283
283
|
defaultOrderType = "market",
|
|
284
284
|
orderbook,
|
|
285
|
+
ownershipPercentOverride,
|
|
285
286
|
onConfirmOrder,
|
|
286
287
|
className,
|
|
287
288
|
...props
|
|
@@ -292,6 +293,7 @@ function HousePositionSlider({
|
|
|
292
293
|
const [deltaTokensBuy, setDeltaTokensBuy] = React5.useState(0);
|
|
293
294
|
const [deltaTokensSell, setDeltaTokensSell] = React5.useState(0);
|
|
294
295
|
const [isDragging, setIsDragging] = React5.useState(false);
|
|
296
|
+
const [visualTargetPct, setVisualTargetPct] = React5.useState(null);
|
|
295
297
|
const [orderType, setOrderType] = React5.useState(defaultOrderType);
|
|
296
298
|
const [limitPrice, setLimitPrice] = React5.useState(currentPrice);
|
|
297
299
|
const [limitPriceInput, setLimitPriceInput] = React5.useState(currentPrice.toFixed(2));
|
|
@@ -313,9 +315,8 @@ function HousePositionSlider({
|
|
|
313
315
|
const effectiveAvailableCash = Math.max(0, availableCash - pendingBuyValue);
|
|
314
316
|
const effectiveTokensHeld = Math.max(0, tokensHeld - pendingSellTokens);
|
|
315
317
|
const holdingsValue = tokensHeld * effectivePrice;
|
|
316
|
-
const
|
|
317
|
-
const
|
|
318
|
-
const baselinePct = sliderTotalCapacity <= 0 ? 0 : sliderHoldingsValue / sliderTotalCapacity * 100;
|
|
318
|
+
const safeTotalTokens = totalTokens > 0 ? totalTokens : 1;
|
|
319
|
+
const baselineOwnershipActual = clamp(effectiveTokensHeld / safeTotalTokens * 100, 0, 100);
|
|
319
320
|
let deltaTokens = 0;
|
|
320
321
|
let deltaValue = 0;
|
|
321
322
|
let marketAvgPrice = null;
|
|
@@ -361,29 +362,21 @@ function HousePositionSlider({
|
|
|
361
362
|
}
|
|
362
363
|
targetTokens = tokensHeld + deltaTokens;
|
|
363
364
|
targetValue = targetTokens * effectivePrice;
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
if (buyTrackingMode === "dollars") {
|
|
367
|
-
const notional = Math.min(Math.max(0, deltaDollars), effectiveAvailableCash);
|
|
368
|
-
return notional;
|
|
369
|
-
}
|
|
370
|
-
const tokensPlanned = Math.max(0, deltaTokensBuy);
|
|
371
|
-
const referencePrice = orderType === "market" ? currentPrice || limitPriceSafe : limitPriceSafe;
|
|
372
|
-
return Math.min(tokensPlanned * referencePrice, effectiveAvailableCash);
|
|
373
|
-
}
|
|
374
|
-
if (orderMode === "sell") {
|
|
375
|
-
const tokensToSell = Math.abs(Math.min(0, deltaTokensSell));
|
|
376
|
-
const sellValue = tokensToSell * effectivePrice;
|
|
377
|
-
return -Math.min(sellValue, sliderHoldingsValue);
|
|
378
|
-
}
|
|
379
|
-
return 0;
|
|
380
|
-
})();
|
|
381
|
-
const sliderTargetValue = clamp(sliderHoldingsValue + plannedDeltaValue, 0, sliderTotalCapacity);
|
|
382
|
-
const targetPct = sliderTotalCapacity <= 0 ? 0 : sliderTargetValue / sliderTotalCapacity * 100;
|
|
365
|
+
const sliderTargetTokens = clamp(effectiveTokensHeld + deltaTokens, 0, safeTotalTokens);
|
|
366
|
+
const normalizedTargetPct = sliderTargetTokens / safeTotalTokens * 100;
|
|
383
367
|
const isIncrease = orderMode === "buy";
|
|
384
368
|
const hasChange = orderMode !== "none" && (Math.abs(deltaTokens) > 1e-3 || Math.abs(deltaValue) > 0.01);
|
|
385
|
-
const currentOwnership = totalTokens <= 0 ? 0 : tokensHeld / totalTokens * 100;
|
|
386
|
-
const targetOwnership = totalTokens <= 0 ? 0 : targetTokens / totalTokens * 100;
|
|
369
|
+
const currentOwnership = totalTokens <= 0 ? 0 : clamp(tokensHeld / totalTokens * 100, 0, 100);
|
|
370
|
+
const targetOwnership = totalTokens <= 0 ? 0 : clamp(targetTokens / totalTokens * 100, 0, 100);
|
|
371
|
+
const ownershipOverrideValue = typeof ownershipPercentOverride === "number" && Number.isFinite(ownershipPercentOverride) ? clamp(ownershipPercentOverride, 0, 100) : null;
|
|
372
|
+
const ownershipShift = ownershipOverrideValue != null ? ownershipOverrideValue - baselineOwnershipActual : 0;
|
|
373
|
+
const baselinePct = clamp(ownershipOverrideValue ?? baselineOwnershipActual, 0, 100);
|
|
374
|
+
const impliedTargetPct = clamp(normalizedTargetPct + ownershipShift, 0, 100);
|
|
375
|
+
const displayTargetPct = visualTargetPct ?? impliedTargetPct;
|
|
376
|
+
const targetPct = displayTargetPct;
|
|
377
|
+
const displayCurrentOwnership = clamp(ownershipOverrideValue ?? currentOwnership, 0, 100);
|
|
378
|
+
const impliedDisplayTargetOwnership = clamp(targetOwnership + ownershipShift, 0, 100);
|
|
379
|
+
const displayTargetOwnership = visualTargetPct ?? impliedDisplayTargetOwnership;
|
|
387
380
|
const estFeeTokens = Math.abs(deltaValue) * 5e-3 / (effectivePrice || 1);
|
|
388
381
|
const resetOrder = React5.useCallback(() => {
|
|
389
382
|
setOrderMode("none");
|
|
@@ -391,6 +384,7 @@ function HousePositionSlider({
|
|
|
391
384
|
setDeltaDollars(0);
|
|
392
385
|
setDeltaTokensBuy(0);
|
|
393
386
|
setDeltaTokensSell(0);
|
|
387
|
+
setVisualTargetPct(null);
|
|
394
388
|
}, []);
|
|
395
389
|
const updateOrderFromTargetValue = React5.useCallback(
|
|
396
390
|
(newTargetValue) => {
|
|
@@ -421,6 +415,7 @@ function HousePositionSlider({
|
|
|
421
415
|
const nextOwnership = clamp(newOwnershipPercent, 0, 100);
|
|
422
416
|
const newTargetTokens = nextOwnership / 100 * totalTokens;
|
|
423
417
|
updateOrderFromTargetValue(newTargetTokens * effectivePrice);
|
|
418
|
+
setVisualTargetPct(nextOwnership);
|
|
424
419
|
},
|
|
425
420
|
[effectivePrice, totalTokens, updateOrderFromTargetValue]
|
|
426
421
|
);
|
|
@@ -453,12 +448,14 @@ function HousePositionSlider({
|
|
|
453
448
|
const magnitude = Math.min(Math.abs(normalized), 1);
|
|
454
449
|
if (magnitude < 0.02) {
|
|
455
450
|
resetOrder();
|
|
451
|
+
setVisualTargetPct(null);
|
|
456
452
|
return;
|
|
457
453
|
}
|
|
458
454
|
if (normalized > 0) {
|
|
459
455
|
const notional = clamp(magnitude * effectiveAvailableCash, 0, effectiveAvailableCash);
|
|
460
456
|
if (notional <= 0) {
|
|
461
457
|
resetOrder();
|
|
458
|
+
setVisualTargetPct(null);
|
|
462
459
|
return;
|
|
463
460
|
}
|
|
464
461
|
setOrderMode("buy");
|
|
@@ -466,12 +463,14 @@ function HousePositionSlider({
|
|
|
466
463
|
setDeltaDollars(notional);
|
|
467
464
|
setDeltaTokensBuy(0);
|
|
468
465
|
setDeltaTokensSell(0);
|
|
466
|
+
setVisualTargetPct(clamp(pct, 0, 100));
|
|
469
467
|
return;
|
|
470
468
|
}
|
|
471
469
|
if (normalized < 0) {
|
|
472
470
|
const tokensToSell = clamp(magnitude * effectiveTokensHeld, 0, effectiveTokensHeld);
|
|
473
471
|
if (tokensToSell <= 0) {
|
|
474
472
|
resetOrder();
|
|
473
|
+
setVisualTargetPct(null);
|
|
475
474
|
return;
|
|
476
475
|
}
|
|
477
476
|
setOrderMode("sell");
|
|
@@ -479,9 +478,11 @@ function HousePositionSlider({
|
|
|
479
478
|
setDeltaTokensSell(-tokensToSell);
|
|
480
479
|
setDeltaDollars(0);
|
|
481
480
|
setDeltaTokensBuy(0);
|
|
481
|
+
setVisualTargetPct(clamp(pct, 0, 100));
|
|
482
482
|
return;
|
|
483
483
|
}
|
|
484
484
|
resetOrder();
|
|
485
|
+
setVisualTargetPct(null);
|
|
485
486
|
},
|
|
486
487
|
[effectiveAvailableCash, effectiveTokensHeld, resetOrder]
|
|
487
488
|
);
|
|
@@ -644,19 +645,19 @@ function HousePositionSlider({
|
|
|
644
645
|
" Ownership"
|
|
645
646
|
] }),
|
|
646
647
|
/* @__PURE__ */ jsxs("span", { className: "text-white", children: [
|
|
647
|
-
|
|
648
|
+
displayCurrentOwnership.toFixed(2),
|
|
648
649
|
"%",
|
|
649
650
|
/* @__PURE__ */ jsx("span", { className: "mx-1.5 text-white/50", children: "\u2192" }),
|
|
650
651
|
/* @__PURE__ */ jsx(
|
|
651
652
|
"input",
|
|
652
653
|
{
|
|
653
654
|
type: "text",
|
|
654
|
-
value: ownershipInput ||
|
|
655
|
+
value: ownershipInput || displayTargetOwnership.toFixed(2),
|
|
655
656
|
onChange: (e) => {
|
|
656
657
|
const val = e.target.value;
|
|
657
658
|
if (val === "" || /^[0-9]*\.?[0-9]*$/.test(val)) setOwnershipInput(val);
|
|
658
659
|
},
|
|
659
|
-
onFocus: () => setOwnershipInput(
|
|
660
|
+
onFocus: () => setOwnershipInput(displayTargetOwnership.toFixed(2)),
|
|
660
661
|
onBlur: () => {
|
|
661
662
|
const num = Number.parseFloat(ownershipInput);
|
|
662
663
|
if (Number.isFinite(num)) updateOrderFromOwnership(num);
|
|
@@ -667,7 +668,7 @@ function HousePositionSlider({
|
|
|
667
668
|
},
|
|
668
669
|
className: cn(
|
|
669
670
|
"w-[70px] rounded-[4px] border bg-white/10 px-2 py-1 text-right font-semibold outline-none",
|
|
670
|
-
|
|
671
|
+
displayTargetOwnership >= displayCurrentOwnership ? "border-[rgba(14,203,129,0.3)] text-[#0ecb81] focus:border-[#0ecb81]" : "border-[rgba(246,70,93,0.3)] text-[#f6465d] focus:border-[#f6465d]"
|
|
671
672
|
)
|
|
672
673
|
}
|
|
673
674
|
)
|
|
@@ -1227,6 +1228,144 @@ var PropertyNewsUpdates = React5.forwardRef(
|
|
|
1227
1228
|
}
|
|
1228
1229
|
);
|
|
1229
1230
|
PropertyNewsUpdates.displayName = "PropertyNewsUpdates";
|
|
1231
|
+
var PropertyCompareBar = React5.forwardRef(
|
|
1232
|
+
({
|
|
1233
|
+
className,
|
|
1234
|
+
addresses,
|
|
1235
|
+
selectedAddressId,
|
|
1236
|
+
onSelectAddress,
|
|
1237
|
+
compareLabel = "Compare",
|
|
1238
|
+
onCompareClick,
|
|
1239
|
+
compareIcon,
|
|
1240
|
+
...props
|
|
1241
|
+
}, ref) => {
|
|
1242
|
+
const normalizedAddresses = React5.useMemo(() => {
|
|
1243
|
+
return addresses.map(
|
|
1244
|
+
(option) => typeof option === "string" ? { id: option, label: option } : option
|
|
1245
|
+
);
|
|
1246
|
+
}, [addresses]);
|
|
1247
|
+
const hasAddresses = normalizedAddresses.length > 0;
|
|
1248
|
+
const firstAddressId = normalizedAddresses[0]?.id;
|
|
1249
|
+
const isControlled = selectedAddressId !== void 0;
|
|
1250
|
+
const [internalSelectedId, setInternalSelectedId] = React5.useState(
|
|
1251
|
+
() => isControlled ? void 0 : firstAddressId
|
|
1252
|
+
);
|
|
1253
|
+
const resolvedSelectedId = isControlled ? selectedAddressId : internalSelectedId;
|
|
1254
|
+
React5.useEffect(() => {
|
|
1255
|
+
if (!isControlled) {
|
|
1256
|
+
setInternalSelectedId((current) => {
|
|
1257
|
+
if (current != null && normalizedAddresses.some((option) => option.id === current)) {
|
|
1258
|
+
return current;
|
|
1259
|
+
}
|
|
1260
|
+
return firstAddressId;
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}, [firstAddressId, isControlled, normalizedAddresses]);
|
|
1264
|
+
const selectedOption = normalizedAddresses.find((option) => option.id === resolvedSelectedId) ?? normalizedAddresses[0];
|
|
1265
|
+
const [isDropdownOpen, setIsDropdownOpen] = React5.useState(false);
|
|
1266
|
+
const dropdownRef = React5.useRef(null);
|
|
1267
|
+
React5.useEffect(() => {
|
|
1268
|
+
if (!isDropdownOpen) return;
|
|
1269
|
+
const handleClick = (event) => {
|
|
1270
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
1271
|
+
setIsDropdownOpen(false);
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
const handleKey = (event) => {
|
|
1275
|
+
if (event.key === "Escape") {
|
|
1276
|
+
setIsDropdownOpen(false);
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
document.addEventListener("mousedown", handleClick);
|
|
1280
|
+
document.addEventListener("keydown", handleKey);
|
|
1281
|
+
return () => {
|
|
1282
|
+
document.removeEventListener("mousedown", handleClick);
|
|
1283
|
+
document.removeEventListener("keydown", handleKey);
|
|
1284
|
+
};
|
|
1285
|
+
}, [isDropdownOpen]);
|
|
1286
|
+
const defaultCompareIcon = /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4h7v7H4V4zm0 9h7v7H4v-7zm9-9h7v7h-7V4zm0 9h7v7h-7v-7z" }) });
|
|
1287
|
+
const handleAddressSelect = (addressId) => {
|
|
1288
|
+
if (!isControlled) {
|
|
1289
|
+
setInternalSelectedId(addressId);
|
|
1290
|
+
}
|
|
1291
|
+
onSelectAddress?.(addressId);
|
|
1292
|
+
setIsDropdownOpen(false);
|
|
1293
|
+
};
|
|
1294
|
+
return /* @__PURE__ */ jsxs(
|
|
1295
|
+
"div",
|
|
1296
|
+
{
|
|
1297
|
+
ref,
|
|
1298
|
+
className: cn(
|
|
1299
|
+
"flex w-full flex-col gap-3 border border-white/10 px-4 py-3 text-white shadow-[0_18px_40px_rgba(0,0,0,0.55)] md:flex-row md:items-center md:justify-between md:gap-4",
|
|
1300
|
+
className
|
|
1301
|
+
),
|
|
1302
|
+
style: { borderRadius: "16px" },
|
|
1303
|
+
...props,
|
|
1304
|
+
children: [
|
|
1305
|
+
/* @__PURE__ */ jsxs("div", { className: "relative w-auto", ref: dropdownRef, children: [
|
|
1306
|
+
/* @__PURE__ */ jsxs(
|
|
1307
|
+
"button",
|
|
1308
|
+
{
|
|
1309
|
+
type: "button",
|
|
1310
|
+
disabled: !hasAddresses,
|
|
1311
|
+
onClick: () => setIsDropdownOpen((prev) => !prev),
|
|
1312
|
+
className: cn(
|
|
1313
|
+
"flex h-[42px] w-auto items-center gap-2 rounded-[12px] border border-transparent bg-transparent px-0 text-left text-[15px] font-semibold text-white transition hover:text-white/80 focus-visible:outline-none",
|
|
1314
|
+
!hasAddresses && "text-white/40"
|
|
1315
|
+
),
|
|
1316
|
+
children: [
|
|
1317
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: selectedOption ? selectedOption.label : hasAddresses ? "Select address" : "No addresses available" }),
|
|
1318
|
+
/* @__PURE__ */ jsx("span", { className: "ml-3 flex items-center text-white/60 transition-transform", "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
1319
|
+
"svg",
|
|
1320
|
+
{
|
|
1321
|
+
width: "16",
|
|
1322
|
+
height: "16",
|
|
1323
|
+
viewBox: "0 0 24 24",
|
|
1324
|
+
fill: "currentColor",
|
|
1325
|
+
className: cn("transition-transform", isDropdownOpen && "rotate-180"),
|
|
1326
|
+
children: /* @__PURE__ */ jsx("path", { d: "M7 10l5 5 5-5H7z" })
|
|
1327
|
+
}
|
|
1328
|
+
) })
|
|
1329
|
+
]
|
|
1330
|
+
}
|
|
1331
|
+
),
|
|
1332
|
+
isDropdownOpen && hasAddresses ? /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-[calc(100%+8px)] z-20 w-full rounded-[12px] border border-white/10 py-1 shadow-[0_25px_55px_rgba(0,0,0,0.6)] bg-black", children: normalizedAddresses.map((option) => {
|
|
1333
|
+
const active = option.id === resolvedSelectedId;
|
|
1334
|
+
return /* @__PURE__ */ jsx(
|
|
1335
|
+
"button",
|
|
1336
|
+
{
|
|
1337
|
+
type: "button",
|
|
1338
|
+
className: cn(
|
|
1339
|
+
"flex w-full items-center px-4 py-2 text-left text-[14px] text-white/80 transition hover:bg-white/5 hover:text-white",
|
|
1340
|
+
active && "text-white"
|
|
1341
|
+
),
|
|
1342
|
+
onClick: () => handleAddressSelect(option.id),
|
|
1343
|
+
children: option.label
|
|
1344
|
+
},
|
|
1345
|
+
option.id
|
|
1346
|
+
);
|
|
1347
|
+
}) }) : null
|
|
1348
|
+
] }),
|
|
1349
|
+
/* @__PURE__ */ jsxs(
|
|
1350
|
+
Button,
|
|
1351
|
+
{
|
|
1352
|
+
variant: "accentOutline",
|
|
1353
|
+
size: "sm",
|
|
1354
|
+
className: "flex items-center justify-center gap-2 rounded-[10px] border-[var(--color-accent,#e6c87e)] bg-transparent px-4 py-2 text-[14px] font-semibold text-[var(--color-accent,#e6c87e)] transition hover:bg-[rgba(230,200,126,0.08)] md:ml-auto",
|
|
1355
|
+
onClick: onCompareClick,
|
|
1356
|
+
disabled: !hasAddresses,
|
|
1357
|
+
children: [
|
|
1358
|
+
/* @__PURE__ */ jsx("span", { className: "text-base", children: compareIcon ?? defaultCompareIcon }),
|
|
1359
|
+
compareLabel
|
|
1360
|
+
]
|
|
1361
|
+
}
|
|
1362
|
+
)
|
|
1363
|
+
]
|
|
1364
|
+
}
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
);
|
|
1368
|
+
PropertyCompareBar.displayName = "PropertyCompareBar";
|
|
1230
1369
|
var clampPct = (pct) => Math.min(100, Math.max(0, pct));
|
|
1231
1370
|
var EditIcon = ({ className }) => /* @__PURE__ */ jsxs(
|
|
1232
1371
|
"svg",
|
|
@@ -1821,6 +1960,6 @@ var PropertySubheader = React5.forwardRef(
|
|
|
1821
1960
|
);
|
|
1822
1961
|
PropertySubheader.displayName = "PropertySubheader";
|
|
1823
1962
|
|
|
1824
|
-
export { Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, HousePositionSlider, Orderbook, PortfolioSummary, PriceChart, PropertyHeroHeader, PropertyNewsUpdates, PropertySubheader, PropertyTour, YourOrders, badgeVariants, buttonVariants };
|
|
1963
|
+
export { Badge, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, HousePositionSlider, Orderbook, PortfolioSummary, PriceChart, PropertyCompareBar, PropertyHeroHeader, PropertyNewsUpdates, PropertySubheader, PropertyTour, YourOrders, badgeVariants, buttonVariants };
|
|
1825
1964
|
//# sourceMappingURL=index.mjs.map
|
|
1826
1965
|
//# sourceMappingURL=index.mjs.map
|