@mrmeg/expo-ui 0.7.3 → 0.9.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/LLM_USAGE.md +24 -13
- package/README.md +8 -10
- package/dist/components/Accordion.d.ts +4 -4
- package/dist/components/AnimatedView.d.ts +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/BottomSheet.d.ts +7 -7
- package/dist/components/Button.d.ts +3 -3
- package/dist/components/Button.js +17 -1
- package/dist/components/Card.d.ts +6 -6
- package/dist/components/Checkbox.d.ts +2 -1
- package/dist/components/Collapsible.d.ts +4 -3
- package/dist/components/Dialog.d.ts +10 -10
- package/dist/components/DismissKeyboard.d.ts +1 -1
- package/dist/components/Drawer.d.ts +7 -7
- package/dist/components/DropdownMenu.d.ts +10 -10
- package/dist/components/EmptyState.d.ts +1 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/InputOTP.d.ts +2 -1
- package/dist/components/Label.d.ts +1 -1
- package/dist/components/MaxWidthContainer.d.ts +1 -1
- package/dist/components/Notification.d.ts +4 -10
- package/dist/components/Notification.js +12 -13
- package/dist/components/Popover.d.ts +4 -4
- package/dist/components/Progress.d.ts +2 -1
- package/dist/components/RadioGroup.d.ts +3 -2
- package/dist/components/SegmentedControl.d.ts +2 -1
- package/dist/components/Select.d.ts +7 -7
- package/dist/components/Separator.d.ts +2 -1
- package/dist/components/Skeleton.d.ts +5 -4
- package/dist/components/Slider.d.ts +2 -1
- package/dist/components/StatusBar.d.ts +1 -1
- package/dist/components/StyledText.d.ts +12 -12
- package/dist/components/Switch.d.ts +2 -1
- package/dist/components/Tabs.d.ts +5 -5
- package/dist/components/TextInput.d.ts +1 -1
- package/dist/components/TextInput.js +9 -1
- package/dist/components/Toggle.d.ts +3 -2
- package/dist/components/ToggleGroup.d.ts +4 -3
- package/dist/components/ToggleGroup.js +2 -7
- package/dist/components/Tooltip.d.ts +3 -3
- package/dist/components/UIProvider.d.ts +1 -1
- package/dist/hooks/useTheme.d.ts +1 -1
- package/dist/hooks/useTheme.js +28 -93
- package/dist/state/globalUIStore.d.ts +9 -1
- package/dist/state/globalUIStore.js +9 -1
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +1 -0
- package/dist/state/notify.d.ts +50 -0
- package/dist/state/notify.js +31 -0
- package/dist/state/themeColorScope.d.ts +1 -1
- package/llms-full.md +34 -3
- package/package.json +2 -2
package/dist/hooks/useTheme.js
CHANGED
|
@@ -42,7 +42,7 @@ function getCachedOrCompute(key, compute) {
|
|
|
42
42
|
* - getTextColorForBackground("#000") → "light"
|
|
43
43
|
* - getContrastingColor("#f4f4f4", "#222", "#fff") → "#222"
|
|
44
44
|
* - withAlpha("#336699", 0.6) → "rgba(51,102,153,0.6)"
|
|
45
|
-
* - getShadowStyle('base') → {
|
|
45
|
+
* - getShadowStyle('base') → { boxShadow: "0px 1px 3px rgba(0, 0, 0, 0.1)" }
|
|
46
46
|
*/
|
|
47
47
|
export function useTheme() {
|
|
48
48
|
const userTheme = useThemeStore((s) => s.userTheme);
|
|
@@ -93,102 +93,37 @@ export function useTheme() {
|
|
|
93
93
|
}, [setTheme, userTheme]);
|
|
94
94
|
/**
|
|
95
95
|
* getShadowStyle
|
|
96
|
-
* Returns platform
|
|
97
|
-
*
|
|
98
|
-
* -
|
|
96
|
+
* Returns a cross-platform shadow style using the `boxShadow` style prop.
|
|
97
|
+
*
|
|
98
|
+
* RN 0.85 + react-native-web 0.21 deprecate the legacy `shadow*` props in
|
|
99
|
+
* favor of `boxShadow`, which is supported on both native and web. Because
|
|
100
|
+
* `boxShadow` has no separate opacity field, each preset's opacity is folded
|
|
101
|
+
* into the color's alpha via `withAlpha`. `elevation` is dropped — `boxShadow`
|
|
102
|
+
* renders shadows on Android in 0.85+.
|
|
99
103
|
*/
|
|
100
104
|
const getShadowStyle = useCallback((type) => {
|
|
105
|
+
// Each preset: [offsetX, offsetY, blurRadius, color, opacity].
|
|
106
|
+
// Darker themes get a stronger alpha so shadows stay visible.
|
|
107
|
+
const boost = theme.dark ? 3 : 1;
|
|
108
|
+
const overlay = theme.colors.overlay;
|
|
101
109
|
const shadowConfigs = {
|
|
102
|
-
base: {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
shadowOffset: { width: 0, height: 1 },
|
|
119
|
-
shadowOpacity: 0.15,
|
|
120
|
-
shadowRadius: 1,
|
|
121
|
-
elevation: 2,
|
|
122
|
-
},
|
|
123
|
-
subtle: {
|
|
124
|
-
shadowColor: theme.colors.overlay,
|
|
125
|
-
shadowOffset: { width: 0, height: 1 },
|
|
126
|
-
shadowOpacity: 0.05,
|
|
127
|
-
shadowRadius: 2,
|
|
128
|
-
elevation: 1,
|
|
129
|
-
},
|
|
130
|
-
elevated: {
|
|
131
|
-
shadowColor: theme.colors.overlay,
|
|
132
|
-
shadowOffset: { width: 0, height: 20 },
|
|
133
|
-
shadowOpacity: 0.15,
|
|
134
|
-
shadowRadius: 40,
|
|
135
|
-
elevation: 16,
|
|
136
|
-
},
|
|
137
|
-
glow: {
|
|
138
|
-
shadowColor: theme.colors.primary,
|
|
139
|
-
shadowOffset: { width: 0, height: 4 },
|
|
140
|
-
shadowOpacity: 0.4,
|
|
141
|
-
shadowRadius: 20,
|
|
142
|
-
elevation: 10,
|
|
143
|
-
},
|
|
144
|
-
glass: {
|
|
145
|
-
shadowColor: theme.colors.overlay,
|
|
146
|
-
shadowOffset: { width: 0, height: 4 },
|
|
147
|
-
shadowOpacity: 0.05,
|
|
148
|
-
shadowRadius: 30,
|
|
149
|
-
elevation: 4,
|
|
150
|
-
},
|
|
151
|
-
card: {
|
|
152
|
-
shadowColor: theme.colors.overlay,
|
|
153
|
-
shadowOffset: { width: 0, height: 2 },
|
|
154
|
-
shadowOpacity: 0.08,
|
|
155
|
-
shadowRadius: 8,
|
|
156
|
-
elevation: 4,
|
|
157
|
-
},
|
|
158
|
-
cardHover: {
|
|
159
|
-
shadowColor: theme.colors.overlay,
|
|
160
|
-
shadowOffset: { width: 0, height: 8 },
|
|
161
|
-
shadowOpacity: 0.12,
|
|
162
|
-
shadowRadius: 24,
|
|
163
|
-
elevation: 8,
|
|
164
|
-
},
|
|
165
|
-
cardSubtle: {
|
|
166
|
-
shadowColor: theme.colors.overlay,
|
|
167
|
-
shadowOffset: { width: 0, height: 1 },
|
|
168
|
-
shadowOpacity: 0.08,
|
|
169
|
-
shadowRadius: 3,
|
|
170
|
-
elevation: 2,
|
|
171
|
-
},
|
|
110
|
+
base: { x: 0, y: 1, blur: 3, color: overlay, opacity: 0.1 },
|
|
111
|
+
soft: { x: 0, y: 4, blur: 6, color: overlay, opacity: 0.1 },
|
|
112
|
+
sharp: { x: 0, y: 1, blur: 1, color: overlay, opacity: 0.15 },
|
|
113
|
+
subtle: { x: 0, y: 1, blur: 2, color: overlay, opacity: 0.05 },
|
|
114
|
+
elevated: { x: 0, y: 20, blur: 40, color: overlay, opacity: 0.15 },
|
|
115
|
+
glow: { x: 0, y: 4, blur: 20, color: theme.colors.primary, opacity: 0.4 },
|
|
116
|
+
glass: { x: 0, y: 4, blur: 30, color: overlay, opacity: 0.05 },
|
|
117
|
+
card: { x: 0, y: 2, blur: 8, color: overlay, opacity: 0.08 },
|
|
118
|
+
cardHover: { x: 0, y: 8, blur: 24, color: overlay, opacity: 0.12 },
|
|
119
|
+
cardSubtle: { x: 0, y: 1, blur: 3, color: overlay, opacity: 0.08 },
|
|
120
|
+
};
|
|
121
|
+
const { x, y, blur, color, opacity } = shadowConfigs[type];
|
|
122
|
+
// Don't boost the glow accent — it's already a deliberate, vivid alpha.
|
|
123
|
+
const alpha = color === theme.colors.primary ? opacity : Math.min(opacity * boost, 1);
|
|
124
|
+
return {
|
|
125
|
+
boxShadow: `${x}px ${y}px ${blur}px ${withAlpha(color, alpha)}`,
|
|
172
126
|
};
|
|
173
|
-
const config = shadowConfigs[type];
|
|
174
|
-
if (Platform.OS === "web") {
|
|
175
|
-
const webShadows = {
|
|
176
|
-
base: { boxShadow: theme.dark ? "0 1px 2px rgba(0, 0, 0, 0.45)" : "0 1px 2px rgba(0, 0, 0, 0.08)" },
|
|
177
|
-
soft: { boxShadow: theme.dark ? "0 8px 24px rgba(0, 0, 0, 0.36)" : "0 8px 24px rgba(0, 0, 0, 0.10)" },
|
|
178
|
-
sharp: { boxShadow: theme.dark ? "0 1px 1px rgba(0, 0, 0, 0.55)" : "0 1px 1px rgba(0, 0, 0, 0.12)" },
|
|
179
|
-
subtle: { boxShadow: theme.dark ? "0 1px 2px rgba(0, 0, 0, 0.32)" : "0 1px 2px rgba(0, 0, 0, 0.05)" },
|
|
180
|
-
elevated: { boxShadow: theme.dark ? "0 20px 40px rgba(0, 0, 0, 0.38)" : "0 20px 40px rgba(0, 0, 0, 0.15)" },
|
|
181
|
-
glow: { boxShadow: `0 0 20px ${theme.colors.primary}` },
|
|
182
|
-
glass: { boxShadow: theme.dark ? "0 4px 30px rgba(0, 0, 0, 0.32)" : "0 4px 30px rgba(0, 0, 0, 0.05)" },
|
|
183
|
-
card: { boxShadow: theme.dark ? "0 1px 2px rgba(0, 0, 0, 0.32)" : "0 1px 3px rgba(0, 0, 0, 0.08)" },
|
|
184
|
-
cardHover: { boxShadow: theme.dark ? "0 8px 24px rgba(0, 0, 0, 0.36)" : "0 8px 24px rgba(0, 0, 0, 0.12)" },
|
|
185
|
-
cardSubtle: { boxShadow: theme.dark ? "0 1px 2px rgba(0, 0, 0, 0.32)" : "0 1px 3px rgba(0, 0, 0, 0.05)" },
|
|
186
|
-
};
|
|
187
|
-
return webShadows[type];
|
|
188
|
-
}
|
|
189
|
-
return Platform.select({
|
|
190
|
-
default: config,
|
|
191
|
-
});
|
|
192
127
|
}, [theme]);
|
|
193
128
|
const getFocusRingStyle = useCallback((offset = 2) => {
|
|
194
129
|
if (Platform.OS !== "web") {
|
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
* - show({ type, title, messages, duration, loading, action }): displays a notification
|
|
9
9
|
* - hide(): hides the current notification
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Notifications auto-dismiss after `DEFAULT_NOTIFICATION_DURATION` unless a
|
|
12
|
+
* `duration` is given. Pass `duration: 0` to keep one up until dismissed;
|
|
13
|
+
* loading notifications never auto-dismiss.
|
|
14
|
+
*
|
|
15
|
+
* Prefer the `notify` helpers (see ./notify) for triggering notifications from
|
|
16
|
+
* app code; use this store directly for reactive subscription (selectors) and tests.
|
|
12
17
|
*/
|
|
13
18
|
export type GlobalNotificationType = "error" | "success" | "info" | "warning";
|
|
19
|
+
/** Auto-dismiss delay applied when `show()` is called without a `duration`. */
|
|
20
|
+
export declare const DEFAULT_NOTIFICATION_DURATION = 4000;
|
|
14
21
|
export type GlobalNotificationPosition = "top" | "bottom";
|
|
15
22
|
export type GlobalNotificationAction = {
|
|
16
23
|
label: string;
|
|
@@ -21,6 +28,7 @@ export type GlobalNotificationAlert = {
|
|
|
21
28
|
type: GlobalNotificationType;
|
|
22
29
|
title?: string;
|
|
23
30
|
messages?: string[];
|
|
31
|
+
/** Auto-dismiss delay in ms. Defaults to `DEFAULT_NOTIFICATION_DURATION`; 0 = stays until dismissed. */
|
|
24
32
|
duration?: number;
|
|
25
33
|
loading?: boolean;
|
|
26
34
|
/** Where to display the notification */
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
+
/** Auto-dismiss delay applied when `show()` is called without a `duration`. */
|
|
3
|
+
export const DEFAULT_NOTIFICATION_DURATION = 4000;
|
|
2
4
|
export const globalUIStore = create((set) => ({
|
|
3
5
|
alert: null,
|
|
4
6
|
show: (alert) => set({
|
|
5
|
-
alert: {
|
|
7
|
+
alert: {
|
|
8
|
+
...alert,
|
|
9
|
+
// Loading notifications stay up until replaced or hidden (e.g. by
|
|
10
|
+
// notify.promise); everything else falls back to the default timeout.
|
|
11
|
+
duration: alert.duration ?? (alert.loading ? undefined : DEFAULT_NOTIFICATION_DURATION),
|
|
12
|
+
show: true,
|
|
13
|
+
},
|
|
6
14
|
}),
|
|
7
15
|
hide: () => set({ alert: null }),
|
|
8
16
|
}));
|
package/dist/state/index.d.ts
CHANGED
package/dist/state/index.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { GlobalNotificationAlert } from "./globalUIStore";
|
|
2
|
+
/**
|
|
3
|
+
* notify
|
|
4
|
+
*
|
|
5
|
+
* Imperative notification API backed by `globalUIStore`. This is the
|
|
6
|
+
* recommended way to trigger the `Notification` component from app code.
|
|
7
|
+
*
|
|
8
|
+
* Notifications auto-dismiss after `DEFAULT_NOTIFICATION_DURATION` (4s) by
|
|
9
|
+
* default. Pass `duration: 0` to keep one up until dismissed; `notify.loading`
|
|
10
|
+
* is always persistent.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* notify.success("Saved", { messages: ["Your changes have been saved."] });
|
|
15
|
+
* notify.error("Upload failed");
|
|
16
|
+
* notify.loading("Uploading…");
|
|
17
|
+
* notify.hide();
|
|
18
|
+
*
|
|
19
|
+
* // Full control (same payload as globalUIStore show())
|
|
20
|
+
* notify({ type: "info", title: "Copied", duration: 2000, position: "bottom" });
|
|
21
|
+
*
|
|
22
|
+
* // Loading → success/error around a promise
|
|
23
|
+
* await notify.promise(saveProfile(), {
|
|
24
|
+
* loading: "Saving…",
|
|
25
|
+
* success: "Profile saved",
|
|
26
|
+
* error: "Could not save profile",
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export type NotifyOptions = Omit<GlobalNotificationAlert, "show" | "type" | "title">;
|
|
31
|
+
export type NotifyPromiseMessages<T> = {
|
|
32
|
+
loading: string;
|
|
33
|
+
success: string | ((value: T) => string);
|
|
34
|
+
error: string | ((error: unknown) => string);
|
|
35
|
+
};
|
|
36
|
+
export declare const notify: ((alert: Omit<GlobalNotificationAlert, "show">) => void) & {
|
|
37
|
+
success: (title: string, options?: NotifyOptions) => void;
|
|
38
|
+
error: (title: string, options?: NotifyOptions) => void;
|
|
39
|
+
info: (title: string, options?: NotifyOptions) => void;
|
|
40
|
+
warning: (title: string, options?: NotifyOptions) => void;
|
|
41
|
+
/** Persistent spinner notification; stays visible until replaced or hidden. */
|
|
42
|
+
loading: (title: string, options?: NotifyOptions) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Shows a loading notification while the promise is pending, then a
|
|
45
|
+
* success or error notification. Rethrows on rejection and returns the
|
|
46
|
+
* resolved value so it can wrap existing async flows transparently.
|
|
47
|
+
*/
|
|
48
|
+
promise: <T>(promise: Promise<T>, messages: NotifyPromiseMessages<T>) => Promise<T>;
|
|
49
|
+
hide: () => void;
|
|
50
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { globalUIStore } from "./globalUIStore.js";
|
|
2
|
+
const show = (alert) => globalUIStore.getState().show(alert);
|
|
3
|
+
const showType = (type) => (title, options) => show({ type, title, ...options });
|
|
4
|
+
export const notify = Object.assign(show, {
|
|
5
|
+
success: showType("success"),
|
|
6
|
+
error: showType("error"),
|
|
7
|
+
info: showType("info"),
|
|
8
|
+
warning: showType("warning"),
|
|
9
|
+
/** Persistent spinner notification; stays visible until replaced or hidden. */
|
|
10
|
+
loading: (title, options) => show({ type: "info", title, loading: true, ...options }),
|
|
11
|
+
/**
|
|
12
|
+
* Shows a loading notification while the promise is pending, then a
|
|
13
|
+
* success or error notification. Rethrows on rejection and returns the
|
|
14
|
+
* resolved value so it can wrap existing async flows transparently.
|
|
15
|
+
*/
|
|
16
|
+
promise: async (promise, messages) => {
|
|
17
|
+
notify.loading(messages.loading);
|
|
18
|
+
try {
|
|
19
|
+
const value = await promise;
|
|
20
|
+
const title = typeof messages.success === "function" ? messages.success(value) : messages.success;
|
|
21
|
+
notify.success(title);
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const title = typeof messages.error === "function" ? messages.error(error) : messages.error;
|
|
26
|
+
notify.error(title);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
hide: () => globalUIStore.getState().hide(),
|
|
31
|
+
});
|
package/llms-full.md
CHANGED
|
@@ -34,7 +34,7 @@ the root when the app uses package feedback or overlay components.
|
|
|
34
34
|
`UIProvider` owns the package `Notification`, `StatusBar`, and default
|
|
35
35
|
`@rn-primitives` portal host. Mount it before using `Dialog`, `AlertDialog`,
|
|
36
36
|
`BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`,
|
|
37
|
-
`Tooltip`, or `globalUIStore` notifications.
|
|
37
|
+
`Tooltip`, or `notify` / `globalUIStore` notifications.
|
|
38
38
|
|
|
39
39
|
On native, `BottomSheet.Content` composes its sheet transform with React Native
|
|
40
40
|
keyboard event values. Pass `avoidKeyboard={false}` for sheets that should not
|
|
@@ -66,7 +66,7 @@ import { Button, StyledText, UIProvider } from "@mrmeg/expo-ui/components";
|
|
|
66
66
|
import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
|
|
67
67
|
import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
|
|
68
68
|
import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
|
|
69
|
-
import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
|
|
69
|
+
import { globalUIStore, notify, useThemeStore } from "@mrmeg/expo-ui/state";
|
|
70
70
|
import { configureExpoUiI18n, hapticLight } from "@mrmeg/expo-ui/lib";
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -101,7 +101,7 @@ Use this catalog before creating a new app-local primitive.
|
|
|
101
101
|
| `InputOTP` | `@mrmeg/expo-ui/components` | Verification code entry | Prefer over manually managed text input groups. |
|
|
102
102
|
| `Label` | `@mrmeg/expo-ui/components` | Accessible form labels | Use with package form controls. |
|
|
103
103
|
| `MaxWidthContainer` | `@mrmeg/expo-ui/components` | Centered responsive width | Use for web and tablet constrained layouts. |
|
|
104
|
-
| `Notification` | `@mrmeg/expo-ui/components` | Global toast surface | Trigger through `globalUIStore` with root `UIProvider`; optional actions dismiss after press. |
|
|
104
|
+
| `Notification` | `@mrmeg/expo-ui/components` | Global toast surface | Trigger through `notify` (or `globalUIStore` for subscriptions/tests) with root `UIProvider`; optional actions dismiss after press. |
|
|
105
105
|
| `Popover` | `@mrmeg/expo-ui/components` | Anchored contextual content | Requires root `UIProvider` portal setup. |
|
|
106
106
|
| `Progress` | `@mrmeg/expo-ui/components` | Determinate or indeterminate progress | Prefer over layout-shifting spinners for progress regions. |
|
|
107
107
|
| `RadioGroup` | `@mrmeg/expo-ui/components` | Small mutually exclusive choices | Use `Select` for longer option sets. |
|
|
@@ -134,6 +134,37 @@ full page sections. Use `EmptyState` for no-data or recoverable error regions,
|
|
|
134
134
|
`Skeleton` for loading content with stable layout, and `Progress` for real
|
|
135
135
|
progress or indeterminate long-running work.
|
|
136
136
|
|
|
137
|
+
## Notifications
|
|
138
|
+
|
|
139
|
+
`notify` is the primary imperative API for triggering the `Notification` component. Import from `@mrmeg/expo-ui/state` (also re-exported from the package root).
|
|
140
|
+
|
|
141
|
+
Notifications auto-dismiss after 4s (`DEFAULT_NOTIFICATION_DURATION`) unless a `duration` is given; pass `duration: 0` to keep one up until dismissed. `notify.loading` never auto-dismisses.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { notify } from "@mrmeg/expo-ui/state";
|
|
145
|
+
|
|
146
|
+
notify.success("Saved", { messages: ["Your changes were saved."] });
|
|
147
|
+
notify.error("Upload failed");
|
|
148
|
+
notify.warning("Connection slow");
|
|
149
|
+
notify.info("Copied to clipboard");
|
|
150
|
+
|
|
151
|
+
// Loading spinner — persists until replaced or hidden (no auto-dismiss)
|
|
152
|
+
notify.loading("Uploading…");
|
|
153
|
+
notify.hide();
|
|
154
|
+
|
|
155
|
+
// Full control (same payload as globalUIStore show())
|
|
156
|
+
notify({ type: "success", title: "Saved", duration: 3000, position: "bottom" });
|
|
157
|
+
|
|
158
|
+
// Loading → success/error around a promise; rethrows on rejection
|
|
159
|
+
await notify.promise(saveProfile(), {
|
|
160
|
+
loading: "Saving…",
|
|
161
|
+
success: "Profile saved", // or (value) => `Saved ${value.name}`
|
|
162
|
+
error: "Could not save profile", // or (err) => err.message
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`globalUIStore` (the underlying zustand store) remains available for reactive selectors and tests. Use `notify` for all imperative triggers in app code.
|
|
167
|
+
|
|
137
168
|
## Validation
|
|
138
169
|
|
|
139
170
|
Run the UI package gates when changing package code or shipped docs:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrmeg/expo-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
|
|
6
6
|
"keywords": [
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"zustand": ">=5.0.0 <6.0.0"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
125
|
-
"@types/react": "~19.2.
|
|
125
|
+
"@types/react": "~19.2.17",
|
|
126
126
|
"typescript": "~6.0.3"
|
|
127
127
|
}
|
|
128
128
|
}
|