panelui-native 0.6.0 → 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/README.md +1 -1
- package/lib/module/components/accordion/index.js +7 -5
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/alert/index.js +3 -6
- package/lib/module/components/alert/index.js.map +1 -1
- package/lib/module/components/attachment/index.js +219 -0
- package/lib/module/components/attachment/index.js.map +1 -0
- package/lib/module/components/bottom-sheet/index.js +82 -4
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/button/index.js +36 -1
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/dialog/index.js +6 -2
- package/lib/module/components/dialog/index.js.map +1 -1
- package/lib/module/components/empty-state/index.js +98 -22
- package/lib/module/components/empty-state/index.js.map +1 -1
- package/lib/module/components/inline-select/index.js +171 -46
- package/lib/module/components/inline-select/index.js.map +1 -1
- package/lib/module/components/input/index.js +28 -5
- package/lib/module/components/input/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +0 -1
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +359 -0
- package/lib/module/components/item/index.js.map +1 -0
- package/lib/module/components/label/index.js +5 -3
- package/lib/module/components/label/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1024 -0
- package/lib/module/components/line-chart/index.js.map +1 -0
- package/lib/module/components/marker/index.js +190 -0
- package/lib/module/components/marker/index.js.map +1 -0
- package/lib/module/components/message/index.js +334 -0
- package/lib/module/components/message/index.js.map +1 -0
- package/lib/module/components/message-scroller/index.js +451 -0
- package/lib/module/components/message-scroller/index.js.map +1 -0
- package/lib/module/components/popover/index.js +500 -0
- package/lib/module/components/popover/index.js.map +1 -0
- package/lib/module/components/radio-group/index.js +104 -16
- package/lib/module/components/radio-group/index.js.map +1 -1
- package/lib/module/components/scroll-fade/index.js +119 -71
- package/lib/module/components/scroll-fade/index.js.map +1 -1
- package/lib/module/components/select/index.js +35 -6
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/separator/index.js +91 -0
- package/lib/module/components/separator/index.js.map +1 -0
- package/lib/module/components/shimmer/index.js +111 -64
- package/lib/module/components/shimmer/index.js.map +1 -1
- package/lib/module/components/surface/index.js +24 -4
- package/lib/module/components/surface/index.js.map +1 -1
- package/lib/module/components/switch/index.js +20 -1
- package/lib/module/components/switch/index.js.map +1 -1
- package/lib/module/components/toast/index.js +7 -8
- package/lib/module/components/toast/index.js.map +1 -1
- package/lib/module/components/typography/index.js +3 -3
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/use-breakpoint.js +2 -2
- package/lib/module/hooks/use-copy-to-clipboard.js +0 -2
- package/lib/module/hooks/use-copy-to-clipboard.js.map +1 -1
- package/lib/module/hooks/use-keyboard-avoidance.js +120 -0
- package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -0
- package/lib/module/icons/index.js +73 -4
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +13 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/index.js +53 -0
- package/lib/module/native/index.js.map +1 -0
- package/lib/module/primitives/keyboard-avoider.js +40 -0
- package/lib/module/primitives/keyboard-avoider.js.map +1 -0
- package/lib/module/primitives/scrim.js +72 -0
- package/lib/module/primitives/scrim.js.map +1 -0
- package/lib/module/providers/panel-ui-provider.js +24 -4
- package/lib/module/providers/panel-ui-provider.js.map +1 -1
- package/lib/module/theme/use-theme.js +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +7 -5
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/alert/index.d.ts +3 -6
- package/lib/typescript/src/components/alert/index.d.ts.map +1 -1
- package/lib/typescript/src/components/attachment/index.d.ts +93 -0
- package/lib/typescript/src/components/attachment/index.d.ts.map +1 -0
- package/lib/typescript/src/components/bottom-sheet/index.d.ts +27 -2
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +11 -0
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/dialog/index.d.ts +7 -1
- package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
- package/lib/typescript/src/components/empty-state/index.d.ts +156 -7
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -1
- package/lib/typescript/src/components/inline-select/index.d.ts +29 -6
- package/lib/typescript/src/components/inline-select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input/index.d.ts +13 -0
- package/lib/typescript/src/components/input/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts +0 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/item/index.d.ts +286 -0
- package/lib/typescript/src/components/item/index.d.ts.map +1 -0
- package/lib/typescript/src/components/label/index.d.ts +5 -3
- package/lib/typescript/src/components/label/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts +247 -0
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -0
- package/lib/typescript/src/components/marker/index.d.ts +137 -0
- package/lib/typescript/src/components/marker/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message/index.d.ts +177 -0
- package/lib/typescript/src/components/message/index.d.ts.map +1 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts +150 -0
- package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -0
- package/lib/typescript/src/components/popover/index.d.ts +130 -0
- package/lib/typescript/src/components/popover/index.d.ts.map +1 -0
- package/lib/typescript/src/components/radio-group/index.d.ts +32 -0
- package/lib/typescript/src/components/radio-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/scroll-fade/index.d.ts +18 -9
- package/lib/typescript/src/components/scroll-fade/index.d.ts.map +1 -1
- package/lib/typescript/src/components/select/index.d.ts +16 -1
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/separator/index.d.ts +47 -0
- package/lib/typescript/src/components/separator/index.d.ts.map +1 -0
- package/lib/typescript/src/components/shimmer/index.d.ts +38 -20
- package/lib/typescript/src/components/shimmer/index.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/index.d.ts +51 -2
- package/lib/typescript/src/components/surface/index.d.ts.map +1 -1
- package/lib/typescript/src/components/switch/index.d.ts +10 -0
- package/lib/typescript/src/components/switch/index.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/index.d.ts +4 -4
- package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/index.d.ts +1 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-breakpoint.d.ts +2 -2
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts +0 -2
- package/lib/typescript/src/hooks/use-copy-to-clipboard.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +20 -0
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +10 -4
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/index.d.ts +35 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts +22 -0
- package/lib/typescript/src/primitives/keyboard-avoider.d.ts.map +1 -0
- package/lib/typescript/src/primitives/scrim.d.ts +21 -0
- package/lib/typescript/src/primitives/scrim.d.ts.map +1 -0
- package/lib/typescript/src/providers/panel-ui-provider.d.ts +1 -1
- package/lib/typescript/src/providers/panel-ui-provider.d.ts.map +1 -1
- package/lib/typescript/src/theme/use-theme.d.ts +1 -1
- package/package.json +16 -3
- package/src/components/accordion/index.tsx +7 -5
- package/src/components/alert/index.tsx +3 -6
- package/src/components/attachment/index.tsx +279 -0
- package/src/components/bottom-sheet/index.tsx +102 -2
- package/src/components/button/index.tsx +52 -0
- package/src/components/dialog/index.tsx +10 -1
- package/src/components/empty-state/index.tsx +64 -19
- package/src/components/inline-select/index.tsx +191 -44
- package/src/components/input/index.tsx +39 -3
- package/src/components/input-group/index.tsx +0 -1
- package/src/components/item/index.tsx +387 -0
- package/src/components/label/index.tsx +5 -3
- package/src/components/line-chart/index.tsx +1224 -0
- package/src/components/marker/index.tsx +194 -0
- package/src/components/message/index.tsx +336 -0
- package/src/components/message-scroller/index.tsx +574 -0
- package/src/components/popover/index.tsx +599 -0
- package/src/components/radio-group/index.tsx +108 -17
- package/src/components/scroll-fade/index.tsx +163 -51
- package/src/components/select/index.tsx +54 -9
- package/src/components/separator/index.tsx +97 -0
- package/src/components/shimmer/index.tsx +151 -58
- package/src/components/surface/index.tsx +32 -5
- package/src/components/switch/index.tsx +27 -1
- package/src/components/toast/index.tsx +7 -8
- package/src/components/typography/index.tsx +3 -3
- package/src/hooks/index.ts +5 -0
- package/src/hooks/use-breakpoint.ts +2 -2
- package/src/hooks/use-copy-to-clipboard.ts +0 -2
- package/src/hooks/use-keyboard-avoidance.ts +140 -0
- package/src/icons/index.tsx +52 -4
- package/src/index.ts +92 -0
- package/src/native/index.ts +79 -0
- package/src/primitives/keyboard-avoider.tsx +49 -0
- package/src/primitives/scrim.tsx +95 -0
- package/src/providers/panel-ui-provider.tsx +30 -9
- package/src/theme/use-theme.ts +1 -1
- package/theme.css +63 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ViewProps } from 'react-native';
|
|
2
|
+
/** True when a real blur can be drawn — for a caller that wants to know. */
|
|
3
|
+
export declare const hasBlur: boolean;
|
|
4
|
+
export interface ScrimProps extends Omit<ViewProps, 'children'> {
|
|
5
|
+
/** Frost the backdrop instead of dimming it. Falls back to a dim if it can't. */
|
|
6
|
+
blur?: boolean;
|
|
7
|
+
/** Blur strength, 0–100. */
|
|
8
|
+
intensity?: number;
|
|
9
|
+
/** Which way the blur tints. */
|
|
10
|
+
tint?: 'light' | 'dark' | 'default' | 'systemMaterial';
|
|
11
|
+
/**
|
|
12
|
+
* The dim used when not blurring — and when blurring is asked for but
|
|
13
|
+
* unavailable. A popover passes a lighter one than a dialog.
|
|
14
|
+
*/
|
|
15
|
+
dimClassName?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function Scrim({ blur, intensity, tint, dimClassName, style, ...props }: ScrimProps): import("react").JSX.Element;
|
|
18
|
+
export declare namespace Scrim {
|
|
19
|
+
var displayName: string;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=scrim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrim.d.ts","sourceRoot":"","sources":["../../../../src/primitives/scrim.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAyBhE,4EAA4E;AAC5E,eAAO,MAAM,OAAO,SAAoB,CAAC;AAEzC,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC;IAC7D,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAAC;IACvD;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,KAAK,CAAC,EACpB,IAAY,EACZ,SAAc,EACd,IAAgB,EAChB,YAA4B,EAC5B,KAAK,EACL,GAAG,KAAK,EACT,EAAE,UAAU,+BA0BZ;yBAjCe,KAAK"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel-ui-provider.d.ts","sourceRoot":"","sources":["../../../../src/providers/panel-ui-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"panel-ui-provider.d.ts","sourceRoot":"","sources":["../../../../src/providers/panel-ui-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAwBrE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,SAAS,EACT,UAAiB,GAClB,EAAE,oBAAoB,+BAiBtB"}
|
|
@@ -17,7 +17,7 @@ export interface PanelThemeFamily {
|
|
|
17
17
|
/**
|
|
18
18
|
* The theme families PanelUI ships. Each sets its own palette *and* its own
|
|
19
19
|
* radius scale, so switching family changes the shape of the UI as well as
|
|
20
|
-
* its colour — Panel is the
|
|
20
|
+
* its colour — Panel is the default, Moon is sharp and monochrome,
|
|
21
21
|
* Grass is soft and green.
|
|
22
22
|
*
|
|
23
23
|
* Everything beyond `panel` must be registered in your metro config, or
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panelui-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "High-performance React Native UI components for Expo.
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "High-performance React Native UI components for Expo. Semantic design tokens, powered by Uniwind (Tailwind v4) and Reanimated.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|
|
7
7
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
"nativewind",
|
|
44
44
|
"reanimated",
|
|
45
45
|
"design-system",
|
|
46
|
-
"shadcn",
|
|
47
46
|
"dark-mode",
|
|
48
47
|
"theming",
|
|
49
48
|
"accessibility",
|
|
@@ -64,10 +63,15 @@
|
|
|
64
63
|
"license": "MIT",
|
|
65
64
|
"homepage": "https://github.com/panel-ui/PanelUI#readme",
|
|
66
65
|
"peerDependencies": {
|
|
66
|
+
"@expo/ui": "*",
|
|
67
|
+
"@react-native-masked-view/masked-view": ">=0.3.0",
|
|
68
|
+
"expo-blur": ">=13.0.0",
|
|
67
69
|
"expo-clipboard": ">=7.0.0",
|
|
70
|
+
"expo-linear-gradient": ">=13.0.0",
|
|
68
71
|
"react": "*",
|
|
69
72
|
"react-native": "*",
|
|
70
73
|
"react-native-gesture-handler": ">=2.0.0",
|
|
74
|
+
"react-native-keyboard-controller": ">=1.12.0",
|
|
71
75
|
"react-native-reanimated": ">=3.0.0",
|
|
72
76
|
"react-native-safe-area-context": ">=4.0.0",
|
|
73
77
|
"react-native-svg": ">=15.0.0",
|
|
@@ -96,8 +100,17 @@
|
|
|
96
100
|
]
|
|
97
101
|
},
|
|
98
102
|
"peerDependenciesMeta": {
|
|
103
|
+
"@expo/ui": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"expo-blur": {
|
|
107
|
+
"optional": true
|
|
108
|
+
},
|
|
99
109
|
"expo-clipboard": {
|
|
100
110
|
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"react-native-keyboard-controller": {
|
|
113
|
+
"optional": true
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Accordion — collapsible sections.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Split into Item / Trigger / Indicator / Content so a section header can hold
|
|
5
|
+
* anything — a badge, a price, a second line — instead of only a string. The
|
|
6
|
+
* chevron rotates rather than swapping glyphs, and the panel expands through a
|
|
7
|
+
* layout transition, both on the UI thread.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* `selectionMode` decides whether opening one section closes the others, which
|
|
10
|
+
* also changes the shape of `value`: a string when single, an array when
|
|
11
|
+
* multiple.
|
|
10
12
|
*/
|
|
11
13
|
import {
|
|
12
14
|
Children,
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Alert — a status message with an optional leading status icon.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The `variant` prop name is kept from PanelUI v0.2 rather than renamed to
|
|
9
|
-
* HeroUI's `status`, so existing code keeps working.
|
|
4
|
+
* The indicator derives its icon and colour from the variant, so a success
|
|
5
|
+
* alert cannot end up with a warning icon. Pass `icon` on the root to override
|
|
6
|
+
* it everywhere, or give `Alert.Indicator` children to override one.
|
|
10
7
|
*/
|
|
11
8
|
import { createContext, forwardRef, useContext, type ReactNode } from 'react';
|
|
12
9
|
import { View, type Text as RNText, type ViewProps } from 'react-native';
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment — a file, with its upload life.
|
|
3
|
+
*
|
|
4
|
+
* A file in a chat composer or an upload list is the same row shape `Item`
|
|
5
|
+
* already draws — media, a name, a size, a remove button — so this is that row,
|
|
6
|
+
* not a second one. What it adds is the part `Item` has no opinion on: the
|
|
7
|
+
* file's *state*. A file is picked, then uploading, then processing, then done
|
|
8
|
+
* (or failed), and each of those has to look different or the user cannot tell
|
|
9
|
+
* a stuck upload from a finished one.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Attachment state="uploading">
|
|
13
|
+
* <Attachment.Media><FileIcon /></Attachment.Media>
|
|
14
|
+
* <Attachment.Content>
|
|
15
|
+
* <Attachment.Title>report.pdf</Attachment.Title>
|
|
16
|
+
* <Attachment.Description>PDF · 2.4 MB</Attachment.Description>
|
|
17
|
+
* </Attachment.Content>
|
|
18
|
+
* <Attachment.Actions>
|
|
19
|
+
* <Attachment.Action aria-label="Cancel"><XIcon size={14} /></Attachment.Action>
|
|
20
|
+
* </Attachment.Actions>
|
|
21
|
+
* </Attachment>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* While uploading or processing the title shimmers — the same sweep the rest of
|
|
25
|
+
* the library uses for "in progress" — and an optional bar tracks `progress`.
|
|
26
|
+
* A failed file tints its name and description in the destructive colour, which
|
|
27
|
+
* carries the state without relying on an icon nobody reads.
|
|
28
|
+
*/
|
|
29
|
+
import { createContext, forwardRef, useContext, type ReactNode } from 'react';
|
|
30
|
+
import { View, type ViewProps } from 'react-native';
|
|
31
|
+
import { tv } from 'tailwind-variants';
|
|
32
|
+
import { useCSSVariable } from 'uniwind';
|
|
33
|
+
import { AnimatedPressable, type AnimatedPressableProps } from '../../primitives/animated-pressable';
|
|
34
|
+
import { Text, type TextProps } from '../../primitives/text';
|
|
35
|
+
import { cn } from '../../utils/cn';
|
|
36
|
+
import { Item } from '../item';
|
|
37
|
+
import { Shimmer } from '../shimmer';
|
|
38
|
+
|
|
39
|
+
export type AttachmentState = 'idle' | 'uploading' | 'processing' | 'error' | 'done';
|
|
40
|
+
type AttachmentSize = 'default' | 'sm' | 'xs';
|
|
41
|
+
|
|
42
|
+
interface AttachmentContextValue {
|
|
43
|
+
state: AttachmentState;
|
|
44
|
+
size: AttachmentSize;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const AttachmentContext = createContext<AttachmentContextValue>({
|
|
48
|
+
state: 'done',
|
|
49
|
+
size: 'default',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/** True while the file is still moving — the two states that shimmer. */
|
|
53
|
+
function isBusy(state: AttachmentState): boolean {
|
|
54
|
+
return state === 'uploading' || state === 'processing';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rootVariants = tv({
|
|
58
|
+
base: '',
|
|
59
|
+
variants: {
|
|
60
|
+
state: {
|
|
61
|
+
idle: '',
|
|
62
|
+
uploading: '',
|
|
63
|
+
processing: '',
|
|
64
|
+
// The border carries the failure, so it reads at a glance in a list.
|
|
65
|
+
error: 'border border-destructive',
|
|
66
|
+
done: '',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export interface AttachmentProps
|
|
72
|
+
extends Omit<AnimatedPressableProps, 'children' | 'disabled'> {
|
|
73
|
+
className?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Where the file is in its life. `uploading`/`processing` shimmer the name;
|
|
76
|
+
* `error` tints the row destructive. Default `done`.
|
|
77
|
+
*/
|
|
78
|
+
state?: AttachmentState;
|
|
79
|
+
/** Row density, passed through to the underlying Item. */
|
|
80
|
+
size?: AttachmentSize;
|
|
81
|
+
/** Stack the row into a card — for a horizontal group of thumbnails. */
|
|
82
|
+
orientation?: 'horizontal' | 'vertical';
|
|
83
|
+
/** Upload progress 0–1. Draws a thin bar along the bottom while busy. */
|
|
84
|
+
progress?: number;
|
|
85
|
+
disabled?: boolean;
|
|
86
|
+
children?: ReactNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const AttachmentRoot = forwardRef<View, AttachmentProps>(
|
|
90
|
+
(
|
|
91
|
+
{ className, state = 'done', size = 'default', orientation, progress, children, ...props },
|
|
92
|
+
ref
|
|
93
|
+
) => {
|
|
94
|
+
const track = useCSSVariable('--color-primary');
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<AttachmentContext.Provider value={{ state, size }}>
|
|
98
|
+
<Item
|
|
99
|
+
ref={ref}
|
|
100
|
+
size={size}
|
|
101
|
+
orientation={orientation}
|
|
102
|
+
variant="outline"
|
|
103
|
+
className={cn('overflow-hidden', rootVariants({ state }), className)}
|
|
104
|
+
{...props}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
|
|
108
|
+
{/* A determinate bar while busy, if progress is known. Absolute so it
|
|
109
|
+
rides the row's bottom edge without shifting the layout. */}
|
|
110
|
+
{isBusy(state) && progress !== undefined ? (
|
|
111
|
+
<View className="absolute bottom-0 left-0 right-0 h-0.5 bg-muted">
|
|
112
|
+
<View
|
|
113
|
+
style={{
|
|
114
|
+
width: `${Math.min(100, Math.max(0, progress * 100))}%`,
|
|
115
|
+
backgroundColor: typeof track === 'string' ? track : undefined,
|
|
116
|
+
}}
|
|
117
|
+
className="h-full"
|
|
118
|
+
/>
|
|
119
|
+
</View>
|
|
120
|
+
) : null}
|
|
121
|
+
</Item>
|
|
122
|
+
</AttachmentContext.Provider>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
AttachmentRoot.displayName = 'Attachment';
|
|
127
|
+
|
|
128
|
+
export interface AttachmentGroupProps extends ViewProps {
|
|
129
|
+
className?: string;
|
|
130
|
+
orientation?: 'horizontal' | 'vertical';
|
|
131
|
+
children?: ReactNode;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A row (or column) of attachments — the same stack `Item.Group` provides. */
|
|
135
|
+
const AttachmentGroup = forwardRef<View, AttachmentGroupProps>(
|
|
136
|
+
({ className, orientation = 'vertical', children, ...props }, ref) => (
|
|
137
|
+
<Item.Group ref={ref} orientation={orientation} className={className} {...props}>
|
|
138
|
+
{children}
|
|
139
|
+
</Item.Group>
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
AttachmentGroup.displayName = 'Attachment.Group';
|
|
143
|
+
|
|
144
|
+
export interface AttachmentMediaProps extends ViewProps {
|
|
145
|
+
className?: string;
|
|
146
|
+
/** `icon` frames it in a tile; `image` clips it for a thumbnail. */
|
|
147
|
+
variant?: 'default' | 'icon' | 'image';
|
|
148
|
+
children?: ReactNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Leading slot — a file-type icon tile, or an image thumbnail. */
|
|
152
|
+
const AttachmentMedia = forwardRef<View, AttachmentMediaProps>(
|
|
153
|
+
({ className, variant = 'icon', children, ...props }, ref) => (
|
|
154
|
+
<Item.Media ref={ref} variant={variant} className={className} {...props}>
|
|
155
|
+
{children}
|
|
156
|
+
</Item.Media>
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
AttachmentMedia.displayName = 'Attachment.Media';
|
|
160
|
+
|
|
161
|
+
export interface AttachmentContentProps extends ViewProps {
|
|
162
|
+
className?: string;
|
|
163
|
+
children?: ReactNode;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const AttachmentContent = forwardRef<View, AttachmentContentProps>(
|
|
167
|
+
({ className, children, ...props }, ref) => (
|
|
168
|
+
<Item.Content ref={ref} className={className} {...props}>
|
|
169
|
+
{children}
|
|
170
|
+
</Item.Content>
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
AttachmentContent.displayName = 'Attachment.Content';
|
|
174
|
+
|
|
175
|
+
export interface AttachmentTitleProps extends TextProps {
|
|
176
|
+
className?: string;
|
|
177
|
+
children?: ReactNode;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** The file name. Shimmers while the file is uploading or processing. */
|
|
181
|
+
const AttachmentTitle = forwardRef<React.ElementRef<typeof Text>, AttachmentTitleProps>(
|
|
182
|
+
({ className, children, ...props }, ref) => {
|
|
183
|
+
const { state } = useContext(AttachmentContext);
|
|
184
|
+
|
|
185
|
+
if (isBusy(state)) {
|
|
186
|
+
// Shimmer owns the text so it can mask the sweep to the glyphs.
|
|
187
|
+
return <Shimmer textClassName={cn('text-base font-medium', className)}>{children}</Shimmer>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<Item.Title
|
|
192
|
+
ref={ref}
|
|
193
|
+
className={cn(state === 'error' && 'text-destructive', className)}
|
|
194
|
+
{...props}
|
|
195
|
+
>
|
|
196
|
+
{children}
|
|
197
|
+
</Item.Title>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
AttachmentTitle.displayName = 'Attachment.Title';
|
|
202
|
+
|
|
203
|
+
export interface AttachmentDescriptionProps extends TextProps {
|
|
204
|
+
className?: string;
|
|
205
|
+
children?: ReactNode;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** File type, size or status. Tints destructive on `error`. */
|
|
209
|
+
const AttachmentDescription = forwardRef<
|
|
210
|
+
React.ElementRef<typeof Text>,
|
|
211
|
+
AttachmentDescriptionProps
|
|
212
|
+
>(({ className, children, ...props }, ref) => {
|
|
213
|
+
const { state } = useContext(AttachmentContext);
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<Item.Description
|
|
217
|
+
ref={ref}
|
|
218
|
+
className={cn(state === 'error' && 'text-destructive', className)}
|
|
219
|
+
{...props}
|
|
220
|
+
>
|
|
221
|
+
{children}
|
|
222
|
+
</Item.Description>
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
AttachmentDescription.displayName = 'Attachment.Description';
|
|
226
|
+
|
|
227
|
+
export interface AttachmentActionsProps extends ViewProps {
|
|
228
|
+
className?: string;
|
|
229
|
+
children?: ReactNode;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const AttachmentActions = forwardRef<View, AttachmentActionsProps>(
|
|
233
|
+
({ className, children, ...props }, ref) => (
|
|
234
|
+
<Item.Actions ref={ref} className={className} {...props}>
|
|
235
|
+
{children}
|
|
236
|
+
</Item.Actions>
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
AttachmentActions.displayName = 'Attachment.Actions';
|
|
240
|
+
|
|
241
|
+
export interface AttachmentActionProps
|
|
242
|
+
extends Omit<AnimatedPressableProps, 'children'> {
|
|
243
|
+
className?: string;
|
|
244
|
+
accessibilityLabel: string;
|
|
245
|
+
children?: ReactNode;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A single icon button in the actions slot — remove, retry, download. An icon
|
|
250
|
+
* on its own says nothing to a screen reader, so the label is required.
|
|
251
|
+
*/
|
|
252
|
+
const AttachmentAction = forwardRef<View, AttachmentActionProps>(
|
|
253
|
+
({ className, accessibilityLabel, children, ...props }, ref) => (
|
|
254
|
+
<AnimatedPressable
|
|
255
|
+
ref={ref}
|
|
256
|
+
accessibilityRole="button"
|
|
257
|
+
accessibilityLabel={accessibilityLabel}
|
|
258
|
+
hitSlop={8}
|
|
259
|
+
className={cn(
|
|
260
|
+
'h-8 w-8 items-center justify-center rounded-full active:bg-muted',
|
|
261
|
+
className
|
|
262
|
+
)}
|
|
263
|
+
{...props}
|
|
264
|
+
>
|
|
265
|
+
{children}
|
|
266
|
+
</AnimatedPressable>
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
AttachmentAction.displayName = 'Attachment.Action';
|
|
270
|
+
|
|
271
|
+
export const Attachment = Object.assign(AttachmentRoot, {
|
|
272
|
+
Group: AttachmentGroup,
|
|
273
|
+
Media: AttachmentMedia,
|
|
274
|
+
Content: AttachmentContent,
|
|
275
|
+
Title: AttachmentTitle,
|
|
276
|
+
Description: AttachmentDescription,
|
|
277
|
+
Actions: AttachmentActions,
|
|
278
|
+
Action: AttachmentAction,
|
|
279
|
+
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ReactNode,
|
|
11
11
|
} from 'react';
|
|
12
12
|
import { Pressable, useWindowDimensions, View, type ViewProps } from 'react-native';
|
|
13
|
+
import { useCSSVariable } from 'uniwind';
|
|
13
14
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
14
15
|
import Animated, {
|
|
15
16
|
FadeIn,
|
|
@@ -23,6 +24,8 @@ import Animated, {
|
|
|
23
24
|
withTiming,
|
|
24
25
|
} from 'react-native-reanimated';
|
|
25
26
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
27
|
+
import { getNativeUI } from '../../native';
|
|
28
|
+
import { XIcon } from '../../icons';
|
|
26
29
|
import { Portal } from '../../primitives/portal';
|
|
27
30
|
import { cn } from '../../utils/cn';
|
|
28
31
|
|
|
@@ -50,13 +53,40 @@ export interface BottomSheetProps {
|
|
|
50
53
|
open?: boolean;
|
|
51
54
|
onOpenChange?: (open: boolean) => void;
|
|
52
55
|
defaultOpen?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Present the platform's own sheet instead of this one, so it gets the
|
|
58
|
+
* system's detents, scroll interaction and dismiss gesture. Requires the
|
|
59
|
+
* optional `@expo/ui` package; without it this prop does nothing.
|
|
60
|
+
*
|
|
61
|
+
* **Theme tokens do not apply to the sheet chrome** — the platform draws the
|
|
62
|
+
* container, so `BottomSheet.Content`'s `className` and its drag handle are
|
|
63
|
+
* ignored. The content inside is still yours.
|
|
64
|
+
*/
|
|
65
|
+
native?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Heights the native sheet can rest at. Omit to size to the content.
|
|
68
|
+
* `{ fraction }` and `{ height }` are iOS-only; Android snaps them to the
|
|
69
|
+
* nearest of `half` / `full`.
|
|
70
|
+
*/
|
|
71
|
+
snapPoints?: ('half' | 'full' | { fraction: number } | { height: number })[];
|
|
53
72
|
}
|
|
54
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Set by the root so Content knows the platform is drawing the sheet, and with
|
|
76
|
+
* which detents. Null means the styled sheet renders.
|
|
77
|
+
*/
|
|
78
|
+
const NativeSheetContext = createContext<{
|
|
79
|
+
nativeUI: NonNullable<ReturnType<typeof getNativeUI>>;
|
|
80
|
+
snapPoints: BottomSheetProps['snapPoints'];
|
|
81
|
+
} | null>(null);
|
|
82
|
+
|
|
55
83
|
function BottomSheetRoot({
|
|
56
84
|
children,
|
|
57
85
|
open,
|
|
58
86
|
onOpenChange,
|
|
59
87
|
defaultOpen = false,
|
|
88
|
+
native,
|
|
89
|
+
snapPoints,
|
|
60
90
|
}: BottomSheetProps) {
|
|
61
91
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
62
92
|
const isControlled = open !== undefined;
|
|
@@ -75,9 +105,17 @@ function BottomSheetRoot({
|
|
|
75
105
|
[resolvedOpen, setOpen]
|
|
76
106
|
);
|
|
77
107
|
|
|
108
|
+
const nativeUI = native ? getNativeUI() : null;
|
|
109
|
+
const nativeSheet = useMemo(
|
|
110
|
+
() => (nativeUI ? { nativeUI, snapPoints } : null),
|
|
111
|
+
[nativeUI, snapPoints]
|
|
112
|
+
);
|
|
113
|
+
|
|
78
114
|
return (
|
|
79
115
|
<BottomSheetContext.Provider value={context}>
|
|
80
|
-
{
|
|
116
|
+
<NativeSheetContext.Provider value={nativeSheet}>
|
|
117
|
+
{children}
|
|
118
|
+
</NativeSheetContext.Provider>
|
|
81
119
|
</BottomSheetContext.Provider>
|
|
82
120
|
);
|
|
83
121
|
}
|
|
@@ -102,20 +140,28 @@ export interface BottomSheetContentProps extends ViewProps {
|
|
|
102
140
|
className?: string;
|
|
103
141
|
/** Tap on the backdrop closes the sheet. Default true. */
|
|
104
142
|
dismissible?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Show a close button in the top-right corner. On by default for the styled
|
|
145
|
+
* sheet; ignored by the native sheet, which has its own dismiss affordances.
|
|
146
|
+
*/
|
|
147
|
+
showClose?: boolean;
|
|
105
148
|
children?: ReactNode;
|
|
106
149
|
}
|
|
107
150
|
|
|
108
151
|
function BottomSheetContent({
|
|
109
152
|
className,
|
|
110
153
|
dismissible = true,
|
|
154
|
+
showClose = true,
|
|
111
155
|
children,
|
|
112
156
|
...props
|
|
113
157
|
}: BottomSheetContentProps) {
|
|
114
158
|
const context = useBottomSheet('BottomSheet.Content');
|
|
115
159
|
const { open, setOpen } = context;
|
|
116
|
-
const
|
|
160
|
+
const nativeSheet = useContext(NativeSheetContext);
|
|
161
|
+
const { height: screenHeight, width: screenWidth } = useWindowDimensions();
|
|
117
162
|
const insets = useSafeAreaInsets();
|
|
118
163
|
const translateY = useSharedValue(0);
|
|
164
|
+
const closeTint = useCSSVariable('--color-muted-foreground');
|
|
119
165
|
|
|
120
166
|
const close = useCallback(() => setOpen(false), [setOpen]);
|
|
121
167
|
|
|
@@ -142,6 +188,49 @@ function BottomSheetContent({
|
|
|
142
188
|
transform: [{ translateY: translateY.value }],
|
|
143
189
|
}));
|
|
144
190
|
|
|
191
|
+
if (nativeSheet) {
|
|
192
|
+
const { Host, BottomSheet: NativeBottomSheet, RNHostView } = nativeSheet.nativeUI;
|
|
193
|
+
// The platform owns presentation, so this stays mounted and toggles
|
|
194
|
+
// isPresented rather than unmounting on close.
|
|
195
|
+
//
|
|
196
|
+
// RNHostView is not optional: our content is React Native, and the native
|
|
197
|
+
// sheet cannot measure RN views directly. Without it the sheet sizes to
|
|
198
|
+
// nothing and the content spills outside its container.
|
|
199
|
+
return (
|
|
200
|
+
<Host matchContents style={{ position: 'absolute' }}>
|
|
201
|
+
<NativeBottomSheet
|
|
202
|
+
isPresented={open}
|
|
203
|
+
onDismiss={dismissible ? close : () => {}}
|
|
204
|
+
snapPoints={nativeSheet.snapPoints}
|
|
205
|
+
>
|
|
206
|
+
<RNHostView matchContents>
|
|
207
|
+
<BottomSheetContext.Provider value={context}>
|
|
208
|
+
<View
|
|
209
|
+
{...props}
|
|
210
|
+
className={cn(
|
|
211
|
+
// The platform draws the container, but it hands us a bare
|
|
212
|
+
// box — padding and safe-area are still ours.
|
|
213
|
+
'gap-2 px-5 pb-2 pt-1',
|
|
214
|
+
className
|
|
215
|
+
)}
|
|
216
|
+
style={{
|
|
217
|
+
// An explicit width, not `w-full`. Inside the native host
|
|
218
|
+
// there is no parent width for a percentage to resolve
|
|
219
|
+
// against, so `100%` measures against nothing and the
|
|
220
|
+
// content lays out wider than the sheet it sits in.
|
|
221
|
+
width: screenWidth,
|
|
222
|
+
paddingBottom: Math.max(insets.bottom, 16),
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
{children}
|
|
226
|
+
</View>
|
|
227
|
+
</BottomSheetContext.Provider>
|
|
228
|
+
</RNHostView>
|
|
229
|
+
</NativeBottomSheet>
|
|
230
|
+
</Host>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
145
234
|
if (!open) return null;
|
|
146
235
|
|
|
147
236
|
return (
|
|
@@ -176,6 +265,17 @@ function BottomSheetContent({
|
|
|
176
265
|
<View className="mb-3 self-center">
|
|
177
266
|
<View className="h-1 w-10 rounded-full bg-muted-foreground/30" />
|
|
178
267
|
</View>
|
|
268
|
+
{showClose ? (
|
|
269
|
+
<Pressable
|
|
270
|
+
accessibilityRole="button"
|
|
271
|
+
accessibilityLabel="Close"
|
|
272
|
+
onPress={close}
|
|
273
|
+
hitSlop={8}
|
|
274
|
+
className="absolute right-4 top-3 h-8 w-8 items-center justify-center rounded-full bg-muted active:opacity-70"
|
|
275
|
+
>
|
|
276
|
+
<XIcon size={16} color={typeof closeTint === 'string' ? closeTint : undefined} />
|
|
277
|
+
</Pressable>
|
|
278
|
+
) : null}
|
|
179
279
|
{children}
|
|
180
280
|
</Animated.View>
|
|
181
281
|
</GestureDetector>
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from '../../primitives/animated-pressable';
|
|
9
9
|
import { Text } from '../../primitives/text';
|
|
10
10
|
import { IconColorProvider } from '../../icons';
|
|
11
|
+
import { getNativeUI } from '../../native';
|
|
11
12
|
import { Spinner } from '../spinner';
|
|
12
13
|
|
|
13
14
|
const buttonVariants = tv({
|
|
@@ -104,8 +105,32 @@ export interface ButtonProps
|
|
|
104
105
|
endContent?: ReactNode;
|
|
105
106
|
/** Extra classes for the label when children is a string. */
|
|
106
107
|
labelClassName?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Render the platform's own button instead of this one. Requires the
|
|
110
|
+
* optional `@expo/ui` package; without it this prop does nothing.
|
|
111
|
+
*
|
|
112
|
+
* **Theme tokens do not apply** — the platform draws the button, so
|
|
113
|
+
* `className`, `size`, `fullWidth`, `startContent`, `endContent` and
|
|
114
|
+
* `loading` are all ignored. `variant` maps onto the nearest platform
|
|
115
|
+
* style: `primary`/`destructive` → filled, `outline` → outlined, everything
|
|
116
|
+
* else → text.
|
|
117
|
+
*/
|
|
118
|
+
native?: boolean;
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
/** PanelUI variants mapped onto the platform button styles. */
|
|
122
|
+
const NATIVE_VARIANT: Record<
|
|
123
|
+
NonNullable<ButtonVariantProps['variant']>,
|
|
124
|
+
'filled' | 'outlined' | 'text'
|
|
125
|
+
> = {
|
|
126
|
+
primary: 'filled',
|
|
127
|
+
destructive: 'filled',
|
|
128
|
+
secondary: 'filled',
|
|
129
|
+
outline: 'outlined',
|
|
130
|
+
ghost: 'text',
|
|
131
|
+
social: 'outlined',
|
|
132
|
+
};
|
|
133
|
+
|
|
109
134
|
export const Button = forwardRef<View, ButtonProps>(
|
|
110
135
|
(
|
|
111
136
|
{
|
|
@@ -119,11 +144,13 @@ export const Button = forwardRef<View, ButtonProps>(
|
|
|
119
144
|
loading = false,
|
|
120
145
|
startContent,
|
|
121
146
|
endContent,
|
|
147
|
+
native,
|
|
122
148
|
...props
|
|
123
149
|
},
|
|
124
150
|
ref
|
|
125
151
|
) => {
|
|
126
152
|
const isDisabled = disabled || loading;
|
|
153
|
+
const nativeUI = native ? getNativeUI() : null;
|
|
127
154
|
const { root, label, spinner } = buttonVariants({
|
|
128
155
|
variant,
|
|
129
156
|
size,
|
|
@@ -146,6 +173,31 @@ export const Button = forwardRef<View, ButtonProps>(
|
|
|
146
173
|
? themedColor
|
|
147
174
|
: undefined;
|
|
148
175
|
|
|
176
|
+
if (nativeUI) {
|
|
177
|
+
const { Host, Button: NativeButton, RNHostView } = nativeUI;
|
|
178
|
+
const isStringLabel = typeof children === 'string';
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<Host matchContents>
|
|
182
|
+
<NativeButton
|
|
183
|
+
label={isStringLabel ? children : undefined}
|
|
184
|
+
variant={NATIVE_VARIANT[variant ?? 'primary']}
|
|
185
|
+
disabled={isDisabled}
|
|
186
|
+
onPress={props.onPress}
|
|
187
|
+
>
|
|
188
|
+
{/* Non-string children are React Native views, and the native
|
|
189
|
+
button cannot measure those directly — they have to be hosted
|
|
190
|
+
or they render outside the button's bounds. */}
|
|
191
|
+
{isStringLabel ? undefined : (
|
|
192
|
+
<RNHostView matchContents>
|
|
193
|
+
<>{children}</>
|
|
194
|
+
</RNHostView>
|
|
195
|
+
)}
|
|
196
|
+
</NativeButton>
|
|
197
|
+
</Host>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
149
201
|
return (
|
|
150
202
|
<IconColorProvider color={contentColor}>
|
|
151
203
|
<AnimatedPressable
|