@mrmeg/expo-ui 0.11.0 → 0.13.0-rsd.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LLM_USAGE.md +1 -0
  3. package/README.md +2 -1
  4. package/dist/components/Accordion.d.ts +38 -18
  5. package/dist/components/Accordion.js +137 -121
  6. package/dist/components/AnimatedView.d.ts +13 -24
  7. package/dist/components/AnimatedView.js +70 -29
  8. package/dist/components/Badge.d.ts +2 -2
  9. package/dist/components/Badge.js +45 -47
  10. package/dist/components/BottomSheet.d.ts +30 -73
  11. package/dist/components/BottomSheet.js +240 -188
  12. package/dist/components/Button.d.ts +43 -56
  13. package/dist/components/Button.js +189 -203
  14. package/dist/components/Card.d.ts +7 -10
  15. package/dist/components/Card.js +90 -105
  16. package/dist/components/Checkbox.d.ts +20 -8
  17. package/dist/components/Checkbox.js +77 -80
  18. package/dist/components/Collapsible.d.ts +47 -47
  19. package/dist/components/Collapsible.js +108 -29
  20. package/dist/components/Dialog.d.ts +80 -121
  21. package/dist/components/Dialog.js +225 -132
  22. package/dist/components/DismissKeyboard.d.ts +1 -1
  23. package/dist/components/DismissKeyboard.js +73 -22
  24. package/dist/components/Drawer.d.ts +37 -76
  25. package/dist/components/Drawer.js +252 -486
  26. package/dist/components/DropdownMenu.d.ts +106 -113
  27. package/dist/components/DropdownMenu.js +350 -204
  28. package/dist/components/EmptyState.d.ts +2 -2
  29. package/dist/components/EmptyState.js +41 -34
  30. package/dist/components/InputOTP.d.ts +19 -53
  31. package/dist/components/InputOTP.js +81 -102
  32. package/dist/components/KeyboardAvoidingView.d.ts +23 -0
  33. package/dist/components/KeyboardAvoidingView.js +33 -0
  34. package/dist/components/Label.d.ts +12 -17
  35. package/dist/components/Label.js +38 -51
  36. package/dist/components/MaxWidthContainer.d.ts +5 -16
  37. package/dist/components/MaxWidthContainer.js +28 -29
  38. package/dist/components/Notification.d.ts +5 -9
  39. package/dist/components/Notification.js +190 -187
  40. package/dist/components/Popover.d.ts +38 -66
  41. package/dist/components/Popover.js +158 -69
  42. package/dist/components/Progress.d.ts +5 -4
  43. package/dist/components/Progress.js +65 -77
  44. package/dist/components/RadioGroup.d.ts +30 -24
  45. package/dist/components/RadioGroup.js +90 -94
  46. package/dist/components/Select.d.ts +62 -74
  47. package/dist/components/Select.js +241 -154
  48. package/dist/components/Separator.d.ts +13 -23
  49. package/dist/components/Separator.js +29 -36
  50. package/dist/components/Skeleton.d.ts +8 -7
  51. package/dist/components/Skeleton.js +74 -61
  52. package/dist/components/StyledText.context.d.ts +6 -2
  53. package/dist/components/StyledText.context.js +3 -0
  54. package/dist/components/StyledText.d.ts +29 -7
  55. package/dist/components/StyledText.js +92 -29
  56. package/dist/components/Switch.d.ts +18 -6
  57. package/dist/components/Switch.js +112 -106
  58. package/dist/components/Tabs.d.ts +26 -16
  59. package/dist/components/Tabs.js +189 -91
  60. package/dist/components/TextInput.d.ts +6 -19
  61. package/dist/components/TextInput.js +261 -195
  62. package/dist/components/Toggle.d.ts +23 -41
  63. package/dist/components/Toggle.js +84 -98
  64. package/dist/components/ToggleGroup.d.ts +37 -29
  65. package/dist/components/ToggleGroup.js +113 -108
  66. package/dist/components/Tooltip.d.ts +41 -111
  67. package/dist/components/Tooltip.js +156 -118
  68. package/dist/components/UIProvider.d.ts +13 -2
  69. package/dist/components/UIProvider.js +7 -3
  70. package/dist/components/index.d.ts +1 -0
  71. package/dist/components/index.js +1 -0
  72. package/dist/components/keyboardFocusRegistry.d.ts +15 -0
  73. package/dist/components/keyboardFocusRegistry.js +27 -0
  74. package/dist/hooks/useTheme.d.ts +34 -10
  75. package/dist/hooks/useTheme.js +20 -8
  76. package/dist/lib/index.d.ts +2 -0
  77. package/dist/lib/index.js +2 -0
  78. package/dist/lib/portal.d.ts +16 -0
  79. package/dist/lib/portal.js +84 -0
  80. package/dist/lib/styles.d.ts +32 -0
  81. package/dist/lib/styles.js +91 -0
  82. package/dist/lib/useAnchoredPosition.d.ts +57 -0
  83. package/dist/lib/useAnchoredPosition.js +120 -0
  84. package/llms-full.md +9 -4
  85. package/package.json +4 -22
@@ -1,9 +1,35 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useMemo } from "react";
3
- import { View, StyleSheet } from "react-native";
2
+ import React from "react";
3
+ import { css, html } from "react-strict-dom";
4
4
  import { useTheme } from "../hooks/useTheme.js";
5
- import { spacing } from "../constants/spacing.js";
6
5
  import { StyledText } from "./StyledText.js";
6
+ import { sanitizeWebStyle } from "../lib/styles.js";
7
+ // Static structure + function rules for theme-driven colors. The pill radius is
8
+ // a static literal (spacing.radiusFull = 9999) so StyleX can inline it; theme
9
+ // colors flow through function rules. No flex-child props on the outer element.
10
+ const styles = css.create({
11
+ badge: {
12
+ alignSelf: "flex-start",
13
+ borderRadius: 9999,
14
+ paddingLeft: 10,
15
+ paddingRight: 10,
16
+ paddingTop: 2,
17
+ paddingBottom: 2,
18
+ },
19
+ solid: (backgroundColor) => ({ backgroundColor }),
20
+ outline: (borderColor) => ({
21
+ backgroundColor: "transparent",
22
+ borderWidth: 1,
23
+ borderStyle: "solid",
24
+ borderColor,
25
+ }),
26
+ });
27
+ const TEXT_STYLE = {
28
+ fontSize: 12,
29
+ fontWeight: "500",
30
+ lineHeight: 18,
31
+ userSelect: "none",
32
+ };
7
33
  /**
8
34
  * Badge Component
9
35
  *
@@ -19,15 +45,15 @@ import { StyledText } from "./StyledText.js";
19
45
  */
20
46
  function Badge({ children, text, variant = "default", style: styleOverride }) {
21
47
  const { theme } = useTheme();
22
- const styles = useMemo(() => createStyles(theme), [theme]);
23
48
  const badgeContent = text ?? children;
24
- const textStyle = [
25
- styles.text,
26
- variant === "default" && { color: theme.colors.primaryForeground },
27
- variant === "secondary" && { color: theme.colors.secondaryForeground },
28
- variant === "outline" && { color: theme.colors.foreground },
29
- variant === "destructive" && { color: theme.colors.destructiveForeground },
30
- ];
49
+ const textColor = variant === "default"
50
+ ? theme.colors.primaryForeground
51
+ : variant === "secondary"
52
+ ? theme.colors.secondaryForeground
53
+ : variant === "destructive"
54
+ ? theme.colors.destructiveForeground
55
+ : theme.colors.foreground;
56
+ const textStyle = { ...TEXT_STYLE, color: textColor };
31
57
  const normalizedChildren = React.Children.toArray(badgeContent);
32
58
  const hasOnlyTextChildren = normalizedChildren.every((child) => typeof child === "string" || typeof child === "number");
33
59
  const content = hasOnlyTextChildren ? (_jsx(StyledText, { selectable: false, style: textStyle, children: normalizedChildren.join("") })) : (React.Children.map(badgeContent, (child) => {
@@ -36,41 +62,13 @@ function Badge({ children, text, variant = "default", style: styleOverride }) {
36
62
  }
37
63
  return child;
38
64
  }));
39
- return (_jsx(View, { accessibilityRole: "text", style: [
40
- styles.badge,
41
- variant === "default" && styles.default,
42
- variant === "secondary" && styles.secondary,
43
- variant === "outline" && styles.outline,
44
- variant === "destructive" && styles.destructive,
45
- styleOverride,
46
- ], children: content }));
65
+ const background = variant === "default"
66
+ ? styles.solid(theme.colors.primary)
67
+ : variant === "secondary"
68
+ ? styles.solid(theme.colors.secondary)
69
+ : variant === "destructive"
70
+ ? styles.solid(theme.colors.destructive)
71
+ : styles.outline(theme.colors.border);
72
+ return (_jsx(html.div, { style: [styles.badge, background, sanitizeWebStyle(styleOverride)], children: content }));
47
73
  }
48
- const createStyles = (theme) => StyleSheet.create({
49
- badge: {
50
- alignSelf: "flex-start",
51
- borderRadius: spacing.radiusFull,
52
- paddingHorizontal: 10,
53
- paddingVertical: 2,
54
- },
55
- default: {
56
- backgroundColor: theme.colors.primary,
57
- },
58
- secondary: {
59
- backgroundColor: theme.colors.secondary,
60
- },
61
- outline: {
62
- backgroundColor: "transparent",
63
- borderWidth: 1,
64
- borderColor: theme.colors.border,
65
- },
66
- destructive: {
67
- backgroundColor: theme.colors.destructive,
68
- },
69
- text: {
70
- fontSize: 12,
71
- fontWeight: "500",
72
- lineHeight: 18,
73
- userSelect: "none",
74
- },
75
- });
76
74
  export { Badge };
@@ -1,5 +1,6 @@
1
- import React from "react";
2
- import { ViewProps, StyleProp, ViewStyle, ScrollViewProps } from "react-native";
1
+ import * as React from "react";
2
+ import { type ScrollViewProps } from "react-native";
3
+ import { type LooseStyle } from "../lib/styles";
3
4
  /**
4
5
  * BottomSheet — a sliding bottom sheet with a compound API, backed by the
5
6
  * platform's native sheet via `@expo/ui/community/bottom-sheet`:
@@ -8,49 +9,22 @@ import { ViewProps, StyleProp, ViewStyle, ScrollViewProps } from "react-native";
8
9
  * - Android: Material3 `ModalBottomSheet`
9
10
  * - Web: `vaul` drawer (bundled with @expo/ui)
10
11
  *
11
- * The compound surface (Trigger / Content / Handle / Header / Body / Footer /
12
- * Close), controlled + uncontrolled state, and theming match a hand-rolled
13
- * sheet, but the platform owns gestures and keyboard avoidance so there's no
14
- * PanResponder, snap-physics, or keyboard lift-and-shrink code to maintain.
15
- *
16
- * Platform-owned behaviors (props accepted for ergonomics, but the platform
17
- * decides):
18
- * - `.Handle` replaces the native drag indicator with a pressable equivalent.
19
- * Pressing it walks through the configured snap points, reversing direction
20
- * at either end. Dragging the sheet continues to use the platform gesture.
21
- * - `swipeEnabled` / `avoidKeyboard` / `dismissKeyboardOnDrag` are accepted
22
- * for call-site ergonomics but have no effect — the platform handles them.
23
- * - Sheet *chrome* (corner radius, system background, safe area) is the
24
- * platform's on native; theming reaches the content + background color.
25
- * - On Android only two snap states exist (partial / expanded); extra snap
26
- * points map to the nearest of those two.
27
- *
28
- * Scrollable bodies: the native sheet doesn't bound the hosted RN content to
29
- * the detent height, so a tall `Body` overflows and clips its footer/tail. When
30
- * `Body` detects overflow, `Content` caps the column to the detent height so the
31
- * `ScrollView` actually scrolls to the bottom. Swipe-to-dismiss still works (the
32
- * native sheet coordinates the pan: a pull at the top of the scroll view
33
- * dismisses, elsewhere it scrolls). A close affordance is also surfaced for
34
- * scrollable sheets so there's a one-tap close: the `Header` renders a trailing
35
- * X, or — if there's no `Header` — `Content` floats one in the top-right corner.
36
- * No extra props required.
12
+ * react-strict-dom port: the sheet itself stays @expo/ui (vaul on web not
13
+ * RNW); the compound chrome (Content column / Header / Footer / Handle / Close /
14
+ * Trigger / close button) is html.* so web is RNW-free. The scrollable Body is
15
+ * the one platform split: RSD html.div maps to a NON-scrolling RN View on
16
+ * native, so native keeps RN ScrollView (with its onLayout/onContentSizeChange
17
+ * overflow detection); web uses an html.div with overflow:auto and measures
18
+ * scrollHeight vs clientHeight.
37
19
  *
38
20
  * @example
39
21
  * ```tsx
40
22
  * <BottomSheet open={open} onOpenChange={setOpen} snapPoints={["50%"]}>
41
- * <BottomSheet.Trigger asChild>
42
- * <Button>Open</Button>
43
- * </BottomSheet.Trigger>
23
+ * <BottomSheet.Trigger asChild><Button>Open</Button></BottomSheet.Trigger>
44
24
  * <BottomSheet.Content>
45
- * <BottomSheet.Header>
46
- * <SansSerifBoldText>Title</SansSerifBoldText>
47
- * </BottomSheet.Header>
48
- * <BottomSheet.Body>
49
- * <SansSerifText>Content</SansSerifText>
50
- * </BottomSheet.Body>
51
- * <BottomSheet.Footer>
52
- * <Button>Action</Button>
53
- * </BottomSheet.Footer>
25
+ * <BottomSheet.Header><SansSerifBoldText>Title</SansSerifBoldText></BottomSheet.Header>
26
+ * <BottomSheet.Body><SansSerifText>Content</SansSerifText></BottomSheet.Body>
27
+ * <BottomSheet.Footer><Button>Action</Button></BottomSheet.Footer>
54
28
  * </BottomSheet.Content>
55
29
  * </BottomSheet>
56
30
  * ```
@@ -65,19 +39,10 @@ interface BottomSheetContextValue {
65
39
  setSnapIndex: (index: number) => void;
66
40
  cycleSnapPoint: () => void;
67
41
  closeOnBackdropPress: boolean;
68
- /**
69
- * True when a `Body`'s content overflows its viewport (is genuinely
70
- * scrollable). Drives two things: `Content` caps the column to the detent
71
- * height so the `ScrollView` can reach its bottom, and a close affordance is
72
- * surfaced (a `Header` X, or floating if there's no `Header`) so a long sheet
73
- * always has a one-tap close. Set by `Body`.
74
- */
75
42
  scrollable: boolean;
76
43
  setScrollable: (scrollable: boolean) => void;
77
- /** Whether a `Header` is mounted, so `Content` knows to render a fallback close. */
78
44
  hasHeader: boolean;
79
45
  setHasHeader: (present: boolean) => void;
80
- /** Whether a `Footer` is mounted, so `Body` can own the bottom safe-area inset when it isn't. */
81
46
  hasFooter: boolean;
82
47
  setHasFooter: (present: boolean) => void;
83
48
  }
@@ -90,62 +55,54 @@ interface BottomSheetProps {
90
55
  defaultOpen?: boolean;
91
56
  /** Snap point heights (px or percentage strings). Default: ["50%"]. */
92
57
  snapPoints?: SnapPoint[];
93
- /**
94
- * Whether swiping/pulling down (or tapping the backdrop) dismisses the sheet.
95
- * Maps to the native sheet's `enablePanDownToClose` (iOS
96
- * `interactiveDismissDisabled`). Default: true.
97
- *
98
- * Works with scrollable bodies — the native sheet coordinates the pan (pull at
99
- * the top of the scroll view dismisses, elsewhere it scrolls). Set to `false`
100
- * to disable dismiss entirely; the sheet then surfaces an explicit close
101
- * affordance automatically (a `Header` X, or a floating X if there's no
102
- * `Header`). Scrollable sheets also get that close affordance regardless, so
103
- * there's always a one-tap close.
104
- */
58
+ /** Whether swiping down / tapping the backdrop dismisses the sheet. Default: true. */
105
59
  closeOnBackdropPress?: boolean;
106
60
  children: React.ReactNode;
107
61
  }
108
62
  interface BottomSheetTriggerProps {
109
63
  asChild?: boolean;
110
64
  children: React.ReactNode;
111
- style?: StyleProp<ViewStyle>;
65
+ style?: LooseStyle;
112
66
  }
113
- interface BottomSheetContentProps extends ViewProps {
67
+ interface BottomSheetContentProps {
114
68
  /** Accepted for call-site ergonomics; ignored (platform owns gestures). */
115
69
  swipeEnabled?: boolean;
116
70
  /** Accepted for call-site ergonomics; ignored (platform owns keyboard avoidance). */
117
71
  avoidKeyboard?: boolean;
118
72
  /** Accepted for call-site ergonomics; ignored (platform owns keyboard). */
119
73
  dismissKeyboardOnDrag?: boolean;
120
- style?: StyleProp<ViewStyle>;
74
+ style?: LooseStyle;
121
75
  children: React.ReactNode;
122
76
  }
123
- interface BottomSheetHeaderProps extends ViewProps {
77
+ interface BottomSheetHeaderProps {
124
78
  children: React.ReactNode;
79
+ style?: LooseStyle;
125
80
  }
126
81
  interface BottomSheetBodyProps extends ScrollViewProps {
127
82
  children: React.ReactNode;
83
+ testID?: string;
128
84
  }
129
- interface BottomSheetFooterProps extends ViewProps {
85
+ interface BottomSheetFooterProps {
130
86
  children: React.ReactNode;
87
+ style?: LooseStyle;
131
88
  }
132
89
  interface BottomSheetHandleProps {
133
- style?: StyleProp<ViewStyle>;
90
+ style?: LooseStyle;
134
91
  }
135
92
  interface BottomSheetCloseProps {
136
93
  asChild?: boolean;
137
94
  children: React.ReactNode;
138
- style?: StyleProp<ViewStyle>;
95
+ style?: LooseStyle;
139
96
  }
140
97
  declare function useBottomSheetContext(): BottomSheetContextValue;
141
98
  declare function BottomSheetRoot({ open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, snapPoints, closeOnBackdropPress, children, }: BottomSheetProps): React.JSX.Element;
142
- declare function BottomSheetTrigger({ asChild, children, style: styleOverride }: BottomSheetTriggerProps): React.JSX.Element;
99
+ declare function BottomSheetTrigger({ asChild, children, style }: BottomSheetTriggerProps): React.JSX.Element;
143
100
  declare function BottomSheetContent({ swipeEnabled: _swipeEnabled, avoidKeyboard: _avoidKeyboard, dismissKeyboardOnDrag: _dismissKeyboardOnDrag, style: styleOverride, children, }: BottomSheetContentProps): React.JSX.Element;
144
101
  declare function BottomSheetHandle({ style }: BottomSheetHandleProps): React.JSX.Element;
145
- declare function BottomSheetHeader({ children, style, ...props }: BottomSheetHeaderProps): React.JSX.Element;
146
- declare function BottomSheetBody({ children, style, contentContainerStyle, onContentSizeChange, onLayout, ...props }: BottomSheetBodyProps): React.JSX.Element;
147
- declare function BottomSheetFooter({ children, style, ...props }: BottomSheetFooterProps): React.JSX.Element;
148
- declare function BottomSheetClose({ asChild, children, style: styleOverride }: BottomSheetCloseProps): React.JSX.Element;
102
+ declare function BottomSheetHeader({ children, style }: BottomSheetHeaderProps): React.JSX.Element;
103
+ declare function BottomSheetBody(props: BottomSheetBodyProps): React.JSX.Element;
104
+ declare function BottomSheetFooter({ children, style }: BottomSheetFooterProps): React.JSX.Element;
105
+ declare function BottomSheetClose({ asChild, children, style }: BottomSheetCloseProps): React.JSX.Element;
149
106
  declare const BottomSheet: typeof BottomSheetRoot & {
150
107
  Trigger: typeof BottomSheetTrigger;
151
108
  Content: typeof BottomSheetContent;