feat : BottomBar + Base component + Clean Nativewind

This commit is contained in:
Yaël Perret
2026-09-08 19:50:28 +02:00
parent 51a522eafc
commit 8ed48a6cd3
16 changed files with 564 additions and 722 deletions

View File

@@ -13,17 +13,15 @@
* Dépendances : expo-blur · expo-linear-gradient ·
* react-native-safe-area-context · react-native-svg
* ------------------------------------------------------------------ */
import { BlurView } from 'expo-blur';
import { LinearGradient } from 'expo-linear-gradient';
import { Tabs } from 'expo-router';
import { useCallback, type ComponentProps } from 'react';
import {
Platform,
Pressable,
StyleSheet,
Text,
View,
type ViewStyle,
type ViewStyle
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -33,13 +31,9 @@ import { Plus } from 'lucide-react-native';
/** Hauteur de la barre hors safe area. La hauteur réelle y ajoute l'inset bas. */
export const TAB_BAR_BASE_HEIGHT = 52;
const FAB_SIZE = 62;
/**
* expo-blur ne rend rien d'exploitable sous Android 9 : sans ce repli on
* obtient un rectangle transparent et des libellés illisibles.
*/
const SUPPORTS_BLUR = Platform.OS === 'ios' || Number(Platform.Version) >= 28;
const FAB_SIZE = 72;
const FAB_RADIUS = 24;
const FAB_LIFT = 40;
type ExpoRouterTabBarProps = Parameters<
NonNullable<ComponentProps<typeof Tabs>['tabBar']>
@@ -88,18 +82,13 @@ export function BahlaTabBar({
);
return (
<BlurView
tint="dark"
intensity={SUPPORTS_BLUR ? 20 : 0}
<View
style={[
styles.bar,
{
height: TAB_BAR_BASE_HEIGHT + insets.bottom,
paddingBottom: insets.bottom,
// Sans flou, le fond translucide ne suffit pas : on opacifie.
backgroundColor: SUPPORTS_BLUR
? colors.deepBackground
: 'rgba(10,10,18,0.96)',
backgroundColor: colors.deepBackground,
},
]}
>
@@ -112,24 +101,28 @@ export function BahlaTabBar({
if (fabRouteName && route.name === fabRouteName) {
return (
<View key={route.key} style={styles.fabSlot}>
<View style={styles.fabShell}>
<Pressable
accessibilityRole="button"
accessibilityLabel={fabAccessibilityLabel}
onPress={onFabPress}
style={({ pressed }) => [
styles.fab,
pressed && styles.fabPressed,
]}
style={styles.fabPressable}
>
<LinearGradient
colors={gradients.signature}
start={gradientDirection.start}
end={gradientDirection.end}
style={styles.fabGradient}
>
<Plus size={28} color={colors.onPrimary} />
</LinearGradient>
{({ pressed }) => (
<LinearGradient
colors={gradients.signature}
start={gradientDirection.start}
end={gradientDirection.end}
style={[
styles.fabGradient,
pressed && styles.fabGradientPressed,
]}
>
<Plus size={28} color={colors.onPrimary} />
</LinearGradient>
)}
</Pressable>
</View>
</View>
);
}
@@ -181,7 +174,7 @@ export function BahlaTabBar({
</Pressable>
);
})}
</BlurView>
</View>
);
}
@@ -237,41 +230,51 @@ const styles = StyleSheet.create({
/* Le créneau central est plus étroit que les onglets : 84 px fixes,
comme la colonne du même nom dans la maquette. */
fabSlot: {
width: 84,
width: 96,
alignItems: 'center',
overflow: 'visible',
zIndex: 20,
},
fab: {
fabShell: {
width: FAB_SIZE,
height: FAB_SIZE,
aspectRatio: 1,
borderRadius: 15,
borderRadius: FAB_RADIUS,
// Remonte au-dessus de la barre et découpe un anneau dans le fond.
marginTop: -30,
marginTop: -FAB_LIFT,
overflow: 'hidden',
alignSelf: 'center',
zIndex: 20,
borderWidth: 3,
borderColor: colors.deepBackground,
shadowColor: colors.primary,
shadowOffset: { width: 0, height: 14 },
shadowOpacity: 0.85,
shadowRadius: 17,
elevation: 12,
shadowOffset: { width: 0, height: 16 },
shadowOpacity: 0.9,
shadowRadius: 20,
elevation: 18,
},
fabPressed: {
transform: [{ scale: 0.94 }],
shadowOpacity: 0.5,
fabPressable: {
width: '100%',
height: '100%',
borderRadius: FAB_RADIUS,
overflow: 'hidden',
},
fabGradient: {
width: '100%',
height: '100%',
aspectRatio: 1,
borderRadius: 15,
borderRadius: FAB_RADIUS,
alignItems: 'center',
justifyContent: 'center',
},
fabGradientPressed: {
transform: [{ scale: 0.96 }],
opacity: 0.92,
},
});
export default BahlaTabBar;