@m3000/market 0.0.1
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/LICENSE +21 -0
- package/dist/components/blocks/auction/Auction.d.ts +49 -0
- package/dist/components/blocks/auction/Auction.js +44 -0
- package/dist/components/blocks/auction/AuctionBidForm.d.ts +11 -0
- package/dist/components/blocks/auction/AuctionBidForm.js +88 -0
- package/dist/components/blocks/auction/AuctionBidInput.d.ts +9 -0
- package/dist/components/blocks/auction/AuctionBidInput.js +99 -0
- package/dist/components/blocks/auction/AuctionContext.d.ts +71 -0
- package/dist/components/blocks/auction/AuctionContext.js +228 -0
- package/dist/components/blocks/auction/AuctionInfo.d.ts +9 -0
- package/dist/components/blocks/auction/AuctionInfo.js +37 -0
- package/dist/components/blocks/auction/AuctionLayout.d.ts +63 -0
- package/dist/components/blocks/auction/AuctionLayout.js +80 -0
- package/dist/components/blocks/auction/AuctionRankings.d.ts +16 -0
- package/dist/components/blocks/auction/AuctionRankings.js +334 -0
- package/dist/components/blocks/auction/AuctionStatusTag.d.ts +15 -0
- package/dist/components/blocks/auction/AuctionStatusTag.js +60 -0
- package/dist/components/blocks/auction/AuctionSuggestedBids.d.ts +38 -0
- package/dist/components/blocks/auction/AuctionSuggestedBids.js +116 -0
- package/dist/components/blocks/auction/AuctionYourBidCard.d.ts +27 -0
- package/dist/components/blocks/auction/AuctionYourBidCard.js +94 -0
- package/dist/components/blocks/auction/AuctionYourBids.d.ts +9 -0
- package/dist/components/blocks/auction/AuctionYourBids.js +49 -0
- package/dist/components/blocks/auction/index.d.ts +12 -0
- package/dist/components/blocks/index.d.ts +12 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/primitives/Button.d.ts +31 -0
- package/dist/components/primitives/Button.js +117 -0
- package/dist/components/primitives/Drawer.d.ts +43 -0
- package/dist/components/primitives/Drawer.js +51 -0
- package/dist/components/primitives/Feedback.d.ts +28 -0
- package/dist/components/primitives/Feedback.js +147 -0
- package/dist/components/primitives/MorphDialog.d.ts +39 -0
- package/dist/components/primitives/MorphDialog.js +87 -0
- package/dist/components/primitives/Price.d.ts +84 -0
- package/dist/components/primitives/Price.js +255 -0
- package/dist/components/primitives/PriceInput.d.ts +33 -0
- package/dist/components/primitives/PriceInput.js +25 -0
- package/dist/components/primitives/Receipt.d.ts +164 -0
- package/dist/components/primitives/Receipt.js +344 -0
- package/dist/components/primitives/Scale.d.ts +67 -0
- package/dist/components/primitives/Scale.js +132 -0
- package/dist/components/primitives/Separator.d.ts +22 -0
- package/dist/components/primitives/Separator.js +62 -0
- package/dist/components/primitives/Skeleton.d.ts +14 -0
- package/dist/components/primitives/Skeleton.js +20 -0
- package/dist/components/primitives/SteppedInput.d.ts +94 -0
- package/dist/components/primitives/SteppedInput.js +154 -0
- package/dist/components/primitives/Tabs.d.ts +37 -0
- package/dist/components/primitives/Tabs.js +99 -0
- package/dist/components/primitives/Tag.d.ts +24 -0
- package/dist/components/primitives/Tag.js +22 -0
- package/dist/components/primitives/Text.d.ts +32 -0
- package/dist/components/primitives/Text.js +65 -0
- package/dist/components/primitives/countdown/Countdown.d.ts +24 -0
- package/dist/components/primitives/countdown/Countdown.js +22 -0
- package/dist/components/primitives/framed-image/FramedImage.d.ts +13 -0
- package/dist/components/primitives/framed-image/FramedImage.js +37 -0
- package/dist/components/primitives/index.d.ts +17 -0
- package/dist/components/primitives/ranked-list/Ranking.d.ts +117 -0
- package/dist/components/primitives/ranked-list/Ranking.js +219 -0
- package/dist/components/primitives/ranked-list/index.d.ts +1 -0
- package/dist/hooks/useCountdown.d.ts +20 -0
- package/dist/hooks/useCountdown.js +75 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +36 -0
- package/dist/lib/cn.d.ts +6 -0
- package/dist/lib/cn.js +75 -0
- package/dist/lib/motion.d.ts +19 -0
- package/dist/lib/motion.js +43 -0
- package/dist/types/index.d.ts +120 -0
- package/dist/utils/format.d.ts +38 -0
- package/dist/utils/format.js +103 -0
- package/dist/utils/rank-utils.d.ts +34 -0
- package/dist/utils/rank-utils.js +80 -0
- package/dist/utils/tick-validation.d.ts +22 -0
- package/dist/utils/tick-validation.js +40 -0
- package/package.json +92 -0
- package/src/components/blocks/auction/Auction.tsx +74 -0
- package/src/components/blocks/auction/AuctionArtwork.tsx +4 -0
- package/src/components/blocks/auction/AuctionBidForm.tsx +138 -0
- package/src/components/blocks/auction/AuctionBidInput.tsx +166 -0
- package/src/components/blocks/auction/AuctionContext.tsx +401 -0
- package/src/components/blocks/auction/AuctionInfo.tsx +36 -0
- package/src/components/blocks/auction/AuctionLayout.tsx +200 -0
- package/src/components/blocks/auction/AuctionRankings.tsx +435 -0
- package/src/components/blocks/auction/AuctionStatusTag.tsx +98 -0
- package/src/components/blocks/auction/AuctionSuggestedBids.tsx +203 -0
- package/src/components/blocks/auction/AuctionYourBidCard.tsx +125 -0
- package/src/components/blocks/auction/AuctionYourBids.tsx +61 -0
- package/src/components/blocks/auction/index.ts +42 -0
- package/src/components/blocks/index.ts +1 -0
- package/src/components/index.ts +2 -0
- package/src/components/primitives/Button.tsx +183 -0
- package/src/components/primitives/Drawer.tsx +125 -0
- package/src/components/primitives/Feedback.tsx +185 -0
- package/src/components/primitives/MorphDialog.tsx +160 -0
- package/src/components/primitives/Price.tsx +394 -0
- package/src/components/primitives/PriceInput.tsx +48 -0
- package/src/components/primitives/Receipt.tsx +711 -0
- package/src/components/primitives/Scale.tsx +287 -0
- package/src/components/primitives/Separator.tsx +87 -0
- package/src/components/primitives/Skeleton.tsx +33 -0
- package/src/components/primitives/SteppedInput.tsx +313 -0
- package/src/components/primitives/Tabs.tsx +161 -0
- package/src/components/primitives/Tag.tsx +48 -0
- package/src/components/primitives/Text.tsx +102 -0
- package/src/components/primitives/countdown/Countdown.tsx +43 -0
- package/src/components/primitives/countdown/index.ts +2 -0
- package/src/components/primitives/framed-image/FramedImage.tsx +51 -0
- package/src/components/primitives/framed-image/index.ts +1 -0
- package/src/components/primitives/index.ts +42 -0
- package/src/components/primitives/ranked-list/RankedList.tsx +9 -0
- package/src/components/primitives/ranked-list/Ranking.tsx +454 -0
- package/src/components/primitives/ranked-list/index.ts +8 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useCountdown.ts +91 -0
- package/src/index.ts +130 -0
- package/src/lib/cn.ts +81 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/motion.ts +55 -0
- package/src/public/lea-83-time-walk.png +0 -0
- package/src/public/lea-83-time-walk.webp +0 -0
- package/src/stories/Auction.stories.tsx +658 -0
- package/src/stories/AuctionLayout.stories.tsx +313 -0
- package/src/stories/AuctionStatusTag.stories.tsx +166 -0
- package/src/stories/AuctionYourBidCard.stories.tsx +257 -0
- package/src/stories/Button.stories.tsx +306 -0
- package/src/stories/Countdown.stories.tsx +158 -0
- package/src/stories/Feedback.stories.tsx +80 -0
- package/src/stories/FramedImage.stories.tsx +46 -0
- package/src/stories/MorphDialog.stories.tsx +88 -0
- package/src/stories/Price.stories.tsx +292 -0
- package/src/stories/RankedList.stories.tsx +190 -0
- package/src/stories/Receipt.stories.tsx +221 -0
- package/src/stories/Scale.stories.tsx +578 -0
- package/src/stories/Separator.stories.tsx +188 -0
- package/src/stories/Skeleton.stories.tsx +138 -0
- package/src/stories/SteppedInput.stories.tsx +321 -0
- package/src/stories/Tabs.stories.tsx +215 -0
- package/src/stories/Tag.stories.tsx +138 -0
- package/src/stories/Text.stories.tsx +245 -0
- package/src/styles/globals.css +39 -0
- package/src/styles/index.css +4 -0
- package/src/styles/theme/animation.css +11 -0
- package/src/styles/theme/color.css +185 -0
- package/src/styles/theme/index.css +3 -0
- package/src/styles/theme/typography.css +3 -0
- package/src/styles/utility.css +8 -0
- package/src/types/index.ts +149 -0
- package/src/utils/format.ts +130 -0
- package/src/utils/index.ts +16 -0
- package/src/utils/rank-utils.ts +131 -0
- package/src/utils/tick-validation.ts +65 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
6
|
+
|
|
7
|
+
//#region src/components/primitives/Feedback.tsx
|
|
8
|
+
const FeedbackContext = createContext(null);
|
|
9
|
+
function useFeedback() {
|
|
10
|
+
const context = useContext(FeedbackContext);
|
|
11
|
+
if (!context) throw new Error("Feedback.Content must be used within Feedback.Root");
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
const positionStyles = {
|
|
15
|
+
bottom: "top-full right-0 left-0",
|
|
16
|
+
top: "bottom-full right-0 left-0",
|
|
17
|
+
left: "right-full top-0 bottom-0",
|
|
18
|
+
right: "left-full top-0 bottom-0"
|
|
19
|
+
};
|
|
20
|
+
const initialAnimations = {
|
|
21
|
+
bottom: { y: "-100%" },
|
|
22
|
+
top: { y: "100%" },
|
|
23
|
+
left: { x: "100%" },
|
|
24
|
+
right: { x: "-100%" }
|
|
25
|
+
};
|
|
26
|
+
const FeedbackRoot = ({ children, show = false, position = "bottom", className, transition = {
|
|
27
|
+
stiffness: 300,
|
|
28
|
+
damping: 20
|
|
29
|
+
} }) => {
|
|
30
|
+
const contextValue = React.useMemo(() => ({
|
|
31
|
+
show,
|
|
32
|
+
position,
|
|
33
|
+
transition: {
|
|
34
|
+
stiffness: transition.stiffness ?? 300,
|
|
35
|
+
damping: transition.damping ?? 20
|
|
36
|
+
}
|
|
37
|
+
}), [
|
|
38
|
+
show,
|
|
39
|
+
position,
|
|
40
|
+
transition
|
|
41
|
+
]);
|
|
42
|
+
return /* @__PURE__ */ jsx(FeedbackContext.Provider, {
|
|
43
|
+
value: contextValue,
|
|
44
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
45
|
+
className: cn("relative inline-block", className),
|
|
46
|
+
children: React.Children.map(children, (child) => {
|
|
47
|
+
if (React.isValidElement(child) && child.type === FeedbackContent) return child;
|
|
48
|
+
return /* @__PURE__ */ jsx("span", {
|
|
49
|
+
className: "relative z-10",
|
|
50
|
+
children: child
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
const contentAnimations = {
|
|
57
|
+
crossfade: {
|
|
58
|
+
initial: { opacity: 0 },
|
|
59
|
+
animate: { opacity: 1 },
|
|
60
|
+
exit: { opacity: 0 }
|
|
61
|
+
},
|
|
62
|
+
"slide-up": {
|
|
63
|
+
initial: {
|
|
64
|
+
opacity: 0,
|
|
65
|
+
y: 8
|
|
66
|
+
},
|
|
67
|
+
animate: {
|
|
68
|
+
opacity: 1,
|
|
69
|
+
y: 0
|
|
70
|
+
},
|
|
71
|
+
exit: {
|
|
72
|
+
opacity: 0,
|
|
73
|
+
y: -8
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"slide-down": {
|
|
77
|
+
initial: {
|
|
78
|
+
opacity: 0,
|
|
79
|
+
y: -8
|
|
80
|
+
},
|
|
81
|
+
animate: {
|
|
82
|
+
opacity: 1,
|
|
83
|
+
y: 0
|
|
84
|
+
},
|
|
85
|
+
exit: {
|
|
86
|
+
opacity: 0,
|
|
87
|
+
y: 8
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
function getContentKey(children) {
|
|
92
|
+
if (typeof children === "string" || typeof children === "number") return String(children);
|
|
93
|
+
if (React.isValidElement(children)) return children.key ?? JSON.stringify(children.props);
|
|
94
|
+
return JSON.stringify(children);
|
|
95
|
+
}
|
|
96
|
+
const FeedbackContent = ({ children, className, contentKey, mode = "slide-up" }) => {
|
|
97
|
+
const { show, position, transition } = useFeedback();
|
|
98
|
+
const key = contentKey ?? getContentKey(children);
|
|
99
|
+
const contentAnimation = contentAnimations[mode];
|
|
100
|
+
return /* @__PURE__ */ jsx(AnimatePresence, {
|
|
101
|
+
mode: "wait",
|
|
102
|
+
children: show && /* @__PURE__ */ jsx(motion.div, {
|
|
103
|
+
initial: {
|
|
104
|
+
...initialAnimations[position],
|
|
105
|
+
opacity: 0,
|
|
106
|
+
rotateZ: 5
|
|
107
|
+
},
|
|
108
|
+
animate: {
|
|
109
|
+
x: "0%",
|
|
110
|
+
y: "0%",
|
|
111
|
+
opacity: 1,
|
|
112
|
+
rotateZ: 0
|
|
113
|
+
},
|
|
114
|
+
exit: {
|
|
115
|
+
...initialAnimations[position],
|
|
116
|
+
opacity: 0,
|
|
117
|
+
rotateZ: 0
|
|
118
|
+
},
|
|
119
|
+
className: cn("absolute z-0 flex items-center justify-center", positionStyles[position], className),
|
|
120
|
+
transition: {
|
|
121
|
+
type: "spring",
|
|
122
|
+
stiffness: transition.stiffness,
|
|
123
|
+
damping: transition.damping
|
|
124
|
+
},
|
|
125
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, {
|
|
126
|
+
mode: "wait",
|
|
127
|
+
children: /* @__PURE__ */ jsx(motion.span, {
|
|
128
|
+
initial: contentAnimation.initial,
|
|
129
|
+
animate: contentAnimation.animate,
|
|
130
|
+
exit: contentAnimation.exit,
|
|
131
|
+
transition: {
|
|
132
|
+
duration: .2,
|
|
133
|
+
ease: "easeInOut"
|
|
134
|
+
},
|
|
135
|
+
children
|
|
136
|
+
}, key)
|
|
137
|
+
})
|
|
138
|
+
}, "feedback-container")
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
const Feedback = {
|
|
142
|
+
Root: FeedbackRoot,
|
|
143
|
+
Content: FeedbackContent
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
export { Feedback };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
3
|
+
|
|
4
|
+
//#region src/components/primitives/MorphDialog.d.ts
|
|
5
|
+
type OpenChangeDetails = Parameters<NonNullable<React.ComponentProps<typeof Dialog.Root>["onOpenChange"]>>[1];
|
|
6
|
+
interface MorphDialogProps {
|
|
7
|
+
trigger: React.ReactNode;
|
|
8
|
+
content: React.ReactNode;
|
|
9
|
+
contentClosesDialog?: boolean;
|
|
10
|
+
open?: boolean;
|
|
11
|
+
defaultOpen?: boolean;
|
|
12
|
+
onOpenChange?: (open: boolean, eventDetails: OpenChangeDetails) => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
triggerClassName?: string;
|
|
15
|
+
popupClassName?: string;
|
|
16
|
+
backdropClassName?: string;
|
|
17
|
+
modal?: React.ComponentProps<typeof Dialog.Root>["modal"];
|
|
18
|
+
initialFocus?: React.ComponentProps<typeof Dialog.Popup>["initialFocus"];
|
|
19
|
+
finalFocus?: React.ComponentProps<typeof Dialog.Popup>["finalFocus"];
|
|
20
|
+
popupAriaLabel?: string;
|
|
21
|
+
}
|
|
22
|
+
declare function MorphDialog({
|
|
23
|
+
trigger,
|
|
24
|
+
content,
|
|
25
|
+
contentClosesDialog,
|
|
26
|
+
open,
|
|
27
|
+
defaultOpen,
|
|
28
|
+
onOpenChange,
|
|
29
|
+
className,
|
|
30
|
+
triggerClassName,
|
|
31
|
+
popupClassName,
|
|
32
|
+
backdropClassName,
|
|
33
|
+
modal,
|
|
34
|
+
initialFocus,
|
|
35
|
+
finalFocus,
|
|
36
|
+
popupAriaLabel
|
|
37
|
+
}: MorphDialogProps): React.ReactElement;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { MorphDialog, MorphDialogProps };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { springs, transitions } from "../../lib/motion.js";
|
|
5
|
+
import { useCallback, useId, useMemo, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { AnimatePresence, LayoutGroup, motion, useReducedMotion } from "motion/react";
|
|
8
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
9
|
+
|
|
10
|
+
//#region src/components/primitives/MorphDialog.tsx
|
|
11
|
+
function MorphDialog({ trigger, content, contentClosesDialog = false, open, defaultOpen = false, onOpenChange, className, triggerClassName, popupClassName, backdropClassName, modal = true, initialFocus, finalFocus, popupAriaLabel }) {
|
|
12
|
+
const generatedId = useId();
|
|
13
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
14
|
+
const prefersReducedMotion = useReducedMotion();
|
|
15
|
+
const isControlled = open !== void 0;
|
|
16
|
+
const isOpen = isControlled ? open : uncontrolledOpen;
|
|
17
|
+
const layoutId = useMemo(() => `morph-dialog-${generatedId.replace(/:/g, "")}`, [generatedId]);
|
|
18
|
+
const handleOpenChange = useCallback((nextOpen, eventDetails) => {
|
|
19
|
+
if (!isControlled) setUncontrolledOpen(nextOpen);
|
|
20
|
+
onOpenChange?.(nextOpen, eventDetails);
|
|
21
|
+
}, [isControlled, onOpenChange]);
|
|
22
|
+
const handleBackdropClick = useCallback(() => {
|
|
23
|
+
handleOpenChange(false, {});
|
|
24
|
+
}, [handleOpenChange]);
|
|
25
|
+
const handleContentClick = useCallback((event) => {
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
}, []);
|
|
28
|
+
return /* @__PURE__ */ jsx(LayoutGroup, {
|
|
29
|
+
id: layoutId,
|
|
30
|
+
children: /* @__PURE__ */ jsxs(Dialog.Root, {
|
|
31
|
+
open: isOpen,
|
|
32
|
+
onOpenChange: handleOpenChange,
|
|
33
|
+
modal,
|
|
34
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
35
|
+
className,
|
|
36
|
+
children: /* @__PURE__ */ jsx(Dialog.Trigger, {
|
|
37
|
+
className: cn("block cursor-pointer appearance-none border-0 bg-transparent p-0 text-left outline-none disabled:cursor-default", triggerClassName),
|
|
38
|
+
children: /* @__PURE__ */ jsx(motion.div, {
|
|
39
|
+
layout: !prefersReducedMotion,
|
|
40
|
+
layoutId: prefersReducedMotion ? void 0 : layoutId,
|
|
41
|
+
transition: springs.quick,
|
|
42
|
+
className: "h-full w-full",
|
|
43
|
+
children: trigger
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
}), /* @__PURE__ */ jsxs(Dialog.Portal, { children: [/* @__PURE__ */ jsx(Dialog.Backdrop, {
|
|
47
|
+
className: cn("fixed inset-0 z-60 cursor-pointer backdrop-blur-sm transition-opacity duration-150 ease-out", isOpen ? "opacity-100" : "opacity-0", backdropClassName),
|
|
48
|
+
onClick: handleBackdropClick
|
|
49
|
+
}), /* @__PURE__ */ jsx(Dialog.Popup, {
|
|
50
|
+
"aria-label": popupAriaLabel,
|
|
51
|
+
initialFocus,
|
|
52
|
+
finalFocus,
|
|
53
|
+
className: "fixed inset-0 z-70 flex items-center justify-center p-5 outline-none sm:p-8",
|
|
54
|
+
onClick: handleBackdropClick,
|
|
55
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, {
|
|
56
|
+
initial: false,
|
|
57
|
+
children: isOpen ? /* @__PURE__ */ jsx(motion.div, {
|
|
58
|
+
layout: !prefersReducedMotion,
|
|
59
|
+
layoutId: prefersReducedMotion ? void 0 : layoutId,
|
|
60
|
+
initial: prefersReducedMotion ? {
|
|
61
|
+
opacity: 0,
|
|
62
|
+
scale: .98
|
|
63
|
+
} : false,
|
|
64
|
+
animate: {
|
|
65
|
+
opacity: 1,
|
|
66
|
+
scale: 1
|
|
67
|
+
},
|
|
68
|
+
exit: prefersReducedMotion ? {
|
|
69
|
+
opacity: 0,
|
|
70
|
+
scale: .98
|
|
71
|
+
} : void 0,
|
|
72
|
+
transition: prefersReducedMotion ? transitions.fade : springs.quick,
|
|
73
|
+
className: cn("pointer-events-auto max-h-[90vh] max-w-[90vw]", popupClassName),
|
|
74
|
+
onClick: handleContentClick,
|
|
75
|
+
children: contentClosesDialog ? /* @__PURE__ */ jsx(Dialog.Close, {
|
|
76
|
+
className: "block cursor-pointer appearance-none border-0 bg-transparent p-0 text-left outline-none",
|
|
77
|
+
children: content
|
|
78
|
+
}) : content
|
|
79
|
+
}, "morph-dialog-content") : null
|
|
80
|
+
})
|
|
81
|
+
})] })]
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { MorphDialog };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/components/primitives/Price.d.ts
|
|
4
|
+
type FormatPriceOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Enable K / M / B abbreviation for large values.
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
abbreviate?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum decimal digits to display. Excess precision is ceiled (rounded up)
|
|
12
|
+
* for safety in price contexts. When `undefined`, full precision is shown
|
|
13
|
+
* (trailing zeros stripped).
|
|
14
|
+
*/
|
|
15
|
+
maxDecimals?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Minimum decimal digits to display. Pads trailing zeros when needed.
|
|
18
|
+
*/
|
|
19
|
+
minDecimals?: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Format a bigint price value into a human-readable string.
|
|
23
|
+
* All arithmetic stays in bigint to preserve precision.
|
|
24
|
+
*/
|
|
25
|
+
declare function formatPrice(value: bigint | number | string, decimals: number, options?: FormatPriceOptions): string;
|
|
26
|
+
interface PriceProps extends React.ComponentProps<"span"> {
|
|
27
|
+
/** The raw integer price value. */
|
|
28
|
+
value: bigint | number | string;
|
|
29
|
+
/** Number of decimal places for the value. */
|
|
30
|
+
decimals: number;
|
|
31
|
+
/** Enable K / M / B abbreviation for large values. */
|
|
32
|
+
abbreviate?: boolean;
|
|
33
|
+
/** Max decimal digits to show. Excess is ceiled (rounded up). */
|
|
34
|
+
maxDecimals?: number;
|
|
35
|
+
/** Minimum decimal digits to show. Pads trailing zeros when needed. */
|
|
36
|
+
minDecimals?: number;
|
|
37
|
+
}
|
|
38
|
+
interface PriceValueProps extends React.ComponentProps<"span"> {
|
|
39
|
+
/**
|
|
40
|
+
* A BCP 47 locale tag (e.g. `"de-DE"`, `"ja-JP"`) used to localise
|
|
41
|
+
* decimal and thousands separators. When omitted the raw formatted string
|
|
42
|
+
* is rendered as-is (dot decimal separator, no grouping).
|
|
43
|
+
*/
|
|
44
|
+
locale?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Renders the formatted numeric value. Reads config from the parent `<Price>`.
|
|
48
|
+
*/
|
|
49
|
+
declare function PriceValue({
|
|
50
|
+
locale,
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: PriceValueProps): React.ReactElement;
|
|
54
|
+
interface PriceSymbolProps extends React.ComponentProps<"span"> {}
|
|
55
|
+
/**
|
|
56
|
+
* Renders a currency symbol or label. Just a styled `<span>` — you control
|
|
57
|
+
* placement by ordering it before or after `<Price.Value>`.
|
|
58
|
+
*
|
|
59
|
+
* @example Suffix (default pattern)
|
|
60
|
+
* ```tsx
|
|
61
|
+
* <Price value={1000000n} decimals={6}>
|
|
62
|
+
* <Price.Value /> <Price.Symbol>USD</Price.Symbol>
|
|
63
|
+
* </Price>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @example Prefix
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <Price value={12345n} decimals={2}>
|
|
69
|
+
* <Price.Symbol>$</Price.Symbol><Price.Value />
|
|
70
|
+
* </Price>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
declare function PriceSymbol({
|
|
74
|
+
children,
|
|
75
|
+
className,
|
|
76
|
+
...props
|
|
77
|
+
}: PriceSymbolProps): React.ReactElement;
|
|
78
|
+
declare const Price: {
|
|
79
|
+
(props: PriceProps): React.ReactElement;
|
|
80
|
+
Value: typeof PriceValue;
|
|
81
|
+
Symbol: typeof PriceSymbol;
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
export { FormatPriceOptions, Price, PriceProps, PriceSymbolProps, PriceValueProps, formatPrice };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/components/primitives/Price.tsx
|
|
7
|
+
function bigIntPow(base, exp) {
|
|
8
|
+
let result = 1n;
|
|
9
|
+
let b = base;
|
|
10
|
+
let e = exp;
|
|
11
|
+
while (e > 0) {
|
|
12
|
+
if (e & 1) result *= b;
|
|
13
|
+
b *= b;
|
|
14
|
+
e >>= 1;
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
var BigIntDecimal = class BigIntDecimal {
|
|
19
|
+
constructor(int, decimals) {
|
|
20
|
+
this.int = int;
|
|
21
|
+
this.decimals = decimals;
|
|
22
|
+
}
|
|
23
|
+
clone() {
|
|
24
|
+
return new BigIntDecimal(this.int, this.decimals);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Remove trailing zeros after the decimal point without losing precision.
|
|
28
|
+
*/
|
|
29
|
+
optimizeDecimals() {
|
|
30
|
+
const base = 10n;
|
|
31
|
+
while (this.decimals > 0 && this.int % base === 0n) {
|
|
32
|
+
this.decimals--;
|
|
33
|
+
this.int = this.int / base;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Round UP (ceil) to `precision` decimal places. If the current number of
|
|
38
|
+
* decimals is already ≤ precision, only trailing zeros are stripped.
|
|
39
|
+
*/
|
|
40
|
+
ceil(precision) {
|
|
41
|
+
if (precision < 0) throw new Error("decimal precision < 0");
|
|
42
|
+
if (this.decimals === 0) return;
|
|
43
|
+
if (this.decimals <= precision) {
|
|
44
|
+
this.optimizeDecimals();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const base = 10n;
|
|
48
|
+
while (this.decimals > precision) {
|
|
49
|
+
if (this.int % base > 0n) this.int = this.int + base;
|
|
50
|
+
this.decimals--;
|
|
51
|
+
this.int = this.int / base;
|
|
52
|
+
}
|
|
53
|
+
this.optimizeDecimals();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Full-precision decimal string, e.g. "1.001" or "0" or "42".
|
|
57
|
+
*/
|
|
58
|
+
toString() {
|
|
59
|
+
const isNegative = this.int < 0n;
|
|
60
|
+
let full = (isNegative ? -this.int : this.int).toString();
|
|
61
|
+
if (this.decimals === 0) return `${isNegative ? "-" : ""}${full}`;
|
|
62
|
+
if (this.decimals >= full.length) full = full.padStart(this.decimals + 1, "0");
|
|
63
|
+
const decIdx = full.length - this.decimals;
|
|
64
|
+
return `${isNegative ? "-" : ""}${full.slice(0, decIdx)}.${full.slice(decIdx)}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Abbreviate large numbers: 100K, 2.5M, etc.
|
|
68
|
+
* Values < 1 are not abbreviated but still have maxDecimals applied.
|
|
69
|
+
*/
|
|
70
|
+
abbreviate(maxDecimals = 3) {
|
|
71
|
+
const symbols = [
|
|
72
|
+
{
|
|
73
|
+
exponent: 9,
|
|
74
|
+
symbol: "B"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
exponent: 6,
|
|
78
|
+
symbol: "M"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
exponent: 3,
|
|
82
|
+
symbol: "K"
|
|
83
|
+
}
|
|
84
|
+
];
|
|
85
|
+
const base = (this.int < 0n ? -this.int : this.int) / bigIntPow(10n, this.decimals);
|
|
86
|
+
if (base < 1n) {
|
|
87
|
+
this.ceil(maxDecimals);
|
|
88
|
+
return this.toString();
|
|
89
|
+
}
|
|
90
|
+
for (const { exponent, symbol } of symbols) if (base >= bigIntPow(10n, exponent)) {
|
|
91
|
+
this.decimals += exponent;
|
|
92
|
+
this.ceil(maxDecimals);
|
|
93
|
+
return this.toString() + symbol;
|
|
94
|
+
}
|
|
95
|
+
this.ceil(maxDecimals);
|
|
96
|
+
return this.toString();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function toBigInt(value) {
|
|
100
|
+
if (typeof value === "bigint") return value;
|
|
101
|
+
try {
|
|
102
|
+
return BigInt(value);
|
|
103
|
+
} catch {
|
|
104
|
+
return BigInt(0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function padMinimumDecimals(formatted, minDecimals) {
|
|
108
|
+
if (minDecimals <= 0) return formatted;
|
|
109
|
+
const suffixMatch = formatted.match(/([KMB])$/);
|
|
110
|
+
const suffix = suffixMatch ? suffixMatch[1] : "";
|
|
111
|
+
const [integerPart, fractionPart = ""] = (suffix ? formatted.slice(0, -1) : formatted).split(".");
|
|
112
|
+
if (fractionPart.length >= minDecimals) return formatted;
|
|
113
|
+
return `${integerPart}.${fractionPart.padEnd(minDecimals, "0")}${suffix}`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Format a bigint price value into a human-readable string.
|
|
117
|
+
* All arithmetic stays in bigint to preserve precision.
|
|
118
|
+
*/
|
|
119
|
+
function formatPrice(value, decimals, options) {
|
|
120
|
+
const num = new BigIntDecimal(toBigInt(value), decimals);
|
|
121
|
+
num.optimizeDecimals();
|
|
122
|
+
if (options?.abbreviate) return num.abbreviate(options.maxDecimals ?? 3);
|
|
123
|
+
if (typeof options?.maxDecimals === "number") num.ceil(options.maxDecimals);
|
|
124
|
+
const formatted = num.toString();
|
|
125
|
+
if (typeof options?.minDecimals === "number") return padMinimumDecimals(formatted, options.minDecimals);
|
|
126
|
+
return formatted;
|
|
127
|
+
}
|
|
128
|
+
const PriceContext = React.createContext(null);
|
|
129
|
+
function usePriceContext() {
|
|
130
|
+
const ctx = React.useContext(PriceContext);
|
|
131
|
+
if (!ctx) throw new Error("Price compound components must be used within a <Price> root");
|
|
132
|
+
return ctx;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Root component. Can be used standalone (renders the formatted number) or
|
|
136
|
+
* with compound children for full control over symbol placement and layout.
|
|
137
|
+
*
|
|
138
|
+
* @example Standalone
|
|
139
|
+
* ```tsx
|
|
140
|
+
* <Price value={500000000000000000n} decimals={18} maxDecimals={4} />
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @example Composed
|
|
144
|
+
* ```tsx
|
|
145
|
+
* <Price value={500000000000000000n} decimals={18} maxDecimals={4}>
|
|
146
|
+
* <Price.Symbol>USD</Price.Symbol>
|
|
147
|
+
* <Price.Value />
|
|
148
|
+
* </Price>
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
function PriceRoot({ value, decimals, abbreviate = false, maxDecimals, minDecimals, children, className, ...props }) {
|
|
152
|
+
const ctx = useMemo(() => ({
|
|
153
|
+
value,
|
|
154
|
+
decimals,
|
|
155
|
+
abbreviate,
|
|
156
|
+
maxDecimals,
|
|
157
|
+
minDecimals
|
|
158
|
+
}), [
|
|
159
|
+
value,
|
|
160
|
+
decimals,
|
|
161
|
+
abbreviate,
|
|
162
|
+
maxDecimals,
|
|
163
|
+
minDecimals
|
|
164
|
+
]);
|
|
165
|
+
const hasChildren = children !== void 0 && children !== null && children !== true && children !== false;
|
|
166
|
+
return /* @__PURE__ */ jsx(PriceContext, {
|
|
167
|
+
value: ctx,
|
|
168
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
169
|
+
className: cn("tabular-nums", className),
|
|
170
|
+
...props,
|
|
171
|
+
children: hasChildren ? children : /* @__PURE__ */ jsx(PriceValue, {})
|
|
172
|
+
})
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Localise a formatted price string by replacing the decimal separator and
|
|
177
|
+
* inserting thousands separators according to the given locale. Delegates
|
|
178
|
+
* integer grouping to `Intl.NumberFormat` so locale-specific patterns (e.g.
|
|
179
|
+
* Indian lakh grouping) are handled correctly. Works on abbreviated strings
|
|
180
|
+
* like "2.5K" too.
|
|
181
|
+
*/
|
|
182
|
+
function localizeFormatted(formatted, locale) {
|
|
183
|
+
const suffixMatch = formatted.match(/([KMB])$/);
|
|
184
|
+
const suffix = suffixMatch ? suffixMatch[1] : "";
|
|
185
|
+
const [intPart, fracPart] = (suffix ? formatted.slice(0, -1) : formatted).split(".");
|
|
186
|
+
const isNegative = intPart.startsWith("-");
|
|
187
|
+
const absInt = isNegative ? intPart.slice(1) : intPart;
|
|
188
|
+
const intNum = Number(absInt);
|
|
189
|
+
const intFmt = new Intl.NumberFormat(locale, {
|
|
190
|
+
useGrouping: true,
|
|
191
|
+
maximumFractionDigits: 0
|
|
192
|
+
});
|
|
193
|
+
const grouped = (isNegative ? "-" : "") + intFmt.format(intNum);
|
|
194
|
+
if (fracPart === void 0) return grouped + suffix;
|
|
195
|
+
return `${grouped}${new Intl.NumberFormat(locale).formatToParts(1.1).find((p) => p.type === "decimal")?.value ?? "."}${fracPart}${suffix}`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Renders the formatted numeric value. Reads config from the parent `<Price>`.
|
|
199
|
+
*/
|
|
200
|
+
function PriceValue({ locale, className, ...props }) {
|
|
201
|
+
const ctx = usePriceContext();
|
|
202
|
+
const formatted = useMemo(() => {
|
|
203
|
+
const raw = formatPrice(ctx.value, ctx.decimals, {
|
|
204
|
+
abbreviate: ctx.abbreviate,
|
|
205
|
+
maxDecimals: ctx.maxDecimals,
|
|
206
|
+
minDecimals: ctx.minDecimals
|
|
207
|
+
});
|
|
208
|
+
if (locale) return localizeFormatted(raw, locale);
|
|
209
|
+
return raw;
|
|
210
|
+
}, [
|
|
211
|
+
ctx.value,
|
|
212
|
+
ctx.decimals,
|
|
213
|
+
ctx.abbreviate,
|
|
214
|
+
ctx.maxDecimals,
|
|
215
|
+
ctx.minDecimals,
|
|
216
|
+
locale
|
|
217
|
+
]);
|
|
218
|
+
return /* @__PURE__ */ jsx("span", {
|
|
219
|
+
className,
|
|
220
|
+
...props,
|
|
221
|
+
children: formatted
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Renders a currency symbol or label. Just a styled `<span>` — you control
|
|
226
|
+
* placement by ordering it before or after `<Price.Value>`.
|
|
227
|
+
*
|
|
228
|
+
* @example Suffix (default pattern)
|
|
229
|
+
* ```tsx
|
|
230
|
+
* <Price value={1000000n} decimals={6}>
|
|
231
|
+
* <Price.Value /> <Price.Symbol>USD</Price.Symbol>
|
|
232
|
+
* </Price>
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* @example Prefix
|
|
236
|
+
* ```tsx
|
|
237
|
+
* <Price value={12345n} decimals={2}>
|
|
238
|
+
* <Price.Symbol>$</Price.Symbol><Price.Value />
|
|
239
|
+
* </Price>
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
function PriceSymbol({ children, className, ...props }) {
|
|
243
|
+
return /* @__PURE__ */ jsx("span", {
|
|
244
|
+
className,
|
|
245
|
+
...props,
|
|
246
|
+
children
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const Price = Object.assign(PriceRoot, {
|
|
250
|
+
Value: PriceValue,
|
|
251
|
+
Symbol: PriceSymbol
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
export { Price, formatPrice };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SteppedInput } from "./SteppedInput.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/components/primitives/PriceInput.d.ts
|
|
5
|
+
interface PriceInputRootProps {
|
|
6
|
+
value: bigint;
|
|
7
|
+
onChange: (value: bigint) => void;
|
|
8
|
+
min?: bigint;
|
|
9
|
+
max?: bigint;
|
|
10
|
+
getTickSize: (currentValue: bigint) => bigint;
|
|
11
|
+
decimals?: number;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
snapToTick?: "up" | "down" | "nearest" | false;
|
|
16
|
+
}
|
|
17
|
+
declare function Root({
|
|
18
|
+
decimals,
|
|
19
|
+
...props
|
|
20
|
+
}: PriceInputRootProps): React.ReactElement;
|
|
21
|
+
interface PriceInputComponent {
|
|
22
|
+
Root: typeof Root;
|
|
23
|
+
Group: typeof SteppedInput.Group;
|
|
24
|
+
Decrement: typeof SteppedInput.Decrement;
|
|
25
|
+
Increment: typeof SteppedInput.Increment;
|
|
26
|
+
ScrubArea: typeof SteppedInput.ScrubArea;
|
|
27
|
+
ScrubAreaCursor: typeof SteppedInput.ScrubAreaCursor;
|
|
28
|
+
Input: typeof SteppedInput.Input;
|
|
29
|
+
Value: typeof SteppedInput.Value;
|
|
30
|
+
}
|
|
31
|
+
declare const PriceInput: PriceInputComponent;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PriceInput };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { SteppedInput } from "./SteppedInput.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/components/primitives/PriceInput.tsx
|
|
7
|
+
function Root({ decimals = 2, ...props }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(SteppedInput.Root, {
|
|
9
|
+
...props,
|
|
10
|
+
decimals
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const PriceInput = {
|
|
14
|
+
Root,
|
|
15
|
+
Group: SteppedInput.Group,
|
|
16
|
+
Decrement: SteppedInput.Decrement,
|
|
17
|
+
Increment: SteppedInput.Increment,
|
|
18
|
+
ScrubArea: SteppedInput.ScrubArea,
|
|
19
|
+
ScrubAreaCursor: SteppedInput.ScrubAreaCursor,
|
|
20
|
+
Input: SteppedInput.Input,
|
|
21
|
+
Value: SteppedInput.Value
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { PriceInput };
|