diff --git a/example/package.json b/example/package.json
index 527642c..556830a 100644
--- a/example/package.json
+++ b/example/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"expo": "~54.0.32",
+ "expo-image-picker": "~17.0.0",
"expo-status-bar": "~3.0.9",
"react": "19.1.0",
"react-native": "0.81.5"
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 7fae6d5..d834f99 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,79 +1,29 @@
import { useEffect, useState } from 'react';
import {
BackHandler,
- Image,
PlatformColor,
Pressable,
ScrollView,
StatusBar,
StyleSheet,
Text,
- View,
- type ImageSourcePropType,
} from 'react-native';
-import {
- Palette,
- type PaletteResult,
- type PaletteTarget,
-} from 'react-native-material-palette';
-import Demo from './Demo';
-
-const PALETTE_TYPES: (keyof PaletteResult)[] = [
- 'vibrant',
- 'lightVibrant',
- 'darkVibrant',
- 'muted',
- 'lightMuted',
- 'darkMuted',
-];
-
-const IMAGES = [
- {
- source: require('../assets/images/ishan-seefromthesky.jpg'),
- author: 'Ishan @seefromthesky',
- },
- {
- source: require('../assets/images/mohamed-sameeh.jpg'),
- author: 'Mohamed Sameeh',
- },
- {
- source: require('../assets/images/andrew-pons.jpg'),
- author: 'Andrew Pons',
- },
- {
- source: require('../assets/images/luke-mummert.jpg'),
- author: 'Luke Mummert',
- },
- {
- source: require('../assets/images/paolo-nicolello.jpg'),
- author: 'Paolo Nicolello',
- },
-];
-
-const LIGHT_BACKGROUND_TARGET: PaletteTarget = {
- targetLightness: 0.75,
- minimumLightness: 0.65,
- maximumLightness: 1.0,
- targetSaturation: 0.1,
- minimumSaturation: 0.0,
- maximumSaturation: 0.6,
- lightnessWeight: 0.5,
- saturationWeight: 0.1,
- populationWeight: 0.5,
- exclusive: false,
-};
+import CustomImage from './screens/CustomImage';
+import Demo from './screens/Demo';
+import { COLORS, SPACING } from './constants';
+import { IMAGES } from './data';
+import PaletteExampleItem from './components/PaletteExampleItem';
const ROUNDNESS = 10;
-const SPACING = 8;
export default function App() {
- const [screen, setScreen] = useState<'home' | 'demo'>('home');
+ const [screen, setScreen] = useState<'home' | 'demo' | 'custom'>('home');
useEffect(() => {
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
() => {
- if (screen === 'demo') {
+ if (screen === 'demo' || screen === 'custom') {
setScreen('home');
return true;
}
@@ -89,78 +39,43 @@ export default function App() {
return ;
}
+ if (screen === 'custom') {
+ return ;
+ }
+
return (
{IMAGES.map((img, index) => (
-
+
))}
setScreen('demo')}>
Go to Demo
+ setScreen('custom')}>
+ Build Demo from URL / gallery
+
);
}
-function ExampleItem({
- image,
- author,
-}: {
- image: ImageSourcePropType;
- author: string;
-}) {
- return (
-
-
-
-
-
- {PALETTE_TYPES.map((type) => (
-
-
-
- {type}
-
-
- subtitle
-
-
-
- ))}
-
-
- Credits: {author}
-
- );
-}
-
const styles = StyleSheet.create({
container: {
- backgroundColor: '#fff',
+ backgroundColor: COLORS.background,
},
content: {
- padding: SPACING * 2,
- paddingTop: SPACING * 2 + (StatusBar.currentHeight ?? 0),
- paddingBottom: SPACING * 2 + 10,
- gap: SPACING * 2,
+ padding: SPACING * 4,
+ paddingTop: SPACING * 4 + (StatusBar.currentHeight ?? 0),
+ paddingBottom: SPACING * 4 + 10,
+ gap: SPACING * 4,
},
item: {
width: '100%',
- padding: SPACING,
- borderRadius: ROUNDNESS + SPACING,
+ padding: SPACING * 2,
+ borderRadius: ROUNDNESS + SPACING * 2,
overflow: 'hidden',
},
underlay: {
@@ -176,7 +91,7 @@ const styles = StyleSheet.create({
height: null,
width: null,
borderRadius: ROUNDNESS,
- margin: SPACING / 2,
+ margin: SPACING,
},
palettes: {
flex: 1,
@@ -186,11 +101,11 @@ const styles = StyleSheet.create({
},
palette: {
width: '50%',
- padding: SPACING / 2,
+ padding: SPACING,
},
color: {
alignItems: 'center',
- padding: SPACING,
+ padding: SPACING * 2,
borderRadius: ROUNDNESS,
overflow: 'hidden',
},
@@ -201,15 +116,14 @@ const styles = StyleSheet.create({
fontSize: 14,
},
author: {
- margin: SPACING / 2,
+ margin: SPACING,
fontStyle: 'italic',
},
button: {
- marginVertical: SPACING,
backgroundColor: PlatformColor('@android:color/system_accent1_50'),
- paddingVertical: SPACING,
- paddingHorizontal: SPACING * 2,
- borderRadius: ROUNDNESS + SPACING,
+ paddingVertical: SPACING * 2,
+ paddingHorizontal: SPACING * 4,
+ borderRadius: ROUNDNESS + SPACING * 2,
},
buttonLabel: {
textAlign: 'center',
diff --git a/example/src/components/PaletteExampleItem.tsx b/example/src/components/PaletteExampleItem.tsx
new file mode 100644
index 0000000..ff545ab
--- /dev/null
+++ b/example/src/components/PaletteExampleItem.tsx
@@ -0,0 +1,108 @@
+import {
+ Image,
+ StyleSheet,
+ View,
+ type ImageSourcePropType,
+} from 'react-native';
+import { Palette } from 'react-native-material-palette';
+import { PALETTE_TYPES, LIGHT_BACKGROUND_TARGET, SPACING } from '../constants';
+
+const ROUNDNESS = 10;
+
+export default function PaleteExampleItem({
+ image,
+ author,
+}: {
+ image: ImageSourcePropType;
+ author?: string;
+}) {
+ return (
+
+
+
+
+
+ {PALETTE_TYPES.map((type) => (
+
+
+
+ {type}
+
+
+ subtitle
+
+
+
+ ))}
+
+
+ {author ? (
+ Credits: {author}
+ ) : null}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ item: {
+ width: '100%',
+ padding: SPACING * 2,
+ borderRadius: ROUNDNESS + SPACING * 2,
+ overflow: 'hidden',
+ },
+ underlay: {
+ ...StyleSheet.absoluteFill,
+ backgroundColor: '#e0e0e0',
+ mixBlendMode: 'luminosity',
+ },
+ row: {
+ flexDirection: 'row',
+ },
+ image: {
+ flexGrow: 1,
+ height: null,
+ width: null,
+ borderRadius: ROUNDNESS,
+ margin: SPACING,
+ },
+ palettes: {
+ flex: 1,
+ minWidth: 100,
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ palette: {
+ width: '50%',
+ padding: SPACING,
+ },
+ color: {
+ alignItems: 'center',
+ padding: SPACING * 2,
+ borderRadius: ROUNDNESS,
+ overflow: 'hidden',
+ },
+ title: {
+ fontWeight: 'bold',
+ },
+ subtitle: {
+ fontSize: 14,
+ },
+ author: {
+ margin: SPACING,
+ fontStyle: 'italic',
+ },
+});
diff --git a/example/src/constants.ts b/example/src/constants.ts
new file mode 100644
index 0000000..39d25a9
--- /dev/null
+++ b/example/src/constants.ts
@@ -0,0 +1,63 @@
+import {
+ type PaletteResult,
+ type PaletteTarget,
+} from 'react-native-material-palette';
+
+export const SPACING = 4;
+export const PADDING = SPACING * 5;
+export const GAP = SPACING * 3;
+export const ROUNDNESS = SPACING * 4;
+
+export const HERO_IMAGE_HEIGHT = SPACING * 55;
+
+export const STORY_IMAGE_WIDTH = SPACING * 28;
+export const STORY_IMAGE_HEIGHT = SPACING * 33;
+
+export const DOT_SIZE = SPACING * 1.5;
+export const DISCOVER_CARD_WIDTH_RATIO = 0.38;
+
+export const COLORS = {
+ background: '#FAFAFA',
+ text: '#1A1A1A',
+ subtitle: '#666',
+ fallbackDark: '#2A2A2A',
+ fallbackDarkTitle: '#FFFFFF',
+ fallbackDarkBody: '#CCCCCC',
+ fallbackLight: '#F0F0F0',
+ fallbackLightTitle: '#1A1A1A',
+ fallbackLightBody: '#555555',
+};
+
+export const FALLBACK_DARK = {
+ color: COLORS.fallbackDark,
+ titleTextColor: COLORS.fallbackDarkTitle,
+ bodyTextColor: COLORS.fallbackDarkBody,
+};
+
+export const FALLBACK_LIGHT = {
+ color: COLORS.fallbackLight,
+ titleTextColor: COLORS.fallbackLightTitle,
+ bodyTextColor: COLORS.fallbackLightBody,
+};
+
+export const PALETTE_TYPES: (keyof PaletteResult)[] = [
+ 'vibrant',
+ 'lightVibrant',
+ 'darkVibrant',
+ 'muted',
+ 'lightMuted',
+ 'darkMuted',
+];
+
+export const LIGHT_BACKGROUND_TARGET: PaletteTarget = {
+ targetLightness: 0.75,
+ minimumLightness: 0.65,
+ maximumLightness: 1.0,
+ targetSaturation: 0.1,
+ minimumSaturation: 0.0,
+ maximumSaturation: 0.6,
+ lightnessWeight: 0.5,
+ saturationWeight: 0.1,
+ populationWeight: 0.5,
+ exclusive: false,
+};
diff --git a/example/src/data.ts b/example/src/data.ts
new file mode 100644
index 0000000..ac628a8
--- /dev/null
+++ b/example/src/data.ts
@@ -0,0 +1,101 @@
+import type { DiscoverItem, HeroContent, StoryItem } from './types';
+
+// App
+export const IMAGES = [
+ {
+ source: require('../assets/images/ishan-seefromthesky.jpg'),
+ author: 'Ishan @seefromthesky',
+ },
+ {
+ source: require('../assets/images/mohamed-sameeh.jpg'),
+ author: 'Mohamed Sameeh',
+ },
+ {
+ source: require('../assets/images/andrew-pons.jpg'),
+ author: 'Andrew Pons',
+ },
+ {
+ source: require('../assets/images/luke-mummert.jpg'),
+ author: 'Luke Mummert',
+ },
+ {
+ source: require('../assets/images/paolo-nicolello.jpg'),
+ author: 'Paolo Nicolello',
+ },
+];
+
+// Demo screen
+export const HERO_ITEM: HeroContent = {
+ source: require('../assets/images/ishan-seefromthesky.jpg'),
+ title: 'Crystal Waters',
+ subtitle:
+ 'Discover hidden paradise islands with pristine beaches and vibrant turquoise waters',
+ badgeText: 'Featured',
+};
+
+export const DISCOVER_ITEMS: DiscoverItem[] = [
+ {
+ source: require('../assets/images/mohamed-sameeh.jpg'),
+ title: 'Festival Lights',
+ location: 'Marrakech',
+ },
+ {
+ source: require('../assets/images/andrew-pons.jpg'),
+ title: 'Red Canyons',
+ location: 'Utah',
+ },
+ {
+ source: require('../assets/images/luke-mummert.jpg'),
+ title: 'Golden Gate',
+ location: 'San Francisco',
+ },
+ {
+ source: require('../assets/images/paolo-nicolello.jpg'),
+ title: 'Hidden Falls',
+ location: 'Iceland',
+ },
+];
+
+export const STORY_ITEMS: StoryItem[] = [
+ {
+ source: require('../assets/images/luke-mummert.jpg'),
+ title: 'Crossing the Golden Gate',
+ excerpt:
+ 'A golden hour walk across the iconic bridge with the city skyline in the distance.',
+ meta: '8 min · Sarah Kim',
+ },
+ {
+ source: require('../assets/images/andrew-pons.jpg'),
+ title: 'Above the Red Canyons',
+ excerpt:
+ 'Aerial views reveal sculpted sandstone labyrinths hidden beneath the clouds.',
+ meta: '5 min · James Chen',
+ },
+ {
+ source: require('../assets/images/paolo-nicolello.jpg'),
+ title: 'Chasing Waterfalls in Iceland',
+ excerpt:
+ 'Moss-covered gorges, turquoise pools, and cascades carved into ancient rock.',
+ meta: '6 min · Elena Rossi',
+ },
+];
+
+// Custom image screen
+export const CUSTOM_HERO_ITEM = {
+ title: 'Lorem ipsum dolor sit amet',
+ subtitle:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ badgeText: 'Lorem',
+};
+
+export const STORY_ITEM = {
+ title: 'Lorem ipsum dolor',
+ excerpt:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
+ meta: 'Lorem · Ipsum',
+};
+
+export const DISCOVER_ITEM = {
+ title: 'Lorem ipsum',
+ location: 'Dolor sit amet',
+};
diff --git a/example/src/screens/CustomImage.tsx b/example/src/screens/CustomImage.tsx
new file mode 100644
index 0000000..4328738
--- /dev/null
+++ b/example/src/screens/CustomImage.tsx
@@ -0,0 +1,252 @@
+import * as ImagePicker from 'expo-image-picker';
+import {
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+ type ComponentRef,
+} from 'react';
+import {
+ Alert,
+ Image,
+ Keyboard,
+ PlatformColor,
+ Pressable,
+ ScrollView,
+ StatusBar,
+ StyleSheet,
+ Text,
+ TextInput,
+ View,
+ type LayoutChangeEvent,
+ type ImageSourcePropType,
+} from 'react-native';
+import Demo from './Demo';
+import { CUSTOM_HERO_ITEM, DISCOVER_ITEM, STORY_ITEM } from '../data';
+import { PADDING, SPACING, GAP, ROUNDNESS, COLORS } from '../constants';
+import PaletteExampleItem from '../components/PaletteExampleItem';
+import type { CustomDemoProps } from '../types';
+
+export default function CustomImage() {
+ const [imageSource, setImageSource] = useState(
+ null
+ );
+ const [urlInput, setUrlInput] = useState('');
+ const [isLoadingImage, setIsLoadingImage] = useState(false);
+ const [demoOffsetY, setDemoOffsetY] = useState(null);
+ const scrollViewRef = useRef>(null);
+
+ useEffect(() => {
+ if (!imageSource || demoOffsetY === null) return;
+
+ requestAnimationFrame(() => {
+ scrollViewRef.current?.scrollTo({
+ y: Math.max(demoOffsetY, 0),
+ animated: true,
+ });
+ });
+ }, [demoOffsetY, imageSource]);
+
+ const loadImageFromUri = useCallback((uri: string, errorMessage: string) => {
+ setIsLoadingImage(true);
+ Image.getSize(
+ uri,
+ () => {
+ setImageSource({ uri });
+ setIsLoadingImage(false);
+ },
+ () => {
+ setIsLoadingImage(false);
+ Alert.alert('Image error', errorMessage);
+ }
+ );
+ }, []);
+
+ const pickFromGallery = useCallback(async () => {
+ try {
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ['images'],
+ allowsEditing: false,
+ quality: 1,
+ });
+ if (!result.canceled && result.assets[0]?.uri) {
+ loadImageFromUri(
+ result.assets[0].uri,
+ 'Selected file could not be loaded as an image.'
+ );
+ }
+ } catch {
+ Alert.alert('Image error', 'Could not open image picker.');
+ }
+ }, [loadImageFromUri]);
+
+ const loadFromUrl = useCallback(() => {
+ Keyboard.dismiss();
+ const trimmed = urlInput.trim();
+ if (!trimmed) return;
+
+ try {
+ const parsed = new URL(trimmed);
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
+ Alert.alert(
+ 'Invalid URL',
+ 'Please provide an HTTP or HTTPS image URL.'
+ );
+ return;
+ }
+ loadImageFromUri(
+ parsed.toString(),
+ 'Could not load image from this URL.'
+ );
+ } catch {
+ Alert.alert('Invalid URL', 'Please provide a valid image URL.');
+ }
+ }, [loadImageFromUri, urlInput]);
+
+ const handleDemoLayout = useCallback((event: LayoutChangeEvent) => {
+ setDemoOffsetY(event.nativeEvent.layout.y);
+ }, []);
+
+ const loadFromUrlDisabled = !urlInput.trim() || isLoadingImage;
+
+ return (
+
+
+
+ Custom image
+
+ Load from URL or gallery. Demo preview appears after image is loaded.
+
+
+ Image URL
+
+
+ Load from URL
+
+
+
+ Pick from gallery
+
+
+ {imageSource ? (
+
+ ) : null}
+
+
+ );
+}
+
+function CustomDemo({ imageSource, onLayout }: CustomDemoProps) {
+ const demoContent = {
+ hero: { ...CUSTOM_HERO_ITEM, source: imageSource },
+ discover: { ...DISCOVER_ITEM, source: imageSource },
+ stories: { ...STORY_ITEM, source: imageSource },
+ };
+
+ return (
+
+
+ Palette
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ screen: {
+ flex: 1,
+ backgroundColor: COLORS.background,
+ },
+ scrollContent: {
+ paddingTop: (StatusBar.currentHeight ?? 0) + PADDING,
+ paddingBottom: PADDING * 2,
+ },
+ paletteContainer: {
+ padding: SPACING * 4,
+ },
+ header: {
+ fontSize: 34,
+ fontWeight: '800',
+ color: COLORS.text,
+ marginHorizontal: PADDING,
+ marginBottom: SPACING * 3,
+ },
+ subtitle: {
+ fontSize: 14,
+ color: COLORS.subtitle,
+ marginHorizontal: PADDING,
+ marginBottom: PADDING,
+ },
+ controlsCard: {
+ marginHorizontal: PADDING,
+ borderRadius: ROUNDNESS,
+ padding: GAP,
+ gap: SPACING,
+ backgroundColor: COLORS.background,
+ zIndex: 2,
+ elevation: 1,
+ },
+ label: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: COLORS.text,
+ marginBottom: SPACING / 2,
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#ddd',
+ borderRadius: ROUNDNESS,
+ paddingHorizontal: GAP,
+ paddingVertical: SPACING * 2,
+ fontSize: 14,
+ color: COLORS.text,
+ marginBottom: SPACING * 2,
+ backgroundColor: COLORS.background,
+ },
+ button: {
+ marginVertical: SPACING / 2,
+ backgroundColor: PlatformColor('@android:color/system_accent1_50'),
+ paddingVertical: SPACING * 2,
+ paddingHorizontal: GAP,
+ borderRadius: ROUNDNESS + SPACING,
+ alignItems: 'center',
+ },
+ buttonDisabled: {
+ opacity: 0.5,
+ },
+ buttonLabel: {
+ textAlign: 'center',
+ color: PlatformColor('@android:color/system_accent1_700'),
+ fontSize: 14,
+ fontWeight: '600',
+ },
+});
diff --git a/example/src/Demo.tsx b/example/src/screens/Demo.tsx
similarity index 64%
rename from example/src/Demo.tsx
rename to example/src/screens/Demo.tsx
index a0707d7..d6e6957 100644
--- a/example/src/Demo.tsx
+++ b/example/src/screens/Demo.tsx
@@ -6,104 +6,46 @@ import {
Text,
View,
useWindowDimensions,
- type ImageSourcePropType,
} from 'react-native';
import { Palette } from 'react-native-material-palette';
+import { HERO_ITEM, DISCOVER_ITEMS, STORY_ITEMS } from '../data';
+import type { DemoProps, DiscoverItem, HeroContent, StoryItem } from '../types';
+import {
+ PADDING,
+ SPACING,
+ GAP,
+ ROUNDNESS,
+ HERO_IMAGE_HEIGHT,
+ STORY_IMAGE_WIDTH,
+ STORY_IMAGE_HEIGHT,
+ DOT_SIZE,
+ DISCOVER_CARD_WIDTH_RATIO,
+ COLORS,
+ FALLBACK_DARK,
+ FALLBACK_LIGHT,
+} from '../constants';
-const DISCOVER_CARD_WIDTH_RATIO = 0.38;
-
-const COLORS = {
- background: '#FAFAFA',
- text: '#1A1A1A',
- fallbackDark: '#2A2A2A',
- fallbackDarkTitle: '#FFFFFF',
- fallbackDarkBody: '#CCCCCC',
- fallbackLight: '#F0F0F0',
- fallbackLightTitle: '#1A1A1A',
- fallbackLightBody: '#555555',
-};
-
-const SPACING = 4;
-const PADDING = SPACING * 5;
-const GAP = SPACING * 3;
-const ROUNDNESS = SPACING * 4;
-const HERO_IMAGE_HEIGHT = SPACING * 55;
-const STORY_IMAGE_WIDTH = SPACING * 28;
-const STORY_IMAGE_HEIGHT = SPACING * 33;
-const DOT_SIZE = SPACING * 1.5;
-
-const FALLBACK_DARK = {
- color: COLORS.fallbackDark,
- titleTextColor: COLORS.fallbackDarkTitle,
- bodyTextColor: COLORS.fallbackDarkBody,
-};
-
-const FALLBACK_LIGHT = {
- color: COLORS.fallbackLight,
- titleTextColor: COLORS.fallbackLightTitle,
- bodyTextColor: COLORS.fallbackLightBody,
-};
-
-const HERO_IMAGE = require('../assets/images/ishan-seefromthesky.jpg');
-
-const DISCOVER_ITEMS = [
- {
- source: require('../assets/images/mohamed-sameeh.jpg'),
- title: 'Festival Lights',
- location: 'Marrakech',
- },
- {
- source: require('../assets/images/andrew-pons.jpg'),
- title: 'Red Canyons',
- location: 'Utah',
- },
- {
- source: require('../assets/images/luke-mummert.jpg'),
- title: 'Golden Gate',
- location: 'San Francisco',
- },
- {
- source: require('../assets/images/paolo-nicolello.jpg'),
- title: 'Hidden Falls',
- location: 'Iceland',
- },
-];
+const toArray = (value: T | T[]) =>
+ Array.isArray(value) ? value : [value];
-const STORY_ITEMS = [
- {
- source: require('../assets/images/luke-mummert.jpg'),
- title: 'Crossing the Golden Gate',
- excerpt:
- 'A golden hour walk across the iconic bridge with the city skyline in the distance.',
- meta: '8 min · Sarah Kim',
- },
- {
- source: require('../assets/images/andrew-pons.jpg'),
- title: 'Above the Red Canyons',
- excerpt:
- 'Aerial views reveal sculpted sandstone labyrinths hidden beneath the clouds.',
- meta: '5 min · James Chen',
- },
- {
- source: require('../assets/images/paolo-nicolello.jpg'),
- title: 'Chasing Waterfalls in Iceland',
- excerpt:
- 'Moss-covered gorges, turquoise pools, and cascades carved into ancient rock.',
- meta: '6 min · Elena Rossi',
- },
-];
+export default function Demo({ sourceOverride, content }: DemoProps) {
+ const {
+ hero = HERO_ITEM,
+ discover = DISCOVER_ITEMS,
+ stories = STORY_ITEMS,
+ } = content ?? {};
-export default function Demo() {
return (
Explore
-
-
+
Discover
- {DISCOVER_ITEMS.map((item, i) => (
-
+ {toArray(discover).map((item, i) => (
+
))}
Stories
- {STORY_ITEMS.map((item, i) => (
-
+ {toArray(stories).map((item, i) => (
+
))}
);
}
-function HeroCard() {
+function HeroCard({ source, title, subtitle, badgeText }: HeroContent) {
return (
-
+
- Featured
+ {badgeText}
- Crystal Waters
+ {title}
- Discover hidden paradise islands with pristine beaches and vibrant
- turquoise waters
+ {subtitle}
);
}
-function DiscoverCard({
- source,
- title,
- location,
-}: {
- source: ImageSourcePropType;
- title: string;
- location: string;
-}) {
+function DiscoverCard({ source, title, location }: DiscoverItem) {
const { width } = useWindowDimensions();
const cardWidth = width * DISCOVER_CARD_WIDTH_RATIO;
@@ -193,17 +137,7 @@ function DiscoverCard({
);
}
-function StoryCard({
- source,
- title,
- excerpt,
- meta,
-}: {
- source: ImageSourcePropType;
- title: string;
- excerpt: string;
- meta: string;
-}) {
+function StoryCard({ source, title, excerpt, meta }: StoryItem) {
return (
void;
+};
diff --git a/yarn.lock b/yarn.lock
index 622d9be..34bfe1b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6131,6 +6131,26 @@ __metadata:
languageName: node
linkType: hard
+"expo-image-loader@npm:~6.0.0":
+ version: 6.0.0
+ resolution: "expo-image-loader@npm:6.0.0"
+ peerDependencies:
+ expo: "*"
+ checksum: 10c0/36f1e80d2b3d2a1cce440c7d938cd3cf3138f2b1fa706a1954e415bfae87b5b8eeaec65be0ab17880eb7585dd28c9225d056ebdb98ac81e0ffbfe880d952b0eb
+ languageName: node
+ linkType: hard
+
+"expo-image-picker@npm:~17.0.0":
+ version: 17.0.10
+ resolution: "expo-image-picker@npm:17.0.10"
+ dependencies:
+ expo-image-loader: "npm:~6.0.0"
+ peerDependencies:
+ expo: "*"
+ checksum: 10c0/d05ed6ce53463acc86af062df33a648aaafb27549f875ebf907f0f438c09727ae9b75fb8c333fdcaceb0249d9de16929d2cd65ba81f256403745a30af9a7f8c3
+ languageName: node
+ linkType: hard
+
"expo-json-utils@npm:~0.15.0":
version: 0.15.0
resolution: "expo-json-utils@npm:0.15.0"
@@ -10120,6 +10140,7 @@ __metadata:
dependencies:
expo: "npm:~54.0.32"
expo-dev-client: "npm:~6.0.20"
+ expo-image-picker: "npm:~17.0.0"
expo-status-bar: "npm:~3.0.9"
react: "npm:19.1.0"
react-native: "npm:0.81.5"