Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
437 changes: 437 additions & 0 deletions app/(site)/docs/components/combobox/page.tsx

Large diffs are not rendered by default.

121 changes: 71 additions & 50 deletions app/(site)/docs/components/drawer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import {
Dimensions,
StyleSheet,
Easing,
KeyboardAvoidingView,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { cn } from "@/lib/utils";

interface DrawerProps {
Expand All @@ -37,6 +39,9 @@ interface DrawerProps {
initialSnapIndex?: number;
className?: string;
contentClassName?: string;
avoidKeyboard?: boolean;
closeOnBackdropPress?: boolean;
disableBackHandler?: boolean;
}

const { height: SCREEN_HEIGHT } = Dimensions.get("window");
Expand All @@ -59,9 +64,13 @@ const Drawer = React.forwardRef<View, DrawerProps>(
initialSnapIndex = 0,
className,
contentClassName,
avoidKeyboard = true,
closeOnBackdropPress = true,
disableBackHandler = false,
},
ref
) => {
const [isVisible, setIsVisible] = React.useState(false);
const snapPointsPixels = snapPoints.map(
(point) => SCREEN_HEIGHT - SCREEN_HEIGHT * point
);
Expand Down Expand Up @@ -114,23 +123,21 @@ const Drawer = React.forwardRef<View, DrawerProps>(
useNativeDriver: true,
delay: 100,
}).start(() => {
onClose();
setIsVisible(false);
isClosing.current = false;
onClose();
});
}, [backdropOpacity, translateY, onClose]);

React.useEffect(() => {
if (open && !isClosing.current) {
if (open && !isVisible) {
setIsVisible(true);
} else if (open && !isClosing.current) {
animateOpen();
}
}, [open, animateOpen]);

// Ensure drawer animates close when open becomes false
React.useEffect(() => {
if (!open && !isClosing.current) {
} else if (!open && isVisible && !isClosing.current) {
animateClose();
}
}, [open, animateClose]);
}, [open, isVisible, animateOpen, animateClose, isClosing]);

const animateToSnapPoint = (index: number, velocity = 0) => {
if (index < 0 || index >= snapPointsPixels.length) return;
Expand Down Expand Up @@ -242,57 +249,71 @@ const Drawer = React.forwardRef<View, DrawerProps>(
}
},
});
}, [snapPointsPixels, onClose, translateY, animateClose]);
}, [snapPointsPixels, animateClose]);

if (!isVisible) return null;

const renderContent = () => (
<View className="flex-1">
<Animated.View style={[styles.backdrop, { opacity: backdropOpacity }]}>
{closeOnBackdropPress && (
<TouchableWithoutFeedback onPress={animateClose}>
<View style={StyleSheet.absoluteFillObject} />
</TouchableWithoutFeedback>
)}
</Animated.View>

<Animated.View
style={[styles.drawerContainer, { transform: [{ translateY }] }]}
className={cn(
"absolute bottom-0 left-0 right-0 bg-popover rounded-t-xl overflow-hidden",
Platform.OS === "ios" ? "ios:shadow-xl" : "android:elevation-24",
contentClassName
)}
>
<View {...panResponder.panHandlers}>
<View className="w-full items-center py-2">
<View className="w-10 h-1 rounded-full bg-muted-foreground/30" />
</View>

{title && (
<View className="px-4 pt-1 pb-3 border-b border-border">
<Text className="text-xl font-medium text-center text-foreground">
{title}
</Text>
</View>
)}
</View>

if (!open) return null;
<SafeAreaView className="flex-1" edges={["bottom"]}>
<View ref={ref} className="flex-1">
{children}
</View>
</SafeAreaView>
</Animated.View>
</View>
);

return (
<DrawerContext.Provider value={{ animateClose }}>
<Modal
visible={open}
visible={isVisible}
transparent
animationType="none"
statusBarTranslucent
onRequestClose={animateClose}
onRequestClose={disableBackHandler ? undefined : animateClose}
>
<View className="flex-1">
<Animated.View
style={[styles.backdrop, { opacity: backdropOpacity }]}
{avoidKeyboard && Platform.OS === "ios" ? (
<KeyboardAvoidingView
behavior="padding"
style={{ flex: 1 }}
keyboardVerticalOffset={10}
>
<TouchableWithoutFeedback onPress={animateClose}>
<View style={StyleSheet.absoluteFillObject} />
</TouchableWithoutFeedback>
</Animated.View>

<Animated.View
style={[styles.drawerContainer, { transform: [{ translateY }] }]}
className={cn(
"absolute bottom-0 left-0 right-0 bg-popover rounded-t-xl overflow-hidden",
Platform.OS === "ios"
? "ios:shadow-xl"
: "android:elevation-24",
contentClassName
)}
>
<View {...panResponder.panHandlers}>
<View className="w-full items-center py-2">
<View className="w-10 h-1 rounded-full bg-muted-foreground/30" />
</View>

{title && (
<View className="px-4 pt-1 pb-3 border-b border-border">
<Text className="text-xl font-medium text-center text-foreground">
{title}
</Text>
</View>
)}
</View>

<View ref={ref} className="flex-1">
{children}
</View>
</Animated.View>
</View>
{renderContent()}
</KeyboardAvoidingView>
) : (
renderContent()
)}
</Modal>
</DrawerContext.Provider>
);
Expand Down
66 changes: 42 additions & 24 deletions app/(site)/docs/components/select/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ interface SelectProps {
className?: string;
triggerClassName?: string;
contentClassName?: string;
snapPoints?: number[];
initialSnapIndex?: number;
avoidKeyboard?: boolean;
children: React.ReactNode;
}

Expand Down Expand Up @@ -63,6 +66,9 @@ const Select = React.forwardRef<View, SelectProps>(
className,
triggerClassName,
contentClassName,
snapPoints = [0.5, 0.8],
initialSnapIndex = 0,
avoidKeyboard = true,
children,
},
ref
Expand All @@ -73,49 +79,53 @@ const Select = React.forwardRef<View, SelectProps>(
React.useState<React.ReactNode>("");

React.useEffect(() => {
if (value === undefined) return;
setSelectedValue(value);
}, [value]);

React.useEffect(() => {
if (selectedValue === undefined) return;

let found = false;

React.Children.forEach(children, (child) => {
const findLabel = (child: React.ReactNode) => {
if (!React.isValidElement(child)) return;

const childElement = child as React.ReactElement<any>;

if (
childElement.type === SelectItem &&
childElement.props.value === value
childElement.props.value === selectedValue
) {
setSelectedLabel(childElement.props.children);
setSelectedValue(value);
found = true;
return;
}

if (childElement.type === SelectGroup) {
React.Children.forEach(childElement.props.children, (groupChild) => {
if (
React.isValidElement(groupChild) &&
(groupChild as React.ReactElement<any>).type === SelectItem &&
(groupChild as React.ReactElement<any>).props.value === value
) {
setSelectedLabel(
(groupChild as React.ReactElement<any>).props.children
);
setSelectedValue(value);
}
});
React.Children.forEach(childElement.props.children, findLabel);
}
});
}, [value, children]);
};

React.Children.forEach(children, findLabel);

if (!found) {
setSelectedLabel("");
}
}, [selectedValue, children]);

const handleSelect = (value: string, label: React.ReactNode) => {
setSelectedValue(value);
setSelectedLabel(label);
if (onValueChange) {
onValueChange(value);
}

if (!onValueChange) {
setSelectedValue(value);
setSelectedLabel(label);
}

setTimeout(() => {
setOpen(false);
}, 300); // Delay setting open to false until after the animation completes
}, 300);
};

const enhancedChildren = React.Children.map(children, (child) => {
Expand Down Expand Up @@ -191,11 +201,19 @@ const Select = React.forwardRef<View, SelectProps>(
open={open}
onClose={() => setOpen(false)}
title={placeholder || "Select an option"}
snapPoints={[0.5, 0.8]}
initialSnapIndex={0}
snapPoints={snapPoints}
initialSnapIndex={initialSnapIndex}
contentClassName={contentClassName}
avoidKeyboard={avoidKeyboard}
closeOnBackdropPress={true}
>
<ScrollView className="px-1 pt-2 pb-6">{enhancedChildren}</ScrollView>
<ScrollView
className="px-1 pt-2 pb-6"
keyboardShouldPersistTaps="handled"
nestedScrollEnabled={true}
>
{enhancedChildren}
</ScrollView>
</Drawer>
</View>
);
Expand Down
Loading