@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,158 @@
1
+ import { Meter } from "@base-ui/react/meter";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import React from "react";
4
+ import { Countdown } from "@/components/primitives/countdown";
5
+ import { cn } from "@/lib";
6
+
7
+ const meta: Meta<typeof Countdown> = {
8
+ title: "Primitives/Countdown",
9
+ component: Countdown,
10
+ parameters: {
11
+ layout: "centered",
12
+ },
13
+ decorators: [
14
+ (Story) => (
15
+ <div className="flex flex-col items-center gap-4 rounded-lg p-8">
16
+ <Story />
17
+ </div>
18
+ ),
19
+ ],
20
+ argTypes: {
21
+ showWhenExpired: {
22
+ control: "boolean",
23
+ },
24
+ },
25
+ };
26
+
27
+ export default meta;
28
+
29
+ export const Live: StoryObj<typeof Countdown> = {
30
+ render: (args) => (
31
+ <Countdown {...args} to={new Date(Date.now() + 3600000 * 2 + 45000)} />
32
+ ),
33
+ };
34
+
35
+ export const ShortTime: StoryObj<typeof Countdown> = {
36
+ render: () => <Countdown to={new Date(Date.now() + 60000)} />,
37
+ };
38
+
39
+ export const DaysLeft: StoryObj<typeof Countdown> = {
40
+ render: () => <Countdown to={new Date(Date.now() + 86400000 * 5)} />,
41
+ };
42
+
43
+ export const ShowWhenExpired: StoryObj<typeof Countdown> = {
44
+ render: () => (
45
+ <Countdown to={new Date(Date.now() - 3600000)} showWhenExpired />
46
+ ),
47
+ };
48
+
49
+ export const CustomRender: StoryObj<typeof Countdown> = {
50
+ render: () => (
51
+ <Countdown to={new Date(Date.now() + 86400000)}>
52
+ {({ timeString }) => (
53
+ <span className="font-mono text-xl text-success">{timeString}</span>
54
+ )}
55
+ </Countdown>
56
+ ),
57
+ };
58
+
59
+ export const SemanticLabels: StoryObj<typeof Countdown> = {
60
+ decorators: [
61
+ (Story) => (
62
+ <div className="flex flex-col gap-6 rounded-lg p-8">
63
+ <Story />
64
+ </div>
65
+ ),
66
+ ],
67
+ render: () => (
68
+ <>
69
+ <div className="flex flex-col gap-2">
70
+ <span className="text-xs text-muted-foreground">Open:</span>
71
+ <Countdown to={new Date(Date.now() + 3600000)}>
72
+ {({ timeString, isExpired }) => (
73
+ <span className="font-mono text-base text-success">
74
+ {isExpired ? "Closed" : `Open - ${timeString} left`}
75
+ </span>
76
+ )}
77
+ </Countdown>
78
+ </div>
79
+
80
+ <div className="flex flex-col gap-2">
81
+ <span className="text-xs text-muted-foreground">Upcoming:</span>
82
+ <Countdown to={new Date(Date.now() + 86400000 * 3)}>
83
+ {({ timeString, isExpired }) => (
84
+ <span className="font-mono text-base text-warning">
85
+ {isExpired ? "Live now!" : `Opens in ${timeString}`}
86
+ </span>
87
+ )}
88
+ </Countdown>
89
+ </div>
90
+
91
+ <div className="flex flex-col gap-2">
92
+ <span className="text-xs text-muted-foreground">Closed:</span>
93
+ <Countdown to={new Date(Date.now() - 3600000)} showWhenExpired>
94
+ {({ timeString }) => (
95
+ <span className="font-mono text-base text-destructive">
96
+ Closed - {timeString} ago
97
+ </span>
98
+ )}
99
+ </Countdown>
100
+ </div>
101
+ </>
102
+ ),
103
+ };
104
+
105
+ export const LiveToClosed: StoryObj<typeof Countdown> = {
106
+ render: () => {
107
+ const [targetDate] = React.useState(() => new Date(Date.now() + 10000));
108
+
109
+ return (
110
+ <>
111
+ <p className="mb-2 text-sm text-muted-foreground">
112
+ Watch the transition (expires in ~10s):
113
+ </p>
114
+ <Countdown to={targetDate}>
115
+ {({ timeString, isExpired }) => (
116
+ <span
117
+ className={cn(
118
+ "font-mono text-xl transition-colors",
119
+ isExpired ? "text-destructive" : "text-success",
120
+ )}
121
+ >
122
+ {isExpired
123
+ ? `Closed - ${timeString} ago`
124
+ : `Live - ${timeString} left`}
125
+ </span>
126
+ )}
127
+ </Countdown>
128
+ </>
129
+ );
130
+ },
131
+ };
132
+
133
+ export const WithProgress: StoryObj<typeof Countdown> = {
134
+ render: () => (
135
+ <Countdown to={new Date(Date.now() + 60 * 1 * 1000)} stopOnExpired>
136
+ {({ timeString, remainingMs, isExpired }) => {
137
+ const total = 60 * 1 * 1000;
138
+ const progress = isExpired
139
+ ? 100
140
+ : ((total - (remainingMs ?? 0)) / total) * 100;
141
+
142
+ return (
143
+ <Meter.Root value={progress} className="w-full">
144
+ <div className="mb-1 flex justify-between gap-4 text-sm">
145
+ <Meter.Label className="text-muted-foreground">
146
+ Time remaining
147
+ </Meter.Label>
148
+ <span className="font-mono">{timeString}</span>
149
+ </div>
150
+ <Meter.Track className="h-2 overflow-hidden rounded-full bg-muted">
151
+ <Meter.Indicator className="h-full bg-success transition-all duration-1000" />
152
+ </Meter.Track>
153
+ </Meter.Root>
154
+ );
155
+ }}
156
+ </Countdown>
157
+ ),
158
+ };
@@ -0,0 +1,80 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Button, Feedback } from "@/components/primitives";
4
+
5
+ const meta: Meta<typeof Button> = {
6
+ title: "Primitives/Feedback",
7
+ component: Button,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ argTypes: {
12
+ color: {
13
+ control: "select",
14
+ options: ["primary", "secondary", "tertiary", "ghost"],
15
+ },
16
+ size: {
17
+ control: "select",
18
+ options: ["default", "md"],
19
+ },
20
+ loading: {
21
+ control: "boolean",
22
+ },
23
+ disabled: {
24
+ control: "boolean",
25
+ },
26
+ active: {
27
+ control: "boolean",
28
+ },
29
+ },
30
+ };
31
+
32
+ export default meta;
33
+
34
+ export const Primary: StoryObj<typeof Button> = {
35
+ render() {
36
+ const [feedback, setFeedback] = useState<string | null>();
37
+ const handleClick = async () => {
38
+ setFeedback("feedback for 2000ms");
39
+ await new Promise((resolve) => setTimeout(resolve, 2000));
40
+ setFeedback(null);
41
+ };
42
+ return (
43
+ <Feedback.Root show={!!feedback}>
44
+ <Button onClick={handleClick}>Test</Button>
45
+ <Feedback.Content className="w-full">
46
+ <div className="bg-solid text-background-primary mt-0.5 rounded-md px-2 py-1 text-sm whitespace-nowrap">
47
+ {feedback}
48
+ </div>
49
+ </Feedback.Content>
50
+ </Feedback.Root>
51
+ );
52
+ },
53
+ };
54
+
55
+ export const ChainedFeedback: StoryObj<typeof Button> = {
56
+ render() {
57
+ const [message, setMessage] = useState<string | null>(null);
58
+
59
+ const handleClick = async () => {
60
+ setMessage("Waiting for confirmation...");
61
+ await new Promise((resolve) => setTimeout(resolve, 2000));
62
+ setMessage("Transaction confirmed!");
63
+ await new Promise((resolve) => setTimeout(resolve, 2000));
64
+ setMessage(null);
65
+ };
66
+
67
+ return (
68
+ <Feedback.Root show={!!message}>
69
+ <Button onClick={handleClick} loading={!!message}>
70
+ Submit Transaction
71
+ </Button>
72
+ <Feedback.Content className="w-full">
73
+ <div className="bg-solid text-background-primary mt-0.5 rounded-md px-2 py-1 text-sm whitespace-nowrap">
74
+ {message}
75
+ </div>
76
+ </Feedback.Content>
77
+ </Feedback.Root>
78
+ );
79
+ },
80
+ };
@@ -0,0 +1,46 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Button } from "@/components";
3
+ import { FramedImage } from "@/components/primitives/framed-image";
4
+
5
+ // Classical painting from Wikimedia Commons (public domain)
6
+ const ARTWORK_URL =
7
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/1665_Girl_with_a_Pearl_Earring.jpg/1920px-1665_Girl_with_a_Pearl_Earring.jpg";
8
+
9
+ const meta: Meta<typeof FramedImage> = {
10
+ title: "Primitives/FramedImage",
11
+ component: FramedImage,
12
+ parameters: {
13
+ layout: "fullscreen",
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof FramedImage>;
20
+
21
+ export const Editorial: Story = {
22
+ render: () => (
23
+ <div className="flex min-h-screen w-full items-center justify-center bg-background p-8">
24
+ <article className="flex w-full max-w-md flex-col gap-4">
25
+ <div>
26
+ <h1 className="text-2xl font-semibold">Girl with a Pearl Earring</h1>
27
+ <h2 className="text-lg text-muted-foreground">Johannes Vermeer</h2>
28
+ </div>
29
+ <div className="h-100">
30
+ <FramedImage src={ARTWORK_URL} alt="Girl with a Pearl Earring" />
31
+ </div>
32
+ <p className="leading-relaxed text-muted-foreground">
33
+ A unique digital artwork exploring the intersection of color and form.
34
+ Click the image to open a morphing dialog preview.
35
+ </p>
36
+ <div className="flex items-center justify-between border-t border-border pt-4">
37
+ <div>
38
+ <p className="text-sm text-muted-foreground">Current bid</p>
39
+ <p className="text-xl font-semibold">0.85 ETH</p>
40
+ </div>
41
+ <Button type="button">Place bid</Button>
42
+ </div>
43
+ </article>
44
+ </div>
45
+ ),
46
+ };
@@ -0,0 +1,88 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Button, MorphDialog } from "@/components";
4
+
5
+ // Classical painting from Wikimedia Commons (public domain)
6
+ const ARTWORK_URL =
7
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/1665_Girl_with_a_Pearl_Earring.jpg/1920px-1665_Girl_with_a_Pearl_Earring.jpg";
8
+
9
+ const meta: Meta<typeof MorphDialog> = {
10
+ title: "Primitives/MorphDialog",
11
+ component: MorphDialog,
12
+ parameters: {
13
+ layout: "fullscreen",
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof MorphDialog>;
20
+
21
+ export const Uncontrolled: Story = {
22
+ render: () => (
23
+ <div className="flex min-h-screen items-center justify-center bg-background p-8">
24
+ <MorphDialog
25
+ popupAriaLabel="Expanded artwork preview"
26
+ trigger={
27
+ <div className="overflow-hidden rounded-xl border border-border bg-accent shadow-sm">
28
+ {/* biome-ignore lint/performance/noImgElement: Storybook does not use next/image. */}
29
+ <img
30
+ src={ARTWORK_URL}
31
+ alt="Girl with a Pearl Earring"
32
+ className="block h-[22rem] w-[16rem] object-cover"
33
+ />
34
+ </div>
35
+ }
36
+ content={
37
+ <div className="overflow-hidden rounded-2xl border border-border bg-background shadow-2xl">
38
+ {/* biome-ignore lint/performance/noImgElement: Storybook does not use next/image. */}
39
+ <img
40
+ src={ARTWORK_URL}
41
+ alt="Girl with a Pearl Earring"
42
+ className="block max-h-[85vh] w-auto object-contain"
43
+ />
44
+ </div>
45
+ }
46
+ />
47
+ </div>
48
+ ),
49
+ };
50
+
51
+ export const Controlled: Story = {
52
+ render: () => {
53
+ const [open, setOpen] = useState(false);
54
+
55
+ return (
56
+ <div className="flex min-h-screen flex-col items-center justify-center gap-6 bg-background p-8">
57
+ <Button type="button" color="secondary" onClick={() => setOpen(true)}>
58
+ Open preview
59
+ </Button>
60
+ <MorphDialog
61
+ open={open}
62
+ onOpenChange={setOpen}
63
+ popupAriaLabel="Expanded artwork preview"
64
+ trigger={
65
+ <div className="overflow-hidden rounded-xl border border-border bg-accent shadow-sm">
66
+ {/* biome-ignore lint/performance/noImgElement: Storybook does not use next/image. */}
67
+ <img
68
+ src={ARTWORK_URL}
69
+ alt="Girl with a Pearl Earring"
70
+ className="block h-[18rem] w-[13rem] object-cover"
71
+ />
72
+ </div>
73
+ }
74
+ content={
75
+ <div className="overflow-hidden rounded-2xl border border-border bg-background shadow-2xl">
76
+ {/* biome-ignore lint/performance/noImgElement: Storybook does not use next/image. */}
77
+ <img
78
+ src={ARTWORK_URL}
79
+ alt="Girl with a Pearl Earring"
80
+ className="block max-h-[85vh] w-auto object-contain"
81
+ />
82
+ </div>
83
+ }
84
+ />
85
+ </div>
86
+ );
87
+ },
88
+ };
@@ -0,0 +1,292 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Price } from "@/components/primitives";
3
+
4
+ const meta: Meta<typeof Price> = {
5
+ title: "Primitives/Price",
6
+ component: Price,
7
+ parameters: {
8
+ layout: "centered",
9
+ },
10
+ decorators: [
11
+ (Story) => (
12
+ <div className="flex flex-col items-center gap-4 p-8">
13
+ <Story />
14
+ </div>
15
+ ),
16
+ ],
17
+ };
18
+
19
+ export default meta;
20
+
21
+ export const Basic: StoryObj<typeof Price> = {
22
+ render: () => (
23
+ <div className="flex flex-col gap-2">
24
+ <Price value={12345} decimals={2} />
25
+ <span className="text-xs text-muted-foreground">
26
+ 12345 smallest units (2 decimals) → 123.45
27
+ </span>
28
+ </div>
29
+ ),
30
+ };
31
+
32
+ export const WithSymbolSuffix: StoryObj<typeof Price> = {
33
+ render: () => (
34
+ <div className="flex flex-col gap-2">
35
+ <Price value={500000000000000000n} decimals={18} maxDecimals={4}>
36
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
37
+ </Price>
38
+ <span className="text-xs text-muted-foreground">0.5 ETH (suffix)</span>
39
+ </div>
40
+ ),
41
+ };
42
+
43
+ export const WithSymbolPrefix: StoryObj<typeof Price> = {
44
+ render: () => (
45
+ <div className="flex flex-col gap-2">
46
+ <Price value={12345} decimals={2}>
47
+ <Price.Symbol>$</Price.Symbol>
48
+ <Price.Value />
49
+ </Price>
50
+ <span className="text-xs text-muted-foreground">$123.45 (prefix)</span>
51
+ </div>
52
+ ),
53
+ };
54
+
55
+ export const EthLarge: StoryObj<typeof Price> = {
56
+ render: () => (
57
+ <div className="flex flex-col gap-2">
58
+ <Price value={2450000000000000000n} decimals={18} maxDecimals={4}>
59
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
60
+ </Price>
61
+ <span className="text-xs text-muted-foreground">2.45 ETH</span>
62
+ </div>
63
+ ),
64
+ };
65
+
66
+ export const Usdc: StoryObj<typeof Price> = {
67
+ render: () => (
68
+ <div className="flex flex-col gap-2">
69
+ <Price value={15000000} decimals={6} maxDecimals={2}>
70
+ <Price.Value /> <Price.Symbol>USDC</Price.Symbol>
71
+ </Price>
72
+ <span className="text-xs text-muted-foreground">
73
+ 15 USDC (6 decimals)
74
+ </span>
75
+ </div>
76
+ ),
77
+ };
78
+
79
+ export const DecimalOnly: StoryObj<typeof Price> = {
80
+ render: () => (
81
+ <div className="flex flex-col gap-2">
82
+ <Price value={1000000} decimals={6} />
83
+ <span className="text-xs text-muted-foreground">No symbol: 1</span>
84
+ </div>
85
+ ),
86
+ };
87
+
88
+ export const FractionalEth: StoryObj<typeof Price> = {
89
+ render: () => (
90
+ <div className="flex flex-col gap-2">
91
+ <Price value={420000000000000n} decimals={18} maxDecimals={6}>
92
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
93
+ </Price>
94
+ <span className="text-xs text-muted-foreground">
95
+ 0.00042 ETH (very small)
96
+ </span>
97
+ </div>
98
+ ),
99
+ };
100
+
101
+ export const StringInput: StoryObj<typeof Price> = {
102
+ render: () => (
103
+ <div className="flex flex-col gap-2">
104
+ <Price value="9876543210" decimals={9} maxDecimals={4}>
105
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
106
+ </Price>
107
+ <span className="text-xs text-muted-foreground">
108
+ String input: 9.8766 ETH (ceiled)
109
+ </span>
110
+ </div>
111
+ ),
112
+ };
113
+
114
+ export const MaxDecimals: StoryObj<typeof Price> = {
115
+ render: () => (
116
+ <div className="flex flex-col gap-4">
117
+ <div className="flex flex-col gap-1">
118
+ <Price value={123456789n} decimals={9}>
119
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
120
+ </Price>
121
+ <span className="text-xs text-muted-foreground">
122
+ Full precision: 0.123456789 ETH
123
+ </span>
124
+ </div>
125
+ <div className="flex flex-col gap-1">
126
+ <Price value={123456789n} decimals={9} maxDecimals={4}>
127
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
128
+ </Price>
129
+ <span className="text-xs text-muted-foreground">
130
+ maxDecimals=4: 0.1235 ETH (ceiled)
131
+ </span>
132
+ </div>
133
+ <div className="flex flex-col gap-1">
134
+ <Price value={123456789n} decimals={9} maxDecimals={2}>
135
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
136
+ </Price>
137
+ <span className="text-xs text-muted-foreground">
138
+ maxDecimals=2: 0.13 ETH (ceiled)
139
+ </span>
140
+ </div>
141
+ </div>
142
+ ),
143
+ };
144
+
145
+ export const Abbreviation: StoryObj<typeof Price> = {
146
+ render: () => (
147
+ <div className="flex flex-col gap-4">
148
+ <div className="flex flex-col gap-1">
149
+ <Price value={1500000n} decimals={2} abbreviate>
150
+ <Price.Symbol>$</Price.Symbol>
151
+ <Price.Value />
152
+ </Price>
153
+ <span className="text-xs text-muted-foreground">$15K</span>
154
+ </div>
155
+ <div className="flex flex-col gap-1">
156
+ <Price value={250000000n} decimals={2} abbreviate>
157
+ <Price.Symbol>$</Price.Symbol>
158
+ <Price.Value />
159
+ </Price>
160
+ <span className="text-xs text-muted-foreground">$2.5M</span>
161
+ </div>
162
+ <div className="flex flex-col gap-1">
163
+ <Price
164
+ value={2500000000000000000000n}
165
+ decimals={18}
166
+ abbreviate
167
+ maxDecimals={1}
168
+ >
169
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
170
+ </Price>
171
+ <span className="text-xs text-muted-foreground">2.5K ETH</span>
172
+ </div>
173
+ <div className="flex flex-col gap-1">
174
+ <Price value={50} decimals={2} abbreviate>
175
+ <Price.Symbol>$</Price.Symbol>
176
+ <Price.Value />
177
+ </Price>
178
+ <span className="text-xs text-muted-foreground">
179
+ $0.5 (too small to abbreviate)
180
+ </span>
181
+ </div>
182
+ </div>
183
+ ),
184
+ };
185
+
186
+ export const StyledSymbol: StoryObj<typeof Price> = {
187
+ render: () => (
188
+ <div className="flex flex-col gap-2">
189
+ <Price value={500000000000000000n} decimals={18} maxDecimals={4}>
190
+ <Price.Value />{" "}
191
+ <Price.Symbol className="text-muted-foreground">ETH</Price.Symbol>
192
+ </Price>
193
+ <span className="text-xs text-muted-foreground">
194
+ Symbol with custom styling
195
+ </span>
196
+ </div>
197
+ ),
198
+ };
199
+
200
+ export const ZeroValue: StoryObj<typeof Price> = {
201
+ render: () => (
202
+ <div className="flex flex-col gap-2">
203
+ <Price value={0n} decimals={18}>
204
+ <Price.Value /> <Price.Symbol>ETH</Price.Symbol>
205
+ </Price>
206
+ <span className="text-xs text-muted-foreground">Zero: 0 ETH</span>
207
+ </div>
208
+ ),
209
+ };
210
+
211
+ export const Locales: StoryObj<typeof Price> = {
212
+ render: () => (
213
+ <div className="flex flex-col gap-6">
214
+ <div className="flex flex-col gap-1">
215
+ <span className="text-sm font-medium">Default (no locale)</span>
216
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
217
+ <Price.Symbol>$</Price.Symbol>
218
+ <Price.Value />
219
+ </Price>
220
+ <span className="text-xs text-muted-foreground">$1234567.89</span>
221
+ </div>
222
+
223
+ <div className="flex flex-col gap-1">
224
+ <span className="text-sm font-medium">English (en-US)</span>
225
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
226
+ <Price.Symbol>$</Price.Symbol>
227
+ <Price.Value locale="en-US" />
228
+ </Price>
229
+ <span className="text-xs text-muted-foreground">$1,234,567.89</span>
230
+ </div>
231
+
232
+ <div className="flex flex-col gap-1">
233
+ <span className="text-sm font-medium">German (de-DE)</span>
234
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
235
+ <Price.Value locale="de-DE" /> <Price.Symbol>EUR</Price.Symbol>
236
+ </Price>
237
+ <span className="text-xs text-muted-foreground">1.234.567,89 EUR</span>
238
+ </div>
239
+
240
+ <div className="flex flex-col gap-1">
241
+ <span className="text-sm font-medium">French (fr-FR)</span>
242
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
243
+ <Price.Value locale="fr-FR" /> <Price.Symbol>EUR</Price.Symbol>
244
+ </Price>
245
+ <span className="text-xs text-muted-foreground">
246
+ 1 234 567,89 EUR (thin space grouping)
247
+ </span>
248
+ </div>
249
+
250
+ <div className="flex flex-col gap-1">
251
+ <span className="text-sm font-medium">Japanese (ja-JP)</span>
252
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
253
+ <Price.Symbol>¥</Price.Symbol>
254
+ <Price.Value locale="ja-JP" />
255
+ </Price>
256
+ <span className="text-xs text-muted-foreground">¥1,234,567.89</span>
257
+ </div>
258
+
259
+ <div className="flex flex-col gap-1">
260
+ <span className="text-sm font-medium">Swiss German (de-CH)</span>
261
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
262
+ <Price.Symbol>CHF </Price.Symbol>
263
+ <Price.Value locale="de-CH" />
264
+ </Price>
265
+ <span className="text-xs text-muted-foreground">
266
+ CHF 1'234'567.89 (apostrophe grouping)
267
+ </span>
268
+ </div>
269
+
270
+ <div className="flex flex-col gap-1">
271
+ <span className="text-sm font-medium">Indian (en-IN)</span>
272
+ <Price value={123456789n} decimals={2} maxDecimals={2}>
273
+ <Price.Symbol>₹</Price.Symbol>
274
+ <Price.Value locale="en-IN" />
275
+ </Price>
276
+ <span className="text-xs text-muted-foreground">
277
+ ₹12,34,567.89 (lakh grouping)
278
+ </span>
279
+ </div>
280
+
281
+ <div className="flex flex-col gap-1">
282
+ <span className="text-sm font-medium">
283
+ German + Abbreviation (de-DE)
284
+ </span>
285
+ <Price value={250000000n} decimals={2} abbreviate maxDecimals={1}>
286
+ <Price.Value locale="de-DE" /> <Price.Symbol>EUR</Price.Symbol>
287
+ </Price>
288
+ <span className="text-xs text-muted-foreground">2,5M EUR</span>
289
+ </div>
290
+ </div>
291
+ ),
292
+ };