@vvedantb/native-ui 0.2.0 → 0.3.12
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/README.md +160 -9
- package/package.json +88 -8
- package/src/AlertDialog.tsx +73 -0
- package/src/AmbientBackground.tsx +26 -11
- package/src/Avatar.tsx +141 -0
- package/src/Badge.tsx +108 -0
- package/src/BottomSheetContainer.tsx +7 -1
- package/src/Button.tsx +153 -52
- package/src/Card.tsx +111 -36
- package/src/Checkbox.tsx +78 -0
- package/src/Chip.tsx +5 -3
- package/src/ConfirmDialog.tsx +105 -0
- package/src/DateTimeField.tsx +6 -2
- package/src/Dialog.tsx +253 -0
- package/src/EmptyState.tsx +122 -7
- package/src/FloatingFAB.tsx +2 -4
- package/src/FloatingSurface.tsx +31 -9
- package/src/FloatingTabBar.tsx +39 -11
- package/src/Input.tsx +15 -4
- package/src/Label.tsx +31 -0
- package/src/LabeledSwitchRow.tsx +96 -0
- package/src/LiquidGlass.tsx +2 -10
- package/src/ListRow.tsx +7 -2
- package/src/PillTabs.tsx +28 -14
- package/src/Progress.tsx +67 -0
- package/src/SearchInput.tsx +163 -0
- package/src/Select.tsx +32 -6
- package/src/Separator.tsx +38 -0
- package/src/Sheet.tsx +107 -0
- package/src/Skeleton.tsx +25 -5
- package/src/Spinner.tsx +92 -0
- package/src/StatusDot.tsx +66 -0
- package/src/Switch.tsx +24 -7
- package/src/Tabs.tsx +190 -0
- package/src/Textarea.tsx +47 -0
- package/src/Toast.tsx +170 -0
- package/src/blur.ts +48 -0
- package/src/brand/AppIcons.tsx +106 -0
- package/src/brand/AppMark.tsx +83 -0
- package/src/brand/AppWordmark.tsx +97 -0
- package/src/brand/familyApps.ts +24 -0
- package/src/brand/index.ts +34 -0
- package/src/brand/svg.ts +263 -0
- package/src/brand/vProAppBenefits.ts +22 -0
- package/src/controlStyles.ts +360 -0
- package/src/dialogLayout.ts +20 -0
- package/src/entries/ambient.ts +3 -0
- package/src/entries/bottom-sheet.ts +2 -0
- package/src/entries/brand.ts +2 -0
- package/src/entries/button.ts +3 -0
- package/src/entries/date-time.ts +3 -0
- package/src/entries/floating.ts +24 -0
- package/src/entries/layout.ts +7 -0
- package/src/entries/marketing.ts +10 -0
- package/src/entries/primitives.ts +99 -0
- package/src/entries/search-input.ts +4 -0
- package/src/entries/tab-bar.ts +10 -0
- package/src/entries/theme.ts +15 -0
- package/src/entries/toast.ts +6 -0
- package/src/examples/ParityGallery.tsx +496 -0
- package/src/floating.ts +5 -1
- package/src/glassBlur.tsx +95 -0
- package/src/gradient.ts +21 -0
- package/src/haptics.ts +22 -2
- package/src/index.ts +99 -2
- package/src/initials.ts +10 -0
- package/src/marketing/PricingModal.tsx +410 -0
- package/src/marketing/UpgradeToProCard.tsx +103 -0
- package/src/marketing/pricing.ts +76 -0
- package/src/palette.ts +43 -0
- package/src/primitives/input.tsx +9 -2
- package/src/primitives/native-only-animated-view.tsx +10 -3
- package/src/primitives/overlay.tsx +179 -0
- package/src/primitives/select.tsx +25 -12
- package/src/primitives/switch.tsx +12 -13
- package/src/primitives/use-controllable-state.ts +24 -0
- package/src/settings/SettingsField.tsx +11 -2
- package/src/settings/SettingsSection.tsx +20 -6
- package/src/settings/SettingsToggleRow.tsx +7 -2
- package/src/tabBarRoutes.ts +52 -0
- package/src/toastChrome.ts +103 -0
- package/src/toastStore.ts +86 -0
- package/src/tokens.ts +19 -14
- package/src/typography.ts +24 -0
- package/src/useReduceMotion.ts +24 -0
- package/src/useScheme.ts +6 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `Toaster` look, from the web `SonnerToaster` at its default density
|
|
3
|
+
* (`createToasterClassNames` in `sonner.tsx`) with `toastIcons="filled24"`
|
|
4
|
+
* (`sonner-icons.tsx`). No React Native import, so the tests can load this
|
|
5
|
+
* file.
|
|
6
|
+
*/
|
|
7
|
+
import { palette, withAlpha } from "./palette";
|
|
8
|
+
import { tokens } from "./tokens";
|
|
9
|
+
|
|
10
|
+
type Scheme = "light" | "dark";
|
|
11
|
+
|
|
12
|
+
/** Web `rounded-lg` (`--radius`, 16), the default `toastRadius`. */
|
|
13
|
+
export const TOAST_RADIUS = tokens.radius.lg;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The web `smooth-shadow-ring-md`: a 1 px ring in the border colour at 60%
|
|
17
|
+
* (`color-mix(in srgb, var(--color-border) 60%, transparent)`) and a
|
|
18
|
+
* `0 4px 12px rgb(0 0 0 / 0.08)` drop shadow. Native draws the ring as a
|
|
19
|
+
* border, so the painted toast is the web toast plus its ring.
|
|
20
|
+
*/
|
|
21
|
+
export const TOAST_RING = { width: 1, alpha: 0.6 } as const;
|
|
22
|
+
export const TOAST_SHADOW = { offsetY: 4, blur: 12, opacity: 0.08 } as const;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fill, ring and radius of a toast. The fill is the web `glass-panel-strong`,
|
|
26
|
+
* the flat `--overlay` (`palette.overlay`), with no blur.
|
|
27
|
+
*/
|
|
28
|
+
export function toastSurfaceStyle(scheme: Scheme) {
|
|
29
|
+
return {
|
|
30
|
+
backgroundColor: palette.overlay[scheme],
|
|
31
|
+
borderColor: withAlpha(tokens.color.border[scheme], TOAST_RING.alpha),
|
|
32
|
+
borderWidth: TOAST_RING.width,
|
|
33
|
+
borderRadius: TOAST_RADIUS,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The drop shadow as a style: `boxShadow` on Android and web, the shadow
|
|
39
|
+
* props on iOS. The iOS radius is half the CSS blur, as a CSS blur is twice
|
|
40
|
+
* the Gaussian deviation.
|
|
41
|
+
*/
|
|
42
|
+
export function toastShadowStyle(os: string) {
|
|
43
|
+
const { offsetY, blur, opacity } = TOAST_SHADOW;
|
|
44
|
+
if (os === "ios") {
|
|
45
|
+
return {
|
|
46
|
+
shadowColor: tokens.color.black,
|
|
47
|
+
shadowOffset: { width: 0, height: offsetY },
|
|
48
|
+
shadowOpacity: opacity,
|
|
49
|
+
shadowRadius: blur / 2,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { boxShadow: `0px ${offsetY}px ${blur}px rgba(0, 0, 0, ${opacity})` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type ToastIconVariant = "success" | "info" | "warning" | "error";
|
|
56
|
+
|
|
57
|
+
/** Web `h-6 w-6`. */
|
|
58
|
+
export const TOAST_ICON_SIZE = 24;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The web `filledToastIcons`, path for path: filled shapes painted in the
|
|
62
|
+
* variant colour, with no stroke. `error` keeps the web 20 × 20 viewBox and
|
|
63
|
+
* `warning` its even-odd fill.
|
|
64
|
+
*/
|
|
65
|
+
export const TOAST_ICONS: Record<
|
|
66
|
+
ToastIconVariant,
|
|
67
|
+
{ viewBox: string; d: string; evenOdd?: true }
|
|
68
|
+
> = {
|
|
69
|
+
success: {
|
|
70
|
+
viewBox: "0 0 24 24",
|
|
71
|
+
d: "M12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2ZM16.78 9.7L11.11 15.37C10.97 15.51 10.78 15.59 10.58 15.59C10.38 15.59 10.19 15.51 10.05 15.37L7.22 12.54C6.93 12.25 6.93 11.77 7.22 11.48C7.51 11.19 7.99 11.19 8.28 11.48L10.58 13.78L15.72 8.64C16.01 8.35 16.49 8.35 16.78 8.64C17.07 8.93 17.07 9.4 16.78 9.7Z",
|
|
72
|
+
},
|
|
73
|
+
info: {
|
|
74
|
+
viewBox: "0 0 24 24",
|
|
75
|
+
d: "M12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22ZM12.75 16C12.75 16.41 12.41 16.75 12 16.75C11.59 16.75 11.25 16.41 11.25 16L11.25 11C11.25 10.59 11.59 10.25 12 10.25C12.41 10.25 12.75 10.59 12.75 11L12.75 16ZM11.08 7.62C11.13 7.49 11.2 7.39 11.29 7.29C11.39 7.2 11.5 7.13 11.62 7.08C11.74 7.03 11.87 7 12 7C12.13 7 12.26 7.03 12.38 7.08C12.5 7.13 12.61 7.2 12.71 7.29C12.8 7.39 12.87 7.49 12.92 7.62C12.97 7.74 13 7.87 13 8C13 8.13 12.97 8.26 12.92 8.38C12.87 8.5 12.8 8.61 12.71 8.71C12.61 8.8 12.5 8.87 12.38 8.92C12.14 9.02 11.86 9.02 11.62 8.92C11.5 8.87 11.39 8.8 11.29 8.71C11.2 8.61 11.13 8.5 11.08 8.38C11.03 8.26 11 8.13 11 8C11 7.87 11.03 7.74 11.08 7.62Z",
|
|
76
|
+
},
|
|
77
|
+
warning: {
|
|
78
|
+
viewBox: "0 0 24 24",
|
|
79
|
+
d: "M3 10.417C3 7.219 3 5.62 3.378 5.082C3.755 4.545 5.258 4.03 8.265 3.001L8.838 2.805C10.405 2.268 11.188 2 12 2C12.812 2 13.595 2.268 15.162 2.805L15.735 3.001C18.742 4.03 20.245 4.545 20.622 5.082C21 5.62 21 7.22 21 10.417V11.991C21 17.629 16.761 20.366 14.101 21.527C13.38 21.842 13.02 22 12 22C10.98 22 10.62 21.842 9.899 21.527C7.239 20.365 3 17.63 3 11.991V10.417ZM12 7.25C12.1989 7.25 12.3897 7.32902 12.5303 7.46967C12.671 7.61032 12.75 7.80109 12.75 8V12C12.75 12.1989 12.671 12.3897 12.5303 12.5303C12.3897 12.671 12.1989 12.75 12 12.75C11.8011 12.75 11.6103 12.671 11.4697 12.5303C11.329 12.3897 11.25 12.1989 11.25 12V8C11.25 7.80109 11.329 7.61032 11.4697 7.46967C11.6103 7.32902 11.8011 7.25 12 7.25ZM12 16C12.2652 16 12.5196 15.8946 12.7071 15.7071C12.8946 15.5196 13 15.2652 13 15C13 14.7348 12.8946 14.4804 12.7071 14.2929C12.5196 14.1054 12.2652 14 12 14C11.7348 14 11.4804 14.1054 11.2929 14.2929C11.1054 14.4804 11 14.7348 11 15C11 15.2652 11.1054 15.5196 11.2929 15.7071C11.4804 15.8946 11.7348 16 12 16Z",
|
|
80
|
+
evenOdd: true,
|
|
81
|
+
},
|
|
82
|
+
error: {
|
|
83
|
+
viewBox: "0 0 20 20",
|
|
84
|
+
d: "M17.51 3.85L11.57 0.42C10.6 -0.14 9.4 -0.14 8.42 0.42L2.49 3.85C1.52 4.41 0.919998 5.45 0.919998 6.58V13.42C0.919998 14.54 1.52 15.58 2.49 16.15L8.43 19.58C9.4 20.14 10.6 20.14 11.58 19.58L17.52 16.15C18.49 15.59 19.09 14.55 19.09 13.42V6.58C19.08 5.45 18.48 4.42 17.51 3.85ZM9.25 5.75C9.25 5.34 9.59 5 10 5C10.41 5 10.75 5.34 10.75 5.75V11C10.75 11.41 10.41 11.75 10 11.75C9.59 11.75 9.25 11.41 9.25 11V5.75ZM10.92 14.63C10.87 14.75 10.8 14.86 10.71 14.96C10.52 15.15 10.27 15.25 10 15.25C9.87 15.25 9.74 15.22 9.62 15.17C9.49 15.12 9.39 15.05 9.29 14.96C9.2 14.86 9.13 14.75 9.07 14.63C9.02 14.51 9 14.38 9 14.25C9 13.99 9.1 13.73 9.29 13.54C9.39 13.45 9.49 13.38 9.62 13.33C9.99 13.17 10.43 13.26 10.71 13.54C10.8 13.64 10.87 13.74 10.92 13.87C10.97 13.99 11 14.12 11 14.25C11 14.38 10.97 14.51 10.92 14.63Z",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Icon colour per variant: the web `text-success`, `text-destructive` (the
|
|
90
|
+
* `--danger` token), `text-warning` and `text-primary`.
|
|
91
|
+
*/
|
|
92
|
+
export function toastIconColor(variant: ToastIconVariant, scheme: Scheme): string {
|
|
93
|
+
switch (variant) {
|
|
94
|
+
case "success":
|
|
95
|
+
return palette.success[scheme];
|
|
96
|
+
case "error":
|
|
97
|
+
return palette.danger[scheme];
|
|
98
|
+
case "warning":
|
|
99
|
+
return palette.warning[scheme];
|
|
100
|
+
case "info":
|
|
101
|
+
return palette.primary[scheme];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Module-level toast queue behind `toast()` and `<Toaster />`. No React Native
|
|
2
|
+
// imports, so it runs anywhere (tests, non-component code).
|
|
3
|
+
|
|
4
|
+
export type ToastVariant = "default" | "success" | "error" | "warning" | "info" | "loading";
|
|
5
|
+
|
|
6
|
+
export type ToastOptions = {
|
|
7
|
+
/** Reuse an id to replace a toast in place, for example a `loading` toast with its result. */
|
|
8
|
+
id?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Milliseconds before it closes. Default 4000; `loading` stays until replaced or dismissed. `Infinity` keeps any toast open. */
|
|
11
|
+
duration?: number;
|
|
12
|
+
/** One text button. A press runs `onPress` and closes the toast. */
|
|
13
|
+
action?: { label: string; onPress: () => void };
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ToastItem = {
|
|
17
|
+
id: string;
|
|
18
|
+
variant: ToastVariant;
|
|
19
|
+
title: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
duration: number;
|
|
22
|
+
action?: ToastOptions["action"];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** At most this many toasts show; the oldest drop first. */
|
|
26
|
+
export const MAX_TOASTS = 3;
|
|
27
|
+
export const DEFAULT_TOAST_DURATION = 4000;
|
|
28
|
+
|
|
29
|
+
let toasts: readonly ToastItem[] = [];
|
|
30
|
+
let counter = 0;
|
|
31
|
+
const listeners = new Set<() => void>();
|
|
32
|
+
|
|
33
|
+
function emit() {
|
|
34
|
+
for (const listener of listeners) listener();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function subscribeToasts(listener: () => void): () => void {
|
|
38
|
+
listeners.add(listener);
|
|
39
|
+
return () => {
|
|
40
|
+
listeners.delete(listener);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getToasts(): readonly ToastItem[] {
|
|
45
|
+
return toasts;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function show(variant: ToastVariant, title: string, options: ToastOptions = {}): string {
|
|
49
|
+
const id = options.id ?? `toast-${++counter}`;
|
|
50
|
+
const item: ToastItem = {
|
|
51
|
+
id,
|
|
52
|
+
variant,
|
|
53
|
+
title,
|
|
54
|
+
description: options.description,
|
|
55
|
+
duration:
|
|
56
|
+
options.duration ?? (variant === "loading" ? Infinity : DEFAULT_TOAST_DURATION),
|
|
57
|
+
action: options.action,
|
|
58
|
+
};
|
|
59
|
+
toasts = toasts.some((toast) => toast.id === id)
|
|
60
|
+
? toasts.map((toast) => (toast.id === id ? item : toast))
|
|
61
|
+
: [...toasts, item].slice(-MAX_TOASTS);
|
|
62
|
+
emit();
|
|
63
|
+
return id;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Shows a toast and returns its id. The sonner-style shape of the web `toast`:
|
|
68
|
+
* `toast("Saved")`, `toast.success(...)`, `toast.error(...)`, `toast.warning(...)`,
|
|
69
|
+
* `toast.info(...)`, `toast.loading(...)` and `toast.dismiss(id?)`. Needs one
|
|
70
|
+
* `<Toaster />` mounted near the app root.
|
|
71
|
+
*/
|
|
72
|
+
export const toast = Object.assign(
|
|
73
|
+
(title: string, options?: ToastOptions) => show("default", title, options),
|
|
74
|
+
{
|
|
75
|
+
success: (title: string, options?: ToastOptions) => show("success", title, options),
|
|
76
|
+
error: (title: string, options?: ToastOptions) => show("error", title, options),
|
|
77
|
+
warning: (title: string, options?: ToastOptions) => show("warning", title, options),
|
|
78
|
+
info: (title: string, options?: ToastOptions) => show("info", title, options),
|
|
79
|
+
loading: (title: string, options?: ToastOptions) => show("loading", title, options),
|
|
80
|
+
/** Closes one toast, or every toast without an id. */
|
|
81
|
+
dismiss(id?: string) {
|
|
82
|
+
toasts = id === undefined ? [] : toasts.filter((toast) => toast.id !== id);
|
|
83
|
+
emit();
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
);
|
package/src/tokens.ts
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JS tokens for React Native
|
|
3
|
-
* (oklch neutrals → hex, 1rem = 16).
|
|
2
|
+
* JS tokens for React Native: colours, radii and spacing (1rem = 16).
|
|
4
3
|
*
|
|
5
4
|
* Copy of `packages/ui/tokens/src/native.ts`, kept here so native-ui has no
|
|
6
|
-
* dependency on the web package. A test in `@vvedantb/ui`
|
|
7
|
-
* differ below this header: change both together.
|
|
5
|
+
* dependency on the web package. A test in `@vvedantb/ui` and one in this
|
|
6
|
+
* package fail when the two differ below this header: change both together.
|
|
7
|
+
*
|
|
8
|
+
* The neutrals and `radius.field` match `tokens.css` (oklch → sRGB hex); a
|
|
9
|
+
* test converts `tokens.css` and fails when they drift. `fill` and `hairline`
|
|
10
|
+
* are native glass chrome with no web token. `radius.card` has no web token
|
|
11
|
+
* either; `docs/native-ui-parity.md` lists it. The web semantic colours
|
|
12
|
+
* (primary, danger, success, warning) are in `palette.ts`.
|
|
8
13
|
*/
|
|
9
14
|
export const tokens = {
|
|
10
15
|
color: {
|
|
11
|
-
background: { light: "#
|
|
12
|
-
foreground: { light: "#
|
|
13
|
-
muted: { light: "#
|
|
14
|
-
border: { light: "#
|
|
15
|
-
surface: { light: "#ffffff", dark: "#
|
|
16
|
-
surfaceSecondary: { light: "#
|
|
17
|
-
surfaceCard: { light: "#
|
|
18
|
-
separator: { light: "#
|
|
16
|
+
background: { light: "#f5f5f5", dark: "#060606" },
|
|
17
|
+
foreground: { light: "#181818", dark: "#fcfcfc" },
|
|
18
|
+
muted: { light: "#727272", dark: "#a0a0a0" },
|
|
19
|
+
border: { light: "#dedede", dark: "#292929" },
|
|
20
|
+
surface: { light: "#ffffff", dark: "#181818" },
|
|
21
|
+
surfaceSecondary: { light: "#efefef", dark: "#242323" },
|
|
22
|
+
surfaceCard: { light: "#f8f8f8", dark: "#242323" },
|
|
23
|
+
separator: { light: "#e4e4e4", dark: "#222222" },
|
|
19
24
|
white: "#ffffff",
|
|
20
25
|
black: "#000000",
|
|
21
26
|
hairline: {
|
|
@@ -24,12 +29,12 @@ export const tokens = {
|
|
|
24
29
|
},
|
|
25
30
|
fill: {
|
|
26
31
|
light: "rgba(255, 255, 255, 0.86)",
|
|
27
|
-
dark: "rgba(
|
|
32
|
+
dark: "rgba(24, 24, 24, 0.88)",
|
|
28
33
|
},
|
|
29
34
|
},
|
|
30
35
|
radius: {
|
|
31
36
|
lg: 16,
|
|
32
|
-
field:
|
|
37
|
+
field: 10,
|
|
33
38
|
md: 14,
|
|
34
39
|
sm: 12,
|
|
35
40
|
card: 24,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeWind classes for Instrument Sans. The app loads the font files and maps
|
|
3
|
+
* these classes to them (see README). Literal strings, so Tailwind's source
|
|
4
|
+
* scan generates them.
|
|
5
|
+
*/
|
|
6
|
+
export const fontClass = {
|
|
7
|
+
regular: "font-instrument-sans",
|
|
8
|
+
medium: "font-instrument-sans-medium",
|
|
9
|
+
semibold: "font-instrument-sans-semibold",
|
|
10
|
+
bold: "font-instrument-sans-bold",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `AppWordmark` geometry, the same numbers as the web `AppWordmark` and
|
|
15
|
+
* `.landing-wordmark-text`: Instrument Sans 500, letter spacing -0.02em, icon
|
|
16
|
+
* 1.4x the font size, gap 0.2em. A test checks them against the web source.
|
|
17
|
+
*/
|
|
18
|
+
export const WORDMARK = {
|
|
19
|
+
fontClass: fontClass.medium,
|
|
20
|
+
fontWeight: 500,
|
|
21
|
+
letterSpacingEm: -0.02,
|
|
22
|
+
iconScale: 1.4,
|
|
23
|
+
gapEm: 0.2,
|
|
24
|
+
} as const;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { AccessibilityInfo } from "react-native";
|
|
3
|
+
import { useReducedMotion } from "react-native-reanimated";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The system Reduce Motion setting (iOS) or Remove animations (Android). The
|
|
7
|
+
* first value is Reanimated's synchronous read, so an overlay that mounts open
|
|
8
|
+
* does not animate once before the setting loads. Later changes come from
|
|
9
|
+
* `AccessibilityInfo`.
|
|
10
|
+
*/
|
|
11
|
+
export function useReduceMotion() {
|
|
12
|
+
const initial = useReducedMotion();
|
|
13
|
+
const [enabled, setEnabled] = useState(initial);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const subscription = AccessibilityInfo.addEventListener(
|
|
17
|
+
"reduceMotionChanged",
|
|
18
|
+
setEnabled,
|
|
19
|
+
);
|
|
20
|
+
return () => subscription.remove();
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
return enabled;
|
|
24
|
+
}
|