@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,711 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
import { Price } from "./Price";
|
|
6
|
+
import { Separator } from "./Separator";
|
|
7
|
+
|
|
8
|
+
function toBigInt(value: bigint | number | string): bigint {
|
|
9
|
+
if (typeof value === "bigint") return value;
|
|
10
|
+
try {
|
|
11
|
+
return BigInt(value);
|
|
12
|
+
} catch {
|
|
13
|
+
return BigInt(0);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function scaleValue(
|
|
18
|
+
value: bigint | number | string,
|
|
19
|
+
fromDecimals: number,
|
|
20
|
+
toDecimals: number,
|
|
21
|
+
): bigint {
|
|
22
|
+
const normalizedValue = toBigInt(value);
|
|
23
|
+
if (fromDecimals === toDecimals) return normalizedValue;
|
|
24
|
+
const scale = BigInt(10 ** Math.abs(toDecimals - fromDecimals));
|
|
25
|
+
return fromDecimals < toDecimals
|
|
26
|
+
? normalizedValue * scale
|
|
27
|
+
: normalizedValue / scale;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function computeTaxFromRate(
|
|
31
|
+
subtotal: bigint,
|
|
32
|
+
decimals: number,
|
|
33
|
+
rate: number,
|
|
34
|
+
): bigint {
|
|
35
|
+
const subtotalNum = Number(subtotal) / 10 ** decimals;
|
|
36
|
+
const taxNum = subtotalNum * rate;
|
|
37
|
+
const multiplier = BigInt(10 ** decimals);
|
|
38
|
+
return BigInt(Math.round(taxNum * Number(multiplier)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type ReceiptRowKind =
|
|
42
|
+
| "item"
|
|
43
|
+
| "discount"
|
|
44
|
+
| "fee"
|
|
45
|
+
| "subtotal"
|
|
46
|
+
| "tax"
|
|
47
|
+
| "total";
|
|
48
|
+
|
|
49
|
+
type ComputedReceiptValues = {
|
|
50
|
+
subtotals: Map<number, bigint>;
|
|
51
|
+
taxes: Map<number, bigint>;
|
|
52
|
+
totals: Map<number, bigint>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type ReceiptContextValue = {
|
|
56
|
+
decimals: number;
|
|
57
|
+
computed: ComputedReceiptValues;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const ReceiptContext = React.createContext<ReceiptContextValue | null>(null);
|
|
61
|
+
|
|
62
|
+
function useReceiptContext() {
|
|
63
|
+
const context = React.useContext(ReceiptContext);
|
|
64
|
+
if (!context) {
|
|
65
|
+
throw new Error("Receipt components must be used within a Receipt root");
|
|
66
|
+
}
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type ReceiptPriceDefaults = {
|
|
71
|
+
template: React.ReactNode;
|
|
72
|
+
maxDecimals: number | undefined;
|
|
73
|
+
abbreviate: boolean;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const ReceiptPriceContext = React.createContext<ReceiptPriceDefaults | null>(
|
|
77
|
+
null,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
function useReceiptPriceDefaults() {
|
|
81
|
+
return React.useContext(ReceiptPriceContext);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type ReceiptCalculationDescriptor = {
|
|
85
|
+
index: number;
|
|
86
|
+
kind: ReceiptRowKind;
|
|
87
|
+
decimals: number;
|
|
88
|
+
value?: bigint | number | string;
|
|
89
|
+
rate?: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type ReceiptInternalIndexProp = {
|
|
93
|
+
__receiptIndex?: number;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function isElementOfType<P>(
|
|
97
|
+
child: React.ReactNode,
|
|
98
|
+
component: React.ComponentType<P>,
|
|
99
|
+
): child is React.ReactElement<P> {
|
|
100
|
+
return React.isValidElement(child) && child.type === component;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getDisplayValue(
|
|
104
|
+
explicitValue: bigint | number | string | undefined,
|
|
105
|
+
fallbackValue: bigint,
|
|
106
|
+
): bigint {
|
|
107
|
+
return explicitValue !== undefined ? toBigInt(explicitValue) : fallbackValue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function computeReceiptValues(
|
|
111
|
+
descriptors: ReceiptCalculationDescriptor[],
|
|
112
|
+
receiptDecimals: number,
|
|
113
|
+
): ComputedReceiptValues {
|
|
114
|
+
const subtotals = new Map<number, bigint>();
|
|
115
|
+
const taxes = new Map<number, bigint>();
|
|
116
|
+
const totals = new Map<number, bigint>();
|
|
117
|
+
|
|
118
|
+
let subtotalAccumulator = BigInt(0);
|
|
119
|
+
let positiveAccumulator = BigInt(0);
|
|
120
|
+
let negativeAccumulator = BigInt(0);
|
|
121
|
+
|
|
122
|
+
for (const descriptor of descriptors) {
|
|
123
|
+
switch (descriptor.kind) {
|
|
124
|
+
case "item": {
|
|
125
|
+
const scaledValue = scaleValue(
|
|
126
|
+
descriptor.value ?? 0,
|
|
127
|
+
descriptor.decimals,
|
|
128
|
+
receiptDecimals,
|
|
129
|
+
);
|
|
130
|
+
subtotalAccumulator += scaledValue;
|
|
131
|
+
positiveAccumulator += scaledValue;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "discount": {
|
|
135
|
+
const scaledValue = scaleValue(
|
|
136
|
+
descriptor.value ?? 0,
|
|
137
|
+
descriptor.decimals,
|
|
138
|
+
receiptDecimals,
|
|
139
|
+
);
|
|
140
|
+
negativeAccumulator += scaledValue;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "fee": {
|
|
144
|
+
const scaledValue = scaleValue(
|
|
145
|
+
descriptor.value ?? 0,
|
|
146
|
+
descriptor.decimals,
|
|
147
|
+
receiptDecimals,
|
|
148
|
+
);
|
|
149
|
+
positiveAccumulator += scaledValue;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "subtotal": {
|
|
153
|
+
subtotals.set(descriptor.index, subtotalAccumulator);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "tax": {
|
|
157
|
+
const explicitValue = descriptor.value;
|
|
158
|
+
const computedTax =
|
|
159
|
+
explicitValue !== undefined
|
|
160
|
+
? scaleValue(explicitValue, descriptor.decimals, receiptDecimals)
|
|
161
|
+
: descriptor.rate !== undefined
|
|
162
|
+
? computeTaxFromRate(
|
|
163
|
+
scaleValue(
|
|
164
|
+
subtotalAccumulator,
|
|
165
|
+
receiptDecimals,
|
|
166
|
+
descriptor.decimals,
|
|
167
|
+
),
|
|
168
|
+
descriptor.decimals,
|
|
169
|
+
descriptor.rate,
|
|
170
|
+
)
|
|
171
|
+
: BigInt(0);
|
|
172
|
+
taxes.set(
|
|
173
|
+
descriptor.index,
|
|
174
|
+
scaleValue(computedTax, descriptor.decimals, receiptDecimals),
|
|
175
|
+
);
|
|
176
|
+
positiveAccumulator += scaleValue(
|
|
177
|
+
computedTax,
|
|
178
|
+
descriptor.decimals,
|
|
179
|
+
receiptDecimals,
|
|
180
|
+
);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "total": {
|
|
184
|
+
totals.set(descriptor.index, positiveAccumulator - negativeAccumulator);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { subtotals, taxes, totals };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface ReceiptProps extends React.ComponentProps<"div"> {
|
|
194
|
+
decimals?: number;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface ReceiptPriceProps {
|
|
198
|
+
maxDecimals?: number;
|
|
199
|
+
abbreviate?: boolean;
|
|
200
|
+
children: React.ReactNode;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function ReceiptPrice(_props: ReceiptPriceProps): React.ReactElement {
|
|
204
|
+
return null as unknown as React.ReactElement;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface ReceiptHeaderProps extends React.ComponentProps<"div"> {}
|
|
208
|
+
|
|
209
|
+
function ReceiptHeader({
|
|
210
|
+
children,
|
|
211
|
+
className,
|
|
212
|
+
...props
|
|
213
|
+
}: ReceiptHeaderProps): React.ReactElement {
|
|
214
|
+
return (
|
|
215
|
+
<div className={cn("text-foreground pb-2", className)} {...props}>
|
|
216
|
+
{children}
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface ReceiptItemProps
|
|
222
|
+
extends Omit<React.ComponentProps<"div">, "children">,
|
|
223
|
+
ReceiptInternalIndexProp {
|
|
224
|
+
label: string;
|
|
225
|
+
value: bigint | number | string;
|
|
226
|
+
decimals?: number;
|
|
227
|
+
maxDecimals?: number;
|
|
228
|
+
abbreviate?: boolean;
|
|
229
|
+
children?: React.ReactNode;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function ReceiptItem({
|
|
233
|
+
label,
|
|
234
|
+
value,
|
|
235
|
+
decimals,
|
|
236
|
+
maxDecimals,
|
|
237
|
+
abbreviate,
|
|
238
|
+
children,
|
|
239
|
+
className,
|
|
240
|
+
__receiptIndex: _receiptIndex,
|
|
241
|
+
...props
|
|
242
|
+
}: ReceiptItemProps): React.ReactElement {
|
|
243
|
+
const context = useReceiptContext();
|
|
244
|
+
const defaults = useReceiptPriceDefaults();
|
|
245
|
+
const itemDecimals = decimals ?? context.decimals;
|
|
246
|
+
const priceChildren = children ?? defaults?.template;
|
|
247
|
+
const priceMaxDecimals = maxDecimals ?? defaults?.maxDecimals;
|
|
248
|
+
const priceAbbreviate = abbreviate ?? defaults?.abbreviate;
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div
|
|
252
|
+
className={cn("flex items-center justify-between gap-4", className)}
|
|
253
|
+
{...props}
|
|
254
|
+
>
|
|
255
|
+
<span className="text-muted-foreground">{label}</span>
|
|
256
|
+
<Price
|
|
257
|
+
value={value}
|
|
258
|
+
decimals={itemDecimals}
|
|
259
|
+
maxDecimals={priceMaxDecimals}
|
|
260
|
+
abbreviate={priceAbbreviate}
|
|
261
|
+
className="text-foreground"
|
|
262
|
+
>
|
|
263
|
+
{priceChildren}
|
|
264
|
+
</Price>
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export interface ReceiptSeparatorProps
|
|
270
|
+
extends React.ComponentProps<typeof Separator> {}
|
|
271
|
+
|
|
272
|
+
function ReceiptSeparator({
|
|
273
|
+
className,
|
|
274
|
+
...props
|
|
275
|
+
}: ReceiptSeparatorProps): React.ReactElement {
|
|
276
|
+
return <Separator className={cn("my-1", className)} {...props} />;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface ReceiptSubtotalProps
|
|
280
|
+
extends React.ComponentProps<"div">,
|
|
281
|
+
ReceiptInternalIndexProp {
|
|
282
|
+
decimals?: number;
|
|
283
|
+
maxDecimals?: number;
|
|
284
|
+
abbreviate?: boolean;
|
|
285
|
+
label?: string;
|
|
286
|
+
value?: bigint | number | string;
|
|
287
|
+
children?: React.ReactNode;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function ReceiptSubtotal({
|
|
291
|
+
label = "Subtotal",
|
|
292
|
+
value,
|
|
293
|
+
decimals,
|
|
294
|
+
maxDecimals,
|
|
295
|
+
abbreviate,
|
|
296
|
+
children,
|
|
297
|
+
className,
|
|
298
|
+
__receiptIndex: _receiptIndex,
|
|
299
|
+
...props
|
|
300
|
+
}: ReceiptSubtotalProps): React.ReactElement {
|
|
301
|
+
const context = useReceiptContext();
|
|
302
|
+
const defaults = useReceiptPriceDefaults();
|
|
303
|
+
const displayDecimals = decimals ?? context.decimals;
|
|
304
|
+
const priceChildren = children ?? defaults?.template;
|
|
305
|
+
const priceMaxDecimals = maxDecimals ?? defaults?.maxDecimals;
|
|
306
|
+
const priceAbbreviate = abbreviate ?? defaults?.abbreviate;
|
|
307
|
+
const computedSubtotal =
|
|
308
|
+
_receiptIndex !== undefined
|
|
309
|
+
? (context.computed.subtotals.get(_receiptIndex) ?? BigInt(0))
|
|
310
|
+
: BigInt(0);
|
|
311
|
+
const displayValue = getDisplayValue(
|
|
312
|
+
value,
|
|
313
|
+
scaleValue(computedSubtotal, context.decimals, displayDecimals),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<div
|
|
318
|
+
className={cn("flex items-center justify-between gap-4", className)}
|
|
319
|
+
{...props}
|
|
320
|
+
>
|
|
321
|
+
<span className="text-muted-foreground">{label}</span>
|
|
322
|
+
<Price
|
|
323
|
+
value={displayValue}
|
|
324
|
+
decimals={displayDecimals}
|
|
325
|
+
maxDecimals={priceMaxDecimals}
|
|
326
|
+
abbreviate={priceAbbreviate}
|
|
327
|
+
className="text-foreground"
|
|
328
|
+
>
|
|
329
|
+
{priceChildren}
|
|
330
|
+
</Price>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export interface ReceiptDiscountProps
|
|
336
|
+
extends Omit<React.ComponentProps<"div">, "children">,
|
|
337
|
+
ReceiptInternalIndexProp {
|
|
338
|
+
label: string;
|
|
339
|
+
value: bigint | number | string;
|
|
340
|
+
decimals?: number;
|
|
341
|
+
maxDecimals?: number;
|
|
342
|
+
abbreviate?: boolean;
|
|
343
|
+
children?: React.ReactNode;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function ReceiptDiscount({
|
|
347
|
+
label,
|
|
348
|
+
value,
|
|
349
|
+
decimals,
|
|
350
|
+
maxDecimals,
|
|
351
|
+
abbreviate,
|
|
352
|
+
children,
|
|
353
|
+
className,
|
|
354
|
+
__receiptIndex: _receiptIndex,
|
|
355
|
+
...props
|
|
356
|
+
}: ReceiptDiscountProps): React.ReactElement {
|
|
357
|
+
const context = useReceiptContext();
|
|
358
|
+
const defaults = useReceiptPriceDefaults();
|
|
359
|
+
const displayDecimals = decimals ?? context.decimals;
|
|
360
|
+
const priceChildren = children ?? defaults?.template;
|
|
361
|
+
const priceMaxDecimals = maxDecimals ?? defaults?.maxDecimals;
|
|
362
|
+
const priceAbbreviate = abbreviate ?? defaults?.abbreviate;
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<div
|
|
366
|
+
className={cn("flex items-center justify-between gap-4", className)}
|
|
367
|
+
{...props}
|
|
368
|
+
>
|
|
369
|
+
<span className="text-muted-foreground">{label}</span>
|
|
370
|
+
<span className="text-foreground">
|
|
371
|
+
-
|
|
372
|
+
<Price
|
|
373
|
+
value={value}
|
|
374
|
+
decimals={displayDecimals}
|
|
375
|
+
maxDecimals={priceMaxDecimals}
|
|
376
|
+
abbreviate={priceAbbreviate}
|
|
377
|
+
className="text-foreground"
|
|
378
|
+
>
|
|
379
|
+
{priceChildren}
|
|
380
|
+
</Price>
|
|
381
|
+
</span>
|
|
382
|
+
</div>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export interface ReceiptFeeProps
|
|
387
|
+
extends Omit<React.ComponentProps<"div">, "children">,
|
|
388
|
+
ReceiptInternalIndexProp {
|
|
389
|
+
label: string;
|
|
390
|
+
value: bigint | number | string;
|
|
391
|
+
decimals?: number;
|
|
392
|
+
maxDecimals?: number;
|
|
393
|
+
abbreviate?: boolean;
|
|
394
|
+
children?: React.ReactNode;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function ReceiptFee({
|
|
398
|
+
label,
|
|
399
|
+
value,
|
|
400
|
+
decimals,
|
|
401
|
+
maxDecimals,
|
|
402
|
+
abbreviate,
|
|
403
|
+
children,
|
|
404
|
+
className,
|
|
405
|
+
__receiptIndex: _receiptIndex,
|
|
406
|
+
...props
|
|
407
|
+
}: ReceiptFeeProps): React.ReactElement {
|
|
408
|
+
const context = useReceiptContext();
|
|
409
|
+
const defaults = useReceiptPriceDefaults();
|
|
410
|
+
const displayDecimals = decimals ?? context.decimals;
|
|
411
|
+
const priceChildren = children ?? defaults?.template;
|
|
412
|
+
const priceMaxDecimals = maxDecimals ?? defaults?.maxDecimals;
|
|
413
|
+
const priceAbbreviate = abbreviate ?? defaults?.abbreviate;
|
|
414
|
+
|
|
415
|
+
return (
|
|
416
|
+
<div
|
|
417
|
+
className={cn("flex items-center justify-between gap-4", className)}
|
|
418
|
+
{...props}
|
|
419
|
+
>
|
|
420
|
+
<span className="text-muted-foreground">{label}</span>
|
|
421
|
+
<Price
|
|
422
|
+
value={value}
|
|
423
|
+
decimals={displayDecimals}
|
|
424
|
+
maxDecimals={priceMaxDecimals}
|
|
425
|
+
abbreviate={priceAbbreviate}
|
|
426
|
+
className="text-foreground"
|
|
427
|
+
>
|
|
428
|
+
{priceChildren}
|
|
429
|
+
</Price>
|
|
430
|
+
</div>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export interface ReceiptTaxProps
|
|
435
|
+
extends Omit<React.ComponentProps<"div">, "children">,
|
|
436
|
+
ReceiptInternalIndexProp {
|
|
437
|
+
label?: string;
|
|
438
|
+
value?: bigint | number | string;
|
|
439
|
+
rate?: number;
|
|
440
|
+
decimals?: number;
|
|
441
|
+
maxDecimals?: number;
|
|
442
|
+
abbreviate?: boolean;
|
|
443
|
+
children?: React.ReactNode;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function ReceiptTax({
|
|
447
|
+
label = "Tax",
|
|
448
|
+
value,
|
|
449
|
+
rate,
|
|
450
|
+
decimals,
|
|
451
|
+
maxDecimals,
|
|
452
|
+
abbreviate,
|
|
453
|
+
children,
|
|
454
|
+
className,
|
|
455
|
+
__receiptIndex: _receiptIndex,
|
|
456
|
+
...props
|
|
457
|
+
}: ReceiptTaxProps): React.ReactElement {
|
|
458
|
+
const context = useReceiptContext();
|
|
459
|
+
const defaults = useReceiptPriceDefaults();
|
|
460
|
+
const displayDecimals = decimals ?? context.decimals;
|
|
461
|
+
const priceChildren = children ?? defaults?.template;
|
|
462
|
+
const priceMaxDecimals = maxDecimals ?? defaults?.maxDecimals;
|
|
463
|
+
const priceAbbreviate = abbreviate ?? defaults?.abbreviate;
|
|
464
|
+
const computedTax =
|
|
465
|
+
_receiptIndex !== undefined
|
|
466
|
+
? (context.computed.taxes.get(_receiptIndex) ?? BigInt(0))
|
|
467
|
+
: BigInt(0);
|
|
468
|
+
const displayValue = getDisplayValue(
|
|
469
|
+
value,
|
|
470
|
+
scaleValue(computedTax, context.decimals, displayDecimals),
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
return (
|
|
474
|
+
<div
|
|
475
|
+
className={cn("flex items-center justify-between gap-4", className)}
|
|
476
|
+
{...props}
|
|
477
|
+
>
|
|
478
|
+
<span className="text-muted-foreground">
|
|
479
|
+
{label}
|
|
480
|
+
{rate !== undefined && (
|
|
481
|
+
<span> ({parseFloat((rate * 100).toPrecision(10))}%)</span>
|
|
482
|
+
)}
|
|
483
|
+
</span>
|
|
484
|
+
<Price
|
|
485
|
+
value={displayValue}
|
|
486
|
+
decimals={displayDecimals}
|
|
487
|
+
maxDecimals={priceMaxDecimals}
|
|
488
|
+
abbreviate={priceAbbreviate}
|
|
489
|
+
className="text-foreground"
|
|
490
|
+
>
|
|
491
|
+
{priceChildren}
|
|
492
|
+
</Price>
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export interface ReceiptTotalProps
|
|
498
|
+
extends React.ComponentProps<"div">,
|
|
499
|
+
ReceiptInternalIndexProp {
|
|
500
|
+
label?: string;
|
|
501
|
+
value?: bigint | number | string;
|
|
502
|
+
decimals?: number;
|
|
503
|
+
maxDecimals?: number;
|
|
504
|
+
abbreviate?: boolean;
|
|
505
|
+
children?: React.ReactNode;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function ReceiptTotal({
|
|
509
|
+
label = "Total",
|
|
510
|
+
value,
|
|
511
|
+
decimals,
|
|
512
|
+
maxDecimals,
|
|
513
|
+
abbreviate,
|
|
514
|
+
children,
|
|
515
|
+
className,
|
|
516
|
+
__receiptIndex: _receiptIndex,
|
|
517
|
+
...props
|
|
518
|
+
}: ReceiptTotalProps): React.ReactElement {
|
|
519
|
+
const context = useReceiptContext();
|
|
520
|
+
const defaults = useReceiptPriceDefaults();
|
|
521
|
+
const displayDecimals = decimals ?? context.decimals;
|
|
522
|
+
const priceChildren = children ?? defaults?.template;
|
|
523
|
+
const priceMaxDecimals = maxDecimals ?? defaults?.maxDecimals;
|
|
524
|
+
const priceAbbreviate = abbreviate ?? defaults?.abbreviate;
|
|
525
|
+
const computedTotal =
|
|
526
|
+
_receiptIndex !== undefined
|
|
527
|
+
? (context.computed.totals.get(_receiptIndex) ?? BigInt(0))
|
|
528
|
+
: BigInt(0);
|
|
529
|
+
const displayValue = getDisplayValue(
|
|
530
|
+
value,
|
|
531
|
+
scaleValue(computedTotal, context.decimals, displayDecimals),
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<div
|
|
536
|
+
className={cn(
|
|
537
|
+
"text-foreground flex items-center justify-between gap-4 pt-2 font-medium",
|
|
538
|
+
className,
|
|
539
|
+
)}
|
|
540
|
+
{...props}
|
|
541
|
+
>
|
|
542
|
+
<span>{label}</span>
|
|
543
|
+
<span className="text-foreground">
|
|
544
|
+
<Price
|
|
545
|
+
value={displayValue}
|
|
546
|
+
decimals={displayDecimals}
|
|
547
|
+
maxDecimals={priceMaxDecimals}
|
|
548
|
+
abbreviate={priceAbbreviate}
|
|
549
|
+
className="text-foreground"
|
|
550
|
+
>
|
|
551
|
+
{priceChildren}
|
|
552
|
+
</Price>
|
|
553
|
+
</span>
|
|
554
|
+
</div>
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export interface ReceiptFooterProps extends React.ComponentProps<"div"> {}
|
|
559
|
+
|
|
560
|
+
function ReceiptFooter({
|
|
561
|
+
children,
|
|
562
|
+
className,
|
|
563
|
+
...props
|
|
564
|
+
}: ReceiptFooterProps): React.ReactElement {
|
|
565
|
+
return (
|
|
566
|
+
<div
|
|
567
|
+
className={cn(
|
|
568
|
+
"pt-2 text-xs leading-[18px] text-muted-foreground",
|
|
569
|
+
className,
|
|
570
|
+
)}
|
|
571
|
+
{...props}
|
|
572
|
+
>
|
|
573
|
+
{children}
|
|
574
|
+
</div>
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function ReceiptRoot({
|
|
579
|
+
children,
|
|
580
|
+
className,
|
|
581
|
+
decimals = 6,
|
|
582
|
+
...props
|
|
583
|
+
}: ReceiptProps): React.ReactElement {
|
|
584
|
+
let priceDefaults: ReceiptPriceDefaults | null = null;
|
|
585
|
+
const calculationDescriptors: ReceiptCalculationDescriptor[] = [];
|
|
586
|
+
|
|
587
|
+
const renderedChildren = React.Children.toArray(children).flatMap(
|
|
588
|
+
(child, index) => {
|
|
589
|
+
if (isElementOfType(child, ReceiptPrice)) {
|
|
590
|
+
const {
|
|
591
|
+
children: template,
|
|
592
|
+
maxDecimals,
|
|
593
|
+
abbreviate = false,
|
|
594
|
+
} = child.props as ReceiptPriceProps;
|
|
595
|
+
priceDefaults = { template, maxDecimals, abbreviate };
|
|
596
|
+
return [];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!React.isValidElement(child)) {
|
|
600
|
+
return [child];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (isElementOfType(child, ReceiptItem)) {
|
|
604
|
+
calculationDescriptors.push({
|
|
605
|
+
index,
|
|
606
|
+
kind: "item",
|
|
607
|
+
value: child.props.value,
|
|
608
|
+
decimals: child.props.decimals ?? decimals,
|
|
609
|
+
});
|
|
610
|
+
} else if (isElementOfType(child, ReceiptDiscount)) {
|
|
611
|
+
calculationDescriptors.push({
|
|
612
|
+
index,
|
|
613
|
+
kind: "discount",
|
|
614
|
+
value: child.props.value,
|
|
615
|
+
decimals: child.props.decimals ?? decimals,
|
|
616
|
+
});
|
|
617
|
+
} else if (isElementOfType(child, ReceiptFee)) {
|
|
618
|
+
calculationDescriptors.push({
|
|
619
|
+
index,
|
|
620
|
+
kind: "fee",
|
|
621
|
+
value: child.props.value,
|
|
622
|
+
decimals: child.props.decimals ?? decimals,
|
|
623
|
+
});
|
|
624
|
+
} else if (isElementOfType(child, ReceiptSubtotal)) {
|
|
625
|
+
calculationDescriptors.push({
|
|
626
|
+
index,
|
|
627
|
+
kind: "subtotal",
|
|
628
|
+
value: child.props.value,
|
|
629
|
+
decimals: child.props.decimals ?? decimals,
|
|
630
|
+
});
|
|
631
|
+
} else if (isElementOfType(child, ReceiptTax)) {
|
|
632
|
+
calculationDescriptors.push({
|
|
633
|
+
index,
|
|
634
|
+
kind: "tax",
|
|
635
|
+
value: child.props.value,
|
|
636
|
+
rate: child.props.rate,
|
|
637
|
+
decimals: child.props.decimals ?? decimals,
|
|
638
|
+
});
|
|
639
|
+
} else if (isElementOfType(child, ReceiptTotal)) {
|
|
640
|
+
calculationDescriptors.push({
|
|
641
|
+
index,
|
|
642
|
+
kind: "total",
|
|
643
|
+
value: child.props.value,
|
|
644
|
+
decimals: child.props.decimals ?? decimals,
|
|
645
|
+
});
|
|
646
|
+
} else {
|
|
647
|
+
return [child];
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return [
|
|
651
|
+
React.cloneElement(
|
|
652
|
+
child as React.ReactElement<ReceiptInternalIndexProp>,
|
|
653
|
+
{
|
|
654
|
+
__receiptIndex: index,
|
|
655
|
+
},
|
|
656
|
+
),
|
|
657
|
+
];
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const computed = React.useMemo(
|
|
662
|
+
() => computeReceiptValues(calculationDescriptors, decimals),
|
|
663
|
+
[decimals],
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
const contextValue = React.useMemo(
|
|
667
|
+
() => ({ decimals, computed }),
|
|
668
|
+
[decimals, computed],
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
return (
|
|
672
|
+
<ReceiptContext value={contextValue}>
|
|
673
|
+
<ReceiptPriceContext value={priceDefaults}>
|
|
674
|
+
<div
|
|
675
|
+
className={cn(
|
|
676
|
+
"flex flex-col gap-2 text-sm leading-[21px] text-foreground",
|
|
677
|
+
className,
|
|
678
|
+
)}
|
|
679
|
+
{...props}
|
|
680
|
+
>
|
|
681
|
+
{renderedChildren}
|
|
682
|
+
</div>
|
|
683
|
+
</ReceiptPriceContext>
|
|
684
|
+
</ReceiptContext>
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export const Receipt: {
|
|
689
|
+
(props: ReceiptProps): React.ReactElement;
|
|
690
|
+
Price: typeof ReceiptPrice;
|
|
691
|
+
Header: typeof ReceiptHeader;
|
|
692
|
+
Item: typeof ReceiptItem;
|
|
693
|
+
Separator: typeof ReceiptSeparator;
|
|
694
|
+
Subtotal: typeof ReceiptSubtotal;
|
|
695
|
+
Discount: typeof ReceiptDiscount;
|
|
696
|
+
Fee: typeof ReceiptFee;
|
|
697
|
+
Tax: typeof ReceiptTax;
|
|
698
|
+
Total: typeof ReceiptTotal;
|
|
699
|
+
Footer: typeof ReceiptFooter;
|
|
700
|
+
} = Object.assign(ReceiptRoot, {
|
|
701
|
+
Price: ReceiptPrice,
|
|
702
|
+
Header: ReceiptHeader,
|
|
703
|
+
Item: ReceiptItem,
|
|
704
|
+
Separator: ReceiptSeparator,
|
|
705
|
+
Subtotal: ReceiptSubtotal,
|
|
706
|
+
Discount: ReceiptDiscount,
|
|
707
|
+
Fee: ReceiptFee,
|
|
708
|
+
Tax: ReceiptTax,
|
|
709
|
+
Total: ReceiptTotal,
|
|
710
|
+
Footer: ReceiptFooter,
|
|
711
|
+
}) as typeof Receipt;
|