* react-native-popable HarmonyOS Example
* @format
*/
import React, { useRef, useState } from 'react';
import {
SafeAreaView,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
Platform,
} from 'react-native';
import { Popable, Popover, usePopable } from 'react-native-popable';
const COLORS = {
primary: '#4A90D9',
success: '#27AE60',
warning: '#F39C12',
danger: '#E74C3C',
dark: '#2C3E50',
light: '#ECF0F1',
white: '#FFFFFF',
muted: '#95A5A6',
};
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text>
<View style={styles.sectionContent}>{children}</View>
</View>
);
}
function DemoButton({ label, onPress }: { label: string; onPress?: () => void }) {
const Btn: any = onPress ? TouchableOpacity : View;
return (
<Btn style={styles.demoButton} onPress={onPress}>
<Text style={styles.demoButtonText}>{label}</Text>
</Btn>
);
}
function BasicPopableDemo() {
return (
<Section title="1. Basic Popable (Press)">
<View style={styles.row}>
<Popable content="Hello from Popable!">
<DemoButton label="Press me" />
</Popable>
</View>
</Section>
);
}
function PositionDemo() {
return (
<Section title="2. Four Positions">
<View style={styles.positionGrid}>
<View style={styles.positionRow}>
<Popable content="Top position" position="top">
<DemoButton label="Top" />
</Popable>
<Popable content="Bottom position" position="bottom">
<DemoButton label="Bottom" />
</Popable>
</View>
<View style={styles.positionRow}>
<Popable content="Left position" position="left">
<DemoButton label="Left" />
</Popable>
<Popable content="Right position" position="right">
<DemoButton label="Right" />
</Popable>
</View>
</View>
</Section>
);
}
function LongpressDemo() {
return (
<Section title="3. Long Press Action">
<View style={styles.row}>
<Popable content="Triggered by long press!" action="longpress">
<DemoButton label="Long Press me" />
</Popable>
</View>
</Section>
);
}
function HoverDemo() {
const isHarmony = Platform.OS === 'harmony';
return (
<Section title="4. Hover Action">
<Text style={styles.note}>
{isHarmony
? 'Hover falls back to press on HarmonyOS'
: 'Hover triggers on mouse enter/leave'}
</Text>
<View style={styles.row}>
<Popable content={isHarmony ? 'Press (hover fallback)' : 'Hover over me!'} action="hover">
<DemoButton label={isHarmony ? 'Press (hover)' : 'Hover me'} />
</Popable>
</View>
</Section>
);
}
function ControlledVisibleDemo() {
const [visible, setVisible] = useState(false);
return (
<Section title="5. Controlled Visible">
<View style={styles.row}>
<Popable content="Controlled popover" visible={visible} onAction={(v) => setVisible(v)}>
<DemoButton label="Target" />
</Popable>
<DemoButton label={visible ? 'Hide' : 'Show'} onPress={() => setVisible(!visible)} />
</View>
</Section>
);
}
function UsePopableDemo() {
const { ref, show, hide } = usePopable();
const [lastAction, setLastAction] = useState('none');
return (
<Section title="6. usePopable Hook">
<View style={styles.row}>
<Popable ref={ref} content="Shown by usePopable!">
<DemoButton label="Target" />
</Popable>
<DemoButton
label="Show"
onPress={() => {
show();
setLastAction('show');
}}
/>
<DemoButton
label="Hide"
onPress={() => {
hide();
setLastAction('hide');
}}
/>
</View>
<Text style={styles.resultText}>Last action: {lastAction}</Text>
</Section>
);
}
function CustomStyleDemo() {
return (
<Section title="7. Custom Styling">
<View style={styles.positionRow}>
<Popable
content="Red background"
backgroundColor="#E74C3C"
>
<DemoButton label="Red" />
</Popable>
<Popable
content="Green background"
backgroundColor="#27AE60"
>
<DemoButton label="Green" />
</Popable>
<Popable
content="No caret"
caret={false}
>
<DemoButton label="No Caret" />
</Popable>
</View>
</Section>
);
}
function CaretPositionDemo() {
return (
<Section title="8. Caret Position">
<View style={styles.positionRow}>
<Popable content="Left caret" caretPosition="left" position="bottom">
<DemoButton label="Left" />
</Popable>
<Popable content="Center caret" caretPosition="center" position="bottom">
<DemoButton label="Center" />
</Popable>
<Popable content="Right caret" caretPosition="right" position="bottom">
<DemoButton label="Right" />
</Popable>
</View>
</Section>
);
}
function AnimationDemo() {
return (
<Section title="9. Animation Types">
<View style={styles.positionRow}>
<Popable content="Spring animation" animationType="spring">
<DemoButton label="Spring" />
</Popable>
<Popable content="Timing animation" animationType="timing">
<DemoButton label="Timing" />
</Popable>
<Popable content="No animation" animated={false}>
<DemoButton label="No Anim" />
</Popable>
</View>
</Section>
);
}
function NumberOfLinesDemo() {
return (
<Section title="10. Number of Lines">
<View style={styles.positionRow}>
<Popable
content="This is a long text that will be truncated with numberOfLines=1 set"
numberOfLines={1}
>
<DemoButton label="1 Line" />
</Popable>
<Popable
content="This text has no line limit and can wrap freely to multiple lines"
>
<DemoButton label="Unlimited" />
</Popable>
</View>
</Section>
);
}
function StrictPositionDemo() {
return (
<Section title="11. Strict Position">
<Text style={styles.note}>
strictPosition=true prevents auto-flip when near screen edges
</Text>
<View style={styles.row}>
<Popable content="Strict top (no auto-flip)" position="top" strictPosition={true}>
<DemoButton label="Strict Top" />
</Popable>
<Popable content="Auto top (may flip to bottom)" position="top" strictPosition={false}>
<DemoButton label="Auto Top" />
</Popable>
</View>
</Section>
);
}
function OnActionDemo() {
const [lastVisible, setLastVisible] = useState<boolean | null>(null);
return (
<Section title="12. onAction Callback">
<View style={styles.row}>
<Popable
content="Watch the status below"
onAction={(visible) => setLastVisible(visible)}
>
<DemoButton label="Toggle" />
</Popable>
</View>
<Text style={styles.resultText}>
Last visible state: {lastVisible === null ? 'none' : lastVisible ? 'true' : 'false'}
</Text>
</Section>
);
}
function StandalonePopoverDemo() {
const [visible, setVisible] = useState(true);
return (
<Section title="13. Standalone Popover">
<View style={styles.row}>
<DemoButton
label={visible ? 'Hide Popover' : 'Show Popover'}
onPress={() => setVisible(!visible)}
/>
</View>
<View style={styles.popoverContainer}>
<Popover
visible={visible}
position="bottom"
backgroundColor="#8E44AD"
>
Standalone purple Popover!
</Popover>
</View>
<View style={styles.popoverContainer}>
<Popover
visible={visible}
position="right"
animationType="spring"
backgroundColor="#2980B9"
>
Spring animation
</Popover>
</View>
</Section>
);
}
function CustomContentDemo() {
return (
<Section title="14. Custom Content (ReactElement)">
<View style={styles.row}>
<Popable
content={
<View style={{ padding: 8 }}>
<Text style={{ color: '#E74C3C', fontWeight: 'bold', fontSize: 14 }}>Custom Title</Text>
<Text style={{ color: '#95A5A6', fontSize: 12 }}>Custom description text</Text>
</View>
}
>
<DemoButton label="Custom Content" />
</Popable>
</View>
</Section>
);
}
function WrapperStyleDemo() {
return (
<Section title="15. Wrapper Style">
<View style={styles.row}>
<Popable
content="With margin and padding"
wrapperStyle={{ marginHorizontal: 20, padding: 10, backgroundColor: '#F0F0F0', borderRadius: 8 }}
>
<DemoButton label="Styled Wrapper" />
</Popable>
</View>
</Section>
);
}
export default function App() {
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scrollContent}>
<Text style={styles.headerTitle}>react-native-popable</Text>
<Text style={styles.headerSubtitle}>HarmonyOS Example</Text>
<BasicPopableDemo />
<PositionDemo />
<LongpressDemo />
<HoverDemo />
<ControlledVisibleDemo />
<UsePopableDemo />
<CustomStyleDemo />
<CaretPositionDemo />
<AnimationDemo />
<NumberOfLinesDemo />
<StrictPositionDemo />
<OnActionDemo />
<StandalonePopoverDemo />
<CustomContentDemo />
<WrapperStyleDemo />
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: COLORS.light,
},
scrollContent: {
paddingVertical: 16,
},
headerTitle: {
fontSize: 24,
fontWeight: 'bold',
color: COLORS.dark,
textAlign: 'center',
marginTop: 8,
},
headerSubtitle: {
fontSize: 14,
color: COLORS.muted,
textAlign: 'center',
marginBottom: 16,
},
section: {
marginHorizontal: 16,
marginBottom: 20,
backgroundColor: COLORS.white,
borderRadius: 12,
padding: 16,
borderWidth: 1,
borderColor: '#E0E0E0',
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: COLORS.dark,
marginBottom: 12,
},
sectionContent: {
alignItems: 'center',
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
},
positionGrid: {
alignItems: 'center',
},
positionRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
marginBottom: 8,
},
demoButton: {
backgroundColor: COLORS.primary,
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 8,
minWidth: 80,
alignItems: 'center',
},
demoButtonText: {
color: COLORS.white,
fontSize: 14,
fontWeight: '600',
},
note: {
fontSize: 12,
color: COLORS.muted,
marginBottom: 8,
textAlign: 'center',
},
resultText: {
fontSize: 13,
color: COLORS.dark,
marginTop: 8,
textAlign: 'center',
},
popoverContainer: {
marginTop: 12,
alignItems: 'center',
},
});