@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.
Files changed (79) hide show
  1. package/dist/styles.css +2 -0
  2. package/dist/tokens.css +2 -0
  3. package/package.json +10 -10
  4. package/src/components/blocks/auction/Auction.tsx +0 -74
  5. package/src/components/blocks/auction/AuctionArtwork.tsx +0 -4
  6. package/src/components/blocks/auction/AuctionBidForm.tsx +0 -138
  7. package/src/components/blocks/auction/AuctionBidInput.tsx +0 -166
  8. package/src/components/blocks/auction/AuctionContext.tsx +0 -401
  9. package/src/components/blocks/auction/AuctionInfo.tsx +0 -36
  10. package/src/components/blocks/auction/AuctionLayout.tsx +0 -200
  11. package/src/components/blocks/auction/AuctionRankings.tsx +0 -435
  12. package/src/components/blocks/auction/AuctionStatusTag.tsx +0 -98
  13. package/src/components/blocks/auction/AuctionSuggestedBids.tsx +0 -203
  14. package/src/components/blocks/auction/AuctionYourBidCard.tsx +0 -125
  15. package/src/components/blocks/auction/AuctionYourBids.tsx +0 -61
  16. package/src/components/blocks/auction/index.ts +0 -42
  17. package/src/components/blocks/index.ts +0 -1
  18. package/src/components/index.ts +0 -2
  19. package/src/components/primitives/Button.tsx +0 -183
  20. package/src/components/primitives/Drawer.tsx +0 -125
  21. package/src/components/primitives/Feedback.tsx +0 -185
  22. package/src/components/primitives/MorphDialog.tsx +0 -160
  23. package/src/components/primitives/Price.tsx +0 -394
  24. package/src/components/primitives/PriceInput.tsx +0 -48
  25. package/src/components/primitives/Receipt.tsx +0 -711
  26. package/src/components/primitives/Scale.tsx +0 -287
  27. package/src/components/primitives/Separator.tsx +0 -87
  28. package/src/components/primitives/Skeleton.tsx +0 -33
  29. package/src/components/primitives/SteppedInput.tsx +0 -313
  30. package/src/components/primitives/Tabs.tsx +0 -161
  31. package/src/components/primitives/Tag.tsx +0 -48
  32. package/src/components/primitives/Text.tsx +0 -102
  33. package/src/components/primitives/countdown/Countdown.tsx +0 -43
  34. package/src/components/primitives/countdown/index.ts +0 -2
  35. package/src/components/primitives/framed-image/FramedImage.tsx +0 -51
  36. package/src/components/primitives/framed-image/index.ts +0 -1
  37. package/src/components/primitives/index.ts +0 -42
  38. package/src/components/primitives/ranked-list/RankedList.tsx +0 -9
  39. package/src/components/primitives/ranked-list/Ranking.tsx +0 -454
  40. package/src/components/primitives/ranked-list/index.ts +0 -8
  41. package/src/hooks/index.ts +0 -1
  42. package/src/hooks/useCountdown.ts +0 -91
  43. package/src/index.ts +0 -130
  44. package/src/lib/cn.ts +0 -81
  45. package/src/lib/index.ts +0 -2
  46. package/src/lib/motion.ts +0 -55
  47. package/src/public/lea-83-time-walk.png +0 -0
  48. package/src/public/lea-83-time-walk.webp +0 -0
  49. package/src/stories/Auction.stories.tsx +0 -658
  50. package/src/stories/AuctionLayout.stories.tsx +0 -313
  51. package/src/stories/AuctionStatusTag.stories.tsx +0 -166
  52. package/src/stories/AuctionYourBidCard.stories.tsx +0 -257
  53. package/src/stories/Button.stories.tsx +0 -306
  54. package/src/stories/Countdown.stories.tsx +0 -158
  55. package/src/stories/Feedback.stories.tsx +0 -80
  56. package/src/stories/FramedImage.stories.tsx +0 -46
  57. package/src/stories/MorphDialog.stories.tsx +0 -88
  58. package/src/stories/Price.stories.tsx +0 -292
  59. package/src/stories/RankedList.stories.tsx +0 -190
  60. package/src/stories/Receipt.stories.tsx +0 -221
  61. package/src/stories/Scale.stories.tsx +0 -578
  62. package/src/stories/Separator.stories.tsx +0 -188
  63. package/src/stories/Skeleton.stories.tsx +0 -138
  64. package/src/stories/SteppedInput.stories.tsx +0 -321
  65. package/src/stories/Tabs.stories.tsx +0 -215
  66. package/src/stories/Tag.stories.tsx +0 -138
  67. package/src/stories/Text.stories.tsx +0 -245
  68. package/src/styles/globals.css +0 -39
  69. package/src/styles/index.css +0 -4
  70. package/src/styles/theme/animation.css +0 -11
  71. package/src/styles/theme/color.css +0 -185
  72. package/src/styles/theme/index.css +0 -3
  73. package/src/styles/theme/typography.css +0 -3
  74. package/src/styles/utility.css +0 -8
  75. package/src/types/index.ts +0 -149
  76. package/src/utils/format.ts +0 -130
  77. package/src/utils/index.ts +0 -16
  78. package/src/utils/rank-utils.ts +0 -131
  79. package/src/utils/tick-validation.ts +0 -65
@@ -1,203 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { Button, Scale, Text } from "@/components/primitives";
5
- import { cn } from "@/lib";
6
- import { getProjectedRankForPrice } from "@/utils";
7
- import { useAuctionContext } from "./AuctionContext";
8
-
9
- export interface SuggestedBidContextValue {
10
- value: bigint;
11
- display: string;
12
- position: number;
13
- index: number;
14
- isSelected: boolean;
15
- onSelect: () => void;
16
- disabled: boolean;
17
- projectedRank: number | null;
18
- isWinning: boolean;
19
- }
20
-
21
- const SuggestedBidContext =
22
- React.createContext<SuggestedBidContextValue | null>(null);
23
-
24
- function useSuggestedBid(): SuggestedBidContextValue {
25
- const context = React.useContext(SuggestedBidContext);
26
- if (!context) {
27
- throw new Error(
28
- "useSuggestedBid must be used within AuctionSuggestedBids.Item",
29
- );
30
- }
31
- return context;
32
- }
33
-
34
- export interface AuctionSuggestedBidsProps {
35
- className?: string;
36
- count?: number;
37
- children: (context: SuggestedBidContextValue) => React.ReactNode;
38
- }
39
-
40
- function AuctionSuggestedBidsRoot({
41
- className,
42
- count = 4,
43
- children,
44
- }: AuctionSuggestedBidsProps): React.ReactElement | null {
45
- const {
46
- minBidValue,
47
- tickConfig,
48
- tickSize,
49
- isAuctionEnded,
50
- bidValue,
51
- setBidValue,
52
- formatPrice,
53
- currencySymbol,
54
- bids,
55
- setShowBidPreview,
56
- lockedBid,
57
- userBids,
58
- mergedForRank,
59
- maxTotalItems,
60
- } = useAuctionContext();
61
-
62
- const lockedBidId = React.useMemo(() => {
63
- if (lockedBid === null) return null;
64
- const lockedUserBid = userBids.find(
65
- (ub) => ub.globalBidId === lockedBid.bidId,
66
- );
67
- return lockedUserBid?.id ?? null;
68
- }, [lockedBid, userBids]);
69
-
70
- const bidsForRankProjection = React.useMemo(() => {
71
- if (lockedBidId === null) return mergedForRank;
72
- return mergedForRank.filter((b) => b.id !== lockedBidId);
73
- }, [mergedForRank, lockedBidId]);
74
-
75
- const getProjectedRank = React.useCallback(
76
- (priceValue: bigint) => {
77
- return getProjectedRankForPrice(
78
- priceValue,
79
- bidsForRankProjection,
80
- maxTotalItems,
81
- );
82
- },
83
- [bidsForRankProjection, maxTotalItems],
84
- );
85
-
86
- if (isAuctionEnded) {
87
- return null;
88
- }
89
-
90
- const getTickSize = (value: bigint): bigint => {
91
- if (!tickConfig) return tickSize;
92
- return value > tickConfig.threshold
93
- ? tickConfig.largeTickSize
94
- : tickConfig.smallTickSize;
95
- };
96
-
97
- const effectiveMinBid =
98
- lockedBid !== null && lockedBid.priceValue > minBidValue
99
- ? lockedBid.priceValue
100
- : minBidValue;
101
-
102
- const highestBid =
103
- bids.length > 0 ? BigInt(bids[0].price) : effectiveMinBid * 3n;
104
-
105
- const leadingBid = highestBid + getTickSize(highestBid);
106
- const maxBid =
107
- leadingBid > effectiveMinBid ? leadingBid : effectiveMinBid * 3n;
108
-
109
- const selectedValue = bidValue;
110
- const disabled = isAuctionEnded;
111
-
112
- return (
113
- <Scale.Linear
114
- domain={[effectiveMinBid, maxBid]}
115
- getTickSize={getTickSize}
116
- snapMode="up"
117
- className={cn("grid grid-cols-2 gap-2", className)}
118
- >
119
- <Scale.Ticks count={count}>
120
- {(
121
- { value, position }: { value: bigint; position: number },
122
- index: number,
123
- ) => {
124
- const v = value;
125
- const display = `${formatPrice(v)} ${currencySymbol}`;
126
- const isSelected = v === selectedValue;
127
- const { rank: projectedRank, isWinning } = getProjectedRank(v);
128
-
129
- const contextValue: SuggestedBidContextValue = {
130
- value: v,
131
- display,
132
- position,
133
- index,
134
- isSelected,
135
- onSelect: () => {
136
- setBidValue(v);
137
- setShowBidPreview(true);
138
- },
139
- disabled,
140
- projectedRank,
141
- isWinning,
142
- };
143
-
144
- return (
145
- <SuggestedBidContext.Provider value={contextValue}>
146
- {children(contextValue)}
147
- </SuggestedBidContext.Provider>
148
- );
149
- }}
150
- </Scale.Ticks>
151
- </Scale.Linear>
152
- );
153
- }
154
-
155
- interface AuctionSuggestedBidsItemProps {
156
- labels?: string[];
157
- }
158
-
159
- function AuctionSuggestedBidsItem({
160
- labels = ["Minimum", "Safe Entry", "Competitive", "Leading"],
161
- }: AuctionSuggestedBidsItemProps): React.ReactElement {
162
- const context = useSuggestedBid();
163
- const label = labels[context.index] ?? labels[labels.length - 1] ?? "Bid";
164
-
165
- return (
166
- <Button
167
- type="button"
168
- color="tertiary"
169
- active={context.isSelected}
170
- className="h-auto w-full items-start justify-between gap-2 px-3 py-2.5 text-left"
171
- disabled={context.disabled}
172
- onClick={context.onSelect}
173
- size="card"
174
- >
175
- <div className="flex flex-col">
176
- <Text color="secondary" size="1">
177
- {label}
178
- </Text>
179
- <Text>{context.display}</Text>
180
- </div>
181
- {context.projectedRank != null && (
182
- <span className="flex shrink-0 flex-col">
183
- <Text color="secondary" size="1">
184
- Rank
185
- </Text>
186
- <Text>#{context.projectedRank}</Text>
187
- </span>
188
- )}
189
- </Button>
190
- );
191
- }
192
-
193
- interface AuctionSuggestedBidsComponent {
194
- Root: typeof AuctionSuggestedBidsRoot;
195
- Item: typeof AuctionSuggestedBidsItem;
196
- }
197
-
198
- export const AuctionSuggestedBids: AuctionSuggestedBidsComponent = {
199
- Root: AuctionSuggestedBidsRoot,
200
- Item: AuctionSuggestedBidsItem,
201
- };
202
-
203
- export { useSuggestedBid };
@@ -1,125 +0,0 @@
1
- "use client";
2
-
3
- import { Button, Text } from "@/components/primitives";
4
- import { cn } from "@/lib";
5
- import type { AuctionUserBid } from "@/types";
6
- import { formatDateTime } from "@/utils";
7
-
8
- export interface AuctionYourBidCardProps {
9
- bid: AuctionUserBid;
10
- getRankForBid: (bidId: string) => number | null;
11
- lockedBidId: bigint | null;
12
- onLockForTopUp: (bidId: bigint, priceValue: bigint) => void;
13
- onCancelTopUp: () => void;
14
- isAuctionEnded: boolean;
15
- onClaim?: (bidId: string) => Promise<boolean>;
16
- formatPrice?: (priceValue: bigint) => string;
17
- currencySymbol?: string;
18
- }
19
-
20
- function getBidStatusLabel(
21
- status: AuctionUserBid["status"],
22
- isWinning: boolean,
23
- ): string {
24
- if (status === "claimed") return "Won & Claimed";
25
- if (isWinning) return "Winning";
26
- if (status === "refunded") return "Refunded";
27
- return "Outbid";
28
- }
29
-
30
- export function AuctionYourBidCard({
31
- bid,
32
- getRankForBid,
33
- lockedBidId,
34
- onLockForTopUp,
35
- onCancelTopUp,
36
- isAuctionEnded,
37
- onClaim,
38
- formatPrice = (v) =>
39
- (Number(v) / 1e18).toLocaleString("en-US", {
40
- minimumFractionDigits: 3,
41
- maximumFractionDigits: 3,
42
- }),
43
- currencySymbol = "USD",
44
- }: AuctionYourBidCardProps): React.ReactElement {
45
- const rank = getRankForBid(bid.id);
46
- const isLocked = lockedBidId !== null && bid.globalBidId === lockedBidId;
47
-
48
- return (
49
- <div
50
- className={cn(
51
- "rounded-xs border bg-muted p-4",
52
- isLocked ? "border-success" : "border-transparent",
53
- )}
54
- >
55
- <div className="flex items-start justify-between gap-2">
56
- <Text size="1" color="tertiary">
57
- Rank #{rank ?? "-"}
58
- </Text>
59
- <Text
60
- size="1"
61
- color="tertiary"
62
- className="tabular-nums"
63
- suppressHydrationWarning
64
- >
65
- {formatDateTime(bid.createdAt)}
66
- </Text>
67
- </div>
68
- <div className="flex justify-between">
69
- <Text size="2" className="mt-2">
70
- {formatPrice(bid.price)} {currencySymbol}
71
- </Text>
72
- <div className="mt-2 flex items-center gap-1.5">
73
- {bid.isWinning ? (
74
- <>
75
- <div className="size-4 rounded-full bg-success" aria-hidden />
76
- <Text size="2" color="success">
77
- {getBidStatusLabel(bid.status, bid.isWinning)}
78
- </Text>
79
- </>
80
- ) : (
81
- <>
82
- <div className="size-4 rounded-full bg-destructive" aria-hidden />
83
- <Text size="2" color="error">
84
- {getBidStatusLabel(bid.status, bid.isWinning)}
85
- </Text>
86
- </>
87
- )}
88
- </div>
89
- </div>
90
- {bid.status === "claimed" ? (
91
- <Text
92
- size="1"
93
- color="tertiary"
94
- className="mt-2"
95
- suppressHydrationWarning
96
- >
97
- Claimed {bid.claimedAt ? formatDateTime(bid.claimedAt) : ""}
98
- </Text>
99
- ) : bid.isWinning && bid.status === "active" && onClaim ? (
100
- <Button
101
- color="secondary"
102
- className="mt-3 w-full"
103
- onClick={() => onClaim(bid.id)}
104
- >
105
- Claim
106
- </Button>
107
- ) : null}
108
- {bid.isWinning && !isAuctionEnded ? (
109
- <div className="mt-3">
110
- <Button
111
- color="secondary"
112
- className="w-full"
113
- onClick={() =>
114
- isLocked
115
- ? onCancelTopUp()
116
- : onLockForTopUp(bid.globalBidId, bid.price)
117
- }
118
- >
119
- {isLocked ? "Cancel top-up" : "Top up"}
120
- </Button>
121
- </div>
122
- ) : null}
123
- </div>
124
- );
125
- }
@@ -1,61 +0,0 @@
1
- "use client";
2
-
3
- import { Text } from "@/components/primitives";
4
- import { useAuctionContext } from "./AuctionContext";
5
- import { AuctionYourBidCard } from "./AuctionYourBidCard";
6
-
7
- export interface AuctionYourBidsProps {
8
- className?: string;
9
- }
10
-
11
- export function AuctionYourBids({
12
- className,
13
- }: AuctionYourBidsProps): React.ReactElement | null {
14
- const {
15
- userBids,
16
- getRankForBid,
17
- lockedBid,
18
- setLockedBid,
19
- isAuctionEnded,
20
- handleClaimEdition,
21
- formatPrice,
22
- currencySymbol,
23
- } = useAuctionContext();
24
-
25
- if (userBids.length === 0) return null;
26
-
27
- const onLockForTopUp = (bidId: bigint, priceValue: bigint) => {
28
- setLockedBid({ bidId, priceValue });
29
- };
30
-
31
- const onCancelTopUp = () => setLockedBid(null);
32
-
33
- return (
34
- <div className={className}>
35
- <Text
36
- as="h3"
37
- color="tertiary"
38
- className="mb-3 shrink-0"
39
- aria-label={`Your Bids (${userBids.length})`}
40
- >
41
- Your Bids ({userBids.length})
42
- </Text>
43
- <div className="-mr-1 min-h-0 flex-1 space-y-3 overflow-y-auto py-1 pr-1">
44
- {userBids.map((bid) => (
45
- <AuctionYourBidCard
46
- key={bid.id}
47
- bid={bid}
48
- getRankForBid={getRankForBid}
49
- lockedBidId={lockedBid?.bidId ?? null}
50
- onLockForTopUp={onLockForTopUp}
51
- onCancelTopUp={onCancelTopUp}
52
- isAuctionEnded={isAuctionEnded}
53
- onClaim={handleClaimEdition}
54
- formatPrice={formatPrice}
55
- currencySymbol={currencySymbol}
56
- />
57
- ))}
58
- </div>
59
- </div>
60
- );
61
- }
@@ -1,42 +0,0 @@
1
- export type { AuctionProps } from "./Auction";
2
- export { Auction, useAuctionContext } from "./Auction";
3
- export type { AuctionArtworkProps } from "./AuctionArtwork";
4
- export { AuctionArtwork } from "./AuctionArtwork";
5
- export { AuctionBidForm } from "./AuctionBidForm";
6
- export type { AuctionBidInputProps } from "./AuctionBidInput";
7
- export { AuctionBidInput } from "./AuctionBidInput";
8
- export type { AuctionProviderProps } from "./AuctionContext";
9
- export { AuctionProvider } from "./AuctionContext";
10
- export type { AuctionInfoProps } from "./AuctionInfo";
11
- export { AuctionInfo } from "./AuctionInfo";
12
- export type {
13
- AuctionBiddingPanelProps,
14
- AuctionDetailsBodyProps,
15
- AuctionDetailsFooterProps,
16
- AuctionDetailsHeaderProps,
17
- AuctionDetailsProps,
18
- AuctionLayoutProps,
19
- AuctionRankingsContainerProps,
20
- } from "./AuctionLayout";
21
- export {
22
- AuctionBiddingPanel,
23
- AuctionDetails,
24
- AuctionDetailsBody,
25
- AuctionDetailsFooter,
26
- AuctionDetailsHeader,
27
- AuctionLayout,
28
- AuctionRankingsContainer,
29
- } from "./AuctionLayout";
30
- export type { AuctionRankingsProps } from "./AuctionRankings";
31
- export { AuctionRankings, RankingsSkeleton } from "./AuctionRankings";
32
- export type { AuctionStatusTagProps } from "./AuctionStatusTag";
33
- export { AuctionStatusTag } from "./AuctionStatusTag";
34
- export type {
35
- AuctionSuggestedBidsProps,
36
- SuggestedBidContextValue,
37
- } from "./AuctionSuggestedBids";
38
- export { AuctionSuggestedBids, useSuggestedBid } from "./AuctionSuggestedBids";
39
- export type { AuctionYourBidCardProps } from "./AuctionYourBidCard";
40
- export { AuctionYourBidCard } from "./AuctionYourBidCard";
41
- export type { AuctionYourBidsProps } from "./AuctionYourBids";
42
- export { AuctionYourBids } from "./AuctionYourBids";
@@ -1 +0,0 @@
1
- export * from "./auction";
@@ -1,2 +0,0 @@
1
- export * from "./blocks";
2
- export * from "./primitives";
@@ -1,183 +0,0 @@
1
- import { Button as ButtonPrimitive } from "@base-ui/react/button";
2
- import { IconLoader2 } from "@tabler/icons-react";
3
- import { cva, type VariantProps } from "class-variance-authority";
4
- import type * as React from "react";
5
- import { cn } from "@/lib/cn";
6
-
7
- type ButtonColor =
8
- | "primary"
9
- | "secondary"
10
- | "tertiary"
11
- | "ghost"
12
- | "destructive"
13
- | "link";
14
- type ButtonSize =
15
- | "default"
16
- | "xs"
17
- | "sm"
18
- | "lg"
19
- | "xl"
20
- | "icon"
21
- | "icon-xs"
22
- | "icon-sm"
23
- | "icon-lg"
24
- | "card";
25
-
26
- interface ButtonVariantProps {
27
- color?: ButtonColor;
28
- size?: ButtonSize;
29
- className?: string;
30
- }
31
-
32
- const buttonVariants = cva(
33
- [
34
- // Base layout & typography
35
- "inline-flex cursor-pointer items-center justify-center gap-1.5",
36
- "font-sans text-sm leading-none font-medium whitespace-nowrap",
37
- "shrink-0 select-none",
38
- // Transitions
39
- "transition-all duration-150 ease-out",
40
- // SVG children
41
- "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
42
- // Focus ring
43
- "outline-none",
44
- "focus-visible:ring-2 focus-visible:ring-offset-2",
45
- "focus-visible:ring-ring focus-visible:ring-offset-background",
46
- // Disabled - prevent interaction
47
- "disabled:pointer-events-none disabled:cursor-not-allowed aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed",
48
- // aria-invalid
49
- "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/30",
50
- ].join(" "),
51
- {
52
- variants: {
53
- color: {
54
- primary: [
55
- "border border-solid",
56
- "border-primary bg-primary text-primary-foreground",
57
- "hover:border-primary-hover hover:bg-primary-hover",
58
- "active:opacity-90",
59
- "shadow-[0_1px_2px_0_rgb(0,0,0,0.08)]",
60
- "disabled:opacity-50 aria-disabled:opacity-50",
61
- ].join(" "),
62
-
63
- secondary: [
64
- "border border-solid",
65
- "border-border bg-transparent text-foreground",
66
- "hover:border-input hover:bg-accent",
67
- "active:border-input active:bg-accent-active",
68
- "data-[state=active]:border-input data-[state=active]:bg-accent-active",
69
- "shadow-[0_1px_2px_0_rgb(0,0,0,0.04)]",
70
- "disabled:opacity-50 aria-disabled:opacity-50",
71
- ].join(" "),
72
-
73
- tertiary: [
74
- "border border-solid",
75
- "border-border bg-muted text-muted-foreground",
76
- "hover:border-input hover:bg-accent-hover hover:text-foreground",
77
- "active:bg-accent-active active:text-foreground",
78
- "data-[state=active]:bg-accent-active data-[state=active]:text-foreground",
79
- "disabled:opacity-50 aria-disabled:opacity-50",
80
- ].join(" "),
81
-
82
- ghost: [
83
- "border border-transparent bg-transparent",
84
- "text-muted-foreground",
85
- "hover:bg-accent hover:text-foreground",
86
- "active:bg-accent-active active:text-foreground",
87
- "data-[state=active]:bg-accent-active data-[state=active]:text-foreground",
88
- "disabled:text-disabled-foreground",
89
- "aria-disabled:text-disabled-foreground",
90
- ].join(" "),
91
-
92
- destructive: [
93
- "border border-solid",
94
- "border-destructive bg-destructive text-destructive-foreground",
95
- "hover:border-destructive-hover hover:bg-destructive-hover",
96
- "active:opacity-90",
97
- "shadow-[0_1px_2px_0_rgb(0,0,0,0.08)]",
98
- "disabled:opacity-50 aria-disabled:opacity-50",
99
- ].join(" "),
100
-
101
- link: [
102
- "border-transparent bg-transparent",
103
- "text-foreground underline-offset-4",
104
- "hover:underline",
105
- "h-auto px-0 py-0",
106
- "disabled:text-disabled-foreground disabled:no-underline",
107
- "aria-disabled:text-disabled-foreground aria-disabled:no-underline",
108
- ].join(" "),
109
- },
110
-
111
- size: {
112
- default: "h-9 rounded-md px-4 py-2 has-[>svg]:px-3",
113
- xs: "h-6 gap-1 rounded px-2 text-xs has-[>svg]:px-1.5",
114
- sm: "h-8 gap-1 rounded-md px-3 text-xs has-[>svg]:px-2.5",
115
- lg: "h-10 rounded-lg px-5 text-[15px] has-[>svg]:px-4",
116
- xl: "h-12 rounded-lg px-6 text-[15px] font-semibold has-[>svg]:px-5",
117
- icon: "size-9 rounded-md",
118
- "icon-xs": "size-6 rounded",
119
- "icon-sm": "size-8 rounded-md",
120
- "icon-lg": "size-10 rounded-lg",
121
- card: "min-h-[4rem] rounded-md px-4 py-3",
122
- },
123
- },
124
- defaultVariants: {
125
- color: "primary",
126
- size: "default",
127
- },
128
- },
129
- ) as (props?: ButtonVariantProps) => string;
130
-
131
- type ButtonVariants = typeof buttonVariants;
132
-
133
- export type ButtonProps = React.ComponentProps<typeof ButtonPrimitive> &
134
- VariantProps<ButtonVariants> & {
135
- loading?: boolean;
136
- active?: boolean;
137
- };
138
-
139
- export type ButtonElement = React.ComponentRef<"button">;
140
-
141
- export function Button({
142
- className,
143
- color,
144
- size,
145
- disabled,
146
- loading,
147
- children,
148
- active,
149
- ...props
150
- }: ButtonProps): React.ReactElement {
151
- const _disabled = disabled || loading;
152
-
153
- const _children = loading ? (
154
- <>
155
- <span className="invisible contents">{children}</span>
156
- <span className="absolute inset-0 flex items-center justify-center">
157
- <IconLoader2 aria-hidden className="size-4 animate-spin" />
158
- </span>
159
- </>
160
- ) : (
161
- children
162
- );
163
-
164
- return (
165
- <ButtonPrimitive
166
- data-state={active ? "active" : undefined}
167
- disabled={_disabled}
168
- focusableWhenDisabled
169
- className={cn(
170
- buttonVariants({ color, size }),
171
- {
172
- relative: loading,
173
- },
174
- className,
175
- )}
176
- {...props}
177
- >
178
- {_children}
179
- </ButtonPrimitive>
180
- );
181
- }
182
-
183
- export { buttonVariants };