@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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/blocks/auction/Auction.d.ts +49 -0
  3. package/dist/components/blocks/auction/Auction.js +44 -0
  4. package/dist/components/blocks/auction/AuctionBidForm.d.ts +11 -0
  5. package/dist/components/blocks/auction/AuctionBidForm.js +88 -0
  6. package/dist/components/blocks/auction/AuctionBidInput.d.ts +9 -0
  7. package/dist/components/blocks/auction/AuctionBidInput.js +99 -0
  8. package/dist/components/blocks/auction/AuctionContext.d.ts +71 -0
  9. package/dist/components/blocks/auction/AuctionContext.js +228 -0
  10. package/dist/components/blocks/auction/AuctionInfo.d.ts +9 -0
  11. package/dist/components/blocks/auction/AuctionInfo.js +37 -0
  12. package/dist/components/blocks/auction/AuctionLayout.d.ts +63 -0
  13. package/dist/components/blocks/auction/AuctionLayout.js +80 -0
  14. package/dist/components/blocks/auction/AuctionRankings.d.ts +16 -0
  15. package/dist/components/blocks/auction/AuctionRankings.js +334 -0
  16. package/dist/components/blocks/auction/AuctionStatusTag.d.ts +15 -0
  17. package/dist/components/blocks/auction/AuctionStatusTag.js +60 -0
  18. package/dist/components/blocks/auction/AuctionSuggestedBids.d.ts +38 -0
  19. package/dist/components/blocks/auction/AuctionSuggestedBids.js +116 -0
  20. package/dist/components/blocks/auction/AuctionYourBidCard.d.ts +27 -0
  21. package/dist/components/blocks/auction/AuctionYourBidCard.js +94 -0
  22. package/dist/components/blocks/auction/AuctionYourBids.d.ts +9 -0
  23. package/dist/components/blocks/auction/AuctionYourBids.js +49 -0
  24. package/dist/components/blocks/auction/index.d.ts +12 -0
  25. package/dist/components/blocks/index.d.ts +12 -0
  26. package/dist/components/index.d.ts +28 -0
  27. package/dist/components/primitives/Button.d.ts +31 -0
  28. package/dist/components/primitives/Button.js +117 -0
  29. package/dist/components/primitives/Drawer.d.ts +43 -0
  30. package/dist/components/primitives/Drawer.js +51 -0
  31. package/dist/components/primitives/Feedback.d.ts +28 -0
  32. package/dist/components/primitives/Feedback.js +147 -0
  33. package/dist/components/primitives/MorphDialog.d.ts +39 -0
  34. package/dist/components/primitives/MorphDialog.js +87 -0
  35. package/dist/components/primitives/Price.d.ts +84 -0
  36. package/dist/components/primitives/Price.js +255 -0
  37. package/dist/components/primitives/PriceInput.d.ts +33 -0
  38. package/dist/components/primitives/PriceInput.js +25 -0
  39. package/dist/components/primitives/Receipt.d.ts +164 -0
  40. package/dist/components/primitives/Receipt.js +344 -0
  41. package/dist/components/primitives/Scale.d.ts +67 -0
  42. package/dist/components/primitives/Scale.js +132 -0
  43. package/dist/components/primitives/Separator.d.ts +22 -0
  44. package/dist/components/primitives/Separator.js +62 -0
  45. package/dist/components/primitives/Skeleton.d.ts +14 -0
  46. package/dist/components/primitives/Skeleton.js +20 -0
  47. package/dist/components/primitives/SteppedInput.d.ts +94 -0
  48. package/dist/components/primitives/SteppedInput.js +154 -0
  49. package/dist/components/primitives/Tabs.d.ts +37 -0
  50. package/dist/components/primitives/Tabs.js +99 -0
  51. package/dist/components/primitives/Tag.d.ts +24 -0
  52. package/dist/components/primitives/Tag.js +22 -0
  53. package/dist/components/primitives/Text.d.ts +32 -0
  54. package/dist/components/primitives/Text.js +65 -0
  55. package/dist/components/primitives/countdown/Countdown.d.ts +24 -0
  56. package/dist/components/primitives/countdown/Countdown.js +22 -0
  57. package/dist/components/primitives/framed-image/FramedImage.d.ts +13 -0
  58. package/dist/components/primitives/framed-image/FramedImage.js +37 -0
  59. package/dist/components/primitives/index.d.ts +17 -0
  60. package/dist/components/primitives/ranked-list/Ranking.d.ts +117 -0
  61. package/dist/components/primitives/ranked-list/Ranking.js +219 -0
  62. package/dist/components/primitives/ranked-list/index.d.ts +1 -0
  63. package/dist/hooks/useCountdown.d.ts +20 -0
  64. package/dist/hooks/useCountdown.js +75 -0
  65. package/dist/index.d.ts +36 -0
  66. package/dist/index.js +36 -0
  67. package/dist/lib/cn.d.ts +6 -0
  68. package/dist/lib/cn.js +75 -0
  69. package/dist/lib/motion.d.ts +19 -0
  70. package/dist/lib/motion.js +43 -0
  71. package/dist/types/index.d.ts +120 -0
  72. package/dist/utils/format.d.ts +38 -0
  73. package/dist/utils/format.js +103 -0
  74. package/dist/utils/rank-utils.d.ts +34 -0
  75. package/dist/utils/rank-utils.js +80 -0
  76. package/dist/utils/tick-validation.d.ts +22 -0
  77. package/dist/utils/tick-validation.js +40 -0
  78. package/package.json +92 -0
  79. package/src/components/blocks/auction/Auction.tsx +74 -0
  80. package/src/components/blocks/auction/AuctionArtwork.tsx +4 -0
  81. package/src/components/blocks/auction/AuctionBidForm.tsx +138 -0
  82. package/src/components/blocks/auction/AuctionBidInput.tsx +166 -0
  83. package/src/components/blocks/auction/AuctionContext.tsx +401 -0
  84. package/src/components/blocks/auction/AuctionInfo.tsx +36 -0
  85. package/src/components/blocks/auction/AuctionLayout.tsx +200 -0
  86. package/src/components/blocks/auction/AuctionRankings.tsx +435 -0
  87. package/src/components/blocks/auction/AuctionStatusTag.tsx +98 -0
  88. package/src/components/blocks/auction/AuctionSuggestedBids.tsx +203 -0
  89. package/src/components/blocks/auction/AuctionYourBidCard.tsx +125 -0
  90. package/src/components/blocks/auction/AuctionYourBids.tsx +61 -0
  91. package/src/components/blocks/auction/index.ts +42 -0
  92. package/src/components/blocks/index.ts +1 -0
  93. package/src/components/index.ts +2 -0
  94. package/src/components/primitives/Button.tsx +183 -0
  95. package/src/components/primitives/Drawer.tsx +125 -0
  96. package/src/components/primitives/Feedback.tsx +185 -0
  97. package/src/components/primitives/MorphDialog.tsx +160 -0
  98. package/src/components/primitives/Price.tsx +394 -0
  99. package/src/components/primitives/PriceInput.tsx +48 -0
  100. package/src/components/primitives/Receipt.tsx +711 -0
  101. package/src/components/primitives/Scale.tsx +287 -0
  102. package/src/components/primitives/Separator.tsx +87 -0
  103. package/src/components/primitives/Skeleton.tsx +33 -0
  104. package/src/components/primitives/SteppedInput.tsx +313 -0
  105. package/src/components/primitives/Tabs.tsx +161 -0
  106. package/src/components/primitives/Tag.tsx +48 -0
  107. package/src/components/primitives/Text.tsx +102 -0
  108. package/src/components/primitives/countdown/Countdown.tsx +43 -0
  109. package/src/components/primitives/countdown/index.ts +2 -0
  110. package/src/components/primitives/framed-image/FramedImage.tsx +51 -0
  111. package/src/components/primitives/framed-image/index.ts +1 -0
  112. package/src/components/primitives/index.ts +42 -0
  113. package/src/components/primitives/ranked-list/RankedList.tsx +9 -0
  114. package/src/components/primitives/ranked-list/Ranking.tsx +454 -0
  115. package/src/components/primitives/ranked-list/index.ts +8 -0
  116. package/src/hooks/index.ts +1 -0
  117. package/src/hooks/useCountdown.ts +91 -0
  118. package/src/index.ts +130 -0
  119. package/src/lib/cn.ts +81 -0
  120. package/src/lib/index.ts +2 -0
  121. package/src/lib/motion.ts +55 -0
  122. package/src/public/lea-83-time-walk.png +0 -0
  123. package/src/public/lea-83-time-walk.webp +0 -0
  124. package/src/stories/Auction.stories.tsx +658 -0
  125. package/src/stories/AuctionLayout.stories.tsx +313 -0
  126. package/src/stories/AuctionStatusTag.stories.tsx +166 -0
  127. package/src/stories/AuctionYourBidCard.stories.tsx +257 -0
  128. package/src/stories/Button.stories.tsx +306 -0
  129. package/src/stories/Countdown.stories.tsx +158 -0
  130. package/src/stories/Feedback.stories.tsx +80 -0
  131. package/src/stories/FramedImage.stories.tsx +46 -0
  132. package/src/stories/MorphDialog.stories.tsx +88 -0
  133. package/src/stories/Price.stories.tsx +292 -0
  134. package/src/stories/RankedList.stories.tsx +190 -0
  135. package/src/stories/Receipt.stories.tsx +221 -0
  136. package/src/stories/Scale.stories.tsx +578 -0
  137. package/src/stories/Separator.stories.tsx +188 -0
  138. package/src/stories/Skeleton.stories.tsx +138 -0
  139. package/src/stories/SteppedInput.stories.tsx +321 -0
  140. package/src/stories/Tabs.stories.tsx +215 -0
  141. package/src/stories/Tag.stories.tsx +138 -0
  142. package/src/stories/Text.stories.tsx +245 -0
  143. package/src/styles/globals.css +39 -0
  144. package/src/styles/index.css +4 -0
  145. package/src/styles/theme/animation.css +11 -0
  146. package/src/styles/theme/color.css +185 -0
  147. package/src/styles/theme/index.css +3 -0
  148. package/src/styles/theme/typography.css +3 -0
  149. package/src/styles/utility.css +8 -0
  150. package/src/types/index.ts +149 -0
  151. package/src/utils/format.ts +130 -0
  152. package/src/utils/index.ts +16 -0
  153. package/src/utils/rank-utils.ts +131 -0
  154. package/src/utils/tick-validation.ts +65 -0
@@ -0,0 +1,578 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import React from "react";
3
+ import { Button } from "@/components/primitives/Button";
4
+ import { Scale } from "@/components/primitives/Scale";
5
+ import { Text } from "@/components/primitives/Text";
6
+
7
+ const meta: Meta<typeof Scale.Linear> = {
8
+ title: "Primitives/Scale",
9
+ component: Scale.Linear,
10
+ parameters: {
11
+ layout: "centered",
12
+ },
13
+ decorators: [
14
+ (Story) => (
15
+ <div className="flex flex-col items-center gap-4 rounded-lg border border-border bg-background p-8">
16
+ <Story />
17
+ </div>
18
+ ),
19
+ ],
20
+ };
21
+
22
+ export default meta;
23
+
24
+ // ─── Helpers ────────────────────────────────────────────────────────────────
25
+
26
+ const formatDollars = (cents: bigint) => {
27
+ const dollars = Number(cents) / 100;
28
+ return dollars.toLocaleString("en-US", {
29
+ style: "currency",
30
+ currency: "USD",
31
+ minimumFractionDigits: 0,
32
+ maximumFractionDigits: 0,
33
+ });
34
+ };
35
+
36
+ const formatValue = (value: bigint) => {
37
+ const val = Number(value) / 1e18;
38
+ return `${val.toFixed(4)}`;
39
+ };
40
+
41
+ const formatPercent = (value: number) => `${value.toFixed(0)}%`;
42
+
43
+ // ─── Stories ────────────────────────────────────────────────────────────────
44
+
45
+ export const BasicBigInt: StoryObj<typeof Scale.Linear> = {
46
+ render: () => {
47
+ const [selected, setSelected] = React.useState<bigint | null>(null);
48
+
49
+ return (
50
+ <div className="w-80">
51
+ <Text size="2" color="secondary" className="mb-4">
52
+ BigInt scale: $100 - $500, tick size: $10
53
+ </Text>
54
+ <Scale.Linear
55
+ domain={[10000n, 50000n]} // $100 - $500 in cents
56
+ getTickSize={() => 1000n} // $10 in cents
57
+ className="flex gap-2"
58
+ >
59
+ <Scale.Tick position={0}>
60
+ {({ value }) => {
61
+ const v = value as bigint;
62
+ return (
63
+ <Button
64
+ color={selected === v ? "primary" : "tertiary"}
65
+ onClick={() => setSelected(v)}
66
+ className="flex-1"
67
+ >
68
+ {formatDollars(v)}
69
+ </Button>
70
+ );
71
+ }}
72
+ </Scale.Tick>
73
+ <Scale.Tick position={0.5}>
74
+ {({ value }) => {
75
+ const v = value as bigint;
76
+ return (
77
+ <Button
78
+ color={selected === v ? "primary" : "tertiary"}
79
+ onClick={() => setSelected(v)}
80
+ className="flex-1"
81
+ >
82
+ {formatDollars(v)}
83
+ </Button>
84
+ );
85
+ }}
86
+ </Scale.Tick>
87
+ <Scale.Tick position={1}>
88
+ {({ value }) => {
89
+ const v = value as bigint;
90
+ return (
91
+ <Button
92
+ color={selected === v ? "primary" : "tertiary"}
93
+ onClick={() => setSelected(v)}
94
+ className="flex-1"
95
+ >
96
+ {formatDollars(v)}
97
+ </Button>
98
+ );
99
+ }}
100
+ </Scale.Tick>
101
+ </Scale.Linear>
102
+ {selected && (
103
+ <Text size="1" color="tertiary" className="mt-2">
104
+ Selected: {formatDollars(selected)}
105
+ </Text>
106
+ )}
107
+ </div>
108
+ );
109
+ },
110
+ };
111
+
112
+ export const BasicNumber: StoryObj<typeof Scale.Linear> = {
113
+ render: () => {
114
+ const [selected, setSelected] = React.useState<number | null>(null);
115
+
116
+ return (
117
+ <div className="w-80">
118
+ <Text size="2" color="secondary" className="mb-4">
119
+ Number scale: 0% - 100%, tick size: 10%
120
+ </Text>
121
+ <Scale.Linear
122
+ domain={[0, 100]}
123
+ getTickSize={() => 10}
124
+ className="flex gap-2"
125
+ >
126
+ <Scale.Tick position={0}>
127
+ {({ value }) => {
128
+ const v = value as number;
129
+ return (
130
+ <Button
131
+ color={selected === v ? "primary" : "tertiary"}
132
+ onClick={() => setSelected(v)}
133
+ className="flex-1"
134
+ >
135
+ {formatPercent(v)}
136
+ </Button>
137
+ );
138
+ }}
139
+ </Scale.Tick>
140
+ <Scale.Tick position={0.5}>
141
+ {({ value }) => {
142
+ const v = value as number;
143
+ return (
144
+ <Button
145
+ color={selected === v ? "primary" : "tertiary"}
146
+ onClick={() => setSelected(v)}
147
+ className="flex-1"
148
+ >
149
+ {formatPercent(v)}
150
+ </Button>
151
+ );
152
+ }}
153
+ </Scale.Tick>
154
+ <Scale.Tick position={1}>
155
+ {({ value }) => {
156
+ const v = value as number;
157
+ return (
158
+ <Button
159
+ color={selected === v ? "primary" : "tertiary"}
160
+ onClick={() => setSelected(v)}
161
+ className="flex-1"
162
+ >
163
+ {formatPercent(v)}
164
+ </Button>
165
+ );
166
+ }}
167
+ </Scale.Tick>
168
+ </Scale.Linear>
169
+ {selected !== null && (
170
+ <Text size="1" color="tertiary" className="mt-2">
171
+ Selected: {formatPercent(selected)}
172
+ </Text>
173
+ )}
174
+ </div>
175
+ );
176
+ },
177
+ };
178
+
179
+ export const UsingTicksHelper: StoryObj<typeof Scale.Linear> = {
180
+ render: () => {
181
+ const [selected, setSelected] = React.useState<bigint | null>(null);
182
+
183
+ return (
184
+ <div className="w-80">
185
+ <Text size="2" color="secondary" className="mb-4">
186
+ Using Scale.Ticks to generate 4 evenly-spaced options
187
+ </Text>
188
+ <Scale.Linear
189
+ domain={[10000n, 50000n]}
190
+ getTickSize={() => 1000n}
191
+ className="flex gap-2"
192
+ >
193
+ <Scale.Ticks count={4}>
194
+ {({ value }) => {
195
+ const v = value as bigint;
196
+ return (
197
+ <Button
198
+ color={selected === v ? "primary" : "tertiary"}
199
+ onClick={() => setSelected(v)}
200
+ className="flex-1"
201
+ >
202
+ {formatDollars(v)}
203
+ </Button>
204
+ );
205
+ }}
206
+ </Scale.Ticks>
207
+ </Scale.Linear>
208
+ {selected && (
209
+ <Text size="1" color="tertiary" className="mt-2">
210
+ Selected: {formatDollars(selected)}
211
+ </Text>
212
+ )}
213
+ </div>
214
+ );
215
+ },
216
+ };
217
+
218
+ export const DynamicTickSize: StoryObj<typeof Scale.Linear> = {
219
+ render: () => {
220
+ const [selected, setSelected] = React.useState<bigint | null>(null);
221
+
222
+ // Tick size changes at $1000: $10 below, $100 above
223
+ const getTickSize = (value: bigint) => {
224
+ return value > 100000n ? 10000n : 1000n; // $100 vs $10 in cents
225
+ };
226
+
227
+ return (
228
+ <div className="w-96">
229
+ <Text size="2" color="secondary" className="mb-2">
230
+ Dynamic tick size: $10 below $1,000, $100 above
231
+ </Text>
232
+ <Text size="1" color="tertiary" className="mb-4">
233
+ Scale: $100 - $2,000
234
+ </Text>
235
+ <Scale.Linear
236
+ domain={[10000n, 200000n]} // $100 - $2000
237
+ getTickSize={getTickSize}
238
+ className="flex flex-wrap gap-2"
239
+ >
240
+ <Scale.Ticks count={5}>
241
+ {({ value, position }) => {
242
+ const v = value as bigint;
243
+ return (
244
+ <Button
245
+ color={selected === v ? "primary" : "tertiary"}
246
+ onClick={() => setSelected(v)}
247
+ size="sm"
248
+ >
249
+ <div className="flex flex-col items-center">
250
+ <span>{formatDollars(v)}</span>
251
+ <span className="text-xs opacity-60">
252
+ {(position * 100).toFixed(0)}%
253
+ </span>
254
+ </div>
255
+ </Button>
256
+ );
257
+ }}
258
+ </Scale.Ticks>
259
+ </Scale.Linear>
260
+ {selected && (
261
+ <Text size="1" color="tertiary" className="mt-2">
262
+ Selected: {formatDollars(selected)}
263
+ </Text>
264
+ )}
265
+ </div>
266
+ );
267
+ },
268
+ };
269
+
270
+ export const SnapModes: StoryObj<typeof Scale.Linear> = {
271
+ render: () => {
272
+ // Show how different snap modes affect the values
273
+ // With min=100, max=195, tick=10, position=0.5 → raw value = 147.5
274
+ // - nearest: 150
275
+ // - up: 150
276
+ // - down: 140
277
+
278
+ return (
279
+ <div className="w-96 space-y-6">
280
+ <Text size="2" color="secondary">
281
+ Scale: $100 - $195 (odd range), tick: $10, position: 0.5
282
+ </Text>
283
+ <Text size="1" color="tertiary">
284
+ Raw interpolated value would be $147.50
285
+ </Text>
286
+
287
+ <div>
288
+ <Text size="1" weight="medium" className="mb-2">
289
+ snapMode=&quot;nearest&quot; (default) → rounds to $150
290
+ </Text>
291
+ <Scale.Linear
292
+ domain={[10000n, 19500n]}
293
+ getTickSize={() => 1000n}
294
+ snapMode="nearest"
295
+ className="flex gap-2"
296
+ >
297
+ <Scale.Tick position={0.5}>
298
+ {({ value }) => (
299
+ <Button color="tertiary">
300
+ {formatDollars(value as bigint)}
301
+ </Button>
302
+ )}
303
+ </Scale.Tick>
304
+ </Scale.Linear>
305
+ </div>
306
+
307
+ <div>
308
+ <Text size="1" weight="medium" className="mb-2">
309
+ snapMode=&quot;up&quot; → rounds up to $150
310
+ </Text>
311
+ <Scale.Linear
312
+ domain={[10000n, 19500n]}
313
+ getTickSize={() => 1000n}
314
+ snapMode="up"
315
+ className="flex gap-2"
316
+ >
317
+ <Scale.Tick position={0.5}>
318
+ {({ value }) => (
319
+ <Button color="tertiary">
320
+ {formatDollars(value as bigint)}
321
+ </Button>
322
+ )}
323
+ </Scale.Tick>
324
+ </Scale.Linear>
325
+ </div>
326
+
327
+ <div>
328
+ <Text size="1" weight="medium" className="mb-2">
329
+ snapMode=&quot;down&quot; → rounds down to $140
330
+ </Text>
331
+ <Scale.Linear
332
+ domain={[10000n, 19500n]}
333
+ getTickSize={() => 1000n}
334
+ snapMode="down"
335
+ className="flex gap-2"
336
+ >
337
+ <Scale.Tick position={0.5}>
338
+ {({ value }) => (
339
+ <Button color="tertiary">
340
+ {formatDollars(value as bigint)}
341
+ </Button>
342
+ )}
343
+ </Scale.Tick>
344
+ </Scale.Linear>
345
+ </div>
346
+
347
+ <div>
348
+ <Text size="1" weight="medium" className="mb-2">
349
+ snapMode=false → no snapping, $147
350
+ </Text>
351
+ <Scale.Linear
352
+ domain={[10000n, 19500n]}
353
+ getTickSize={() => 1000n}
354
+ snapMode={false}
355
+ className="flex gap-2"
356
+ >
357
+ <Scale.Tick position={0.5}>
358
+ {({ value }) => (
359
+ <Button color="tertiary">
360
+ {formatDollars(value as bigint)}
361
+ </Button>
362
+ )}
363
+ </Scale.Tick>
364
+ </Scale.Linear>
365
+ </div>
366
+ </div>
367
+ );
368
+ },
369
+ };
370
+
371
+ export const BidSuggestions: StoryObj<typeof Scale.Linear> = {
372
+ render: () => {
373
+ const [selected, setSelected] = React.useState<bigint | null>(null);
374
+ const UNIT = BigInt(10 ** 18);
375
+
376
+ // Min: 0.01, Max: 1
377
+ const min = UNIT / 100n;
378
+ const max = UNIT;
379
+
380
+ // Tick: 0.01 below 0.1, 0.1 above
381
+ const getTickSize = (value: bigint) => {
382
+ return value > UNIT / 10n
383
+ ? UNIT / 10n // 0.1
384
+ : UNIT / 100n; // 0.01
385
+ };
386
+
387
+ return (
388
+ <div className="w-96">
389
+ <Text size="3" weight="medium" className="mb-2">
390
+ Bid Suggestions
391
+ </Text>
392
+ <Text size="2" color="secondary" className="mb-4">
393
+ Reserve: 0.01, Max suggestion: 1
394
+ </Text>
395
+ <Scale.Linear
396
+ domain={[min, max]}
397
+ getTickSize={getTickSize}
398
+ snapMode="up"
399
+ className="flex flex-col gap-2"
400
+ >
401
+ <Scale.Ticks count={4}>
402
+ {({ value, position }) => {
403
+ const v = value as bigint;
404
+ return (
405
+ <Button
406
+ color={selected === v ? "primary" : "secondary"}
407
+ onClick={() => setSelected(v)}
408
+ className="w-full justify-between"
409
+ >
410
+ <span className="font-mono">{formatValue(v)}</span>
411
+ <span className="text-xs opacity-60">
412
+ {(position * 100).toFixed(0)}% of range
413
+ </span>
414
+ </Button>
415
+ );
416
+ }}
417
+ </Scale.Ticks>
418
+ </Scale.Linear>
419
+ {selected && (
420
+ <div className="mt-4 rounded-md border border-border p-3">
421
+ <Text size="1" color="tertiary">
422
+ Selected bid:
423
+ </Text>
424
+ <Text size="3" weight="medium" className="font-mono">
425
+ {formatValue(selected)}
426
+ </Text>
427
+ </div>
428
+ )}
429
+ </div>
430
+ );
431
+ },
432
+ };
433
+
434
+ export const WithExternalRankCalculation: StoryObj<typeof Scale.Linear> = {
435
+ render: () => {
436
+ const [selected, setSelected] = React.useState<bigint | null>(null);
437
+
438
+ // Mock rank calculation - higher bid = better rank
439
+ // This shows how to combine Scale with external rank logic
440
+ const getRank = (value: bigint) => {
441
+ const cents = Number(value);
442
+ // Simulate: $500 = rank 1, $100 = rank 20
443
+ const rank = Math.max(1, Math.round(21 - (cents / 10000) * 4));
444
+ return {
445
+ rank,
446
+ isWinning: rank <= 10, // Top 10 are winners
447
+ };
448
+ };
449
+
450
+ return (
451
+ <div className="w-96">
452
+ <Text size="2" color="secondary" className="mb-4">
453
+ Scale + external rank calculation (10 winning spots)
454
+ </Text>
455
+ <Scale.Linear
456
+ domain={[10000n, 50000n]}
457
+ getTickSize={() => 1000n}
458
+ className="flex flex-col gap-2"
459
+ >
460
+ <Scale.Ticks count={4}>
461
+ {({ value }) => {
462
+ const v = value as bigint;
463
+ const { rank, isWinning } = getRank(v);
464
+ return (
465
+ <Button
466
+ color={selected === v ? "primary" : "tertiary"}
467
+ onClick={() => setSelected(v)}
468
+ className="w-full justify-between"
469
+ >
470
+ <span>{formatDollars(v)}</span>
471
+ <span
472
+ className={isWinning ? "text-success" : "text-destructive"}
473
+ >
474
+ Rank #{rank} {isWinning ? "(winning)" : "(outbid)"}
475
+ </span>
476
+ </Button>
477
+ );
478
+ }}
479
+ </Scale.Ticks>
480
+ </Scale.Linear>
481
+ </div>
482
+ );
483
+ },
484
+ };
485
+
486
+ export const CustomCardStyling: StoryObj<typeof Scale.Linear> = {
487
+ render: () => {
488
+ const [selected, setSelected] = React.useState<bigint | null>(null);
489
+
490
+ return (
491
+ <div className="w-80">
492
+ <Text size="2" color="secondary" className="mb-4">
493
+ Custom card-style rendering
494
+ </Text>
495
+ <Scale.Linear
496
+ domain={[10000n, 50000n]}
497
+ getTickSize={() => 5000n}
498
+ className="grid grid-cols-3 gap-3"
499
+ >
500
+ <Scale.Ticks count={3}>
501
+ {({ value, position }) => {
502
+ const v = value as bigint;
503
+ return (
504
+ <button
505
+ type="button"
506
+ onClick={() => setSelected(v)}
507
+ className={`rounded-lg border-2 p-3 text-center transition-colors ${
508
+ selected === v
509
+ ? "border-foreground bg-foreground text-background"
510
+ : "border-border bg-muted hover:border-foreground/50"
511
+ }`}
512
+ >
513
+ <div className="text-lg font-semibold">
514
+ {formatDollars(v)}
515
+ </div>
516
+ <div
517
+ className={`mt-1 text-xs ${
518
+ selected === v ? "opacity-80" : "text-muted-foreground"
519
+ }`}
520
+ >
521
+ {(position * 100).toFixed(0)}%
522
+ </div>
523
+ </button>
524
+ );
525
+ }}
526
+ </Scale.Ticks>
527
+ </Scale.Linear>
528
+ </div>
529
+ );
530
+ },
531
+ };
532
+
533
+ export const TemperatureScale: StoryObj<typeof Scale.Linear> = {
534
+ render: () => {
535
+ const [selected, setSelected] = React.useState<number | null>(null);
536
+
537
+ return (
538
+ <div className="w-80">
539
+ <Text size="2" color="secondary" className="mb-4">
540
+ Number scale: Temperature 0°C - 40°C
541
+ </Text>
542
+ <Scale.Linear
543
+ domain={[0, 40]}
544
+ getTickSize={() => 5}
545
+ className="flex gap-2"
546
+ >
547
+ <Scale.Ticks count={5}>
548
+ {({ value }) => {
549
+ const v = value as number;
550
+ const isCold = v < 15;
551
+ const isHot = v > 30;
552
+ return (
553
+ <Button
554
+ color={selected === v ? "primary" : "tertiary"}
555
+ onClick={() => setSelected(v)}
556
+ className="flex-1"
557
+ >
558
+ <span
559
+ className={
560
+ isCold ? "text-blue-400" : isHot ? "text-red-400" : ""
561
+ }
562
+ >
563
+ {v}°C
564
+ </span>
565
+ </Button>
566
+ );
567
+ }}
568
+ </Scale.Ticks>
569
+ </Scale.Linear>
570
+ {selected !== null && (
571
+ <Text size="1" color="tertiary" className="mt-2">
572
+ Selected: {selected}°C
573
+ </Text>
574
+ )}
575
+ </div>
576
+ );
577
+ },
578
+ };