@mrmeg/expo-ui 0.25.0 → 0.26.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 +118 -0
- package/LLM_USAGE.md +13 -1
- package/README.md +89 -18
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/BottomSheet.d.ts +1 -1
- package/dist/components/BottomSheet.js +28 -51
- package/dist/components/DismissKeyboard.d.ts +8 -6
- package/dist/components/DismissKeyboard.js +9 -7
- 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/TextInput.d.ts +1 -1
- package/dist/components/TextInput.js +55 -35
- 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/keyboardDismiss.js +126 -55
- package/dist/components/keyboardFocusRegistry.d.ts +14 -4
- 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/constants/spacing.d.ts +2 -2
- package/dist/constants/spacing.js +2 -2
- package/dist/design-system.json +152 -3
- package/dist/hooks/useResources.d.ts +5 -18
- package/dist/hooks/useResources.js +5 -43
- package/dist/state/themeStore.d.ts +1 -1
- package/llms-full.md +3 -3
- 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;
|
|
@@ -10,7 +10,7 @@ export type TextInputSize = "sm" | "md" | "lg";
|
|
|
10
10
|
export type TextInputVariant = "outline" | "filled" | "underlined";
|
|
11
11
|
interface TextInputCustomProps extends TextInputProps {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* RNTextInput ref on web; an RN-compatible imperative handle on native.
|
|
14
14
|
*
|
|
15
15
|
* Written as `ComponentRef<typeof RNTextInput>` rather than RN 0.88's
|
|
16
16
|
* `TextInputInstance` alias so the emitted declarations stay valid on
|
|
@@ -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,23 +180,33 @@ 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
|
-
* controlled RN inputs.
|
|
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
193
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
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.
|
|
205
|
+
*
|
|
206
|
+
* This path supports label / helper / error text and a password visibility
|
|
207
|
+
* toggle laid out beside the native Host. Clear buttons, left/right elements,
|
|
208
|
+
* and the error icon remain web-only; RN overlays are not layered over the
|
|
209
|
+
* native host view.
|
|
199
210
|
*/
|
|
200
211
|
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,
|
|
201
212
|
// Web-only affordances. Destructured out of `rest` so they're NOT forwarded to
|
|
@@ -231,9 +242,9 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
231
242
|
// parity) once it settles.
|
|
232
243
|
const queuedTogglesRef = useRef(0);
|
|
233
244
|
const toggleRef = useRef(() => { });
|
|
234
|
-
//
|
|
235
|
-
// view so React
|
|
236
|
-
// instead of reusing one
|
|
245
|
+
// iOS-only per-flavour mount generation. Bumped whenever a flavour becomes
|
|
246
|
+
// the incoming view so React mounts a FRESH native view (whose `autoFocus`
|
|
247
|
+
// will run) instead of reusing one still unmounting from the previous handoff.
|
|
237
248
|
const generationRef = useRef({ secure: 0, plain: 0 });
|
|
238
249
|
// Last text the JS side knows about; see handleChangeText.
|
|
239
250
|
const lastTextRef = useRef(value ?? defaultValue ?? "");
|
|
@@ -256,12 +267,18 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
256
267
|
// RNTextInput because that's what the public TextInputCustomProps declares.
|
|
257
268
|
// We expose the subset consumers use, plus a `setNativeProps` shim so the
|
|
258
269
|
// uncontrolled AuthTextField can push corrected text into the native buffer.
|
|
259
|
-
//
|
|
260
|
-
// iOS
|
|
270
|
+
// Android keeps one field/ref as visualTransformation changes in place.
|
|
271
|
+
// iOS has one ref per flavour; both are mounted only during a handoff.
|
|
272
|
+
const androidRef = useRef(null);
|
|
261
273
|
const secureRef = useRef(null);
|
|
262
274
|
const plainRef = useRef(null);
|
|
263
|
-
const activeInput = useCallback(() =>
|
|
275
|
+
const activeInput = useCallback(() => {
|
|
276
|
+
if (Platform.OS === "android")
|
|
277
|
+
return androidRef.current;
|
|
278
|
+
return (activeSecureRef.current ? secureRef : plainRef).current;
|
|
279
|
+
}, []);
|
|
264
280
|
const blurAll = useCallback(() => {
|
|
281
|
+
androidRef.current?.blur();
|
|
265
282
|
secureRef.current?.blur();
|
|
266
283
|
plainRef.current?.blur();
|
|
267
284
|
}, []);
|
|
@@ -282,19 +299,16 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
282
299
|
}, []);
|
|
283
300
|
const parentOnFocus = onFocus;
|
|
284
301
|
const parentOnBlur = onBlur;
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
// React is unmounting.
|
|
302
|
+
// On iOS, ignore focus/blur from the outgoing flavour during a handoff or
|
|
303
|
+
// unmount. Android events always belong to the same mounted field.
|
|
288
304
|
const handleFocus = useCallback((secure) => {
|
|
289
|
-
if (secure !== activeSecureRef.current)
|
|
305
|
+
if (Platform.OS === "ios" && secure !== activeSecureRef.current)
|
|
290
306
|
return;
|
|
291
307
|
isFocusedRef.current = true;
|
|
292
|
-
// Register
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
// native bottom sheet `KeyboardController.dismiss()` can miss, the ref
|
|
297
|
-
// never does.
|
|
308
|
+
// Register the field's own blur handle for tap-away dismissal
|
|
309
|
+
// (`DismissKeyboard`, `BottomSheet.Content`). RN supports Expo-hosted field
|
|
310
|
+
// interop; this direct ref also targets the actual field inside a native
|
|
311
|
+
// sheet's window.
|
|
298
312
|
setKeyboardFocusedInput(focusRegistryToken, blurAll);
|
|
299
313
|
if (handoffRef.current) {
|
|
300
314
|
// The incoming view took first responder; the parent never saw focus leave.
|
|
@@ -305,7 +319,7 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
305
319
|
parentOnFocus?.();
|
|
306
320
|
}, [blurAll, finishHandoff, focusRegistryToken, parentOnFocus]);
|
|
307
321
|
const handleBlur = useCallback((secure) => {
|
|
308
|
-
if (secure !== activeSecureRef.current)
|
|
322
|
+
if (Platform.OS === "ios" && secure !== activeSecureRef.current)
|
|
309
323
|
return;
|
|
310
324
|
isFocusedRef.current = false;
|
|
311
325
|
restoreAfterSecureHandoffRef.current = false;
|
|
@@ -321,10 +335,12 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
321
335
|
}
|
|
322
336
|
restoreAfterSecureHandoffRef.current = false;
|
|
323
337
|
const outgoingSecure = activeSecureRef.current;
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
338
|
+
if (Platform.OS === "ios") {
|
|
339
|
+
if (outgoingSecure)
|
|
340
|
+
generationRef.current.plain += 1;
|
|
341
|
+
else
|
|
342
|
+
generationRef.current.secure += 1;
|
|
343
|
+
}
|
|
328
344
|
if (Platform.OS === "ios" && isFocusedRef.current) {
|
|
329
345
|
const timer = setTimeout(() => {
|
|
330
346
|
// The incoming view never took first responder. Rather than unmount
|
|
@@ -460,7 +476,7 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
460
476
|
fontSize: sizeConfig.fontSize,
|
|
461
477
|
...(nativeFontFamily ? { fontFamily: nativeFontFamily } : null),
|
|
462
478
|
};
|
|
463
|
-
return (_jsxs(View, { style: wrapperStyle, children: [!!label && (_jsx(View, { style: styles.labelContainer, children: _jsxs(StyledText, { selectable: false, size: "base", fontWeight: "medium", style: styles.label, children: [label, required && _jsx(StyledText, { selectable: false, fontWeight: "bold", style: styles.required, children: " *" })] }) })), _jsxs(View, { style: [surfaceStyle, hasSecureToggle && styles.nativeRow], hitSlop: { top: surfaceHitSlop, bottom: surfaceHitSlop }, ...surfaceResponderProps, children: [[true, false].map((secure) => {
|
|
479
|
+
return (_jsxs(View, { style: wrapperStyle, children: [!!label && (_jsx(View, { style: styles.labelContainer, children: _jsxs(StyledText, { selectable: false, size: "base", fontWeight: "medium", style: styles.label, children: [label, required && _jsx(StyledText, { selectable: false, fontWeight: "bold", style: styles.required, children: " *" })] }) })), _jsxs(View, { style: [surfaceStyle, hasSecureToggle && styles.nativeRow], hitSlop: { top: surfaceHitSlop, bottom: surfaceHitSlop }, ...surfaceResponderProps, children: [(Platform.OS === "android" ? [effectiveSecureTextEntry] : [true, false]).map((secure) => {
|
|
464
480
|
const active = secure === effectiveSecureTextEntry;
|
|
465
481
|
const outgoing = handoff != null && secure === handoff.outgoingSecure;
|
|
466
482
|
if (!active && !outgoing)
|
|
@@ -472,7 +488,11 @@ leftElement, rightElement, clearable, focusedStyle, style, ...rest }) {
|
|
|
472
488
|
? hasSecureToggle
|
|
473
489
|
? styles.nativeHostFlex
|
|
474
490
|
: styles.nativeHost
|
|
475
|
-
: styles.nativeHostHandoff, pointerEvents: inLayout ? "auto" : "none", accessibilityElementsHidden: !inLayout, importantForAccessibility: inLayout ? "auto" : "no-hide-descendants", children: _jsx(
|
|
491
|
+
: 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"
|
|
492
|
+
? "android"
|
|
493
|
+
: secure
|
|
494
|
+
? `secure-${generationRef.current.secure}`
|
|
495
|
+
: `plain-${generationRef.current.plain}`));
|
|
476
496
|
}), hasSecureToggle && (_jsx(Pressable, { style: styles.nativePasswordToggle, onPress: togglePasswordVisible, accessibilityLabel: passwordVisible ? "Hide password" : "Show password", accessibilityRole: "button", children: _jsx(Icon, { name: passwordVisible ? "eye-off" : "eye", size: spacing.iconSm + 4, color: "textDim" }) }))] }), !!(helperText || errorText) && (_jsx(StyledText, { selectable: false, size: "sm", style: [styles.helperText, hasError && styles.errorText], children: errorText || helperText }))] }));
|
|
477
497
|
}
|
|
478
498
|
const createStyles = (theme, variant, size) => StyleSheet.create({
|
|
@@ -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;
|