@@ -3,8 +3,12 @@ import { Animated, StyleSheet, View } from 'react-native';
33import type { ColorValue , StyleProp , ViewProps , ViewStyle } from 'react-native' ;
44
55import AppbarContent from './AppbarContent' ;
6- import { AppbarContext } from './AppbarContext' ;
7- import { getAppbarBackgroundColor , modeAppbarHeight } from './utils' ;
6+ import {
7+ getAppbarBackgroundColor ,
8+ modeAppbarHeight ,
9+ renderAppbarContent ,
10+ filterAppbarActions ,
11+ } from './utils' ;
812import type { AppbarModes , AppbarChildProps } from './utils' ;
913import { useInternalTheme } from '../../core/theming' ;
1014import type { Elevation , ThemeProp } from '../../types' ;
@@ -171,60 +175,45 @@ const Appbar = ({
171175
172176 const isDark = typeof dark === 'boolean' ? dark : false ;
173177
178+ const isCenterAlignedMode = isMode ( 'center-aligned' ) ;
179+
180+ let shouldCenterContent = false ;
181+ let shouldAddLeftSpacing = false ;
182+ let shouldAddRightSpacing = false ;
183+ if ( isCenterAlignedMode ) {
184+ let hasAppbarContent = false ;
185+ let leftItemsCount = 0 ;
186+ let rightItemsCount = 0 ;
187+
188+ React . Children . forEach ( children , ( child ) => {
189+ if ( React . isValidElement < AppbarChildProps > ( child ) ) {
190+ const isLeading = child . props . isLeading === true ;
191+
192+ if ( child . type === AppbarContent ) {
193+ hasAppbarContent = true ;
194+ } else if ( isLeading || ! hasAppbarContent ) {
195+ leftItemsCount ++ ;
196+ } else {
197+ rightItemsCount ++ ;
198+ }
199+ }
200+ } ) ;
201+
202+ shouldCenterContent =
203+ hasAppbarContent && leftItemsCount < 2 && rightItemsCount < 3 ;
204+ shouldAddLeftSpacing = shouldCenterContent && leftItemsCount === 0 ;
205+ shouldAddRightSpacing = shouldCenterContent && rightItemsCount === 0 ;
206+ }
207+
208+ const spacingStyle = styles . v3Spacing ;
209+
174210 const insets = {
175211 paddingBottom : safeAreaInsets ?. bottom ,
176212 paddingTop : safeAreaInsets ?. top ,
177213 paddingLeft : safeAreaInsets ?. left ,
178214 paddingRight : safeAreaInsets ?. right ,
179215 } ;
180216
181- const appbarContextValue = React . useMemo (
182- ( ) => ( { isDark, mode } ) ,
183- [ isDark , mode ]
184- ) ;
185-
186- let content : React . ReactNode = children ;
187-
188- if ( isMode ( 'medium' ) || isMode ( 'large' ) ) {
189- // Medium/large top app bars use a two-row layout: a controls row with the
190- // leading and trailing actions above a full-width title row. React Native
191- // flexbox has no `order`, so the title has to be separated from the actions
192- // structurally. We partition the children by element identity and the
193- // `isLeading` prop for layout only — nothing is injected into them; shared
194- // values flow through `AppbarContext`.
195- const items = React . Children . toArray ( children ) . filter (
196- ( child ) : child is React . ReactElement < AppbarChildProps > =>
197- React . isValidElement ( child )
198- ) ;
199- const isAppbarContent = ( child : React . ReactElement < AppbarChildProps > ) => {
200- const { type } = child ;
201- // React.memo(AppbarContent) wraps the component in an object whose
202- // `.type` holds the original component — unwrap it so memoized
203- // Content still lands in the title row.
204- const innerType =
205- typeof type === 'object' && type !== null && 'type' in type
206- ? ( type as { type : unknown } ) . type
207- : type ;
208- return innerType === AppbarContent ;
209- } ;
210- const titleItems = items . filter ( isAppbarContent ) ;
211- const actionItems = items . filter ( ( child ) => ! isAppbarContent ( child ) ) ;
212- const leadingActions = actionItems . filter ( ( child ) => child . props . isLeading ) ;
213- const trailingActions = actionItems . filter (
214- ( child ) => ! child . props . isLeading
215- ) ;
216-
217- content = (
218- < View style = { styles . columnContainer } >
219- < View style = { styles . controlsRow } >
220- { leadingActions }
221- < View style = { styles . rightActionControls } > { trailingActions } </ View >
222- </ View >
223- { titleItems }
224- </ View >
225- ) ;
226- }
227-
228217 return (
229218 < Surface
230219 style = { [
@@ -240,9 +229,77 @@ const Appbar = ({
240229 container
241230 { ...rest }
242231 >
243- < AppbarContext . Provider value = { appbarContextValue } >
244- { content }
245- </ AppbarContext . Provider >
232+ { shouldAddLeftSpacing ? < View style = { spacingStyle } /> : null }
233+ { ( isMode ( 'small' ) || isMode ( 'center-aligned' ) ) && (
234+ < >
235+ { /* Render only the back action at first place */ }
236+ { renderAppbarContent ( {
237+ children,
238+ isDark,
239+ theme,
240+ renderOnly : [ 'Appbar.BackAction' ] ,
241+ shouldCenterContent : isCenterAlignedMode || shouldCenterContent ,
242+ } ) }
243+ { /* Render the rest of the content except the back action */ }
244+ { renderAppbarContent ( {
245+ // Filter appbar actions - first leading icons, then trailing icons
246+ children : [
247+ ...filterAppbarActions ( children , true ) ,
248+ ...filterAppbarActions ( children ) ,
249+ ] ,
250+ isDark,
251+ theme,
252+ renderExcept : [ 'Appbar.BackAction' ] ,
253+ shouldCenterContent : isCenterAlignedMode || shouldCenterContent ,
254+ } ) }
255+ </ >
256+ ) }
257+ { ( isMode ( 'medium' ) || isMode ( 'large' ) ) && (
258+ < View
259+ style = { [
260+ styles . columnContainer ,
261+ isMode ( 'center-aligned' ) && styles . centerAlignedContainer ,
262+ ] }
263+ >
264+ { /* Appbar top row with controls */ }
265+ < View style = { styles . controlsRow } >
266+ { /* Left side of row container, can contain AppbarBackAction or AppbarAction if it's leading icon */ }
267+ { renderAppbarContent ( {
268+ children,
269+ isDark,
270+ renderOnly : [ 'Appbar.BackAction' ] ,
271+ mode,
272+ } ) }
273+ { renderAppbarContent ( {
274+ children : filterAppbarActions ( children , true ) ,
275+ isDark,
276+ renderOnly : [ 'Appbar.Action' ] ,
277+ mode,
278+ } ) }
279+ { /* Right side of row container, can contain other AppbarAction if they are not leading icons */ }
280+ < View style = { styles . rightActionControls } >
281+ { renderAppbarContent ( {
282+ children : filterAppbarActions ( children ) ,
283+ isDark,
284+ renderExcept : [
285+ 'Appbar' ,
286+ 'Appbar.BackAction' ,
287+ 'Appbar.Content' ,
288+ 'Appbar.Header' ,
289+ ] ,
290+ mode,
291+ } ) }
292+ </ View >
293+ </ View >
294+ { renderAppbarContent ( {
295+ children,
296+ isDark,
297+ renderOnly : [ 'Appbar.Content' ] ,
298+ mode,
299+ } ) }
300+ </ View >
301+ ) }
302+ { shouldAddRightSpacing ? < View style = { spacingStyle } /> : null }
246303 </ Surface >
247304 ) ;
248305} ;
@@ -253,6 +310,9 @@ const styles = StyleSheet.create({
253310 alignItems : 'center' ,
254311 paddingHorizontal : 4 ,
255312 } ,
313+ v3Spacing : {
314+ width : 52 ,
315+ } ,
256316 controlsRow : {
257317 flex : 1 ,
258318 flexDirection : 'row' ,
@@ -269,6 +329,9 @@ const styles = StyleSheet.create({
269329 flex : 1 ,
270330 paddingTop : 8 ,
271331 } ,
332+ centerAlignedContainer : {
333+ paddingTop : 0 ,
334+ } ,
272335} ) ;
273336
274337export default Appbar ;
0 commit comments