@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 +21 -0
- package/README.md +110 -0
- package/dist/TriggerGauge-HIPRGEWJ.mjs +117 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +649 -0
- package/dist/index.mjs +475 -0
- package/package.json +103 -0
- package/src/InlineColorPicker.tsx +183 -0
- package/src/PopoverBody.tsx +131 -0
- package/src/PressAwayOverlay.tsx +20 -0
- package/src/ReadyButton.tsx +43 -0
- package/src/SectionedDropdown.tsx +387 -0
- package/src/TriggerGauge.tsx +285 -0
- package/src/TriggerGaugeHost.tsx +27 -0
- package/src/fonts.ts +6 -0
- package/src/index.ts +18 -0
- package/src/loadSkiaWeb.ts +14 -0
- package/src/useAutoAlign.ts +88 -0
- package/src/usePopoverHost.ts +24 -0
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
|
+
};
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|