@tastic/hud 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jay Deaton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @tastic/hud
2
+
3
+ Visual component kit for local-multiplayer React Native games — inline (non-modal) popovers,
4
+ dropdowns, color pickers, gauges, ready buttons, and dialogs. Every interactive piece stays scoped
5
+ to one player's own zone on a shared screen, so it never blocks the rest of the screen the way a
6
+ full-screen modal would.
7
+
8
+ Sibling to [`@tastic/split-screen`](https://github.com/jayrdeaton/react-native-split-screen), the
9
+ two-player layout/orientation engine this kit's components are built to render correctly inside —
10
+ in particular, `PopoverBody`'s positioning survives an ancestor's 180° rotation. Neither package
11
+ depends on the other; they compose at your own screen.
12
+
13
+ ## Why not a modal?
14
+
15
+ A full-screen `Dialog` blocks the whole screen. In a two-player lobby that's a non-starter — player
16
+ 2 can't touch anything while player 1's color picker is open. Every popover here renders **inline**,
17
+ as an absolutely-positioned sibling of its own trigger, so it never blocks anything outside its own
18
+ corner of the screen.
19
+
20
+ ## What's in here
21
+
22
+ - **`usePopoverHost()`** — tracks which *one* popover (by id) is open within a group, so opening one
23
+ closes any sibling already open in the same group.
24
+ - **`PopoverBody`** — the low-level popover shell (position, caret, alignment). `SectionedDropdown`
25
+ and `InlineColorPicker` are built on it.
26
+ - **`useAutoAlign`** — measures a trigger's actual on-screen position and picks whichever alignment
27
+ (left/right/center, above/below) keeps a popover from overflowing a screen edge — including a
28
+ `maxHeight` for the caller to cap content and scroll when neither direction has enough room.
29
+ - **`SectionedDropdown`** — a popover holding any mix of single-select ("pick one", radio-style) and
30
+ multi-select ("pick some", checkbox-style, optional all/clear footer) sections, divided by rules.
31
+ - **`InlineColorPicker`** — swatch-grid color popover, auto-sized columns, with an optional "taken"
32
+ color (shown disabled, or swappable).
33
+ - **`TriggerGauge`** — decorative ring of tick marks around a trigger, showing which option(s) are
34
+ active without opening the popover.
35
+ - **`ReadyButton`** — a per-player or standalone ready toggle.
36
+ - **`PressAwayOverlay`** — an invisible full-bleed tap-catcher for press-away-to-close. This is the
37
+ one piece that needs care in a split-screen layout — see below.
38
+
39
+ ## The press-away pattern (read this before wiring it up)
40
+
41
+ A single full-screen `PressAwayOverlay` works fine for a one-player screen. It does **not** work
42
+ for a two-player split screen: if player 1's press-away covers the whole screen, then any tap on
43
+ player 2's side — even one that has nothing to do with player 1 — falls through and closes player
44
+ 1's popover. That defeats the point of letting both players drive their own settings at once.
45
+
46
+ The fix is two overlays with an asymmetric relationship, not two independent halves:
47
+
48
+ ```tsx
49
+ const p1Host = usePopoverHost()
50
+ const p2Host = usePopoverHost()
51
+
52
+ return (
53
+ <View style={styles.container}>
54
+ {/* Player 1's overlay covers the ENTIRE screen. This is correct even though player 1 visually
55
+ only owns "their side" — any shared-settings row that reads as "player 1's" (see your own
56
+ layout) may not stay confined to a literal half of the screen, and this overlay needs to cover
57
+ everywhere player 1 might have something open. */}
58
+ <PressAwayOverlay active={p1Host.openId !== null} onPress={p1Host.close} />
59
+
60
+ {/* Player 2's overlay is scoped to just their own zone (half the screen, whichever side
61
+ they're on — see @tastic/split-screen's panelLayout for how to size this) — and, being a later
62
+ sibling, it paints on top of player 1's overlay within that rect, so a tap there is always
63
+ player 2's to own.
64
+
65
+ Critically: this has to mount whenever EITHER host is open, not just p2Host. If it only mounted
66
+ for p2Host, then closing player 2's popover would unmount it — exposing player 1's full-screen
67
+ overlay underneath for the rest of that render, and the next tap anywhere on player 2's side
68
+ (even if player 2 has nothing open) would fall through and close player 1's popover instead.
69
+ Mounting it any time p1Host is open too "shields" player 2's zone from player 1's overlay
70
+ unconditionally; its onPress (p2Host.close) is just a harmless no-op when p2Host is already
71
+ closed. */}
72
+ <PressAwayOverlay active={p1Host.openId !== null || p2Host.openId !== null} onPress={p2Host.close} style={styles.p2Zone} />
73
+
74
+ {/* Real content goes after both overlays — plain paint order (later siblings on top) is what
75
+ keeps every real trigger/button directly tappable; only genuinely empty space falls through to
76
+ the overlays above. */}
77
+ <Player1Panel host={p1Host} />
78
+ <Player2Panel host={p2Host} style={styles.p2Zone} />
79
+ </View>
80
+ )
81
+ ```
82
+
83
+ The general rule for N players: the "owner" of the broadest zone (usually whoever's panel absorbs
84
+ your shared/global settings) gets the whole-screen overlay; every other player's zone-scoped overlay
85
+ must stay mounted (shielding, if not actually closing anything) whenever *any* host with broader
86
+ reach is open — not just their own.
87
+
88
+ ## Install (local dev via yalc)
89
+
90
+ Not published to the public npm registry yet.
91
+
92
+ ```bash
93
+ cd react-native-hud
94
+ npm run build
95
+ yalc publish
96
+
97
+ cd ../your-game
98
+ yalc add @tastic/hud
99
+ npm install
100
+ ```
101
+
102
+ Re-run `npm run build && yalc push` from this package after any change to propagate it to every
103
+ linked consumer at once.
104
+
105
+ ## Peer dependencies
106
+
107
+ `react`, `react-native`, `react-native-paper` (`Icon`, `IconButton`, `Text`), `@rific/auto-paper`
108
+ (`defaultColors`, `getContrastColor`, `getBlendedColor`, `SeedColor`), `@rific/feedback-press`
109
+ (`IconButton`, `TouchableRipple`) — none of these are bundled, so use whatever versions your app
110
+ already has.
@@ -0,0 +1,117 @@
1
+ // src/TriggerGauge.tsx
2
+ import { Canvas, Path, Skia } from "@shopify/react-native-skia";
3
+ import { useCallback, useLayoutEffect, useRef, useState } from "react";
4
+ import { StyleSheet, View } from "react-native";
5
+ import { Easing, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ var UNLIT_THICKNESS = 3;
8
+ var LIT_THICKNESS = 7;
9
+ var DASH_GAP_PX = 9;
10
+ var ARC_GAP = 2;
11
+ var DEFAULT_START_DEG = 180;
12
+ var MIN_DASH_ANGLE_DEG = 4;
13
+ var MOUNT_DURATION_MS = 450;
14
+ var MOUNT_STAGGER_MS = 40;
15
+ var SELECTION_TRANSITION_DURATION_MS = 260;
16
+ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, dashWidth, startDeg = DEFAULT_START_DEG, endDeg = startDeg }) {
17
+ const totalDurationMs = MOUNT_DURATION_MS + Math.max(0, segments - 1) * MOUNT_STAGGER_MS;
18
+ const mountProgress = useSharedValue(0);
19
+ useLayoutEffect(() => {
20
+ mountProgress.value = withTiming(1, { duration: totalDurationMs, easing: Easing.out(Easing.cubic) });
21
+ }, []);
22
+ const rawSweepDeg = ((endDeg - startDeg) % 360 + 360) % 360;
23
+ const totalSweepDeg = rawSweepDeg === 0 ? 360 : rawSweepDeg;
24
+ const step = segments > 0 ? totalSweepDeg / segments : 0;
25
+ const centerDeg = useCallback((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
26
+ const singleSelect = litIndices.length === 1;
27
+ const angleDeg = useSharedValue(centerDeg(litIndices[0] ?? 0));
28
+ const litIndicesKey = [...litIndices].sort((a, b) => a - b).join(",");
29
+ const [transitionFrom, setTransitionFrom] = useState(litIndices);
30
+ const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
31
+ const lastKeyRef = useRef(litIndicesKey);
32
+ const lastIndicesRef = useRef(litIndices);
33
+ const selectionProgress = useSharedValue(1);
34
+ useLayoutEffect(() => {
35
+ if (litIndicesKey !== lastKeyRef.current) {
36
+ if (singleSelect && lastIndicesRef.current.length === 1) {
37
+ angleDeg.value = withTiming(centerDeg(litIndices[0]), { duration: SELECTION_TRANSITION_DURATION_MS, easing: Easing.out(Easing.cubic) });
38
+ } else {
39
+ setTransitionFrom(lastIndicesRef.current);
40
+ selectionProgress.value = 0;
41
+ selectionProgress.value = withTiming(1, { duration: SELECTION_TRANSITION_DURATION_MS, easing: Easing.out(Easing.cubic) });
42
+ }
43
+ lastKeyRef.current = litIndicesKey;
44
+ }
45
+ lastIndicesRef.current = litIndices;
46
+ }, [litIndicesKey, litIndices, selectionProgress, angleDeg, singleSelect, centerDeg]);
47
+ const radius = size / 2 + ARC_GAP;
48
+ const slotDeg = step;
49
+ const gapDeg = DASH_GAP_PX / radius * (180 / Math.PI);
50
+ const autoDashAngleDeg = Math.max(MIN_DASH_ANGLE_DEG, slotDeg - gapDeg);
51
+ const dashAngleDeg = dashWidth != null ? Math.max(MIN_DASH_ANGLE_DEG, dashWidth / radius * (180 / Math.PI)) : autoDashAngleDeg;
52
+ const diameter = radius * 2;
53
+ const canvasSize = diameter + LIT_THICKNESS;
54
+ const oval = Skia.XYWHRect(LIT_THICKNESS / 2, LIT_THICKNESS / 2, diameter, diameter);
55
+ const unlitPath = useDerivedValue(() => {
56
+ const path = Skia.Path.Make();
57
+ for (let i = 0; i < segments; i++) {
58
+ if (litIndices.includes(i)) continue;
59
+ let local = 1;
60
+ if (mountProgress.value < 1) {
61
+ const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
62
+ const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
63
+ local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)));
64
+ }
65
+ const sweep = dashAngleDeg * local;
66
+ if (sweep <= 0) continue;
67
+ const dashCenterDeg = startDeg + (i + 0.5) * step;
68
+ path.addArc(oval, dashCenterDeg - 90 - sweep / 2, sweep);
69
+ }
70
+ return path;
71
+ }, [segments, litIndicesKey, step, startDeg, dashAngleDeg, totalDurationMs, oval.x, oval.y, oval.width, oval.height]);
72
+ const litPath = useDerivedValue(() => {
73
+ const path = Skia.Path.Make();
74
+ if (mountProgress.value >= 1 && singleSelect) {
75
+ path.addArc(oval, angleDeg.value - 90 - dashAngleDeg / 2, dashAngleDeg);
76
+ return path;
77
+ }
78
+ for (let i = 0; i < segments; i++) {
79
+ const isLitNow = litIndices.includes(i);
80
+ const wasLit = transitionFrom.includes(i);
81
+ if (!isLitNow && !wasLit) continue;
82
+ let local;
83
+ if (mountProgress.value < 1) {
84
+ if (!isLitNow) continue;
85
+ const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
86
+ const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
87
+ local = Math.min(1, Math.max(0, (mountProgress.value - startFrac) / (endFrac - startFrac)));
88
+ } else if (isLitNow === wasLit) {
89
+ local = 1;
90
+ } else if (isLitNow) {
91
+ local = selectionProgress.value;
92
+ } else {
93
+ local = 1 - selectionProgress.value;
94
+ }
95
+ const sweep = dashAngleDeg * local;
96
+ if (sweep <= 0) continue;
97
+ const dashCenterDeg = startDeg + (i + 0.5) * step;
98
+ path.addArc(oval, dashCenterDeg - 90 - sweep / 2, sweep);
99
+ }
100
+ return path;
101
+ }, [segments, litIndicesKey, transitionFromKey, singleSelect, step, startDeg, dashAngleDeg, totalDurationMs, oval.x, oval.y, oval.width, oval.height]);
102
+ if (segments < 2) return null;
103
+ return /* @__PURE__ */ jsx(View, { style: [styles.container, { height: size, width: size }], pointerEvents: "none", children: /* @__PURE__ */ jsxs(Canvas, { style: { height: canvasSize, width: canvasSize }, children: [
104
+ /* @__PURE__ */ jsx(Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
105
+ /* @__PURE__ */ jsx(Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
106
+ ] }) });
107
+ }
108
+ var styles = StyleSheet.create({
109
+ container: {
110
+ alignItems: "center",
111
+ justifyContent: "center",
112
+ position: "absolute"
113
+ }
114
+ });
115
+ export {
116
+ TriggerGauge
117
+ };
@@ -0,0 +1,127 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { SeedColor } from '@rific/auto-paper';
4
+ import { StyleProp, ViewStyle, View } from 'react-native';
5
+
6
+ declare const MONO_FONT: string;
7
+
8
+ interface PopoverHost {
9
+ openId: string | null;
10
+ toggle: (id: string) => void;
11
+ close: () => void;
12
+ }
13
+ declare function usePopoverHost(): PopoverHost;
14
+
15
+ interface Props$4 {
16
+ id: string;
17
+ host: PopoverHost;
18
+ value: string;
19
+ onChange: (hex: string) => void;
20
+ swatches?: SeedColor[];
21
+ takenValue?: string;
22
+ allowSwapTaken?: boolean;
23
+ dark: boolean;
24
+ align?: 'left' | 'right' | 'center';
25
+ icon?: string;
26
+ autoDismiss?: boolean;
27
+ columns?: number;
28
+ }
29
+ declare function InlineColorPicker({ id, host, value, onChange, swatches, takenValue, allowSwapTaken, dark, align: alignOverride, icon, autoDismiss, columns }: Props$4): react.JSX.Element;
30
+
31
+ declare function loadSkiaWeb(): Promise<void>;
32
+
33
+ interface Props$3 {
34
+ visible: boolean;
35
+ children: ReactNode;
36
+ align?: 'left' | 'right' | 'center';
37
+ verticalAlign?: 'below' | 'above';
38
+ caretColor?: string;
39
+ caretBorderColor?: string;
40
+ caretBorderWidth?: number;
41
+ triggerSize?: number;
42
+ }
43
+ declare function PopoverBody({ visible, children, align, verticalAlign, caretColor, caretBorderColor, caretBorderWidth, triggerSize }: Props$3): react.JSX.Element | null;
44
+
45
+ interface Props$2 {
46
+ active: boolean;
47
+ onPress: () => void;
48
+ style?: StyleProp<ViewStyle>;
49
+ }
50
+ declare function PressAwayOverlay({ active, onPress, style }: Props$2): react.JSX.Element | null;
51
+
52
+ interface Props$1 {
53
+ color: string;
54
+ ready: boolean;
55
+ onToggleReady: () => void;
56
+ style?: StyleProp<ViewStyle>;
57
+ labelFontFamily?: string;
58
+ }
59
+ declare function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily }: Props$1): react.JSX.Element;
60
+
61
+ interface MenuOption<T extends string | number> {
62
+ value: T;
63
+ label: string;
64
+ description?: string;
65
+ icon?: string;
66
+ iconSize?: number;
67
+ }
68
+ interface SingleSelectSection<T extends string | number> {
69
+ kind: 'single';
70
+ id: string;
71
+ options: MenuOption<T>[];
72
+ value: T;
73
+ onChange: (value: T) => void;
74
+ }
75
+ interface MultiSelectSection<T extends string | number> {
76
+ kind: 'multi';
77
+ id: string;
78
+ options: MenuOption<T>[];
79
+ value: T[];
80
+ onChange: (value: T[]) => void;
81
+ allClear?: boolean;
82
+ }
83
+ type MenuSection = SingleSelectSection<any> | MultiSelectSection<any>;
84
+ interface Props {
85
+ id: string;
86
+ host: PopoverHost;
87
+ icon: string;
88
+ accessibilityLabel: string;
89
+ sections: MenuSection[];
90
+ accentColor: string;
91
+ mutedColor: string;
92
+ onAccentColor?: string;
93
+ dark: boolean;
94
+ align?: 'left' | 'right' | 'center';
95
+ autoDismiss?: boolean;
96
+ labelFontFamily?: string;
97
+ allClearLabels?: {
98
+ all: string;
99
+ clear: string;
100
+ };
101
+ }
102
+ declare function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss, labelFontFamily, allClearLabels }: Props): react.JSX.Element;
103
+
104
+ interface TriggerGaugeProps {
105
+ segments: number;
106
+ litIndices: number[];
107
+ size: number;
108
+ accentColor: string;
109
+ mutedColor: string;
110
+ dashWidth?: number;
111
+ startDeg?: number;
112
+ endDeg?: number;
113
+ }
114
+
115
+ declare function TriggerGaugeHost(props: TriggerGaugeProps): react.JSX.Element;
116
+
117
+ type PopoverAlign = 'left' | 'right' | 'center';
118
+ type PopoverVerticalAlign = 'below' | 'above';
119
+ declare function useAutoAlign(open: boolean, contentWidth: number, contentHeight: number): {
120
+ align: PopoverAlign;
121
+ maxHeight: number;
122
+ measured: boolean;
123
+ triggerRef: react.RefObject<View | null>;
124
+ verticalAlign: PopoverVerticalAlign;
125
+ };
126
+
127
+ export { InlineColorPicker, MONO_FONT, type MenuOption, type MenuSection, type MultiSelectSection, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverVerticalAlign, PressAwayOverlay, ReadyButton, SectionedDropdown, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, loadSkiaWeb, useAutoAlign, usePopoverHost };
@@ -0,0 +1,127 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { SeedColor } from '@rific/auto-paper';
4
+ import { StyleProp, ViewStyle, View } from 'react-native';
5
+
6
+ declare const MONO_FONT: string;
7
+
8
+ interface PopoverHost {
9
+ openId: string | null;
10
+ toggle: (id: string) => void;
11
+ close: () => void;
12
+ }
13
+ declare function usePopoverHost(): PopoverHost;
14
+
15
+ interface Props$4 {
16
+ id: string;
17
+ host: PopoverHost;
18
+ value: string;
19
+ onChange: (hex: string) => void;
20
+ swatches?: SeedColor[];
21
+ takenValue?: string;
22
+ allowSwapTaken?: boolean;
23
+ dark: boolean;
24
+ align?: 'left' | 'right' | 'center';
25
+ icon?: string;
26
+ autoDismiss?: boolean;
27
+ columns?: number;
28
+ }
29
+ declare function InlineColorPicker({ id, host, value, onChange, swatches, takenValue, allowSwapTaken, dark, align: alignOverride, icon, autoDismiss, columns }: Props$4): react.JSX.Element;
30
+
31
+ declare function loadSkiaWeb(): Promise<void>;
32
+
33
+ interface Props$3 {
34
+ visible: boolean;
35
+ children: ReactNode;
36
+ align?: 'left' | 'right' | 'center';
37
+ verticalAlign?: 'below' | 'above';
38
+ caretColor?: string;
39
+ caretBorderColor?: string;
40
+ caretBorderWidth?: number;
41
+ triggerSize?: number;
42
+ }
43
+ declare function PopoverBody({ visible, children, align, verticalAlign, caretColor, caretBorderColor, caretBorderWidth, triggerSize }: Props$3): react.JSX.Element | null;
44
+
45
+ interface Props$2 {
46
+ active: boolean;
47
+ onPress: () => void;
48
+ style?: StyleProp<ViewStyle>;
49
+ }
50
+ declare function PressAwayOverlay({ active, onPress, style }: Props$2): react.JSX.Element | null;
51
+
52
+ interface Props$1 {
53
+ color: string;
54
+ ready: boolean;
55
+ onToggleReady: () => void;
56
+ style?: StyleProp<ViewStyle>;
57
+ labelFontFamily?: string;
58
+ }
59
+ declare function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily }: Props$1): react.JSX.Element;
60
+
61
+ interface MenuOption<T extends string | number> {
62
+ value: T;
63
+ label: string;
64
+ description?: string;
65
+ icon?: string;
66
+ iconSize?: number;
67
+ }
68
+ interface SingleSelectSection<T extends string | number> {
69
+ kind: 'single';
70
+ id: string;
71
+ options: MenuOption<T>[];
72
+ value: T;
73
+ onChange: (value: T) => void;
74
+ }
75
+ interface MultiSelectSection<T extends string | number> {
76
+ kind: 'multi';
77
+ id: string;
78
+ options: MenuOption<T>[];
79
+ value: T[];
80
+ onChange: (value: T[]) => void;
81
+ allClear?: boolean;
82
+ }
83
+ type MenuSection = SingleSelectSection<any> | MultiSelectSection<any>;
84
+ interface Props {
85
+ id: string;
86
+ host: PopoverHost;
87
+ icon: string;
88
+ accessibilityLabel: string;
89
+ sections: MenuSection[];
90
+ accentColor: string;
91
+ mutedColor: string;
92
+ onAccentColor?: string;
93
+ dark: boolean;
94
+ align?: 'left' | 'right' | 'center';
95
+ autoDismiss?: boolean;
96
+ labelFontFamily?: string;
97
+ allClearLabels?: {
98
+ all: string;
99
+ clear: string;
100
+ };
101
+ }
102
+ declare function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss, labelFontFamily, allClearLabels }: Props): react.JSX.Element;
103
+
104
+ interface TriggerGaugeProps {
105
+ segments: number;
106
+ litIndices: number[];
107
+ size: number;
108
+ accentColor: string;
109
+ mutedColor: string;
110
+ dashWidth?: number;
111
+ startDeg?: number;
112
+ endDeg?: number;
113
+ }
114
+
115
+ declare function TriggerGaugeHost(props: TriggerGaugeProps): react.JSX.Element;
116
+
117
+ type PopoverAlign = 'left' | 'right' | 'center';
118
+ type PopoverVerticalAlign = 'below' | 'above';
119
+ declare function useAutoAlign(open: boolean, contentWidth: number, contentHeight: number): {
120
+ align: PopoverAlign;
121
+ maxHeight: number;
122
+ measured: boolean;
123
+ triggerRef: react.RefObject<View | null>;
124
+ verticalAlign: PopoverVerticalAlign;
125
+ };
126
+
127
+ export { InlineColorPicker, MONO_FONT, type MenuOption, type MenuSection, type MultiSelectSection, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverVerticalAlign, PressAwayOverlay, ReadyButton, SectionedDropdown, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, loadSkiaWeb, useAutoAlign, usePopoverHost };