@vvedantb/native-ui 0.0.0-stage → 0.1.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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/package.json +68 -4
  4. package/src/AmbientBackground.tsx +29 -0
  5. package/src/BottomSheetContainer.tsx +92 -0
  6. package/src/Button.tsx +111 -0
  7. package/src/Card.tsx +64 -0
  8. package/src/Chip.tsx +49 -0
  9. package/src/DateTimeField.tsx +78 -0
  10. package/src/EmptyState.tsx +51 -0
  11. package/src/FloatingFAB.tsx +62 -0
  12. package/src/FloatingSurface.tsx +114 -0
  13. package/src/FloatingTabBar.tsx +107 -0
  14. package/src/HeaderIconButton.tsx +29 -0
  15. package/src/Input.tsx +32 -0
  16. package/src/LiquidGlass.tsx +14 -0
  17. package/src/ListRow.tsx +85 -0
  18. package/src/PillTabs.tsx +51 -0
  19. package/src/SafeAreaContainer.tsx +26 -0
  20. package/src/ScreenContainer.tsx +36 -0
  21. package/src/Select.tsx +132 -0
  22. package/src/Skeleton.tsx +37 -0
  23. package/src/Switch.tsx +45 -0
  24. package/src/cn.ts +6 -0
  25. package/src/colors.ts +17 -0
  26. package/src/floating.ts +40 -0
  27. package/src/haptics.ts +9 -0
  28. package/src/index.ts +58 -0
  29. package/src/primitives/button.tsx +101 -0
  30. package/src/primitives/card.tsx +102 -0
  31. package/src/primitives/icon.tsx +14 -0
  32. package/src/primitives/input.tsx +37 -0
  33. package/src/primitives/native-only-animated-view.tsx +33 -0
  34. package/src/primitives/select.tsx +265 -0
  35. package/src/primitives/switch.tsx +77 -0
  36. package/src/primitives/text.tsx +95 -0
  37. package/src/settings/SettingsField.tsx +33 -0
  38. package/src/settings/SettingsSection.tsx +75 -0
  39. package/src/settings/SettingsStack.tsx +5 -0
  40. package/src/settings/SettingsToggleRow.tsx +58 -0
  41. package/src/theme.tsx +44 -0
  42. package/src/useReduceTransparency.ts +24 -0
@@ -0,0 +1,265 @@
1
+ import * as SelectPrimitive from "@rn-primitives/select";
2
+ import {
3
+ Check,
4
+ ChevronDown,
5
+ ChevronDownIcon,
6
+ ChevronUpIcon,
7
+ } from "lucide-react-native";
8
+ import * as React from "react";
9
+ import { Platform, ScrollView, StyleSheet, View } from "react-native";
10
+ import { FadeIn, FadeOut } from "react-native-reanimated";
11
+ import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
12
+ import { NativeOnlyAnimatedView } from "./native-only-animated-view";
13
+ import { TextClassContext } from "./text";
14
+ import { LiquidGlass } from "../LiquidGlass";
15
+ import { cn } from "../cn";
16
+
17
+ type Option = SelectPrimitive.Option;
18
+
19
+ const Select = SelectPrimitive.Root;
20
+ const SelectGroup = SelectPrimitive.Group;
21
+ const FullWindowOverlay =
22
+ Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
23
+
24
+ type SelectValueProps = React.ComponentPropsWithoutRef<
25
+ typeof SelectPrimitive.Value
26
+ > & {
27
+ className?: string;
28
+ };
29
+
30
+ const SelectValue = React.forwardRef<
31
+ React.ElementRef<typeof SelectPrimitive.Value>,
32
+ SelectValueProps
33
+ >(({ className, ...props }, ref) => {
34
+ return (
35
+ <SelectPrimitive.Value
36
+ ref={ref}
37
+ className={cn("line-clamp-1 text-sm text-foreground", className)}
38
+ {...props}
39
+ />
40
+ );
41
+ });
42
+
43
+ type SelectTriggerProps = React.ComponentPropsWithoutRef<
44
+ typeof SelectPrimitive.Trigger
45
+ > & {
46
+ className?: string;
47
+ size?: "default" | "sm";
48
+ };
49
+
50
+ const SelectTrigger = React.forwardRef<
51
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
52
+ SelectTriggerProps
53
+ >(({ className, children, size = "default", disabled, ...props }, ref) => {
54
+ return (
55
+ <SelectPrimitive.Trigger
56
+ ref={ref}
57
+ disabled={disabled}
58
+ className={cn(
59
+ "h-10 flex-row items-center justify-between gap-2 rounded-md border border-input bg-background px-3 py-2",
60
+ size === "sm" && "h-8 py-1.5",
61
+ disabled && "opacity-50",
62
+ className,
63
+ )}
64
+ {...props}
65
+ >
66
+ <>{children}</>
67
+ <ChevronDown size={16} color="#737373" />
68
+ </SelectPrimitive.Trigger>
69
+ );
70
+ });
71
+
72
+ type SelectContentProps = React.ComponentPropsWithoutRef<
73
+ typeof SelectPrimitive.Content
74
+ > & {
75
+ className?: string;
76
+ portalHost?: string;
77
+ };
78
+
79
+ const SelectContent = React.forwardRef<
80
+ React.ElementRef<typeof SelectPrimitive.Content>,
81
+ SelectContentProps
82
+ >(({ className, children, position = "popper", portalHost, ...props }, ref) => {
83
+ return (
84
+ <SelectPrimitive.Portal hostName={portalHost}>
85
+ <FullWindowOverlay>
86
+ <SelectPrimitive.Overlay
87
+ style={Platform.select({ native: StyleSheet.absoluteFill })}
88
+ >
89
+ <TextClassContext.Provider value="text-popover-foreground">
90
+ <NativeOnlyAnimatedView
91
+ entering={FadeIn}
92
+ exiting={FadeOut}
93
+ className="z-50"
94
+ >
95
+ <SelectPrimitive.Content
96
+ ref={ref}
97
+ position={position}
98
+ className={cn(
99
+ "z-50 min-w-[8rem] rounded-lg",
100
+ Platform.select({
101
+ native: "p-1",
102
+ web: "max-h-52 overflow-hidden",
103
+ }),
104
+ className,
105
+ )}
106
+ {...props}
107
+ >
108
+ <LiquidGlass
109
+ pointerEvents="none"
110
+ elevated={false}
111
+ className="absolute inset-0 rounded-lg"
112
+ />
113
+ <SelectScrollUpButton />
114
+ <SelectPrimitive.Viewport
115
+ className={cn("p-1", position === "popper" && "w-full")}
116
+ >
117
+ {children}
118
+ </SelectPrimitive.Viewport>
119
+ <SelectScrollDownButton />
120
+ </SelectPrimitive.Content>
121
+ </NativeOnlyAnimatedView>
122
+ </TextClassContext.Provider>
123
+ </SelectPrimitive.Overlay>
124
+ </FullWindowOverlay>
125
+ </SelectPrimitive.Portal>
126
+ );
127
+ });
128
+
129
+ type SelectLabelProps = React.ComponentPropsWithoutRef<
130
+ typeof SelectPrimitive.Label
131
+ > & {
132
+ className?: string;
133
+ };
134
+
135
+ const SelectLabel = React.forwardRef<
136
+ React.ElementRef<typeof SelectPrimitive.Label>,
137
+ SelectLabelProps
138
+ >(({ className, ...props }, ref) => {
139
+ return (
140
+ <SelectPrimitive.Label
141
+ ref={ref}
142
+ className={cn("px-2 py-2 text-xs text-muted-foreground", className)}
143
+ {...props}
144
+ />
145
+ );
146
+ });
147
+
148
+ type SelectItemProps = React.ComponentPropsWithoutRef<
149
+ typeof SelectPrimitive.Item
150
+ > & {
151
+ className?: string;
152
+ };
153
+
154
+ const SelectItem = React.forwardRef<
155
+ React.ElementRef<typeof SelectPrimitive.Item>,
156
+ SelectItemProps
157
+ >(({ className, disabled, ...props }, ref) => {
158
+ return (
159
+ <SelectPrimitive.Item
160
+ ref={ref}
161
+ disabled={disabled}
162
+ className={cn(
163
+ "relative flex-row items-center gap-2 rounded-sm py-3 pl-2 pr-8 active:opacity-70",
164
+ disabled && "opacity-50",
165
+ className,
166
+ )}
167
+ {...props}
168
+ >
169
+ <View className="absolute right-2 size-3.5 items-center justify-center">
170
+ <SelectPrimitive.ItemIndicator>
171
+ <Check size={16} color="#737373" />
172
+ </SelectPrimitive.ItemIndicator>
173
+ </View>
174
+ <SelectPrimitive.ItemText className="select-none text-sm font-instrument-sans-medium text-foreground" />
175
+ </SelectPrimitive.Item>
176
+ );
177
+ });
178
+
179
+ type SelectSeparatorProps = React.ComponentPropsWithoutRef<
180
+ typeof SelectPrimitive.Separator
181
+ > & {
182
+ className?: string;
183
+ };
184
+
185
+ const SelectSeparator = React.forwardRef<
186
+ React.ElementRef<typeof SelectPrimitive.Separator>,
187
+ SelectSeparatorProps
188
+ >(({ className, ...props }, ref) => {
189
+ return (
190
+ <SelectPrimitive.Separator
191
+ ref={ref}
192
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
193
+ {...props}
194
+ />
195
+ );
196
+ });
197
+
198
+ function SelectScrollUpButton(
199
+ props: React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>,
200
+ ) {
201
+ if (Platform.OS !== "web") {
202
+ return null;
203
+ }
204
+
205
+ return (
206
+ <SelectPrimitive.ScrollUpButton
207
+ className="items-center justify-center py-1"
208
+ {...props}
209
+ >
210
+ <ChevronUpIcon size={16} color="#737373" />
211
+ </SelectPrimitive.ScrollUpButton>
212
+ );
213
+ }
214
+
215
+ function SelectScrollDownButton(
216
+ props: React.ComponentPropsWithoutRef<
217
+ typeof SelectPrimitive.ScrollDownButton
218
+ >,
219
+ ) {
220
+ if (Platform.OS !== "web") {
221
+ return null;
222
+ }
223
+
224
+ return (
225
+ <SelectPrimitive.ScrollDownButton
226
+ className="items-center justify-center py-1"
227
+ {...props}
228
+ >
229
+ <ChevronDownIcon size={16} color="#737373" />
230
+ </SelectPrimitive.ScrollDownButton>
231
+ );
232
+ }
233
+
234
+ function NativeSelectScrollView({
235
+ className,
236
+ ...props
237
+ }: React.ComponentPropsWithoutRef<typeof ScrollView> & { className?: string }) {
238
+ if (Platform.OS === "web") {
239
+ return <>{props.children}</>;
240
+ }
241
+
242
+ return <ScrollView className={cn("max-h-52", className)} {...props} />;
243
+ }
244
+
245
+ SelectValue.displayName = "SelectValue";
246
+ SelectTrigger.displayName = "SelectTrigger";
247
+ SelectContent.displayName = "SelectContent";
248
+ SelectLabel.displayName = "SelectLabel";
249
+ SelectItem.displayName = "SelectItem";
250
+ SelectSeparator.displayName = "SelectSeparator";
251
+
252
+ export {
253
+ NativeSelectScrollView,
254
+ Select,
255
+ SelectContent,
256
+ SelectGroup,
257
+ SelectItem,
258
+ SelectLabel,
259
+ SelectScrollDownButton,
260
+ SelectScrollUpButton,
261
+ SelectSeparator,
262
+ SelectTrigger,
263
+ SelectValue,
264
+ type Option,
265
+ };
@@ -0,0 +1,77 @@
1
+ import * as SwitchPrimitives from "@rn-primitives/switch";
2
+ import * as React from "react";
3
+ import { Platform } from "react-native";
4
+ import { cn } from "../cn";
5
+
6
+ type SwitchProps = React.ComponentPropsWithoutRef<
7
+ typeof SwitchPrimitives.Root
8
+ > & {
9
+ className?: string;
10
+ thumbClassName?: string;
11
+ onColor?: string;
12
+ offColor?: string;
13
+ };
14
+
15
+ const Switch = React.forwardRef<
16
+ React.ElementRef<typeof SwitchPrimitives.Root>,
17
+ SwitchProps
18
+ >(
19
+ (
20
+ {
21
+ className,
22
+ thumbClassName,
23
+ onColor,
24
+ offColor,
25
+ checked,
26
+ style,
27
+ disabled,
28
+ ...props
29
+ },
30
+ ref,
31
+ ) => {
32
+ const isChecked = Boolean(checked);
33
+ const trackColor =
34
+ Platform.OS !== "web" && (onColor || offColor)
35
+ ? { backgroundColor: isChecked ? onColor : offColor }
36
+ : null;
37
+ const resolvedStyle =
38
+ Platform.OS === "web" || typeof style === "function"
39
+ ? undefined
40
+ : trackColor
41
+ ? [trackColor, style]
42
+ : style;
43
+
44
+ return (
45
+ <SwitchPrimitives.Root
46
+ ref={ref}
47
+ checked={checked}
48
+ disabled={disabled}
49
+ style={resolvedStyle}
50
+ className={cn(
51
+ "h-[1.15rem] w-8 shrink-0 rounded-full border border-transparent",
52
+ Platform.select({
53
+ web: "outline-none transition-all",
54
+ }),
55
+ isChecked ? "bg-primary" : "bg-input dark:bg-input/80",
56
+ disabled && "opacity-50",
57
+ className,
58
+ )}
59
+ {...props}
60
+ >
61
+ <SwitchPrimitives.Thumb
62
+ className={cn(
63
+ "h-4 w-4 rounded-full bg-background transition-transform",
64
+ isChecked
65
+ ? "translate-x-3.5 dark:bg-primary-foreground"
66
+ : "translate-x-0 dark:bg-foreground",
67
+ thumbClassName,
68
+ )}
69
+ />
70
+ </SwitchPrimitives.Root>
71
+ );
72
+ },
73
+ );
74
+
75
+ Switch.displayName = "Switch";
76
+
77
+ export { Switch };
@@ -0,0 +1,95 @@
1
+ import * as Slot from "@rn-primitives/slot";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
+ import { Platform, Text as RNText, type Role } from "react-native";
5
+ import { cn } from "../cn";
6
+
7
+ const textVariants = cva(
8
+ cn(
9
+ "text-foreground text-base",
10
+ Platform.select({
11
+ web: "select-text",
12
+ }),
13
+ ),
14
+ {
15
+ variants: {
16
+ variant: {
17
+ default: "",
18
+ h1: cn(
19
+ "text-4xl font-extrabold tracking-tight",
20
+ Platform.select({ web: "scroll-m-20" }),
21
+ ),
22
+ h2: cn(
23
+ "text-3xl font-semibold tracking-tight border-border border-b pb-2",
24
+ Platform.select({ web: "scroll-m-20" }),
25
+ ),
26
+ h3: cn(
27
+ "text-2xl font-semibold tracking-tight",
28
+ Platform.select({ web: "scroll-m-20" }),
29
+ ),
30
+ h4: cn(
31
+ "text-xl font-semibold tracking-tight",
32
+ Platform.select({ web: "scroll-m-20" }),
33
+ ),
34
+ p: "leading-7",
35
+ blockquote: "border-l-2 border-border pl-4 italic",
36
+ code: "bg-muted rounded px-[0.3rem] py-[0.2rem] text-sm font-semibold",
37
+ lead: "text-xl text-muted-foreground",
38
+ large: "text-lg font-semibold",
39
+ small: "text-sm font-medium",
40
+ muted: "text-sm text-muted-foreground",
41
+ },
42
+ },
43
+ defaultVariants: {
44
+ variant: "default",
45
+ },
46
+ },
47
+ );
48
+
49
+ type TextVariantProps = VariantProps<typeof textVariants>;
50
+ type TextVariant = NonNullable<TextVariantProps["variant"]>;
51
+
52
+ const ROLE: Partial<Record<TextVariant, Role>> = {
53
+ h1: "heading",
54
+ h2: "heading",
55
+ h3: "heading",
56
+ h4: "heading",
57
+ blockquote: Platform.select({ web: "blockquote" as Role }),
58
+ code: Platform.select({ web: "code" as Role }),
59
+ };
60
+
61
+ const ARIA_LEVEL: Partial<Record<TextVariant, string>> = {
62
+ h1: "1",
63
+ h2: "2",
64
+ h3: "3",
65
+ h4: "4",
66
+ };
67
+
68
+ const TextClassContext = React.createContext<string | undefined>(undefined);
69
+
70
+ type TextProps = React.ComponentPropsWithoutRef<typeof RNText> &
71
+ TextVariantProps & {
72
+ asChild?: boolean;
73
+ className?: string;
74
+ };
75
+
76
+ const Text = React.forwardRef<React.ElementRef<typeof RNText>, TextProps>(
77
+ ({ className, asChild = false, variant = "default", ...props }, ref) => {
78
+ const textClass = React.useContext(TextClassContext);
79
+ const Component = asChild ? Slot.Text : RNText;
80
+
81
+ return (
82
+ <Component
83
+ ref={ref}
84
+ className={cn(textVariants({ variant }), textClass, className)}
85
+ role={variant ? ROLE[variant] : undefined}
86
+ aria-level={variant ? ARIA_LEVEL[variant] : undefined}
87
+ {...props}
88
+ />
89
+ );
90
+ },
91
+ );
92
+
93
+ Text.displayName = "Text";
94
+
95
+ export { Text, TextClassContext };
@@ -0,0 +1,33 @@
1
+ import { View, Text } from "react-native";
2
+
3
+ export function SettingsField({
4
+ label,
5
+ description,
6
+ children,
7
+ }: {
8
+ label: React.ReactNode;
9
+ description?: React.ReactNode;
10
+ children: React.ReactNode;
11
+ }) {
12
+ return (
13
+ <View>
14
+ {typeof label === "string" ? (
15
+ <Text className="mb-2 text-base font-instrument-sans-semibold text-foreground">
16
+ {label}
17
+ </Text>
18
+ ) : (
19
+ label
20
+ )}
21
+ {children}
22
+ {description != null ? (
23
+ typeof description === "string" ? (
24
+ <Text className="mt-1.5 text-sm font-instrument-sans text-muted-foreground">
25
+ {description}
26
+ </Text>
27
+ ) : (
28
+ description
29
+ )
30
+ ) : null}
31
+ </View>
32
+ );
33
+ }
@@ -0,0 +1,75 @@
1
+ import { View, Text } from "react-native";
2
+ import { cn } from "../cn";
3
+ import { FloatingSurface } from "../FloatingSurface";
4
+
5
+ type SettingsSectionBodyVariant = "form" | "list" | "compact";
6
+
7
+ export function SettingsSection({
8
+ title,
9
+ description,
10
+ action,
11
+ footer,
12
+ children,
13
+ bodyVariant = "form",
14
+ className,
15
+ }: {
16
+ title: React.ReactNode;
17
+ description?: React.ReactNode;
18
+ action?: React.ReactNode;
19
+ footer?: React.ReactNode;
20
+ children?: React.ReactNode;
21
+ bodyVariant?: SettingsSectionBodyVariant;
22
+ className?: string;
23
+ }) {
24
+ const hasBody = children != null;
25
+ const hasCard = hasBody || footer != null;
26
+
27
+ return (
28
+ <View className={cn("flex-col gap-2", className)}>
29
+ <View className="flex-row items-start justify-between gap-4 px-4">
30
+ <View className="min-w-0 flex-1">
31
+ {typeof title === "string" ? (
32
+ <Text className="text-lg font-instrument-sans-bold text-foreground">
33
+ {title}
34
+ </Text>
35
+ ) : (
36
+ title
37
+ )}
38
+ {description != null ? (
39
+ typeof description === "string" ? (
40
+ <Text className="mt-0.5 text-base font-instrument-sans text-muted-foreground">
41
+ {description}
42
+ </Text>
43
+ ) : (
44
+ description
45
+ )
46
+ ) : null}
47
+ </View>
48
+ {action != null ? <View className="shrink-0">{action}</View> : null}
49
+ </View>
50
+
51
+ {hasCard ? (
52
+ <FloatingSurface>
53
+ {hasBody ? (
54
+ <View
55
+ className={
56
+ bodyVariant === "list"
57
+ ? "flex-col"
58
+ : bodyVariant === "compact"
59
+ ? "px-4 py-3"
60
+ : "px-4 py-5"
61
+ }
62
+ >
63
+ {children}
64
+ </View>
65
+ ) : null}
66
+ {footer != null ? (
67
+ <View className="flex-row items-center justify-end gap-2 bg-background px-4 py-3">
68
+ {footer}
69
+ </View>
70
+ ) : null}
71
+ </FloatingSurface>
72
+ ) : null}
73
+ </View>
74
+ );
75
+ }
@@ -0,0 +1,5 @@
1
+ import { View } from "react-native";
2
+
3
+ export function SettingsStack({ children }: { children: React.ReactNode }) {
4
+ return <View className="flex-col gap-8 px-4 pt-2 pb-8">{children}</View>;
5
+ }
@@ -0,0 +1,58 @@
1
+ import { View, Text } from "react-native";
2
+ import { cn } from "../cn";
3
+ import { useLargerText } from "../theme";
4
+
5
+ export function SettingsToggleRow({
6
+ title,
7
+ description,
8
+ action,
9
+ className,
10
+ }: {
11
+ title: React.ReactNode;
12
+ description?: React.ReactNode;
13
+ action: React.ReactNode;
14
+ className?: string;
15
+ }) {
16
+ const largerText = useLargerText();
17
+ return (
18
+ <View
19
+ className={cn(
20
+ "flex-row items-center justify-between gap-4 px-4 py-3",
21
+ largerText ? "min-h-[72px]" : "min-h-16",
22
+ className,
23
+ )}
24
+ >
25
+ <View className="min-w-0 flex-1">
26
+ {typeof title === "string" ? (
27
+ <Text
28
+ className={cn(
29
+ "font-instrument-sans-semibold text-foreground",
30
+ largerText ? "text-xl" : "text-lg",
31
+ )}
32
+ >
33
+ {title}
34
+ </Text>
35
+ ) : (
36
+ title
37
+ )}
38
+ {description != null ? (
39
+ typeof description === "string" ? (
40
+ <Text
41
+ className={cn(
42
+ "mt-1 font-instrument-sans text-muted-foreground",
43
+ largerText ? "text-lg" : "text-base",
44
+ )}
45
+ >
46
+ {description}
47
+ </Text>
48
+ ) : (
49
+ description
50
+ )
51
+ ) : null}
52
+ </View>
53
+ <View className={cn("shrink-0 items-center justify-center", "min-h-12")}>
54
+ {action}
55
+ </View>
56
+ </View>
57
+ );
58
+ }
package/src/theme.tsx ADDED
@@ -0,0 +1,44 @@
1
+ import { createContext, useContext, type ReactNode } from "react";
2
+ import { tokens } from "@vvedantb/tokens/native";
3
+
4
+ export const DEFAULT_ACCENT = "#db2777";
5
+
6
+ export type NativeUiTheme = {
7
+ largerText: boolean;
8
+ accentColor: string;
9
+ };
10
+
11
+ const NativeUiContext = createContext<NativeUiTheme>({
12
+ largerText: false,
13
+ accentColor: DEFAULT_ACCENT,
14
+ });
15
+
16
+ export function NativeUiProvider({
17
+ largerText = false,
18
+ accentColor = DEFAULT_ACCENT,
19
+ children,
20
+ }: {
21
+ largerText?: boolean;
22
+ accentColor?: string;
23
+ children: ReactNode;
24
+ }) {
25
+ return (
26
+ <NativeUiContext.Provider value={{ largerText, accentColor }}>
27
+ {children}
28
+ </NativeUiContext.Provider>
29
+ );
30
+ }
31
+
32
+ export function useNativeUi() {
33
+ return useContext(NativeUiContext);
34
+ }
35
+
36
+ export function useLargerText() {
37
+ return useContext(NativeUiContext).largerText;
38
+ }
39
+
40
+ export function useAccentColor() {
41
+ return useContext(NativeUiContext).accentColor;
42
+ }
43
+
44
+ export { tokens };
@@ -0,0 +1,24 @@
1
+ import { useEffect, useState } from "react";
2
+ import { AccessibilityInfo, Platform } from "react-native";
3
+
4
+ export function useReduceTransparency() {
5
+ const [enabled, setEnabled] = useState(false);
6
+
7
+ useEffect(() => {
8
+ if (
9
+ Platform.OS !== "ios" ||
10
+ typeof AccessibilityInfo.isReduceTransparencyEnabled !== "function"
11
+ ) {
12
+ return;
13
+ }
14
+
15
+ void AccessibilityInfo.isReduceTransparencyEnabled().then(setEnabled);
16
+ const subscription = AccessibilityInfo.addEventListener(
17
+ "reduceTransparencyChanged",
18
+ setEnabled,
19
+ );
20
+ return () => subscription.remove();
21
+ }, []);
22
+
23
+ return enabled;
24
+ }