@ankhorage/zora-tabletop 0.0.4 → 0.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @ankhorage/zora-tabletop
2
2
 
3
+ ## 0.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - ae78fc9: Update Ankhorage dependencies: `@ankhorage/paradox`, `@ankhorage/zora`.
8
+ - 67b6760: Update Ankhorage dependencies: `@ankhorage/zora`.
9
+
10
+ ## 0.0.5
11
+
12
+ ### Patch Changes
13
+
14
+ - 01b7403: Render playing-card suit glyphs, improve accessible card labels, and separate tabletop seat panels from token pills for more readable compact card-game trainer layouts.
15
+
3
16
  ## 0.0.4
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/zora-tabletop
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v0.0.2](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v0.0.5](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Reusable tabletop, playing-card, seat, token, and card-game presentation components for React Native and React Native Web.
9
9
 
@@ -210,8 +210,8 @@ PlayingCard({
210
210
  Theme-aware face-up playing card primitive.
211
211
 
212
212
  Use `PlayingCard` for visible card values in hands, shared table cards, piles,
213
- or custom tabletop layouts. The component renders rank and suit text and exposes
214
- an accessible card label by default.
213
+ or custom tabletop layouts. The component renders rank and suit glyphs and
214
+ exposes an accessible card label by default.
215
215
 
216
216
  #### Face-up card
217
217
 
@@ -1 +1 @@
1
- {"version":3,"file":"cardLabel.d.ts","sourceRoot":"","sources":["../src/cardLabel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAElE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5D"}
1
+ {"version":3,"file":"cardLabel.d.ts","sourceRoot":"","sources":["../src/cardLabel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAShD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAElE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5D"}
package/dist/cardLabel.js CHANGED
@@ -1,9 +1,18 @@
1
+ const rankLabels = {
2
+ A: 'ace',
3
+ J: 'jack',
4
+ K: 'king',
5
+ Q: 'queen',
6
+ };
1
7
  export function getPlayingCardLabel(card) {
2
- return `${card.rank} ${card.suit}`;
8
+ return `${getPlayingCardRankLabel(card.rank)} of ${card.suit}`;
3
9
  }
4
10
  export function createHiddenCardLabel(index) {
5
11
  if (index === undefined)
6
12
  return 'Hidden card';
7
13
  return `Hidden card ${index + 1}`;
8
14
  }
15
+ function getPlayingCardRankLabel(rank) {
16
+ return rankLabels[rank] ?? rank;
17
+ }
9
18
  //# sourceMappingURL=cardLabel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cardLabel.js","sourceRoot":"","sources":["../src/cardLabel.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,IAAsB;IACxD,OAAO,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IAE9C,OAAO,eAAe,KAAK,GAAG,CAAC,EAAE,CAAC;AACpC,CAAC","sourcesContent":["import type { PlayingCardValue } from './types';\n\nexport function getPlayingCardLabel(card: PlayingCardValue): string {\n return `${card.rank} ${card.suit}`;\n}\n\nexport function createHiddenCardLabel(index?: number): string {\n if (index === undefined) return 'Hidden card';\n\n return `Hidden card ${index + 1}`;\n}\n"]}
1
+ {"version":3,"file":"cardLabel.js","sourceRoot":"","sources":["../src/cardLabel.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAqC;IACnD,CAAC,EAAE,KAAK;IACR,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,OAAO;CACX,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,IAAsB;IACxD,OAAO,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IAE9C,OAAO,eAAe,KAAK,GAAG,CAAC,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAY;IAC3C,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAClC,CAAC","sourcesContent":["import type { PlayingCardValue } from './types';\n\nconst rankLabels: Readonly<Record<string, string>> = {\n A: 'ace',\n J: 'jack',\n K: 'king',\n Q: 'queen',\n};\n\nexport function getPlayingCardLabel(card: PlayingCardValue): string {\n return `${getPlayingCardRankLabel(card.rank)} of ${card.suit}`;\n}\n\nexport function createHiddenCardLabel(index?: number): string {\n if (index === undefined) return 'Hidden card';\n\n return `Hidden card ${index + 1}`;\n}\n\nfunction getPlayingCardRankLabel(rank: string): string {\n return rankLabels[rank] ?? rank;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { PlayingCardSuit } from './types';
2
+ export declare function getPlayingCardSuitMark(suit: PlayingCardSuit): string;
3
+ export declare function isRedPlayingCardSuit(suit: PlayingCardSuit): boolean;
4
+ //# sourceMappingURL=cardSymbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardSymbols.d.ts","sourceRoot":"","sources":["../src/cardSymbols.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAS/C,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAEpE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAEnE"}
@@ -0,0 +1,13 @@
1
+ const suitMarks = {
2
+ clubs: '♣',
3
+ diamonds: '♦',
4
+ hearts: '♥',
5
+ spades: '♠',
6
+ };
7
+ export function getPlayingCardSuitMark(suit) {
8
+ return suitMarks[suit];
9
+ }
10
+ export function isRedPlayingCardSuit(suit) {
11
+ return suit === 'diamonds' || suit === 'hearts';
12
+ }
13
+ //# sourceMappingURL=cardSymbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardSymbols.js","sourceRoot":"","sources":["../src/cardSymbols.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAA8C;IAC3D,KAAK,EAAE,GAAG;IACV,QAAQ,EAAE,GAAG;IACb,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,GAAG;CACZ,CAAC;AAEF,MAAM,UAAU,sBAAsB,CAAC,IAAqB;IAC1D,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAqB;IACxD,OAAO,IAAI,KAAK,UAAU,IAAI,IAAI,KAAK,QAAQ,CAAC;AAClD,CAAC","sourcesContent":["import type { PlayingCardSuit } from './types';\n\nconst suitMarks: Readonly<Record<PlayingCardSuit, string>> = {\n clubs: '♣',\n diamonds: '♦',\n hearts: '♥',\n spades: '♠',\n};\n\nexport function getPlayingCardSuitMark(suit: PlayingCardSuit): string {\n return suitMarks[suit];\n}\n\nexport function isRedPlayingCardSuit(suit: PlayingCardSuit): boolean {\n return suit === 'diamonds' || suit === 'hearts';\n}\n"]}
@@ -4,8 +4,8 @@ import type { PlayingCardProps } from './types';
4
4
  * Theme-aware face-up playing card primitive.
5
5
  *
6
6
  * Use `PlayingCard` for visible card values in hands, shared table cards, piles,
7
- * or custom tabletop layouts. The component renders rank and suit text and exposes
8
- * an accessible card label by default.
7
+ * or custom tabletop layouts. The component renders rank and suit glyphs and
8
+ * exposes an accessible card label by default.
9
9
  *
10
10
  * @readme
11
11
  * @example Face-up card
@@ -1 +1 @@
1
- {"version":3,"file":"PlayingCard.d.ts","sourceRoot":"","sources":["../../../src/components/playing-card/PlayingCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAahD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,IAAe,EACf,QAAgB,EAChB,KAAa,EACb,kBAAkB,EAClB,WAAW,EACX,MAAM,GACP,EAAE,gBAAgB,qBAsDlB"}
1
+ {"version":3,"file":"PlayingCard.d.ts","sourceRoot":"","sources":["../../../src/components/playing-card/PlayingCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,IAAe,EACf,QAAgB,EAChB,KAAa,EACb,kBAAkB,EAClB,WAAW,EACX,MAAM,GACP,EAAE,gBAAgB,qBAsDlB"}
@@ -3,22 +3,14 @@ import React from 'react';
3
3
  import { StyleSheet, Text, View } from 'react-native';
4
4
  import { getPlayingCardLabel } from '../../cardLabel';
5
5
  import { getTabletopCardDimensions } from '../../cardSizing';
6
+ import { getPlayingCardSuitMark, isRedPlayingCardSuit } from '../../cardSymbols';
6
7
  import { createTabletopColorScheme } from '../../colors';
7
- const suitMarks = {
8
- clubs: 'C',
9
- diamonds: 'D',
10
- hearts: 'H',
11
- spades: 'S',
12
- };
13
- function isRedSuit(suit) {
14
- return suit === 'diamonds' || suit === 'hearts';
15
- }
16
8
  /***
17
9
  * Theme-aware face-up playing card primitive.
18
10
  *
19
11
  * Use `PlayingCard` for visible card values in hands, shared table cards, piles,
20
- * or custom tabletop layouts. The component renders rank and suit text and exposes
21
- * an accessible card label by default.
12
+ * or custom tabletop layouts. The component renders rank and suit glyphs and
13
+ * exposes an accessible card label by default.
22
14
  *
23
15
  * @readme
24
16
  * @example Face-up card
@@ -30,7 +22,7 @@ export function PlayingCard({ card, size = 'medium', selected = false, muted = f
30
22
  const { theme } = useZoraTheme();
31
23
  const colors = React.useMemo(() => createTabletopColorScheme(theme, colorScheme), [colorScheme, theme]);
32
24
  const dimensions = getTabletopCardDimensions(size);
33
- const suitColor = isRedSuit(card.suit) ? colors.redSuitText : colors.cardText;
25
+ const suitColor = isRedPlayingCardSuit(card.suit) ? colors.redSuitText : colors.cardText;
34
26
  return (<View accessibilityLabel={accessibilityLabel ?? getPlayingCardLabel(card)} accessibilityRole="image" style={[
35
27
  styles.card,
36
28
  {
@@ -60,7 +52,7 @@ export function PlayingCard({ card, size = 'medium', selected = false, muted = f
60
52
  lineHeight: dimensions.suitFontSize + 3,
61
53
  },
62
54
  ]}>
63
- {suitMarks[card.suit]}
55
+ {getPlayingCardSuitMark(card.suit)}
64
56
  </Text>
65
57
  </View>);
66
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlayingCard.js","sourceRoot":"","sources":["../../../src/components/playing-card/PlayingCard.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useZoraTheme } from '@ankhorage/zora';\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { getPlayingCardLabel } from '../../cardLabel';\nimport { getTabletopCardDimensions } from '../../cardSizing';\nimport { createTabletopColorScheme } from '../../colors';\nimport type { PlayingCardSuit } from '../../types';\nimport type { PlayingCardProps } from './types';\n\nconst suitMarks: Readonly<Record<PlayingCardSuit, string>> = {\n clubs: 'C',\n diamonds: 'D',\n hearts: 'H',\n spades: 'S',\n};\n\nfunction isRedSuit(suit: PlayingCardSuit): boolean {\n return suit === 'diamonds' || suit === 'hearts';\n}\n\n/***\n * Theme-aware face-up playing card primitive.\n *\n * Use `PlayingCard` for visible card values in hands, shared table cards, piles,\n * or custom tabletop layouts. The component renders rank and suit text and exposes\n * an accessible card label by default.\n *\n * @readme\n * @example Face-up card\n * ```tsx\n * <PlayingCard card={{ rank: 'A', suit: 'spades' }} selected />\n * ```\n */\nexport function PlayingCard({\n card,\n size = 'medium',\n selected = false,\n muted = false,\n accessibilityLabel,\n colorScheme,\n testID,\n}: PlayingCardProps) {\n const { theme } = useZoraTheme();\n const colors = React.useMemo(\n () => createTabletopColorScheme(theme, colorScheme),\n [colorScheme, theme],\n );\n const dimensions = getTabletopCardDimensions(size);\n const suitColor = isRedSuit(card.suit) ? colors.redSuitText : colors.cardText;\n\n return (\n <View\n accessibilityLabel={accessibilityLabel ?? getPlayingCardLabel(card)}\n accessibilityRole=\"image\"\n style={[\n styles.card,\n {\n backgroundColor: colors.cardSurface,\n borderColor: selected ? colors.seatSelectedBorder : colors.cardBorder,\n borderRadius: dimensions.radius,\n height: dimensions.height,\n opacity: muted ? 0.48 : 1,\n width: dimensions.width,\n },\n ]}\n testID={testID}\n >\n <Text\n selectable={false}\n style={[\n styles.rank,\n {\n color: suitColor,\n fontSize: dimensions.rankFontSize,\n lineHeight: dimensions.rankFontSize + 4,\n },\n ]}\n >\n {card.rank}\n </Text>\n <Text\n selectable={false}\n style={[\n styles.suit,\n {\n color: suitColor,\n fontSize: dimensions.suitFontSize,\n lineHeight: dimensions.suitFontSize + 3,\n },\n ]}\n >\n {suitMarks[card.suit]}\n </Text>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n card: {\n alignItems: 'center',\n borderWidth: 1,\n justifyContent: 'center',\n },\n rank: {\n fontWeight: '800',\n },\n suit: {\n fontWeight: '700',\n marginTop: -2,\n },\n});\n"]}
1
+ {"version":3,"file":"PlayingCard.js","sourceRoot":"","sources":["../../../src/components/playing-card/PlayingCard.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useZoraTheme } from '@ankhorage/zora';\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { getPlayingCardLabel } from '../../cardLabel';\nimport { getTabletopCardDimensions } from '../../cardSizing';\nimport { getPlayingCardSuitMark, isRedPlayingCardSuit } from '../../cardSymbols';\nimport { createTabletopColorScheme } from '../../colors';\nimport type { PlayingCardProps } from './types';\n\n/***\n * Theme-aware face-up playing card primitive.\n *\n * Use `PlayingCard` for visible card values in hands, shared table cards, piles,\n * or custom tabletop layouts. The component renders rank and suit glyphs and\n * exposes an accessible card label by default.\n *\n * @readme\n * @example Face-up card\n * ```tsx\n * <PlayingCard card={{ rank: 'A', suit: 'spades' }} selected />\n * ```\n */\nexport function PlayingCard({\n card,\n size = 'medium',\n selected = false,\n muted = false,\n accessibilityLabel,\n colorScheme,\n testID,\n}: PlayingCardProps) {\n const { theme } = useZoraTheme();\n const colors = React.useMemo(\n () => createTabletopColorScheme(theme, colorScheme),\n [colorScheme, theme],\n );\n const dimensions = getTabletopCardDimensions(size);\n const suitColor = isRedPlayingCardSuit(card.suit) ? colors.redSuitText : colors.cardText;\n\n return (\n <View\n accessibilityLabel={accessibilityLabel ?? getPlayingCardLabel(card)}\n accessibilityRole=\"image\"\n style={[\n styles.card,\n {\n backgroundColor: colors.cardSurface,\n borderColor: selected ? colors.seatSelectedBorder : colors.cardBorder,\n borderRadius: dimensions.radius,\n height: dimensions.height,\n opacity: muted ? 0.48 : 1,\n width: dimensions.width,\n },\n ]}\n testID={testID}\n >\n <Text\n selectable={false}\n style={[\n styles.rank,\n {\n color: suitColor,\n fontSize: dimensions.rankFontSize,\n lineHeight: dimensions.rankFontSize + 4,\n },\n ]}\n >\n {card.rank}\n </Text>\n <Text\n selectable={false}\n style={[\n styles.suit,\n {\n color: suitColor,\n fontSize: dimensions.suitFontSize,\n lineHeight: dimensions.suitFontSize + 3,\n },\n ]}\n >\n {getPlayingCardSuitMark(card.suit)}\n </Text>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n card: {\n alignItems: 'center',\n borderWidth: 1,\n justifyContent: 'center',\n },\n rank: {\n fontWeight: '800',\n },\n suit: {\n fontWeight: '700',\n marginTop: -2,\n },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"TabletopTable.d.ts","sourceRoot":"","sources":["../../../src/components/tabletop-table/TabletopTable.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAelD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,WAAgB,EAChB,WAAW,EACX,cAAc,EACd,KAAc,EACd,SAAS,EACT,QAAkB,EAClB,QAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,MAAM,GACP,EAAE,kBAAkB,qBAmGpB"}
1
+ {"version":3,"file":"TabletopTable.d.ts","sourceRoot":"","sources":["../../../src/components/tabletop-table/TabletopTable.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAelD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,WAAgB,EAChB,WAAW,EACX,cAAc,EACd,KAAc,EACd,SAAS,EACT,QAAkB,EAClB,QAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,MAAM,GACP,EAAE,kBAAkB,qBA8HpB"}
@@ -2,6 +2,7 @@ import { useZoraTheme } from '@ankhorage/zora';
2
2
  import React from 'react';
3
3
  import { StyleSheet, Text, View } from 'react-native';
4
4
  import { createTabletopColorScheme } from '../../colors';
5
+ import { getTabletopSeatCardSize } from '../../tableLayout';
5
6
  import { getTabletopSeatPosition } from '../../tablePositions';
6
7
  import { CardHand } from '../card-hand';
7
8
  function createSeatAccessibilityLabel(seat) {
@@ -72,22 +73,44 @@ export function TabletopTable({ seats, centerCards = [], centerLabel, centerSubl
72
73
 
73
74
  {seats.map((seat, index) => {
74
75
  const position = getTabletopSeatPosition(index, resolvedSeatCount);
76
+ const hasCards = seat.cards !== undefined || seat.faceDownCards !== undefined;
77
+ const seatCardSize = getTabletopSeatCardSize({
78
+ selected: seat.selected,
79
+ tableCardSize: cardSize,
80
+ });
75
81
  return (<View accessibilityLabel={createSeatAccessibilityLabel(seat)} accessibilityRole="summary" key={seat.id} style={[
76
- styles.seat,
82
+ styles.seatSlot,
77
83
  {
78
- borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,
79
- backgroundColor: colors.seatSurface,
80
84
  left: position.left,
81
85
  opacity: seat.muted ? 0.48 : 1,
82
86
  top: position.top,
83
87
  },
84
88
  ]} testID={testID ? `${testID}-seat-${seat.id}` : undefined}>
85
- {seat.cards !== undefined || seat.faceDownCards !== undefined ? (<CardHand cards={seat.cards} colorScheme={colorScheme} faceDownCards={seat.faceDownCards} muted={seat.muted ?? seat.disabled} size={cardSize}/>) : null}
86
- <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>
87
- {seat.sublabel !== undefined ? (<Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>
88
- {seat.sublabel}
89
- </Text>) : null}
90
- {seat.tokenLabel !== undefined ? (<View style={[styles.token, { backgroundColor: colors.tokenSurface }]}>
89
+ <View style={[
90
+ styles.seatPanel,
91
+ seat.selected ? styles.selectedSeatPanel : null,
92
+ {
93
+ borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,
94
+ backgroundColor: colors.seatSurface,
95
+ },
96
+ ]}>
97
+ {hasCards ? (<CardHand cards={seat.cards} colorScheme={colorScheme} faceDownCards={seat.faceDownCards} muted={seat.muted ?? seat.disabled} size={seatCardSize}/>) : null}
98
+ <View style={styles.seatTextGroup}>
99
+ <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>
100
+ {seat.sublabel !== undefined ? (<Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>
101
+ {seat.sublabel}
102
+ </Text>) : null}
103
+ </View>
104
+ </View>
105
+ {seat.tokenLabel !== undefined ? (<View style={[
106
+ styles.token,
107
+ {
108
+ backgroundColor: colors.tokenSurface,
109
+ borderColor: seat.selected
110
+ ? colors.seatSelectedBorder
111
+ : colors.tableInnerBorder,
112
+ },
113
+ ]}>
91
114
  <Text style={[styles.tokenText, { color: colors.tokenText }]}>
92
115
  {seat.tokenLabel}
93
116
  </Text>
@@ -142,25 +165,39 @@ const styles = StyleSheet.create({
142
165
  roundedSurface: {
143
166
  borderRadius: 28,
144
167
  },
145
- seat: {
146
- alignItems: 'center',
147
- borderRadius: 10,
148
- borderWidth: 1,
149
- gap: 3,
150
- minWidth: 64,
151
- paddingHorizontal: 6,
152
- paddingVertical: 5,
153
- position: 'absolute',
154
- transform: [{ translateX: -32 }, { translateY: -28 }],
155
- },
156
168
  seatLabel: {
157
169
  fontSize: 11,
158
170
  fontWeight: '800',
159
171
  },
172
+ seatPanel: {
173
+ alignItems: 'center',
174
+ borderRadius: 12,
175
+ borderWidth: 1,
176
+ gap: 4,
177
+ minWidth: 68,
178
+ paddingHorizontal: 7,
179
+ paddingVertical: 6,
180
+ },
181
+ seatSlot: {
182
+ alignItems: 'center',
183
+ gap: 4,
184
+ minWidth: 78,
185
+ position: 'absolute',
186
+ transform: [{ translateX: -39 }, { translateY: -34 }],
187
+ },
160
188
  seatSublabel: {
161
189
  fontSize: 10,
162
190
  fontWeight: '700',
163
191
  },
192
+ seatTextGroup: {
193
+ alignItems: 'center',
194
+ gap: 1,
195
+ },
196
+ selectedSeatPanel: {
197
+ borderWidth: 2,
198
+ paddingHorizontal: 9,
199
+ paddingVertical: 7,
200
+ },
164
201
  surface: {
165
202
  borderWidth: 8,
166
203
  bottom: 28,
@@ -171,7 +208,8 @@ const styles = StyleSheet.create({
171
208
  },
172
209
  token: {
173
210
  borderRadius: 999,
174
- paddingHorizontal: 6,
211
+ borderWidth: 1,
212
+ paddingHorizontal: 7,
175
213
  paddingVertical: 2,
176
214
  },
177
215
  tokenText: {
@@ -1 +1 @@
1
- {"version":3,"file":"TabletopTable.js","sourceRoot":"","sources":["../../../src/components/tabletop-table/TabletopTable.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEtD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,SAAS,4BAA4B,CAAC,IAAuB;IAC3D,IAAI,IAAI,CAAC,kBAAkB,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,kBAAkB,CAAC;IAC1E,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAErD,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjE,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrE,IAAI,IAAI,CAAC,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1C,IAAI,IAAI,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEpC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,KAAK,EACL,WAAW,GAAG,EAAE,EAChB,WAAW,EACX,cAAc,EACd,KAAK,GAAG,MAAM,EACd,SAAS,EACT,QAAQ,GAAG,OAAO,EAClB,QAAQ,GAAG,KAAK,EAChB,WAAW,EACX,kBAAkB,EAClB,MAAM,GACa;IACnB,MAAM,EAAE,KAAK,EAAE,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAC1B,GAAG,EAAE,CAAC,yBAAyB,CAAC,KAAK,EAAE,WAAW,CAAC,EACnD,CAAC,WAAW,EAAE,KAAK,CAAC,CACrB,CAAC;IACF,MAAM,iBAAiB,GAAG,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC;IAEpD,OAAO,CACL,CAAC,IAAI,CACH,kBAAkB,CAAC,CAAC,kBAAkB,CAAC,CACvC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CACvD,MAAM,CAAC,CAAC,MAAM,CAAC,CAEf;MAAA,CAAC,IAAI,CACH,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,OAAO;YACd,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW;YAC9D,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI;YAClD;gBACE,eAAe,EAAE,MAAM,CAAC,SAAS;gBACjC,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC;SACF,CAAC,CAEF;QAAA,CAAC,IAAI,CACH,aAAa,CAAC,MAAM,CACpB,KAAK,CAAC,CAAC;YACL,MAAM,CAAC,YAAY;YACnB,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW;YAC9D,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI;YAClD,EAAE,WAAW,EAAE,MAAM,CAAC,gBAAgB,EAAE;SACzC,CAAC,EAEJ;QAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAChC;UAAA,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACxB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAG,CAC3E,CAAC,CAAC,CAAC,IAAI,CACR;UAAA,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,CAC3B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CACzE;cAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAChE;gBAAA,CAAC,WAAW,CACd;cAAA,EAAE,IAAI,CACR;YAAA,EAAE,IAAI,CAAC,CACR,CAAC,CAAC,CAAC,IAAI,CACR;UAAA,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,CAC9B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CACrE;cAAA,CAAC,cAAc,CACjB;YAAA,EAAE,IAAI,CAAC,CACR,CAAC,CAAC,CAAC,IAAI,CACV;QAAA,EAAE,IAAI,CACR;MAAA,EAAE,IAAI,CAEN;;MAAA,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YACzB,MAAM,QAAQ,GAAG,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;YACnE,OAAO,CACL,CAAC,IAAI,CACH,kBAAkB,CAAC,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC,CACvD,iBAAiB,CAAC,SAAS,CAC3B,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CACb,KAAK,CAAC,CAAC;oBACL,MAAM,CAAC,IAAI;oBACX;wBACE,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU;wBAC1E,eAAe,EAAE,MAAM,CAAC,WAAW;wBACnC,IAAI,EAAE,QAAQ,CAAC,IAAI;wBACnB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;wBAC9B,GAAG,EAAE,QAAQ,CAAC,GAAG;qBAClB;iBACF,CAAC,CACF,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,SAAS,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAEzD;YAAA,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,CAC9D,CAAC,QAAQ,CACP,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAClB,WAAW,CAAC,CAAC,WAAW,CAAC,CACzB,aAAa,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAClC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,CACnC,IAAI,CAAC,CAAC,QAAQ,CAAC,EACf,CACH,CAAC,CAAC,CAAC,IAAI,CACR;YAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,CAC/E;YAAA,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,CAC7B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAClE;gBAAA,CAAC,IAAI,CAAC,QAAQ,CAChB;cAAA,EAAE,IAAI,CAAC,CACR,CAAC,CAAC,CAAC,IAAI,CACR;YAAA,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,CAC/B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,eAAe,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC,CACpE;gBAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAC3D;kBAAA,CAAC,IAAI,CAAC,UAAU,CAClB;gBAAA,EAAE,IAAI,CACR;cAAA,EAAE,IAAI,CAAC,CACR,CAAC,CAAC,CAAC,IAAI,CACV;UAAA,EAAE,IAAI,CAAC,CACR,CAAC;QACJ,CAAC,CAAC,CACJ;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,aAAa,EAAE;QACb,UAAU,EAAE,QAAQ;QACpB,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,CAAC;QACP,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,CAAC;QACR,GAAG,EAAE,KAAK;KACX;IACD,WAAW,EAAE;QACX,YAAY,EAAE,GAAG;QACjB,iBAAiB,EAAE,EAAE;QACrB,eAAe,EAAE,CAAC;KACnB;IACD,eAAe,EAAE;QACf,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;KAClB;IACD,cAAc,EAAE;QACd,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;KAClB;IACD,aAAa,EAAE;QACb,YAAY,EAAE,GAAG;KAClB;IACD,YAAY,EAAE;QACZ,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,EAAE;QACV,IAAI,EAAE,EAAE;QACR,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,EAAE;QACT,GAAG,EAAE,EAAE;KACR;IACD,WAAW,EAAE;QACX,YAAY,EAAE,GAAG;KAClB;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,GAAG;QAChB,QAAQ,EAAE,GAAG;QACb,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,MAAM;KACd;IACD,cAAc,EAAE;QACd,YAAY,EAAE,EAAE;KACjB;IACD,IAAI,EAAE;QACJ,UAAU,EAAE,QAAQ;QACpB,YAAY,EAAE,EAAE;QAChB,WAAW,EAAE,CAAC;QACd,GAAG,EAAE,CAAC;QACN,QAAQ,EAAE,EAAE;QACZ,iBAAiB,EAAE,CAAC;QACpB,eAAe,EAAE,CAAC;QAClB,QAAQ,EAAE,UAAU;QACpB,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;KACtD;IACD,SAAS,EAAE;QACT,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;KAClB;IACD,YAAY,EAAE;QACZ,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;KAClB;IACD,OAAO,EAAE;QACP,WAAW,EAAE,CAAC;QACd,MAAM,EAAE,EAAE;QACV,IAAI,EAAE,EAAE;QACR,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,EAAE;QACT,GAAG,EAAE,EAAE;KACR;IACD,KAAK,EAAE;QACL,YAAY,EAAE,GAAG;QACjB,iBAAiB,EAAE,CAAC;QACpB,eAAe,EAAE,CAAC;KACnB;IACD,SAAS,EAAE;QACT,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,KAAK;KAClB;CACF,CAAC,CAAC","sourcesContent":["import { useZoraTheme } from '@ankhorage/zora';\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { createTabletopColorScheme } from '../../colors';\nimport { getTabletopSeatPosition } from '../../tablePositions';\nimport type { TabletopSeatState } from '../../types';\nimport { CardHand } from '../card-hand';\nimport type { TabletopTableProps } from './types';\n\nfunction createSeatAccessibilityLabel(seat: TabletopSeatState): string | undefined {\n if (seat.accessibilityLabel !== undefined) return seat.accessibilityLabel;\n if (typeof seat.label !== 'string') return undefined;\n\n const parts = [seat.label];\n if (typeof seat.sublabel === 'string') parts.push(seat.sublabel);\n if (typeof seat.tokenLabel === 'string') parts.push(seat.tokenLabel);\n if (seat.selected) parts.push('selected');\n if (seat.muted) parts.push('muted');\n\n return parts.join(', ');\n}\n\n/***\n * Responsive tabletop surface for generic card-game and board-game scenes.\n *\n * Use `TabletopTable` to arrange seats around a themed table surface, display\n * shared center cards, and show neutral seat labels/tokens without embedding game\n * rules into the component.\n *\n * @readme\n * @example Basic table\n * ```tsx\n * <TabletopTable\n * seats={seats}\n * centerCards={[{ rank: 'A', suit: 'spades' }]}\n * centerLabel=\"Round 1\"\n * />\n * ```\n */\nexport function TabletopTable({\n seats,\n centerCards = [],\n centerLabel,\n centerSublabel,\n shape = 'oval',\n seatCount,\n cardSize = 'small',\n disabled = false,\n colorScheme,\n accessibilityLabel,\n testID,\n}: TabletopTableProps) {\n const { theme } = useZoraTheme();\n const colors = React.useMemo(\n () => createTabletopColorScheme(theme, colorScheme),\n [colorScheme, theme],\n );\n const resolvedSeatCount = seatCount ?? seats.length;\n\n return (\n <View\n accessibilityLabel={accessibilityLabel}\n style={[styles.root, { opacity: disabled ? 0.56 : 1 }]}\n testID={testID}\n >\n <View\n style={[\n styles.surface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n {\n backgroundColor: colors.tableFelt,\n borderColor: colors.tableBorder,\n },\n ]}\n >\n <View\n pointerEvents=\"none\"\n style={[\n styles.innerSurface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n { borderColor: colors.tableInnerBorder },\n ]}\n />\n <View style={styles.centerContent}>\n {centerCards.length > 0 ? (\n <CardHand cards={centerCards} colorScheme={colorScheme} size={cardSize} />\n ) : null}\n {centerLabel !== undefined ? (\n <View style={[styles.centerLabel, { backgroundColor: colors.seatSurface }]}>\n <Text style={[styles.centerLabelText, { color: colors.seatText }]}>\n {centerLabel}\n </Text>\n </View>\n ) : null}\n {centerSublabel !== undefined ? (\n <Text style={[styles.centerSublabel, { color: colors.tableMutedText }]}>\n {centerSublabel}\n </Text>\n ) : null}\n </View>\n </View>\n\n {seats.map((seat, index) => {\n const position = getTabletopSeatPosition(index, resolvedSeatCount);\n return (\n <View\n accessibilityLabel={createSeatAccessibilityLabel(seat)}\n accessibilityRole=\"summary\"\n key={seat.id}\n style={[\n styles.seat,\n {\n borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,\n backgroundColor: colors.seatSurface,\n left: position.left,\n opacity: seat.muted ? 0.48 : 1,\n top: position.top,\n },\n ]}\n testID={testID ? `${testID}-seat-${seat.id}` : undefined}\n >\n {seat.cards !== undefined || seat.faceDownCards !== undefined ? (\n <CardHand\n cards={seat.cards}\n colorScheme={colorScheme}\n faceDownCards={seat.faceDownCards}\n muted={seat.muted ?? seat.disabled}\n size={cardSize}\n />\n ) : null}\n <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>\n {seat.sublabel !== undefined ? (\n <Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>\n {seat.sublabel}\n </Text>\n ) : null}\n {seat.tokenLabel !== undefined ? (\n <View style={[styles.token, { backgroundColor: colors.tokenSurface }]}>\n <Text style={[styles.tokenText, { color: colors.tokenText }]}>\n {seat.tokenLabel}\n </Text>\n </View>\n ) : null}\n </View>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n centerContent: {\n alignItems: 'center',\n gap: 6,\n left: 0,\n position: 'absolute',\n right: 0,\n top: '35%',\n },\n centerLabel: {\n borderRadius: 999,\n paddingHorizontal: 10,\n paddingVertical: 4,\n },\n centerLabelText: {\n fontSize: 12,\n fontWeight: '800',\n },\n centerSublabel: {\n fontSize: 11,\n fontWeight: '700',\n },\n circleSurface: {\n borderRadius: 999,\n },\n innerSurface: {\n borderWidth: 1,\n bottom: 10,\n left: 10,\n opacity: 0.44,\n position: 'absolute',\n right: 10,\n top: 10,\n },\n ovalSurface: {\n borderRadius: 999,\n },\n root: {\n aspectRatio: 1.6,\n maxWidth: 760,\n position: 'relative',\n width: '100%',\n },\n roundedSurface: {\n borderRadius: 28,\n },\n seat: {\n alignItems: 'center',\n borderRadius: 10,\n borderWidth: 1,\n gap: 3,\n minWidth: 64,\n paddingHorizontal: 6,\n paddingVertical: 5,\n position: 'absolute',\n transform: [{ translateX: -32 }, { translateY: -28 }],\n },\n seatLabel: {\n fontSize: 11,\n fontWeight: '800',\n },\n seatSublabel: {\n fontSize: 10,\n fontWeight: '700',\n },\n surface: {\n borderWidth: 8,\n bottom: 28,\n left: 24,\n position: 'absolute',\n right: 24,\n top: 24,\n },\n token: {\n borderRadius: 999,\n paddingHorizontal: 6,\n paddingVertical: 2,\n },\n tokenText: {\n fontSize: 10,\n fontWeight: '800',\n },\n});\n"]}
1
+ {"version":3,"file":"TabletopTable.js","sourceRoot":"","sources":["../../../src/components/tabletop-table/TabletopTable.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useZoraTheme } from '@ankhorage/zora';\nimport React from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\n\nimport { createTabletopColorScheme } from '../../colors';\nimport { getTabletopSeatCardSize } from '../../tableLayout';\nimport { getTabletopSeatPosition } from '../../tablePositions';\nimport type { TabletopSeatState } from '../../types';\nimport { CardHand } from '../card-hand';\nimport type { TabletopTableProps } from './types';\n\nfunction createSeatAccessibilityLabel(seat: TabletopSeatState): string | undefined {\n if (seat.accessibilityLabel !== undefined) return seat.accessibilityLabel;\n if (typeof seat.label !== 'string') return undefined;\n\n const parts = [seat.label];\n if (typeof seat.sublabel === 'string') parts.push(seat.sublabel);\n if (typeof seat.tokenLabel === 'string') parts.push(seat.tokenLabel);\n if (seat.selected) parts.push('selected');\n if (seat.muted) parts.push('muted');\n\n return parts.join(', ');\n}\n\n/***\n * Responsive tabletop surface for generic card-game and board-game scenes.\n *\n * Use `TabletopTable` to arrange seats around a themed table surface, display\n * shared center cards, and show neutral seat labels/tokens without embedding game\n * rules into the component.\n *\n * @readme\n * @example Basic table\n * ```tsx\n * <TabletopTable\n * seats={seats}\n * centerCards={[{ rank: 'A', suit: 'spades' }]}\n * centerLabel=\"Round 1\"\n * />\n * ```\n */\nexport function TabletopTable({\n seats,\n centerCards = [],\n centerLabel,\n centerSublabel,\n shape = 'oval',\n seatCount,\n cardSize = 'small',\n disabled = false,\n colorScheme,\n accessibilityLabel,\n testID,\n}: TabletopTableProps) {\n const { theme } = useZoraTheme();\n const colors = React.useMemo(\n () => createTabletopColorScheme(theme, colorScheme),\n [colorScheme, theme],\n );\n const resolvedSeatCount = seatCount ?? seats.length;\n\n return (\n <View\n accessibilityLabel={accessibilityLabel}\n style={[styles.root, { opacity: disabled ? 0.56 : 1 }]}\n testID={testID}\n >\n <View\n style={[\n styles.surface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n {\n backgroundColor: colors.tableFelt,\n borderColor: colors.tableBorder,\n },\n ]}\n >\n <View\n pointerEvents=\"none\"\n style={[\n styles.innerSurface,\n shape === 'circle' ? styles.circleSurface : styles.ovalSurface,\n shape === 'rounded' ? styles.roundedSurface : null,\n { borderColor: colors.tableInnerBorder },\n ]}\n />\n <View style={styles.centerContent}>\n {centerCards.length > 0 ? (\n <CardHand cards={centerCards} colorScheme={colorScheme} size={cardSize} />\n ) : null}\n {centerLabel !== undefined ? (\n <View style={[styles.centerLabel, { backgroundColor: colors.seatSurface }]}>\n <Text style={[styles.centerLabelText, { color: colors.seatText }]}>\n {centerLabel}\n </Text>\n </View>\n ) : null}\n {centerSublabel !== undefined ? (\n <Text style={[styles.centerSublabel, { color: colors.tableMutedText }]}>\n {centerSublabel}\n </Text>\n ) : null}\n </View>\n </View>\n\n {seats.map((seat, index) => {\n const position = getTabletopSeatPosition(index, resolvedSeatCount);\n const hasCards = seat.cards !== undefined || seat.faceDownCards !== undefined;\n const seatCardSize = getTabletopSeatCardSize({\n selected: seat.selected,\n tableCardSize: cardSize,\n });\n\n return (\n <View\n accessibilityLabel={createSeatAccessibilityLabel(seat)}\n accessibilityRole=\"summary\"\n key={seat.id}\n style={[\n styles.seatSlot,\n {\n left: position.left,\n opacity: seat.muted ? 0.48 : 1,\n top: position.top,\n },\n ]}\n testID={testID ? `${testID}-seat-${seat.id}` : undefined}\n >\n <View\n style={[\n styles.seatPanel,\n seat.selected ? styles.selectedSeatPanel : null,\n {\n borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,\n backgroundColor: colors.seatSurface,\n },\n ]}\n >\n {hasCards ? (\n <CardHand\n cards={seat.cards}\n colorScheme={colorScheme}\n faceDownCards={seat.faceDownCards}\n muted={seat.muted ?? seat.disabled}\n size={seatCardSize}\n />\n ) : null}\n <View style={styles.seatTextGroup}>\n <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>\n {seat.sublabel !== undefined ? (\n <Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>\n {seat.sublabel}\n </Text>\n ) : null}\n </View>\n </View>\n {seat.tokenLabel !== undefined ? (\n <View\n style={[\n styles.token,\n {\n backgroundColor: colors.tokenSurface,\n borderColor: seat.selected\n ? colors.seatSelectedBorder\n : colors.tableInnerBorder,\n },\n ]}\n >\n <Text style={[styles.tokenText, { color: colors.tokenText }]}>\n {seat.tokenLabel}\n </Text>\n </View>\n ) : null}\n </View>\n );\n })}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n centerContent: {\n alignItems: 'center',\n gap: 6,\n left: 0,\n position: 'absolute',\n right: 0,\n top: '35%',\n },\n centerLabel: {\n borderRadius: 999,\n paddingHorizontal: 10,\n paddingVertical: 4,\n },\n centerLabelText: {\n fontSize: 12,\n fontWeight: '800',\n },\n centerSublabel: {\n fontSize: 11,\n fontWeight: '700',\n },\n circleSurface: {\n borderRadius: 999,\n },\n innerSurface: {\n borderWidth: 1,\n bottom: 10,\n left: 10,\n opacity: 0.44,\n position: 'absolute',\n right: 10,\n top: 10,\n },\n ovalSurface: {\n borderRadius: 999,\n },\n root: {\n aspectRatio: 1.6,\n maxWidth: 760,\n position: 'relative',\n width: '100%',\n },\n roundedSurface: {\n borderRadius: 28,\n },\n seatLabel: {\n fontSize: 11,\n fontWeight: '800',\n },\n seatPanel: {\n alignItems: 'center',\n borderRadius: 12,\n borderWidth: 1,\n gap: 4,\n minWidth: 68,\n paddingHorizontal: 7,\n paddingVertical: 6,\n },\n seatSlot: {\n alignItems: 'center',\n gap: 4,\n minWidth: 78,\n position: 'absolute',\n transform: [{ translateX: -39 }, { translateY: -34 }],\n },\n seatSublabel: {\n fontSize: 10,\n fontWeight: '700',\n },\n seatTextGroup: {\n alignItems: 'center',\n gap: 1,\n },\n selectedSeatPanel: {\n borderWidth: 2,\n paddingHorizontal: 9,\n paddingVertical: 7,\n },\n surface: {\n borderWidth: 8,\n bottom: 28,\n left: 24,\n position: 'absolute',\n right: 24,\n top: 24,\n },\n token: {\n borderRadius: 999,\n borderWidth: 1,\n paddingHorizontal: 7,\n paddingVertical: 2,\n },\n tokenText: {\n fontSize: 10,\n fontWeight: '800',\n },\n});\n"]}
@@ -0,0 +1,6 @@
1
+ import type { TabletopCardSize } from './types';
2
+ export declare function getTabletopSeatCardSize(args: {
3
+ readonly selected?: boolean;
4
+ readonly tableCardSize: TabletopCardSize;
5
+ }): TabletopCardSize;
6
+ //# sourceMappingURL=tableLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tableLayout.d.ts","sourceRoot":"","sources":["../src/tableLayout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC;CAC1C,GAAG,gBAAgB,CAInB"}
@@ -0,0 +1,6 @@
1
+ export function getTabletopSeatCardSize(args) {
2
+ if (args.selected && args.tableCardSize === 'small')
3
+ return 'medium';
4
+ return args.tableCardSize;
5
+ }
6
+ //# sourceMappingURL=tableLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tableLayout.js","sourceRoot":"","sources":["../src/tableLayout.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,uBAAuB,CAAC,IAGvC;IACC,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC;IAErE,OAAO,IAAI,CAAC,aAAa,CAAC;AAC5B,CAAC","sourcesContent":["import type { TabletopCardSize } from './types';\n\nexport function getTabletopSeatCardSize(args: {\n readonly selected?: boolean;\n readonly tableCardSize: TabletopCardSize;\n}): TabletopCardSize {\n if (args.selected && args.tableCardSize === 'small') return 'medium';\n\n return args.tableCardSize;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/zora-tabletop",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.0.6",
5
5
  "description": "Tabletop and card-game UI primitives for React Native and React Native Web, built on ZORA.",
6
6
  "homepage": "https://github.com/ankhorage/zora-tabletop#readme",
7
7
  "bugs": {
@@ -48,10 +48,9 @@
48
48
  },
49
49
  "scripts": {
50
50
  "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.json",
51
- "changeset": "changeset",
52
- "changeset:status": "changeset status --since=origin/main",
51
+ "changeset": "ankhorage-changeset",
52
+ "changeset:status": "ankhorage-changeset status --since=origin/main",
53
53
  "docs": "paradox",
54
- "knip": "ankhorage-knip",
55
54
  "lint": "ankhorage-eslint . --max-warnings=0",
56
55
  "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
57
56
  "format": "ankhorage-prettier --write .",
@@ -59,24 +58,33 @@
59
58
  "prepack": "bun run build",
60
59
  "test": "bun test src",
61
60
  "typecheck": "bun x tsc --noEmit -p tsconfig.json",
62
- "version-packages": "changeset version"
61
+ "version-packages": "ankhorage-changeset version",
62
+ "knip:check": "ankhorage-knip"
63
63
  },
64
64
  "peerDependencies": {
65
- "@ankhorage/zora": ">=0.1.0",
65
+ "@ankhorage/zora": ">=3.3.1",
66
66
  "react": ">=18.2.0",
67
67
  "react-native": ">=0.72.0"
68
68
  },
69
69
  "devDependencies": {
70
- "@ankhorage/devtools": "^1.0.6",
71
- "@ankhorage/paradox": "^0.1.10",
72
- "@ankhorage/zora": "^2.5.4",
73
- "@changesets/cli": "^2.31.0",
74
- "@types/bun": "^1.3.13",
75
- "@types/node": "^25.6.0",
76
- "@types/react": "^19.1.8",
77
- "react": "^19.1.0",
78
- "react-native": "^0.81.5",
79
- "typescript": "^5.9.3"
70
+ "@ankhorage/devtools": "^1.9.2",
71
+ "@ankhorage/paradox": "^0.1.24",
72
+ "@ankhorage/zora": "^3.3.1",
73
+ "@react-native-picker/picker": "2.11.4",
74
+ "@react-native-vector-icons/fontawesome": "^13.1.3",
75
+ "@react-native-vector-icons/fontawesome5": "^13.1.3",
76
+ "@react-native-vector-icons/fontawesome6": "^13.1.3",
77
+ "@react-native-vector-icons/ionicons": "^13.1.3",
78
+ "@types/bun": "^1.4.0",
79
+ "@types/node": "^24.13.3",
80
+ "@types/react": "^19.2.18",
81
+ "@types/react-dom": "^19.2.4",
82
+ "react": "19.2.3",
83
+ "react-dom": "19.2.3",
84
+ "react-native": "0.86.2",
85
+ "react-native-safe-area-context": "~5.7.0",
86
+ "react-native-web": "~0.21.0",
87
+ "typescript": "~6.0.3"
80
88
  },
81
- "packageManager": "bun@1.3.13"
89
+ "packageManager": "bun@1.4.0"
82
90
  }
@@ -0,0 +1,11 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { getPlayingCardLabel } from './cardLabel';
4
+
5
+ describe('cardLabel', () => {
6
+ test('creates accessible rank and suit labels', () => {
7
+ expect(getPlayingCardLabel({ rank: 'A', suit: 'hearts' })).toBe('ace of hearts');
8
+ expect(getPlayingCardLabel({ rank: 'K', suit: 'clubs' })).toBe('king of clubs');
9
+ expect(getPlayingCardLabel({ rank: '10', suit: 'spades' })).toBe('10 of spades');
10
+ });
11
+ });
package/src/cardLabel.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import type { PlayingCardValue } from './types';
2
2
 
3
+ const rankLabels: Readonly<Record<string, string>> = {
4
+ A: 'ace',
5
+ J: 'jack',
6
+ K: 'king',
7
+ Q: 'queen',
8
+ };
9
+
3
10
  export function getPlayingCardLabel(card: PlayingCardValue): string {
4
- return `${card.rank} ${card.suit}`;
11
+ return `${getPlayingCardRankLabel(card.rank)} of ${card.suit}`;
5
12
  }
6
13
 
7
14
  export function createHiddenCardLabel(index?: number): string {
@@ -9,3 +16,7 @@ export function createHiddenCardLabel(index?: number): string {
9
16
 
10
17
  return `Hidden card ${index + 1}`;
11
18
  }
19
+
20
+ function getPlayingCardRankLabel(rank: string): string {
21
+ return rankLabels[rank] ?? rank;
22
+ }
@@ -0,0 +1,19 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { getPlayingCardSuitMark, isRedPlayingCardSuit } from './cardSymbols';
4
+
5
+ describe('cardSymbols', () => {
6
+ test('maps card suits to visual glyphs', () => {
7
+ expect(getPlayingCardSuitMark('clubs')).toBe('♣');
8
+ expect(getPlayingCardSuitMark('diamonds')).toBe('♦');
9
+ expect(getPlayingCardSuitMark('hearts')).toBe('♥');
10
+ expect(getPlayingCardSuitMark('spades')).toBe('♠');
11
+ });
12
+
13
+ test('identifies red suits', () => {
14
+ expect(isRedPlayingCardSuit('diamonds')).toBe(true);
15
+ expect(isRedPlayingCardSuit('hearts')).toBe(true);
16
+ expect(isRedPlayingCardSuit('clubs')).toBe(false);
17
+ expect(isRedPlayingCardSuit('spades')).toBe(false);
18
+ });
19
+ });
@@ -0,0 +1,16 @@
1
+ import type { PlayingCardSuit } from './types';
2
+
3
+ const suitMarks: Readonly<Record<PlayingCardSuit, string>> = {
4
+ clubs: '♣',
5
+ diamonds: '♦',
6
+ hearts: '♥',
7
+ spades: '♠',
8
+ };
9
+
10
+ export function getPlayingCardSuitMark(suit: PlayingCardSuit): string {
11
+ return suitMarks[suit];
12
+ }
13
+
14
+ export function isRedPlayingCardSuit(suit: PlayingCardSuit): boolean {
15
+ return suit === 'diamonds' || suit === 'hearts';
16
+ }
@@ -4,27 +4,16 @@ import { StyleSheet, Text, View } from 'react-native';
4
4
 
5
5
  import { getPlayingCardLabel } from '../../cardLabel';
6
6
  import { getTabletopCardDimensions } from '../../cardSizing';
7
+ import { getPlayingCardSuitMark, isRedPlayingCardSuit } from '../../cardSymbols';
7
8
  import { createTabletopColorScheme } from '../../colors';
8
- import type { PlayingCardSuit } from '../../types';
9
9
  import type { PlayingCardProps } from './types';
10
10
 
11
- const suitMarks: Readonly<Record<PlayingCardSuit, string>> = {
12
- clubs: 'C',
13
- diamonds: 'D',
14
- hearts: 'H',
15
- spades: 'S',
16
- };
17
-
18
- function isRedSuit(suit: PlayingCardSuit): boolean {
19
- return suit === 'diamonds' || suit === 'hearts';
20
- }
21
-
22
11
  /***
23
12
  * Theme-aware face-up playing card primitive.
24
13
  *
25
14
  * Use `PlayingCard` for visible card values in hands, shared table cards, piles,
26
- * or custom tabletop layouts. The component renders rank and suit text and exposes
27
- * an accessible card label by default.
15
+ * or custom tabletop layouts. The component renders rank and suit glyphs and
16
+ * exposes an accessible card label by default.
28
17
  *
29
18
  * @readme
30
19
  * @example Face-up card
@@ -47,7 +36,7 @@ export function PlayingCard({
47
36
  [colorScheme, theme],
48
37
  );
49
38
  const dimensions = getTabletopCardDimensions(size);
50
- const suitColor = isRedSuit(card.suit) ? colors.redSuitText : colors.cardText;
39
+ const suitColor = isRedPlayingCardSuit(card.suit) ? colors.redSuitText : colors.cardText;
51
40
 
52
41
  return (
53
42
  <View
@@ -90,7 +79,7 @@ export function PlayingCard({
90
79
  },
91
80
  ]}
92
81
  >
93
- {suitMarks[card.suit]}
82
+ {getPlayingCardSuitMark(card.suit)}
94
83
  </Text>
95
84
  </View>
96
85
  );
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { StyleSheet, Text, View } from 'react-native';
4
4
 
5
5
  import { createTabletopColorScheme } from '../../colors';
6
+ import { getTabletopSeatCardSize } from '../../tableLayout';
6
7
  import { getTabletopSeatPosition } from '../../tablePositions';
7
8
  import type { TabletopSeatState } from '../../types';
8
9
  import { CardHand } from '../card-hand';
@@ -105,16 +106,20 @@ export function TabletopTable({
105
106
 
106
107
  {seats.map((seat, index) => {
107
108
  const position = getTabletopSeatPosition(index, resolvedSeatCount);
109
+ const hasCards = seat.cards !== undefined || seat.faceDownCards !== undefined;
110
+ const seatCardSize = getTabletopSeatCardSize({
111
+ selected: seat.selected,
112
+ tableCardSize: cardSize,
113
+ });
114
+
108
115
  return (
109
116
  <View
110
117
  accessibilityLabel={createSeatAccessibilityLabel(seat)}
111
118
  accessibilityRole="summary"
112
119
  key={seat.id}
113
120
  style={[
114
- styles.seat,
121
+ styles.seatSlot,
115
122
  {
116
- borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,
117
- backgroundColor: colors.seatSurface,
118
123
  left: position.left,
119
124
  opacity: seat.muted ? 0.48 : 1,
120
125
  top: position.top,
@@ -122,23 +127,46 @@ export function TabletopTable({
122
127
  ]}
123
128
  testID={testID ? `${testID}-seat-${seat.id}` : undefined}
124
129
  >
125
- {seat.cards !== undefined || seat.faceDownCards !== undefined ? (
126
- <CardHand
127
- cards={seat.cards}
128
- colorScheme={colorScheme}
129
- faceDownCards={seat.faceDownCards}
130
- muted={seat.muted ?? seat.disabled}
131
- size={cardSize}
132
- />
133
- ) : null}
134
- <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>
135
- {seat.sublabel !== undefined ? (
136
- <Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>
137
- {seat.sublabel}
138
- </Text>
139
- ) : null}
130
+ <View
131
+ style={[
132
+ styles.seatPanel,
133
+ seat.selected ? styles.selectedSeatPanel : null,
134
+ {
135
+ borderColor: seat.selected ? colors.seatSelectedBorder : colors.seatBorder,
136
+ backgroundColor: colors.seatSurface,
137
+ },
138
+ ]}
139
+ >
140
+ {hasCards ? (
141
+ <CardHand
142
+ cards={seat.cards}
143
+ colorScheme={colorScheme}
144
+ faceDownCards={seat.faceDownCards}
145
+ muted={seat.muted ?? seat.disabled}
146
+ size={seatCardSize}
147
+ />
148
+ ) : null}
149
+ <View style={styles.seatTextGroup}>
150
+ <Text style={[styles.seatLabel, { color: colors.seatText }]}>{seat.label}</Text>
151
+ {seat.sublabel !== undefined ? (
152
+ <Text style={[styles.seatSublabel, { color: colors.seatMutedText }]}>
153
+ {seat.sublabel}
154
+ </Text>
155
+ ) : null}
156
+ </View>
157
+ </View>
140
158
  {seat.tokenLabel !== undefined ? (
141
- <View style={[styles.token, { backgroundColor: colors.tokenSurface }]}>
159
+ <View
160
+ style={[
161
+ styles.token,
162
+ {
163
+ backgroundColor: colors.tokenSurface,
164
+ borderColor: seat.selected
165
+ ? colors.seatSelectedBorder
166
+ : colors.tableInnerBorder,
167
+ },
168
+ ]}
169
+ >
142
170
  <Text style={[styles.tokenText, { color: colors.tokenText }]}>
143
171
  {seat.tokenLabel}
144
172
  </Text>
@@ -197,25 +225,39 @@ const styles = StyleSheet.create({
197
225
  roundedSurface: {
198
226
  borderRadius: 28,
199
227
  },
200
- seat: {
201
- alignItems: 'center',
202
- borderRadius: 10,
203
- borderWidth: 1,
204
- gap: 3,
205
- minWidth: 64,
206
- paddingHorizontal: 6,
207
- paddingVertical: 5,
208
- position: 'absolute',
209
- transform: [{ translateX: -32 }, { translateY: -28 }],
210
- },
211
228
  seatLabel: {
212
229
  fontSize: 11,
213
230
  fontWeight: '800',
214
231
  },
232
+ seatPanel: {
233
+ alignItems: 'center',
234
+ borderRadius: 12,
235
+ borderWidth: 1,
236
+ gap: 4,
237
+ minWidth: 68,
238
+ paddingHorizontal: 7,
239
+ paddingVertical: 6,
240
+ },
241
+ seatSlot: {
242
+ alignItems: 'center',
243
+ gap: 4,
244
+ minWidth: 78,
245
+ position: 'absolute',
246
+ transform: [{ translateX: -39 }, { translateY: -34 }],
247
+ },
215
248
  seatSublabel: {
216
249
  fontSize: 10,
217
250
  fontWeight: '700',
218
251
  },
252
+ seatTextGroup: {
253
+ alignItems: 'center',
254
+ gap: 1,
255
+ },
256
+ selectedSeatPanel: {
257
+ borderWidth: 2,
258
+ paddingHorizontal: 9,
259
+ paddingVertical: 7,
260
+ },
219
261
  surface: {
220
262
  borderWidth: 8,
221
263
  bottom: 28,
@@ -226,7 +268,8 @@ const styles = StyleSheet.create({
226
268
  },
227
269
  token: {
228
270
  borderRadius: 999,
229
- paddingHorizontal: 6,
271
+ borderWidth: 1,
272
+ paddingHorizontal: 7,
230
273
  paddingVertical: 2,
231
274
  },
232
275
  tokenText: {
@@ -0,0 +1,15 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { getTabletopSeatCardSize } from './tableLayout';
4
+
5
+ describe('tableLayout', () => {
6
+ test('promotes selected compact seat cards for readability', () => {
7
+ expect(getTabletopSeatCardSize({ selected: true, tableCardSize: 'small' })).toBe('medium');
8
+ });
9
+
10
+ test('preserves non-selected and already readable card sizes', () => {
11
+ expect(getTabletopSeatCardSize({ selected: false, tableCardSize: 'small' })).toBe('small');
12
+ expect(getTabletopSeatCardSize({ selected: true, tableCardSize: 'medium' })).toBe('medium');
13
+ expect(getTabletopSeatCardSize({ selected: true, tableCardSize: 'large' })).toBe('large');
14
+ });
15
+ });
@@ -0,0 +1,10 @@
1
+ import type { TabletopCardSize } from './types';
2
+
3
+ export function getTabletopSeatCardSize(args: {
4
+ readonly selected?: boolean;
5
+ readonly tableCardSize: TabletopCardSize;
6
+ }): TabletopCardSize {
7
+ if (args.selected && args.tableCardSize === 'small') return 'medium';
8
+
9
+ return args.tableCardSize;
10
+ }