panelui-native 0.6.0 → 0.9.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/README.md +1 -1
- package/lib/module/components/accordion/index.js +7 -5
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/alert/index.js +3 -6
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/attachment/index.js +219 -0
- package/lib/module/components/attachment/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +82 -4
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +36 -1
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +6 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +98 -22
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/inline-select/index.js +171 -46
- package/lib/module/components/inline-select/index.js.map +1 -1
- package/lib/module/components/input/index.js +28 -5
- package/lib/module/components/input/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +0 -1
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +359 -0
- package/lib/module/components/item/index.js.map +1 -0
- package/lib/module/components/label/index.js +5 -3
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1024 -0
- package/lib/module/components/line-chart/index.js.map +1 -0
- package/lib/module/components/marker/index.js +190 -0
- package/lib/module/components/marker/index.js.map +1 -0
- package/lib/module/components/message/index.js +334 -0
- package/lib/module/components/message/index.js.map +1 -0
- package/lib/module/components/message-scroller/index.js +451 -0
- package/lib/module/components/message-scroller/index.js.map +1 -0
- package/lib/module/components/popover/index.js +500 -0
- package/lib/module/components/popover/index.js.map +1 -0
- package/lib/module/components/radio-group/index.js +104 -16
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/scroll-fade/index.js +119 -71
- package/lib/module/components/scroll-fade/index.js.map +1 -1
- package/lib/module/components/select/index.js +35 -6
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/separator/index.js +91 -0
- package/lib/module/components/separator/index.js.map +1 -0
- package/lib/module/components/shimmer/index.js +111 -64
- package/lib/module/components/shimmer/index.js.map +1 -1
- package/lib/module/components/surface/index.js +24 -4
- package/lib/module/components/surface/index.js.map +1 -1
- package/lib/module/components/switch/index.js +20 -1
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/toast/index.js +7 -8
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/typography/index.js +3 -3
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-breakpoint.js +2 -2
- package/lib/module/hooks/use-copy-to-clipboard.js +0 -2
- package/lib/module/hooks/use-copy-to-clipboard.js.map +1 -1
- package/lib/module/hooks/use-keyboard-avoidance.js +120 -0
- package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -0
- package/lib/module/icons/index.js +73 -4
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/index.js +53 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/primitives/keyboard-avoider.js +40 -0
- package/lib/module/primitives/keyboard-avoider.js.map +1 -0
- package/lib/module/primitives/scrim.js +72 -0
- package/lib/module/primitives/scrim.js.map +1 -0
- package/lib/module/providers/panel-ui-provider.js +24 -4
- package/lib/module/providers/panel-ui-provider.js.map +1 -1
- package/lib/module/theme/use-theme.js +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +7 -5
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/alert/index.d.ts +3 -6
- package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
- package/lib/typescript/src/components/attachment/index.d.ts +93 -0
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +27 -2
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +11 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +7 -1
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts +156 -7
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/inline-select/index.d.ts +29 -6
- package/lib/typescript/src/components/inline-select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/index.d.ts +13 -0
- package/lib/typescript/src/components/input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts +0 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/item/index.d.ts +286 -0
- package/lib/typescript/src/components/item/index.d.ts.map +1 -0
- package/lib/typescript/src/components/label/index.d.ts +5 -3
- package/lib/typescript/src/components/label/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +247 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/marker/index.d.ts +137 -0
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts +177 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts +150 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -0
- package/lib/typescript/src/components/popover/index.d.ts +130 -0
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts +32 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/scroll-fade/index.d.ts +18 -9
- package/lib/typescript/src/components/scroll-fade/index.d.ts.map +1 -1
- package/lib/typescript/src/components/select/index.d.ts +16 -1
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/separator/index.d.ts +47 -0
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -0
- package/lib/typescript/src/components/shimmer/index.d.ts +38 -20
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/index.d.ts +51 -2
- package/lib/typescript/src/components/surface/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +10 -0
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts +4 -4
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-breakpoint.d.ts +2 -2
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts +0 -2
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +20 -0
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +10 -4
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +35 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts +22 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +21 -0
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -0
- package/lib/typescript/src/providers/panel-ui-provider.d.ts +1 -1
- package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
- package/lib/typescript/src/theme/use-theme.d.ts +1 -1
- package/package.json +16 -3
- package/src/components/accordion/index.tsx +7 -5
- package/src/components/alert/index.tsx +3 -6
- package/src/components/attachment/index.tsx +279 -0
- package/src/components/bottom-sheet/index.tsx +102 -2
- package/src/components/button/index.tsx +52 -0
- package/src/components/dialog/index.tsx +10 -1
- package/src/components/empty-state/index.tsx +64 -19
- package/src/components/inline-select/index.tsx +191 -44
- package/src/components/input/index.tsx +39 -3
- package/src/components/input-group/index.tsx +0 -1
- package/src/components/item/index.tsx +387 -0
- package/src/components/label/index.tsx +5 -3
- package/src/components/line-chart/index.tsx +1224 -0
- package/src/components/marker/index.tsx +194 -0
- package/src/components/message/index.tsx +336 -0
- package/src/components/message-scroller/index.tsx +574 -0
- package/src/components/popover/index.tsx +599 -0
- package/src/components/radio-group/index.tsx +108 -17
- package/src/components/scroll-fade/index.tsx +163 -51
- package/src/components/select/index.tsx +54 -9
- package/src/components/separator/index.tsx +97 -0
- package/src/components/shimmer/index.tsx +151 -58
- package/src/components/surface/index.tsx +32 -5
- package/src/components/switch/index.tsx +27 -1
- package/src/components/toast/index.tsx +7 -8
- package/src/components/typography/index.tsx +3 -3
- package/src/hooks/index.ts +5 -0
- package/src/hooks/use-breakpoint.ts +2 -2
- package/src/hooks/use-copy-to-clipboard.ts +0 -2
- package/src/hooks/use-keyboard-avoidance.ts +140 -0
- package/src/icons/index.tsx +52 -4
- package/src/index.ts +92 -0
- package/src/native/index.ts +79 -0
- package/src/primitives/keyboard-avoider.tsx +49 -0
- package/src/primitives/scrim.tsx +95 -0
- package/src/providers/panel-ui-provider.tsx +30 -9
- package/src/theme/use-theme.ts +1 -1
- package/theme.css +63 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useKeyboardAvoidance — lift an element just clear of the software keyboard.
|
|
3
|
+
*
|
|
4
|
+
* `KeyboardAvoidingView` shifts or pads an entire subtree by the full keyboard
|
|
5
|
+
* height regardless of where the element actually sits, which over-scrolls
|
|
6
|
+
* short forms and does nothing useful for an element already above the fold.
|
|
7
|
+
* This measures the element and moves it by exactly the overlap — and not at
|
|
8
|
+
* all when there is none.
|
|
9
|
+
*
|
|
10
|
+
* ## Install the keyboard controller
|
|
11
|
+
*
|
|
12
|
+
* ```sh
|
|
13
|
+
* npx expo install react-native-keyboard-controller
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* It is an optional peer, but on Android it is close to required. Reanimated's
|
|
17
|
+
* own `useAnimatedKeyboard` — the fallback used when the controller is absent —
|
|
18
|
+
* is deprecated in Reanimated 4, and merely *calling* it switches Android out
|
|
19
|
+
* of `adjustResize` into manual inset handling for the whole app. That is a
|
|
20
|
+
* global side effect from a local hook, and it is why keyboard avoidance built
|
|
21
|
+
* on it tends to work on iOS and break on Android.
|
|
22
|
+
*
|
|
23
|
+
* With the controller installed, wrap your app in its `KeyboardProvider` —
|
|
24
|
+
* `PanelUIProvider` does this for you when the package is present.
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* const { ref, onLayout, animatedStyle } = useKeyboardAvoidance();
|
|
28
|
+
*
|
|
29
|
+
* <Animated.View ref={ref} onLayout={onLayout} style={animatedStyle}>
|
|
30
|
+
* <TextInput />
|
|
31
|
+
* </Animated.View>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
import { useCallback } from 'react';
|
|
35
|
+
import { useWindowDimensions, type LayoutChangeEvent, type View } from 'react-native';
|
|
36
|
+
import {
|
|
37
|
+
useAnimatedKeyboard,
|
|
38
|
+
useAnimatedRef,
|
|
39
|
+
useAnimatedStyle,
|
|
40
|
+
useSharedValue,
|
|
41
|
+
type AnimatedRef,
|
|
42
|
+
type SharedValue,
|
|
43
|
+
} from 'react-native-reanimated';
|
|
44
|
+
|
|
45
|
+
export interface UseKeyboardAvoidanceOptions {
|
|
46
|
+
/** Set false to leave the element where it is. */
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
/** Gap to keep between the element's bottom edge and the keyboard. */
|
|
49
|
+
offset?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface UseKeyboardAvoidanceResult {
|
|
53
|
+
/** Attach to the element that should stay visible. */
|
|
54
|
+
ref: AnimatedRef<View>;
|
|
55
|
+
/** Attach to the same element — it is how the resting position is measured. */
|
|
56
|
+
onLayout: (event: LayoutChangeEvent) => void;
|
|
57
|
+
/** Apply to the same element. */
|
|
58
|
+
animatedStyle: ReturnType<typeof useAnimatedStyle>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type KeyboardHeightHook = () => SharedValue<number>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolved once, at module load, so the hook below always calls the same
|
|
65
|
+
* underlying hook — swapping between them per render would break the rules of
|
|
66
|
+
* hooks.
|
|
67
|
+
*/
|
|
68
|
+
const useKeyboardHeight: KeyboardHeightHook = (() => {
|
|
69
|
+
try {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
71
|
+
const controller = require('react-native-keyboard-controller');
|
|
72
|
+
if (typeof controller?.useReanimatedKeyboardAnimation === 'function') {
|
|
73
|
+
return () => {
|
|
74
|
+
// The controller reports height as a negative offset, matching the
|
|
75
|
+
// translation you would apply. Normalise it to a positive height so
|
|
76
|
+
// the arithmetic below reads the same either way.
|
|
77
|
+
const { height } = controller.useReanimatedKeyboardAnimation();
|
|
78
|
+
return height as SharedValue<number>;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Not installed — fall through.
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return () => useAnimatedKeyboard().height;
|
|
86
|
+
})();
|
|
87
|
+
|
|
88
|
+
/** True when the keyboard controller is driving this, rather than the fallback. */
|
|
89
|
+
export function hasKeyboardController(): boolean {
|
|
90
|
+
try {
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
92
|
+
return typeof require('react-native-keyboard-controller')
|
|
93
|
+
?.useReanimatedKeyboardAnimation === 'function';
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function useKeyboardAvoidance({
|
|
100
|
+
enabled = true,
|
|
101
|
+
offset = 16,
|
|
102
|
+
}: UseKeyboardAvoidanceOptions = {}): UseKeyboardAvoidanceResult {
|
|
103
|
+
const ref = useAnimatedRef<View>();
|
|
104
|
+
const rawHeight = useKeyboardHeight();
|
|
105
|
+
const { height: screenHeight } = useWindowDimensions();
|
|
106
|
+
|
|
107
|
+
/** Window-space bottom edge of the element with no translation applied. */
|
|
108
|
+
const restingBottom = useSharedValue(0);
|
|
109
|
+
|
|
110
|
+
const measure = useCallback(() => {
|
|
111
|
+
ref.current?.measureInWindow((_x, y, _width, height) => {
|
|
112
|
+
if (height > 0) restingBottom.value = y + height;
|
|
113
|
+
});
|
|
114
|
+
}, [ref, restingBottom]);
|
|
115
|
+
|
|
116
|
+
const onLayout = useCallback(
|
|
117
|
+
(_event: LayoutChangeEvent) => {
|
|
118
|
+
// measureInWindow is only meaningful once the view is attached and
|
|
119
|
+
// positioned, which is a frame later than onLayout on both platforms.
|
|
120
|
+
requestAnimationFrame(measure);
|
|
121
|
+
},
|
|
122
|
+
[measure]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
126
|
+
// Both sources are normalised to a positive height here.
|
|
127
|
+
const keyboardHeight = Math.abs(rawHeight.value);
|
|
128
|
+
|
|
129
|
+
if (!enabled || keyboardHeight === 0 || restingBottom.value === 0) {
|
|
130
|
+
return { transform: [{ translateY: 0 }] };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const keyboardTop = screenHeight - keyboardHeight;
|
|
134
|
+
const overlap = restingBottom.value + offset - keyboardTop;
|
|
135
|
+
|
|
136
|
+
return { transform: [{ translateY: overlap > 0 ? -overlap : 0 }] };
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return { ref, onLayout, animatedStyle };
|
|
140
|
+
}
|
package/src/icons/index.tsx
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Icon set.
|
|
3
3
|
*
|
|
4
4
|
* Two families live here:
|
|
5
|
-
* - Stroked 24×24 icons
|
|
6
|
-
*
|
|
7
|
-
* - Filled 16×16 status icons for Alert and Toast indicators
|
|
8
|
-
*
|
|
5
|
+
* - Stroked 24×24 icons on Lucide geometry, for chrome such as chevrons and
|
|
6
|
+
* the close button.
|
|
7
|
+
* - Filled 16×16 status icons for Alert and Toast indicators, which read
|
|
8
|
+
* better small than a stroked equivalent would.
|
|
9
9
|
*
|
|
10
10
|
* Every icon resolves its colour in the same order: an explicit `color` prop,
|
|
11
11
|
* then the colour inherited from an enclosing `IconColorProvider`, then its
|
|
@@ -462,3 +462,51 @@ export function SendIcon({ size = 16, color, ...props }: IconProps) {
|
|
|
462
462
|
</Svg>
|
|
463
463
|
);
|
|
464
464
|
}
|
|
465
|
+
|
|
466
|
+
/** Document with a folded corner — a file attachment. */
|
|
467
|
+
export function FileIcon({ size = 16, color, ...props }: IconProps) {
|
|
468
|
+
const resolved = useResolvedColor(color, '#737373');
|
|
469
|
+
return (
|
|
470
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
|
|
471
|
+
<Path
|
|
472
|
+
d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8zM14 3v5h5"
|
|
473
|
+
stroke={resolved}
|
|
474
|
+
strokeWidth={2}
|
|
475
|
+
strokeLinecap="round"
|
|
476
|
+
strokeLinejoin="round"
|
|
477
|
+
/>
|
|
478
|
+
</Svg>
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Framed picture with a sun and a corner peak — an image attachment. */
|
|
483
|
+
export function ImageIcon({ size = 16, color, ...props }: IconProps) {
|
|
484
|
+
const resolved = useResolvedColor(color, '#737373');
|
|
485
|
+
return (
|
|
486
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
|
|
487
|
+
<Path
|
|
488
|
+
d="M4 4h16v16H4z M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM20 15l-4.5-4.5L6 20"
|
|
489
|
+
stroke={resolved}
|
|
490
|
+
strokeWidth={2}
|
|
491
|
+
strokeLinecap="round"
|
|
492
|
+
strokeLinejoin="round"
|
|
493
|
+
/>
|
|
494
|
+
</Svg>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** A paperclip — attaching a file to a composer. */
|
|
499
|
+
export function PaperclipIcon({ size = 16, color, ...props }: IconProps) {
|
|
500
|
+
const resolved = useResolvedColor(color, '#737373');
|
|
501
|
+
return (
|
|
502
|
+
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props}>
|
|
503
|
+
<Path
|
|
504
|
+
d="M21 8.5 11.3 18.2a4 4 0 0 1-5.6-5.6l9-9a2.5 2.5 0 0 1 3.6 3.6l-9 9a1 1 0 0 1-1.5-1.5l8.3-8.3"
|
|
505
|
+
stroke={resolved}
|
|
506
|
+
strokeWidth={2}
|
|
507
|
+
strokeLinecap="round"
|
|
508
|
+
strokeLinejoin="round"
|
|
509
|
+
/>
|
|
510
|
+
</Svg>
|
|
511
|
+
);
|
|
512
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -17,11 +17,18 @@ export {
|
|
|
17
17
|
// Primitives
|
|
18
18
|
export { Portal, PortalHost, PortalProvider } from './primitives/portal';
|
|
19
19
|
export { Text, type TextProps } from './primitives/text';
|
|
20
|
+
export {
|
|
21
|
+
KeyboardAvoider,
|
|
22
|
+
type KeyboardAvoiderProps,
|
|
23
|
+
} from './primitives/keyboard-avoider';
|
|
20
24
|
export {
|
|
21
25
|
AnimatedPressable,
|
|
22
26
|
type AnimatedPressableProps,
|
|
23
27
|
} from './primitives/animated-pressable';
|
|
24
28
|
|
|
29
|
+
// Native UI bridge
|
|
30
|
+
export { hasNativeUI } from './native';
|
|
31
|
+
|
|
25
32
|
// Components
|
|
26
33
|
export {
|
|
27
34
|
Accordion,
|
|
@@ -43,6 +50,18 @@ export {
|
|
|
43
50
|
type AvatarProps,
|
|
44
51
|
type AvatarBadgeProps,
|
|
45
52
|
} from './components/avatar';
|
|
53
|
+
export {
|
|
54
|
+
Attachment,
|
|
55
|
+
type AttachmentProps,
|
|
56
|
+
type AttachmentGroupProps,
|
|
57
|
+
type AttachmentMediaProps,
|
|
58
|
+
type AttachmentContentProps,
|
|
59
|
+
type AttachmentTitleProps,
|
|
60
|
+
type AttachmentDescriptionProps,
|
|
61
|
+
type AttachmentActionsProps,
|
|
62
|
+
type AttachmentActionProps,
|
|
63
|
+
type AttachmentState,
|
|
64
|
+
} from './components/attachment';
|
|
46
65
|
export { Badge, type BadgeProps } from './components/badge';
|
|
47
66
|
export {
|
|
48
67
|
BottomSheet,
|
|
@@ -83,7 +102,76 @@ export {
|
|
|
83
102
|
type InputGroupInputProps,
|
|
84
103
|
type InputGroupDecoratorProps,
|
|
85
104
|
} from './components/input-group';
|
|
105
|
+
export {
|
|
106
|
+
Item,
|
|
107
|
+
type ItemProps,
|
|
108
|
+
type ItemGroupProps,
|
|
109
|
+
type ItemSeparatorProps,
|
|
110
|
+
type ItemMediaProps,
|
|
111
|
+
type ItemContentProps,
|
|
112
|
+
type ItemTitleProps,
|
|
113
|
+
type ItemDescriptionProps,
|
|
114
|
+
type ItemActionsProps,
|
|
115
|
+
type ItemHeaderProps,
|
|
116
|
+
type ItemFooterProps,
|
|
117
|
+
} from './components/item';
|
|
86
118
|
export { Label, type LabelProps, type LabelTextProps } from './components/label';
|
|
119
|
+
export {
|
|
120
|
+
LineChart,
|
|
121
|
+
useLineChart,
|
|
122
|
+
type LineChartProps,
|
|
123
|
+
type LineChartHandle,
|
|
124
|
+
type LineChartGridProps,
|
|
125
|
+
type LineChartLineProps,
|
|
126
|
+
type LineChartAreaProps,
|
|
127
|
+
type LineChartSkeletonProps,
|
|
128
|
+
type LineChartXAxisProps,
|
|
129
|
+
type LineChartTooltipProps,
|
|
130
|
+
type LineChartLegendProps,
|
|
131
|
+
type LineChartDatum,
|
|
132
|
+
type LineChartStatus,
|
|
133
|
+
type LineChartCurve,
|
|
134
|
+
} from './components/line-chart';
|
|
135
|
+
export {
|
|
136
|
+
Marker,
|
|
137
|
+
type MarkerProps,
|
|
138
|
+
type MarkerIconProps,
|
|
139
|
+
type MarkerContentProps,
|
|
140
|
+
} from './components/marker';
|
|
141
|
+
export {
|
|
142
|
+
MessageScroller,
|
|
143
|
+
useMessageScroller,
|
|
144
|
+
useMessageScrollerVisibility,
|
|
145
|
+
type MessageScrollerProps,
|
|
146
|
+
type MessageScrollerViewportProps,
|
|
147
|
+
type MessageScrollerContentProps,
|
|
148
|
+
type MessageScrollerItemProps,
|
|
149
|
+
type MessageScrollerButtonProps,
|
|
150
|
+
type MessageScrollerPosition,
|
|
151
|
+
} from './components/message-scroller';
|
|
152
|
+
export {
|
|
153
|
+
Message,
|
|
154
|
+
type MessageProps,
|
|
155
|
+
type MessageGroupProps,
|
|
156
|
+
type MessageAvatarProps,
|
|
157
|
+
type MessageContentProps,
|
|
158
|
+
type MessageHeaderProps,
|
|
159
|
+
type MessageBubbleProps,
|
|
160
|
+
type MessageBubbleContentProps,
|
|
161
|
+
type MessageFooterProps,
|
|
162
|
+
type MessageActionsProps,
|
|
163
|
+
} from './components/message';
|
|
164
|
+
export {
|
|
165
|
+
Popover,
|
|
166
|
+
type PopoverProps,
|
|
167
|
+
type PopoverTriggerProps,
|
|
168
|
+
type PopoverContentProps,
|
|
169
|
+
type PopoverArrowProps,
|
|
170
|
+
type PopoverCloseProps,
|
|
171
|
+
type PopoverPlacement,
|
|
172
|
+
type PopoverAlign,
|
|
173
|
+
type PopoverPresentation,
|
|
174
|
+
} from './components/popover';
|
|
87
175
|
export { Progress, type ProgressProps } from './components/progress';
|
|
88
176
|
export {
|
|
89
177
|
RadioGroup,
|
|
@@ -94,6 +182,7 @@ export {
|
|
|
94
182
|
ScrollFade,
|
|
95
183
|
type ScrollFadeProps,
|
|
96
184
|
} from './components/scroll-fade';
|
|
185
|
+
export { Separator, type SeparatorProps } from './components/separator';
|
|
97
186
|
export { Shimmer, type ShimmerProps } from './components/shimmer';
|
|
98
187
|
export { Skeleton, type SkeletonProps } from './components/skeleton';
|
|
99
188
|
export { Spinner, type SpinnerProps } from './components/spinner';
|
|
@@ -154,11 +243,14 @@ export {
|
|
|
154
243
|
ChevronLeftIcon,
|
|
155
244
|
ChevronRightIcon,
|
|
156
245
|
FacebookIcon,
|
|
246
|
+
FileIcon,
|
|
157
247
|
GoogleIcon,
|
|
158
248
|
IconColorProvider,
|
|
159
249
|
InfoIcon,
|
|
250
|
+
ImageIcon,
|
|
160
251
|
MoonIcon,
|
|
161
252
|
PackageIcon,
|
|
253
|
+
PaperclipIcon,
|
|
162
254
|
PlusSquareIcon,
|
|
163
255
|
ReceiptIcon,
|
|
164
256
|
SearchIcon,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional bridge to the platform's own UI toolkit.
|
|
3
|
+
*
|
|
4
|
+
* A handful of controls are better served by the real thing than by a faithful
|
|
5
|
+
* re-implementation: a switch, a picker, a sheet and a button are muscle
|
|
6
|
+
* memory, and users notice when the animation curve or the haptic is a
|
|
7
|
+
* near-miss. Passing `native` to those components hands rendering to SwiftUI
|
|
8
|
+
* on iOS and Jetpack Compose on Android.
|
|
9
|
+
*
|
|
10
|
+
* It is optional on purpose. The package is resolved lazily and every caller
|
|
11
|
+
* falls back to the styled implementation when it is missing, so nobody who
|
|
12
|
+
* never writes `native` has to install anything.
|
|
13
|
+
*
|
|
14
|
+
* ```sh
|
|
15
|
+
* npx expo install @expo/ui
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* **Theme tokens do not apply in native mode.** The platform draws the control
|
|
19
|
+
* with its own colours, metrics and typography — that is the entire point, and
|
|
20
|
+
* it means `className` and the variant props are ignored on those components.
|
|
21
|
+
*/
|
|
22
|
+
import { Platform } from 'react-native';
|
|
23
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
24
|
+
|
|
25
|
+
interface NativeUIModule {
|
|
26
|
+
Host: ComponentType<{
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
matchContents?: boolean;
|
|
29
|
+
style?: unknown;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Hosts React Native views inside the native tree. Anything of ours that
|
|
34
|
+
* goes inside a native container has to be wrapped in this or the native
|
|
35
|
+
* layout does not measure it — children spill outside their container.
|
|
36
|
+
*/
|
|
37
|
+
RNHostView: ComponentType<{
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
matchContents?: boolean;
|
|
40
|
+
style?: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
Button: ComponentType<Record<string, unknown>>;
|
|
43
|
+
Switch: ComponentType<Record<string, unknown>>;
|
|
44
|
+
Picker: ComponentType<Record<string, unknown>> & {
|
|
45
|
+
Item: ComponentType<Record<string, unknown>>;
|
|
46
|
+
};
|
|
47
|
+
BottomSheet: ComponentType<Record<string, unknown>>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let resolved = false;
|
|
51
|
+
let module: NativeUIModule | null = null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The native UI module, or null when it is not installed or the platform has
|
|
55
|
+
* no toolkit behind it. Resolved once and cached — a failed require is not
|
|
56
|
+
* retried on every render.
|
|
57
|
+
*/
|
|
58
|
+
export function getNativeUI(): NativeUIModule | null {
|
|
59
|
+
if (resolved) return module;
|
|
60
|
+
resolved = true;
|
|
61
|
+
|
|
62
|
+
// Web has no SwiftUI and no Compose; @expo/ui renders plain views there,
|
|
63
|
+
// which loses the styling without gaining anything.
|
|
64
|
+
if (Platform.OS === 'web') return null;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
68
|
+
module = require('@expo/ui') as NativeUIModule;
|
|
69
|
+
} catch {
|
|
70
|
+
module = null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return module;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Whether `native` will actually render a platform control. */
|
|
77
|
+
export function hasNativeUI(): boolean {
|
|
78
|
+
return getNativeUI() !== null;
|
|
79
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KeyboardAvoider — a view that lifts itself just clear of the keyboard.
|
|
3
|
+
*
|
|
4
|
+
* A thin wrapper over `useKeyboardAvoidance` for the common case of "keep this
|
|
5
|
+
* box visible", so a form does not have to wire the ref, layout handler and
|
|
6
|
+
* animated style by hand.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
import type { ViewProps } from 'react-native';
|
|
10
|
+
import Animated from 'react-native-reanimated';
|
|
11
|
+
import { useKeyboardAvoidance } from '../hooks/use-keyboard-avoidance';
|
|
12
|
+
|
|
13
|
+
export interface KeyboardAvoiderProps extends ViewProps {
|
|
14
|
+
className?: string;
|
|
15
|
+
/** Set false to leave the content where it is. */
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
/** Gap to keep between the content's bottom edge and the keyboard. */
|
|
18
|
+
offset?: number;
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function KeyboardAvoider({
|
|
23
|
+
className,
|
|
24
|
+
enabled = true,
|
|
25
|
+
offset = 16,
|
|
26
|
+
children,
|
|
27
|
+
style,
|
|
28
|
+
onLayout,
|
|
29
|
+
...props
|
|
30
|
+
}: KeyboardAvoiderProps) {
|
|
31
|
+
const avoidance = useKeyboardAvoidance({ enabled, offset });
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Animated.View
|
|
35
|
+
{...props}
|
|
36
|
+
ref={avoidance.ref}
|
|
37
|
+
onLayout={(event) => {
|
|
38
|
+
avoidance.onLayout(event);
|
|
39
|
+
onLayout?.(event);
|
|
40
|
+
}}
|
|
41
|
+
className={className}
|
|
42
|
+
style={[style, avoidance.animatedStyle]}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</Animated.View>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
KeyboardAvoider.displayName = 'KeyboardAvoider';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrim — the layer behind an overlay.
|
|
3
|
+
*
|
|
4
|
+
* An overlay needs the screen behind it to recede. There are two honest ways to
|
|
5
|
+
* do that: dim it, or blur it. Dimming is free and always works; blurring reads
|
|
6
|
+
* as more physical — the content behind stays legible as shape and colour while
|
|
7
|
+
* losing its detail — but it needs a native view to do it.
|
|
8
|
+
*
|
|
9
|
+
* `expo-blur` is resolved lazily and only when `blur` is asked for, so a project
|
|
10
|
+
* that never blurs anything installs nothing. When it is asked for and the
|
|
11
|
+
* package is missing, this falls back to a dim rather than failing: a blur you
|
|
12
|
+
* cannot draw is better shown as a darkened screen than as a crash.
|
|
13
|
+
*
|
|
14
|
+
* It fills its parent and does not intercept touches itself — the overlay layers
|
|
15
|
+
* its own dismiss `Pressable` over it — so it is purely the visual backdrop.
|
|
16
|
+
*/
|
|
17
|
+
import { type ComponentType } from 'react';
|
|
18
|
+
import { StyleSheet, View, type ViewProps } from 'react-native';
|
|
19
|
+
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
|
|
20
|
+
|
|
21
|
+
interface BlurViewProps {
|
|
22
|
+
intensity?: number;
|
|
23
|
+
tint?: 'light' | 'dark' | 'default' | 'systemMaterial';
|
|
24
|
+
style?: unknown;
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `expo-blur`'s BlurView, or null when it is not installed. Resolved once at
|
|
30
|
+
* module load — the require is cheap and caching it avoids a try/catch on every
|
|
31
|
+
* render.
|
|
32
|
+
*/
|
|
33
|
+
const BlurView: ComponentType<BlurViewProps> | null = (() => {
|
|
34
|
+
try {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const mod = require('expo-blur');
|
|
37
|
+
return (mod?.BlurView as ComponentType<BlurViewProps>) ?? null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
|
|
43
|
+
/** True when a real blur can be drawn — for a caller that wants to know. */
|
|
44
|
+
export const hasBlur = BlurView !== null;
|
|
45
|
+
|
|
46
|
+
export interface ScrimProps extends Omit<ViewProps, 'children'> {
|
|
47
|
+
/** Frost the backdrop instead of dimming it. Falls back to a dim if it can't. */
|
|
48
|
+
blur?: boolean;
|
|
49
|
+
/** Blur strength, 0–100. */
|
|
50
|
+
intensity?: number;
|
|
51
|
+
/** Which way the blur tints. */
|
|
52
|
+
tint?: 'light' | 'dark' | 'default' | 'systemMaterial';
|
|
53
|
+
/**
|
|
54
|
+
* The dim used when not blurring — and when blurring is asked for but
|
|
55
|
+
* unavailable. A popover passes a lighter one than a dialog.
|
|
56
|
+
*/
|
|
57
|
+
dimClassName?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function Scrim({
|
|
61
|
+
blur = false,
|
|
62
|
+
intensity = 24,
|
|
63
|
+
tint = 'default',
|
|
64
|
+
dimClassName = 'bg-black/50',
|
|
65
|
+
style,
|
|
66
|
+
...props
|
|
67
|
+
}: ScrimProps) {
|
|
68
|
+
if (blur && BlurView) {
|
|
69
|
+
return (
|
|
70
|
+
<Animated.View
|
|
71
|
+
entering={FadeIn.duration(180)}
|
|
72
|
+
exiting={FadeOut.duration(150)}
|
|
73
|
+
style={[StyleSheet.absoluteFill, style]}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{/* A faint dim under the blur so the frost has something to sit on —
|
|
77
|
+
a pure blur over a dark scene is nearly invisible. */}
|
|
78
|
+
<BlurView intensity={intensity} tint={tint} style={StyleSheet.absoluteFill} />
|
|
79
|
+
<View className="absolute inset-0 bg-black/10" />
|
|
80
|
+
</Animated.View>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Animated.View
|
|
86
|
+
entering={FadeIn.duration(150)}
|
|
87
|
+
exiting={FadeOut.duration(150)}
|
|
88
|
+
style={[StyleSheet.absoluteFill, style]}
|
|
89
|
+
className={dimClassName}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Scrim.displayName = 'Scrim';
|
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { Fragment, type ComponentType, type ReactNode } from 'react';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
4
|
import { PortalHost, PortalProvider } from '../primitives/portal';
|
|
5
5
|
import { ToastViewport } from '../components/toast';
|
|
6
6
|
import { cn } from '../utils/cn';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* `react-native-keyboard-controller` needs its provider at the root, and
|
|
10
|
+
* forgetting it is a silent failure — the hooks return zeroes and keyboard
|
|
11
|
+
* avoidance simply does nothing. Mount it here when the package is installed
|
|
12
|
+
* so `avoidKeyboard` works without any extra setup, and fall back to a
|
|
13
|
+
* pass-through when it is not.
|
|
14
|
+
*/
|
|
15
|
+
const KeyboardProvider: ComponentType<{ children?: ReactNode }> = (() => {
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
|
+
const controller = require('react-native-keyboard-controller');
|
|
19
|
+
return controller?.KeyboardProvider ?? Fragment;
|
|
20
|
+
} catch {
|
|
21
|
+
return Fragment;
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
|
|
8
25
|
export interface PanelUIProviderProps {
|
|
9
26
|
children: ReactNode;
|
|
10
27
|
/**
|
|
@@ -38,14 +55,18 @@ export function PanelUIProvider({
|
|
|
38
55
|
}: PanelUIProviderProps) {
|
|
39
56
|
return (
|
|
40
57
|
<GestureHandlerRootView style={styles.root}>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
{/* Outermost of ours, so every field below it can avoid the keyboard.
|
|
59
|
+
A no-op Fragment when the controller is not installed. */}
|
|
60
|
+
<KeyboardProvider>
|
|
61
|
+
<View className={cn('flex-1', background && 'bg-background', className)}>
|
|
62
|
+
<PortalProvider>
|
|
63
|
+
{children}
|
|
64
|
+
{/* Sits before PortalHost so it can portal into it. */}
|
|
65
|
+
<ToastViewport />
|
|
66
|
+
<PortalHost />
|
|
67
|
+
</PortalProvider>
|
|
68
|
+
</View>
|
|
69
|
+
</KeyboardProvider>
|
|
49
70
|
</GestureHandlerRootView>
|
|
50
71
|
);
|
|
51
72
|
}
|
package/src/theme/use-theme.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface PanelThemeFamily {
|
|
|
29
29
|
/**
|
|
30
30
|
* The theme families PanelUI ships. Each sets its own palette *and* its own
|
|
31
31
|
* radius scale, so switching family changes the shape of the UI as well as
|
|
32
|
-
* its colour — Panel is the
|
|
32
|
+
* its colour — Panel is the default, Moon is sharp and monochrome,
|
|
33
33
|
* Grass is soft and green.
|
|
34
34
|
*
|
|
35
35
|
* Everything beyond `panel` must be registered in your metro config, or
|