react-native-element-inspector 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.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/lib/ElementCycler.d.ts +14 -0
  4. package/lib/ElementCycler.d.ts.map +1 -0
  5. package/lib/ElementCycler.js +33 -0
  6. package/lib/ElementCycler.js.map +1 -0
  7. package/lib/ElementHighlighter.d.ts +21 -0
  8. package/lib/ElementHighlighter.d.ts.map +1 -0
  9. package/lib/ElementHighlighter.js +89 -0
  10. package/lib/ElementHighlighter.js.map +1 -0
  11. package/lib/ElementInspector.d.ts +12 -0
  12. package/lib/ElementInspector.d.ts.map +1 -0
  13. package/lib/ElementInspector.js +74 -0
  14. package/lib/ElementInspector.js.map +1 -0
  15. package/lib/components/Checkbox.d.ts +7 -0
  16. package/lib/components/Checkbox.d.ts.map +1 -0
  17. package/lib/components/Checkbox.js +30 -0
  18. package/lib/components/Checkbox.js.map +1 -0
  19. package/lib/components/index.d.ts +2 -0
  20. package/lib/components/index.d.ts.map +1 -0
  21. package/lib/components/index.js +2 -0
  22. package/lib/components/index.js.map +1 -0
  23. package/lib/constants/colors.d.ts +24 -0
  24. package/lib/constants/colors.d.ts.map +1 -0
  25. package/lib/constants/colors.js +18 -0
  26. package/lib/constants/colors.js.map +1 -0
  27. package/lib/constants/index.d.ts +3 -0
  28. package/lib/constants/index.d.ts.map +1 -0
  29. package/lib/constants/index.js +3 -0
  30. package/lib/constants/index.js.map +1 -0
  31. package/lib/constants/ui.d.ts +56 -0
  32. package/lib/constants/ui.d.ts.map +1 -0
  33. package/lib/constants/ui.js +46 -0
  34. package/lib/constants/ui.js.map +1 -0
  35. package/lib/fiber/FiberAdapter.d.ts +52 -0
  36. package/lib/fiber/FiberAdapter.d.ts.map +1 -0
  37. package/lib/fiber/FiberAdapter.js +112 -0
  38. package/lib/fiber/FiberAdapter.js.map +1 -0
  39. package/lib/fiber/detectVersion.d.ts +9 -0
  40. package/lib/fiber/detectVersion.d.ts.map +1 -0
  41. package/lib/fiber/detectVersion.js +17 -0
  42. package/lib/fiber/detectVersion.js.map +1 -0
  43. package/lib/fiber/index.d.ts +5 -0
  44. package/lib/fiber/index.d.ts.map +1 -0
  45. package/lib/fiber/index.js +4 -0
  46. package/lib/fiber/index.js.map +1 -0
  47. package/lib/fiber/types.d.ts +35 -0
  48. package/lib/fiber/types.d.ts.map +1 -0
  49. package/lib/fiber/types.js +3 -0
  50. package/lib/fiber/types.js.map +1 -0
  51. package/lib/floatingPanel/AddPropertyRow.d.ts +13 -0
  52. package/lib/floatingPanel/AddPropertyRow.d.ts.map +1 -0
  53. package/lib/floatingPanel/AddPropertyRow.js +61 -0
  54. package/lib/floatingPanel/AddPropertyRow.js.map +1 -0
  55. package/lib/floatingPanel/CloseButton.d.ts +7 -0
  56. package/lib/floatingPanel/CloseButton.d.ts.map +1 -0
  57. package/lib/floatingPanel/CloseButton.js +21 -0
  58. package/lib/floatingPanel/CloseButton.js.map +1 -0
  59. package/lib/floatingPanel/EditableValue.d.ts +12 -0
  60. package/lib/floatingPanel/EditableValue.d.ts.map +1 -0
  61. package/lib/floatingPanel/EditableValue.js +67 -0
  62. package/lib/floatingPanel/EditableValue.js.map +1 -0
  63. package/lib/floatingPanel/FloatingPanel.d.ts +7 -0
  64. package/lib/floatingPanel/FloatingPanel.d.ts.map +1 -0
  65. package/lib/floatingPanel/FloatingPanel.js +70 -0
  66. package/lib/floatingPanel/FloatingPanel.js.map +1 -0
  67. package/lib/floatingPanel/HandleContent.d.ts +7 -0
  68. package/lib/floatingPanel/HandleContent.d.ts.map +1 -0
  69. package/lib/floatingPanel/HandleContent.js +35 -0
  70. package/lib/floatingPanel/HandleContent.js.map +1 -0
  71. package/lib/floatingPanel/InspectorBubble.d.ts +6 -0
  72. package/lib/floatingPanel/InspectorBubble.d.ts.map +1 -0
  73. package/lib/floatingPanel/InspectorBubble.js +91 -0
  74. package/lib/floatingPanel/InspectorBubble.js.map +1 -0
  75. package/lib/floatingPanel/PanelBody.d.ts +8 -0
  76. package/lib/floatingPanel/PanelBody.d.ts.map +1 -0
  77. package/lib/floatingPanel/PanelBody.js +72 -0
  78. package/lib/floatingPanel/PanelBody.js.map +1 -0
  79. package/lib/floatingPanel/PanelFooter.d.ts +9 -0
  80. package/lib/floatingPanel/PanelFooter.d.ts.map +1 -0
  81. package/lib/floatingPanel/PanelFooter.js +21 -0
  82. package/lib/floatingPanel/PanelFooter.js.map +1 -0
  83. package/lib/floatingPanel/PanelHeader.d.ts +13 -0
  84. package/lib/floatingPanel/PanelHeader.d.ts.map +1 -0
  85. package/lib/floatingPanel/PanelHeader.js +56 -0
  86. package/lib/floatingPanel/PanelHeader.js.map +1 -0
  87. package/lib/floatingPanel/index.d.ts +12 -0
  88. package/lib/floatingPanel/index.d.ts.map +1 -0
  89. package/lib/floatingPanel/index.js +11 -0
  90. package/lib/floatingPanel/index.js.map +1 -0
  91. package/lib/floatingPanel/panelUtils.d.ts +3 -0
  92. package/lib/floatingPanel/panelUtils.d.ts.map +1 -0
  93. package/lib/floatingPanel/panelUtils.js +12 -0
  94. package/lib/floatingPanel/panelUtils.js.map +1 -0
  95. package/lib/floatingPanel/types.d.ts +23 -0
  96. package/lib/floatingPanel/types.d.ts.map +1 -0
  97. package/lib/floatingPanel/types.js +2 -0
  98. package/lib/floatingPanel/types.js.map +1 -0
  99. package/lib/hooks/index.d.ts +8 -0
  100. package/lib/hooks/index.d.ts.map +1 -0
  101. package/lib/hooks/index.js +8 -0
  102. package/lib/hooks/index.js.map +1 -0
  103. package/lib/hooks/useDebouncedCallback.d.ts +2 -0
  104. package/lib/hooks/useDebouncedCallback.d.ts.map +1 -0
  105. package/lib/hooks/useDebouncedCallback.js +12 -0
  106. package/lib/hooks/useDebouncedCallback.js.map +1 -0
  107. package/lib/hooks/useDebouncedValue.d.ts +2 -0
  108. package/lib/hooks/useDebouncedValue.d.ts.map +1 -0
  109. package/lib/hooks/useDebouncedValue.js +10 -0
  110. package/lib/hooks/useDebouncedValue.js.map +1 -0
  111. package/lib/hooks/useFloatingPanel.d.ts +14 -0
  112. package/lib/hooks/useFloatingPanel.d.ts.map +1 -0
  113. package/lib/hooks/useFloatingPanel.js +132 -0
  114. package/lib/hooks/useFloatingPanel.js.map +1 -0
  115. package/lib/hooks/useLayoutSnapshot.d.ts +14 -0
  116. package/lib/hooks/useLayoutSnapshot.d.ts.map +1 -0
  117. package/lib/hooks/useLayoutSnapshot.js +39 -0
  118. package/lib/hooks/useLayoutSnapshot.js.map +1 -0
  119. package/lib/hooks/useStyleMutation.d.ts +14 -0
  120. package/lib/hooks/useStyleMutation.d.ts.map +1 -0
  121. package/lib/hooks/useStyleMutation.js +22 -0
  122. package/lib/hooks/useStyleMutation.js.map +1 -0
  123. package/lib/hooks/useStyleOverrides.d.ts +24 -0
  124. package/lib/hooks/useStyleOverrides.d.ts.map +1 -0
  125. package/lib/hooks/useStyleOverrides.js +165 -0
  126. package/lib/hooks/useStyleOverrides.js.map +1 -0
  127. package/lib/hooks/useTapToSelect.d.ts +20 -0
  128. package/lib/hooks/useTapToSelect.d.ts.map +1 -0
  129. package/lib/hooks/useTapToSelect.js +58 -0
  130. package/lib/hooks/useTapToSelect.js.map +1 -0
  131. package/lib/index.d.ts +3 -0
  132. package/lib/index.d.ts.map +1 -0
  133. package/lib/index.js +2 -0
  134. package/lib/index.js.map +1 -0
  135. package/lib/utils/clamp.d.ts +2 -0
  136. package/lib/utils/clamp.d.ts.map +1 -0
  137. package/lib/utils/clamp.js +2 -0
  138. package/lib/utils/clamp.js.map +1 -0
  139. package/lib/utils/flattenStyles.d.ts +6 -0
  140. package/lib/utils/flattenStyles.d.ts.map +1 -0
  141. package/lib/utils/flattenStyles.js +11 -0
  142. package/lib/utils/flattenStyles.js.map +1 -0
  143. package/lib/utils/hitTest.d.ts +7 -0
  144. package/lib/utils/hitTest.d.ts.map +1 -0
  145. package/lib/utils/hitTest.js +20 -0
  146. package/lib/utils/hitTest.js.map +1 -0
  147. package/lib/utils/index.d.ts +12 -0
  148. package/lib/utils/index.d.ts.map +1 -0
  149. package/lib/utils/index.js +9 -0
  150. package/lib/utils/index.js.map +1 -0
  151. package/lib/utils/layoutSnapshot.d.ts +7 -0
  152. package/lib/utils/layoutSnapshot.d.ts.map +1 -0
  153. package/lib/utils/layoutSnapshot.js +42 -0
  154. package/lib/utils/layoutSnapshot.js.map +1 -0
  155. package/lib/utils/sourceMapping.d.ts +26 -0
  156. package/lib/utils/sourceMapping.d.ts.map +1 -0
  157. package/lib/utils/sourceMapping.js +53 -0
  158. package/lib/utils/sourceMapping.js.map +1 -0
  159. package/lib/utils/styleFormatting.d.ts +5 -0
  160. package/lib/utils/styleFormatting.d.ts.map +1 -0
  161. package/lib/utils/styleFormatting.js +38 -0
  162. package/lib/utils/styleFormatting.js.map +1 -0
  163. package/lib/utils/styleInputParsing.d.ts +5 -0
  164. package/lib/utils/styleInputParsing.d.ts.map +1 -0
  165. package/lib/utils/styleInputParsing.js +33 -0
  166. package/lib/utils/styleInputParsing.js.map +1 -0
  167. package/lib/utils/yogaLayout.d.ts +33 -0
  168. package/lib/utils/yogaLayout.d.ts.map +1 -0
  169. package/lib/utils/yogaLayout.js +33 -0
  170. package/lib/utils/yogaLayout.js.map +1 -0
  171. package/package.json +74 -0
  172. package/src/ElementCycler.tsx +64 -0
  173. package/src/ElementHighlighter.tsx +122 -0
  174. package/src/ElementInspector.tsx +119 -0
  175. package/src/components/Checkbox.tsx +41 -0
  176. package/src/components/index.ts +1 -0
  177. package/src/constants/colors.ts +18 -0
  178. package/src/constants/index.ts +9 -0
  179. package/src/constants/ui.ts +51 -0
  180. package/src/fiber/FiberAdapter.ts +153 -0
  181. package/src/fiber/detectVersion.ts +19 -0
  182. package/src/fiber/index.ts +4 -0
  183. package/src/fiber/types.ts +36 -0
  184. package/src/floatingPanel/AddPropertyRow.tsx +102 -0
  185. package/src/floatingPanel/CloseButton.tsx +34 -0
  186. package/src/floatingPanel/EditableValue.tsx +109 -0
  187. package/src/floatingPanel/FloatingPanel.tsx +114 -0
  188. package/src/floatingPanel/HandleContent.tsx +45 -0
  189. package/src/floatingPanel/InspectorBubble.tsx +121 -0
  190. package/src/floatingPanel/PanelBody.tsx +162 -0
  191. package/src/floatingPanel/PanelFooter.tsx +36 -0
  192. package/src/floatingPanel/PanelHeader.tsx +111 -0
  193. package/src/floatingPanel/index.ts +11 -0
  194. package/src/floatingPanel/panelUtils.ts +13 -0
  195. package/src/floatingPanel/types.ts +26 -0
  196. package/src/hooks/index.ts +7 -0
  197. package/src/hooks/useDebouncedCallback.ts +18 -0
  198. package/src/hooks/useDebouncedValue.ts +12 -0
  199. package/src/hooks/useFloatingPanel.ts +191 -0
  200. package/src/hooks/useLayoutSnapshot.ts +42 -0
  201. package/src/hooks/useStyleMutation.ts +31 -0
  202. package/src/hooks/useStyleOverrides.ts +176 -0
  203. package/src/hooks/useTapToSelect.ts +76 -0
  204. package/src/index.ts +2 -0
  205. package/src/utils/clamp.ts +2 -0
  206. package/src/utils/flattenStyles.ts +12 -0
  207. package/src/utils/hitTest.ts +29 -0
  208. package/src/utils/index.ts +11 -0
  209. package/src/utils/layoutSnapshot.ts +48 -0
  210. package/src/utils/sourceMapping.ts +67 -0
  211. package/src/utils/styleFormatting.ts +34 -0
  212. package/src/utils/styleInputParsing.ts +33 -0
  213. package/src/utils/yogaLayout.ts +49 -0
@@ -0,0 +1,109 @@
1
+ import { useRef, useState } from 'react';
2
+ import { Pressable, StyleSheet, Text, TextInput } from 'react-native';
3
+ import { EDITABLE_VALUE, EDITABLE_VALUE_COLORS, MONOSPACE_FONT } from '../constants';
4
+ import { parseInput, toEditableString } from '../utils';
5
+
6
+ interface EditableValueProps {
7
+ value: unknown;
8
+ displayValue: string;
9
+ onSubmit: (parsed: unknown) => void;
10
+ variant: 'key' | 'value';
11
+ disabled?: boolean;
12
+ initialEditing?: boolean;
13
+ }
14
+
15
+ /** Tappable style value that becomes an inline TextInput on press. */
16
+ export const EditableValue = ({
17
+ value,
18
+ displayValue,
19
+ onSubmit,
20
+ variant,
21
+ disabled,
22
+ initialEditing = false,
23
+ }: EditableValueProps) => {
24
+ const colors = EDITABLE_VALUE_COLORS[variant];
25
+ const [editing, setEditing] = useState(initialEditing);
26
+ const [draft, setDraft] = useState('');
27
+ const [invalid, setInvalid] = useState(false);
28
+ const committedRef = useRef(false);
29
+
30
+ const startEditing = () => {
31
+ if (disabled) return;
32
+ committedRef.current = false;
33
+ setDraft(toEditableString(value));
34
+ setEditing(true);
35
+ };
36
+
37
+ const commitEdit = () => {
38
+ // Guard against double-fire from onSubmitEditing + onBlur
39
+ if (committedRef.current) return;
40
+ committedRef.current = true;
41
+
42
+ const trimmed = draft.trim();
43
+ const changed = trimmed !== '' && trimmed !== toEditableString(value);
44
+
45
+ if (changed) {
46
+ const isInvalidKey = variant === 'key' && !EDITABLE_VALUE.VALID_STYLE_KEY.test(trimmed);
47
+ const isTooLong = trimmed.length > EDITABLE_VALUE.MAX_VALUE_LENGTH;
48
+
49
+ if (isInvalidKey || isTooLong) {
50
+ setInvalid(true);
51
+ setTimeout(() => setEditing(false), 300);
52
+ return;
53
+ }
54
+
55
+ const parsed = parseInput(trimmed, value);
56
+ onSubmit(parsed);
57
+ }
58
+
59
+ setEditing(false);
60
+ };
61
+
62
+ if (editing) {
63
+ return (
64
+ <TextInput
65
+ style={[
66
+ styles.base,
67
+ styles.editing,
68
+ { color: colors.text, borderColor: invalid ? '#f44' : colors.underline },
69
+ ]}
70
+ value={draft}
71
+ onChangeText={setDraft}
72
+ onSubmitEditing={commitEdit}
73
+ onBlur={commitEdit}
74
+ autoCapitalize='none'
75
+ autoCorrect={false}
76
+ autoFocus
77
+ selectTextOnFocus
78
+ returnKeyType='done'
79
+ />
80
+ );
81
+ }
82
+
83
+ return (
84
+ <Pressable onPress={startEditing} hitSlop={4}>
85
+ <Text style={[styles.base, { color: colors.text }, disabled && styles.strikethrough]}>
86
+ {displayValue}
87
+ </Text>
88
+ </Pressable>
89
+ );
90
+ };
91
+
92
+ const styles = StyleSheet.create({
93
+ base: {
94
+ fontSize: 13,
95
+ fontFamily: MONOSPACE_FONT,
96
+ padding: 0,
97
+ margin: 0,
98
+ minWidth: 20,
99
+ },
100
+ editing: {
101
+ borderWidth: 1,
102
+ borderRadius: 3,
103
+ paddingHorizontal: 4,
104
+ paddingVertical: 2,
105
+ },
106
+ strikethrough: {
107
+ textDecorationLine: 'line-through',
108
+ },
109
+ });
@@ -0,0 +1,114 @@
1
+ import { Animated, StyleSheet, useWindowDimensions, View } from 'react-native';
2
+ import { FLOATING_PANEL, Z_INDEX } from '../constants';
3
+ import { useFloatingPanel } from '../hooks';
4
+ import { HandleContent } from './HandleContent';
5
+ import { InspectorBubble } from './InspectorBubble';
6
+ import { PanelBody } from './PanelBody';
7
+ import { PanelHeader } from './PanelHeader';
8
+ import type { FloatingPanelProps } from './types';
9
+
10
+ /**
11
+ * Unified floating panel that renders as a bubble, handle, or expanded panel.
12
+ * Draggable via PanResponder. Bubble/handle snap to edge; expanded floats freely.
13
+ */
14
+ export const FloatingPanel = ({
15
+ panelState,
16
+ selected,
17
+ matches,
18
+ selectedIndex,
19
+ onToggleInspect,
20
+ onCycleNext,
21
+ onCyclePrevious,
22
+ onClose,
23
+ }: FloatingPanelProps) => {
24
+ const { width: screenWidth, height: screenHeight } = useWindowDimensions();
25
+
26
+ const { position, expandProgress, panHandlers, panelSize } = useFloatingPanel({
27
+ panelState,
28
+ screenWidth,
29
+ screenHeight,
30
+ onTap: onToggleInspect,
31
+ });
32
+
33
+ const bodyHeight = expandProgress.interpolate({
34
+ inputRange: [0, 1],
35
+ outputRange: [0, FLOATING_PANEL.PANEL_BODY_HEIGHT],
36
+ });
37
+
38
+ const bodyOpacity = expandProgress.interpolate({
39
+ inputRange: [0, 0.6, 1],
40
+ outputRange: [0, 0, 1],
41
+ });
42
+
43
+ return (
44
+ <Animated.View
45
+ style={[
46
+ styles.container,
47
+ {
48
+ left: position.x,
49
+ top: position.y,
50
+ width: panelSize.width,
51
+ zIndex: Z_INDEX.FLOATING_PANEL,
52
+ },
53
+ panelState === 'bubble' && styles.bubbleContainer,
54
+ panelState === 'handle' && styles.handleContainer,
55
+ ]}
56
+ >
57
+ {/* Drag surface — the touchable/draggable area */}
58
+ <View {...panHandlers}>
59
+ {panelState === 'bubble' && <InspectorBubble />}
60
+ {panelState === 'handle' && <HandleContent onClose={onClose} />}
61
+ {panelState === 'expanded' && selected && (
62
+ <PanelHeader
63
+ selected={selected}
64
+ matches={matches}
65
+ selectedIndex={selectedIndex}
66
+ onCycleNext={onCycleNext}
67
+ onCyclePrevious={onCyclePrevious}
68
+ onClose={onClose}
69
+ />
70
+ )}
71
+ </View>
72
+
73
+ {/* Expandable body — always mounted when selected so added-property state survives collapse */}
74
+ {selected && (
75
+ <Animated.View
76
+ style={[styles.body, { height: bodyHeight, opacity: bodyOpacity }]}
77
+ pointerEvents={panelState === 'expanded' ? 'auto' : 'none'}
78
+ >
79
+ <PanelBody element={selected} />
80
+ </Animated.View>
81
+ )}
82
+ </Animated.View>
83
+ );
84
+ };
85
+
86
+ const styles = StyleSheet.create({
87
+ container: {
88
+ position: 'absolute',
89
+ backgroundColor: 'rgba(30, 30, 30, 0.97)',
90
+ borderRadius: 12,
91
+ borderWidth: 1,
92
+ borderColor: 'rgba(255, 255, 255, 0.08)',
93
+ shadowColor: '#000',
94
+ shadowOffset: { width: 0, height: 4 },
95
+ shadowOpacity: 0.4,
96
+ shadowRadius: 8,
97
+ elevation: 8,
98
+ overflow: 'hidden',
99
+ },
100
+ bubbleContainer: {
101
+ borderRadius: FLOATING_PANEL.BUBBLE_SIZE / 2,
102
+ backgroundColor: 'transparent',
103
+ borderWidth: 0,
104
+ overflow: 'visible',
105
+ shadowOpacity: 0,
106
+ elevation: 0,
107
+ },
108
+ handleContainer: {
109
+ borderColor: 'rgba(255, 59, 48, 0.2)',
110
+ },
111
+ body: {
112
+ overflow: 'hidden',
113
+ },
114
+ });
@@ -0,0 +1,45 @@
1
+ import { StyleSheet, Text, View } from 'react-native';
2
+ import { CloseButton } from './CloseButton';
3
+
4
+ interface HandleContentProps {
5
+ onClose: () => void;
6
+ }
7
+
8
+ /** Floating handle shown in inspect mode before an element is selected. */
9
+ export const HandleContent = ({ onClose }: HandleContentProps) => (
10
+ <View style={styles.handle}>
11
+ <View style={styles.dot} />
12
+ <Text style={styles.handleText}>Tap an element</Text>
13
+ <CloseButton onPress={onClose} />
14
+ </View>
15
+ );
16
+
17
+ const styles = StyleSheet.create({
18
+ handle: {
19
+ flexDirection: 'row',
20
+ alignItems: 'center',
21
+ paddingHorizontal: 14,
22
+ paddingVertical: 10,
23
+ backgroundColor: '#0F3460',
24
+ borderRadius: 22,
25
+ borderWidth: 1,
26
+ borderColor: 'rgba(79, 195, 247, 0.3)',
27
+ gap: 8,
28
+ },
29
+ dot: {
30
+ width: 8,
31
+ height: 8,
32
+ borderRadius: 4,
33
+ backgroundColor: '#E94560',
34
+ shadowColor: '#E94560',
35
+ shadowOffset: { width: 0, height: 0 },
36
+ shadowOpacity: 0.6,
37
+ shadowRadius: 3,
38
+ },
39
+ handleText: {
40
+ color: '#FFFFFF',
41
+ fontSize: 13,
42
+ fontWeight: '500',
43
+ flex: 1,
44
+ },
45
+ });
@@ -0,0 +1,121 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { Animated, Easing, StyleSheet, View } from 'react-native';
3
+ import { INSPECTOR_BUBBLE } from '../constants';
4
+
5
+ const { SIZE: BUBBLE_SIZE, SWEEP_WIDTH, CENTER_DOT_SIZE, TRAIL_ARMS } = INSPECTOR_BUBBLE;
6
+
7
+ /**
8
+ * Inspector bubble with a radar-sweep animation.
9
+ * A bright orange line rotates around a dark orange circle with a fading trail.
10
+ */
11
+ export const InspectorBubble = () => {
12
+ const sweepAnim = useRef(new Animated.Value(0)).current;
13
+
14
+ useEffect(() => {
15
+ const loop = Animated.loop(
16
+ Animated.timing(sweepAnim, {
17
+ toValue: 1,
18
+ duration: 3000,
19
+ easing: Easing.linear,
20
+ useNativeDriver: true,
21
+ }),
22
+ );
23
+ loop.start();
24
+ return () => loop.stop();
25
+ }, [sweepAnim]);
26
+
27
+ return (
28
+ <View style={styles.container}>
29
+ {/* Clipping circle — contains all radar elements */}
30
+ <View style={styles.clipCircle}>
31
+ {/* Dark orange base fill */}
32
+ <View style={styles.baseFill} />
33
+
34
+ {/* Subtle grid ring at ~60% radius for radar feel */}
35
+ <View style={styles.gridRing} />
36
+
37
+ {/* Sweep line + fading trail arms */}
38
+ {TRAIL_ARMS.map((arm) => {
39
+ const rotation = sweepAnim.interpolate({
40
+ inputRange: [0, 1],
41
+ outputRange: [`${arm.offsetDeg}deg`, `${360 + arm.offsetDeg}deg`],
42
+ });
43
+
44
+ return (
45
+ <Animated.View
46
+ key={arm.key}
47
+ style={[
48
+ styles.armWrapper,
49
+ {
50
+ opacity: arm.opacity,
51
+ transform: [{ rotate: rotation }],
52
+ },
53
+ ]}
54
+ >
55
+ <View style={styles.sweepLine} />
56
+ </Animated.View>
57
+ );
58
+ })}
59
+
60
+ {/* Center dot */}
61
+ <View style={styles.centerDot} />
62
+ </View>
63
+ </View>
64
+ );
65
+ };
66
+
67
+ const styles = StyleSheet.create({
68
+ container: {
69
+ width: BUBBLE_SIZE,
70
+ height: BUBBLE_SIZE,
71
+ justifyContent: 'center',
72
+ alignItems: 'center',
73
+ shadowColor: '#f97316',
74
+ shadowOffset: { width: 0, height: 0 },
75
+ shadowOpacity: 0.5,
76
+ shadowRadius: 10,
77
+ elevation: 8,
78
+ },
79
+ clipCircle: {
80
+ width: BUBBLE_SIZE,
81
+ height: BUBBLE_SIZE,
82
+ borderRadius: BUBBLE_SIZE / 2,
83
+ borderWidth: 2,
84
+ borderColor: '#ea580c',
85
+ overflow: 'hidden',
86
+ justifyContent: 'center',
87
+ alignItems: 'center',
88
+ },
89
+ baseFill: {
90
+ ...StyleSheet.absoluteFillObject,
91
+ backgroundColor: '#7c2d12',
92
+ },
93
+ gridRing: {
94
+ position: 'absolute',
95
+ width: BUBBLE_SIZE * 0.55,
96
+ height: BUBBLE_SIZE * 0.55,
97
+ borderRadius: (BUBBLE_SIZE * 0.55) / 2,
98
+ borderWidth: 1,
99
+ borderColor: 'rgba(234, 88, 12, 0.25)',
100
+ },
101
+ armWrapper: {
102
+ position: 'absolute',
103
+ width: BUBBLE_SIZE,
104
+ height: BUBBLE_SIZE,
105
+ alignItems: 'center',
106
+ },
107
+ sweepLine: {
108
+ width: SWEEP_WIDTH,
109
+ height: BUBBLE_SIZE / 2 - CENTER_DOT_SIZE / 2 - 2,
110
+ backgroundColor: '#fb923c',
111
+ borderRadius: SWEEP_WIDTH / 2,
112
+ marginTop: 3,
113
+ },
114
+ centerDot: {
115
+ position: 'absolute',
116
+ width: CENTER_DOT_SIZE,
117
+ height: CENTER_DOT_SIZE,
118
+ borderRadius: CENTER_DOT_SIZE / 2,
119
+ backgroundColor: '#f97316',
120
+ },
121
+ });
@@ -0,0 +1,162 @@
1
+ import { useState } from 'react';
2
+ import { ScrollView, StyleSheet, View } from 'react-native';
3
+ import { Checkbox } from '../components';
4
+ import type { MeasuredElement } from '../fiber';
5
+ import { useStyleOverrides } from '../hooks';
6
+ import { formatValue, isColorProp } from '../utils';
7
+ import { AddPropertyButton, AddPropertyRow } from './AddPropertyRow';
8
+ import { EditableValue } from './EditableValue';
9
+ import { PanelFooter } from './PanelFooter';
10
+
11
+ interface PanelBodyProps {
12
+ element: MeasuredElement;
13
+ }
14
+
15
+ /** Scrollable style property list with footer — the main content of the expanded panel. */
16
+ export const PanelBody = ({ element }: PanelBodyProps) => {
17
+ const {
18
+ entries,
19
+ addedEntries,
20
+ resolveEntry,
21
+ handleToggle,
22
+ handleValueChange,
23
+ handleKeyChange,
24
+ handleAddProperty,
25
+ handleAddedValueChange,
26
+ handleAddedKeyChange,
27
+ handleRemoveProperty,
28
+ } = useStyleOverrides(element);
29
+
30
+ const [pendingAdd, setPendingAdd] = useState(false);
31
+
32
+ const totalCount = entries.length + addedEntries.length;
33
+ if (entries.length === 0 && addedEntries.length === 0 && !pendingAdd) return null;
34
+
35
+ return (
36
+ <View style={styles.container}>
37
+ <ScrollView
38
+ style={styles.list}
39
+ contentContainerStyle={styles.listContent}
40
+ nestedScrollEnabled
41
+ keyboardShouldPersistTaps='handled'
42
+ >
43
+ {entries.map(([originalKey, originalValue], index) => {
44
+ const { activeKey, displayValue, disabled } = resolveEntry(originalKey, originalValue);
45
+ return (
46
+ <View key={originalKey} style={[styles.row, index % 2 === 0 && styles.rowAlt]}>
47
+ <Checkbox checked={!disabled} onToggle={() => handleToggle(originalKey)} />
48
+ <View style={[styles.rowContent, disabled && styles.rowDisabled]}>
49
+ <EditableValue
50
+ value={activeKey}
51
+ displayValue={activeKey}
52
+ onSubmit={(newKey) => handleKeyChange(originalKey, String(newKey))}
53
+ variant='key'
54
+ disabled={disabled}
55
+ />
56
+ <View style={styles.valueContainer}>
57
+ {isColorProp(activeKey) && (
58
+ <View style={[styles.colorSwatch, { backgroundColor: String(displayValue) }]} />
59
+ )}
60
+ <EditableValue
61
+ value={displayValue}
62
+ displayValue={formatValue(displayValue)}
63
+ onSubmit={(newValue) => handleValueChange(originalKey, newValue)}
64
+ variant='value'
65
+ disabled={disabled}
66
+ />
67
+ </View>
68
+ </View>
69
+ </View>
70
+ );
71
+ })}
72
+
73
+ {addedEntries.map(([key, value], index) => {
74
+ const rowIndex = entries.length + index;
75
+ return (
76
+ <View key={`added-${key}`} style={[styles.row, rowIndex % 2 === 0 && styles.rowAlt]}>
77
+ <Checkbox checked onToggle={() => handleRemoveProperty(key)} />
78
+ <View style={styles.rowContent}>
79
+ <EditableValue
80
+ value={key}
81
+ displayValue={key}
82
+ onSubmit={(newKey) => handleAddedKeyChange(key, String(newKey))}
83
+ variant='key'
84
+ />
85
+ <View style={styles.valueContainer}>
86
+ {isColorProp(key) && (
87
+ <View style={[styles.colorSwatch, { backgroundColor: String(value) }]} />
88
+ )}
89
+ <EditableValue
90
+ value={value}
91
+ displayValue={formatValue(value)}
92
+ onSubmit={(newValue) => handleAddedValueChange(key, newValue)}
93
+ variant='value'
94
+ />
95
+ </View>
96
+ </View>
97
+ </View>
98
+ );
99
+ })}
100
+
101
+ {pendingAdd ? (
102
+ <AddPropertyRow
103
+ onAdd={(key, value) => {
104
+ handleAddProperty(key, value);
105
+ setPendingAdd(false);
106
+ }}
107
+ onCancel={() => setPendingAdd(false)}
108
+ />
109
+ ) : (
110
+ <AddPropertyButton onPress={() => setPendingAdd(true)} />
111
+ )}
112
+ </ScrollView>
113
+
114
+ <PanelFooter propertyCount={totalCount} width={element.width} height={element.height} />
115
+ </View>
116
+ );
117
+ };
118
+
119
+ const styles = StyleSheet.create({
120
+ container: {
121
+ flex: 1,
122
+ },
123
+ list: {
124
+ flex: 1,
125
+ },
126
+ listContent: {
127
+ paddingHorizontal: 12,
128
+ paddingVertical: 4,
129
+ },
130
+ row: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ paddingVertical: 5,
134
+ paddingHorizontal: 8,
135
+ borderRadius: 4,
136
+ },
137
+ rowContent: {
138
+ flex: 1,
139
+ flexDirection: 'row',
140
+ justifyContent: 'space-between',
141
+ alignItems: 'center',
142
+ },
143
+ rowAlt: {
144
+ backgroundColor: 'rgba(255, 255, 255, 0.03)',
145
+ },
146
+ rowDisabled: {
147
+ opacity: 0.35,
148
+ },
149
+ valueContainer: {
150
+ flexDirection: 'row',
151
+ alignItems: 'center',
152
+ flexShrink: 1,
153
+ },
154
+ colorSwatch: {
155
+ width: 14,
156
+ height: 14,
157
+ borderRadius: 3,
158
+ borderWidth: StyleSheet.hairlineWidth,
159
+ borderColor: '#666',
160
+ marginRight: 6,
161
+ },
162
+ });
@@ -0,0 +1,36 @@
1
+ import { StyleSheet, Text, View } from 'react-native';
2
+ import { MONOSPACE_FONT } from '../constants';
3
+
4
+ interface PanelFooterProps {
5
+ propertyCount: number;
6
+ width: number;
7
+ height: number;
8
+ }
9
+
10
+ /** Expanded panel footer — property count, element dimensions, with top divider. */
11
+ export const PanelFooter = ({ propertyCount, width, height }: PanelFooterProps) => (
12
+ <View style={styles.footer}>
13
+ <Text style={styles.footerText}>
14
+ {propertyCount} {propertyCount === 1 ? 'property' : 'properties'}
15
+ </Text>
16
+ <Text style={styles.footerText}>
17
+ {Math.round(width)} × {Math.round(height)}
18
+ </Text>
19
+ </View>
20
+ );
21
+
22
+ const styles = StyleSheet.create({
23
+ footer: {
24
+ flexDirection: 'row',
25
+ justifyContent: 'space-between',
26
+ paddingHorizontal: 12,
27
+ paddingVertical: 6,
28
+ borderTopWidth: StyleSheet.hairlineWidth,
29
+ borderTopColor: '#444',
30
+ },
31
+ footerText: {
32
+ color: '#666',
33
+ fontSize: 11,
34
+ fontFamily: MONOSPACE_FONT,
35
+ },
36
+ });
@@ -0,0 +1,111 @@
1
+ import { StyleSheet, Text, View } from 'react-native';
2
+ import { MONOSPACE_FONT } from '../constants';
3
+ import { ElementCycler } from '../ElementCycler';
4
+ import type { MeasuredElement } from '../fiber';
5
+ import { formatSourceLocation, getOwnerName, getSourceLocation } from '../utils';
6
+ import { CloseButton } from './CloseButton';
7
+
8
+ interface PanelHeaderProps {
9
+ selected: MeasuredElement;
10
+ matches: MeasuredElement[];
11
+ selectedIndex: number;
12
+ onCycleNext: () => void;
13
+ onCyclePrevious: () => void;
14
+ onClose: () => void;
15
+ }
16
+
17
+ /** Expanded panel header — grab bar, component info, cycling controls, close button. */
18
+ export const PanelHeader = ({
19
+ selected,
20
+ matches,
21
+ selectedIndex,
22
+ onCycleNext,
23
+ onCyclePrevious,
24
+ onClose,
25
+ }: PanelHeaderProps) => {
26
+ const source = getSourceLocation(selected.fiber);
27
+ const ownerName = getOwnerName(selected.fiber);
28
+
29
+ return (
30
+ <View style={styles.header}>
31
+ {/* Grab bar — visual drag indicator */}
32
+ <View style={styles.grabBarContainer}>
33
+ <View style={styles.grabBar} />
34
+ </View>
35
+
36
+ {/* Component info row */}
37
+ <View style={styles.infoRow}>
38
+ <Text style={styles.componentName} numberOfLines={1}>
39
+ &lt;{selected.componentName}&gt;
40
+ </Text>
41
+
42
+ <View style={styles.controls}>
43
+ <ElementCycler
44
+ total={matches.length}
45
+ currentIndex={selectedIndex}
46
+ componentName={selected.componentName}
47
+ onPrevious={onCyclePrevious}
48
+ onNext={onCycleNext}
49
+ />
50
+
51
+ <CloseButton onPress={onClose} />
52
+ </View>
53
+ </View>
54
+
55
+ {/* Source location — on its own line so it's not truncated */}
56
+ {source ? (
57
+ <Text style={styles.sourceText} numberOfLines={1}>
58
+ {formatSourceLocation(source)}
59
+ </Text>
60
+ ) : ownerName ? (
61
+ <Text style={styles.sourceText} numberOfLines={1}>
62
+ {ownerName}
63
+ </Text>
64
+ ) : null}
65
+ </View>
66
+ );
67
+ };
68
+
69
+ const styles = StyleSheet.create({
70
+ header: {
71
+ paddingTop: 6,
72
+ paddingHorizontal: 12,
73
+ paddingBottom: 8,
74
+ borderBottomWidth: StyleSheet.hairlineWidth,
75
+ borderBottomColor: '#444',
76
+ },
77
+ grabBarContainer: {
78
+ alignItems: 'center',
79
+ paddingBottom: 6,
80
+ },
81
+ grabBar: {
82
+ width: 40,
83
+ height: 4,
84
+ borderRadius: 2,
85
+ backgroundColor: '#666',
86
+ },
87
+ infoRow: {
88
+ flexDirection: 'row',
89
+ alignItems: 'center',
90
+ justifyContent: 'space-between',
91
+ },
92
+ componentName: {
93
+ color: '#E06C75',
94
+ fontSize: 14,
95
+ fontWeight: '600',
96
+ fontFamily: MONOSPACE_FONT,
97
+ flexShrink: 1,
98
+ },
99
+ controls: {
100
+ flexDirection: 'row',
101
+ alignItems: 'center',
102
+ gap: 6,
103
+ flexShrink: 0,
104
+ },
105
+ sourceText: {
106
+ color: '#888',
107
+ fontSize: 11,
108
+ marginTop: 4,
109
+ fontFamily: MONOSPACE_FONT,
110
+ },
111
+ });