@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.
@@ -1,7 +1,14 @@
1
1
  import * as React from "react";
2
- import type { StyleProp, TextProps, TextStyle } from "react-native";
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<TextProps, "accessible" | "importantForAccessibility" | "accessibilityElementsHidden" | "aria-hidden">;
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<TextStyle>;
38
+ style?: StyleProp<ViewStyle>;
28
39
  } & Partial<IconAccessibilityProps>;
29
- type FeatherIconProps = IconBaseProps & {
30
- /** The icon name to render (Feather icons) */
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
- /** Custom component to render instead of Feather. Receives size and color as props. */
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 = FeatherIconProps | CustomIconProps;
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;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useTheme } from "../hooks/useTheme.js";
3
- import Feather from "@expo/vector-icons/Feather";
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 @expo/vector-icons Feather with theme integration and style support
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
- return (_jsx(Feather, { name: props.name, size: size, color: iconColor, style: iconStyle, ...accessibilityProps }));
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-circle",
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-circle",
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-triangle",
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 (Feather), rendered beside the label. */
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 enabled by default; pass `selectable={false}` for control
78
- * chrome such as button labels, tabs, badges, and field labels
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 enabled by default; pass `selectable={false}` for control
71
- * chrome such as button labels, tabs, badges, and field labels
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
- const resolvedSelectable = selectable ?? contextSelectable ?? true;
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, TextInput as ExpoTextInput, useNativeState, } from "@expo/ui";
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-circle", size: spacing.iconSm, color: "destructive", decorative: true }) }))] }), !!(helperText || errorText) && (_jsx(StyledText, { selectable: false, size: "sm", style: [
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 `@expo/ui`'s TextInput, which
189
- * bridges to SwiftUI's `TextField`/`SecureField` and Jetpack Compose's
190
- * `TextField`. The text buffer lives natively (via `useNativeState`), so typing
191
- * never round-trips through React state — eliminating the cursor flicker seen on
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
+ *
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 and the
297
- // BottomSheet overlay. RN supports Expo-hosted field interop; this direct
298
- // ref also targets the actual field inside a native sheet's window.
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(ExpoTextInput, { ...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"
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;