@stargazeryan/poker-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -27
- package/dist/index.cjs +99 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -39
- package/dist/index.d.ts +67 -39
- package/dist/index.js +96 -89
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
declare function cn(...parts: Array<string | false | null | undefined>): string;
|
|
4
5
|
|
|
@@ -7,6 +8,22 @@ declare const BB = 20;
|
|
|
7
8
|
/** Format a chip amount as a Big-Blind string, e.g. 1234 -> "61.7 BB". */
|
|
8
9
|
declare function formatBB(amount: number): string;
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Seat-ring helpers. Pure functions — they contain no game-specific rules.
|
|
13
|
+
*/
|
|
14
|
+
interface NextSeatOptions {
|
|
15
|
+
/** +1 for clockwise, -1 for counter-clockwise. Default +1. */
|
|
16
|
+
dir?: 1 | -1;
|
|
17
|
+
/** Return true to skip a seat (e.g. folded / empty / out). */
|
|
18
|
+
skip?: (index: number) => boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Return the index of the next seat on the ring after `current`, skipping any
|
|
22
|
+
* seat matched by `opts.skip`. Wraps around. Returns `current` if every other
|
|
23
|
+
* seat is skipped.
|
|
24
|
+
*/
|
|
25
|
+
declare function nextSeatIndex(current: number, seatCount: number, opts?: NextSeatOptions): number;
|
|
26
|
+
|
|
10
27
|
type CardSize = "xs" | "sm" | "md" | "lg";
|
|
11
28
|
/** Parse a compact card code like "As", "10h", "kd" into rank + suit. */
|
|
12
29
|
declare function parseCard(card: string): {
|
|
@@ -46,57 +63,68 @@ interface CommunityCardsProps {
|
|
|
46
63
|
/** The five community cards, each dealt with a staggered flip animation. */
|
|
47
64
|
declare function CommunityCards({ cards, size, className }: CommunityCardsProps): react.JSX.Element;
|
|
48
65
|
|
|
49
|
-
/** Presentational view-model for one seat (no game logic). */
|
|
50
|
-
interface SeatView {
|
|
51
|
-
nickname: string;
|
|
52
|
-
stack: number;
|
|
53
|
-
holeCards?: (string | null)[] | null;
|
|
54
|
-
isDealer?: boolean;
|
|
55
|
-
isTurn?: boolean;
|
|
56
|
-
folded?: boolean;
|
|
57
|
-
busted?: boolean;
|
|
58
|
-
allIn?: boolean;
|
|
59
|
-
currentBet?: number;
|
|
60
|
-
isYou?: boolean;
|
|
61
|
-
isWinner?: boolean;
|
|
62
|
-
/** Optional short label shown in a bubble (e.g. "Raise 500"). */
|
|
63
|
-
actionLabel?: string | null;
|
|
64
|
-
}
|
|
65
66
|
interface SeatProps {
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
/** Turn/active highlight (generic). */
|
|
68
|
+
isActive?: boolean;
|
|
69
|
+
/** Winner highlight (generic). */
|
|
70
|
+
isWinner?: boolean;
|
|
71
|
+
/** Dimmed, e.g. folded or out (generic). */
|
|
72
|
+
isDimmed?: boolean;
|
|
73
|
+
/** Optional click handler (e.g. "sit here"). */
|
|
74
|
+
onClick?: () => void;
|
|
75
|
+
/** Slot: top row — name / badges. */
|
|
76
|
+
header?: ReactNode;
|
|
77
|
+
/** Slot: top-right floating element — e.g. a dealer button. */
|
|
78
|
+
badge?: ReactNode;
|
|
79
|
+
/** Slot: center content — 0..N cards, avatar, anything. */
|
|
80
|
+
children?: ReactNode;
|
|
81
|
+
/** Slot: bottom row — stack / bet / status. */
|
|
82
|
+
footer?: ReactNode;
|
|
83
|
+
/** Slot: overlay — e.g. an "All-in" label. */
|
|
84
|
+
overlay?: ReactNode;
|
|
85
|
+
width?: number;
|
|
86
|
+
height?: number;
|
|
68
87
|
className?: string;
|
|
69
88
|
}
|
|
70
89
|
/**
|
|
71
|
-
* A
|
|
72
|
-
*
|
|
90
|
+
* A neutral seat: a frame with generic visual states and named slots.
|
|
91
|
+
* It knows nothing about any particular game — put whatever you want into
|
|
92
|
+
* `header` / `children` / `footer` / `badge` / `overlay`.
|
|
73
93
|
*/
|
|
74
|
-
declare function Seat({
|
|
94
|
+
declare function Seat({ isActive, isWinner, isDimmed, onClick, header, badge, children, footer, overlay, width, height, className, }: SeatProps): react.JSX.Element;
|
|
75
95
|
|
|
76
|
-
interface
|
|
77
|
-
label: string;
|
|
96
|
+
interface PotProps {
|
|
78
97
|
amount: number;
|
|
98
|
+
/** Optional label, e.g. "Main" / "Side". */
|
|
99
|
+
label?: string;
|
|
100
|
+
/** Formatter for the amount; defaults to Big-Blind formatting. */
|
|
101
|
+
format?: (amount: number) => string;
|
|
102
|
+
className?: string;
|
|
79
103
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
/** A small, standalone pot pill. Optional — the table does not require it. */
|
|
105
|
+
declare function Pot({ amount, label, format, className }: PotProps): react.JSX.Element;
|
|
106
|
+
|
|
107
|
+
interface CardTableProps {
|
|
108
|
+
/** Number of seats (2–10). Default 6. */
|
|
109
|
+
seatCount?: number;
|
|
110
|
+
/** Optional per-seat content. Length should equal `seatCount`. */
|
|
111
|
+
seats?: (ReactNode | null)[];
|
|
112
|
+
/** Alternative to `seats`: render a seat by index. */
|
|
113
|
+
renderSeat?: (index: number, isYou: boolean) => ReactNode;
|
|
83
114
|
/** Which seat belongs to the viewer; rotates that seat to the bottom. */
|
|
84
115
|
yourSeatIndex?: number | null;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
seatSize?: CardSize;
|
|
91
|
-
/** Optional override for rendering a seat. */
|
|
92
|
-
renderSeat?: (seat: SeatView | null, index: number) => React.ReactNode;
|
|
116
|
+
/** Optional content in the middle of the table (cards, pot, anything). */
|
|
117
|
+
center?: ReactNode;
|
|
118
|
+
/** Seat frame size used for layout. */
|
|
119
|
+
seatWidth?: number;
|
|
120
|
+
seatHeight?: number;
|
|
93
121
|
className?: string;
|
|
94
122
|
}
|
|
95
123
|
/**
|
|
96
|
-
* A responsive,
|
|
97
|
-
* its container and arranges
|
|
98
|
-
*
|
|
124
|
+
* A responsive, game-agnostic card table. It scales a fixed 1200x900 design
|
|
125
|
+
* onto its container and arranges `seatCount` seats around the felt. Purely
|
|
126
|
+
* presentational: supply seat content and an optional `center` slot.
|
|
99
127
|
*/
|
|
100
|
-
declare function
|
|
128
|
+
declare function CardTable({ seatCount, seats, renderSeat, yourSeatIndex, center, seatWidth, seatHeight, className, }: CardTableProps): react.JSX.Element;
|
|
101
129
|
|
|
102
|
-
export { BB, CardBack, type CardBackProps, type CardSize, CommunityCards, type CommunityCardsProps, HoleCards, type HoleCardsProps, PlayingCard, type PlayingCardProps, PokerTable, type PokerTableProps, type
|
|
130
|
+
export { BB, CardBack, type CardBackProps, type CardSize, CardTable, type CardTableProps, CommunityCards, type CommunityCardsProps, HoleCards, type HoleCardsProps, type NextSeatOptions, PlayingCard, type PlayingCardProps, CardTable as PokerTable, type CardTableProps as PokerTableProps, Pot, type PotProps, Seat, type SeatProps, cn, formatBB, getSuitSymbol, nextSeatIndex, parseCard };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
declare function cn(...parts: Array<string | false | null | undefined>): string;
|
|
4
5
|
|
|
@@ -7,6 +8,22 @@ declare const BB = 20;
|
|
|
7
8
|
/** Format a chip amount as a Big-Blind string, e.g. 1234 -> "61.7 BB". */
|
|
8
9
|
declare function formatBB(amount: number): string;
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Seat-ring helpers. Pure functions — they contain no game-specific rules.
|
|
13
|
+
*/
|
|
14
|
+
interface NextSeatOptions {
|
|
15
|
+
/** +1 for clockwise, -1 for counter-clockwise. Default +1. */
|
|
16
|
+
dir?: 1 | -1;
|
|
17
|
+
/** Return true to skip a seat (e.g. folded / empty / out). */
|
|
18
|
+
skip?: (index: number) => boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Return the index of the next seat on the ring after `current`, skipping any
|
|
22
|
+
* seat matched by `opts.skip`. Wraps around. Returns `current` if every other
|
|
23
|
+
* seat is skipped.
|
|
24
|
+
*/
|
|
25
|
+
declare function nextSeatIndex(current: number, seatCount: number, opts?: NextSeatOptions): number;
|
|
26
|
+
|
|
10
27
|
type CardSize = "xs" | "sm" | "md" | "lg";
|
|
11
28
|
/** Parse a compact card code like "As", "10h", "kd" into rank + suit. */
|
|
12
29
|
declare function parseCard(card: string): {
|
|
@@ -46,57 +63,68 @@ interface CommunityCardsProps {
|
|
|
46
63
|
/** The five community cards, each dealt with a staggered flip animation. */
|
|
47
64
|
declare function CommunityCards({ cards, size, className }: CommunityCardsProps): react.JSX.Element;
|
|
48
65
|
|
|
49
|
-
/** Presentational view-model for one seat (no game logic). */
|
|
50
|
-
interface SeatView {
|
|
51
|
-
nickname: string;
|
|
52
|
-
stack: number;
|
|
53
|
-
holeCards?: (string | null)[] | null;
|
|
54
|
-
isDealer?: boolean;
|
|
55
|
-
isTurn?: boolean;
|
|
56
|
-
folded?: boolean;
|
|
57
|
-
busted?: boolean;
|
|
58
|
-
allIn?: boolean;
|
|
59
|
-
currentBet?: number;
|
|
60
|
-
isYou?: boolean;
|
|
61
|
-
isWinner?: boolean;
|
|
62
|
-
/** Optional short label shown in a bubble (e.g. "Raise 500"). */
|
|
63
|
-
actionLabel?: string | null;
|
|
64
|
-
}
|
|
65
66
|
interface SeatProps {
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
/** Turn/active highlight (generic). */
|
|
68
|
+
isActive?: boolean;
|
|
69
|
+
/** Winner highlight (generic). */
|
|
70
|
+
isWinner?: boolean;
|
|
71
|
+
/** Dimmed, e.g. folded or out (generic). */
|
|
72
|
+
isDimmed?: boolean;
|
|
73
|
+
/** Optional click handler (e.g. "sit here"). */
|
|
74
|
+
onClick?: () => void;
|
|
75
|
+
/** Slot: top row — name / badges. */
|
|
76
|
+
header?: ReactNode;
|
|
77
|
+
/** Slot: top-right floating element — e.g. a dealer button. */
|
|
78
|
+
badge?: ReactNode;
|
|
79
|
+
/** Slot: center content — 0..N cards, avatar, anything. */
|
|
80
|
+
children?: ReactNode;
|
|
81
|
+
/** Slot: bottom row — stack / bet / status. */
|
|
82
|
+
footer?: ReactNode;
|
|
83
|
+
/** Slot: overlay — e.g. an "All-in" label. */
|
|
84
|
+
overlay?: ReactNode;
|
|
85
|
+
width?: number;
|
|
86
|
+
height?: number;
|
|
68
87
|
className?: string;
|
|
69
88
|
}
|
|
70
89
|
/**
|
|
71
|
-
* A
|
|
72
|
-
*
|
|
90
|
+
* A neutral seat: a frame with generic visual states and named slots.
|
|
91
|
+
* It knows nothing about any particular game — put whatever you want into
|
|
92
|
+
* `header` / `children` / `footer` / `badge` / `overlay`.
|
|
73
93
|
*/
|
|
74
|
-
declare function Seat({
|
|
94
|
+
declare function Seat({ isActive, isWinner, isDimmed, onClick, header, badge, children, footer, overlay, width, height, className, }: SeatProps): react.JSX.Element;
|
|
75
95
|
|
|
76
|
-
interface
|
|
77
|
-
label: string;
|
|
96
|
+
interface PotProps {
|
|
78
97
|
amount: number;
|
|
98
|
+
/** Optional label, e.g. "Main" / "Side". */
|
|
99
|
+
label?: string;
|
|
100
|
+
/** Formatter for the amount; defaults to Big-Blind formatting. */
|
|
101
|
+
format?: (amount: number) => string;
|
|
102
|
+
className?: string;
|
|
79
103
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
/** A small, standalone pot pill. Optional — the table does not require it. */
|
|
105
|
+
declare function Pot({ amount, label, format, className }: PotProps): react.JSX.Element;
|
|
106
|
+
|
|
107
|
+
interface CardTableProps {
|
|
108
|
+
/** Number of seats (2–10). Default 6. */
|
|
109
|
+
seatCount?: number;
|
|
110
|
+
/** Optional per-seat content. Length should equal `seatCount`. */
|
|
111
|
+
seats?: (ReactNode | null)[];
|
|
112
|
+
/** Alternative to `seats`: render a seat by index. */
|
|
113
|
+
renderSeat?: (index: number, isYou: boolean) => ReactNode;
|
|
83
114
|
/** Which seat belongs to the viewer; rotates that seat to the bottom. */
|
|
84
115
|
yourSeatIndex?: number | null;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
seatSize?: CardSize;
|
|
91
|
-
/** Optional override for rendering a seat. */
|
|
92
|
-
renderSeat?: (seat: SeatView | null, index: number) => React.ReactNode;
|
|
116
|
+
/** Optional content in the middle of the table (cards, pot, anything). */
|
|
117
|
+
center?: ReactNode;
|
|
118
|
+
/** Seat frame size used for layout. */
|
|
119
|
+
seatWidth?: number;
|
|
120
|
+
seatHeight?: number;
|
|
93
121
|
className?: string;
|
|
94
122
|
}
|
|
95
123
|
/**
|
|
96
|
-
* A responsive,
|
|
97
|
-
* its container and arranges
|
|
98
|
-
*
|
|
124
|
+
* A responsive, game-agnostic card table. It scales a fixed 1200x900 design
|
|
125
|
+
* onto its container and arranges `seatCount` seats around the felt. Purely
|
|
126
|
+
* presentational: supply seat content and an optional `center` slot.
|
|
99
127
|
*/
|
|
100
|
-
declare function
|
|
128
|
+
declare function CardTable({ seatCount, seats, renderSeat, yourSeatIndex, center, seatWidth, seatHeight, className, }: CardTableProps): react.JSX.Element;
|
|
101
129
|
|
|
102
|
-
export { BB, CardBack, type CardBackProps, type CardSize, CommunityCards, type CommunityCardsProps, HoleCards, type HoleCardsProps, PlayingCard, type PlayingCardProps, PokerTable, type PokerTableProps, type
|
|
130
|
+
export { BB, CardBack, type CardBackProps, type CardSize, CardTable, type CardTableProps, CommunityCards, type CommunityCardsProps, HoleCards, type HoleCardsProps, type NextSeatOptions, PlayingCard, type PlayingCardProps, CardTable as PokerTable, type CardTableProps as PokerTableProps, Pot, type PotProps, Seat, type SeatProps, cn, formatBB, getSuitSymbol, nextSeatIndex, parseCard };
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,20 @@ function formatBB(amount) {
|
|
|
12
12
|
return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// src/seat.ts
|
|
16
|
+
function nextSeatIndex(current, seatCount, opts = {}) {
|
|
17
|
+
const n = Math.max(1, Math.floor(seatCount));
|
|
18
|
+
const dir = opts.dir ?? 1;
|
|
19
|
+
const skip = opts.skip;
|
|
20
|
+
const start = (Math.floor(current) % n + n) % n;
|
|
21
|
+
let i = start;
|
|
22
|
+
for (let step = 0; step < n - 1; step++) {
|
|
23
|
+
i = (i + dir + n) % n;
|
|
24
|
+
if (!skip || !skip(i)) return i;
|
|
25
|
+
}
|
|
26
|
+
return start;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
// src/components/PlayingCard.tsx
|
|
16
30
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
31
|
var SIZE_CLASSES = {
|
|
@@ -142,61 +156,61 @@ function CommunityCards({ cards, size = "md", className }) {
|
|
|
142
156
|
|
|
143
157
|
// src/components/Seat.tsx
|
|
144
158
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
145
|
-
function Seat({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return /* @__PURE__ */ jsxs2("div", { className: cn("relative w-40 h-44", className), children: [
|
|
161
|
-
seat.actionLabel && /* @__PURE__ */ jsx2("div", { className: "animate-bubble-pop absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-white px-2.5 py-1 text-[11px] font-semibold text-neutral-900 shadow-lg", children: seat.actionLabel }),
|
|
159
|
+
function Seat({
|
|
160
|
+
isActive = false,
|
|
161
|
+
isWinner = false,
|
|
162
|
+
isDimmed = false,
|
|
163
|
+
onClick,
|
|
164
|
+
header,
|
|
165
|
+
badge,
|
|
166
|
+
children,
|
|
167
|
+
footer,
|
|
168
|
+
overlay,
|
|
169
|
+
width = 160,
|
|
170
|
+
height = 176,
|
|
171
|
+
className
|
|
172
|
+
}) {
|
|
173
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("relative", className), style: { width, height }, children: [
|
|
162
174
|
/* @__PURE__ */ jsxs2(
|
|
163
175
|
"div",
|
|
164
176
|
{
|
|
177
|
+
role: onClick ? "button" : void 0,
|
|
178
|
+
tabIndex: onClick ? 0 : void 0,
|
|
179
|
+
onClick,
|
|
180
|
+
onKeyDown: onClick ? (e) => {
|
|
181
|
+
if (e.key === "Enter" || e.key === " ") onClick();
|
|
182
|
+
} : void 0,
|
|
165
183
|
className: cn(
|
|
166
|
-
"flex h-full w-full flex-col items-center justify-between rounded-2xl border bg-neutral-900/80 p-2 backdrop-blur",
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
"flex h-full w-full flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 backdrop-blur transition",
|
|
185
|
+
isActive ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
|
|
186
|
+
isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
|
|
187
|
+
isDimmed && "opacity-50",
|
|
188
|
+
onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400"
|
|
170
189
|
),
|
|
171
190
|
children: [
|
|
172
|
-
/* @__PURE__ */
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
seat.nickname,
|
|
176
|
-
seat.isYou ? " \u{1F448}" : ""
|
|
177
|
-
] }),
|
|
178
|
-
seat.isDealer && /* @__PURE__ */ jsx2("span", { className: "flex h-4 w-4 items-center justify-center rounded-full bg-white text-[9px] font-bold text-neutral-900", children: "D" })
|
|
179
|
-
] }),
|
|
180
|
-
/* @__PURE__ */ jsx2("div", { className: "flex flex-1 items-center justify-center", children: seat.busted ? /* @__PURE__ */ jsx2("span", { className: "text-xs font-semibold text-red-400", children: "Busted" }) : showCards ? /* @__PURE__ */ jsx2(HoleCards, { cards: seat.holeCards, size }) : /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
181
|
-
/* @__PURE__ */ jsx2(CardBack, { size }),
|
|
182
|
-
/* @__PURE__ */ jsx2(CardBack, { size })
|
|
183
|
-
] }) }),
|
|
184
|
-
/* @__PURE__ */ jsxs2("div", { className: "flex w-full items-center justify-between text-[11px] text-neutral-300", children: [
|
|
185
|
-
/* @__PURE__ */ jsx2("span", { className: "font-mono", children: formatBB(seat.stack) }),
|
|
186
|
-
seat.allIn ? /* @__PURE__ */ jsx2("span", { className: "rounded bg-red-600/80 px-1.5 text-[10px] font-bold text-white", children: "ALL-IN" }) : seat.currentBet && seat.currentBet > 0 ? /* @__PURE__ */ jsx2("span", { className: "font-mono text-amber-300", children: formatBB(seat.currentBet) }) : null
|
|
187
|
-
] })
|
|
191
|
+
header !== void 0 && /* @__PURE__ */ jsx2("div", { className: "flex w-full items-center justify-between gap-1 text-[11px]", children: header }),
|
|
192
|
+
/* @__PURE__ */ jsx2("div", { className: "flex flex-1 items-center justify-center", children }),
|
|
193
|
+
footer !== void 0 && /* @__PURE__ */ jsx2("div", { className: "flex w-full items-center justify-between text-[11px] text-neutral-300", children: footer })
|
|
188
194
|
]
|
|
189
195
|
}
|
|
190
|
-
)
|
|
196
|
+
),
|
|
197
|
+
badge !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute -top-1 -right-1", children: badge }),
|
|
198
|
+
overlay !== void 0 && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center", children: overlay })
|
|
191
199
|
] });
|
|
192
200
|
}
|
|
193
201
|
|
|
194
|
-
// src/components/
|
|
195
|
-
import { useRef, useLayoutEffect, useState, useMemo } from "react";
|
|
202
|
+
// src/components/Pot.tsx
|
|
196
203
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
function Pot({ amount, label, format = formatBB, className }) {
|
|
205
|
+
return /* @__PURE__ */ jsxs3("div", { className: cn("flex items-center gap-2 font-mono text-sm text-amber-400", className), children: [
|
|
206
|
+
label && /* @__PURE__ */ jsx3("span", { className: "text-neutral-400", children: label }),
|
|
207
|
+
/* @__PURE__ */ jsx3("span", { className: "whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner", children: format(amount) })
|
|
208
|
+
] });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/components/CardTable.tsx
|
|
212
|
+
import { useRef, useLayoutEffect, useState, useMemo } from "react";
|
|
213
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
200
214
|
var DESIGN_W = 1200;
|
|
201
215
|
var DESIGN_H = 900;
|
|
202
216
|
var MARGIN_X = 164;
|
|
@@ -207,47 +221,46 @@ var FELT_GAP = 11;
|
|
|
207
221
|
var BET_SCALE = 0.72;
|
|
208
222
|
var BET_RX = TABLE_RX * BET_SCALE;
|
|
209
223
|
var BET_RY = TABLE_RY * BET_SCALE;
|
|
210
|
-
var SEAT_RING_X = TABLE_RX - FELT_GAP + SEAT_W / 2 + 6;
|
|
211
|
-
var SEAT_RING_Y = TABLE_RY - FELT_GAP + SEAT_H / 2 + 6;
|
|
212
|
-
var BASE_ANGLES = [-90, -30, 30, 90, 150, 210];
|
|
213
224
|
function getEllipsePoint(cx, cy, rx, ry, angleDeg) {
|
|
214
225
|
const rad = angleDeg * Math.PI / 180;
|
|
215
226
|
return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };
|
|
216
227
|
}
|
|
217
|
-
function calculateSeatLayouts(
|
|
228
|
+
function calculateSeatLayouts(seatCount, yourSeatIndex, seatWidth, seatHeight) {
|
|
218
229
|
const cx = DESIGN_W / 2;
|
|
219
230
|
const cy = DESIGN_H / 2;
|
|
220
|
-
const
|
|
231
|
+
const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;
|
|
232
|
+
const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;
|
|
233
|
+
const step = 360 / seatCount;
|
|
234
|
+
const angleOf = (i) => -90 + i * step;
|
|
235
|
+
const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;
|
|
221
236
|
const layouts = [];
|
|
222
|
-
for (let i = 0; i <
|
|
223
|
-
const pt = getEllipsePoint(cx, cy,
|
|
237
|
+
for (let i = 0; i < seatCount; i++) {
|
|
238
|
+
const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);
|
|
224
239
|
layouts.push({
|
|
225
|
-
x: pt.x -
|
|
226
|
-
y: pt.y -
|
|
227
|
-
|
|
228
|
-
isYou: i === yourSeatIndex
|
|
229
|
-
seat: seats[i] ?? null
|
|
240
|
+
x: pt.x - seatWidth / 2,
|
|
241
|
+
y: pt.y - seatHeight / 2,
|
|
242
|
+
index: i,
|
|
243
|
+
isYou: i === yourSeatIndex
|
|
230
244
|
});
|
|
231
245
|
}
|
|
232
246
|
return layouts;
|
|
233
247
|
}
|
|
234
|
-
function
|
|
248
|
+
function CardTable({
|
|
249
|
+
seatCount = 6,
|
|
235
250
|
seats,
|
|
236
|
-
yourSeatIndex = null,
|
|
237
|
-
communityCards = [],
|
|
238
|
-
pot = 0,
|
|
239
|
-
pots = [],
|
|
240
|
-
isWaiting = false,
|
|
241
|
-
winnerSeats = [],
|
|
242
|
-
seatSize = "sm",
|
|
243
251
|
renderSeat,
|
|
252
|
+
yourSeatIndex = null,
|
|
253
|
+
center,
|
|
254
|
+
seatWidth = 160,
|
|
255
|
+
seatHeight = 176,
|
|
244
256
|
className
|
|
245
257
|
}) {
|
|
258
|
+
const count = Math.max(2, Math.min(10, Math.floor(seatCount)));
|
|
246
259
|
const outerRef = useRef(null);
|
|
247
260
|
const [scale, setScale] = useState(1);
|
|
248
261
|
const layouts = useMemo(
|
|
249
|
-
() => calculateSeatLayouts(
|
|
250
|
-
[
|
|
262
|
+
() => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),
|
|
263
|
+
[count, yourSeatIndex, seatWidth, seatHeight]
|
|
251
264
|
);
|
|
252
265
|
useLayoutEffect(() => {
|
|
253
266
|
const el = outerRef.current;
|
|
@@ -269,26 +282,25 @@ function PokerTable({
|
|
|
269
282
|
const betLineInset = {
|
|
270
283
|
inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`
|
|
271
284
|
};
|
|
272
|
-
|
|
273
|
-
return /* @__PURE__ */ jsx3(
|
|
285
|
+
return /* @__PURE__ */ jsx4(
|
|
274
286
|
"div",
|
|
275
287
|
{
|
|
276
288
|
ref: outerRef,
|
|
277
|
-
className: cn("relative flex h-full w-full items-center justify-center overflow-hidden
|
|
278
|
-
children: /* @__PURE__ */
|
|
289
|
+
className: cn("relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden", className),
|
|
290
|
+
children: /* @__PURE__ */ jsx4("div", { style: { width: DESIGN_W * scale, height: DESIGN_H * scale }, children: /* @__PURE__ */ jsxs4(
|
|
279
291
|
"div",
|
|
280
292
|
{
|
|
281
293
|
className: "relative",
|
|
282
294
|
style: { width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: "top left" },
|
|
283
295
|
children: [
|
|
284
|
-
/* @__PURE__ */
|
|
296
|
+
/* @__PURE__ */ jsx4(
|
|
285
297
|
"div",
|
|
286
298
|
{
|
|
287
299
|
className: "absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl",
|
|
288
300
|
"aria-hidden": "true"
|
|
289
301
|
}
|
|
290
302
|
),
|
|
291
|
-
/* @__PURE__ */
|
|
303
|
+
/* @__PURE__ */ jsx4(
|
|
292
304
|
"div",
|
|
293
305
|
{
|
|
294
306
|
className: "absolute rounded-[50%] bg-gradient-to-b from-[#a5744a] via-[#6b3f22] to-[#38200f] shadow-[0_26px_70px_-12px_rgba(0,0,0,0.85),inset_0_3px_0_rgba(255,255,255,0.20),inset_0_-6px_16px_rgba(0,0,0,0.5)]",
|
|
@@ -296,7 +308,7 @@ function PokerTable({
|
|
|
296
308
|
"aria-hidden": "true"
|
|
297
309
|
}
|
|
298
310
|
),
|
|
299
|
-
/* @__PURE__ */
|
|
311
|
+
/* @__PURE__ */ jsx4(
|
|
300
312
|
"div",
|
|
301
313
|
{
|
|
302
314
|
className: "absolute rounded-[50%] bg-[radial-gradient(ellipse_at_50%_32%,#23804b,#0f4d2e_55%,#093b22)] shadow-[inset_0_0_90px_rgba(0,0,0,0.55),inset_0_3px_10px_rgba(255,255,255,0.06)]",
|
|
@@ -304,7 +316,7 @@ function PokerTable({
|
|
|
304
316
|
"aria-hidden": "true"
|
|
305
317
|
}
|
|
306
318
|
),
|
|
307
|
-
/* @__PURE__ */
|
|
319
|
+
/* @__PURE__ */ jsx4(
|
|
308
320
|
"div",
|
|
309
321
|
{
|
|
310
322
|
className: "absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]",
|
|
@@ -312,22 +324,14 @@ function PokerTable({
|
|
|
312
324
|
"aria-hidden": "true"
|
|
313
325
|
}
|
|
314
326
|
),
|
|
315
|
-
/* @__PURE__ */
|
|
316
|
-
/* @__PURE__ */
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
/* @__PURE__ */ jsx3("span", { className: "whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner", children: formatBB(p.amount) })
|
|
322
|
-
] }, i)) })
|
|
323
|
-
] }),
|
|
324
|
-
isWaiting && /* @__PURE__ */ jsx3("div", { className: "rounded-full border border-neutral-700 bg-neutral-900/70 px-3 py-1 text-xs text-neutral-400", children: "Waiting to start" })
|
|
325
|
-
] }),
|
|
326
|
-
/* @__PURE__ */ jsx3("div", { className: "absolute inset-0 z-20 pointer-events-none", children: layouts.map(({ x, y, logicalIndex, isYou, seat }) => {
|
|
327
|
-
const merged = seat ? { ...seat, isYou, isWinner: winnerSeats.includes(logicalIndex) || seat.isWinner } : null;
|
|
328
|
-
return /* @__PURE__ */ jsx3("div", { className: "absolute", style: { left: `${x}px`, top: `${y}px` }, children: renderSeat ? renderSeat(merged, logicalIndex) : /* @__PURE__ */ jsx3(Seat, { seat: merged, size: seatSize }) }, logicalIndex);
|
|
327
|
+
/* @__PURE__ */ jsx4("div", { className: "absolute flex items-center justify-center pointer-events-none", style: feltInset, "aria-hidden": "true", children: /* @__PURE__ */ jsx4("span", { className: "text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none", children: "\u2660\u2665\u2666\u2663" }) }),
|
|
328
|
+
center !== void 0 && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center", children: center }),
|
|
329
|
+
/* @__PURE__ */ jsx4("div", { className: "absolute inset-0 z-20", children: layouts.map(({ x, y, index, isYou }) => {
|
|
330
|
+
const content = seats ? seats[index] ?? null : renderSeat ? renderSeat(index, isYou) : null;
|
|
331
|
+
if (content === null) return null;
|
|
332
|
+
return /* @__PURE__ */ jsx4("div", { className: "absolute", style: { left: `${x}px`, top: `${y}px` }, children: content }, index);
|
|
329
333
|
}) }),
|
|
330
|
-
/* @__PURE__ */
|
|
334
|
+
/* @__PURE__ */ jsx4("div", { className: "absolute rounded-[50%] border border-white/10 pointer-events-none", style: rimInset, "aria-hidden": "true" })
|
|
331
335
|
]
|
|
332
336
|
}
|
|
333
337
|
) })
|
|
@@ -337,14 +341,17 @@ function PokerTable({
|
|
|
337
341
|
export {
|
|
338
342
|
BB,
|
|
339
343
|
CardBack,
|
|
344
|
+
CardTable,
|
|
340
345
|
CommunityCards,
|
|
341
346
|
HoleCards,
|
|
342
347
|
PlayingCard,
|
|
343
|
-
PokerTable,
|
|
348
|
+
CardTable as PokerTable,
|
|
349
|
+
Pot,
|
|
344
350
|
Seat,
|
|
345
351
|
cn,
|
|
346
352
|
formatBB,
|
|
347
353
|
getSuitSymbol,
|
|
354
|
+
nextSeatIndex,
|
|
348
355
|
parseCard
|
|
349
356
|
};
|
|
350
357
|
//# sourceMappingURL=index.js.map
|