@m3000/market 0.0.1 → 0.0.2
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/styles.css +2 -0
- package/dist/tokens.css +2 -0
- package/package.json +10 -10
- package/src/components/blocks/auction/Auction.tsx +0 -74
- package/src/components/blocks/auction/AuctionArtwork.tsx +0 -4
- package/src/components/blocks/auction/AuctionBidForm.tsx +0 -138
- package/src/components/blocks/auction/AuctionBidInput.tsx +0 -166
- package/src/components/blocks/auction/AuctionContext.tsx +0 -401
- package/src/components/blocks/auction/AuctionInfo.tsx +0 -36
- package/src/components/blocks/auction/AuctionLayout.tsx +0 -200
- package/src/components/blocks/auction/AuctionRankings.tsx +0 -435
- package/src/components/blocks/auction/AuctionStatusTag.tsx +0 -98
- package/src/components/blocks/auction/AuctionSuggestedBids.tsx +0 -203
- package/src/components/blocks/auction/AuctionYourBidCard.tsx +0 -125
- package/src/components/blocks/auction/AuctionYourBids.tsx +0 -61
- package/src/components/blocks/auction/index.ts +0 -42
- package/src/components/blocks/index.ts +0 -1
- package/src/components/index.ts +0 -2
- package/src/components/primitives/Button.tsx +0 -183
- package/src/components/primitives/Drawer.tsx +0 -125
- package/src/components/primitives/Feedback.tsx +0 -185
- package/src/components/primitives/MorphDialog.tsx +0 -160
- package/src/components/primitives/Price.tsx +0 -394
- package/src/components/primitives/PriceInput.tsx +0 -48
- package/src/components/primitives/Receipt.tsx +0 -711
- package/src/components/primitives/Scale.tsx +0 -287
- package/src/components/primitives/Separator.tsx +0 -87
- package/src/components/primitives/Skeleton.tsx +0 -33
- package/src/components/primitives/SteppedInput.tsx +0 -313
- package/src/components/primitives/Tabs.tsx +0 -161
- package/src/components/primitives/Tag.tsx +0 -48
- package/src/components/primitives/Text.tsx +0 -102
- package/src/components/primitives/countdown/Countdown.tsx +0 -43
- package/src/components/primitives/countdown/index.ts +0 -2
- package/src/components/primitives/framed-image/FramedImage.tsx +0 -51
- package/src/components/primitives/framed-image/index.ts +0 -1
- package/src/components/primitives/index.ts +0 -42
- package/src/components/primitives/ranked-list/RankedList.tsx +0 -9
- package/src/components/primitives/ranked-list/Ranking.tsx +0 -454
- package/src/components/primitives/ranked-list/index.ts +0 -8
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useCountdown.ts +0 -91
- package/src/index.ts +0 -130
- package/src/lib/cn.ts +0 -81
- package/src/lib/index.ts +0 -2
- package/src/lib/motion.ts +0 -55
- 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 +0 -658
- package/src/stories/AuctionLayout.stories.tsx +0 -313
- package/src/stories/AuctionStatusTag.stories.tsx +0 -166
- package/src/stories/AuctionYourBidCard.stories.tsx +0 -257
- package/src/stories/Button.stories.tsx +0 -306
- package/src/stories/Countdown.stories.tsx +0 -158
- package/src/stories/Feedback.stories.tsx +0 -80
- package/src/stories/FramedImage.stories.tsx +0 -46
- package/src/stories/MorphDialog.stories.tsx +0 -88
- package/src/stories/Price.stories.tsx +0 -292
- package/src/stories/RankedList.stories.tsx +0 -190
- package/src/stories/Receipt.stories.tsx +0 -221
- package/src/stories/Scale.stories.tsx +0 -578
- package/src/stories/Separator.stories.tsx +0 -188
- package/src/stories/Skeleton.stories.tsx +0 -138
- package/src/stories/SteppedInput.stories.tsx +0 -321
- package/src/stories/Tabs.stories.tsx +0 -215
- package/src/stories/Tag.stories.tsx +0 -138
- package/src/stories/Text.stories.tsx +0 -245
- package/src/styles/globals.css +0 -39
- package/src/styles/index.css +0 -4
- package/src/styles/theme/animation.css +0 -11
- package/src/styles/theme/color.css +0 -185
- package/src/styles/theme/index.css +0 -3
- package/src/styles/theme/typography.css +0 -3
- package/src/styles/utility.css +0 -8
- package/src/types/index.ts +0 -149
- package/src/utils/format.ts +0 -130
- package/src/utils/index.ts +0 -16
- package/src/utils/rank-utils.ts +0 -131
- package/src/utils/tick-validation.ts +0 -65
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
|
|
5
|
-
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
type ScaleValue = number | bigint;
|
|
8
|
-
type SnapMode = "up" | "down" | "nearest";
|
|
9
|
-
|
|
10
|
-
interface ScaleContextValue<T extends ScaleValue> {
|
|
11
|
-
domain: [T, T];
|
|
12
|
-
getTickSize: (value: T) => T;
|
|
13
|
-
snapMode: SnapMode | false;
|
|
14
|
-
getValueAtPosition: (position: number) => T;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface TickContext<T extends ScaleValue> {
|
|
18
|
-
/** The position on the scale (0-1) */
|
|
19
|
-
position: number;
|
|
20
|
-
/** The calculated value at this position (snapped to tick grid) */
|
|
21
|
-
value: T;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// ─── Context ────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
// We use `unknown` for the context and cast at usage sites to support generics
|
|
27
|
-
// This is a common pattern when React Context needs to work with generic types
|
|
28
|
-
interface ScaleContextValueInternal {
|
|
29
|
-
domain: [ScaleValue, ScaleValue];
|
|
30
|
-
getTickSize: (value: ScaleValue) => ScaleValue;
|
|
31
|
-
snapMode: SnapMode | false;
|
|
32
|
-
getValueAtPosition: (position: number) => ScaleValue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const ScaleContext = React.createContext<ScaleContextValueInternal | null>(
|
|
36
|
-
null,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
function useScale<T extends ScaleValue>(): ScaleContextValue<T> {
|
|
40
|
-
const context = React.useContext(ScaleContext);
|
|
41
|
-
if (!context) {
|
|
42
|
-
throw new Error("useScale must be used within Scale.Linear");
|
|
43
|
-
}
|
|
44
|
-
// Cast is safe because we know the provider was created with the correct type
|
|
45
|
-
return context as unknown as ScaleContextValue<T>;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ─── Utilities ──────────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Type guard to check if a value is bigint
|
|
52
|
-
*/
|
|
53
|
-
function isBigInt(value: ScaleValue): value is bigint {
|
|
54
|
-
return typeof value === "bigint";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Linearly interpolate between min and max based on position (0-1).
|
|
59
|
-
* Works with both number and bigint.
|
|
60
|
-
*/
|
|
61
|
-
function interpolate<T extends ScaleValue>(
|
|
62
|
-
min: T,
|
|
63
|
-
max: T,
|
|
64
|
-
position: number,
|
|
65
|
-
): T {
|
|
66
|
-
// Clamp position to [0, 1]
|
|
67
|
-
const clampedPosition = Math.max(0, Math.min(1, position));
|
|
68
|
-
|
|
69
|
-
if (isBigInt(min) && isBigInt(max)) {
|
|
70
|
-
const range = max - min;
|
|
71
|
-
// Use high precision for bigint calculation
|
|
72
|
-
const scaledPosition = BigInt(Math.round(clampedPosition * 1_000_000));
|
|
73
|
-
return (min + (range * scaledPosition) / 1_000_000n) as T;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Number math
|
|
77
|
-
const numMin = min as number;
|
|
78
|
-
const numMax = max as number;
|
|
79
|
-
return (numMin + (numMax - numMin) * clampedPosition) as T;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Snaps a value to the tick grid based on the specified mode.
|
|
84
|
-
* Works with both number and bigint.
|
|
85
|
-
*/
|
|
86
|
-
function snapToTick<T extends ScaleValue>(
|
|
87
|
-
value: T,
|
|
88
|
-
tickSize: T,
|
|
89
|
-
mode: SnapMode,
|
|
90
|
-
min?: T,
|
|
91
|
-
): T {
|
|
92
|
-
if (isBigInt(value) && isBigInt(tickSize)) {
|
|
93
|
-
if (tickSize <= 0n) return value;
|
|
94
|
-
|
|
95
|
-
const remainder = value % tickSize;
|
|
96
|
-
if (remainder === 0n) return value;
|
|
97
|
-
|
|
98
|
-
let snapped: bigint;
|
|
99
|
-
switch (mode) {
|
|
100
|
-
case "up":
|
|
101
|
-
snapped = value - remainder + tickSize;
|
|
102
|
-
break;
|
|
103
|
-
case "down":
|
|
104
|
-
snapped = value - remainder;
|
|
105
|
-
break;
|
|
106
|
-
case "nearest":
|
|
107
|
-
snapped =
|
|
108
|
-
remainder * 2n >= tickSize
|
|
109
|
-
? value - remainder + tickSize
|
|
110
|
-
: value - remainder;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Ensure we don't snap below minimum
|
|
115
|
-
if (min !== undefined && isBigInt(min) && snapped < min) {
|
|
116
|
-
snapped = min;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return snapped as T;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Number math
|
|
123
|
-
const numValue = value as number;
|
|
124
|
-
const numTickSize = tickSize as number;
|
|
125
|
-
const numMin = min as number | undefined;
|
|
126
|
-
|
|
127
|
-
if (numTickSize <= 0) return value;
|
|
128
|
-
|
|
129
|
-
const remainder = numValue % numTickSize;
|
|
130
|
-
if (Math.abs(remainder) < Number.EPSILON) return value;
|
|
131
|
-
|
|
132
|
-
let snapped: number;
|
|
133
|
-
switch (mode) {
|
|
134
|
-
case "up":
|
|
135
|
-
snapped = numValue - remainder + numTickSize;
|
|
136
|
-
break;
|
|
137
|
-
case "down":
|
|
138
|
-
snapped = numValue - remainder;
|
|
139
|
-
break;
|
|
140
|
-
case "nearest":
|
|
141
|
-
snapped =
|
|
142
|
-
remainder * 2 >= numTickSize
|
|
143
|
-
? numValue - remainder + numTickSize
|
|
144
|
-
: numValue - remainder;
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Ensure we don't snap below minimum
|
|
149
|
-
if (numMin !== undefined && snapped < numMin) {
|
|
150
|
-
snapped = numMin;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return snapped as T;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ─── Linear Component ───────────────────────────────────────────────────────
|
|
157
|
-
|
|
158
|
-
interface LinearProps<T extends ScaleValue> {
|
|
159
|
-
/** Domain tuple: [min, max] */
|
|
160
|
-
domain: [T, T];
|
|
161
|
-
/** Function to get tick size at a given value */
|
|
162
|
-
getTickSize: (value: T) => T;
|
|
163
|
-
/** How to snap values to the tick grid (default: 'nearest', false to disable) */
|
|
164
|
-
snapMode?: SnapMode | false;
|
|
165
|
-
children: React.ReactNode;
|
|
166
|
-
className?: string;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function Linear<T extends ScaleValue>({
|
|
170
|
-
domain,
|
|
171
|
-
getTickSize,
|
|
172
|
-
snapMode = "nearest",
|
|
173
|
-
children,
|
|
174
|
-
className,
|
|
175
|
-
}: LinearProps<T>): React.ReactElement {
|
|
176
|
-
const [min, max] = domain;
|
|
177
|
-
|
|
178
|
-
const getValueAtPosition = React.useCallback(
|
|
179
|
-
(position: number): T => {
|
|
180
|
-
const rawValue = interpolate(min, max, position);
|
|
181
|
-
|
|
182
|
-
if (snapMode === false) {
|
|
183
|
-
return rawValue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const tickSize = getTickSize(rawValue);
|
|
187
|
-
return snapToTick(rawValue, tickSize, snapMode, min);
|
|
188
|
-
},
|
|
189
|
-
[min, max, getTickSize, snapMode],
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const contextValue = React.useMemo(
|
|
193
|
-
(): ScaleContextValue<T> => ({
|
|
194
|
-
domain,
|
|
195
|
-
getTickSize,
|
|
196
|
-
snapMode,
|
|
197
|
-
getValueAtPosition,
|
|
198
|
-
}),
|
|
199
|
-
[domain, getTickSize, snapMode, getValueAtPosition],
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
return (
|
|
203
|
-
<ScaleContext.Provider
|
|
204
|
-
value={contextValue as unknown as ScaleContextValueInternal}
|
|
205
|
-
>
|
|
206
|
-
<div className={className}>{children}</div>
|
|
207
|
-
</ScaleContext.Provider>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ─── Tick Component ─────────────────────────────────────────────────────────
|
|
212
|
-
|
|
213
|
-
interface TickProps<T extends ScaleValue> {
|
|
214
|
-
/** Position on the scale (0 = min, 1 = max) */
|
|
215
|
-
position: number;
|
|
216
|
-
/** Render function receiving the tick context */
|
|
217
|
-
children: (context: TickContext<T>) => React.ReactNode;
|
|
218
|
-
className?: string;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function Tick<T extends ScaleValue>({
|
|
222
|
-
position,
|
|
223
|
-
children,
|
|
224
|
-
className,
|
|
225
|
-
}: TickProps<T>): React.ReactElement {
|
|
226
|
-
const { getValueAtPosition } = useScale<T>();
|
|
227
|
-
|
|
228
|
-
const value = React.useMemo(
|
|
229
|
-
() => getValueAtPosition(position),
|
|
230
|
-
[getValueAtPosition, position],
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
const context: TickContext<T> = {
|
|
234
|
-
position,
|
|
235
|
-
value,
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
return <div className={className}>{children(context)}</div>;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ─── Ticks Component (helper for evenly-spaced ticks) ───────────────────────
|
|
242
|
-
|
|
243
|
-
interface TicksProps<T extends ScaleValue> {
|
|
244
|
-
/** Number of ticks to generate (evenly spaced from 0 to 1) */
|
|
245
|
-
count: number;
|
|
246
|
-
/** Render function for each tick */
|
|
247
|
-
children: (context: TickContext<T>, index: number) => React.ReactNode;
|
|
248
|
-
className?: string;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function Ticks<T extends ScaleValue>({
|
|
252
|
-
count,
|
|
253
|
-
children,
|
|
254
|
-
className,
|
|
255
|
-
}: TicksProps<T>): React.ReactElement {
|
|
256
|
-
const positions = React.useMemo(() => {
|
|
257
|
-
if (count <= 1) return [0];
|
|
258
|
-
return Array.from({ length: count }, (_, i) => i / (count - 1));
|
|
259
|
-
}, [count]);
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
<>
|
|
263
|
-
{positions.map((position, index) => (
|
|
264
|
-
<Tick<T> key={position} position={position} className={className}>
|
|
265
|
-
{(context) => children(context, index)}
|
|
266
|
-
</Tick>
|
|
267
|
-
))}
|
|
268
|
-
</>
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ─── Export ─────────────────────────────────────────────────────────────────
|
|
273
|
-
|
|
274
|
-
interface ScaleComponent {
|
|
275
|
-
Linear: typeof Linear;
|
|
276
|
-
Tick: typeof Tick;
|
|
277
|
-
Ticks: typeof Ticks;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const Scale: ScaleComponent = {
|
|
281
|
-
Linear,
|
|
282
|
-
Tick,
|
|
283
|
-
Ticks,
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
export { Scale, useScale };
|
|
287
|
-
export type { TickContext as ScaleTickContext, SnapMode, ScaleValue };
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
|
|
2
|
-
import type * as React from "react";
|
|
3
|
-
import { cn } from "@/lib/cn";
|
|
4
|
-
import { Text } from "./Text";
|
|
5
|
-
|
|
6
|
-
type SeparatorVariantsProps = {
|
|
7
|
-
color?: "default" | "subtle";
|
|
8
|
-
orientation?: "horizontal" | "vertical";
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const separatorVariants = (props: SeparatorVariantsProps) => {
|
|
12
|
-
const colorClasses = {
|
|
13
|
-
default: "bg-border",
|
|
14
|
-
subtle: "bg-separator",
|
|
15
|
-
};
|
|
16
|
-
const orientationClasses = {
|
|
17
|
-
horizontal: "h-px",
|
|
18
|
-
vertical: "w-px",
|
|
19
|
-
};
|
|
20
|
-
return `${colorClasses[props.color ?? "default"]} ${orientationClasses[props.orientation ?? "horizontal"]}`;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type SeparatorOwnProps = SeparatorVariantsProps & {
|
|
24
|
-
label?: React.ReactNode;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
interface SeparatorProps
|
|
28
|
-
extends React.ComponentProps<typeof SeparatorPrimitive>,
|
|
29
|
-
SeparatorOwnProps {}
|
|
30
|
-
|
|
31
|
-
function Separator({
|
|
32
|
-
className,
|
|
33
|
-
color,
|
|
34
|
-
orientation,
|
|
35
|
-
label,
|
|
36
|
-
ref,
|
|
37
|
-
...props
|
|
38
|
-
}: SeparatorProps): React.ReactElement {
|
|
39
|
-
const colorClass = color ?? "default";
|
|
40
|
-
const orientationValue = orientation ?? "horizontal";
|
|
41
|
-
const lineColorClass =
|
|
42
|
-
colorClass === "default" ? "bg-border" : "bg-separator";
|
|
43
|
-
|
|
44
|
-
if (label) {
|
|
45
|
-
if (orientationValue === "vertical") {
|
|
46
|
-
return (
|
|
47
|
-
<div
|
|
48
|
-
className={cn(
|
|
49
|
-
"flex min-h-0 min-w-px flex-col items-center justify-center gap-2",
|
|
50
|
-
className,
|
|
51
|
-
)}
|
|
52
|
-
aria-hidden
|
|
53
|
-
>
|
|
54
|
-
<div className={cn("min-h-0 w-px flex-1", lineColorClass)} />
|
|
55
|
-
<Text size="1" color="secondary" className="shrink-0">
|
|
56
|
-
{label}
|
|
57
|
-
</Text>
|
|
58
|
-
<div className={cn("min-h-0 w-px flex-1", lineColorClass)} />
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div
|
|
65
|
-
className={cn("flex min-w-0 items-center gap-3 py-2", className)}
|
|
66
|
-
aria-hidden
|
|
67
|
-
>
|
|
68
|
-
<div className={cn("h-px min-w-0 flex-1", lineColorClass)} />
|
|
69
|
-
<Text size="1" color="secondary" className="shrink-0">
|
|
70
|
-
{label}
|
|
71
|
-
</Text>
|
|
72
|
-
<div className={cn("h-px min-w-0 flex-1", lineColorClass)} />
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<SeparatorPrimitive
|
|
79
|
-
ref={ref}
|
|
80
|
-
className={cn(separatorVariants({ color, orientation }), className)}
|
|
81
|
-
orientation={orientation}
|
|
82
|
-
{...props}
|
|
83
|
-
/>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export { Separator };
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { mergeProps } from "@base-ui/react/merge-props";
|
|
2
|
-
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
-
import type * as React from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
export type SkeletonProps = Omit<useRender.ComponentProps<"span">, "color">;
|
|
7
|
-
|
|
8
|
-
export type SkeletonElement = React.ComponentRef<"span">;
|
|
9
|
-
|
|
10
|
-
export function Skeleton({
|
|
11
|
-
className,
|
|
12
|
-
render,
|
|
13
|
-
ref,
|
|
14
|
-
...props
|
|
15
|
-
}: SkeletonProps): React.ReactElement {
|
|
16
|
-
const defaultProps: useRender.ElementProps<"span"> = {
|
|
17
|
-
className: cn(
|
|
18
|
-
"pointer-events-none animate-pulse rounded-xs border-none! bg-muted bg-none! text-transparent! shadow-none! select-none *:invisible empty:block",
|
|
19
|
-
className,
|
|
20
|
-
),
|
|
21
|
-
"aria-hidden": true,
|
|
22
|
-
tabIndex: -1,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const element = useRender({
|
|
26
|
-
defaultTagName: "span",
|
|
27
|
-
render,
|
|
28
|
-
props: mergeProps<"span">(defaultProps, props),
|
|
29
|
-
ref: ref,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return element;
|
|
33
|
-
}
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { NumberField } from "@base-ui/react/number-field";
|
|
4
|
-
import { IconArrowsHorizontal } from "@tabler/icons-react";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import { cn } from "@/lib";
|
|
7
|
-
|
|
8
|
-
interface SteppedInputContextValue {
|
|
9
|
-
value: bigint;
|
|
10
|
-
inputValue: number;
|
|
11
|
-
step: number;
|
|
12
|
-
disabled: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const SteppedInputContext =
|
|
16
|
-
React.createContext<SteppedInputContextValue | null>(null);
|
|
17
|
-
|
|
18
|
-
function useSteppedInput(): SteppedInputContextValue {
|
|
19
|
-
const context = React.useContext(SteppedInputContext);
|
|
20
|
-
if (!context) {
|
|
21
|
-
throw new Error("useSteppedInput must be used within SteppedInput.Root");
|
|
22
|
-
}
|
|
23
|
-
return context;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type SnapToTickMode = "up" | "down" | "nearest";
|
|
27
|
-
|
|
28
|
-
function getScale(decimals: number): number {
|
|
29
|
-
return 10 ** decimals;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function toInputValue(value: bigint, decimals: number): number {
|
|
33
|
-
return Number(value) / getScale(decimals);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function fromInputValue(value: number, decimals: number): bigint {
|
|
37
|
-
return BigInt(Math.round(value * getScale(decimals)));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function snapValueToTick(
|
|
41
|
-
value: bigint,
|
|
42
|
-
tickSize: bigint,
|
|
43
|
-
mode: SnapToTickMode,
|
|
44
|
-
min?: bigint,
|
|
45
|
-
): bigint {
|
|
46
|
-
if (tickSize <= 0n) return value;
|
|
47
|
-
|
|
48
|
-
const remainder = value % tickSize;
|
|
49
|
-
if (remainder === 0n) return value;
|
|
50
|
-
|
|
51
|
-
let snapped: bigint;
|
|
52
|
-
switch (mode) {
|
|
53
|
-
case "up":
|
|
54
|
-
snapped = value - remainder + tickSize;
|
|
55
|
-
break;
|
|
56
|
-
case "down":
|
|
57
|
-
snapped = value - remainder;
|
|
58
|
-
break;
|
|
59
|
-
case "nearest":
|
|
60
|
-
snapped =
|
|
61
|
-
remainder * 2n >= tickSize
|
|
62
|
-
? value - remainder + tickSize
|
|
63
|
-
: value - remainder;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (min !== undefined && snapped < min) {
|
|
68
|
-
snapped = min;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return snapped;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
interface RootProps {
|
|
75
|
-
value: bigint;
|
|
76
|
-
onChange: (value: bigint) => void;
|
|
77
|
-
min?: bigint;
|
|
78
|
-
max?: bigint;
|
|
79
|
-
getTickSize: (currentValue: bigint) => bigint;
|
|
80
|
-
decimals?: number;
|
|
81
|
-
disabled?: boolean;
|
|
82
|
-
className?: string;
|
|
83
|
-
children: React.ReactNode;
|
|
84
|
-
snapToTick?: SnapToTickMode | false;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function Root({
|
|
88
|
-
value,
|
|
89
|
-
onChange,
|
|
90
|
-
min,
|
|
91
|
-
max,
|
|
92
|
-
getTickSize,
|
|
93
|
-
decimals = 0,
|
|
94
|
-
disabled = false,
|
|
95
|
-
className,
|
|
96
|
-
children,
|
|
97
|
-
snapToTick = false,
|
|
98
|
-
}: RootProps): React.ReactElement {
|
|
99
|
-
const inputValue = toInputValue(value, decimals);
|
|
100
|
-
const step = toInputValue(getTickSize(value), decimals);
|
|
101
|
-
|
|
102
|
-
const handleValueChange = React.useCallback(
|
|
103
|
-
(val: number | null) => {
|
|
104
|
-
if (val === null || val === undefined) return;
|
|
105
|
-
|
|
106
|
-
let newValue = fromInputValue(val, decimals);
|
|
107
|
-
|
|
108
|
-
if (min !== undefined && newValue < min) {
|
|
109
|
-
newValue = min;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (max !== undefined && newValue > max) {
|
|
113
|
-
newValue = max;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (snapToTick) {
|
|
117
|
-
const tickSize = getTickSize(value);
|
|
118
|
-
newValue = snapValueToTick(newValue, tickSize, snapToTick, min);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
onChange(newValue);
|
|
122
|
-
},
|
|
123
|
-
[decimals, snapToTick, getTickSize, min, max, onChange, value],
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
const contextValue: SteppedInputContextValue = React.useMemo(
|
|
127
|
-
() => ({
|
|
128
|
-
value,
|
|
129
|
-
inputValue,
|
|
130
|
-
step,
|
|
131
|
-
disabled,
|
|
132
|
-
}),
|
|
133
|
-
[value, inputValue, step, disabled],
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<SteppedInputContext.Provider value={contextValue}>
|
|
138
|
-
<NumberField.Root
|
|
139
|
-
value={inputValue}
|
|
140
|
-
onValueChange={handleValueChange}
|
|
141
|
-
min={min !== undefined ? toInputValue(min, decimals) : -Infinity}
|
|
142
|
-
max={max !== undefined ? toInputValue(max, decimals) : Infinity}
|
|
143
|
-
step={step}
|
|
144
|
-
className={cn("w-full", className)}
|
|
145
|
-
disabled={disabled}
|
|
146
|
-
>
|
|
147
|
-
{children}
|
|
148
|
-
</NumberField.Root>
|
|
149
|
-
</SteppedInputContext.Provider>
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
interface GroupProps {
|
|
154
|
-
children: React.ReactNode;
|
|
155
|
-
className?: string;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function Group({ children, className }: GroupProps): React.ReactElement {
|
|
159
|
-
return (
|
|
160
|
-
<NumberField.Group className={cn("flex", className)}>
|
|
161
|
-
{children}
|
|
162
|
-
</NumberField.Group>
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
interface DecrementProps {
|
|
167
|
-
className?: string;
|
|
168
|
-
children?: React.ReactNode;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function Decrement({
|
|
172
|
-
className,
|
|
173
|
-
children,
|
|
174
|
-
}: DecrementProps): React.ReactElement {
|
|
175
|
-
return (
|
|
176
|
-
<NumberField.Decrement
|
|
177
|
-
className={cn(
|
|
178
|
-
"flex size-10 items-center justify-center rounded-l-md border-y border-l",
|
|
179
|
-
"border-input bg-muted text-foreground select-none",
|
|
180
|
-
"hover:bg-accent-hover",
|
|
181
|
-
"active:bg-accent-active",
|
|
182
|
-
"disabled:cursor-not-allowed disabled:border-disabled disabled:bg-disabled disabled:text-disabled-foreground",
|
|
183
|
-
className,
|
|
184
|
-
)}
|
|
185
|
-
>
|
|
186
|
-
{children ?? "-"}
|
|
187
|
-
</NumberField.Decrement>
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
interface IncrementProps {
|
|
192
|
-
className?: string;
|
|
193
|
-
children?: React.ReactNode;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function Increment({
|
|
197
|
-
className,
|
|
198
|
-
children,
|
|
199
|
-
}: IncrementProps): React.ReactElement {
|
|
200
|
-
return (
|
|
201
|
-
<NumberField.Increment
|
|
202
|
-
className={cn(
|
|
203
|
-
"flex size-10 items-center justify-center rounded-r-md border-y border-r",
|
|
204
|
-
"border-input bg-muted text-foreground select-none",
|
|
205
|
-
"hover:bg-accent-hover",
|
|
206
|
-
"active:bg-accent-active",
|
|
207
|
-
"disabled:cursor-not-allowed disabled:border-disabled disabled:bg-disabled disabled:text-disabled-foreground",
|
|
208
|
-
className,
|
|
209
|
-
)}
|
|
210
|
-
>
|
|
211
|
-
{children ?? "+"}
|
|
212
|
-
</NumberField.Increment>
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
interface ScrubAreaProps {
|
|
217
|
-
children: React.ReactNode;
|
|
218
|
-
className?: string;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function ScrubArea({
|
|
222
|
-
children,
|
|
223
|
-
className,
|
|
224
|
-
}: ScrubAreaProps): React.ReactElement {
|
|
225
|
-
return (
|
|
226
|
-
<NumberField.ScrubArea
|
|
227
|
-
className={cn(
|
|
228
|
-
"relative flex w-full items-center",
|
|
229
|
-
"disabled:cursor-not-allowed",
|
|
230
|
-
className,
|
|
231
|
-
)}
|
|
232
|
-
>
|
|
233
|
-
{children}
|
|
234
|
-
</NumberField.ScrubArea>
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
interface InputProps {
|
|
239
|
-
className?: string;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function Input({ className }: InputProps): React.ReactElement {
|
|
243
|
-
return (
|
|
244
|
-
<NumberField.Input
|
|
245
|
-
className={cn(
|
|
246
|
-
"h-10 w-auto grow border border-input bg-background px-4",
|
|
247
|
-
"text-center text-sm leading-[21px] text-foreground tabular-nums",
|
|
248
|
-
"focus:ring-2 focus:ring-ring focus:outline-none focus:ring-inset",
|
|
249
|
-
"disabled:cursor-not-allowed disabled:border-disabled disabled:text-disabled-foreground",
|
|
250
|
-
className,
|
|
251
|
-
)}
|
|
252
|
-
/>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
interface ValueProps {
|
|
257
|
-
children: (context: {
|
|
258
|
-
value: bigint;
|
|
259
|
-
inputValue: number;
|
|
260
|
-
step: number;
|
|
261
|
-
disabled: boolean;
|
|
262
|
-
}) => React.ReactNode;
|
|
263
|
-
className?: string;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function Value({ children, className }: ValueProps): React.ReactElement {
|
|
267
|
-
const { value, inputValue, step, disabled } = useSteppedInput();
|
|
268
|
-
return (
|
|
269
|
-
<div
|
|
270
|
-
className={cn(
|
|
271
|
-
"relative flex h-10 grow items-center border border-input bg-background",
|
|
272
|
-
"px-4",
|
|
273
|
-
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-inset",
|
|
274
|
-
className,
|
|
275
|
-
)}
|
|
276
|
-
>
|
|
277
|
-
<NumberField.Input readOnly className="sr-only" />
|
|
278
|
-
<div className="w-full text-center text-sm leading-[21px] text-foreground tabular-nums">
|
|
279
|
-
{children({ value, inputValue, step, disabled })}
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function CursorGrowIcon(
|
|
286
|
-
props: React.ComponentProps<"svg">,
|
|
287
|
-
): React.ReactElement {
|
|
288
|
-
return <IconArrowsHorizontal aria-label="Drag to adjust" {...props} />;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
interface SteppedInputComponent {
|
|
292
|
-
Root: typeof Root;
|
|
293
|
-
Group: typeof Group;
|
|
294
|
-
Decrement: typeof Decrement;
|
|
295
|
-
Increment: typeof Increment;
|
|
296
|
-
ScrubArea: typeof ScrubArea;
|
|
297
|
-
ScrubAreaCursor: typeof NumberField.ScrubAreaCursor;
|
|
298
|
-
Input: typeof Input;
|
|
299
|
-
Value: typeof Value;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const SteppedInput = {
|
|
303
|
-
Root,
|
|
304
|
-
Group,
|
|
305
|
-
Decrement,
|
|
306
|
-
Increment,
|
|
307
|
-
ScrubArea,
|
|
308
|
-
ScrubAreaCursor: NumberField.ScrubAreaCursor,
|
|
309
|
-
Input,
|
|
310
|
-
Value,
|
|
311
|
-
} as SteppedInputComponent;
|
|
312
|
-
|
|
313
|
-
export { SteppedInput };
|