@tastic/hud 0.8.2 → 0.8.3

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/dist/index.mjs CHANGED
@@ -74,10 +74,43 @@ var styles = StyleSheet.create({
74
74
  import { AutoAppearancePicker, Dialog, useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
75
75
  import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from "@rific/feedback-press";
76
76
  import { useUpdater } from "@rific/updater";
77
- import { useIsTouchPrimaryDevice, useRotation } from "@tastic/core";
77
+ import { toRotationStyle, useIsTouchPrimaryDevice, useRotation } from "@tastic/core";
78
78
  import { useContext, useState } from "react";
79
- import { Platform, ScrollView, StyleSheet as StyleSheet2, View as View2 } from "react-native";
79
+ import { Platform, ScrollView, StyleSheet as StyleSheet3, View as View2 } from "react-native";
80
80
  import { Icon as Icon2, Portal, Text as Text2 } from "react-native-paper";
81
+
82
+ // src/overlayCard.ts
83
+ import { StyleSheet as StyleSheet2 } from "react-native";
84
+ function getOverlayCardColors(dark) {
85
+ return {
86
+ fg: dark ? "#FFFFFF" : "#000000",
87
+ fgMuted: dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)",
88
+ cardBg: dark ? "#111111" : "#F2F2F2",
89
+ cardBorder: dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"
90
+ };
91
+ }
92
+ var overlayStyles = StyleSheet2.create({
93
+ card: {
94
+ alignItems: "center",
95
+ borderRadius: 20,
96
+ borderWidth: 1,
97
+ gap: 16,
98
+ maxWidth: 360,
99
+ padding: 32
100
+ },
101
+ overlay: {
102
+ alignItems: "center",
103
+ backgroundColor: "rgba(0,0,0,0.72)",
104
+ bottom: 0,
105
+ justifyContent: "center",
106
+ left: 0,
107
+ position: "absolute",
108
+ right: 0,
109
+ top: 0
110
+ }
111
+ });
112
+
113
+ // src/BaseSettingsDialog.tsx
81
114
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
82
115
  function SettingIcon({ source, color, containerColor }) {
83
116
  return /* @__PURE__ */ jsx2(View2, { style: [styles2.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx2(Icon2, { source, size: 18, color }) });
@@ -104,11 +137,9 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
104
137
  const showSound = !hideSound;
105
138
  const showAppearance = !hideAppearance;
106
139
  const showUpdateCheck = Platform.OS !== "web" && !hideUpdateCheck;
107
- const fg = dark ? "#FFFFFF" : "#000000";
108
- const cardBg = dark ? "#111111" : "#F2F2F2";
109
- const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
140
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
110
141
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
111
- /* @__PURE__ */ jsxs2(Dialog, { visible, onDismiss, style: [styles2.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
142
+ /* @__PURE__ */ jsxs2(Dialog, { visible, onDismiss, style: [styles2.dialog, toRotationStyle(rotation)], children: [
112
143
  /* @__PURE__ */ jsx2(Dialog.Title, { children: "Settings" }),
113
144
  /* @__PURE__ */ jsx2(Dialog.ScrollArea, { style: styles2.scrollArea, children: /* @__PURE__ */ jsxs2(ScrollView, { contentContainerStyle: styles2.content, showsVerticalScrollIndicator: false, children: [
114
145
  /* @__PURE__ */ jsxs2(View2, { style: styles2.toggleGroup, children: [
@@ -178,7 +209,7 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
178
209
  ] })
179
210
  ] }) })
180
211
  ] }),
181
- infoMessage && /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(View2, { style: styles2.overlay, children: /* @__PURE__ */ jsxs2(View2, { style: [styles2.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
212
+ infoMessage && /* @__PURE__ */ jsx2(Portal, { children: /* @__PURE__ */ jsx2(View2, { style: overlayStyles.overlay, children: /* @__PURE__ */ jsxs2(View2, { style: [overlayStyles.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)], children: [
182
213
  /* @__PURE__ */ jsx2(Icon2, { source: "information-outline", size: 64, color: colors.secondary }),
183
214
  /* @__PURE__ */ jsx2(Text2, { variant: "headlineLarge", style: [styles2.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
184
215
  /* @__PURE__ */ jsx2(Text2, { variant: "bodyLarge", style: [styles2.overlayBody, { color: fg }], children: infoMessage.message }),
@@ -186,7 +217,7 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
186
217
  ] }) }) })
187
218
  ] });
188
219
  }
189
- var styles2 = StyleSheet2.create({
220
+ var styles2 = StyleSheet3.create({
190
221
  content: {
191
222
  gap: 24,
192
223
  paddingVertical: 20
@@ -208,26 +239,8 @@ var styles2 = StyleSheet2.create({
208
239
  justifyContent: "center",
209
240
  width: 36
210
241
  },
211
- overlay: {
212
- alignItems: "center",
213
- backgroundColor: "rgba(0,0,0,0.72)",
214
- bottom: 0,
215
- justifyContent: "center",
216
- left: 0,
217
- position: "absolute",
218
- right: 0,
219
- top: 0
220
- },
221
242
  overlayBody: { textAlign: "center" },
222
243
  overlayButton: { width: 160 },
223
- overlayCard: {
224
- alignItems: "center",
225
- borderRadius: 20,
226
- borderWidth: 1,
227
- gap: 16,
228
- maxWidth: 360,
229
- padding: 32
230
- },
231
244
  overlayTitle: { fontWeight: "bold" },
232
245
  scrollArea: {
233
246
  marginBottom: 0
@@ -279,24 +292,23 @@ import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
279
292
  import { Button as Button3, IconButton } from "@rific/feedback-press";
280
293
  import { useRotation as useRotation2 } from "@tastic/core";
281
294
  import { useState as useState2 } from "react";
282
- import { ScrollView as ScrollView2, StyleSheet as StyleSheet4, View as View4 } from "react-native";
295
+ import { ScrollView as ScrollView2, StyleSheet as StyleSheet5, View as View4 } from "react-native";
283
296
  import { Text as Text4 } from "react-native-paper";
284
297
 
285
298
  // src/ConfirmDialog.tsx
286
299
  import { useAutoPaperTheme as useAutoPaperTheme3 } from "@rific/auto-paper";
287
300
  import { Button as Button2 } from "@rific/feedback-press";
288
- import { StyleSheet as StyleSheet3, View as View3 } from "react-native";
301
+ import { toRotationStyle as toRotationStyle2 } from "@tastic/core";
302
+ import { StyleSheet as StyleSheet4, View as View3 } from "react-native";
289
303
  import { Icon as Icon3, Portal as Portal2, Text as Text3 } from "react-native-paper";
290
304
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
291
305
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
292
306
  const { dark, colors } = useAutoPaperTheme3();
293
307
  if (!visible) return null;
294
- const fg = dark ? "#FFFFFF" : "#000000";
295
- const cardBg = dark ? "#111111" : "#F2F2F2";
296
- const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
308
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark);
297
309
  const accent = destructive ? colors.danger : colors.primary;
298
310
  const onAccent = destructive ? colors.onDanger : colors.onPrimary;
299
- return /* @__PURE__ */ jsx3(Portal2, { children: /* @__PURE__ */ jsx3(View3, { style: styles3.overlay, children: /* @__PURE__ */ jsxs3(View3, { style: [styles3.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
311
+ return /* @__PURE__ */ jsx3(Portal2, { children: /* @__PURE__ */ jsx3(View3, { style: [overlayStyles.overlay, styles3.overlay], children: /* @__PURE__ */ jsxs3(View3, { style: [overlayStyles.card, styles3.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle2(rotation)], children: [
300
312
  /* @__PURE__ */ jsx3(Icon3, { source: icon, size: 64, color: accent }),
301
313
  /* @__PURE__ */ jsx3(Text3, { variant: "headlineLarge", style: [styles3.title, { color: accent }], children: title }),
302
314
  /* @__PURE__ */ jsx3(Text3, { variant: "bodyLarge", style: [styles3.body, { color: fg }], children: message }),
@@ -306,7 +318,7 @@ function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onC
306
318
  ] })
307
319
  ] }) }) });
308
320
  }
309
- var styles3 = StyleSheet3.create({
321
+ var styles3 = StyleSheet4.create({
310
322
  // flexBasis (not flex: 1's own 0%) is each button's real floor: whenever both fit side by side,
311
323
  // flexGrow still splits any leftover width evenly beyond that floor, same visual result as the
312
324
  // old flex: 1 on a roomy screen - but a button can never be squeezed narrower than 128, only
@@ -331,32 +343,20 @@ var styles3 = StyleSheet3.create({
331
343
  body: {
332
344
  textAlign: "center"
333
345
  },
346
+ // Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
347
+ // padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
348
+ // card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
349
+ // instead of stretching, so the card (and therefore actions below) can end up narrower than the
350
+ // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
334
351
  card: {
335
- alignItems: "center",
336
- borderRadius: 20,
337
- borderWidth: 1,
338
- gap: 16,
339
- maxWidth: 360,
340
- padding: 32,
341
- // Claims its full allowance instead of just maxWidth alone, which only caps how wide the card
342
- // is allowed to get - without an explicit width, a flex-centered child sizes to its own content
343
- // instead of stretching, so the card (and therefore actions below) can end up narrower than the
344
- // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
345
352
  width: "100%"
346
353
  },
354
+ // Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
355
+ // from the screen edges now that it reliably stretches to fill its full width allowance (see
356
+ // card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
357
+ // screen narrower than maxWidth's own 360.
347
358
  overlay: {
348
- alignItems: "center",
349
- backgroundColor: "rgba(0,0,0,0.72)",
350
- bottom: 0,
351
- justifyContent: "center",
352
- left: 0,
353
- // Gives the card room to breathe from the screen edges now that it reliably stretches to fill
354
- // its full width allowance (see card's own width comment) - without this, width: '100%' would
355
- // run the card edge-to-edge on a screen narrower than maxWidth's own 360.
356
- paddingHorizontal: 24,
357
- position: "absolute",
358
- right: 0,
359
- top: 0
359
+ paddingHorizontal: 24
360
360
  },
361
361
  // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
362
362
  // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
@@ -406,7 +406,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
406
406
  )
407
407
  ] });
408
408
  }
409
- var styles4 = StyleSheet4.create({
409
+ var styles4 = StyleSheet5.create({
410
410
  container: {
411
411
  flex: 1
412
412
  },
@@ -438,7 +438,7 @@ var styles4 = StyleSheet4.create({
438
438
 
439
439
  // src/ContentGutter.tsx
440
440
  import { computeContentBounds } from "@tastic/core";
441
- import { StyleSheet as StyleSheet5, useWindowDimensions, View as View5 } from "react-native";
441
+ import { StyleSheet as StyleSheet6, useWindowDimensions, View as View5 } from "react-native";
442
442
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
443
443
  function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
444
444
  const { width: windowWidth } = useWindowDimensions();
@@ -449,7 +449,7 @@ function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, childr
449
449
  /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: rightGutter })
450
450
  ] });
451
451
  }
452
- var styles5 = StyleSheet5.create({
452
+ var styles5 = StyleSheet6.create({
453
453
  // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
454
454
  // column instead of clipping cleanly at its own real width.
455
455
  gutter: {
@@ -468,7 +468,7 @@ import { Platform as Platform3 } from "react-native";
468
468
  import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
469
469
  import { IconButton as IconButton2, TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
470
470
  import { useRotation as useRotation3 } from "@tastic/core";
471
- import { ScrollView as ScrollView3, StyleSheet as StyleSheet7, View as View8 } from "react-native";
471
+ import { ScrollView as ScrollView3, StyleSheet as StyleSheet8, View as View8 } from "react-native";
472
472
  import { Icon as Icon4, Text as Text5 } from "react-native-paper";
473
473
 
474
474
  // src/fonts.ts
@@ -476,7 +476,7 @@ import { Platform as Platform2 } from "react-native";
476
476
  var MONO_FONT = Platform2.select({ ios: "Menlo", android: "monospace", default: "monospace" });
477
477
 
478
478
  // src/PopoverBody.tsx
479
- import { StyleSheet as StyleSheet6, View as View6 } from "react-native";
479
+ import { StyleSheet as StyleSheet7, View as View6 } from "react-native";
480
480
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
481
481
  var CARET_SIZE = 7;
482
482
  var CARET_RING_OFFSET = 2;
@@ -499,7 +499,7 @@ function PopoverBody({ visible, children, align = "center", verticalAlign = "bel
499
499
  ] })
500
500
  ] });
501
501
  }
502
- var styles6 = StyleSheet6.create({
502
+ var styles6 = StyleSheet7.create({
503
503
  caret: {
504
504
  borderLeftColor: "transparent",
505
505
  borderRightColor: "transparent",
@@ -721,7 +721,7 @@ function MultiSection({
721
721
  section.allClear && /* @__PURE__ */ jsx8(TouchableRipple2, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles7.item, children: /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: [styles7.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
722
722
  ] });
723
723
  }
724
- var styles7 = StyleSheet7.create({
724
+ var styles7 = StyleSheet8.create({
725
725
  allClearLabel: {
726
726
  fontWeight: "bold",
727
727
  textAlign: "center"
@@ -738,7 +738,7 @@ var styles7 = StyleSheet7.create({
738
738
  zIndex: 100
739
739
  },
740
740
  divider: {
741
- height: StyleSheet7.hairlineWidth,
741
+ height: StyleSheet8.hairlineWidth,
742
742
  marginVertical: 4
743
743
  },
744
744
  item: {
@@ -800,7 +800,7 @@ function ControlSchemePicker({ id, host, value, onChange, options, takenValue, a
800
800
 
801
801
  // src/CornerActionButtons.tsx
802
802
  import { IconButton as IconButton3 } from "@rific/feedback-press";
803
- import { StyleSheet as StyleSheet8 } from "react-native";
803
+ import { StyleSheet as StyleSheet9 } from "react-native";
804
804
  import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
805
805
  function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
806
806
  return /* @__PURE__ */ jsxs8(Fragment4, { children: [
@@ -808,7 +808,7 @@ function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow
808
808
  /* @__PURE__ */ jsx10(IconButton3, { icon: "cog", iconColor: fg, size: 24, style: [styles8.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
809
809
  ] });
810
810
  }
811
- var styles8 = StyleSheet8.create({
811
+ var styles8 = StyleSheet9.create({
812
812
  back: {
813
813
  position: "absolute"
814
814
  },
@@ -818,7 +818,7 @@ var styles8 = StyleSheet8.create({
818
818
  });
819
819
 
820
820
  // src/CornerStatusBadges.tsx
821
- import { StyleSheet as StyleSheet9, View as View9 } from "react-native";
821
+ import { StyleSheet as StyleSheet10, View as View9 } from "react-native";
822
822
  import { Icon as Icon5 } from "react-native-paper";
823
823
  import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
824
824
  var BADGE_SIZE = 30;
@@ -833,7 +833,7 @@ function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
833
833
  p2 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p2OnLeftSide ? styles9.topLeft : styles9.topRight, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p2.icon }) })
834
834
  ] });
835
835
  }
836
- var styles9 = StyleSheet9.create({
836
+ var styles9 = StyleSheet10.create({
837
837
  badge: {
838
838
  alignItems: "center",
839
839
  backgroundColor: "rgba(0,0,0,0.28)",
@@ -855,7 +855,7 @@ var styles9 = StyleSheet9.create({
855
855
  import { defaultColors, getContrastColor } from "@rific/auto-paper";
856
856
  import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
857
857
  import { clamp, useRotation as useRotation4 } from "@tastic/core";
858
- import { ScrollView as ScrollView4, StyleSheet as StyleSheet10, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
858
+ import { ScrollView as ScrollView4, StyleSheet as StyleSheet11, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
859
859
  import { Icon as Icon6, Text as Text6 } from "react-native-paper";
860
860
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
861
861
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
@@ -911,7 +911,7 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
911
911
  }) }) })
912
912
  ] });
913
913
  }
914
- var styles10 = StyleSheet10.create({
914
+ var styles10 = StyleSheet11.create({
915
915
  anchor: {
916
916
  position: "relative"
917
917
  },
@@ -985,7 +985,7 @@ var styles10 = StyleSheet10.create({
985
985
  // src/LabeledDropdown.tsx
986
986
  import { getContrastColor as getContrastColor2 } from "@rific/auto-paper";
987
987
  import { TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
988
- import { ScrollView as ScrollView5, StyleSheet as StyleSheet11, View as View11 } from "react-native";
988
+ import { ScrollView as ScrollView5, StyleSheet as StyleSheet12, View as View11 } from "react-native";
989
989
  import { Icon as Icon7, Text as Text7 } from "react-native-paper";
990
990
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
991
991
  var TRIGGER_HEIGHT = 28;
@@ -1025,7 +1025,7 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1025
1025
  }) }) })
1026
1026
  ] });
1027
1027
  }
1028
- var styles11 = StyleSheet11.create({
1028
+ var styles11 = StyleSheet12.create({
1029
1029
  anchor: {
1030
1030
  position: "relative"
1031
1031
  },
@@ -1083,16 +1083,16 @@ function loadSkiaWeb() {
1083
1083
  }
1084
1084
 
1085
1085
  // src/PressAwayOverlay.tsx
1086
- import { Pressable, StyleSheet as StyleSheet12 } from "react-native";
1086
+ import { Pressable, StyleSheet as StyleSheet13 } from "react-native";
1087
1087
  import { jsx as jsx14 } from "react/jsx-runtime";
1088
1088
  function PressAwayOverlay({ active, onPress, style }) {
1089
1089
  if (!active) return null;
1090
- return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet12.absoluteFill, style] });
1090
+ return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet13.absoluteFill, style] });
1091
1091
  }
1092
1092
 
1093
1093
  // src/ReadyButton.tsx
1094
1094
  import { TouchableRipple as TouchableRipple5 } from "@rific/feedback-press";
1095
- import { StyleSheet as StyleSheet13 } from "react-native";
1095
+ import { StyleSheet as StyleSheet14 } from "react-native";
1096
1096
  import { Text as Text8 } from "react-native-paper";
1097
1097
  import { jsx as jsx15 } from "react/jsx-runtime";
1098
1098
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
@@ -1101,7 +1101,7 @@ function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MON
1101
1101
  const labelColorStyle = { color: ready ? "#000000" : color };
1102
1102
  return /* @__PURE__ */ jsx15(TouchableRipple5, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ jsx15(Text8, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1103
1103
  }
1104
- var styles12 = StyleSheet13.create({
1104
+ var styles12 = StyleSheet14.create({
1105
1105
  readyButton: {
1106
1106
  alignItems: "center",
1107
1107
  borderRadius: 12,
@@ -1115,7 +1115,7 @@ var styles12 = StyleSheet13.create({
1115
1115
 
1116
1116
  // src/SharedActionBand.tsx
1117
1117
  import { IconButton as IconButton4 } from "@rific/feedback-press";
1118
- import { StyleSheet as StyleSheet14, View as View12 } from "react-native";
1118
+ import { StyleSheet as StyleSheet15, View as View12 } from "react-native";
1119
1119
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1120
1120
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
1121
1121
  return /* @__PURE__ */ jsxs12(View12, { style: [styles13.column, popoverOpen && styles13.columnOpen], children: [
@@ -1128,7 +1128,7 @@ function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popove
1128
1128
  children
1129
1129
  ] });
1130
1130
  }
1131
- var styles13 = StyleSheet14.create({
1131
+ var styles13 = StyleSheet15.create({
1132
1132
  actionsRow: {
1133
1133
  alignItems: "center",
1134
1134
  flexDirection: "row",
@@ -1145,7 +1145,7 @@ var styles13 = StyleSheet14.create({
1145
1145
 
1146
1146
  // src/StatRow.tsx
1147
1147
  import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
1148
- import { StyleSheet as StyleSheet15, View as View13 } from "react-native";
1148
+ import { StyleSheet as StyleSheet16, View as View13 } from "react-native";
1149
1149
  import { Text as Text9 } from "react-native-paper";
1150
1150
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
1151
1151
  function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
@@ -1157,7 +1157,7 @@ function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
1157
1157
  /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: [styles14.value, { color: fg }], children: value })
1158
1158
  ] });
1159
1159
  }
1160
- var styles14 = StyleSheet15.create({
1160
+ var styles14 = StyleSheet16.create({
1161
1161
  row: {
1162
1162
  alignItems: "center",
1163
1163
  flexDirection: "row",
@@ -1170,7 +1170,7 @@ var styles14 = StyleSheet15.create({
1170
1170
 
1171
1171
  // src/StatSection.tsx
1172
1172
  import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
1173
- import { StyleSheet as StyleSheet16, View as View14 } from "react-native";
1173
+ import { StyleSheet as StyleSheet17, View as View14 } from "react-native";
1174
1174
  import { Text as Text10 } from "react-native-paper";
1175
1175
  import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1176
1176
  function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
@@ -1182,7 +1182,7 @@ function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, chil
1182
1182
  children
1183
1183
  ] });
1184
1184
  }
1185
- var styles15 = StyleSheet16.create({
1185
+ var styles15 = StyleSheet17.create({
1186
1186
  // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1187
1187
  // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1188
1188
  // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
@@ -1232,7 +1232,9 @@ export {
1232
1232
  StatSection,
1233
1233
  TriggerGaugeHost,
1234
1234
  getLabeledDropdownContentHeight,
1235
+ getOverlayCardColors,
1235
1236
  loadSkiaWeb,
1237
+ overlayStyles,
1236
1238
  useAutoAlign,
1237
1239
  usePopoverHost
1238
1240
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tastic/hud",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Visual component kit for local-multiplayer React Native games: inline (non-modal) popovers, dropdowns, color pickers, gauges, ready buttons, and dialogs, built to stay scoped to one player's own zone on a shared screen",
5
5
  "keywords": [
6
6
  "react-native",
@@ -77,7 +77,7 @@
77
77
  "@rific/feedback-press": "^0.10.8",
78
78
  "@rific/updater": "^0.6.0",
79
79
  "@shopify/react-native-skia": "^2.6.2",
80
- "@tastic/core": "^0.3.0",
80
+ "@tastic/core": "^0.4.1",
81
81
  "@testing-library/dom": "^10.4.1",
82
82
  "@testing-library/react": "^16.3.2",
83
83
  "@types/jest": "^30.0.0",
@@ -1,11 +1,13 @@
1
1
  import { AutoAppearancePicker, Dialog, useAutoPaperTheme } from '@rific/auto-paper'
2
2
  import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from '@rific/feedback-press'
3
3
  import { useUpdater } from '@rific/updater'
4
- import { useIsTouchPrimaryDevice, useRotation } from '@tastic/core'
4
+ import { toRotationStyle, useIsTouchPrimaryDevice, useRotation, type ViewRotation } from '@tastic/core'
5
5
  import { ReactNode, useContext, useState } from 'react'
6
6
  import { Platform, ScrollView, StyleSheet, View } from 'react-native'
7
7
  import { Icon, Portal, Text } from 'react-native-paper'
8
8
 
9
+ import { getOverlayCardColors, overlayStyles } from './overlayCard'
10
+
9
11
  interface SettingIconProps {
10
12
  source: string
11
13
  color: string
@@ -31,7 +33,7 @@ export interface BaseSettingsDialogProps {
31
33
  // dialog), so it just rotates its own content in place. Defaults to a live ambient read via
32
34
  // useRotation() when omitted; pass this only when a caller's own reading needs to differ from the
33
35
  // ambient one (rare — most callers should simply omit it).
34
- rotation?: number
36
+ rotation?: ViewRotation
35
37
  // Each app's own OTA version (e.g. release.otaVersion) — not something this package can read
36
38
  // itself, since every consuming app tracks its own release.ts independently. Accepts a number too
37
39
  // since that's what release.otaVersion actually is in most apps (a bare incrementing counter, not
@@ -118,15 +120,11 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
118
120
  const showAppearance = !hideAppearance
119
121
  const showUpdateCheck = Platform.OS !== 'web' && !hideUpdateCheck
120
122
 
121
- // High-contrast retro look: literal black/white by appearance, not auto-paper's own (slightly
122
- // tinted) background role.
123
- const fg = dark ? '#FFFFFF' : '#000000'
124
- const cardBg = dark ? '#111111' : '#F2F2F2'
125
- const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
123
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark)
126
124
 
127
125
  return (
128
126
  <>
129
- <Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }]}>
127
+ <Dialog visible={visible} onDismiss={onDismiss} style={[styles.dialog, toRotationStyle(rotation)]}>
130
128
  <Dialog.Title>Settings</Dialog.Title>
131
129
  {/* react-native-paper's Dialog.ScrollArea has a fixed 24px marginBottom baked in (meant to
132
130
  reserve room for a Dialog.Actions row below it) — overridden to 0 since this dialog has
@@ -265,8 +263,8 @@ export function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverr
265
263
  </Dialog>
266
264
  {infoMessage && (
267
265
  <Portal>
268
- <View style={styles.overlay}>
269
- <View style={[styles.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }]}>
266
+ <View style={overlayStyles.overlay}>
267
+ <View style={[overlayStyles.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)]}>
270
268
  <Icon source='information-outline' size={64} color={colors.secondary} />
271
269
  <Text variant='headlineLarge' style={[styles.overlayTitle, { color: colors.secondary }]}>
272
270
  {infoMessage.title}
@@ -307,26 +305,8 @@ const styles = StyleSheet.create({
307
305
  justifyContent: 'center',
308
306
  width: 36
309
307
  },
310
- overlay: {
311
- alignItems: 'center',
312
- backgroundColor: 'rgba(0,0,0,0.72)',
313
- bottom: 0,
314
- justifyContent: 'center',
315
- left: 0,
316
- position: 'absolute',
317
- right: 0,
318
- top: 0
319
- },
320
308
  overlayBody: { textAlign: 'center' },
321
309
  overlayButton: { width: 160 },
322
- overlayCard: {
323
- alignItems: 'center',
324
- borderRadius: 20,
325
- borderWidth: 1,
326
- gap: 16,
327
- maxWidth: 360,
328
- padding: 32
329
- },
330
310
  overlayTitle: { fontWeight: 'bold' },
331
311
  scrollArea: {
332
312
  marginBottom: 0
@@ -1,6 +1,6 @@
1
1
  import { useAutoPaperTheme } from '@rific/auto-paper'
2
2
  import { Button, IconButton } from '@rific/feedback-press'
3
- import { useRotation } from '@tastic/core'
3
+ import { useRotation, type ViewRotation } from '@tastic/core'
4
4
  import { ReactNode, useState } from 'react'
5
5
  import { ScrollView, StyleSheet, View } from 'react-native'
6
6
  import { Text } from 'react-native-paper'
@@ -20,7 +20,7 @@ interface Props {
20
20
  // read via useRotation() when omitted — note this does NOT make the header/scrollable content
21
21
  // below rotate; the caller's own screen still needs its own FakeLandscapeView wrap for that
22
22
  // (this component has no transform on its own main body).
23
- rotation?: number
23
+ rotation?: ViewRotation
24
24
  title?: string
25
25
  // 'headlineSmall' (this screen's own default, below) — was originally 'displaySmall' (the full
26
26
  // display tier's own smallest step), then 'headlineLarge', both still read as oversized for a
@@ -1,8 +1,11 @@
1
1
  import { useAutoPaperTheme } from '@rific/auto-paper'
2
2
  import { Button } from '@rific/feedback-press'
3
+ import { toRotationStyle, type ViewRotation } from '@tastic/core'
3
4
  import { StyleSheet, View } from 'react-native'
4
5
  import { Icon, Portal, Text } from 'react-native-paper'
5
6
 
7
+ import { getOverlayCardColors, overlayStyles } from './overlayCard'
8
+
6
9
  export interface ConfirmDialogProps {
7
10
  visible: boolean
8
11
  title: string
@@ -11,10 +14,10 @@ export interface ConfirmDialogProps {
11
14
  cancelLabel: string
12
15
  onConfirm: () => void
13
16
  onCancel: () => void
14
- // Live physical-hold rotation (see @tastic/split-screen's getViewRotation) - same convention as
17
+ // Live physical-hold rotation (see @tastic/core's getViewRotation) - same convention as
15
18
  // BaseSettingsDialog's own rotation prop; defaults to 0 for a caller with no per-player zone to
16
19
  // match (a portrait-only or single-player game has nothing to stay consistent with).
17
- rotation?: number
20
+ rotation?: ViewRotation
18
21
  // Any react-native-paper Icon `source` name, shown above the title - defaults to a plain
19
22
  // question mark, since this component has no way to know why a caller is asking.
20
23
  icon?: string
@@ -35,18 +38,14 @@ export function ConfirmDialog({ visible, title, message, confirmLabel, cancelLab
35
38
  const { dark, colors } = useAutoPaperTheme()
36
39
  if (!visible) return null
37
40
 
38
- // High-contrast retro look: literal black/white by appearance, not auto-paper's own (slightly
39
- // tinted) background role - same convention BaseSettingsDialog's own overlay card uses.
40
- const fg = dark ? '#FFFFFF' : '#000000'
41
- const cardBg = dark ? '#111111' : '#F2F2F2'
42
- const cardBorder = dark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.2)'
41
+ const { fg, cardBg, cardBorder } = getOverlayCardColors(dark)
43
42
  const accent = destructive ? colors.danger : colors.primary
44
43
  const onAccent = destructive ? colors.onDanger : colors.onPrimary
45
44
 
46
45
  return (
47
46
  <Portal>
48
- <View style={styles.overlay}>
49
- <View style={[styles.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }]}>
47
+ <View style={[overlayStyles.overlay, styles.overlay]}>
48
+ <View style={[overlayStyles.card, styles.card, { backgroundColor: cardBg, borderColor: cardBorder }, toRotationStyle(rotation)]}>
50
49
  <Icon source={icon} size={64} color={accent} />
51
50
  <Text variant='headlineLarge' style={[styles.title, { color: accent }]}>
52
51
  {title}
@@ -93,32 +92,20 @@ const styles = StyleSheet.create({
93
92
  body: {
94
93
  textAlign: 'center'
95
94
  },
95
+ // Additive on top of overlayCard's own shared `card` shape (borderRadius/borderWidth/gap/maxWidth/
96
+ // padding) — claims its full allowance instead of just maxWidth alone, which only caps how wide the
97
+ // card is allowed to get - without an explicit width, a flex-centered child sizes to its own content
98
+ // instead of stretching, so the card (and therefore actions below) can end up narrower than the
99
+ // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
96
100
  card: {
97
- alignItems: 'center',
98
- borderRadius: 20,
99
- borderWidth: 1,
100
- gap: 16,
101
- maxWidth: 360,
102
- padding: 32,
103
- // Claims its full allowance instead of just maxWidth alone, which only caps how wide the card
104
- // is allowed to get - without an explicit width, a flex-centered child sizes to its own content
105
- // instead of stretching, so the card (and therefore actions below) can end up narrower than the
106
- // screen actually has room for, needlessly forcing the action row to wrap sooner than it has to.
107
101
  width: '100%'
108
102
  },
103
+ // Additive on top of overlayCard's own shared `overlay` backdrop — gives the card room to breathe
104
+ // from the screen edges now that it reliably stretches to fill its full width allowance (see
105
+ // card's own width comment) - without this, width: '100%' would run the card edge-to-edge on a
106
+ // screen narrower than maxWidth's own 360.
109
107
  overlay: {
110
- alignItems: 'center',
111
- backgroundColor: 'rgba(0,0,0,0.72)',
112
- bottom: 0,
113
- justifyContent: 'center',
114
- left: 0,
115
- // Gives the card room to breathe from the screen edges now that it reliably stretches to fill
116
- // its full width allowance (see card's own width comment) - without this, width: '100%' would
117
- // run the card edge-to-edge on a screen narrower than maxWidth's own 360.
118
- paddingHorizontal: 24,
119
- position: 'absolute',
120
- right: 0,
121
- top: 0
108
+ paddingHorizontal: 24
122
109
  },
123
110
  // textAlign is load-bearing, not decorative, once `title` wraps to more than one line - centered
124
111
  // within card's own alignItems: 'center' only centers the Text's block as a whole; each wrapped
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export { MONO_FONT } from './fonts'
10
10
  export { InlineColorPicker } from './InlineColorPicker'
11
11
  export { getLabeledDropdownContentHeight, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption } from './LabeledDropdown'
12
12
  export { loadSkiaWeb } from './loadSkiaWeb'
13
+ export { getOverlayCardColors, type OverlayCardColors, overlayStyles } from './overlayCard'
13
14
  export { PopoverBody } from './PopoverBody'
14
15
  export { PressAwayOverlay } from './PressAwayOverlay'
15
16
  export { ReadyButton } from './ReadyButton'