Поставьте зависимости:
npm install @expo/vector-icons expo-blur expo-symbols react-native-reanimated react-native-workletspnpm add @expo/vector-icons expo-blur expo-symbols react-native-reanimated react-native-workletsyarn add @expo/vector-icons expo-blur expo-symbols react-native-reanimated react-native-workletsbun add @expo/vector-icons expo-blur expo-symbols react-native-reanimated react-native-workletsСкопируйте эти файлы в src/shared/components/molecules/search-bar/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { useRef } from "react";
import { Platform, StyleSheet, View, useWindowDimensions } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { SearchBar } from "@/shared/components/molecules/search-bar";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "placeholder", label: "Подсказка", type: "text", default: "Search", maxLength: 30 },
{ key: "tint", label: "Акцент", type: "color", default: "#ffffff" },
{ key: "containerWidth", label: "Ширина", type: "slider", min: 220, max: 380, step: 2, unit: "px", default: 350 },
{ key: "cancelButtonWidth", label: "Ширина «Отмена»", type: "slider", min: 50, max: 110, step: 1, unit: "px", default: 68 },
{ key: "centerWhenUnfocused", label: "По центру", type: "toggle", default: true },
{ key: "enableWidthAnimation", label: "Анимация ширины", type: "toggle", default: true },
] as const;
export default function App() {
const custom = useCustomize(CUSTOMIZE);
const { width: screenWidth } = useWindowDimensions();
const containerWidth = Math.min(custom.containerWidth, Math.floor(screenWidth - 32));
const hostRef = useRef<View>(null);
const findInput = () =>
(hostRef.current as unknown as HTMLElement | null)?.querySelector("input");
useAutoplay(async ({ wait }) => {
await wait(500);
findInput()?.focus({ preventScroll: true });
await wait(1000);
findInput()?.blur();
await wait(450);
});
return (
<Showcase>
<StatusBar style="inverted" />
<GestureHandlerRootView style={styles.container}>
<View ref={hostRef}>
<SearchBar
key={`${containerWidth}-${custom.cancelButtonWidth}-${custom.centerWhenUnfocused}-${custom.enableWidthAnimation}`}
placeholder={custom.placeholder}
containerWidth={containerWidth}
cancelButtonWidth={custom.cancelButtonWidth}
centerWhenUnfocused={custom.centerWhenUnfocused}
enableWidthAnimation={custom.enableWidthAnimation}
tint={custom.tint}
textCenterOffset={2.2}
iconCenterOffset={2.2}
inputStyle={Platform.OS === "web" ? { minHeight: 34 } : undefined}
/>
</View>
</GestureHandlerRootView>
</Showcase>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0a",
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 24,
paddingTop: 70,
},
title: {
fontSize: 35,
color: "#fff",
letterSpacing: 2,
},
subtitle: {
fontSize: 15,
color: "#aaa",
},
headerRight: {
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: "#1a1a1a",
justifyContent: "center",
alignItems: "center",
},
card: {
width: "100%",
height: 340,
borderRadius: 24,
overflow: "hidden",
},
cardImage: {
width: 300,
height: 400,
resizeMode: "cover",
},
cardGradient: {
...StyleSheet.absoluteFillObject,
},
cardContent: {
position: "absolute",
bottom: 0,
left: 0,
right: 0,
padding: 20,
gap: 8,
},
albumName: {
fontSize: 16,
color: "#fff",
letterSpacing: 1,
},
artistRow: {
flexDirection: "row",
alignItems: "center",
gap: 6,
},
artistText: {
fontSize: 13,
color: "rgba(255,255,255,0.7)",
},
dot: {
width: 3,
height: 3,
borderRadius: 1.5,
backgroundColor: "rgba(255,255,255,0.4)",
},
yearText: {
fontSize: 13,
color: "rgba(255,255,255,0.5)",
},
footer: {
paddingHorizontal: 24,
marginTop: 32,
gap: 20,
},
nowPlaying: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "#141414",
padding: 12,
borderRadius: 16,
},
nowPlayingLeft: {
flexDirection: "row",
alignItems: "center",
gap: 12,
flex: 1,
},
nowPlayingImage: {
width: 48,
height: 48,
borderRadius: 10,
},
nowPlayingInfo: {
flex: 1,
gap: 2,
},
nowPlayingTitle: {
fontSize: 14,
fontWeight: "600",
color: "#fff",
},
nowPlayingArtist: {
fontSize: 12,
color: "#666",
},
nowPlayingControls: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: "#fff",
justifyContent: "center",
alignItems: "center",
},
dots: {
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 6,
},
dotIndicator: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: "#333",
},
dotIndicatorActive: {
width: 20,
backgroundColor: "#fff",
},
});placeholder?Placeholder text for the search input
Тип string·По умолчанию "Search..."
onSearch?Callback when search text changes
Тип (query: string) => void
onClear?Callback when search is cleared
Тип () => void
style?Additional style for the container
Тип ViewStyle
inputStyle?Style for the input
Тип TextStyle
width?Width of the search bar
Тип DimensionValue·По умолчанию "100%"
maxWidth?Maximum width of the search bar
Тип number·По умолчанию screenWidth - 32
parentHeight?Height of the parent container
Тип number | 40·По умолчанию 40
tint?Tint color for the search
Тип string
iconPadding?Тип number
renderTrailingIcons?Тип () => React.ReactNode
renderLeadingIcons?Тип () => React.ReactNode
onSearchDone?Тип () => void
onSearchMount?Тип () => void
containerWidth?Тип number
focusedWidth?Тип number
cancelButtonWidth?Тип number
iconStyle?Тип StyleProp<ViewStyle>
enableWidthAnimation?Тип boolean
centerWhenUnfocused?Тип boolean
textCenterOffset?Тип number
iconCenterOffset?Тип number