@mrmeg/expo-ui 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.
- package/README.md +96 -0
- package/dist/components/Accordion.d.ts +54 -0
- package/dist/components/Accordion.js +149 -0
- package/dist/components/Alert.d.ts +30 -0
- package/dist/components/Alert.js +25 -0
- package/dist/components/AnimatedView.d.ts +55 -0
- package/dist/components/AnimatedView.js +39 -0
- package/dist/components/Badge.d.ts +23 -0
- package/dist/components/Badge.js +74 -0
- package/dist/components/BottomSheet.d.ts +74 -0
- package/dist/components/BottomSheet.js +513 -0
- package/dist/components/Button.d.ts +129 -0
- package/dist/components/Button.js +216 -0
- package/dist/components/Card.d.ts +42 -0
- package/dist/components/Card.js +126 -0
- package/dist/components/Checkbox.d.ts +39 -0
- package/dist/components/Checkbox.js +96 -0
- package/dist/components/Collapsible.d.ts +67 -0
- package/dist/components/Collapsible.js +38 -0
- package/dist/components/Dialog.d.ts +140 -0
- package/dist/components/Dialog.js +167 -0
- package/dist/components/DismissKeyboard.d.ts +15 -0
- package/dist/components/DismissKeyboard.js +13 -0
- package/dist/components/Drawer.d.ts +74 -0
- package/dist/components/Drawer.js +423 -0
- package/dist/components/DropdownMenu.d.ts +120 -0
- package/dist/components/DropdownMenu.js +211 -0
- package/dist/components/EmptyState.d.ts +42 -0
- package/dist/components/EmptyState.js +58 -0
- package/dist/components/ErrorBoundary.d.ts +53 -0
- package/dist/components/ErrorBoundary.js +75 -0
- package/dist/components/Icon.d.ts +46 -0
- package/dist/components/Icon.js +40 -0
- package/dist/components/InputOTP.d.ts +72 -0
- package/dist/components/InputOTP.js +155 -0
- package/dist/components/Label.d.ts +61 -0
- package/dist/components/Label.js +72 -0
- package/dist/components/MaxWidthContainer.d.ts +58 -0
- package/dist/components/MaxWidthContainer.js +64 -0
- package/dist/components/Notification.d.ts +26 -0
- package/dist/components/Notification.js +230 -0
- package/dist/components/Popover.d.ts +79 -0
- package/dist/components/Popover.js +91 -0
- package/dist/components/Progress.d.ts +28 -0
- package/dist/components/Progress.js +107 -0
- package/dist/components/RadioGroup.d.ts +65 -0
- package/dist/components/RadioGroup.js +142 -0
- package/dist/components/Select.d.ts +88 -0
- package/dist/components/Select.js +172 -0
- package/dist/components/Separator.d.ts +83 -0
- package/dist/components/Separator.js +85 -0
- package/dist/components/Skeleton.d.ts +68 -0
- package/dist/components/Skeleton.js +99 -0
- package/dist/components/Slider.d.ts +24 -0
- package/dist/components/Slider.js +162 -0
- package/dist/components/StatusBar.d.ts +1 -0
- package/dist/components/StatusBar.js +19 -0
- package/dist/components/StyledText.d.ts +161 -0
- package/dist/components/StyledText.js +193 -0
- package/dist/components/Switch.d.ts +44 -0
- package/dist/components/Switch.js +129 -0
- package/dist/components/Tabs.d.ts +31 -0
- package/dist/components/Tabs.js +127 -0
- package/dist/components/TextInput.d.ts +120 -0
- package/dist/components/TextInput.js +263 -0
- package/dist/components/Toggle.d.ts +106 -0
- package/dist/components/Toggle.js +150 -0
- package/dist/components/ToggleGroup.d.ts +80 -0
- package/dist/components/ToggleGroup.js +189 -0
- package/dist/components/Tooltip.d.ts +121 -0
- package/dist/components/Tooltip.js +132 -0
- package/dist/components/index.d.ts +35 -0
- package/dist/components/index.js +35 -0
- package/dist/constants/colors.d.ts +82 -0
- package/dist/constants/colors.js +116 -0
- package/dist/constants/fonts.d.ts +32 -0
- package/dist/constants/fonts.js +91 -0
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.js +3 -0
- package/dist/constants/spacing.d.ts +40 -0
- package/dist/constants/spacing.js +48 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/useDimensions.d.ts +19 -0
- package/dist/hooks/useDimensions.js +55 -0
- package/dist/hooks/useReduceMotion.d.ts +5 -0
- package/dist/hooks/useReduceMotion.js +64 -0
- package/dist/hooks/useResources.d.ts +12 -0
- package/dist/hooks/useResources.js +56 -0
- package/dist/hooks/useScalePress.d.ts +57 -0
- package/dist/hooks/useScalePress.js +55 -0
- package/dist/hooks/useStaggeredEntrance.d.ts +67 -0
- package/dist/hooks/useStaggeredEntrance.js +74 -0
- package/dist/hooks/useTheme.d.ts +88 -0
- package/dist/hooks/useTheme.js +328 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/lib/animations.d.ts +1 -0
- package/dist/lib/animations.js +3 -0
- package/dist/lib/haptics.d.ts +3 -0
- package/dist/lib/haptics.js +29 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/sentry.d.ts +16 -0
- package/dist/lib/sentry.js +55 -0
- package/dist/state/globalUIStore.d.ts +30 -0
- package/dist/state/globalUIStore.js +8 -0
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.js +2 -0
- package/dist/state/themeStore.d.ts +6 -0
- package/dist/state/themeStore.js +38 -0
- package/package.json +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @mrmeg/expo-ui
|
|
2
|
+
|
|
3
|
+
Reusable Expo and React Native UI primitives shared by the template and consumer apps. The package does not ship font files; web consumers load Lato from Google Fonts and native consumers use platform sans-serif fallbacks.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install from npm after publishing:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add @mrmeg/expo-ui
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Consumers must also install the peer dependencies listed in `package.json`. Keep npm auth tokens in developer or CI configuration, not in this repository.
|
|
14
|
+
|
|
15
|
+
## Imports
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Button, StyledText } from "@mrmeg/expo-ui/components";
|
|
19
|
+
import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
|
|
20
|
+
import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
|
|
21
|
+
import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
|
|
22
|
+
import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
|
|
23
|
+
import { hapticLight, setupSentry } from "@mrmeg/expo-ui/lib";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The root barrel also exports the public surface:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { Button, colors, useTheme } from "@mrmeg/expo-ui";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## App Startup
|
|
33
|
+
|
|
34
|
+
Call `useResources()` once near the Expo app root before hiding the splash screen:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { ThemeProvider } from "@react-navigation/native";
|
|
38
|
+
import { colors } from "@mrmeg/expo-ui/constants";
|
|
39
|
+
import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
|
|
40
|
+
import { Notification, StatusBar } from "@mrmeg/expo-ui/components";
|
|
41
|
+
import { PortalHost } from "@rn-primitives/portal";
|
|
42
|
+
|
|
43
|
+
export default function RootLayout() {
|
|
44
|
+
const { scheme } = useTheme();
|
|
45
|
+
const { loaded } = useResources();
|
|
46
|
+
|
|
47
|
+
if (!loaded) return null;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<ThemeProvider
|
|
51
|
+
value={{
|
|
52
|
+
dark: colors[scheme ?? "light"].dark,
|
|
53
|
+
colors: colors[scheme ?? "light"].navigation,
|
|
54
|
+
fonts: colors[scheme ?? "light"].fonts,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{/* App navigation goes here. */}
|
|
58
|
+
<Notification />
|
|
59
|
+
<PortalHost />
|
|
60
|
+
<StatusBar />
|
|
61
|
+
</ThemeProvider>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Fonts
|
|
67
|
+
|
|
68
|
+
This package does not ship Lato `.ttf` files or other font binaries.
|
|
69
|
+
|
|
70
|
+
On web, `useResources()` injects the Google Fonts Lato stylesheet after hydration if the app has not already added it. For better first paint in Expo Router web apps, add the links in app-owned `app/+html.tsx`:
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
74
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
75
|
+
<link
|
|
76
|
+
id="mrmeg-expo-ui-lato"
|
|
77
|
+
rel="stylesheet"
|
|
78
|
+
href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap"
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
On native, the package uses platform sans-serif fallbacks. `useResources()` still loads `Feather.font` from the consumer app's `@expo/vector-icons` peer dependency for icon rendering.
|
|
83
|
+
|
|
84
|
+
## Package Checks
|
|
85
|
+
|
|
86
|
+
Run these before publishing:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
bun run ui:typecheck
|
|
90
|
+
bun run ui:test
|
|
91
|
+
bun run ui:build
|
|
92
|
+
bun run ui:pack
|
|
93
|
+
bun run ui:consumer-smoke
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`bun run ui:pack` runs a dry pack so the published file list and package size can be inspected before release.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { View, ViewStyle } from "react-native";
|
|
2
|
+
import * as AccordionPrimitive from "@rn-primitives/accordion";
|
|
3
|
+
type BaseAccordionRootProps = Omit<React.ComponentProps<typeof View>, "style"> & React.RefAttributes<AccordionPrimitive.RootRef> & {
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
collapsible?: boolean;
|
|
6
|
+
dir?: "ltr" | "rtl";
|
|
7
|
+
orientation?: "vertical" | "horizontal";
|
|
8
|
+
style?: ViewStyle;
|
|
9
|
+
};
|
|
10
|
+
type WebSingleAccordionRootProps = BaseAccordionRootProps & {
|
|
11
|
+
type: "single";
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
value?: string;
|
|
14
|
+
onValueChange?: (value: string | undefined) => void;
|
|
15
|
+
};
|
|
16
|
+
type WebMultipleAccordionRootProps = BaseAccordionRootProps & {
|
|
17
|
+
type: "multiple";
|
|
18
|
+
defaultValue?: string[];
|
|
19
|
+
value?: string[];
|
|
20
|
+
onValueChange?: (value: string[]) => void;
|
|
21
|
+
};
|
|
22
|
+
type AccordionRootProps = WebSingleAccordionRootProps | WebMultipleAccordionRootProps;
|
|
23
|
+
/**
|
|
24
|
+
* Accordion Root Component
|
|
25
|
+
* Container for accordion items with support for single or multiple open items
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* <Accordion type="single" collapsible>
|
|
29
|
+
* <AccordionItem value="item-1">
|
|
30
|
+
* <AccordionTrigger>Title</AccordionTrigger>
|
|
31
|
+
* <AccordionContent>Content</AccordionContent>
|
|
32
|
+
* </AccordionItem>
|
|
33
|
+
* </Accordion>
|
|
34
|
+
*/
|
|
35
|
+
declare function Accordion({ children, style, ...props }: AccordionRootProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
/**
|
|
37
|
+
* Accordion Item Component
|
|
38
|
+
* Individual accordion item with border styling
|
|
39
|
+
*/
|
|
40
|
+
declare function AccordionItem({ children, value, style: styleOverride, ...props }: AccordionPrimitive.ItemProps & React.RefAttributes<AccordionPrimitive.ItemRef>): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
/**
|
|
42
|
+
* Accordion Trigger Component
|
|
43
|
+
* Clickable header that expands/collapses the content
|
|
44
|
+
* Includes animated chevron icon
|
|
45
|
+
*/
|
|
46
|
+
declare function AccordionTrigger({ children, style: styleOverride, ...props }: AccordionPrimitive.TriggerProps & {
|
|
47
|
+
children?: React.ReactNode;
|
|
48
|
+
} & React.RefAttributes<AccordionPrimitive.TriggerRef>): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
/**
|
|
50
|
+
* Accordion Content Component
|
|
51
|
+
* Expandable content area with animations
|
|
52
|
+
*/
|
|
53
|
+
declare function AccordionContent({ children, style: styleOverride, ...props }: AccordionPrimitive.ContentProps & React.RefAttributes<AccordionPrimitive.ContentRef>): import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Platform, Pressable, View } from "react-native";
|
|
4
|
+
import Animated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
|
|
5
|
+
import { Icon } from "./Icon";
|
|
6
|
+
import { TextClassContext } from "./StyledText";
|
|
7
|
+
import { useTheme } from "../hooks/useTheme";
|
|
8
|
+
import { spacing } from "../constants/spacing";
|
|
9
|
+
import * as AccordionPrimitive from "@rn-primitives/accordion";
|
|
10
|
+
function normalizeSingleValue(value) {
|
|
11
|
+
return value ?? "";
|
|
12
|
+
}
|
|
13
|
+
function denormalizeSingleValue(value) {
|
|
14
|
+
return value === "" ? undefined : value;
|
|
15
|
+
}
|
|
16
|
+
function normalizeMultipleValue(value) {
|
|
17
|
+
return value ?? [];
|
|
18
|
+
}
|
|
19
|
+
function WebSingleAccordionRoot(props) {
|
|
20
|
+
const isControlled = Object.prototype.hasOwnProperty.call(props, "value");
|
|
21
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeSingleValue(props.defaultValue));
|
|
22
|
+
const normalizedValue = isControlled
|
|
23
|
+
? normalizeSingleValue(props.value)
|
|
24
|
+
: uncontrolledValue;
|
|
25
|
+
const handleValueChange = (nextValue) => {
|
|
26
|
+
const normalizedNextValue = normalizeSingleValue(nextValue);
|
|
27
|
+
if (!isControlled) {
|
|
28
|
+
setUncontrolledValue(normalizedNextValue);
|
|
29
|
+
}
|
|
30
|
+
props.onValueChange?.(denormalizeSingleValue(normalizedNextValue));
|
|
31
|
+
};
|
|
32
|
+
const { children, style, type: _type, defaultValue: _defaultValue, onValueChange: _onValueChange, value: _value, ...rootProps } = props;
|
|
33
|
+
return (_jsx(AccordionPrimitive.Root, { ...rootProps, type: "single", value: normalizedValue, onValueChange: handleValueChange, asChild: false, children: _jsx(View, { style: style, children: children }) }));
|
|
34
|
+
}
|
|
35
|
+
function WebMultipleAccordionRoot(props) {
|
|
36
|
+
const isControlled = Object.prototype.hasOwnProperty.call(props, "value");
|
|
37
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(() => normalizeMultipleValue(props.defaultValue));
|
|
38
|
+
const normalizedValue = isControlled
|
|
39
|
+
? normalizeMultipleValue(props.value)
|
|
40
|
+
: uncontrolledValue;
|
|
41
|
+
const handleValueChange = (nextValue) => {
|
|
42
|
+
const normalizedNextValue = normalizeMultipleValue(nextValue);
|
|
43
|
+
if (!isControlled) {
|
|
44
|
+
setUncontrolledValue(normalizedNextValue);
|
|
45
|
+
}
|
|
46
|
+
props.onValueChange?.(normalizedNextValue);
|
|
47
|
+
};
|
|
48
|
+
const { children, style, type: _type, defaultValue: _defaultValue, onValueChange: _onValueChange, value: _value, ...rootProps } = props;
|
|
49
|
+
return (_jsx(AccordionPrimitive.Root, { ...rootProps, type: "multiple", value: normalizedValue, onValueChange: handleValueChange, asChild: false, children: _jsx(View, { style: style, children: children }) }));
|
|
50
|
+
}
|
|
51
|
+
function WebAccordionRoot(props) {
|
|
52
|
+
if (props.type === "multiple") {
|
|
53
|
+
return _jsx(WebMultipleAccordionRoot, { ...props });
|
|
54
|
+
}
|
|
55
|
+
return _jsx(WebSingleAccordionRoot, { ...props });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Accordion Root Component
|
|
59
|
+
* Container for accordion items with support for single or multiple open items
|
|
60
|
+
*
|
|
61
|
+
* Usage:
|
|
62
|
+
* <Accordion type="single" collapsible>
|
|
63
|
+
* <AccordionItem value="item-1">
|
|
64
|
+
* <AccordionTrigger>Title</AccordionTrigger>
|
|
65
|
+
* <AccordionContent>Content</AccordionContent>
|
|
66
|
+
* </AccordionItem>
|
|
67
|
+
* </Accordion>
|
|
68
|
+
*/
|
|
69
|
+
function Accordion({ children, style, ...props }) {
|
|
70
|
+
if (Platform.OS === "web") {
|
|
71
|
+
return (_jsx(WebAccordionRoot, { ...props, style: style, children: children }));
|
|
72
|
+
}
|
|
73
|
+
return (_jsx(AccordionPrimitive.Root, { ...props, asChild: true, children: _jsx(View, { style: style, children: children }) }));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Accordion Item Component
|
|
77
|
+
* Individual accordion item with border styling
|
|
78
|
+
*/
|
|
79
|
+
function AccordionItem({ children, value, style: styleOverride, ...props }) {
|
|
80
|
+
const { theme } = useTheme();
|
|
81
|
+
return (_jsx(AccordionPrimitive.Item, { value: value, asChild: true, ...props, children: _jsx(View, { style: [
|
|
82
|
+
{
|
|
83
|
+
borderBottomWidth: 1,
|
|
84
|
+
borderBottomColor: theme.colors.border,
|
|
85
|
+
overflow: "hidden",
|
|
86
|
+
},
|
|
87
|
+
// Spread array styles from primitives to prevent nested arrays on web
|
|
88
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
89
|
+
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
90
|
+
: []),
|
|
91
|
+
], children: children }) }));
|
|
92
|
+
}
|
|
93
|
+
const Trigger = Platform.OS === "web" ? View : Pressable;
|
|
94
|
+
/**
|
|
95
|
+
* Accordion Trigger Component
|
|
96
|
+
* Clickable header that expands/collapses the content
|
|
97
|
+
* Includes animated chevron icon
|
|
98
|
+
*/
|
|
99
|
+
function AccordionTrigger({ children, style: styleOverride, ...props }) {
|
|
100
|
+
const { theme } = useTheme();
|
|
101
|
+
const reduceMotion = useReducedMotion();
|
|
102
|
+
const { isExpanded } = AccordionPrimitive.useItemContext();
|
|
103
|
+
const rotation = useSharedValue(isExpanded ? 1 : 0);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const target = isExpanded ? 1 : 0;
|
|
106
|
+
if (reduceMotion) {
|
|
107
|
+
rotation.value = target;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
rotation.value = withTiming(target, {
|
|
111
|
+
duration: isExpanded ? 200 : 150,
|
|
112
|
+
});
|
|
113
|
+
}, [isExpanded, reduceMotion]);
|
|
114
|
+
const chevronStyle = useAnimatedStyle(() => ({
|
|
115
|
+
transform: [{ rotate: `${rotation.value * 180}deg` }],
|
|
116
|
+
}));
|
|
117
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(AccordionPrimitive.Header, { children: _jsx(AccordionPrimitive.Trigger, { ...props, asChild: true, children: _jsxs(Trigger, { style: [
|
|
118
|
+
{
|
|
119
|
+
flexDirection: "row",
|
|
120
|
+
alignItems: "center",
|
|
121
|
+
justifyContent: "space-between",
|
|
122
|
+
gap: spacing.md,
|
|
123
|
+
borderRadius: spacing.radiusMd,
|
|
124
|
+
paddingVertical: spacing.md,
|
|
125
|
+
...(Platform.OS === "web" && { cursor: "pointer" }),
|
|
126
|
+
},
|
|
127
|
+
// Spread array styles from primitives to prevent nested arrays on web
|
|
128
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
129
|
+
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
130
|
+
: []),
|
|
131
|
+
], children: [_jsx(_Fragment, { children: children }), _jsx(Animated.View, { style: chevronStyle, children: _jsx(Icon, { name: "chevron-down", size: 16, color: theme.colors.textDim, decorative: true }) })] }) }) }) }));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Accordion Content Component
|
|
135
|
+
* Expandable content area with animations
|
|
136
|
+
*/
|
|
137
|
+
function AccordionContent({ children, style: styleOverride, ...props }) {
|
|
138
|
+
return (_jsx(TextClassContext.Provider, { value: "", children: _jsx(AccordionPrimitive.Content, { ...props, children: _jsx(View, { style: [
|
|
139
|
+
{
|
|
140
|
+
paddingBottom: spacing.sm,
|
|
141
|
+
overflow: "hidden",
|
|
142
|
+
},
|
|
143
|
+
// Spread array styles from primitives to prevent nested arrays on web
|
|
144
|
+
...(styleOverride && typeof styleOverride !== "function"
|
|
145
|
+
? (Array.isArray(styleOverride) ? styleOverride : [styleOverride])
|
|
146
|
+
: []),
|
|
147
|
+
], children: children }) }) }));
|
|
148
|
+
}
|
|
149
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform Alert utility.
|
|
3
|
+
*
|
|
4
|
+
* Wraps React Native's Alert API to support both native and web platforms.
|
|
5
|
+
* On web, it uses `window.alert()` or `window.confirm()` to simulate native alerts.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* Alert.show({
|
|
9
|
+
* title: "Delete Item",
|
|
10
|
+
* message: "Are you sure you want to delete this?",
|
|
11
|
+
* buttons: [
|
|
12
|
+
* { text: "Cancel", style: "cancel" },
|
|
13
|
+
* { text: "Delete", style: "destructive", onPress: () => handleDelete() }
|
|
14
|
+
* ]
|
|
15
|
+
* });
|
|
16
|
+
*/
|
|
17
|
+
type AlertButton = {
|
|
18
|
+
text: string;
|
|
19
|
+
onPress?: () => void;
|
|
20
|
+
style?: "default" | "cancel" | "destructive";
|
|
21
|
+
};
|
|
22
|
+
interface AlertParams {
|
|
23
|
+
title?: string;
|
|
24
|
+
message: string;
|
|
25
|
+
buttons?: AlertButton[];
|
|
26
|
+
}
|
|
27
|
+
export declare const Alert: {
|
|
28
|
+
show: ({ title, message, buttons }: AlertParams) => void;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Alert as RNAlert, Platform } from "react-native";
|
|
2
|
+
export const Alert = {
|
|
3
|
+
show: ({ title = "", message, buttons = [{ text: "OK" }] }) => {
|
|
4
|
+
if (Platform.OS === "web") {
|
|
5
|
+
if (buttons.length > 1) {
|
|
6
|
+
const result = window.confirm(`${title}\n${message}`);
|
|
7
|
+
const confirmButton = buttons.find(b => b.style !== "cancel");
|
|
8
|
+
const cancelButton = buttons.find(b => b.style === "cancel");
|
|
9
|
+
if (result && confirmButton?.onPress) {
|
|
10
|
+
confirmButton.onPress();
|
|
11
|
+
}
|
|
12
|
+
else if (!result && cancelButton?.onPress) {
|
|
13
|
+
cancelButton.onPress();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
window.alert(`${title}\n${message}`);
|
|
18
|
+
buttons[0]?.onPress?.();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
RNAlert.alert(title, message, buttons);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ViewProps } from "react-native";
|
|
3
|
+
/**
|
|
4
|
+
* Animation type options
|
|
5
|
+
*/
|
|
6
|
+
export type AnimationType = "fade" | "fadeSlideUp" | "fadeSlideDown" | "scale";
|
|
7
|
+
interface AnimatedViewProps extends ViewProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
/**
|
|
10
|
+
* Type of animation to use
|
|
11
|
+
* @default "fade"
|
|
12
|
+
*/
|
|
13
|
+
type?: AnimationType;
|
|
14
|
+
/**
|
|
15
|
+
* Animation duration in milliseconds for enter animation
|
|
16
|
+
* @default 200
|
|
17
|
+
*/
|
|
18
|
+
enterDuration?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Delay before starting the enter animation (in milliseconds)
|
|
21
|
+
* Useful for staggered animations
|
|
22
|
+
* @default 0
|
|
23
|
+
*/
|
|
24
|
+
delay?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cross-Platform Animated View Component
|
|
28
|
+
* Uses Reanimated for smooth 60fps animations on all platforms
|
|
29
|
+
*
|
|
30
|
+
* Features:
|
|
31
|
+
* - Multiple animation types (fade, fadeSlideUp, fadeSlideDown, scale)
|
|
32
|
+
* - Configurable enter duration
|
|
33
|
+
* - Optional delay for staggered animations
|
|
34
|
+
* - Respects reduced motion accessibility preference
|
|
35
|
+
*
|
|
36
|
+
* Usage:
|
|
37
|
+
* ```tsx
|
|
38
|
+
* // Simple fade
|
|
39
|
+
* <AnimatedView>
|
|
40
|
+
* {children}
|
|
41
|
+
* </AnimatedView>
|
|
42
|
+
*
|
|
43
|
+
* // Fade with slide up
|
|
44
|
+
* <AnimatedView type="fadeSlideUp">
|
|
45
|
+
* {children}
|
|
46
|
+
* </AnimatedView>
|
|
47
|
+
*
|
|
48
|
+
* // With delay (for staggered lists)
|
|
49
|
+
* <AnimatedView type="fadeSlideUp" delay={100}>
|
|
50
|
+
* {children}
|
|
51
|
+
* </AnimatedView>
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function AnimatedView({ children, type, enterDuration, delay, style, ...props }: AnimatedViewProps): import("react/jsx-runtime").JSX.Element;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import Animated from "react-native-reanimated";
|
|
3
|
+
import { useStaggeredEntrance } from "../hooks/useStaggeredEntrance";
|
|
4
|
+
/**
|
|
5
|
+
* Cross-Platform Animated View Component
|
|
6
|
+
* Uses Reanimated for smooth 60fps animations on all platforms
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Multiple animation types (fade, fadeSlideUp, fadeSlideDown, scale)
|
|
10
|
+
* - Configurable enter duration
|
|
11
|
+
* - Optional delay for staggered animations
|
|
12
|
+
* - Respects reduced motion accessibility preference
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // Simple fade
|
|
17
|
+
* <AnimatedView>
|
|
18
|
+
* {children}
|
|
19
|
+
* </AnimatedView>
|
|
20
|
+
*
|
|
21
|
+
* // Fade with slide up
|
|
22
|
+
* <AnimatedView type="fadeSlideUp">
|
|
23
|
+
* {children}
|
|
24
|
+
* </AnimatedView>
|
|
25
|
+
*
|
|
26
|
+
* // With delay (for staggered lists)
|
|
27
|
+
* <AnimatedView type="fadeSlideUp" delay={100}>
|
|
28
|
+
* {children}
|
|
29
|
+
* </AnimatedView>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function AnimatedView({ children, type = "fade", enterDuration = 200, delay = 0, style, ...props }) {
|
|
33
|
+
const entranceStyle = useStaggeredEntrance({
|
|
34
|
+
type,
|
|
35
|
+
delay,
|
|
36
|
+
duration: enterDuration,
|
|
37
|
+
});
|
|
38
|
+
return (_jsx(Animated.View, { style: [style, entranceStyle], ...props, children: children }));
|
|
39
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { StyleProp, ViewStyle } from "react-native";
|
|
3
|
+
export type BadgeVariant = "default" | "secondary" | "outline" | "destructive";
|
|
4
|
+
export interface BadgeProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
variant?: BadgeVariant;
|
|
7
|
+
style?: StyleProp<ViewStyle>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Badge Component
|
|
11
|
+
*
|
|
12
|
+
* Small inline status label with pill shape.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <Badge>Default</Badge>
|
|
17
|
+
* <Badge variant="secondary">Secondary</Badge>
|
|
18
|
+
* <Badge variant="outline">Outline</Badge>
|
|
19
|
+
* <Badge variant="destructive">Error</Badge>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function Badge({ children, variant, style: styleOverride }: BadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export { Badge };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import { useTheme } from "../hooks/useTheme";
|
|
5
|
+
import { spacing } from "../constants/spacing";
|
|
6
|
+
import { StyledText } from "./StyledText";
|
|
7
|
+
/**
|
|
8
|
+
* Badge Component
|
|
9
|
+
*
|
|
10
|
+
* Small inline status label with pill shape.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Badge>Default</Badge>
|
|
15
|
+
* <Badge variant="secondary">Secondary</Badge>
|
|
16
|
+
* <Badge variant="outline">Outline</Badge>
|
|
17
|
+
* <Badge variant="destructive">Error</Badge>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function Badge({ children, variant = "default", style: styleOverride }) {
|
|
21
|
+
const { theme } = useTheme();
|
|
22
|
+
const styles = createStyles(theme);
|
|
23
|
+
const textStyle = [
|
|
24
|
+
styles.text,
|
|
25
|
+
variant === "default" && { color: theme.colors.primaryForeground },
|
|
26
|
+
variant === "secondary" && { color: theme.colors.secondaryForeground },
|
|
27
|
+
variant === "outline" && { color: theme.colors.foreground },
|
|
28
|
+
variant === "destructive" && { color: theme.colors.destructiveForeground },
|
|
29
|
+
];
|
|
30
|
+
const normalizedChildren = React.Children.toArray(children);
|
|
31
|
+
const hasOnlyTextChildren = normalizedChildren.every((child) => typeof child === "string" || typeof child === "number");
|
|
32
|
+
const content = hasOnlyTextChildren ? (_jsx(StyledText, { style: textStyle, children: normalizedChildren.join("") })) : (React.Children.map(children, (child) => {
|
|
33
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
34
|
+
return _jsx(StyledText, { style: textStyle, children: child });
|
|
35
|
+
}
|
|
36
|
+
return child;
|
|
37
|
+
}));
|
|
38
|
+
return (_jsx(View, { accessibilityRole: "text", style: [
|
|
39
|
+
styles.badge,
|
|
40
|
+
variant === "default" && styles.default,
|
|
41
|
+
variant === "secondary" && styles.secondary,
|
|
42
|
+
variant === "outline" && styles.outline,
|
|
43
|
+
variant === "destructive" && styles.destructive,
|
|
44
|
+
styleOverride,
|
|
45
|
+
], children: content }));
|
|
46
|
+
}
|
|
47
|
+
const createStyles = (theme) => StyleSheet.create({
|
|
48
|
+
badge: {
|
|
49
|
+
alignSelf: "flex-start",
|
|
50
|
+
borderRadius: spacing.radiusFull,
|
|
51
|
+
paddingHorizontal: 10,
|
|
52
|
+
paddingVertical: 2,
|
|
53
|
+
},
|
|
54
|
+
default: {
|
|
55
|
+
backgroundColor: theme.colors.primary,
|
|
56
|
+
},
|
|
57
|
+
secondary: {
|
|
58
|
+
backgroundColor: theme.colors.secondary,
|
|
59
|
+
},
|
|
60
|
+
outline: {
|
|
61
|
+
backgroundColor: "transparent",
|
|
62
|
+
borderWidth: 1,
|
|
63
|
+
borderColor: theme.colors.border,
|
|
64
|
+
},
|
|
65
|
+
destructive: {
|
|
66
|
+
backgroundColor: theme.colors.destructive,
|
|
67
|
+
},
|
|
68
|
+
text: {
|
|
69
|
+
fontSize: 12,
|
|
70
|
+
fontWeight: "500",
|
|
71
|
+
lineHeight: 18,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
export { Badge };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ViewProps, StyleProp, ViewStyle, ScrollViewProps } from "react-native";
|
|
3
|
+
type SnapPoint = number | `${number}%`;
|
|
4
|
+
interface BottomSheetContextValue {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onOpenChange: (open: boolean) => void;
|
|
7
|
+
toggle: () => void;
|
|
8
|
+
snapPoints: number[];
|
|
9
|
+
currentSnapIndex: number;
|
|
10
|
+
closeOnBackdropPress: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface BottomSheetProps {
|
|
13
|
+
/** Controlled open state */
|
|
14
|
+
open?: boolean;
|
|
15
|
+
/** Callback when open state changes */
|
|
16
|
+
onOpenChange?: (open: boolean) => void;
|
|
17
|
+
/** Default open state for uncontrolled mode */
|
|
18
|
+
defaultOpen?: boolean;
|
|
19
|
+
/** Snap point heights (px or percentage strings) */
|
|
20
|
+
snapPoints?: SnapPoint[];
|
|
21
|
+
/** Whether to close when backdrop is pressed */
|
|
22
|
+
closeOnBackdropPress?: boolean;
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
interface BottomSheetTriggerProps {
|
|
26
|
+
asChild?: boolean;
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
style?: StyleProp<ViewStyle>;
|
|
29
|
+
}
|
|
30
|
+
interface BottomSheetContentProps extends ViewProps {
|
|
31
|
+
/** Whether to enable swipe/drag gestures */
|
|
32
|
+
swipeEnabled?: boolean;
|
|
33
|
+
/** Velocity threshold for quick swipe to close */
|
|
34
|
+
velocityThreshold?: number;
|
|
35
|
+
style?: StyleProp<ViewStyle>;
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
interface BottomSheetHeaderProps extends ViewProps {
|
|
39
|
+
children: React.ReactNode;
|
|
40
|
+
}
|
|
41
|
+
interface BottomSheetBodyProps extends ScrollViewProps {
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
}
|
|
44
|
+
interface BottomSheetFooterProps extends ViewProps {
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
}
|
|
47
|
+
interface BottomSheetHandleProps {
|
|
48
|
+
style?: StyleProp<ViewStyle>;
|
|
49
|
+
}
|
|
50
|
+
interface BottomSheetCloseProps {
|
|
51
|
+
asChild?: boolean;
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
style?: StyleProp<ViewStyle>;
|
|
54
|
+
}
|
|
55
|
+
declare function useBottomSheetContext(): BottomSheetContextValue;
|
|
56
|
+
declare function BottomSheetRoot({ open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen, snapPoints: rawSnapPoints, closeOnBackdropPress, children, }: BottomSheetProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
declare function BottomSheetTrigger({ asChild, children, style: styleOverride }: BottomSheetTriggerProps): import("react/jsx-runtime").JSX.Element;
|
|
58
|
+
declare function BottomSheetContent({ swipeEnabled, velocityThreshold, style: styleOverride, children, ...props }: BottomSheetContentProps): import("react/jsx-runtime").JSX.Element | null;
|
|
59
|
+
declare function BottomSheetHandle({ style }: BottomSheetHandleProps): import("react/jsx-runtime").JSX.Element;
|
|
60
|
+
declare function BottomSheetHeader({ children, style, ...props }: BottomSheetHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
61
|
+
declare function BottomSheetBody({ children, style, ...props }: BottomSheetBodyProps): import("react/jsx-runtime").JSX.Element;
|
|
62
|
+
declare function BottomSheetFooter({ children, style, ...props }: BottomSheetFooterProps): import("react/jsx-runtime").JSX.Element;
|
|
63
|
+
declare function BottomSheetClose({ asChild, children, style: styleOverride }: BottomSheetCloseProps): import("react/jsx-runtime").JSX.Element;
|
|
64
|
+
declare const BottomSheet: typeof BottomSheetRoot & {
|
|
65
|
+
Trigger: typeof BottomSheetTrigger;
|
|
66
|
+
Content: typeof BottomSheetContent;
|
|
67
|
+
Handle: typeof BottomSheetHandle;
|
|
68
|
+
Header: typeof BottomSheetHeader;
|
|
69
|
+
Body: typeof BottomSheetBody;
|
|
70
|
+
Footer: typeof BottomSheetFooter;
|
|
71
|
+
Close: typeof BottomSheetClose;
|
|
72
|
+
};
|
|
73
|
+
export { BottomSheet, BottomSheetTrigger, BottomSheetContent, BottomSheetHandle, BottomSheetHeader, BottomSheetBody, BottomSheetFooter, BottomSheetClose, useBottomSheetContext, };
|
|
74
|
+
export type { BottomSheetProps, BottomSheetTriggerProps, BottomSheetContentProps, BottomSheetHandleProps, BottomSheetHeaderProps, BottomSheetBodyProps, BottomSheetFooterProps, BottomSheetCloseProps, };
|