@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,454 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "@/lib/cn";
5
+ import { Separator, Text } from "..";
6
+
7
+ interface SlotSpec {
8
+ key: string;
9
+ atIndex: number;
10
+ children: SlotProps["children"];
11
+ }
12
+
13
+ interface SlotContext {
14
+ globalIndex: number;
15
+ groupIndex: number;
16
+ rank: number;
17
+ rankInGroup: number;
18
+ isFirstInGroup: boolean;
19
+ isLastInGroup: boolean;
20
+ isLastItem: boolean;
21
+ }
22
+
23
+ type SlotChildren =
24
+ | React.ReactNode
25
+ | ((context: SlotContext) => React.ReactNode);
26
+
27
+ type ListEntry<T> =
28
+ | { type: "item"; item: T; key: string }
29
+ | { type: "slot"; spec: SlotSpec };
30
+
31
+ interface RankingContextValue<T> {
32
+ items: T[];
33
+ entries: ListEntry<T>[];
34
+ boundaries: number[];
35
+ labels: string[];
36
+ getKey: (item: T) => string;
37
+ }
38
+
39
+ interface GroupContextValue<T> {
40
+ groupIndex: number;
41
+ groupLabel: string;
42
+ entries: ListEntry<T>[];
43
+ startIndex: number;
44
+ totalEntryCount: number;
45
+ }
46
+
47
+ interface GroupItemContextValue<T> {
48
+ item: T;
49
+ globalIndex: number;
50
+ groupIndex: number;
51
+ rank: number;
52
+ rankInGroup: number;
53
+ isFirstInGroup: boolean;
54
+ isLastInGroup: boolean;
55
+ isLastItem: boolean;
56
+ }
57
+
58
+ const RankingContext = React.createContext<RankingContextValue<unknown> | null>(
59
+ null,
60
+ );
61
+
62
+ function useRanking<T>(): RankingContextValue<T> {
63
+ const context = React.useContext(RankingContext);
64
+ if (!context) {
65
+ throw new Error("useRanking must be used within Ranking.Root");
66
+ }
67
+ return context as RankingContextValue<T>;
68
+ }
69
+
70
+ const GroupContext = React.createContext<GroupContextValue<unknown> | null>(
71
+ null,
72
+ );
73
+
74
+ function useGroup<T>(): GroupContextValue<T> {
75
+ const context = React.useContext(GroupContext);
76
+ if (!context) {
77
+ throw new Error("useGroup must be used within Ranking.Group");
78
+ }
79
+ return context as GroupContextValue<T>;
80
+ }
81
+
82
+ const GroupItemContext =
83
+ React.createContext<GroupItemContextValue<unknown> | null>(null);
84
+
85
+ function useGroupItem<T>(): GroupItemContextValue<T> {
86
+ const context = React.useContext(GroupItemContext);
87
+ if (!context) {
88
+ throw new Error("useGroupItem must be used within Ranking.GroupItem");
89
+ }
90
+ return context as GroupItemContextValue<T>;
91
+ }
92
+
93
+ interface EmptyProps {
94
+ children: React.ReactNode;
95
+ className?: string;
96
+ }
97
+
98
+ function Empty({ children, className }: EmptyProps): React.ReactElement {
99
+ return (
100
+ <div className={cn("flex flex-1 items-center justify-center", className)}>
101
+ {children}
102
+ </div>
103
+ );
104
+ }
105
+
106
+ interface SlotProps {
107
+ slotKey: string;
108
+ atIndex: number;
109
+ children: SlotChildren;
110
+ }
111
+
112
+ function Slot({
113
+ slotKey: _slotKey,
114
+ atIndex: _atIndex,
115
+ children: _children,
116
+ }: SlotProps): React.ReactElement | null {
117
+ return null;
118
+ }
119
+
120
+ function isElementOfType<P>(
121
+ child: React.ReactNode,
122
+ component: React.ComponentType<P>,
123
+ ): child is React.ReactElement<P> {
124
+ return React.isValidElement(child) && child.type === component;
125
+ }
126
+
127
+ interface RankingProps<T> {
128
+ items: T[];
129
+ children: React.ReactNode;
130
+ getKey: (item: T) => string;
131
+ boundaries?: number[];
132
+ labels?: string[];
133
+ className?: string;
134
+ }
135
+
136
+ function RankingRoot<T>({
137
+ items,
138
+ children,
139
+ getKey,
140
+ boundaries = [],
141
+ labels = [],
142
+ className,
143
+ }: RankingProps<T>): React.ReactElement {
144
+ const childArray = React.Children.toArray(children);
145
+
146
+ const emptyChild = childArray.find((child) => isElementOfType(child, Empty));
147
+
148
+ const slots = childArray.flatMap((child) => {
149
+ if (!isElementOfType(child, Slot)) {
150
+ return [];
151
+ }
152
+
153
+ return [
154
+ {
155
+ key: child.props.slotKey,
156
+ atIndex: child.props.atIndex,
157
+ children: child.props.children,
158
+ } satisfies SlotSpec,
159
+ ];
160
+ });
161
+
162
+ const otherChildren = childArray.filter(
163
+ (child) => !isElementOfType(child, Empty) && !isElementOfType(child, Slot),
164
+ );
165
+
166
+ const entries = React.useMemo<ListEntry<T>[]>(() => {
167
+ const result: ListEntry<T>[] = items.map((item) => ({
168
+ type: "item" as const,
169
+ item,
170
+ key: getKey(item),
171
+ }));
172
+
173
+ const sortedSlots = [...slots].sort((a, b) => a.atIndex - b.atIndex);
174
+
175
+ let offset = 0;
176
+ for (const spec of sortedSlots) {
177
+ const slotEntry: ListEntry<T> = { type: "slot", spec };
178
+ const targetIndex = Math.max(0, Math.min(spec.atIndex, items.length));
179
+ const insertAt = targetIndex + offset;
180
+ result.splice(insertAt, 0, slotEntry);
181
+ offset++;
182
+ }
183
+
184
+ return result;
185
+ }, [items, slots, getKey]);
186
+
187
+ if (entries.length === 0) {
188
+ return (
189
+ <div className={className}>
190
+ {emptyChild ? (
191
+ emptyChild
192
+ ) : (
193
+ <Empty>
194
+ <Text color="tertiary">No items</Text>
195
+ </Empty>
196
+ )}
197
+ </div>
198
+ );
199
+ }
200
+
201
+ return (
202
+ <div className={className}>
203
+ <RankingContext.Provider
204
+ value={{
205
+ items: items as unknown[],
206
+ entries: entries as ListEntry<unknown>[],
207
+ boundaries,
208
+ labels,
209
+ getKey: getKey as (item: unknown) => string,
210
+ }}
211
+ >
212
+ {otherChildren}
213
+ </RankingContext.Provider>
214
+ </div>
215
+ );
216
+ }
217
+
218
+ interface GroupProps {
219
+ children: React.ReactNode;
220
+ className?: string;
221
+ }
222
+
223
+ function Group({ children, className }: GroupProps): React.ReactElement {
224
+ const { entries, boundaries, labels } = useRanking();
225
+
226
+ const groups: Array<{
227
+ label: string;
228
+ startIndex: number;
229
+ entries: ListEntry<unknown>[];
230
+ }> = [];
231
+ let currentGroupEntries: ListEntry<unknown>[] = [];
232
+ let currentGroupStartIndex = 0;
233
+ let currentGroupIndex = 0;
234
+ let nextBoundary = boundaries[0] ?? Infinity;
235
+
236
+ entries.forEach((entry, index) => {
237
+ if (index >= nextBoundary) {
238
+ groups.push({
239
+ label: labels[currentGroupIndex] ?? `Group ${currentGroupIndex + 1}`,
240
+ startIndex: currentGroupStartIndex,
241
+ entries: currentGroupEntries,
242
+ });
243
+ currentGroupEntries = [];
244
+ currentGroupStartIndex = index;
245
+ currentGroupIndex++;
246
+ nextBoundary = boundaries[currentGroupIndex] ?? Infinity;
247
+ }
248
+ currentGroupEntries.push(entry);
249
+ });
250
+
251
+ if (currentGroupEntries.length > 0) {
252
+ groups.push({
253
+ label: labels[currentGroupIndex] ?? `Group ${currentGroupIndex + 1}`,
254
+ startIndex: currentGroupStartIndex,
255
+ entries: currentGroupEntries,
256
+ });
257
+ }
258
+
259
+ const childArray = React.Children.toArray(children);
260
+ const dividerChildren = childArray.filter(
261
+ (child) => React.isValidElement(child) && child.type === GroupDivider,
262
+ );
263
+ const itemChildren = childArray.filter(
264
+ (child) => !React.isValidElement(child) || child.type !== GroupDivider,
265
+ );
266
+
267
+ return (
268
+ <>
269
+ {groups.map((group, groupIndex) => (
270
+ <GroupContext.Provider
271
+ key={`${group.startIndex}-${group.label}`}
272
+ value={{
273
+ groupIndex,
274
+ groupLabel: group.label,
275
+ entries: group.entries,
276
+ startIndex: group.startIndex,
277
+ totalEntryCount: entries.length,
278
+ }}
279
+ >
280
+ <div>
281
+ {dividerChildren}
282
+ <ol
283
+ className={cn("m-0 list-none p-0", className)}
284
+ start={group.startIndex + 1}
285
+ >
286
+ {itemChildren}
287
+ </ol>
288
+ </div>
289
+ </GroupContext.Provider>
290
+ ))}
291
+ </>
292
+ );
293
+ }
294
+
295
+ interface GroupItemProps {
296
+ children: React.ReactNode;
297
+ className?: string;
298
+ }
299
+
300
+ function GroupItem({
301
+ children,
302
+ className,
303
+ }: GroupItemProps): React.ReactElement {
304
+ const { entries, startIndex, groupIndex, totalEntryCount } =
305
+ useGroup<unknown>();
306
+ const { getKey } = useRanking<unknown>();
307
+
308
+ return (
309
+ <>
310
+ {entries.map((entry, entryIndexInGroup) => {
311
+ const globalIndex = startIndex + entryIndexInGroup;
312
+ const sharedContext = {
313
+ globalIndex,
314
+ groupIndex,
315
+ rank: globalIndex + 1,
316
+ rankInGroup: entryIndexInGroup + 1,
317
+ isFirstInGroup: entryIndexInGroup === 0,
318
+ isLastInGroup: entryIndexInGroup === entries.length - 1,
319
+ isLastItem: globalIndex === totalEntryCount - 1,
320
+ };
321
+
322
+ if (entry.type === "slot") {
323
+ return (
324
+ <li key={entry.spec.key} className={className}>
325
+ {typeof entry.spec.children === "function"
326
+ ? entry.spec.children(sharedContext)
327
+ : entry.spec.children}
328
+ </li>
329
+ );
330
+ }
331
+
332
+ const context: GroupItemContextValue<unknown> = {
333
+ item: entry.item,
334
+ ...sharedContext,
335
+ };
336
+
337
+ return (
338
+ <GroupItemContext.Provider key={getKey(entry.item)} value={context}>
339
+ <li className={className}>{children}</li>
340
+ </GroupItemContext.Provider>
341
+ );
342
+ })}
343
+ </>
344
+ );
345
+ }
346
+
347
+ interface GroupItemIndexProps {
348
+ children?: (context: {
349
+ globalIndex: number;
350
+ rank: number;
351
+ groupIndex: number;
352
+ }) => React.ReactNode;
353
+ className?: string;
354
+ }
355
+
356
+ function GroupItemIndex({
357
+ children,
358
+ className,
359
+ }: GroupItemIndexProps): React.ReactElement | null {
360
+ const { globalIndex, rank, groupIndex } = useGroupItem();
361
+
362
+ if (children) {
363
+ return <>{children({ globalIndex, rank, groupIndex })}</>;
364
+ }
365
+
366
+ return (
367
+ <Text color="tertiary" className={cn("w-8 shrink-0", className)}>
368
+ #{rank}
369
+ </Text>
370
+ );
371
+ }
372
+
373
+ interface GroupItemValueProps<T> {
374
+ children: (item: T, context: GroupItemContextValue<T>) => React.ReactNode;
375
+ }
376
+
377
+ function GroupItemValue<T>({
378
+ children,
379
+ }: GroupItemValueProps<T>): React.ReactElement | null {
380
+ const groupItemContext = useGroupItem<T>();
381
+ return <>{children(groupItemContext.item, groupItemContext)}</>;
382
+ }
383
+
384
+ interface GroupDividerProps {
385
+ children?: (context: {
386
+ label: string;
387
+ groupIndex: number;
388
+ }) => React.ReactNode;
389
+ className?: string;
390
+ }
391
+
392
+ function GroupDivider({
393
+ children,
394
+ className,
395
+ }: GroupDividerProps): React.ReactElement | null {
396
+ const { groupLabel, groupIndex } = useGroup();
397
+
398
+ if (children) {
399
+ return (
400
+ <div
401
+ data-ranking-group-divider
402
+ className={cn("sticky top-0 bg-background", className)}
403
+ style={{ zIndex: 10 + groupIndex }}
404
+ >
405
+ {children({ label: groupLabel, groupIndex })}
406
+ </div>
407
+ );
408
+ }
409
+
410
+ return (
411
+ <div
412
+ data-ranking-group-divider
413
+ className={cn("sticky top-0 bg-background", className)}
414
+ style={{ zIndex: 10 + groupIndex }}
415
+ >
416
+ <Separator label={groupLabel} />
417
+ </div>
418
+ );
419
+ }
420
+
421
+ interface RankingComponent {
422
+ Root: typeof RankingRoot;
423
+ Empty: typeof Empty;
424
+ Slot: typeof Slot;
425
+ Group: typeof Group;
426
+ GroupItem: typeof GroupItem;
427
+ GroupItemIndex: typeof GroupItemIndex;
428
+ GroupItemValue: typeof GroupItemValue;
429
+ GroupDivider: typeof GroupDivider;
430
+ }
431
+
432
+ const Ranking = {
433
+ Root: RankingRoot,
434
+ Empty,
435
+ Slot,
436
+ Group,
437
+ GroupItem,
438
+ GroupItemIndex,
439
+ GroupItemValue,
440
+ GroupDivider,
441
+ } as RankingComponent;
442
+
443
+ const RankedList: RankingComponent = Ranking;
444
+
445
+ type RankedListProps<T> = RankingProps<T>;
446
+
447
+ export { Ranking, RankedList };
448
+ export type {
449
+ GroupItemContextValue,
450
+ RankedListProps,
451
+ RankingProps,
452
+ SlotContext,
453
+ SlotProps,
454
+ };
@@ -0,0 +1,8 @@
1
+ export type {
2
+ GroupItemContextValue,
3
+ RankedListProps,
4
+ RankingProps,
5
+ SlotContext,
6
+ SlotProps,
7
+ } from "./Ranking";
8
+ export { RankedList, Ranking } from "./Ranking";
@@ -0,0 +1 @@
1
+ export * from "./useCountdown";
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ import { useLayoutEffect, useState } from "react";
4
+ import { formatCountdownString } from "@/utils/format";
5
+
6
+ export interface UseCountdownOptions {
7
+ /** Stop updating when countdown reaches zero (default: false - keeps counting up) */
8
+ stopOnExpired?: boolean;
9
+ }
10
+
11
+ export interface UseCountdownResult {
12
+ timeString: string | null;
13
+ remainingMs: number | null;
14
+ isExpired: boolean;
15
+ }
16
+
17
+ /**
18
+ * Returns countdown state that updates every second via requestAnimationFrame.
19
+ * Only re-renders when the seconds value changes to minimize layout shift.
20
+ *
21
+ * @param to - Target date to count down to (null disables the countdown)
22
+ * @returns Object with formatted timeString, remainingMs, and isExpired flag
23
+ */
24
+ export function useCountdown(
25
+ to: Date | null,
26
+ options?: UseCountdownOptions,
27
+ ): UseCountdownResult {
28
+ const { stopOnExpired = false } = options ?? {};
29
+
30
+ const [result, setResult] = useState<UseCountdownResult>(() => {
31
+ if (!to) return { timeString: null, remainingMs: null, isExpired: false };
32
+ const remainingMs = to.getTime() - Date.now();
33
+ return {
34
+ timeString: formatCountdownString(remainingMs),
35
+ remainingMs,
36
+ isExpired: remainingMs <= 0,
37
+ };
38
+ });
39
+
40
+ useLayoutEffect(() => {
41
+ if (!to) {
42
+ setResult({ timeString: null, remainingMs: null, isExpired: false });
43
+ return;
44
+ }
45
+
46
+ const target = to.getTime();
47
+ const initialRemainingMs = target - Date.now();
48
+ setResult({
49
+ timeString: formatCountdownString(initialRemainingMs),
50
+ remainingMs: initialRemainingMs,
51
+ isExpired: initialRemainingMs <= 0,
52
+ });
53
+ let lastSeconds = Math.floor(Math.abs(initialRemainingMs) / 1000);
54
+ let hasExpired = initialRemainingMs <= 0;
55
+ let frameId: number;
56
+
57
+ const tick = () => {
58
+ const remainingMs = target - Date.now();
59
+ const absMs = Math.abs(remainingMs);
60
+ const isExpired = remainingMs <= 0;
61
+
62
+ // If stopOnExpired and already expired, don't update
63
+ if (stopOnExpired && hasExpired) {
64
+ frameId = requestAnimationFrame(tick);
65
+ return;
66
+ }
67
+
68
+ if (isExpired) {
69
+ hasExpired = true;
70
+ }
71
+
72
+ const currentSeconds = Math.floor(absMs / 1000);
73
+
74
+ if (currentSeconds !== lastSeconds || (isExpired && lastSeconds !== -2)) {
75
+ lastSeconds = isExpired ? -2 : currentSeconds;
76
+ setResult({
77
+ timeString: formatCountdownString(remainingMs),
78
+ remainingMs,
79
+ isExpired,
80
+ });
81
+ }
82
+
83
+ frameId = requestAnimationFrame(tick);
84
+ };
85
+
86
+ frameId = requestAnimationFrame(tick);
87
+ return () => cancelAnimationFrame(frameId);
88
+ }, [to, stopOnExpired]);
89
+
90
+ return result;
91
+ }
package/src/index.ts ADDED
@@ -0,0 +1,130 @@
1
+ export {
2
+ Auction,
3
+ AuctionArtwork,
4
+ AuctionBidForm,
5
+ AuctionBidInput,
6
+ AuctionBiddingPanel,
7
+ AuctionDetails,
8
+ AuctionDetailsBody,
9
+ AuctionDetailsFooter,
10
+ AuctionDetailsHeader,
11
+ AuctionInfo,
12
+ AuctionLayout,
13
+ AuctionProvider,
14
+ AuctionRankings,
15
+ AuctionRankingsContainer,
16
+ AuctionStatusTag,
17
+ AuctionSuggestedBids,
18
+ AuctionYourBidCard,
19
+ AuctionYourBids,
20
+ RankingsSkeleton,
21
+ useAuctionContext,
22
+ useSuggestedBid,
23
+ } from "./components/blocks/auction";
24
+ export type {
25
+ AuctionArtworkProps,
26
+ AuctionBidInputProps,
27
+ AuctionBiddingPanelProps,
28
+ AuctionDetailsBodyProps,
29
+ AuctionDetailsFooterProps,
30
+ AuctionDetailsHeaderProps,
31
+ AuctionDetailsProps,
32
+ AuctionInfoProps,
33
+ AuctionLayoutProps,
34
+ AuctionProps,
35
+ AuctionProviderProps,
36
+ AuctionRankingsContainerProps,
37
+ AuctionRankingsProps,
38
+ AuctionStatusTagProps,
39
+ AuctionSuggestedBidsProps,
40
+ AuctionYourBidCardProps,
41
+ AuctionYourBidsProps,
42
+ SuggestedBidContextValue,
43
+ } from "./components/blocks/auction";
44
+ export { Button, buttonVariants } from "./components/primitives/Button";
45
+ export type {
46
+ ButtonElement,
47
+ ButtonProps,
48
+ } from "./components/primitives/Button";
49
+ export { Countdown } from "./components/primitives/countdown/Countdown";
50
+ export * from "./components/primitives/countdown";
51
+ export { Drawer } from "./components/primitives/Drawer";
52
+ export type { DrawerProps } from "./components/primitives/Drawer";
53
+ export { Feedback } from "./components/primitives/Feedback";
54
+ export { FramedImage } from "./components/primitives/framed-image";
55
+ export type { FramedImageProps } from "./components/primitives/framed-image";
56
+ export { MorphDialog } from "./components/primitives/MorphDialog";
57
+ export type { MorphDialogProps } from "./components/primitives/MorphDialog";
58
+ export { Price, formatPrice } from "./components/primitives/Price";
59
+ export { PriceInput } from "./components/primitives/PriceInput";
60
+ export type {
61
+ FormatPriceOptions,
62
+ PriceProps,
63
+ PriceSymbolProps,
64
+ PriceValueProps,
65
+ } from "./components/primitives/Price";
66
+ export { Receipt } from "./components/primitives/Receipt";
67
+ export type {
68
+ ReceiptDiscountProps,
69
+ ReceiptFeeProps,
70
+ ReceiptFooterProps,
71
+ ReceiptHeaderProps,
72
+ ReceiptItemProps,
73
+ ReceiptPriceProps,
74
+ ReceiptProps,
75
+ ReceiptSeparatorProps,
76
+ ReceiptSubtotalProps,
77
+ ReceiptTaxProps,
78
+ ReceiptTotalProps,
79
+ } from "./components/primitives/Receipt";
80
+ export * from "./components/primitives/ranked-list";
81
+ export { Scale, useScale } from "./components/primitives/Scale";
82
+ export type {
83
+ ScaleTickContext,
84
+ ScaleValue,
85
+ SnapMode,
86
+ } from "./components/primitives/Scale";
87
+ export { Separator } from "./components/primitives/Separator";
88
+ export { Skeleton } from "./components/primitives/Skeleton";
89
+ export type {
90
+ SkeletonElement,
91
+ SkeletonProps,
92
+ } from "./components/primitives/Skeleton";
93
+ export {
94
+ CursorGrowIcon,
95
+ SteppedInput,
96
+ } from "./components/primitives/SteppedInput";
97
+ export {
98
+ Tab,
99
+ Tabs,
100
+ TabsIndicator,
101
+ TabsList,
102
+ TabsPanel,
103
+ } from "./components/primitives/Tabs";
104
+ export type { TabsContextType } from "./components/primitives/Tabs";
105
+ export { Tag } from "./components/primitives/Tag";
106
+ export type { TagElement, TagProps } from "./components/primitives/Tag";
107
+ export { Text, textVariants } from "./components/primitives/Text";
108
+ export type { TextElement, TextProps } from "./components/primitives/Text";
109
+ export { useCountdown } from "./hooks/useCountdown";
110
+ export { cn } from "./lib/cn";
111
+ export { springs, transitions } from "./lib/motion";
112
+ export * from "./types";
113
+ export {
114
+ formatCountdownString,
115
+ formatDateTime,
116
+ formatFullTimestamp,
117
+ formatShortDate,
118
+ formatShortRelative,
119
+ formatWeiToEth,
120
+ parseEthToWei,
121
+ } from "./utils/format";
122
+ export {
123
+ getActiveTickSize,
124
+ isValidTickPrice,
125
+ roundDownToValidBid,
126
+ } from "./utils/tick-validation";
127
+ export {
128
+ getProjectedRankForPrice,
129
+ getSuggestedBidPrices,
130
+ } from "./utils/rank-utils";