@mrmeg/expo-ui 0.25.1 → 0.27.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/CHANGELOG.md +140 -0
- package/LLM_USAGE.md +15 -7
- package/README.md +94 -23
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/BottomSheet.d.ts +24 -4
- package/dist/components/BottomSheet.js +37 -52
- package/dist/components/DropdownMenu.js +1 -1
- package/dist/components/Icon.d.ts +24 -22
- package/dist/components/Icon.js +26 -3
- package/dist/components/Notification.js +3 -3
- package/dist/components/Popover.js +1 -1
- package/dist/components/Select.js +1 -1
- package/dist/components/StatCard.d.ts +1 -1
- package/dist/components/StyledText.d.ts +6 -2
- package/dist/components/StyledText.js +10 -4
- package/dist/components/TextInput.js +35 -11
- package/dist/components/Tooltip.js +1 -1
- package/dist/components/iconRegistry.generated.d.ts +148 -0
- package/dist/components/iconRegistry.generated.js +296 -0
- package/dist/components/keyboardFocusRegistry.d.ts +10 -2
- package/dist/components/keyboardFocusRegistry.js +13 -5
- package/dist/components/nativeTextField.android.d.ts +3 -0
- package/dist/components/nativeTextField.android.js +226 -0
- package/dist/components/nativeTextField.d.ts +14 -0
- package/dist/components/nativeTextField.js +14 -0
- package/dist/design-system.json +151 -2
- package/dist/hooks/useResources.d.ts +5 -18
- package/dist/hooks/useResources.js +5 -43
- package/llms-full.md +4 -4
- package/llms.txt +4 -1
- package/package.json +4 -3
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type { StyleProp,
|
|
3
|
-
import Feather from "@expo/vector-icons/Feather";
|
|
2
|
+
import type { StyleProp, TextStyle, ViewProps, ViewStyle } from "react-native";
|
|
4
3
|
import type { ThemeColors } from "../constants/colors";
|
|
4
|
+
import { type IconName } from "./iconRegistry.generated";
|
|
5
|
+
/**
|
|
6
|
+
* Kebab-case Lucide icon names the package bundles, served from
|
|
7
|
+
* `icon-names.json` through the generated registry. Add a name there and run
|
|
8
|
+
* `bun run ui:icons` to extend the union; pass any other Lucide component
|
|
9
|
+
* through `component` without touching the registry.
|
|
10
|
+
*/
|
|
11
|
+
export type { IconName };
|
|
5
12
|
/**
|
|
6
13
|
* Theme color names that can be used as shortcuts.
|
|
7
14
|
*
|
|
@@ -9,7 +16,6 @@ import type { ThemeColors } from "../constants/colors";
|
|
|
9
16
|
* (`foreground`, `accent`, `border`, …) and can never drift from the theme.
|
|
10
17
|
*/
|
|
11
18
|
export type ThemeColorName = keyof ThemeColors;
|
|
12
|
-
export type IconName = React.ComponentProps<typeof Feather>["name"];
|
|
13
19
|
type IconBaseProps = {
|
|
14
20
|
/** Size of the icon in pixels */
|
|
15
21
|
size?: number;
|
|
@@ -20,33 +26,29 @@ type IconBaseProps = {
|
|
|
20
26
|
/** When true, hides the icon from the accessibility tree. @default false */
|
|
21
27
|
decorative?: boolean;
|
|
22
28
|
};
|
|
23
|
-
type IconAccessibilityProps = Pick<
|
|
29
|
+
type IconAccessibilityProps = Pick<ViewProps, "accessible" | "importantForAccessibility" | "accessibilityElementsHidden" | "aria-hidden">;
|
|
30
|
+
/**
|
|
31
|
+
* Props handed to a `component`. Wide enough that any `lucide-react-native`
|
|
32
|
+
* component type-checks as-is (`LucideProps` extends `SvgProps`, whose `style`
|
|
33
|
+
* is a view style), and any component that accepts `size` and `color` works.
|
|
34
|
+
*/
|
|
24
35
|
type CustomIconComponentProps = {
|
|
25
36
|
size: number;
|
|
26
37
|
color: string;
|
|
27
|
-
style?: StyleProp<
|
|
38
|
+
style?: StyleProp<ViewStyle>;
|
|
28
39
|
} & Partial<IconAccessibilityProps>;
|
|
29
|
-
type
|
|
30
|
-
/** The icon name to render (
|
|
40
|
+
type RegistryIconProps = IconBaseProps & {
|
|
41
|
+
/** The icon name to render (a Lucide name from the package registry) */
|
|
31
42
|
name: IconName;
|
|
32
43
|
component?: never;
|
|
33
44
|
};
|
|
34
45
|
type CustomIconProps = IconBaseProps & {
|
|
35
46
|
name?: never;
|
|
36
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Custom component to render instead of a registry icon. Receives size and
|
|
49
|
+
* color as props — any `lucide-react-native` icon import works here.
|
|
50
|
+
*/
|
|
37
51
|
component: React.ComponentType<CustomIconComponentProps>;
|
|
38
52
|
};
|
|
39
|
-
export type IconProps =
|
|
40
|
-
|
|
41
|
-
* Universal Icon Component
|
|
42
|
-
* Renders @expo/vector-icons Feather with theme integration and style support
|
|
43
|
-
*
|
|
44
|
-
* Usage:
|
|
45
|
-
* ```tsx
|
|
46
|
-
* <Icon name="check" color="primary" size={16} />
|
|
47
|
-
* <Icon name="calendar" color="#FF0000" size={24} />
|
|
48
|
-
* <Icon name="terminal" style={{ marginRight: 8 }} />
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
export declare function Icon(props: IconProps): React.JSX.Element;
|
|
52
|
-
export {};
|
|
53
|
+
export type IconProps = RegistryIconProps | CustomIconProps;
|
|
54
|
+
export declare function Icon(props: IconProps): React.JSX.Element | null;
|
package/dist/components/Icon.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useTheme } from "../hooks/useTheme.js";
|
|
3
|
-
import
|
|
3
|
+
import { ICONS } from "./iconRegistry.generated";
|
|
4
4
|
/**
|
|
5
5
|
* Resolve an icon color against the active theme.
|
|
6
6
|
*
|
|
@@ -20,15 +20,26 @@ function resolveIconColor(color, themeColors) {
|
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Universal Icon Component
|
|
23
|
-
* Renders
|
|
23
|
+
* Renders `lucide-react-native` SVG icons with theme integration and style
|
|
24
|
+
* support. Icons are SVG on every platform: no font to load, and server markup
|
|
25
|
+
* carries the glyph.
|
|
24
26
|
*
|
|
25
27
|
* Usage:
|
|
26
28
|
* ```tsx
|
|
27
29
|
* <Icon name="check" color="primary" size={16} />
|
|
28
30
|
* <Icon name="calendar" color="#FF0000" size={24} />
|
|
29
31
|
* <Icon name="terminal" style={{ marginRight: 8 }} />
|
|
32
|
+
* <Icon component={Rocket} color="accent" /> // any Lucide import
|
|
30
33
|
* ```
|
|
31
34
|
*/
|
|
35
|
+
const warnedUnknownIconNames = new Set();
|
|
36
|
+
function warnUnknownIconName(name) {
|
|
37
|
+
if (!__DEV__ || warnedUnknownIconNames.has(name))
|
|
38
|
+
return;
|
|
39
|
+
warnedUnknownIconNames.add(name);
|
|
40
|
+
console.warn(`Icon: "${name}" is not in the package icon registry, so nothing was rendered. ` +
|
|
41
|
+
"Add it to icon-names.json and run `bun run ui:icons`, or pass a Lucide component through `component`.");
|
|
42
|
+
}
|
|
32
43
|
export function Icon(props) {
|
|
33
44
|
const { size = 24, color, style, decorative = false } = props;
|
|
34
45
|
const { theme } = useTheme();
|
|
@@ -42,9 +53,21 @@ export function Icon(props) {
|
|
|
42
53
|
"aria-hidden": true,
|
|
43
54
|
}
|
|
44
55
|
: { accessible: true };
|
|
56
|
+
// `style` stays a text style on the public prop for source compatibility
|
|
57
|
+
// with the font-icon era; SVG roots take a view style, and every layout
|
|
58
|
+
// property callers actually pass (margins, transforms) belongs to both.
|
|
45
59
|
const iconStyle = [style, { pointerEvents: "none" }];
|
|
46
60
|
if (CustomComponent) {
|
|
47
61
|
return (_jsx(CustomComponent, { size: size, color: iconColor, style: iconStyle, ...accessibilityProps }));
|
|
48
62
|
}
|
|
49
|
-
|
|
63
|
+
const name = props.name;
|
|
64
|
+
const Lucide = ICONS[name];
|
|
65
|
+
// `IconName` makes an unknown name a type error, but names also arrive at
|
|
66
|
+
// runtime (a database field, a config file). Rendering `undefined` would
|
|
67
|
+
// throw; draw nothing and say so once per name in development instead.
|
|
68
|
+
if (!Lucide) {
|
|
69
|
+
warnUnknownIconName(name);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return (_jsx(Lucide, { size: size, color: iconColor, style: iconStyle, ...accessibilityProps }));
|
|
50
73
|
}
|
|
@@ -156,19 +156,19 @@ export const Notification = () => {
|
|
|
156
156
|
switch (alert?.type) {
|
|
157
157
|
case "error":
|
|
158
158
|
return {
|
|
159
|
-
icon: "alert
|
|
159
|
+
icon: "circle-alert",
|
|
160
160
|
color: theme.colors.destructive,
|
|
161
161
|
bgColor: withAlpha(theme.colors.destructive, 0.08),
|
|
162
162
|
};
|
|
163
163
|
case "success":
|
|
164
164
|
return {
|
|
165
|
-
icon: "check-
|
|
165
|
+
icon: "circle-check-big",
|
|
166
166
|
color: theme.colors.success,
|
|
167
167
|
bgColor: withAlpha(theme.colors.success, 0.08),
|
|
168
168
|
};
|
|
169
169
|
case "warning":
|
|
170
170
|
return {
|
|
171
|
-
icon: "alert
|
|
171
|
+
icon: "triangle-alert",
|
|
172
172
|
color: theme.colors.warning,
|
|
173
173
|
bgColor: withAlpha(theme.colors.warning, 0.08),
|
|
174
174
|
};
|
|
@@ -41,7 +41,7 @@ function PopoverContent({ side, align = "center", sideOffset = 4, portalHost, ..
|
|
|
41
41
|
...getShadowStyle("soft"),
|
|
42
42
|
},
|
|
43
43
|
]) ?? undefined;
|
|
44
|
-
return (_jsx(PopoverPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(PopoverPrimitive.Overlay, { style: Platform.select({ native: StyleSheet.absoluteFill }), children: _jsx(AnimatedView, { type: "fade", enterDuration: 200, children: _jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(PopoverPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, style: contentStyle, ...props }) }) }) }) }) }) }));
|
|
44
|
+
return (_jsx(PopoverPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(PopoverPrimitive.Overlay, { style: Platform.select({ native: StyleSheet.absoluteFill }), children: _jsx(AnimatedView, { type: "fade", enterDuration: 200, style: StyleSheet.absoluteFill, pointerEvents: "box-none", children: _jsx(TextColorContext.Provider, { value: textColor, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(PopoverPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, style: contentStyle, ...props }) }) }) }) }) }) }));
|
|
45
45
|
}
|
|
46
46
|
function PopoverHeader({ children, style, ...props }) {
|
|
47
47
|
const { theme } = useTheme();
|
|
@@ -100,7 +100,7 @@ function SelectContent({ side, align = "start", sideOffset = 4, portalHost, styl
|
|
|
100
100
|
return (_jsx(SelectPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(SelectPrimitive.Overlay, { style: Platform.select({
|
|
101
101
|
native: StyleSheet.absoluteFill,
|
|
102
102
|
default: undefined,
|
|
103
|
-
}), children: _jsx(AnimatedView, { type: "fade", children: _jsx(TextColorContext.Provider, { value: theme.colors.popoverForeground, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(SelectPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, insets: insets, avoidCollisions: true, ...props, style: {
|
|
103
|
+
}), children: _jsx(AnimatedView, { type: "fade", style: StyleSheet.absoluteFill, pointerEvents: "box-none", children: _jsx(TextColorContext.Provider, { value: theme.colors.popoverForeground, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(SelectPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, insets: insets, avoidCollisions: true, ...props, style: {
|
|
104
104
|
backgroundColor: theme.colors.popover,
|
|
105
105
|
borderWidth: 1,
|
|
106
106
|
borderColor: theme.colors.border,
|
|
@@ -17,7 +17,7 @@ export interface StatCardProps {
|
|
|
17
17
|
unit?: string;
|
|
18
18
|
/** Optional signed change line below the value. */
|
|
19
19
|
change?: StatCardChange;
|
|
20
|
-
/** Optional leading icon name (
|
|
20
|
+
/** Optional leading icon name (Lucide), rendered beside the label. */
|
|
21
21
|
icon?: IconName;
|
|
22
22
|
/** Makes the card pressable with scale feedback (delegates to Card). */
|
|
23
23
|
onPress?: () => void;
|
|
@@ -74,8 +74,12 @@ export type TextProps = RNTextProps & {
|
|
|
74
74
|
* real Inter font files on native, and family + numeric fontWeight on web
|
|
75
75
|
* - Per-size letter-spacing scale (tight on large headings, positive on micro
|
|
76
76
|
* labels); explicit `style` letterSpacing always wins
|
|
77
|
-
* - Text selection
|
|
78
|
-
*
|
|
77
|
+
* - Text selection on by default on iOS and web, off by default on Android:
|
|
78
|
+
* a selectable Android `Text` is focusable-in-touch-mode, so a tap on a label
|
|
79
|
+
* next to a focused input moved view focus to the label and hid the keyboard.
|
|
80
|
+
* Pass `selectable` to opt copyable content in (codes, addresses, chat
|
|
81
|
+
* bubbles); pass `selectable={false}` on every platform for control chrome
|
|
82
|
+
* such as button labels, tabs, badges, and field labels
|
|
79
83
|
* - numberOfLines and ellipsizeMode support from RN TextProps
|
|
80
84
|
*/
|
|
81
85
|
export declare function StyledText(props: TextProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { use } from "react";
|
|
3
|
-
import { Text as RNText, StyleSheet } from "react-native";
|
|
3
|
+
import { Platform, Text as RNText, StyleSheet } from "react-native";
|
|
4
4
|
import { useTheme } from "../hooks/useTheme.js";
|
|
5
5
|
import { resolveFontStyle } from "../constants/fonts.js";
|
|
6
6
|
import { useThemeStore } from "../state/themeStore.js";
|
|
@@ -67,8 +67,12 @@ const SEMANTIC_CONFIGS = {
|
|
|
67
67
|
* real Inter font files on native, and family + numeric fontWeight on web
|
|
68
68
|
* - Per-size letter-spacing scale (tight on large headings, positive on micro
|
|
69
69
|
* labels); explicit `style` letterSpacing always wins
|
|
70
|
-
* - Text selection
|
|
71
|
-
*
|
|
70
|
+
* - Text selection on by default on iOS and web, off by default on Android:
|
|
71
|
+
* a selectable Android `Text` is focusable-in-touch-mode, so a tap on a label
|
|
72
|
+
* next to a focused input moved view focus to the label and hid the keyboard.
|
|
73
|
+
* Pass `selectable` to opt copyable content in (codes, addresses, chat
|
|
74
|
+
* bubbles); pass `selectable={false}` on every platform for control chrome
|
|
75
|
+
* such as button labels, tabs, badges, and field labels
|
|
72
76
|
* - numberOfLines and ellipsizeMode support from RN TextProps
|
|
73
77
|
*/
|
|
74
78
|
export function StyledText(props) {
|
|
@@ -81,7 +85,9 @@ export function StyledText(props) {
|
|
|
81
85
|
const contextColor = use(TextColorContext);
|
|
82
86
|
const contextTextStyle = use(TextStyleContext);
|
|
83
87
|
const contextSelectable = use(TextSelectabilityContext);
|
|
84
|
-
|
|
88
|
+
// Read at render time (not module load) so tests can flip `Platform.OS`.
|
|
89
|
+
const platformDefaultSelectable = Platform.OS !== "android";
|
|
90
|
+
const resolvedSelectable = selectable ?? contextSelectable ?? platformDefaultSelectable;
|
|
85
91
|
// Use context color if provided, otherwise use theme default
|
|
86
92
|
const color = contextColor ?? theme.colors.text;
|
|
87
93
|
// If semantic variant is provided, use its config
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useCallback, useImperativeHandle, useMemo, useRef, useState, } from "react";
|
|
3
3
|
import { StyleSheet, TextInput as RNTextInput, Platform, View, Pressable, } from "react-native";
|
|
4
|
-
import { Host,
|
|
4
|
+
import { Host, useNativeState, } from "@expo/ui";
|
|
5
|
+
import { NativeTextField } from "./nativeTextField";
|
|
5
6
|
import { useTheme } from "../hooks/useTheme.js";
|
|
6
7
|
import { spacing } from "../constants/spacing.js";
|
|
7
8
|
import { useFontStyle } from "../hooks/useFontStyle.js";
|
|
@@ -179,22 +180,37 @@ function WebTextInput({ variant = "outline", size = "md", label, helperText, err
|
|
|
179
180
|
}, accessibilityLabel: "Clear input", accessibilityRole: "button", children: _jsx(Icon, { name: "x", size: spacing.iconSm, color: "textDim", decorative: true }) })), showClearButton && rightElement && !hasSecureToggle && (_jsxs(View, { style: styles.rightElements, children: [_jsx(Pressable, { onPress: () => {
|
|
180
181
|
hapticLight();
|
|
181
182
|
onChangeText?.("");
|
|
182
|
-
}, accessibilityLabel: "Clear input", accessibilityRole: "button", children: _jsx(Icon, { name: "x", size: spacing.iconSm, color: "textDim", decorative: true }) }), rightElement] })), !showClearButton && rightElement && !hasSecureToggle && (_jsx(View, { style: styles.rightElement, children: rightElement })), secureTextEntry && showSecureEntryToggle && (_jsx(Pressable, { style: styles.passwordToggle, onPress: togglePasswordVisible, accessibilityLabel: passwordVisible ? "Hide password" : "Show password", accessibilityRole: "button", children: _jsx(Icon, { name: passwordVisible ? "eye-off" : "eye", size: spacing.iconSm + 4, color: "textDim" }) })), showErrorIcon && (_jsx(View, { style: [styles.errorIcon, { pointerEvents: "none" }], accessibilityLabel: "Error", children: _jsx(Icon, { name: "alert
|
|
183
|
+
}, accessibilityLabel: "Clear input", accessibilityRole: "button", children: _jsx(Icon, { name: "x", size: spacing.iconSm, color: "textDim", decorative: true }) }), rightElement] })), !showClearButton && rightElement && !hasSecureToggle && (_jsx(View, { style: styles.rightElement, children: rightElement })), secureTextEntry && showSecureEntryToggle && (_jsx(Pressable, { style: styles.passwordToggle, onPress: togglePasswordVisible, accessibilityLabel: passwordVisible ? "Hide password" : "Show password", accessibilityRole: "button", children: _jsx(Icon, { name: passwordVisible ? "eye-off" : "eye", size: spacing.iconSm + 4, color: "textDim" }) })), showErrorIcon && (_jsx(View, { style: [styles.errorIcon, { pointerEvents: "none" }], accessibilityLabel: "Error", children: _jsx(Icon, { name: "circle-alert", size: spacing.iconSm, color: "destructive", decorative: true }) }))] }), !!(helperText || errorText) && (_jsx(StyledText, { selectable: false, size: "sm", style: [
|
|
183
184
|
styles.helperText,
|
|
184
185
|
hasError && styles.errorText,
|
|
185
186
|
], children: errorText || helperText }))] }));
|
|
186
187
|
}
|
|
187
188
|
/**
|
|
188
|
-
* Native (iOS / Android) implementation backed by
|
|
189
|
-
*
|
|
190
|
-
* `
|
|
191
|
-
*
|
|
192
|
-
*
|
|
189
|
+
* Native (iOS / Android) implementation backed by a native text field rendered
|
|
190
|
+
* inside an `@expo/ui` `Host`. The text buffer lives natively (via
|
|
191
|
+
* `useNativeState`), so typing never round-trips through React state —
|
|
192
|
+
* eliminating the cursor flicker seen on controlled RN inputs.
|
|
193
|
+
*
|
|
194
|
+
* The field comes from `./nativeTextField`, a platform-split module. iOS gets
|
|
195
|
+
* `@expo/ui`'s universal TextInput (SwiftUI `TextField`/`SecureField`, which
|
|
196
|
+
* already reports a password field to the keyboard). Android gets a
|
|
197
|
+
* package-owned port of that universal field built on
|
|
198
|
+
* `@expo/ui/jetpack-compose`'s `BasicTextField` (`nativeTextField.android.tsx`),
|
|
199
|
+
* because the universal Android field maps `secureTextEntry` to a masking
|
|
200
|
+
* transformation only and offers no way to set Compose's `KeyboardType.Password`
|
|
201
|
+
* — the IME saw a plain text field, kept its suggestion strip and learned the
|
|
202
|
+
* password. The port declares a password keyboard for secure fields and keeps it
|
|
203
|
+
* across the eye toggle, so revealing the text never restarts the IME. Metro
|
|
204
|
+
* picks the platform file; TypeScript types both through the default file.
|
|
193
205
|
*
|
|
194
206
|
* This path supports label / helper / error text and a password visibility
|
|
195
207
|
* toggle laid out beside the native Host. Clear buttons, left/right elements,
|
|
196
208
|
* and the error icon remain web-only; RN overlays are not layered over the
|
|
197
209
|
* native host view.
|
|
210
|
+
*
|
|
211
|
+
* On Android `onBlur` is reported only after a forwarded `onFocus`: Compose
|
|
212
|
+
* reports `isFocused=false` on first composition and the field forwards it,
|
|
213
|
+
* so an unguarded consumer saw a blur before any interaction.
|
|
198
214
|
*/
|
|
199
215
|
function NativeTextInput({ variant = "outline", size = "md", label, helperText, errorText, error, required, rows, forceLight, inputMode, onChangeText, onFocus, onBlur, value, defaultValue, editable = true, multiline, secureTextEntry, showSecureEntryToggle, ref, wrapperStyle,
|
|
200
216
|
// Web-only affordances. Destructured out of `rest` so they're NOT forwarded to
|
|
@@ -293,9 +309,10 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
293
309
|
if (Platform.OS === "ios" && secure !== activeSecureRef.current)
|
|
294
310
|
return;
|
|
295
311
|
isFocusedRef.current = true;
|
|
296
|
-
// Register the field's own blur handle for tap-away dismissal
|
|
297
|
-
// BottomSheet
|
|
298
|
-
// ref also targets the actual field inside a native
|
|
312
|
+
// Register the field's own blur handle for tap-away dismissal
|
|
313
|
+
// (`DismissKeyboard`, `BottomSheet.Content`). RN supports Expo-hosted field
|
|
314
|
+
// interop; this direct ref also targets the actual field inside a native
|
|
315
|
+
// sheet's window.
|
|
299
316
|
setKeyboardFocusedInput(focusRegistryToken, blurAll);
|
|
300
317
|
if (handoffRef.current) {
|
|
301
318
|
// The incoming view took first responder; the parent never saw focus leave.
|
|
@@ -308,9 +325,16 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
308
325
|
const handleBlur = useCallback((secure) => {
|
|
309
326
|
if (Platform.OS === "ios" && secure !== activeSecureRef.current)
|
|
310
327
|
return;
|
|
328
|
+
// Compose reports `isFocused=false` when the field is first composed and
|
|
329
|
+
// the Android field forwards it as a blur. A blur the field never
|
|
330
|
+
// preceded with a focus is not the user leaving the field: keep it from
|
|
331
|
+
// the consumer so blur validation does not flag untouched fields.
|
|
332
|
+
const wasFocused = isFocusedRef.current;
|
|
311
333
|
isFocusedRef.current = false;
|
|
312
334
|
restoreAfterSecureHandoffRef.current = false;
|
|
313
335
|
clearKeyboardFocusedInput(focusRegistryToken);
|
|
336
|
+
if (Platform.OS === "android" && !wasFocused)
|
|
337
|
+
return;
|
|
314
338
|
parentOnBlur?.();
|
|
315
339
|
}, [focusRegistryToken, parentOnBlur]);
|
|
316
340
|
const togglePasswordVisible = useCallback(() => {
|
|
@@ -475,7 +499,7 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
475
499
|
? hasSecureToggle
|
|
476
500
|
? styles.nativeHostFlex
|
|
477
501
|
: styles.nativeHost
|
|
478
|
-
: styles.nativeHostHandoff, pointerEvents: inLayout ? "auto" : "none", accessibilityElementsHidden: !inLayout, importantForAccessibility: inLayout ? "auto" : "no-hide-descendants", children: _jsx(
|
|
502
|
+
: styles.nativeHostHandoff, pointerEvents: inLayout ? "auto" : "none", accessibilityElementsHidden: !inLayout, importantForAccessibility: inLayout ? "auto" : "no-hide-descendants", children: _jsx(NativeTextField, { ...rest, autoFocus: rest.autoFocus || (handoff != null && active), ref: Platform.OS === "android" ? androidRef : secure ? secureRef : plainRef, value: state, defaultValue: defaultValue, onChangeText: handleChangeText, onFocus: () => handleFocus(secure), onBlur: () => handleBlur(secure), editable: editable, multiline: multiline, rows: rows, inputMode: inputMode, secureTextEntry: secure, placeholderTextColor: theme.colors.textDim, style: boxStyle, textStyle: textStyle }) }, Platform.OS === "android"
|
|
479
503
|
? "android"
|
|
480
504
|
: secure
|
|
481
505
|
? `secure-${generationRef.current.secure}`
|
|
@@ -71,7 +71,7 @@ function TooltipContent({ side = "top", align = "center", sideOffset = 4, portal
|
|
|
71
71
|
...getShadowStyle("soft"),
|
|
72
72
|
},
|
|
73
73
|
]) ?? undefined;
|
|
74
|
-
return (_jsx(TooltipPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(TooltipPrimitive.Overlay, { style: Platform.select({ native: StyleSheet.absoluteFill }), children: _jsx(AnimatedView, { type: "fade", enterDuration: 150, children: _jsx(TextColorContext.Provider, { value: colors.text, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(TooltipPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, style: contentStyle, ...props }) }) }) }) }) }) }) }));
|
|
74
|
+
return (_jsx(TooltipPrimitive.Portal, { hostName: portalHost, children: _jsx(FullWindowOverlay, { children: _jsx(TooltipPrimitive.Overlay, { style: Platform.select({ native: StyleSheet.absoluteFill }), children: _jsx(AnimatedView, { type: "fade", enterDuration: 150, style: StyleSheet.absoluteFill, pointerEvents: "box-none", children: _jsx(TextColorContext.Provider, { value: colors.text, children: _jsx(TextClassContext.Provider, { value: "", children: _jsx(TextSelectabilityContext.Provider, { value: false, children: _jsx(TooltipPrimitive.Content, { side: side, align: align, sideOffset: sideOffset, style: contentStyle, ...props }) }) }) }) }) }) }) }));
|
|
75
75
|
}
|
|
76
76
|
function TooltipBody({ children, style, ...props }) {
|
|
77
77
|
return (_jsx(View, { style: [
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export declare const ICONS: {
|
|
2
|
+
readonly archive: import("lucide-react-native").LucideIcon;
|
|
3
|
+
readonly "arrow-down": import("lucide-react-native").LucideIcon;
|
|
4
|
+
readonly "arrow-left": import("lucide-react-native").LucideIcon;
|
|
5
|
+
readonly "arrow-right": import("lucide-react-native").LucideIcon;
|
|
6
|
+
readonly "arrow-up": import("lucide-react-native").LucideIcon;
|
|
7
|
+
readonly award: import("lucide-react-native").LucideIcon;
|
|
8
|
+
readonly beer: import("lucide-react-native").LucideIcon;
|
|
9
|
+
readonly bell: import("lucide-react-native").LucideIcon;
|
|
10
|
+
readonly "bell-off": import("lucide-react-native").LucideIcon;
|
|
11
|
+
readonly bold: import("lucide-react-native").LucideIcon;
|
|
12
|
+
readonly bookmark: import("lucide-react-native").LucideIcon;
|
|
13
|
+
readonly box: import("lucide-react-native").LucideIcon;
|
|
14
|
+
readonly briefcase: import("lucide-react-native").LucideIcon;
|
|
15
|
+
readonly calendar: import("lucide-react-native").LucideIcon;
|
|
16
|
+
readonly "calendar-check": import("lucide-react-native").LucideIcon;
|
|
17
|
+
readonly camera: import("lucide-react-native").LucideIcon;
|
|
18
|
+
readonly "camera-off": import("lucide-react-native").LucideIcon;
|
|
19
|
+
readonly "chart-no-axes-column": import("lucide-react-native").LucideIcon;
|
|
20
|
+
readonly check: import("lucide-react-native").LucideIcon;
|
|
21
|
+
readonly "chevron-down": import("lucide-react-native").LucideIcon;
|
|
22
|
+
readonly "chevron-left": import("lucide-react-native").LucideIcon;
|
|
23
|
+
readonly "chevron-right": import("lucide-react-native").LucideIcon;
|
|
24
|
+
readonly "chevron-up": import("lucide-react-native").LucideIcon;
|
|
25
|
+
readonly "circle-alert": import("lucide-react-native").LucideIcon;
|
|
26
|
+
readonly "circle-check-big": import("lucide-react-native").LucideIcon;
|
|
27
|
+
readonly "circle-plus": import("lucide-react-native").LucideIcon;
|
|
28
|
+
readonly "circle-question-mark": import("lucide-react-native").LucideIcon;
|
|
29
|
+
readonly "circle-stop": import("lucide-react-native").LucideIcon;
|
|
30
|
+
readonly "circle-x": import("lucide-react-native").LucideIcon;
|
|
31
|
+
readonly clock: import("lucide-react-native").LucideIcon;
|
|
32
|
+
readonly cloud: import("lucide-react-native").LucideIcon;
|
|
33
|
+
readonly "cloud-off": import("lucide-react-native").LucideIcon;
|
|
34
|
+
readonly code: import("lucide-react-native").LucideIcon;
|
|
35
|
+
readonly coffee: import("lucide-react-native").LucideIcon;
|
|
36
|
+
readonly compass: import("lucide-react-native").LucideIcon;
|
|
37
|
+
readonly copy: import("lucide-react-native").LucideIcon;
|
|
38
|
+
readonly "credit-card": import("lucide-react-native").LucideIcon;
|
|
39
|
+
readonly crosshair: import("lucide-react-native").LucideIcon;
|
|
40
|
+
readonly database: import("lucide-react-native").LucideIcon;
|
|
41
|
+
readonly "dollar-sign": import("lucide-react-native").LucideIcon;
|
|
42
|
+
readonly download: import("lucide-react-native").LucideIcon;
|
|
43
|
+
readonly droplet: import("lucide-react-native").LucideIcon;
|
|
44
|
+
readonly "ellipsis-vertical": import("lucide-react-native").LucideIcon;
|
|
45
|
+
readonly "external-link": import("lucide-react-native").LucideIcon;
|
|
46
|
+
readonly eye: import("lucide-react-native").LucideIcon;
|
|
47
|
+
readonly "eye-off": import("lucide-react-native").LucideIcon;
|
|
48
|
+
readonly "face-slightly-smiling": import("lucide-react-native").LucideIcon;
|
|
49
|
+
readonly file: import("lucide-react-native").LucideIcon;
|
|
50
|
+
readonly "file-text": import("lucide-react-native").LucideIcon;
|
|
51
|
+
readonly folder: import("lucide-react-native").LucideIcon;
|
|
52
|
+
readonly gift: import("lucide-react-native").LucideIcon;
|
|
53
|
+
readonly "git-branch": import("lucide-react-native").LucideIcon;
|
|
54
|
+
readonly "git-pull-request": import("lucide-react-native").LucideIcon;
|
|
55
|
+
readonly globe: import("lucide-react-native").LucideIcon;
|
|
56
|
+
readonly "grid-3x3": import("lucide-react-native").LucideIcon;
|
|
57
|
+
readonly hash: import("lucide-react-native").LucideIcon;
|
|
58
|
+
readonly heart: import("lucide-react-native").LucideIcon;
|
|
59
|
+
readonly hexagon: import("lucide-react-native").LucideIcon;
|
|
60
|
+
readonly house: import("lucide-react-native").LucideIcon;
|
|
61
|
+
readonly image: import("lucide-react-native").LucideIcon;
|
|
62
|
+
readonly inbox: import("lucide-react-native").LucideIcon;
|
|
63
|
+
readonly info: import("lucide-react-native").LucideIcon;
|
|
64
|
+
readonly italic: import("lucide-react-native").LucideIcon;
|
|
65
|
+
readonly key: import("lucide-react-native").LucideIcon;
|
|
66
|
+
readonly layers: import("lucide-react-native").LucideIcon;
|
|
67
|
+
readonly "layout-grid": import("lucide-react-native").LucideIcon;
|
|
68
|
+
readonly link: import("lucide-react-native").LucideIcon;
|
|
69
|
+
readonly "link-2": import("lucide-react-native").LucideIcon;
|
|
70
|
+
readonly list: import("lucide-react-native").LucideIcon;
|
|
71
|
+
readonly loader: import("lucide-react-native").LucideIcon;
|
|
72
|
+
readonly lock: import("lucide-react-native").LucideIcon;
|
|
73
|
+
readonly "lock-open": import("lucide-react-native").LucideIcon;
|
|
74
|
+
readonly "log-in": import("lucide-react-native").LucideIcon;
|
|
75
|
+
readonly "log-out": import("lucide-react-native").LucideIcon;
|
|
76
|
+
readonly mail: import("lucide-react-native").LucideIcon;
|
|
77
|
+
readonly map: import("lucide-react-native").LucideIcon;
|
|
78
|
+
readonly "map-pin": import("lucide-react-native").LucideIcon;
|
|
79
|
+
readonly maximize: import("lucide-react-native").LucideIcon;
|
|
80
|
+
readonly "maximize-2": import("lucide-react-native").LucideIcon;
|
|
81
|
+
readonly menu: import("lucide-react-native").LucideIcon;
|
|
82
|
+
readonly "message-circle": import("lucide-react-native").LucideIcon;
|
|
83
|
+
readonly "message-square": import("lucide-react-native").LucideIcon;
|
|
84
|
+
readonly mic: import("lucide-react-native").LucideIcon;
|
|
85
|
+
readonly "mic-off": import("lucide-react-native").LucideIcon;
|
|
86
|
+
readonly minus: import("lucide-react-native").LucideIcon;
|
|
87
|
+
readonly monitor: import("lucide-react-native").LucideIcon;
|
|
88
|
+
readonly moon: import("lucide-react-native").LucideIcon;
|
|
89
|
+
readonly music: import("lucide-react-native").LucideIcon;
|
|
90
|
+
readonly navigation: import("lucide-react-native").LucideIcon;
|
|
91
|
+
readonly package: import("lucide-react-native").LucideIcon;
|
|
92
|
+
readonly "panel-left": import("lucide-react-native").LucideIcon;
|
|
93
|
+
readonly "panels-top-left": import("lucide-react-native").LucideIcon;
|
|
94
|
+
readonly "party-popper": import("lucide-react-native").LucideIcon;
|
|
95
|
+
readonly pen: import("lucide-react-native").LucideIcon;
|
|
96
|
+
readonly "pen-line": import("lucide-react-native").LucideIcon;
|
|
97
|
+
readonly pencil: import("lucide-react-native").LucideIcon;
|
|
98
|
+
readonly percent: import("lucide-react-native").LucideIcon;
|
|
99
|
+
readonly phone: import("lucide-react-native").LucideIcon;
|
|
100
|
+
readonly play: import("lucide-react-native").LucideIcon;
|
|
101
|
+
readonly plus: import("lucide-react-native").LucideIcon;
|
|
102
|
+
readonly printer: import("lucide-react-native").LucideIcon;
|
|
103
|
+
readonly radio: import("lucide-react-native").LucideIcon;
|
|
104
|
+
readonly "refresh-cw": import("lucide-react-native").LucideIcon;
|
|
105
|
+
readonly repeat: import("lucide-react-native").LucideIcon;
|
|
106
|
+
readonly "rotate-cw": import("lucide-react-native").LucideIcon;
|
|
107
|
+
readonly search: import("lucide-react-native").LucideIcon;
|
|
108
|
+
readonly send: import("lucide-react-native").LucideIcon;
|
|
109
|
+
readonly server: import("lucide-react-native").LucideIcon;
|
|
110
|
+
readonly settings: import("lucide-react-native").LucideIcon;
|
|
111
|
+
readonly share: import("lucide-react-native").LucideIcon;
|
|
112
|
+
readonly "share-2": import("lucide-react-native").LucideIcon;
|
|
113
|
+
readonly shield: import("lucide-react-native").LucideIcon;
|
|
114
|
+
readonly "shield-off": import("lucide-react-native").LucideIcon;
|
|
115
|
+
readonly "shopping-bag": import("lucide-react-native").LucideIcon;
|
|
116
|
+
readonly "shopping-cart": import("lucide-react-native").LucideIcon;
|
|
117
|
+
readonly shuffle: import("lucide-react-native").LucideIcon;
|
|
118
|
+
readonly slash: import("lucide-react-native").LucideIcon;
|
|
119
|
+
readonly "sliders-horizontal": import("lucide-react-native").LucideIcon;
|
|
120
|
+
readonly "sliders-vertical": import("lucide-react-native").LucideIcon;
|
|
121
|
+
readonly smartphone: import("lucide-react-native").LucideIcon;
|
|
122
|
+
readonly square: import("lucide-react-native").LucideIcon;
|
|
123
|
+
readonly "square-check-big": import("lucide-react-native").LucideIcon;
|
|
124
|
+
readonly "square-pen": import("lucide-react-native").LucideIcon;
|
|
125
|
+
readonly star: import("lucide-react-native").LucideIcon;
|
|
126
|
+
readonly sun: import("lucide-react-native").LucideIcon;
|
|
127
|
+
readonly tag: import("lucide-react-native").LucideIcon;
|
|
128
|
+
readonly terminal: import("lucide-react-native").LucideIcon;
|
|
129
|
+
readonly "thumbs-up": import("lucide-react-native").LucideIcon;
|
|
130
|
+
readonly trash: import("lucide-react-native").LucideIcon;
|
|
131
|
+
readonly "trending-down": import("lucide-react-native").LucideIcon;
|
|
132
|
+
readonly "trending-up": import("lucide-react-native").LucideIcon;
|
|
133
|
+
readonly "triangle-alert": import("lucide-react-native").LucideIcon;
|
|
134
|
+
readonly truck: import("lucide-react-native").LucideIcon;
|
|
135
|
+
readonly underline: import("lucide-react-native").LucideIcon;
|
|
136
|
+
readonly upload: import("lucide-react-native").LucideIcon;
|
|
137
|
+
readonly user: import("lucide-react-native").LucideIcon;
|
|
138
|
+
readonly "user-check": import("lucide-react-native").LucideIcon;
|
|
139
|
+
readonly "user-minus": import("lucide-react-native").LucideIcon;
|
|
140
|
+
readonly "user-plus": import("lucide-react-native").LucideIcon;
|
|
141
|
+
readonly users: import("lucide-react-native").LucideIcon;
|
|
142
|
+
readonly video: import("lucide-react-native").LucideIcon;
|
|
143
|
+
readonly "wifi-off": import("lucide-react-native").LucideIcon;
|
|
144
|
+
readonly wrench: import("lucide-react-native").LucideIcon;
|
|
145
|
+
readonly x: import("lucide-react-native").LucideIcon;
|
|
146
|
+
readonly zap: import("lucide-react-native").LucideIcon;
|
|
147
|
+
};
|
|
148
|
+
export type IconName = keyof typeof ICONS;
|