@mrmeg/expo-ui 0.7.2 → 0.8.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 (59) hide show
  1. package/LLM_USAGE.md +21 -11
  2. package/README.md +8 -10
  3. package/dist/components/Accordion.d.ts +4 -4
  4. package/dist/components/AnimatedView.d.ts +1 -1
  5. package/dist/components/Badge.d.ts +1 -1
  6. package/dist/components/BottomSheet.d.ts +96 -20
  7. package/dist/components/BottomSheet.js +203 -444
  8. package/dist/components/Button.d.ts +3 -3
  9. package/dist/components/Button.js +17 -1
  10. package/dist/components/Card.d.ts +6 -6
  11. package/dist/components/Checkbox.d.ts +2 -1
  12. package/dist/components/Collapsible.d.ts +4 -3
  13. package/dist/components/Dialog.d.ts +10 -10
  14. package/dist/components/Dialog.js +16 -8
  15. package/dist/components/DismissKeyboard.d.ts +1 -1
  16. package/dist/components/Drawer.d.ts +7 -7
  17. package/dist/components/DropdownMenu.d.ts +10 -10
  18. package/dist/components/EmptyState.d.ts +1 -1
  19. package/dist/components/ErrorBoundary.d.ts +1 -1
  20. package/dist/components/Icon.d.ts +1 -1
  21. package/dist/components/InputOTP.d.ts +2 -1
  22. package/dist/components/Label.d.ts +1 -1
  23. package/dist/components/MaxWidthContainer.d.ts +1 -1
  24. package/dist/components/Notification.d.ts +4 -10
  25. package/dist/components/Notification.js +12 -13
  26. package/dist/components/Popover.d.ts +4 -4
  27. package/dist/components/Progress.d.ts +2 -1
  28. package/dist/components/RadioGroup.d.ts +3 -2
  29. package/dist/components/SegmentedControl.d.ts +53 -0
  30. package/dist/components/SegmentedControl.js +25 -0
  31. package/dist/components/Select.d.ts +7 -7
  32. package/dist/components/Separator.d.ts +2 -1
  33. package/dist/components/Skeleton.d.ts +5 -4
  34. package/dist/components/Slider.d.ts +24 -3
  35. package/dist/components/Slider.js +26 -147
  36. package/dist/components/StatusBar.d.ts +1 -1
  37. package/dist/components/StyledText.d.ts +12 -12
  38. package/dist/components/Switch.d.ts +2 -1
  39. package/dist/components/Tabs.d.ts +5 -5
  40. package/dist/components/Tabs.js +10 -2
  41. package/dist/components/TextInput.d.ts +1 -1
  42. package/dist/components/TextInput.js +129 -2
  43. package/dist/components/Toggle.d.ts +3 -2
  44. package/dist/components/ToggleGroup.d.ts +4 -3
  45. package/dist/components/Tooltip.d.ts +3 -3
  46. package/dist/components/UIProvider.d.ts +1 -1
  47. package/dist/components/index.d.ts +1 -0
  48. package/dist/components/index.js +1 -0
  49. package/dist/state/globalUIStore.d.ts +9 -1
  50. package/dist/state/globalUIStore.js +9 -1
  51. package/dist/state/index.d.ts +1 -0
  52. package/dist/state/index.js +1 -0
  53. package/dist/state/notify.d.ts +50 -0
  54. package/dist/state/notify.js +31 -0
  55. package/dist/state/themeColorScope.d.ts +1 -1
  56. package/llms-full.md +34 -3
  57. package/package.json +3 -2
  58. package/dist/components/BottomSheetKeyboard.d.ts +0 -7
  59. package/dist/components/BottomSheetKeyboard.js +0 -39
package/LLM_USAGE.md CHANGED
@@ -15,7 +15,7 @@ import { Button, StyledText, UIProvider } from "@mrmeg/expo-ui/components";
15
15
  import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
16
16
  import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
17
17
  import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
18
- import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
18
+ import { globalUIStore, notify, useThemeStore } from "@mrmeg/expo-ui/state";
19
19
  import { configureExpoUiI18n, hapticLight } from "@mrmeg/expo-ui/lib";
20
20
  ```
21
21
 
@@ -92,7 +92,7 @@ configureExpoUiI18n((key, options) => i18n.t(key, options));
92
92
  - Use `Button.preset`, not `variant`, for buttons.
93
93
  - Button visible heights are compact: `sm` 28px, `md` 32px, and `lg` 40px.
94
94
  - Use `Button size="sm"` for compact popover, tooltip, and toolbar triggers; nested `StyledText` inherits the selected Button size.
95
- - Use `globalUIStore` plus root-mounted `UIProvider` for transient global feedback.
95
+ - Use `notify` plus root-mounted `UIProvider` for transient global feedback. (`globalUIStore` remains available for reactive subscriptions and tests.)
96
96
  - Keep app monitoring, auth, API, and domain behavior outside this package.
97
97
 
98
98
  Useful theme tokens include:
@@ -249,19 +249,29 @@ import { Button, Switch, TextInput } from "@mrmeg/expo-ui/components";
249
249
 
250
250
  ```tsx
251
251
  import { EmptyState, Progress, SkeletonCard } from "@mrmeg/expo-ui/components";
252
- import { globalUIStore } from "@mrmeg/expo-ui/state";
252
+ import { notify } from "@mrmeg/expo-ui/state";
253
253
 
254
254
  {isLoading ? <SkeletonCard /> : null}
255
255
  <Progress value={65} variant="accent" />
256
256
  <EmptyState icon="inbox" title="No messages" description="New messages will appear here." />
257
257
 
258
- globalUIStore.getState().show({
259
- type: "success",
260
- title: "Saved",
261
- messages: ["Your changes were saved."],
262
- action: {
263
- label: "View",
264
- onPress: openSavedItem,
265
- },
258
+ // Convenience helpers
259
+ notify.success("Saved", { messages: ["Your changes were saved."] });
260
+ notify.error("Upload failed");
261
+ notify.warning("Connection slow");
262
+ notify.info("Copied to clipboard");
263
+
264
+ // Loading spinner — stays until replaced or hidden (no auto-dismiss)
265
+ notify.loading("Uploading…");
266
+ notify.hide();
267
+
268
+ // Full control (same payload as globalUIStore show())
269
+ notify({ type: "success", title: "Saved", action: { label: "View", onPress: openSavedItem } });
270
+
271
+ // Loading → success/error around a promise
272
+ await notify.promise(saveProfile(), {
273
+ loading: "Saving…",
274
+ success: "Profile saved", // or (value) => `Saved ${value.name}`
275
+ error: "Could not save profile", // or (err) => err.message
266
276
  });
267
277
  ```
package/README.md CHANGED
@@ -28,8 +28,8 @@ bun add @mrmeg/expo-ui
28
28
  ```
29
29
 
30
30
  Consumers must also install the native and Expo peer dependencies listed in
31
- `package.json`. The tested baseline is Expo SDK 55 with React 19.2, React
32
- Native 0.83, and React Native Web 0.21. UI animations and keyboard-aware
31
+ `package.json`. The tested baseline is Expo SDK 56 with React 19.2, React
32
+ Native 0.85, and React Native Web 0.21. UI animations and keyboard-aware
33
33
  sheet offsets use React Native Animated by default.
34
34
  `@rn-primitives/*` packages are managed by `@mrmeg/expo-ui` because they are
35
35
  implementation details of the exported UI components. Native bottom sheet
@@ -49,14 +49,14 @@ import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
49
49
  import { colors as colorsDirect } from "@mrmeg/expo-ui/constants/colors";
50
50
  import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
51
51
  import { useTheme as useThemeDirect } from "@mrmeg/expo-ui/hooks/useTheme";
52
- import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
52
+ import { globalUIStore, notify, useThemeStore } from "@mrmeg/expo-ui/state";
53
53
  import { configureExpoUiI18n, hapticLight } from "@mrmeg/expo-ui/lib";
54
54
  ```
55
55
 
56
56
  The root barrel also exports the public surface:
57
57
 
58
58
  ```tsx
59
- import { Button, UIProvider, colors, useTheme } from "@mrmeg/expo-ui";
59
+ import { Button, UIProvider, colors, notify, useTheme } from "@mrmeg/expo-ui";
60
60
  ```
61
61
 
62
62
  ## Theme System
@@ -272,7 +272,7 @@ Use `Button.preset`, not `variant`. `default` is the neutral primary action, `se
272
272
 
273
273
  Use `StyledText` or its aliases instead of raw `Text` whenever the text is part of app UI. Use `TextInput` for labeled fields because it already owns label, helper text, error text, clear buttons, password visibility, numeric filtering, and left/right elements.
274
274
 
275
- Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. On native, `BottomSheet.Content` listens to React Native keyboard events when `avoidKeyboard` is enabled; it defaults to `true` and can be disabled per sheet. Trigger transient feedback from `globalUIStore`.
275
+ Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. On native, `BottomSheet.Content` listens to React Native keyboard events when `avoidKeyboard` is enabled; it defaults to `true` and can be disabled per sheet. Trigger transient feedback with `notify`.
276
276
 
277
277
  Use `Skeleton` components for loading content with stable dimensions, `EmptyState` for no-data/recoverable errors, `Alert` for blocking confirm/alert dialogs, and `Notification` for transient global feedback.
278
278
 
@@ -322,15 +322,13 @@ Feedback:
322
322
 
323
323
  ```tsx
324
324
  import { EmptyState, Progress, SkeletonCard } from "@mrmeg/expo-ui/components";
325
- import { globalUIStore } from "@mrmeg/expo-ui/state";
325
+ import { notify } from "@mrmeg/expo-ui/state";
326
326
 
327
327
  {isLoading ? <SkeletonCard /> : null}
328
328
  <Progress value={65} variant="accent" />
329
329
  <EmptyState icon="inbox" title="No messages" description="New messages will appear here." />
330
330
 
331
- globalUIStore.getState().show({
332
- type: "success",
333
- title: "Saved",
331
+ notify.success("Saved", {
334
332
  messages: ["Your changes were saved."],
335
333
  action: {
336
334
  label: "View",
@@ -457,5 +455,5 @@ bun run ui:consumer-smoke
457
455
  `bun run ui:consumer-smoke` installs the packed tarball into a clean fixture,
458
456
  checks the documented export-map files, type-checks all public package
459
457
  entrypoints, verifies that `@mrmeg/expo-ui/constants` can be imported by
460
- plain Node ESM for token inspection, and runs an Expo SDK 55 iOS export
458
+ plain Node ESM for token inspection, and runs an Expo SDK 56 iOS export
461
459
  against the packed package without a custom Metro config.
@@ -32,12 +32,12 @@ type AccordionRootProps = WebSingleAccordionRootProps | WebMultipleAccordionRoot
32
32
  * </AccordionItem>
33
33
  * </Accordion>
34
34
  */
35
- declare function Accordion({ children, style, ...props }: AccordionRootProps): import("react/jsx-runtime").JSX.Element;
35
+ declare function Accordion({ children, style, ...props }: AccordionRootProps): import("react").JSX.Element;
36
36
  /**
37
37
  * Accordion Item Component
38
38
  * Individual accordion item with border styling
39
39
  */
40
- declare function AccordionItem({ children, value, style: styleOverride, ...props }: AccordionPrimitive.ItemProps & React.RefAttributes<AccordionPrimitive.ItemRef>): import("react/jsx-runtime").JSX.Element;
40
+ declare function AccordionItem({ children, value, style: styleOverride, ...props }: AccordionPrimitive.ItemProps & React.RefAttributes<AccordionPrimitive.ItemRef>): import("react").JSX.Element;
41
41
  /**
42
42
  * Accordion Trigger Component
43
43
  * Clickable header that expands/collapses the content
@@ -45,10 +45,10 @@ declare function AccordionItem({ children, value, style: styleOverride, ...props
45
45
  */
46
46
  declare function AccordionTrigger({ children, style: styleOverride, ...props }: AccordionPrimitive.TriggerProps & {
47
47
  children?: React.ReactNode;
48
- } & React.RefAttributes<AccordionPrimitive.TriggerRef>): import("react/jsx-runtime").JSX.Element;
48
+ } & React.RefAttributes<AccordionPrimitive.TriggerRef>): import("react").JSX.Element;
49
49
  /**
50
50
  * Accordion Content Component
51
51
  * Expandable content area with animations
52
52
  */
53
- declare function AccordionContent({ children, style: styleOverride, ...props }: AccordionPrimitive.ContentProps & React.RefAttributes<AccordionPrimitive.ContentRef>): import("react/jsx-runtime").JSX.Element;
53
+ declare function AccordionContent({ children, style: styleOverride, ...props }: AccordionPrimitive.ContentProps & React.RefAttributes<AccordionPrimitive.ContentRef>): import("react").JSX.Element;
54
54
  export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -51,5 +51,5 @@ interface AnimatedViewProps extends ViewProps {
51
51
  * </AnimatedView>
52
52
  * ```
53
53
  */
54
- export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): React.JSX.Element;
55
55
  export {};
@@ -20,5 +20,5 @@ export interface BadgeProps {
20
20
  * <Badge variant="destructive">Error</Badge>
21
21
  * ```
22
22
  */
23
- declare function Badge({ children, text, variant, style: styleOverride }: BadgeProps): import("react/jsx-runtime").JSX.Element;
23
+ declare function Badge({ children, text, variant, style: styleOverride }: BadgeProps): React.JSX.Element;
24
24
  export { Badge };
@@ -1,24 +1,102 @@
1
1
  import React from "react";
2
2
  import { ViewProps, StyleProp, ViewStyle, ScrollViewProps } from "react-native";
3
+ /**
4
+ * BottomSheet — a sliding bottom sheet with a compound API, backed by the
5
+ * platform's native sheet via `@expo/ui/community/bottom-sheet`:
6
+ *
7
+ * - iOS: SwiftUI `.sheet()` with presentation detents
8
+ * - Android: Material3 `ModalBottomSheet`
9
+ * - Web: `vaul` drawer (bundled with @expo/ui)
10
+ *
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` is a no-op: the platform draws the drag indicator.
19
+ * - `swipeEnabled` / `avoidKeyboard` / `dismissKeyboardOnDrag` are accepted
20
+ * for call-site ergonomics but have no effect — the platform handles them.
21
+ * - Sheet *chrome* (corner radius, system background, safe area) is the
22
+ * platform's on native; theming reaches the content + background color.
23
+ * - On Android only two snap states exist (partial / expanded); extra snap
24
+ * points map to the nearest of those two.
25
+ *
26
+ * Scrollable bodies: the native sheet doesn't bound the hosted RN content to
27
+ * the detent height, so a tall `Body` overflows and clips its footer/tail. When
28
+ * `Body` detects overflow, `Content` caps the column to the detent height so the
29
+ * `ScrollView` actually scrolls to the bottom. Swipe-to-dismiss still works (the
30
+ * native sheet coordinates the pan: a pull at the top of the scroll view
31
+ * dismisses, elsewhere it scrolls). A close affordance is also surfaced for
32
+ * scrollable sheets so there's a one-tap close: the `Header` renders a trailing
33
+ * X, or — if there's no `Header` — `Content` floats one in the top-right corner.
34
+ * No extra props required.
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * <BottomSheet open={open} onOpenChange={setOpen} snapPoints={["50%"]}>
39
+ * <BottomSheet.Trigger asChild>
40
+ * <Button>Open</Button>
41
+ * </BottomSheet.Trigger>
42
+ * <BottomSheet.Content>
43
+ * <BottomSheet.Header>
44
+ * <SansSerifBoldText>Title</SansSerifBoldText>
45
+ * </BottomSheet.Header>
46
+ * <BottomSheet.Body>
47
+ * <SansSerifText>Content</SansSerifText>
48
+ * </BottomSheet.Body>
49
+ * <BottomSheet.Footer>
50
+ * <Button>Action</Button>
51
+ * </BottomSheet.Footer>
52
+ * </BottomSheet.Content>
53
+ * </BottomSheet>
54
+ * ```
55
+ */
3
56
  type SnapPoint = number | `${number}%`;
4
57
  interface BottomSheetContextValue {
5
58
  open: boolean;
6
59
  onOpenChange: (open: boolean) => void;
7
60
  toggle: () => void;
8
- snapPoints: number[];
9
- currentSnapIndex: number;
61
+ snapPoints: SnapPoint[];
10
62
  closeOnBackdropPress: boolean;
63
+ /**
64
+ * True when a `Body`'s content overflows its viewport (is genuinely
65
+ * scrollable). Drives two things: `Content` caps the column to the detent
66
+ * height so the `ScrollView` can reach its bottom, and a close affordance is
67
+ * surfaced (a `Header` X, or floating if there's no `Header`) so a long sheet
68
+ * always has a one-tap close. Set by `Body`.
69
+ */
70
+ scrollable: boolean;
71
+ setScrollable: (scrollable: boolean) => void;
72
+ /** Whether a `Header` is mounted, so `Content` knows to render a fallback close. */
73
+ hasHeader: boolean;
74
+ setHasHeader: (present: boolean) => void;
75
+ /** Whether a `Footer` is mounted, so `Body` can own the bottom safe-area inset when it isn't. */
76
+ hasFooter: boolean;
77
+ setHasFooter: (present: boolean) => void;
11
78
  }
12
79
  interface BottomSheetProps {
13
- /** Controlled open state */
80
+ /** Controlled open state. Omit to use uncontrolled mode with `defaultOpen`. */
14
81
  open?: boolean;
15
- /** Callback when open state changes */
82
+ /** Callback when open state changes. */
16
83
  onOpenChange?: (open: boolean) => void;
17
- /** Default open state for uncontrolled mode */
84
+ /** Initial open state for uncontrolled mode. Default: false. */
18
85
  defaultOpen?: boolean;
19
- /** Snap point heights (px or percentage strings) */
86
+ /** Snap point heights (px or percentage strings). Default: ["50%"]. */
20
87
  snapPoints?: SnapPoint[];
21
- /** Whether to close when backdrop is pressed */
88
+ /**
89
+ * Whether swiping/pulling down (or tapping the backdrop) dismisses the sheet.
90
+ * Maps to the native sheet's `enablePanDownToClose` (iOS
91
+ * `interactiveDismissDisabled`). Default: true.
92
+ *
93
+ * Works with scrollable bodies — the native sheet coordinates the pan (pull at
94
+ * the top of the scroll view dismisses, elsewhere it scrolls). Set to `false`
95
+ * to disable dismiss entirely; the sheet then surfaces an explicit close
96
+ * affordance automatically (a `Header` X, or a floating X if there's no
97
+ * `Header`). Scrollable sheets also get that close affordance regardless, so
98
+ * there's always a one-tap close.
99
+ */
22
100
  closeOnBackdropPress?: boolean;
23
101
  children: React.ReactNode;
24
102
  }
@@ -28,13 +106,11 @@ interface BottomSheetTriggerProps {
28
106
  style?: StyleProp<ViewStyle>;
29
107
  }
30
108
  interface BottomSheetContentProps extends ViewProps {
31
- /** Whether to enable swipe/drag gestures */
109
+ /** Accepted for call-site ergonomics; ignored (platform owns gestures). */
32
110
  swipeEnabled?: boolean;
33
- /** Velocity threshold for quick swipe to close */
34
- velocityThreshold?: number;
35
- /** Whether to move the sheet with the native keyboard animation. Default: true. */
111
+ /** Accepted for call-site ergonomics; ignored (platform owns keyboard avoidance). */
36
112
  avoidKeyboard?: boolean;
37
- /** Whether to dismiss the keyboard when a native drag starts. Default: true. */
113
+ /** Accepted for call-site ergonomics; ignored (platform owns keyboard). */
38
114
  dismissKeyboardOnDrag?: boolean;
39
115
  style?: StyleProp<ViewStyle>;
40
116
  children: React.ReactNode;
@@ -57,14 +133,14 @@ interface BottomSheetCloseProps {
57
133
  style?: StyleProp<ViewStyle>;
58
134
  }
59
135
  declare function useBottomSheetContext(): BottomSheetContextValue;
60
- declare function BottomSheetRoot({ open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, snapPoints: rawSnapPoints, closeOnBackdropPress, children, }: BottomSheetProps): import("react/jsx-runtime").JSX.Element;
61
- declare function BottomSheetTrigger({ asChild, children, style: styleOverride }: BottomSheetTriggerProps): import("react/jsx-runtime").JSX.Element;
62
- declare function BottomSheetContent({ swipeEnabled, velocityThreshold, avoidKeyboard, dismissKeyboardOnDrag, style: styleOverride, children, ...props }: BottomSheetContentProps): import("react/jsx-runtime").JSX.Element | null;
63
- declare function BottomSheetHandle({ style }: BottomSheetHandleProps): import("react/jsx-runtime").JSX.Element;
64
- declare function BottomSheetHeader({ children, style, ...props }: BottomSheetHeaderProps): import("react/jsx-runtime").JSX.Element;
65
- declare function BottomSheetBody({ children, style, ...props }: BottomSheetBodyProps): import("react/jsx-runtime").JSX.Element;
66
- declare function BottomSheetFooter({ children, style, ...props }: BottomSheetFooterProps): import("react/jsx-runtime").JSX.Element;
67
- declare function BottomSheetClose({ asChild, children, style: styleOverride }: BottomSheetCloseProps): import("react/jsx-runtime").JSX.Element;
136
+ declare function BottomSheetRoot({ open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, snapPoints, closeOnBackdropPress, children, }: BottomSheetProps): React.JSX.Element;
137
+ declare function BottomSheetTrigger({ asChild, children, style: styleOverride }: BottomSheetTriggerProps): React.JSX.Element;
138
+ declare function BottomSheetContent({ swipeEnabled: _swipeEnabled, avoidKeyboard: _avoidKeyboard, dismissKeyboardOnDrag: _dismissKeyboardOnDrag, style: styleOverride, children, }: BottomSheetContentProps): React.JSX.Element;
139
+ declare function BottomSheetHandle(_props: BottomSheetHandleProps): null;
140
+ declare function BottomSheetHeader({ children, style, ...props }: BottomSheetHeaderProps): React.JSX.Element;
141
+ declare function BottomSheetBody({ children, style, contentContainerStyle, onContentSizeChange, onLayout, ...props }: BottomSheetBodyProps): React.JSX.Element;
142
+ declare function BottomSheetFooter({ children, style, ...props }: BottomSheetFooterProps): React.JSX.Element;
143
+ declare function BottomSheetClose({ asChild, children, style: styleOverride }: BottomSheetCloseProps): React.JSX.Element;
68
144
  declare const BottomSheet: typeof BottomSheetRoot & {
69
145
  Trigger: typeof BottomSheetTrigger;
70
146
  Content: typeof BottomSheetContent;