From 4a8e6daa643d9542331dbca2669a38b562991784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kata?= Date: Fri, 11 Sep 2026 08:24:36 +0200 Subject: [PATCH 1/3] feat(appbar): squash-rebase onto latest main Squash all appbar-improvements commits and replay them on top of latest main to avoid replaying 37 commits individually through main's unrelated history (notably the v2->v3 Appbar rewrite mid-branch, which produced large, non-semantic conflicts when rebased commit-by-commit). Also adapts the new Appbar/AppbarContent code to the testID conventions established by #5099 (remove derived test IDs for internal, implementation-only elements): drops the `${testID}-title-text`/`${testID}-headline-text`/`${testID}-subtitle-text` derived test IDs on non-interactive text, updating the corresponding tests to query by text content instead, and updates the migration guide's "Test IDs" section accordingly. Co-Authored-By: Claude Sonnet 5 --- docs/6.x/docs/guides/migration.md | 52 +- docs/6.x/docs/guides/react-navigation.md | 65 +- .../guides/theming-with-react-navigation.md | 23 +- docs/component-docs.config.ts | 28 +- .../screenshots/appbar-v6-large-flexible.png | Bin 0 -> 92306 bytes .../screenshots/appbar-v6-medium-flexible.png | Bin 0 -> 86237 bytes docs/public/screenshots/appbar-v6-search.png | Bin 0 -> 72559 bytes docs/public/screenshots/appbar-v6-small.png | Bin 0 -> 83054 bytes docs/src/data/screenshots.ts | 16 +- docs/src/data/themeColors.ts | 23 +- docs/src/data/versionRouteFallbacks.json | 6 + example/src/Examples/AppbarExample.tsx | 451 +++- .../src/Examples/BottomNavigationExample.tsx | 86 +- example/src/Examples/MenuExample.tsx | 49 +- example/src/Examples/TeamDetails.tsx | 13 +- example/src/Examples/TooltipExample.tsx | 53 +- example/src/RootNavigator.tsx | 26 +- src/components/Appbar/Appbar.tsx | 649 +++--- src/components/Appbar/AppbarAction.tsx | 114 - src/components/Appbar/AppbarBackAction.tsx | 77 - src/components/Appbar/AppbarButton.tsx | 68 + src/components/Appbar/AppbarContent.tsx | 323 +-- src/components/Appbar/AppbarHeader.tsx | 146 -- src/components/Appbar/index.ts | 39 +- src/components/Appbar/types.ts | 238 ++ src/components/Appbar/utils.ts | 194 +- src/components/Card/CardActions.tsx | 10 +- .../__tests__/Appbar/Appbar.test.tsx | 973 ++++++-- .../Appbar/__snapshots__/Appbar.test.tsx.snap | 1998 ----------------- src/components/__tests__/Appbar/utils.test.ts | 54 + src/index.tsx | 20 +- 31 files changed, 2356 insertions(+), 3438 deletions(-) create mode 100644 docs/public/screenshots/appbar-v6-large-flexible.png create mode 100644 docs/public/screenshots/appbar-v6-medium-flexible.png create mode 100644 docs/public/screenshots/appbar-v6-search.png create mode 100644 docs/public/screenshots/appbar-v6-small.png delete mode 100644 src/components/Appbar/AppbarAction.tsx delete mode 100644 src/components/Appbar/AppbarBackAction.tsx create mode 100644 src/components/Appbar/AppbarButton.tsx delete mode 100644 src/components/Appbar/AppbarHeader.tsx create mode 100644 src/components/Appbar/types.ts delete mode 100644 src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap create mode 100644 src/components/__tests__/Appbar/utils.test.ts diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index e51de006aa..7ccb666525 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -10,7 +10,7 @@ React Native Paper 6 uses [Reanimated](https://docs.swmansion.com/react-native-r The following props now accept animated styles returned from `useAnimatedStyle`. They no longer accept `Animated.Value` or `Animated.AnimatedInterpolation` where these were previously supported: -- `Appbar.Action` and `Appbar.BackAction`: `style` +- `Appbar`: `style` - `Badge`: `style` - `Banner`: `style` - `Button`: `style` @@ -72,8 +72,7 @@ You can use the component's color prop where available, or override the correspo Hardcoded default test IDs have been removed for the components listed below: -- `Appbar.Content`: `appbar-content` -- `Appbar.Header`: `appbar-header` +- `Appbar`: `${testID}-root-layer` - `BottomNavigation`: `bottom-navigation` - `BottomNavigation.Bar`: `bottom-navigation-bar` - `Button`: `button` @@ -110,9 +109,52 @@ Some components now accept explicit `testID` props for their interactable elemen ### Appbar -The `style` props for `Appbar` and `Appbar.Header` no longer accept `Animated.Value` or `Animated.AnimatedInterpolation`. They only accept static styles. +The Paper 6.x `Appbar` is a big refatctor, which drops the previously used compound component approach. -The `style.elevation` property is no longer supported. Use the `elevated` prop to control Appbar elevation. +#### Migrating from the compound API + +`Appbar.Header`, `Appbar.Content`, `Appbar.Action`, and `Appbar.BackAction` have been removed. Render one `Appbar` and provide its headline, leading button, and trailing actions as props. The former `mode="medium"` and `mode="large"` values are now `variant="medium-flexible"` and `variant="large-flexible"`; centered content uses `headlineAlignment="center"`. + +```tsx +// Before (v5) + +const MyComponent = () => ( + + {}} /> + + {}} /> + {}} /> + +); + +// After (v6) + +const MyComponent = () => ( + {} }} + trailingActions={[ + { + key: 'search', + icon: 'magnify', + 'aria-label': 'Search', + onPress: () => {}, + }, + { + key: 'more', + icon: 'dots-vertical', + 'aria-label': 'More options', + onPress: () => {}, + }, + ]} + /> +); +``` + +#### Bottom toolbar support + +Material Design 3 drops the bottom bar support contained previously in the `Appbar` scope and moves it to `Toolbars`, hence you can't use the component to construct a bottom bar anymore - for these cases please use the `Toolbar` component. ### Surface diff --git a/docs/6.x/docs/guides/react-navigation.md b/docs/6.x/docs/guides/react-navigation.md index 6c01200f11..2e655b2e87 100644 --- a/docs/6.x/docs/guides/react-navigation.md +++ b/docs/6.x/docs/guides/react-navigation.md @@ -136,11 +136,7 @@ Now we will implement `CustomNavigationBar` using `AppBar` component: import { Appbar } from 'react-native-paper'; export default function CustomNavigationBar() { - return ( - - - - ); + return ; } ``` @@ -154,11 +150,7 @@ import { getHeaderTitle } from '@react-navigation/elements'; export default function CustomNavigationBar({ route, options }) { const title = getHeaderTitle(options, route.name); - return ( - - - - ); + return ; } ``` @@ -179,10 +171,13 @@ export default function CustomNavigationBar({ const title = getHeaderTitle(options, route.name); return ( - - {back ? : null} - - + ); } ``` @@ -194,8 +189,8 @@ export default function CustomNavigationBar({ Another interesting pattern that can be implemented with `react-native-paper` and `react-navigation` is a "menu" button. Thanks to the `Menu` component we can add a nice looking pop-up to our `Appbar`. To implement this feature we need to make a couple of changes in `CustomNavigationBar`: - Render a `Menu` component -- Pass `Appbar.Action` to the anchor prop -- Add a state to control `Menu` visibility +- Add a trailing action to `Appbar` +- Store the action coordinates and menu visibility in state :::note To have properly working `Menu` component, remember to wrap your root component with the `PaperProvider`: @@ -236,21 +231,41 @@ export default function CustomNavigationBar({ back, }) { const [visible, setVisible] = React.useState(false); + const [menuAnchor, setMenuAnchor] = React.useState({ x: 0, y: 0 }); const openMenu = () => setVisible(true); const closeMenu = () => setVisible(false); const title = getHeaderTitle(options, route.name); return ( - - {back ? : null} - + <> + { + setMenuAnchor({ + x: event.nativeEvent.pageX, + y: event.nativeEvent.pageY, + }); + openMenu(); + }, + }, + ] + } + /> {!back ? ( - } - > + { console.log('Option 1 was pressed'); @@ -272,7 +287,7 @@ export default function CustomNavigationBar({ /> ) : null} - + ); } ``` diff --git a/docs/6.x/docs/guides/theming-with-react-navigation.md b/docs/6.x/docs/guides/theming-with-react-navigation.md index ada86a611c..33842d29fe 100644 --- a/docs/6.x/docs/guides/theming-with-react-navigation.md +++ b/docs/6.x/docs/guides/theming-with-react-navigation.md @@ -249,24 +249,25 @@ Now that the Context is available at every component, all we need to do is impor ```js import React from 'react'; -import { useTheme, Appbar, TouchableRipple, Switch } from 'react-native-paper'; +import { Appbar } from 'react-native-paper'; import { PreferencesContext } from './PreferencesContext'; const Header = ({ scene }) => { - const theme = useTheme(); const { toggleTheme, isThemeDark } = React.useContext(PreferencesContext); return ( - - - - + ]} + /> ); }; ``` diff --git a/docs/component-docs.config.ts b/docs/component-docs.config.ts index 2cf106849c..19eebcb076 100644 --- a/docs/component-docs.config.ts +++ b/docs/component-docs.config.ts @@ -21,6 +21,13 @@ export type Pages = Record>; type ComponentDocsConfig = { sourceRootDir: string; pages: Pages; + typescriptProps: Record< + string, + { + sourcePath: string; + typeName: string; + } + >; customFields: { moreExamples: Record>; knownIssues: Record>; @@ -32,13 +39,7 @@ type ComponentDocsConfig = { const pages = { ActivityIndicator: 'ActivityIndicator', - Appbar: { - Appbar: 'Appbar/Appbar', - AppbarAction: 'Appbar/AppbarAction', - AppbarBackAction: 'Appbar/AppbarBackAction', - AppbarContent: 'Appbar/AppbarContent', - AppbarHeader: 'Appbar/AppbarHeader', - }, + Appbar: 'Appbar/Appbar', Avatar: { AvatarIcon: 'Avatar/AvatarIcon', AvatarImage: 'Avatar/AvatarImage', @@ -166,6 +167,19 @@ const pages = { const componentDocsConfig: ComponentDocsConfig = { sourceRootDir: path.join(__dirname, '..', 'src', 'components'), pages, + typescriptProps: { + 'Appbar/Appbar': { + sourcePath: path.join( + __dirname, + '..', + 'src', + 'components', + 'Appbar', + 'types.ts' + ), + typeName: 'Props', + }, + }, customFields: { moreExamples: { Portal: { diff --git a/docs/public/screenshots/appbar-v6-large-flexible.png b/docs/public/screenshots/appbar-v6-large-flexible.png new file mode 100644 index 0000000000000000000000000000000000000000..65b7a8c11135e0ea5c814bfa08596317d7752266 GIT binary patch literal 92306 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 D2YtUR literal 0 HcmV?d00001 diff --git a/docs/public/screenshots/appbar-v6-medium-flexible.png b/docs/public/screenshots/appbar-v6-medium-flexible.png new file mode 100644 index 0000000000000000000000000000000000000000..0a1c1db9e0b9d28156a2f2cd399610f3ab925d3c GIT binary patch literal 86237 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 literal 0 HcmV?d00001 diff --git a/docs/public/screenshots/appbar-v6-small.png b/docs/public/screenshots/appbar-v6-small.png new file mode 100644 index 0000000000000000000000000000000000000000..bc47f6512d7ee6783fd88a544f3804514045b44c GIT binary patch literal 83054 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 literal 0 HcmV?d00001 diff --git a/docs/src/data/screenshots.ts b/docs/src/data/screenshots.ts index b7c59d424d..d6f6b3affe 100644 --- a/docs/src/data/screenshots.ts +++ b/docs/src/data/screenshots.ts @@ -1,18 +1,14 @@ export const screenshots = { ActivityIndicator: 'screenshots/activity-indicator.gif', - Appbar: 'screenshots/appbar.png', - 'Appbar.Action': 'screenshots/appbar-action-android.png', - 'Appbar.BackAction': 'screenshots/appbar-backaction-android.png', - 'Appbar.Content': 'screenshots/appbar-content.png', - 'Appbar.Header': { - small: 'screenshots/appbar-small.png', - medium: 'screenshots/appbar-medium.png', - large: 'screenshots/appbar-large.png', - 'center-aligned': 'screenshots/appbar-center-aligned.png', - }, 'Avatar.Icon': 'screenshots/avatar-icon.png', 'Avatar.Image': 'screenshots/avatar-image.png', 'Avatar.Text': 'screenshots/avatar-text.png', + Appbar: { + search: 'screenshots/appbar-v6-search.png', + small: 'screenshots/appbar-v6-small.png', + 'medium flexible': 'screenshots/appbar-v6-medium-flexible.png', + 'large flexible': 'screenshots/appbar-v6-large-flexible.png', + }, Badge: { 'with text': 'screenshots/badge-1.png', 'without text': 'screenshots/badge-2.png', diff --git a/docs/src/data/themeColors.ts b/docs/src/data/themeColors.ts index 118114da27..41d09ce0ee 100644 --- a/docs/src/data/themeColors.ts +++ b/docs/src/data/themeColors.ts @@ -8,29 +8,20 @@ export const themeColors = { default: { backgroundColor: 'theme.colors.surface', }, - elevated: { - backgroundColor: 'theme.colors.elevation.level2', + scrolled: { + backgroundColor: 'theme.colors.surfaceContainer', }, - }, - 'Appbar.Action': { - 'leading icon': { + 'leading action': { iconColor: 'theme.colors.onSurface', }, - 'not leading icon': { + 'trailing action': { iconColor: 'theme.colors.onSurfaceVariant', }, - }, - 'Appbar.Content': { - '-': { + headline: { textColor: 'theme.colors.onSurface', }, - }, - 'Appbar.Header': { - default: { - backgroundColor: 'theme.colors.surface', - }, - elevated: { - backgroundColor: 'theme.colors.elevation.level2', + subtitle: { + textColor: 'theme.colors.onSurfaceVariant', }, }, Banner: { diff --git a/docs/src/data/versionRouteFallbacks.json b/docs/src/data/versionRouteFallbacks.json index 326741b5b5..df2f5e3ac0 100644 --- a/docs/src/data/versionRouteFallbacks.json +++ b/docs/src/data/versionRouteFallbacks.json @@ -1,5 +1,10 @@ { "next": { + "/docs/components/Appbar/Appbar": "/6.x/docs/components/Appbar", + "/docs/components/Appbar/AppbarAction": "/6.x/docs/components/Appbar", + "/docs/components/Appbar/AppbarBackAction": "/6.x/docs/components/Appbar", + "/docs/components/Appbar/AppbarContent": "/6.x/docs/components/Appbar", + "/docs/components/Appbar/AppbarHeader": "/6.x/docs/components/Appbar", "/docs/components/Checkbox/CheckboxAndroid": "/6.x/docs/components/Checkbox/Checkbox", "/docs/components/Checkbox/CheckboxIOS": "/6.x/docs/components/Checkbox/Checkbox", "/docs/components/FAB/AnimatedFAB": "/6.x/docs/components/FAB/FAB", @@ -9,6 +14,7 @@ "/docs/guides/migration-guide-to-5.0": "/6.x/docs/guides/migration" }, "stable": { + "/6.x/docs/components/Appbar": "/docs/components/Appbar/Appbar", "/6.x/docs/components/FAB/FABExtended": "/docs/components/FAB/FAB", "/6.x/docs/components/FAB/FABMenu": "/docs/components/FAB/FAB", "/6.x/docs/guides/migration": "/docs/guides/migration-guide-to-5.0", diff --git a/example/src/Examples/AppbarExample.tsx b/example/src/Examples/AppbarExample.tsx index 2d4eb0bbc4..17f20da4ee 100644 --- a/example/src/Examples/AppbarExample.tsx +++ b/example/src/Examples/AppbarExample.tsx @@ -1,118 +1,320 @@ import * as React from 'react'; -import { Platform, StyleSheet, View } from 'react-native'; +import { + Image, + Platform, + StyleSheet, + useWindowDimensions, + View, +} from 'react-native'; +import type { NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { Appbar, - FAB, List, Palette, RadioButton, Snackbar, Switch, Text, - useTheme, +} from 'react-native-paper'; +import type { + AppbarFilledTrailingAction, + AppbarHeadlineAlignment, + AppbarLeadingButton, + AppbarStandardTrailingAction, + AppbarTrailingActions, + AppbarVariant, } from 'react-native-paper'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import ScreenWrapper from '../ScreenWrapper'; -type AppbarModes = 'small' | 'medium' | 'large' | 'center-aligned'; +type FilledTrailingActionVariant = AppbarFilledTrailingAction['variant']; +type FilledTrailingActionWidth = NonNullable< + AppbarFilledTrailingAction['width'] +>; const MORE_ICON = Platform.OS === 'ios' ? 'dots-horizontal' : 'dots-vertical'; -const MEDIUM_FAB_HEIGHT = 56; + +type SearchAppbarHeaderProps = { + trailingActions: AppbarStandardTrailingAction[]; + isScrolled: boolean; + leadingButton?: AppbarLeadingButton; + showCustomColor: boolean; +}; + +const SearchAppbarHeader = ({ + trailingActions, + isScrolled, + leadingButton, + showCustomColor, +}: SearchAppbarHeaderProps) => { + const [searchQuery, setSearchQuery] = React.useState(''); + + return ( + + ); +}; const AppbarExample = () => { const navigation = useNavigation('Appbar'); - const [showLeftIcon, setShowLeftIcon] = React.useState(true); + const [showLeadingButton, setShowLeadingButton] = React.useState(true); const [showSubtitle, setShowSubtitle] = React.useState(true); + const [showHeadlineImage, setShowHeadlineImage] = React.useState(false); const [showSearchIcon, setShowSearchIcon] = React.useState(true); const [showMoreIcon, setShowMoreIcon] = React.useState(true); const [showCustomColor, setShowCustomColor] = React.useState(false); - const [appbarMode, setAppbarMode] = React.useState('small'); + const [appbarConfiguration, setAppbarConfiguration] = + React.useState('small'); + const [isHeadlineCentered, setIsHeadlineCentered] = React.useState(false); const [showCalendarIcon, setShowCalendarIcon] = React.useState(false); - const [showElevated, setShowElevated] = React.useState(false); + const [showFilledTrailingAction, setShowFilledTrailingAction] = + React.useState(false); + const [filledTrailingActionVariant, setFilledTrailingActionVariant] = + React.useState('filled'); + const [filledTrailingActionWidth, setFilledTrailingActionWidth] = + React.useState('default'); + const [isScrolled, setIsScrolled] = React.useState(false); const [showSnackbar, setShowSnackbar] = React.useState(false); - const theme = useTheme(); - const { bottom, left, right } = useSafeAreaInsets(); - const height = 80; - - const isCenterAlignedMode = appbarMode === 'center-aligned'; + const { bottom } = useSafeAreaInsets(); + const { height: windowHeight } = useWindowDimensions(); + const headlineAlignment: AppbarHeadlineAlignment = isHeadlineCentered + ? 'center' + : 'leading'; React.useLayoutEffect(() => { + const standardTrailingActions: AppbarStandardTrailingAction[] = []; + + if (showCalendarIcon) { + standardTrailingActions.push({ + key: 'calendar', + icon: 'calendar', + 'aria-label': 'Calendar', + onPress: () => {}, + }); + } + + if (showSearchIcon && appbarConfiguration !== 'search') { + standardTrailingActions.push({ + key: 'search', + icon: 'magnify', + 'aria-label': 'Search', + onPress: () => {}, + }); + } + + if (showMoreIcon) { + standardTrailingActions.push({ + key: 'more', + icon: MORE_ICON, + 'aria-label': 'More options', + onPress: () => {}, + }); + } + + const trailingActions: AppbarTrailingActions = showFilledTrailingAction + ? [ + { + key: 'share', + icon: 'share-variant', + 'aria-label': 'Share', + variant: filledTrailingActionVariant, + width: filledTrailingActionWidth, + onPress: () => {}, + }, + ] + : standardTrailingActions; + + const leadingButton = showLeadingButton + ? { + type: 'back' as const, + onPress: () => navigation.goBack(), + } + : undefined; + const headlineImage = ( + + ); + const commonProps = { + headlineAlignment, + isScrolled, + leadingButton, + onHeadlinePress: () => setShowSnackbar(true), + style: showCustomColor ? styles.customColor : null, + trailingActions, + }; + navigation.setOptions({ - header: () => ( - - {showLeftIcon && ( - navigation.goBack()} /> - )} - setShowSnackbar(true)} /> - {isCenterAlignedMode - ? false - : showCalendarIcon && ( - {}} /> - )} - {showSearchIcon && ( - {}} /> - )} - {showMoreIcon && ( - {}} /> - )} - - ), + header: () => { + if (appbarConfiguration === 'search') { + return ( + + ); + } + + if (appbarConfiguration === 'small') { + return showHeadlineImage ? ( + + ) : ( + + ); + } + + return showHeadlineImage ? ( + + ) : ( + + ); + }, }); }, [ + appbarConfiguration, + filledTrailingActionVariant, + filledTrailingActionWidth, navigation, - showLeftIcon, - showSubtitle, - showSearchIcon, - showMoreIcon, - showCustomColor, - appbarMode, showCalendarIcon, - isCenterAlignedMode, - showElevated, + showCustomColor, + showFilledTrailingAction, + showLeadingButton, + showMoreIcon, + isScrolled, + showSearchIcon, + showSubtitle, + showHeadlineImage, + headlineAlignment, ]); - const renderFAB = () => { - return ( - {}} - style={[styles.fab, { top: (height - MEDIUM_FAB_HEIGHT) / 2 }]} - /> - ); - }; + const handleScroll = React.useCallback( + ({ nativeEvent }: NativeSyntheticEvent) => { + const nextIsScrolled = nativeEvent.contentOffset.y > 1; + + setIsScrolled((currentIsScrolled) => + currentIsScrolled === nextIsScrolled + ? currentIsScrolled + : nextIsScrolled + ); + }, + [] + ); const renderDefaultOptions = () => ( <> - Left icon - + Leading button + Subtitle - + + + + Headline image + + + + Center headline and subtitle + + + + Filled trailing action + Search icon - + More icon - + Calendar icon @@ -120,73 +322,110 @@ const AppbarExample = () => { Custom Color - - Elevated - - ); return ( <> + + + {renderDefaultOptions()} - + {showFilledTrailingAction ? ( + + Style + { + if (value === 'filled' || value === 'tonal') { + setFilledTrailingActionVariant(value); + } + }} + > + + Primary filled + + + + Tonal filled + + + + Width + { + if (value === 'default' || value === 'wide') { + setFilledTrailingActionWidth(value); + } + }} + > + + Default + + + + Wide + + + + + ) : null} + - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - setAppbarMode(value as AppbarModes) - } + value={appbarConfiguration} + onValueChange={(value: string) => { + if ( + value === 'small' || + value === 'search' || + value === 'medium-flexible' || + value === 'large-flexible' + ) { + setAppbarConfiguration(value); + } + }} > - Small (default) - + Search + - Medium - + Small (default) + - Large - + Medium flexible + - Center-aligned - + Large flexible + - - {}} /> - {}} /> - {}} /> - {}} /> - {renderFAB()} - setShowSnackbar(false)} duration={Snackbar.DURATION_SHORT} > - Heading pressed + Headline pressed ); @@ -207,17 +446,11 @@ const styles = StyleSheet.create({ paddingVertical: 8, paddingHorizontal: 16, }, - bottom: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - }, - fab: { - position: 'absolute', - right: 16, - }, customColor: { backgroundColor: Palette.secondary80, }, + headlineImage: { + width: 32, + height: 32, + }, }); diff --git a/example/src/Examples/BottomNavigationExample.tsx b/example/src/Examples/BottomNavigationExample.tsx index b66bb474d6..ea5ead5ace 100644 --- a/example/src/Examples/BottomNavigationExample.tsx +++ b/example/src/Examples/BottomNavigationExample.tsx @@ -86,45 +86,53 @@ const BottomNavigationExample = () => { return ( - - navigation.goBack()} /> - - setMenuVisible(false)} - anchor={ - setMenuVisible(true)} - /> - } - > - { - setSceneAnimation(undefined); - setMenuVisible(false); - }} - title="Scene animation: none" - /> - { - setSceneAnimation('shifting'); - setMenuVisible(false); - }} - title="Scene animation: shifting" - /> - { - setSceneAnimation('opacity'); - setMenuVisible(false); - }} - title="Scene animation: opacity" - /> - - + navigation.goBack(), + }} + trailingActions={[ + { + key: 'more', + icon: MORE_ICON, + 'aria-label': 'More options', + onPress: () => setMenuVisible(true), + }, + ]} + /> + setMenuVisible(false)} + anchor={{ x: Dimensions.get('window').width - 24, y: 56 }} + > + { + setSceneAnimation(undefined); + setMenuVisible(false); + }} + title="Scene animation: none" + /> + { + setSceneAnimation('shifting'); + setMenuVisible(false); + }} + title="Scene animation: shifting" + /> + { + setSceneAnimation('opacity'); + setMenuVisible(false); + }} + title="Scene animation: opacity" + /> + { return ( - - navigation.goBack()} /> - - - } - > - {}} title="Undo" /> - {}} title="Redo" /> - - {}} title="Cut" disabled /> - {}} title="Copy" disabled /> - {}} title="Paste" /> - - + navigation.goBack(), + }} + trailingActions={[ + { + key: 'more', + icon: MORE_ICON, + 'aria-label': 'More options', + onPress: _toggleMenu('menu1'), + decorate: (button) => ( + + {}} title="Undo" /> + {}} title="Redo" /> + + {}} title="Cut" disabled /> + {}} title="Copy" disabled /> + {}} title="Paste" /> + + ), + }, + ]} + /> { return ( - - navigation.goBack()} /> - - + navigation.goBack(), + }} + /> { React.useLayoutEffect(() => { navigation.setOptions({ header: () => ( - - - navigation.goBack()} /> - - - - {}} /> - - - {}} /> - - - {}} /> - - + navigation.goBack(), + decorate: (button) => {button}, + }} + trailingActions={[ + { + key: 'print', + icon: 'printer', + 'aria-label': 'Print', + onPress: () => {}, + decorate: (button) => ( + {button} + ), + }, + { + key: 'search', + icon: 'magnify', + 'aria-label': 'Search', + onPress: () => {}, + decorate: (button) => {button}, + }, + { + key: 'more', + icon: MORE_ICON, + 'aria-label': 'More options', + onPress: () => {}, + decorate: (button) => ( + {button} + ), + }, + ]} + /> ), }); }); diff --git a/example/src/RootNavigator.tsx b/example/src/RootNavigator.tsx index 95ea077275..872ef9b79e 100644 --- a/example/src/RootNavigator.tsx +++ b/example/src/RootNavigator.tsx @@ -25,18 +25,20 @@ function Header({ navigation, route, options, back }: NativeStackHeaderProps) { const drawerNavigation = useNavigation('Home'); return ( - - {back ? ( - navigation.goBack()} /> - ) : ( - drawerNavigation.openDrawer()} - /> - )} - - + navigation.goBack() } + : { + icon: 'menu', + 'aria-label': 'Open navigation menu', + onPress: () => drawerNavigation.openDrawer(), + } + } + /> ); } diff --git a/src/components/Appbar/Appbar.tsx b/src/components/Appbar/Appbar.tsx index 28ea39426a..e8a0fcce25 100644 --- a/src/components/Appbar/Appbar.tsx +++ b/src/components/Appbar/Appbar.tsx @@ -1,338 +1,469 @@ import * as React from 'react'; import { StyleSheet, View } from 'react-native'; -import type { ColorValue, StyleProp, ViewProps, ViewStyle } from 'react-native'; +import type { ColorValue, ViewProps, ViewStyle } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import AppbarButton from './AppbarButton'; import AppbarContent from './AppbarContent'; +import type { Props } from './types'; import { - getAppbarBackgroundColor, + APPBAR_HEADLINE_IMAGE_HEIGHT, + APPBAR_ICON_BUTTON_SIZE, + APPBAR_SEARCH_MAX_WIDTH, getAppbarBorders, - modeAppbarHeight, - renderAppbarContent, - filterAppbarActions, + getAppbarHeight, + getTrailingActionsWidth, } from './utils'; -import type { AppbarModes, AppbarChildProps } from './utils'; import { useInternalTheme } from '../../core/theming'; -import type { ThemeProp } from '../../theme/types'; +import Searchbar from '../Searchbar'; import Surface from '../Surface'; -const APPBAR_HORIZONTAL_PADDING = 4; - -export type AppbarStyle = Omit; - -export type Props = Omit, 'style'> & { - /** - * Whether the background color is a dark color. A dark appbar will render light text and vice-versa. - */ - dark?: boolean; - /** - * Content of the `Appbar`. - */ - children: React.ReactNode; - /** - * @supported Available in v5.x with theme version 3 - * - * Mode of the Appbar. - * - `small` - Appbar with default height (64). - * - `medium` - Appbar with medium height (112). - * - `large` - Appbar with large height (152). - * - `center-aligned` - Appbar with default height and center-aligned title. - */ - mode?: 'small' | 'medium' | 'large' | 'center-aligned'; - /** - * @supported Available in v5.x with theme version 3 - * Whether Appbar background should have the elevation along with primary color pigment. - */ - elevated?: boolean; - /** - * Safe area insets for the Appbar. This can be used to avoid elements like the navigation bar on Android and bottom safe area on iOS. - */ - safeAreaInsets?: { - bottom?: number; - top?: number; - left?: number; - right?: number; - }; - /** - * @optional - */ - theme?: ThemeProp; - style?: StyleProp; -}; +const EMPTY_TRAILING_ACTIONS = [] as const; /** - * A component to display action items in a bar. It can be placed at the top or bottom. - * The top bar usually contains the screen title, controls such as navigation buttons, menu button etc. - * The bottom bar usually provides access to a drawer and up to four actions. + * A Material Design app bar for displaying a page headline, navigation, and + * contextual actions. Appbar supports small, medium flexible, large flexible, + * and search variants. Its surface color automatically changes when content + * has scrolled, and it accounts for safe-area insets. * * ## Usage - * ### Top bar + * + * ### Small app bar * ```js * import * as React from 'react'; * import { Appbar } from 'react-native-paper'; * * const MyComponent = () => ( - * - * {}} /> - * - * {}} /> - * {}} /> - * + * {} }} + * trailingActions={[ + * { + * key: 'search', + * icon: 'magnify', + * 'aria-label': 'Search', + * onPress: () => {}, + * }, + * { + * key: 'more', + * icon: 'dots-vertical', + * 'aria-label': 'More options', + * onPress: () => {}, + * }, + * ]} + * /> * ); * * export default MyComponent; * ``` * - * ### Bottom bar + * ### Decorated action * ```js - * import * as React from 'react'; - * import { StyleSheet } from 'react-native'; - * import { Appbar, FAB, useTheme } from 'react-native-paper'; - * import { useSafeAreaInsets } from 'react-native-safe-area-context'; + * import { Appbar, Tooltip } from 'react-native-paper'; * - * const BOTTOM_APPBAR_HEIGHT = 80; - * const MEDIUM_FAB_HEIGHT = 56; + * {}, + * decorate: (button) => ( + * {button} + * ), + * }, + * ]} + * /> + * ``` * - * const MyComponent = () => { - * const { bottom } = useSafeAreaInsets(); - * const theme = useTheme(); + * ### Flexible app bar + * ```js + * + * ``` * - * return ( - * - * {}} /> - * {}} /> - * {}} /> - * {}} /> - * {}} - * style={[ - * styles.fab, - * { top: (BOTTOM_APPBAR_HEIGHT - MEDIUM_FAB_HEIGHT) / 2 }, - * ]} - * /> - * - * ); - * }; + * ### Search app bar + * ```js + * {} }} + * searchBar={{ + * placeholder: 'Search messages', + * value: query, + * onChangeText: setQuery, + * }} + * /> + * ``` * - * const styles = StyleSheet.create({ - * bottom: { - * backgroundColor: 'aquamarine', - * position: 'absolute', - * left: 0, - * right: 0, - * bottom: 0, - * }, - * fab: { - * position: 'absolute', - * right: 16, - * }, - * }); + * ## Variants * - * export default MyComponent; - * ``` + * | `variant` | Purpose | + * | --- | --- | + * | `small` | A compact 64dp app bar with a one-line headline. | + * | `medium-flexible` | A flexible app bar with a two-line headline and optional subtitle or image. | + * | `large-flexible` | A prominent flexible app bar with a two-line headline and optional subtitle or image. | + * | `search` | An app bar containing a centered Paper `Searchbar`. | + * + * ## Main props + * + * | Prop | Description | + * | --- | --- | + * | `headline` | Required accessible headline for every non-search variant. | + * | `subtitle` | Optional supporting text for written headlines. | + * | `headlineAlignment` | Aligns headline content to `leading` (default) or `center`. | + * | `headlineImage` | Replaces the visible small headline, or appears above a flexible headline. Images should fit within 32dp height. | + * | `onHeadlinePress` | Makes the headline area interactive; use `headlinePressableProps` for its accessibility state. | + * | `leadingButton` | A back button (`{ type: 'back' }`) or standard icon-button configuration. Use `decorate` to wrap it with components such as `Tooltip` or `Menu`. | + * | `trailingActions` | Standard icon-button configurations, or exactly one `filled`/`tonal` action with optional `wide` width. Every action requires a stable `key` and `aria-label`, and can use `decorate` to wrap its resolved button. | + * | `searchBar` | Required for the `search` variant. Accepts Paper `Searchbar` props except `mode`, `elevation`, `showDivider`, and `theme`. | + * | `isScrolled` | Uses `surfaceContainer` instead of `surface` when content has scrolled. | + * | `safeAreaInsets` | Overrides detected top, left, or right safe-area insets. | + * | `statusBarHeight` | Overrides only the automatic top inset. | + * | `contentStyle` | Styles the headline and subtitle area. | + * | `style` | Styles the app bar and can override its background color. | + * + * ## Migrating from the compound API + * + * `Appbar.Header`, `Appbar.Content`, `Appbar.Action`, and + * `Appbar.BackAction` have been removed. Render one `Appbar` and provide its + * headline, leading button, and trailing actions as props. The former + * `mode="medium"` and `mode="large"` values are now + * `variant="medium-flexible"` and `variant="large-flexible"`; centered + * content uses `headlineAlignment="center"`. + * + * ## Bottom toolbar support + * + * Material Design 3 drops the bottom bar support contained previously in the Appbar scope + * and moves it to Toolbars, hence you can't use the component to construct a bottom bar + * anymore - for these cases please use the Toolbar component. + * + * @extends View props https://reactnative.dev/docs/view#props */ const Appbar = ({ - children, - dark, - style, - mode = 'small', - elevated = false, + contentStyle, + headline, + headlineAlignment = 'leading', + headlineImage, + headlinePressableProps, + headlineProps, + isScrolled = false, + leadingButton, + onHeadlinePress, safeAreaInsets, + searchBar, + statusBarHeight, + style, + subtitle, + subtitleProps, + testID, theme: themeOverrides, + trailingActions = EMPTY_TRAILING_ACTIONS, + variant, + ref, ...rest }: Props) => { const theme = useInternalTheme(themeOverrides); - const flattenedStyle = StyleSheet.flatten(style); - const { backgroundColor: customBackground, ...restStyle } = (flattenedStyle || - {}) as Exclude & { - backgroundColor?: ColorValue; - }; + const detectedInsets = useSafeAreaInsets(); + const { customBackground, restStyle, borderRadius } = React.useMemo(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const resolvedStyle = (StyleSheet.flatten(style) || {}) as ViewStyle & { + backgroundColor?: ColorValue; + }; + const { backgroundColor, ...remainingStyle } = resolvedStyle; - const backgroundColor = getAppbarBackgroundColor( - theme, - elevated, - customBackground + return { + customBackground: backgroundColor, + restStyle: remainingStyle, + borderRadius: getAppbarBorders(remainingStyle), + }; + }, [style]); + const backgroundColor = + customBackground ?? + (isScrolled ? theme.colors.surfaceContainer : theme.colors.surface); + const hasSubtitle = typeof subtitle === 'string' && subtitle.length > 0; + const minHeight = getAppbarHeight(variant, hasSubtitle); + const topInset = statusBarHeight ?? safeAreaInsets?.top ?? detectedInsets.top; + const leftInset = safeAreaInsets?.left ?? detectedInsets.left; + const rightInset = safeAreaInsets?.right ?? detectedInsets.right; + const horizontalInset = Math.max(leftInset, rightInset); + const centered = headlineAlignment === 'center'; + const sideWidth = Math.max( + leadingButton ? APPBAR_ICON_BUTTON_SIZE : 0, + getTrailingActionsWidth(trailingActions) ); + const sideStyle = React.useMemo(() => ({ width: sideWidth }), [sideWidth]); + const surfaceStyle = React.useMemo( + () => ({ + paddingTop: topInset, + paddingHorizontal: horizontalInset, + }), + [horizontalInset, topInset] + ); + const appbarStyle = React.useMemo( + () => [styles.appbar, { backgroundColor, minHeight }, restStyle], + [backgroundColor, minHeight, restStyle] + ); + const resolvedSearchInputStyle = React.useMemo( + () => [{ color: theme.colors.onSurface }, searchBar?.inputStyle], + [searchBar?.inputStyle, theme.colors.onSurface] + ); + const searchBackgroundColor = isScrolled + ? theme.colors.surfaceContainerHighest + : theme.colors.surfaceContainer; + const resolvedSearchStyle = React.useMemo( + () => [styles.searchBar, searchBar?.style], + [searchBar?.style] + ); + const searchTheme = React.useMemo( + () => ({ + ...theme, + colors: { ...theme.colors, surfaceContainerHigh: searchBackgroundColor }, + }), + [theme, searchBackgroundColor] + ); + const { + accessibilityLabel: _accessibilityLabel, + accessibilityRole: _accessibilityRole, + accessible: _accessible, + 'aria-label': _ariaLabel, + role: _role, + ...viewProps + } = rest as ViewProps; - const borderStyles = getAppbarBorders(restStyle); - - const isMode = (modeToCompare: AppbarModes) => { - return mode === modeToCompare; - }; + const renderLeadingButton = () => + leadingButton ? ( + + ) : null; - const isDark = typeof dark === 'boolean' ? dark : false; + const renderTrailingActions = () => + trailingActions.map((action) => ( + + )); - const isCenterAlignedMode = isMode('center-aligned'); + const renderSearchAppbar = () => { + if (!searchBar) { + return null; + } - let shouldCenterContent = false; - let shouldAddLeftSpacing = false; - let shouldAddRightSpacing = false; - if (isCenterAlignedMode) { - let hasAppbarContent = false; - let leftItemsCount = 0; - let rightItemsCount = 0; + const { + inputStyle: _searchInputStyle, + style: _searchStyle, + testID: searchTestID = `${testID}-search`, + ...searchProps + } = searchBar; - React.Children.forEach(children, (child) => { - if (React.isValidElement(child)) { - const isLeading = child.props.isLeading === true; + return ( + + {renderLeadingButton()} + + + + + + {renderTrailingActions()} + + ); + }; - if (child.type === AppbarContent) { - hasAppbarContent = true; - } else if (isLeading || !hasAppbarContent) { - leftItemsCount++; - } else { - rightItemsCount++; + const contentProps = + variant !== 'search' + ? { + alignment: headlineAlignment, + contentStyle, + headline, + headlineImage, + headlinePressableProps, + headlineProps, + onHeadlinePress, + subtitle, + subtitleProps, + testID: `${testID}-content`, + theme, + variant, } - } - }); + : null; + + const renderFlexibleHeadlineImage = () => + headlineImage ? ( + + {headlineImage} + + ) : null; - shouldCenterContent = - hasAppbarContent && leftItemsCount < 2 && rightItemsCount < 3; - shouldAddLeftSpacing = shouldCenterContent && leftItemsCount === 0; - shouldAddRightSpacing = shouldCenterContent && rightItemsCount === 0; - } + const renderSmallAppbar = () => { + if (!contentProps) { + return null; + } - const spacingStyle = styles.v3Spacing; + if (centered || headlineImage) { + return ( + + {renderLeadingButton()} + + + {renderTrailingActions()} + + + ); + } - const insets = { - paddingBottom: safeAreaInsets?.bottom, - paddingTop: safeAreaInsets?.top, - paddingLeft: (safeAreaInsets?.left ?? 0) + APPBAR_HORIZONTAL_PADDING, - paddingRight: (safeAreaInsets?.right ?? 0) + APPBAR_HORIZONTAL_PADDING, + return ( + + {renderLeadingButton()} + + {renderTrailingActions()} + + ); + }; + + const renderFlexibleAppbar = () => { + if (!contentProps) { + return null; + } + + return ( + + {headlineImage ? ( + + + {renderLeadingButton()} + + {renderFlexibleHeadlineImage()} + + {renderTrailingActions()} + + + ) : ( + + {renderLeadingButton()} + + {renderTrailingActions()} + + + )} + + + ); }; return ( - {shouldAddLeftSpacing ? : null} - {(isMode('small') || isMode('center-aligned')) && ( - <> - {/* Render only the back action at first place */} - {renderAppbarContent({ - children, - isDark, - theme, - renderOnly: ['Appbar.BackAction'], - shouldCenterContent: isCenterAlignedMode || shouldCenterContent, - })} - {/* Render the rest of the content except the back action */} - {renderAppbarContent({ - // Filter appbar actions - first leading icons, then trailing icons - children: [ - ...filterAppbarActions(children, true), - ...filterAppbarActions(children), - ], - isDark, - theme, - renderExcept: ['Appbar.BackAction'], - shouldCenterContent: isCenterAlignedMode || shouldCenterContent, - })} - - )} - {(isMode('medium') || isMode('large')) && ( - - {/* Appbar top row with controls */} - - {/* Left side of row container, can contain AppbarBackAction or AppbarAction if it's leading icon */} - {renderAppbarContent({ - children, - isDark, - renderOnly: ['Appbar.BackAction'], - mode, - })} - {renderAppbarContent({ - children: filterAppbarActions(children, true), - isDark, - renderOnly: ['Appbar.Action'], - mode, - })} - {/* Right side of row container, can contain other AppbarAction if they are not leading icons */} - - {renderAppbarContent({ - children: filterAppbarActions(children), - isDark, - renderExcept: [ - 'Appbar', - 'Appbar.BackAction', - 'Appbar.Content', - 'Appbar.Header', - ], - mode, - })} - - - {renderAppbarContent({ - children, - isDark, - renderOnly: ['Appbar.Content'], - mode, - })} - - )} - {shouldAddRightSpacing ? : null} + + {variant === 'search' + ? renderSearchAppbar() + : variant === 'small' + ? renderSmallAppbar() + : renderFlexibleAppbar()} + ); }; const styles = StyleSheet.create({ appbar: { + paddingHorizontal: 4, + }, + smallRow: { + flex: 1, flexDirection: 'row', alignItems: 'center', }, - v3Spacing: { - width: 52, - }, - controlsRow: { + searchRow: { flex: 1, flexDirection: 'row', alignItems: 'center', + }, + searchSlot: { + flex: 1, + marginHorizontal: 8, + alignItems: 'center', + }, + searchBar: { + width: '100%', + }, + searchWidthLimiter: { + width: '100%', + maxWidth: APPBAR_SEARCH_MAX_WIDTH, + }, + flexibleContainer: { + flex: 1, + }, + controlsRow: { + minHeight: 56, + flexDirection: 'row', + alignItems: 'flex-start', justifyContent: 'space-between', }, - rightActionControls: { + trailingActions: { flexDirection: 'row', - flex: 1, justifyContent: 'flex-end', }, - columnContainer: { - flexDirection: 'column', - flex: 1, - paddingTop: 8, + side: { + flexDirection: 'row', + }, + headlineLeading: { + marginStart: 12, }, - centerAlignedContainer: { - paddingTop: 0, + headlineWithLeading: { + marginStart: 4, + }, + flexibleHeadlineImage: { + flex: 1, + height: APPBAR_HEADLINE_IMAGE_HEIGHT, + maxWidth: '100%', + marginTop: 12, + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', }, }); -export default Appbar; +const MemoizedAppbar = React.memo(Appbar); + +export default MemoizedAppbar; // @component-docs ignore-next-line -export { Appbar }; +export { MemoizedAppbar as Appbar }; diff --git a/src/components/Appbar/AppbarAction.tsx b/src/components/Appbar/AppbarAction.tsx deleted file mode 100644 index 1c358397c9..0000000000 --- a/src/components/Appbar/AppbarAction.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as React from 'react'; -import type { ColorValue, StyleProp, View, ViewStyle } from 'react-native'; - -import type { AnimatedStyle } from 'react-native-reanimated'; - -import { useInternalTheme } from '../../core/theming'; -import type { ThemeProp } from '../../theme/types'; -import type { IconSource } from '../Icon'; -import IconButton from '../IconButton/IconButton'; -import type { Props as IconButtonProps } from '../IconButton/IconButton'; - -export type Props = React.PropsWithoutRef & { - /** - * Custom color for action icon. - */ - color?: ColorValue; - /** - * Name of the icon to show. - */ - icon: IconSource; - /** - * Optional icon size. - */ - size?: number; - /** - * Whether the button is disabled. A disabled button is greyed out and `onPress` is not called on touch. - */ - disabled?: boolean; - /** - * Accessibility label for the button. This is read by the screen reader when the user taps the button. - */ - 'aria-label'?: string; - /** - * Function to execute on press. - */ - onPress?: () => void; - /** - * @supported Available in v5.x with theme version 3 - * - * Whether it's the leading button. Note: If `Appbar.BackAction` is present, it will be rendered before any `isLeading` icons. - */ - isLeading?: boolean; - style?: StyleProp>; - ref?: React.Ref; - /** - * @optional - */ - theme?: ThemeProp; -}; - -/** - * A component used to display an action item in the appbar. - * - * ## Usage - * ```js - * import * as React from 'react'; - * import { Appbar } from 'react-native-paper'; - * import { Platform } from 'react-native'; - * - * const MORE_ICON = Platform.OS === 'ios' ? 'dots-horizontal' : 'dots-vertical'; - * - * const MyComponent = () => ( - * - * - * {}} /> - * {}} /> - * - * ); - * - * export default MyComponent; - * ``` - */ -const AppbarAction = ({ - size = 24, - color: iconColor, - icon, - disabled, - onPress, - 'aria-label': ariaLabel, - isLeading, - theme: themeOverrides, - ref, - ...rest -}: Props) => { - const theme = useInternalTheme(themeOverrides); - const { colors } = theme; - - const actionIconColor = iconColor - ? iconColor - : isLeading - ? colors.onSurface - : colors.onSurfaceVariant; - - return ( - - ); -}; - -AppbarAction.displayName = 'Appbar.Action'; - -export default AppbarAction; - -// @component-docs ignore-next-line -export { AppbarAction }; diff --git a/src/components/Appbar/AppbarBackAction.tsx b/src/components/Appbar/AppbarBackAction.tsx deleted file mode 100644 index 4f23dfa542..0000000000 --- a/src/components/Appbar/AppbarBackAction.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react'; -import type { - ColorValue, - GestureResponderEvent, - StyleProp, - View, - ViewStyle, -} from 'react-native'; - -import type { AnimatedStyle } from 'react-native-reanimated'; - -import AppbarAction from './AppbarAction'; -import type { Props as AppbarActionProps } from './AppbarAction'; -import AppbarBackIcon from './AppbarBackIcon'; - -export type Props = Omit, 'icon'> & { - /** - * Custom color for back icon. - */ - color?: ColorValue; - /** - * Optional icon size. - */ - size?: number; - /** - * Whether the button is disabled. A disabled button is greyed out and `onPress` is not called on touch. - */ - disabled?: boolean; - /** - * Accessibility label for the button. This is read by the screen reader when the user taps the button. - */ - 'aria-label'?: string; - /** - * Function to execute on press. - */ - onPress?: (e: GestureResponderEvent) => void; - style?: StyleProp>; - ref?: React.Ref; -}; - -/** - * A component used to display a back button in the appbar. - * - * ## Usage - * ```js - * import * as React from 'react'; - * import { Appbar } from 'react-native-paper'; - * - * const MyComponent = () => ( - * - * {}} /> - * - * ); - * - * export default MyComponent; - * ``` - */ -const AppbarBackAction = ({ - 'aria-label': ariaLabel = 'Back', - ref, - ...rest -}: Props) => ( - -); - -AppbarBackAction.displayName = 'Appbar.BackAction'; - -export default AppbarBackAction; - -// @component-docs ignore-next-line -export { AppbarBackAction }; diff --git a/src/components/Appbar/AppbarButton.tsx b/src/components/Appbar/AppbarButton.tsx new file mode 100644 index 0000000000..9d418c9466 --- /dev/null +++ b/src/components/Appbar/AppbarButton.tsx @@ -0,0 +1,68 @@ +import { StyleSheet } from 'react-native'; + +import AppbarBackIcon from './AppbarBackIcon'; +import type { AppbarLeadingButton, AppbarTrailingAction } from './types'; +import type { Theme } from '../../theme/types'; +import IconButton from '../IconButton/IconButton'; + +type Props = { + button: AppbarTrailingAction | AppbarLeadingButton; + leading?: boolean; + theme: Theme; +}; + +const AppbarButton = ({ button, leading = false, theme }: Props) => { + const { + color, + decorate, + key: _key, + style, + variant = 'standard', + width, + ...rest + } = button; + const { type: _type, ...buttonProps } = rest as typeof rest & { + type?: 'back' | 'icon'; + }; + const isBackButton = 'type' in button && button.type === 'back'; + const mode = + variant === 'filled' + ? 'contained' + : variant === 'tonal' + ? 'contained-tonal' + : undefined; + const iconColor = + color ?? + (mode + ? undefined + : leading + ? theme.colors.onSurface + : theme.colors.onSurfaceVariant); + + const iconButton = ( + + ); + + return decorate ? decorate(iconButton) : iconButton; +}; + +const styles = StyleSheet.create({ + wideButton: { + width: 56, + }, +}); + +export default AppbarButton; diff --git a/src/components/Appbar/AppbarContent.tsx b/src/components/Appbar/AppbarContent.tsx index 37c3b28e2c..85d4707225 100644 --- a/src/components/Appbar/AppbarContent.tsx +++ b/src/components/Appbar/AppbarContent.tsx @@ -1,191 +1,214 @@ import * as React from 'react'; -import { StyleSheet, Pressable, View } from 'react-native'; -import type { - GestureResponderEvent, - StyleProp, - TextStyle, - ViewStyle, - ViewProps, -} from 'react-native'; +import { Pressable, StyleSheet, View } from 'react-native'; +import type { StyleProp, ViewStyle } from 'react-native'; -import { modeTextVariant } from './utils'; -import { useInternalTheme } from '../../core/theming'; -import type { ThemeProp } from '../../theme/types'; +import type { + AppbarHeadlineAlignment, + AppbarHeadlineVariant, + Props as AppbarProps, +} from './types'; +import { APPBAR_HEADLINE_IMAGE_HEIGHT } from './utils'; +import type { Theme, TypescaleKey } from '../../theme/types'; import Text from '../Typography/Text'; -import type { TextRef } from '../Typography/Text'; -type TitleString = { - title: string; - titleStyle?: StyleProp; +type Props = Pick< + AppbarProps, + | 'contentStyle' + | 'headline' + | 'headlineImage' + | 'headlinePressableProps' + | 'headlineProps' + | 'onHeadlinePress' + | 'subtitle' + | 'subtitleProps' +> & { + alignment: AppbarHeadlineAlignment; + theme: Theme; + variant: AppbarHeadlineVariant; + style?: StyleProp; + testID: string; +}; + +const headlineVariants: Record = { + small: 'titleLarge', + 'medium-flexible': 'headlineMedium', + 'large-flexible': 'displaySmall', }; -type TitleElement = { title: React.ReactNode; titleStyle?: never }; +const subtitleVariants: Record = { + small: 'labelMedium', + 'medium-flexible': 'labelLarge', + 'large-flexible': 'titleMedium', +}; -export type Props = Omit, 'children'> & { - // For `title` and `titleStyle` props their types are duplicated due to the generation of documentation. - // Appropriate type for them are either `TitleString` or `TitleElement`, depends on `title` type. - /** - * Text or component for the title. - */ - title: React.ReactNode; - /** - * Style for the title, if `title` is a string. - */ - titleStyle?: StyleProp; - /** - * Reference for the title. - */ - titleRef?: React.RefObject; - /** - * Function to execute on press. - */ - onPress?: (e: GestureResponderEvent) => void; - /** - * If true, disable all interactions for this component. - */ - disabled?: boolean; - /** - * Custom color for the text. - */ - color?: string; - /** - * Specifies the largest possible scale a title font can reach. - */ - titleMaxFontSizeMultiplier?: number; - /** - * @internal - */ - mode?: 'small' | 'medium' | 'large' | 'center-aligned'; - style?: StyleProp; - /** - * @optional - */ - theme?: ThemeProp; - /** - * testID to be used on tests. - */ - testID?: string; -} & (TitleString | TitleElement); +const subtitleSpacing: Record = { + small: 0, + 'medium-flexible': 4, + 'large-flexible': 8, +}; -/** - * A component used to display a title in an appbar. - * - * ## Usage - * ```js - * import * as React from 'react'; - * import { Appbar } from 'react-native-paper'; - * - * const MyComponent = () => ( - * - * - * - * ); - * - * export default MyComponent; - * ``` - */ const AppbarContent = ({ - color: titleColor, - onPress, - disabled, - style, - titleRef, - titleStyle, - title, - titleMaxFontSizeMultiplier, - mode = 'small', - theme: themeOverrides, + alignment, + contentStyle, + headline, + headlineImage, + headlinePressableProps, + headlineProps, + onHeadlinePress, + subtitle, + subtitleProps, testID, - ...rest + theme, + variant, + style, }: Props) => { - const theme = useInternalTheme(themeOverrides); - const { colors, fonts } = theme; - - const titleTextColor = titleColor ? titleColor : colors.onSurface; - - const modeContainerStyles = { - small: styles.v3DefaultContainer, - medium: styles.v3MediumContainer, - large: styles.v3LargeContainer, - 'center-aligned': styles.v3DefaultContainer, - }; - - const variant = modeTextVariant[mode]; - - const contentWrapperProps = { - pointerEvents: 'box-none', - style: [styles.container, modeContainerStyles[mode], style], - testID, - ...rest, - } satisfies ViewProps; + const headlineVariant = headlineVariants[variant]; + const subtitleVariant = subtitleVariants[variant]; + const centered = alignment === 'center'; + const hasHeadlineImage = variant === 'small' && Boolean(headlineImage); + const headlineStyle = React.useMemo( + () => [ + styles.text, + centered && styles.centeredText, + { color: theme.colors.onSurface }, + headlineProps?.style, + ], + [centered, headlineProps?.style, theme.colors.onSurface] + ); + const subtitleStyle = React.useMemo( + () => [ + styles.text, + centered && styles.centeredText, + { marginTop: subtitleSpacing[variant] }, + { color: theme.colors.onSurfaceVariant }, + subtitleProps?.style, + ], + [centered, subtitleProps?.style, theme.colors.onSurfaceVariant, variant] + ); + const wrapperStyle = React.useMemo( + () => [ + styles.container, + variant !== 'small' && styles.flexibleContainer, + centered && styles.centeredContainer, + style, + contentStyle, + ], + [centered, contentStyle, style, variant] + ); - const content = ( + const content = hasHeadlineImage ? ( + + {headlineImage} + + ) : ( <> - {typeof title === 'string' ? ( + + {headline} + + {subtitle ? ( - {title} + {subtitle} - ) : ( - title - )} + ) : null} ); - if (onPress) { + const wrapperProps = { + testID, + style: wrapperStyle, + }; + + if (onHeadlinePress) { + const { + 'aria-label': ariaLabel, + accessibilityLabel, + disabled, + ...restHeadlinePressableProps + } = headlinePressableProps ?? {}; + return ( {content} ); } - return {content}; + return ( + + {content} + + ); }; -AppbarContent.displayName = 'Appbar.Content'; - const styles = StyleSheet.create({ container: { flex: 1, - paddingHorizontal: 12, + justifyContent: 'center', + minWidth: 0, }, - v3DefaultContainer: { - paddingHorizontal: 0, + flexibleContainer: { + flex: 0, + flexBasis: 'auto', + paddingHorizontal: 16, + paddingTop: 8, + paddingBottom: 12, }, - v3MediumContainer: { - paddingHorizontal: 0, - justifyContent: 'flex-end', - paddingBottom: 24, + centeredContainer: { + alignItems: 'center', }, - v3LargeContainer: { - paddingHorizontal: 0, - paddingTop: 36, - justifyContent: 'flex-end', - paddingBottom: 28, + text: { + alignSelf: 'stretch', + }, + centeredText: { + textAlign: 'center', + }, + headlineImage: { + height: APPBAR_HEADLINE_IMAGE_HEIGHT, + maxWidth: '100%', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', }, }); -export default AppbarContent; - -// @component-docs ignore-next-line -export { AppbarContent }; +export default React.memo(AppbarContent); diff --git a/src/components/Appbar/AppbarHeader.tsx b/src/components/Appbar/AppbarHeader.tsx deleted file mode 100644 index 9845238299..0000000000 --- a/src/components/Appbar/AppbarHeader.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import * as React from 'react'; -import { Platform, StyleSheet } from 'react-native'; -import type { ColorValue, StyleProp } from 'react-native'; - -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -import { Appbar } from './Appbar'; -import type { AppbarStyle, Props as AppbarProps } from './Appbar'; -import { getAppbarBackgroundColor, modeAppbarHeight } from './utils'; -import { useInternalTheme } from '../../core/theming'; -import type { ThemeProp } from '../../theme/types'; - -export type Props = Omit & { - /** - * Whether the background color is a dark color. A dark header will render light text and vice-versa. - */ - dark?: boolean; - /** - * Extra padding to add at the top of header to account for translucent status bar. - * This is automatically handled on iOS >= 11 including iPhone X using `SafeAreaView`. - * If you are using Expo, we assume translucent status bar and set a height for status bar automatically. - * Pass `0` or a custom value to disable the default behaviour, and customize the height. - */ - statusBarHeight?: number; - /** - * Content of the header. - */ - children: React.ReactNode; - /** - * @supported Available in v5.x with theme version 3 - * - * Mode of the Appbar. - * - `small` - Appbar with default height (64). - * - `medium` - Appbar with medium height (112). - * - `large` - Appbar with large height (152). - * - `center-aligned` - Appbar with default height and center-aligned title. - */ - mode?: 'small' | 'medium' | 'large' | 'center-aligned'; - /** - * @supported Available in v5.x with theme version 3 - * Whether Appbar background should have the elevation along with primary color pigment. - */ - elevated?: boolean; - /** - * @optional - */ - theme?: ThemeProp; - style?: StyleProp; -}; - -/** - * A component to use as a header at the top of the screen. - * It can contain the screen title, controls such as navigation buttons, menu button etc. - * - * ## Usage - * ```js - * import * as React from 'react'; - * import { Appbar } from 'react-native-paper'; - * - * const MyComponent = () => { - * const _goBack = () => console.log('Went back'); - * - * const _handleSearch = () => console.log('Searching'); - * - * const _handleMore = () => console.log('Shown more'); - * - * return ( - * - * - * - * - * - * - * ); - * }; - * - * export default MyComponent; - * ``` - */ -const AppbarHeader = ({ - // Don't use default props since we check it to know whether we should use SafeAreaView - statusBarHeight, - style, - dark, - mode = Platform.OS === 'ios' ? 'center-aligned' : 'small', - elevated = false, - theme: themeOverrides, - testID, - ...rest -}: Props) => { - const theme = useInternalTheme(themeOverrides); - - const flattenedStyle = StyleSheet.flatten(style); - const { - height = modeAppbarHeight[mode], - backgroundColor: customBackground, - zIndex = elevated ? 1 : 0, - ...restStyle - } = (flattenedStyle || {}) as Exclude & { - height?: AppbarStyle['height']; - backgroundColor?: ColorValue; - zIndex?: number; - }; - - const backgroundColor = getAppbarBackgroundColor( - theme, - elevated, - customBackground - ); - - const { top, left, right } = useSafeAreaInsets(); - const topInset = statusBarHeight ?? top; - const horizontalInset = Math.max(left, right); - const headerHeight = typeof height === 'number' ? height + topInset : height; - - return ( - - ); -}; - -AppbarHeader.displayName = 'Appbar.Header'; - -export default AppbarHeader; - -// @component-docs ignore-next-line -export { AppbarHeader }; diff --git a/src/components/Appbar/index.ts b/src/components/Appbar/index.ts index 498c8761f2..c996944de4 100644 --- a/src/components/Appbar/index.ts +++ b/src/components/Appbar/index.ts @@ -1,22 +1,17 @@ -import AppbarComponent from './Appbar'; -import AppbarAction from './AppbarAction'; -import AppbarBackAction from './AppbarBackAction'; -import AppbarContent from './AppbarContent'; -import AppbarHeader from './AppbarHeader'; - -const Appbar = Object.assign( - // @component ./Appbar.tsx - AppbarComponent, - { - // @component ./AppbarContent.tsx - Content: AppbarContent, - // @component ./AppbarAction.tsx - Action: AppbarAction, - // @component ./AppbarBackAction.tsx - BackAction: AppbarBackAction, - // @component ./AppbarHeader.tsx - Header: AppbarHeader, - } -); - -export default Appbar; +export { default } from './Appbar'; +export { Appbar } from './Appbar'; +export type { + AppbarActionDecorator, + AppbarFilledTrailingAction, + AppbarHeadlineAlignment, + AppbarHeadlinePressableProps, + AppbarHeadlineTextProps, + AppbarLeadingButton, + AppbarSearchbarProps, + AppbarStandardTrailingAction, + AppbarTextProps, + AppbarTrailingAction, + AppbarTrailingActions, + AppbarVariant, + Props, +} from './types'; diff --git a/src/components/Appbar/types.ts b/src/components/Appbar/types.ts new file mode 100644 index 0000000000..1335deae18 --- /dev/null +++ b/src/components/Appbar/types.ts @@ -0,0 +1,238 @@ +import * as React from 'react'; +import type { + ColorValue, + GestureResponderEvent, + StyleProp, + TextStyle, + View, + ViewProps, + ViewStyle, +} from 'react-native'; + +import type { AnimatedStyle } from 'react-native-reanimated'; + +import type { ThemeProp } from '../../theme/types'; +import type { IconSource } from '../Icon'; +import type { Props as IconButtonProps } from '../IconButton/IconButton'; +import type { Props as SearchbarProps } from '../Searchbar'; +import type { TextRef } from '../Typography/Text'; + +export type AppbarVariant = + | 'search' + | 'small' + | 'medium-flexible' + | 'large-flexible'; + +export type AppbarHeadlineVariant = Exclude; + +export type AppbarHeadlineAlignment = 'leading' | 'center'; + +export type AppbarTextProps = { + /** Style applied to the text. */ + style?: StyleProp; + /** Specifies the largest possible scale the font can reach. */ + maxFontSizeMultiplier?: number; +}; + +export type AppbarHeadlineTextProps = AppbarTextProps & { + /** Reference for the headline heading. */ + ref?: React.RefObject; +}; + +export type AppbarHeadlinePressableProps = Pick< + ViewProps, + | 'accessibilityHint' + | 'accessibilityLabel' + | 'accessibilityState' + | 'aria-busy' + | 'aria-disabled' + | 'aria-expanded' + | 'aria-label' + | 'aria-labelledby' + | 'aria-selected' +> & { + /** Whether the headline interaction is disabled. */ + disabled?: boolean; +}; + +/** Decorates the resolved icon button without replacing its action configuration. */ +export type AppbarActionDecorator = ( + button: React.ReactElement +) => React.ReactElement; + +type AppbarTrailingActionBase = Omit< + IconButtonProps, + 'icon' | 'iconColor' | 'mode' | 'selected' | 'size' | 'theme' | 'aria-label' +> & { + /** Stable key used when rendering an action from the trailing actions array. */ + key: React.Key; + /** Icon displayed by the trailing action. */ + icon: IconSource; + /** Accessible label announced for the icon button. */ + 'aria-label': string; + /** Custom trailing action icon color. */ + color?: ColorValue; + /** Wraps the resolved icon button, for example with a Tooltip or Menu. */ + decorate?: AppbarActionDecorator; +}; + +export type AppbarStandardTrailingAction = AppbarTrailingActionBase & { + variant?: 'standard'; + width?: never; +}; + +export type AppbarFilledTrailingAction = AppbarTrailingActionBase & { + /** Filled primary and tonal icons use the corresponding IconButton colors. */ + variant: 'filled' | 'tonal'; + /** Expressive filled icons can use the default or wide container. */ + width?: 'default' | 'wide'; +}; + +export type AppbarTrailingAction = + | AppbarStandardTrailingAction + | AppbarFilledTrailingAction; + +/** A filled trailing action replaces the standard action group and is valid on its own. */ +export type AppbarTrailingActions = + | readonly AppbarStandardTrailingAction[] + | readonly [AppbarFilledTrailingAction]; + +type AppbarLeadingIconButton = Omit & { + key?: never; + type?: 'icon'; +}; + +type AppbarBackButton = Omit< + AppbarStandardTrailingAction, + 'aria-label' | 'icon' | 'key' | 'type' +> & { + /** Uses the platform-aware Paper back icon. */ + type: 'back'; + icon?: never; + key?: never; + 'aria-label'?: string; +}; + +export type AppbarLeadingButton = AppbarLeadingIconButton | AppbarBackButton; + +type AppbarWrittenHeadline = { + /** Written headline displayed by the app bar. */ + headline: string; + /** Optional supporting text displayed below the headline. */ + subtitle?: string; + /** Props applied to the headline heading. */ + headlineProps?: AppbarHeadlineTextProps; + /** Props applied to the subtitle text. */ + subtitleProps?: AppbarTextProps; +}; + +type AppbarHeadlineImage = { + /** Image or logo displayed in the app bar. It should fit within 32dp height. */ + headlineImage: React.ReactElement; +}; + +type AppbarTextHeadline = AppbarWrittenHeadline & { + /** Visual and layout variant of the app bar. */ + variant: AppbarHeadlineVariant; + headlineImage?: never; +}; + +type AppbarSmallImageHeadline = AppbarHeadlineImage & { + /** Visual and layout variant of the app bar. */ + variant: 'small'; + /** Accessible page headline. The image replaces this text visually. */ + headline: string; + subtitle?: never; + headlineProps?: never; + subtitleProps?: never; +}; + +type AppbarFlexibleImageHeadline = AppbarWrittenHeadline & + AppbarHeadlineImage & { + /** Visual and layout variant of the app bar. */ + variant: Exclude; + }; + +type AppbarBaseProps = Omit< + ViewProps, + 'accessibilityLabel' | 'accessibilityRole' | 'children' | 'style' | 'testID' +> & { + /** Optional leading button. */ + leadingButton?: AppbarLeadingButton; + /** Uses the on-scroll container color when true. */ + isScrolled?: boolean; + /** Override for the automatic top safe-area inset. */ + statusBarHeight?: number; + /** Safe-area inset overrides. Unspecified values use the detected insets. */ + safeAreaInsets?: { + top?: number; + left?: number; + right?: number; + }; + /** Style applied to the app bar container. */ + style?: StyleProp>; + /** Reference for the app bar container. */ + ref?: React.Ref; + /** Theme override for the app bar. */ + theme?: ThemeProp; + /** TestID used for testing purposes. */ + testID?: string; +}; + +type AppbarHeadlineProps = { + /** Headline and subtitle alignment. */ + headlineAlignment?: AppbarHeadlineAlignment; + /** Trailing actions. */ + trailingActions?: AppbarTrailingActions; + /** Style applied to the headline and subtitle area. */ + contentStyle?: StyleProp; + searchBar?: never; +} & ( + | { + /** Called when the headline area is pressed. */ + onHeadlinePress: (event: GestureResponderEvent) => void; + /** Props applied to the interactive headline area. */ + headlinePressableProps?: AppbarHeadlinePressableProps; + } + | { + onHeadlinePress?: never; + headlinePressableProps?: never; + } +); + +export type AppbarSearchbarProps = Omit< + SearchbarProps, + 'elevation' | 'mode' | 'showDivider' | 'theme' +> & { + /** Search hint. Material guidance recommends including the word “Search”. */ + placeholder: string; +}; + +type AppbarSearchProps = { + /** Visual and layout variant of the app bar. */ + variant: 'search'; + /** Props forwarded to the existing Paper Searchbar. */ + searchBar: AppbarSearchbarProps; + /** Exterior trailing actions. */ + trailingActions?: readonly AppbarStandardTrailingAction[]; + headline?: never; + subtitle?: never; + headlineImage?: never; + headlineAlignment?: never; + headlineProps?: never; + subtitleProps?: never; + onHeadlinePress?: never; + headlinePressableProps?: never; + contentStyle?: never; +}; + +export type Props = AppbarBaseProps & + ( + | (AppbarHeadlineProps & + ( + | AppbarTextHeadline + | AppbarSmallImageHeadline + | AppbarFlexibleImageHeadline + )) + | AppbarSearchProps + ); diff --git a/src/components/Appbar/utils.ts b/src/components/Appbar/utils.ts index 36b2fd2f09..d8c9d7eca4 100644 --- a/src/components/Appbar/utils.ts +++ b/src/components/Appbar/utils.ts @@ -1,185 +1,57 @@ -import React from 'react'; -import type { ColorValue, StyleProp, ViewStyle } from 'react-native'; -import { StyleSheet } from 'react-native'; +import type { ViewStyle } from 'react-native'; -import { white } from '../../theme/colors'; -import type { InternalTheme, ThemeProp } from '../../theme/types'; +import type { AppbarTrailingAction, AppbarVariant } from './types'; -export type AppbarModes = 'small' | 'medium' | 'large' | 'center-aligned'; +export const APPBAR_ICON_BUTTON_SIZE = 48; +export const APPBAR_WIDE_ICON_BUTTON_SIZE = 64; +export const APPBAR_HEADLINE_IMAGE_HEIGHT = 32; +export const APPBAR_SEARCH_MAX_WIDTH = 720; -export type AppbarChildProps = { - isLeading?: boolean; - color: string; - style?: StyleProp; -}; - -const borderStyleProperties = [ +const borderStyleProperties: readonly (keyof ViewStyle)[] = [ 'borderRadius', - 'borderBottomEndRadius', - 'borderBottomStartRadius', - 'borderEndEndRadius', - 'borderEndStartRadius', - 'borderStartEndRadius', - 'borderStartStartRadius', - 'borderTopEndRadius', - 'borderTopStartRadius', 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius', - 'borderCurve', -] satisfies readonly (keyof ViewStyle)[]; - -export const getAppbarBackgroundColor = ( - theme: InternalTheme, - elevated: boolean, - customBackground?: ColorValue -) => { - const { colors } = theme; - if (customBackground) { - return customBackground; - } - - if (elevated) { - return colors.surfaceContainer; - } - - return colors.surface; -}; - -export const getAppbarColor = ({ - color, - isDark, -}: BaseProps & { color: string }) => { - if (typeof color !== 'undefined') { - return color; - } - - if (isDark) { - return white; - } - - return undefined; -}; +]; export const getAppbarBorders = (style: ViewStyle) => { - let borders: ViewStyle = {}; + const borders: Record = {}; for (const property of borderStyleProperties) { const value = style[property]; - - if (typeof value === 'number' || typeof value === 'string') { - borders = { ...borders, [property]: value }; + if (value) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + borders[property] = value as number; } } return borders; }; -type BaseProps = { - isDark: boolean; -}; - -type RenderAppbarContentProps = BaseProps & { - children: React.ReactNode; - shouldCenterContent?: boolean; - renderOnly?: (string | boolean)[]; - renderExcept?: string[]; - mode?: AppbarModes; - theme?: ThemeProp; -}; - -export const DEFAULT_APPBAR_HEIGHT = 56; -const MD3_DEFAULT_APPBAR_HEIGHT = 64; - -export const modeAppbarHeight = { - small: MD3_DEFAULT_APPBAR_HEIGHT, - medium: 112, - large: 152, - 'center-aligned': MD3_DEFAULT_APPBAR_HEIGHT, -}; - -export const modeTextVariant = { - small: 'titleLarge', - medium: 'headlineSmall', - large: 'headlineMedium', - 'center-aligned': 'titleLarge', -} as const; - -export const filterAppbarActions = ( - children: React.ReactNode, - isLeading = false +export const getAppbarHeight = ( + variant: AppbarVariant, + hasSubtitle: boolean ) => { - return React.Children.toArray(children).filter((child) => { - if (!React.isValidElement(child)) return false; - return isLeading ? child.props.isLeading : !child.props.isLeading; - }); -}; - -export const renderAppbarContent = ({ - children, - isDark, - shouldCenterContent = false, - renderOnly, - renderExcept, - mode = 'small', - theme, -}: RenderAppbarContentProps) => { - return React.Children.toArray(children) - .filter((child) => child != null && typeof child !== 'boolean') - .filter((child) => - // @ts-expect-error: TypeScript complains about the type of type but it doesn't matter - renderExcept ? !renderExcept.includes(child.type.displayName) : child - ) - .filter((child) => - // @ts-expect-error: TypeScript complains about the type of type but it doesn't matter - renderOnly ? renderOnly.includes(child.type.displayName) : child - ) - .map((child, i) => { - if ( - !React.isValidElement(child) || - ![ - 'Appbar.Content', - 'Appbar.Action', - 'Appbar.BackAction', - 'Tooltip', - ].includes( - // @ts-expect-error: TypeScript complains about the type of type but it doesn't matter - child.type.displayName - ) - ) { - return child; - } + if (variant === 'search' || variant === 'small') { + return 64; + } - const props: { - color?: string; - style?: StyleProp; - mode?: AppbarModes; - theme?: ThemeProp; - } = { - theme, - color: getAppbarColor({ color: child.props.color, isDark }), - }; + if (variant === 'medium-flexible') { + return hasSubtitle ? 136 : 112; + } - // @ts-expect-error: TypeScript complains about the type of type but it doesn't matter - if (child.type.displayName === 'Appbar.Content') { - props.mode = mode; - props.style = [ - i === 0 && !shouldCenterContent && styles.v3Spacing, - shouldCenterContent && styles.centerAlignedContent, - child.props.style, - ]; - props.color; - } - return React.cloneElement(child, props); - }); + return hasSubtitle ? 152 : 120; }; -const styles = StyleSheet.create({ - centerAlignedContent: { - alignItems: 'center', - }, - v3Spacing: { - marginLeft: 12, - }, -}); +export const getTrailingActionsWidth = ( + actions: readonly AppbarTrailingAction[] +) => + actions.reduce( + (width, action) => + width + + (action.variant !== 'standard' && action.width === 'wide' + ? APPBAR_WIDE_ICON_BUTTON_SIZE + : APPBAR_ICON_BUTTON_SIZE), + 0 + ); diff --git a/src/components/Card/CardActions.tsx b/src/components/Card/CardActions.tsx index d541c691bc..04a0d0ddee 100644 --- a/src/components/Card/CardActions.tsx +++ b/src/components/Card/CardActions.tsx @@ -15,6 +15,10 @@ export type Props = ViewProps & { theme?: ThemeProp; }; +const defaultContainerStyle: ViewStyle = { + justifyContent: 'flex-end', +}; + /** * A component to show a list of actions inside a Card. * @@ -38,11 +42,7 @@ export type Props = ViewProps & { const CardActions = ({ theme, style, children, ...rest }: Props) => { useInternalTheme(theme); - const containerStyle = [ - styles.container, - { justifyContent: 'flex-end' } satisfies ViewStyle, - style, - ]; + const containerStyle = [styles.container, defaultContainerStyle, style]; return ( diff --git a/src/components/__tests__/Appbar/Appbar.test.tsx b/src/components/__tests__/Appbar/Appbar.test.tsx index 0d6abb6029..98d00e1b49 100644 --- a/src/components/__tests__/Appbar/Appbar.test.tsx +++ b/src/components/__tests__/Appbar/Appbar.test.tsx @@ -1,311 +1,824 @@ -import { describe, expect, it } from '@jest/globals'; +import * as React from 'react'; +import { Dimensions, Text, View } from 'react-native'; +import type { GestureResponderEvent } from 'react-native'; + +import { describe, expect, it, jest } from '@jest/globals'; import { SafeAreaProvider } from 'react-native-safe-area-context'; -import { render, screen } from '../../../test-utils'; -import { DarkTheme, LightTheme } from '../../../theme/schemes'; -import { tokens } from '../../../theme/tokens'; +import { render, screen, userEvent, waitFor } from '../../../test-utils'; +import { LightTheme } from '../../../theme/schemes'; import Appbar from '../../Appbar'; -import { - getAppbarBackgroundColor, - getAppbarBorders, - modeTextVariant, - renderAppbarContent as utilRenderAppbarContent, -} from '../../Appbar/utils'; +import type { AppbarVariant } from '../../Appbar'; import Menu from '../../Menu/Menu'; -import Searchbar from '../../Searchbar'; -import Text from '../../Typography/Text'; +import Portal from '../../Portal/Portal'; +import Tooltip from '../../Tooltip/Tooltip'; + +const testIDPrefix = 'appbar'; + +const writtenHeadlineVariants: Exclude[] = [ + 'small', + 'medium-flexible', + 'large-flexible', +]; + +const decorativeHeadlineImage = ( + + + + Brand words + + +); + +describe('Appbar content', () => { + it.each([ + { + variant: 'small', + headlineVariant: 'titleLarge', + headlineLines: 1, + subtitleVariant: 'labelMedium', + subtitleSpacing: 0, + }, + { + variant: 'medium-flexible', + headlineVariant: 'headlineMedium', + headlineLines: 2, + subtitleVariant: 'labelLarge', + subtitleSpacing: 4, + }, + { + variant: 'large-flexible', + headlineVariant: 'displaySmall', + headlineLines: 2, + subtitleVariant: 'titleMedium', + subtitleSpacing: 8, + }, + ] as const)( + 'uses the $variant content treatment', + async ({ + variant, + headlineVariant, + headlineLines, + subtitleVariant, + subtitleSpacing, + }) => { + await render( + + ); -// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -const renderAppbarContent = utilRenderAppbarContent as ( - props: Parameters[0] -) => { props: any }[]; + expect(screen.getByText('Inbox')).toHaveStyle( + LightTheme.fonts[headlineVariant] + ); + expect(screen.getByText('Inbox')).toHaveProp( + 'numberOfLines', + headlineLines + ); + expect(screen.getByText('3 unread')).toHaveStyle({ + ...LightTheme.fonts[subtitleVariant], + color: LightTheme.colors.onSurfaceVariant, + marginTop: subtitleSpacing, + }); + } + ); -describe('Appbar', () => { - it('does not pass any additional props to Searchbar', async () => { - const tree = ( - await render( - - - - ) - ).toJSON(); + it('centers text and balances asymmetric controls for centered and image layouts', async () => { + const { rerender } = await render( + + ); - expect(tree).toMatchSnapshot(); + expect(screen.getByText('Inbox')).toHaveStyle({ + textAlign: 'center', + }); + expect(screen.getByText('3 unread')).toHaveStyle({ + textAlign: 'center', + }); + expect(screen.getByTestId('leading-action').parent?.parent).toHaveStyle({ + width: 96, + }); + expect(screen.getByTestId('trailing-action').parent?.parent).toHaveStyle({ + width: 96, + }); + + await rerender( + } + leadingButton={{ + type: 'back', + testID: 'leading-action', + }} + trailingActions={[ + { + key: 'more', + icon: 'dots-vertical', + 'aria-label': 'More options', + testID: 'trailing-action', + }, + { + key: 'search', + icon: 'magnify', + 'aria-label': 'Search inbox', + }, + ]} + testID={testIDPrefix} + /> + ); + + expect(screen.getByTestId('leading-action').parent?.parent).toHaveStyle({ + width: 96, + }); + expect(screen.getByTestId('trailing-action').parent?.parent).toHaveStyle({ + width: 96, + }); + expect( + screen.getByTestId('brand-mark', { includeHiddenElements: true }) + ).toBeOnTheScreen(); }); - it('passes additional props to AppbarBackAction, AppbarContent and AppbarAction', async () => { - const tree = ( - await render( - - {}} /> - - {}} /> - - ) - ).toJSON(); - - expect(tree).toMatchSnapshot(); + it('adjusts leading headline spacing when a leading button is present', async () => { + const { rerender } = await render( + + ); + + expect(screen.getByTestId('appbar-content')).toHaveStyle({ + marginStart: 12, + }); + + await rerender( + + ); + + expect(screen.getByTestId('appbar-content')).toHaveStyle({ + marginStart: 4, + }); }); + + it.each(['medium-flexible', 'large-flexible'] as const)( + 'preserves intrinsic content sizing for the %s variant', + async (variant) => { + await render( + + ); + + expect(screen.getByTestId('appbar-content')).toHaveStyle({ + flexBasis: 'auto', + }); + } + ); }); -describe('renderAppbarContent', () => { - const children = [ - {}} key={0} />, - , - {}} key={2} />, - {}} key={3} />, - ]; - - it('should render all children types if renderOnly is not specified', () => { - const result = renderAppbarContent({ - children, - isDark: false, +describe('Appbar surface', () => { + it('uses scroll container colors unless a custom background is supplied', async () => { + const customBackground = 'rebeccapurple'; + const { rerender } = await render( + + ); + + expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + backgroundColor: LightTheme.colors.surface, }); - expect(result).toHaveLength(4); - }); + await rerender( + + ); - it('should render all children types except specified in renderExcept', () => { - const result = renderAppbarContent({ - children: [ - ...children, - {}} />} - visible={false} - > - {null} - , - ], - isDark: false, - renderExcept: [ - 'Appbar', - 'Appbar.Header', - 'Appbar.BackAction', - 'Appbar.Content', - ], + expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + backgroundColor: LightTheme.colors.surfaceContainer, }); - expect(result).toHaveLength(3); + await rerender( + + ); + + expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + backgroundColor: customBackground, + }); }); - it('should render only children types specified in renderOnly', () => { - const result = renderAppbarContent({ - children, - isDark: false, - renderOnly: ['Appbar.Action'], + it('applies border clipping and resolves safe-area overrides on the surface', async () => { + await render( + + + + ); + + expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + borderBottomLeftRadius: 16, + borderBottomRightRadius: 16, + paddingTop: 20, + paddingHorizontal: 12, }); + }); +}); + +describe('Appbar actions', () => { + it('maps leading, trailing, and custom action colors', async () => { + await render( + ( + + ), + 'aria-label': 'Navigation', + }} + trailingActions={[ + { + key: 'default', + icon: ({ color }) => ( + + ), + 'aria-label': 'Default action', + }, + { + key: 'custom', + icon: ({ color }) => ( + + ), + 'aria-label': 'Custom action', + color: 'rebeccapurple', + }, + ]} + testID={testIDPrefix} + /> + ); - expect(result).toHaveLength(2); + expect(screen.getByTestId('leading-icon-color')).toHaveStyle({ + backgroundColor: LightTheme.colors.onSurface, + }); + expect(screen.getByTestId('trailing-icon-color')).toHaveStyle({ + backgroundColor: LightTheme.colors.onSurfaceVariant, + }); + expect(screen.getByTestId('custom-icon-color')).toHaveStyle({ + backgroundColor: 'rebeccapurple', + }); }); - it('should render AppbarContent with correct mode', () => { - const result = renderAppbarContent({ - children, - isDark: false, - renderOnly: ['Appbar.Content'], - mode: 'large', + it.each([ + { variant: 'filled', color: LightTheme.colors.primary }, + { variant: 'tonal', color: LightTheme.colors.secondaryContainer }, + ] as const)( + 'maps $variant actions to their selected container color', + async ({ variant, color }) => { + await render( + + ); + + expect(screen.getByTestId('expressive-action').parent).toHaveStyle({ + backgroundColor: color, + }); + expect(screen.getByTestId('expressive-action').parent).toHaveStyle({ + width: 56, + }); + } + ); + + it('decorates an action with a working tooltip without losing its press handler', async () => { + const onPress = jest.fn<(event: GestureResponderEvent) => void>(); + + await render( + + ( + {button} + ), + }, + ]} + /> + + ); + + const action = screen.getByRole('button', { name: 'Print' }); + + await userEvent.press(action); + expect(onPress).toHaveBeenCalledTimes(1); + + await userEvent.longPress(action); + expect(await screen.findByText('Print shortcut')).toBeOnTheScreen(); + }); + + it('decorates an action with a menu anchored to the resolved button', async () => { + const dimensionsSpy = jest.spyOn(Dimensions, 'get').mockReturnValue({ + width: 400, + height: 800, + scale: 2, + fontScale: 2, }); + const measureSpy = jest + .spyOn(View.prototype, 'measureInWindow') + .mockImplementation((callback) => callback(100, 100, 80, 32)); + const MenuAppbar = () => { + const [visible, setVisible] = React.useState(false); + + return ( + + setVisible(true), + decorate: (button) => ( + setVisible(false)} + anchor={button} + anchorPosition="bottom" + testID="menu" + > + {}} /> + + ), + }, + ]} + /> + + ); + }; - // eslint-disable-next-line no-restricted-syntax -- TODO: replace TestInstance props access with a user-visible assertion. - expect(result[0].props.mode).toBe('large'); + await render(); + + await userEvent.press(screen.getByRole('button', { name: 'More options' })); + + await waitFor(() => { + expect(screen.getByText('Undo')).toBeOnTheScreen(); + expect(screen.getByTestId('menu').parent?.parent).toHaveStyle({ + position: 'absolute', + left: 100, + top: 132, + }); + }); + expect(measureSpy).toHaveBeenCalled(); + + measureSpy.mockRestore(); + dimensionsSpy.mockRestore(); }); +}); + +describe('Appbar search', () => { + it('uses the placeholder as the searchbox label unless an explicit label is supplied', async () => { + const { rerender } = await render( + + ); - it('should render centered AppbarContent', () => { - const result = renderAppbarContent({ - children, - isDark: false, - renderOnly: ['Appbar.Content'], - mode: 'center-aligned', - shouldCenterContent: true, + expect( + screen.getByRole('searchbox', { name: 'Search messages' }) + ).toBeOnTheScreen(); + expect(screen.getByTestId('appbar-search').parent).toHaveStyle({ + backgroundColor: LightTheme.colors.surfaceContainer, }); - const centerAlignedContent = { - alignItems: 'center', + await rerender( + + ); + + expect( + screen.getByRole('searchbox', { name: 'Message search' }) + ).toBeOnTheScreen(); + }); + + it('configures the search field, forwards its behavior, and constrains its width', async () => { + const onChangeText = jest.fn(); + const SearchAppbar = () => { + const [value, setValue] = React.useState(''); + + return ( + { + setValue(nextValue); + onChangeText(nextValue); + }, + testID: 'message-search', + }} + trailingActions={[ + { + key: 'more', + icon: 'dots-vertical', + 'aria-label': 'More options', + }, + ]} + testID={testIDPrefix} + /> + ); }; - // eslint-disable-next-line no-restricted-syntax -- TODO: replace TestInstance props access with a user-visible assertion. - expect(result[0].props.style).toEqual( - expect.arrayContaining([expect.objectContaining(centerAlignedContent)]) + await render(); + + const searchbox = screen.getByRole('searchbox', { + name: 'Search messages', + }); + expect(searchbox).toHaveStyle({ + color: LightTheme.colors.onSurface, + }); + expect(searchbox).toHaveProp( + 'placeholderTextColor', + LightTheme.colors.onSurfaceVariant ); + expect(screen.getByTestId('message-search').parent).toHaveStyle({ + backgroundColor: LightTheme.colors.surfaceContainerHighest, + }); + expect( + screen.getByRole('button', { name: 'Navigate back' }) + ).toBeOnTheScreen(); + expect( + screen.getByRole('button', { name: 'More options' }) + ).toBeOnTheScreen(); + + await userEvent.type(searchbox, 'draft'); + expect(onChangeText).toHaveBeenLastCalledWith('draft'); + expect(searchbox).toHaveProp('value', 'draft'); + + expect(screen.getByTestId('message-search').parent).toHaveStyle({ + width: '100%', + }); + expect(screen.getByTestId('appbar-search-width-limiter')).toHaveStyle({ + width: '100%', + maxWidth: 720, + }); }); +}); - it('should render AppbarContent with correct spacings', () => { - const renderResult = (withAppbarBackAction = false) => - renderAppbarContent({ - children, - isDark: false, - renderOnly: [ - 'Appbar.Content', - withAppbarBackAction && 'Appbar.BackAction', - ], - }); +describe('Appbar accessibility', () => { + it.each(writtenHeadlineVariants)( + 'exposes a written %s headline as a heading', + async (variant) => { + await render(); - const v3Spacing = { - marginLeft: 12, - }; + expect(screen.getByRole('heading', { name: 'Inbox' })).toBeOnTheScreen(); + } + ); - // eslint-disable-next-line no-restricted-syntax -- TODO: replace TestInstance props access with a user-visible assertion. - expect(renderResult()[0].props.style).toEqual( - expect.arrayContaining([expect.objectContaining(v3Spacing)]) + it('hides a small headline image behind one named heading', async () => { + await render( + ); + + expect(screen.getAllByRole('heading')).toHaveLength(1); + expect(screen.getByRole('heading', { name: 'Inbox' })).toBeOnTheScreen(); + expect(screen.queryByRole('img')).not.toBeOnTheScreen(); + expect(screen.queryByText('Brand words')).not.toBeOnTheScreen(); }); - it('Is recognized as a heading when no onPress callback has been passed', async () => { + it.each(['medium-flexible', 'large-flexible'] as const)( + 'does not expose duplicate content for a %s headline image', + async (variant) => { + await render( + + ); + + expect(screen.getAllByRole('heading', { name: 'Inbox' })).toHaveLength(1); + expect(screen.queryByRole('img')).not.toBeOnTheScreen(); + expect(screen.queryByLabelText('Brand artwork')).not.toBeOnTheScreen(); + } + ); + + it('exposes a pressable written headline as a named button and heading', async () => { + const onHeadlinePress = jest.fn(); await render( - - - - - + ); - expect(screen.getByRole('heading')).toBeOnTheScreen(); + expect(screen.getByRole('button', { name: 'Inbox' })).toBeOnTheScreen(); + expect(screen.getByRole('heading', { name: 'Inbox' })).toBeOnTheScreen(); }); - it('is recognized as a button when onPress callback has been passed', async () => { + + it('does not invoke a disabled headline action', async () => { + const onHeadlinePress = jest.fn(); await render( - - - {}} /> - - + ); - expect(screen.getByRole('button')).toBeEnabled(); - expect(screen.queryByRole('heading')).not.toBeOnTheScreen(); + const headlineButton = screen.getByRole('button', { + name: 'Inbox', + disabled: true, + }); + await userEvent.press(headlineButton); + + expect(onHeadlinePress).not.toHaveBeenCalled(); }); - it('is recognized as a disabled button when onPress and disabled is passed', async () => { + + it('names a pressable small headline image from its fallback headline', async () => { await render( - - - {}} disabled /> - - + {}} + testID={testIDPrefix} + /> ); - expect(screen.getByRole('button')).toBeDisabled(); - expect(screen.queryByRole('heading')).not.toBeOnTheScreen(); + expect(screen.getByRole('button', { name: 'Inbox' })).toBeOnTheScreen(); }); -}); -describe('AppbarAction', () => { - it('should be rendered with default theme color', async () => { - const { toJSON } = await render( - - - + it('forwards custom headline button accessibility props', async () => { + await render( + {}} + headlinePressableProps={{ + accessibilityHint: 'Opens the inbox menu', + accessibilityLabel: 'Inbox options', + accessibilityState: { busy: true, expanded: true }, + }} + testID={testIDPrefix} + /> ); - expect(toJSON()).toMatchSnapshot(); + + const titleButton = screen.getByRole('button', { + name: 'Inbox options', + busy: true, + expanded: true, + }); + + expect(titleButton).toHaveProp('accessibilityHint', 'Opens the inbox menu'); }); - it('should be rendered with specific theme color if is leading', async () => { - const { toJSON } = await render( - - - + it('uses default and custom labels for back buttons', async () => { + await render( + + + + ); - expect(toJSON()).toMatchSnapshot(); + + expect(screen.getByRole('button', { name: 'Back' })).toBeOnTheScreen(); + expect( + screen.getByRole('button', { name: 'Return to inbox' }) + ).toBeOnTheScreen(); }); - it('should be rendered with custom color', async () => { - const { toJSON } = await render( - - - + it('preserves trailing action labels, hints, disabled state, and busy state', async () => { + await render( + ); - expect(toJSON()).toMatchSnapshot(); - }); - it('should render AppbarBackAction with custom color', async () => { - const { toJSON } = await render( - - - + const trailingAction = screen.getByRole('button', { + name: 'Save message', + busy: true, + disabled: true, + }); + + expect(trailingAction).toHaveProp( + 'accessibilityHint', + 'Saves the current draft' ); - expect(toJSON()).toMatchSnapshot(); + expect(trailingAction).toBeDisabled(); + expect(trailingAction).toBeBusy(); }); -}); -describe('AppbarContent', () => { - (['small', 'medium', 'large', 'center-aligned'] as const).forEach((mode) => - it(`should render text component with appropriate variant for ${mode} mode`, async () => { + it.each(['small', 'medium-flexible'] as const)( + 'hides decorative descendants of a %s headline image', + async (variant) => { await render( - - - + variant === 'small' ? ( + + ) : ( + + ) ); - expect(screen.getByText('Title')).toHaveStyle( - LightTheme.fonts[modeTextVariant[mode]] + const imageContainer = screen.getByTestId( + 'appbar-content-headline-image', + { includeHiddenElements: true } ); - }) + + expect(imageContainer).toHaveProp('aria-hidden', true); + expect( + screen.queryByRole('heading', { name: 'Brand words' }) + ).not.toBeOnTheScreen(); + } ); - it('should render component passed to title', async () => { + it('exposes the leading button, headline, and trailing actions in source order', async () => { await render( - - - Title - - } - /> - + ); - expect(screen.getByText('Title')).toBeOnTheScreen(); - }); -}); - -describe('getAppbarColors', () => { - const elevated = true; - const customBackground = 'aquamarine'; - - it('should return custom color no matter what is the theme version', () => { - expect( - getAppbarBackgroundColor(LightTheme, elevated, customBackground) - ).toBe(customBackground); + expect(screen.getAllByRole(/button|heading/)).toEqual([ + screen.getByRole('button', { name: 'Navigate back' }), + screen.getByRole('heading', { name: 'Inbox' }), + screen.getByRole('button', { name: 'Search inbox' }), + screen.getByRole('button', { name: 'More options' }), + ]); }); - it('returns the light surface container color for an elevated appbar', () => { - expect(getAppbarBackgroundColor(LightTheme, elevated)).toBe( - tokens.md.ref.palette.neutral94 + it('does not remount a surviving trailing action when its configuration changes', async () => { + const { rerender } = await render( + ); - }); + const survivingTrailingAction = screen.getByRole('button', { + name: 'Keep action', + }); - it('returns the dark surface container color for an elevated appbar', () => { - expect(getAppbarBackgroundColor(DarkTheme, elevated)).toBe( - tokens.md.ref.palette.neutral12 + await rerender( + ); - }); -}); -describe('getAppbarBorders', () => { - const borderStyles = { - borderRadius: 1, - borderBottomEndRadius: 2, - borderBottomStartRadius: 3, - borderEndEndRadius: 4, - borderEndStartRadius: 5, - borderStartEndRadius: 6, - borderStartStartRadius: 7, - borderTopEndRadius: 8, - borderTopStartRadius: 9, - borderTopLeftRadius: 10, - borderTopRightRadius: 11, - borderBottomRightRadius: 12, - borderBottomLeftRadius: 13, - borderCurve: 'continuous' as const, - }; - - it('returns every border style and excludes unrelated styles', () => { - expect(getAppbarBorders({ ...borderStyles, height: 60, top: 13 })).toEqual( - borderStyles + expect(screen.getByRole('button', { name: 'Keep action' })).toBe( + survivingTrailingAction ); }); - - it('returns an empty object when no border styles are passed', () => { - expect(getAppbarBorders({ height: 60, top: 13 })).toEqual({}); - }); }); diff --git a/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap b/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap deleted file mode 100644 index faeb89916b..0000000000 --- a/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap +++ /dev/null @@ -1,1998 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Appbar does not pass any additional props to Searchbar 1`] = ` - - - - - - - - - magnify - - - - - - - - - - - close - - - - - - - -`; - -exports[`Appbar passes additional props to AppbarBackAction, AppbarContent and AppbarAction 1`] = ` - - - - - - - - - - - - - - - - - - Examples - - - - - - - - - menu - - - - - - - -`; - -exports[`AppbarAction should be rendered with custom color 1`] = ` - - - - - - - - - menu - - - - - - - -`; - -exports[`AppbarAction should be rendered with default theme color 1`] = ` - - - - - - - - - menu - - - - - - - -`; - -exports[`AppbarAction should be rendered with specific theme color if is leading 1`] = ` - - - - - - - - - menu - - - - - - - -`; - -exports[`AppbarAction should render AppbarBackAction with custom color 1`] = ` - - - - - - - - - - - - - - - - -`; diff --git a/src/components/__tests__/Appbar/utils.test.ts b/src/components/__tests__/Appbar/utils.test.ts new file mode 100644 index 0000000000..dff18b2684 --- /dev/null +++ b/src/components/__tests__/Appbar/utils.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from '@jest/globals'; + +import { getAppbarHeight, getTrailingActionsWidth } from '../../Appbar/utils'; + +describe('getAppbarHeight', () => { + it.each([ + { variant: 'search', subtitle: false, height: 64 }, + { variant: 'search', subtitle: true, height: 64 }, + { variant: 'small', subtitle: false, height: 64 }, + { variant: 'small', subtitle: true, height: 64 }, + { variant: 'medium-flexible', subtitle: false, height: 112 }, + { variant: 'medium-flexible', subtitle: true, height: 136 }, + { variant: 'large-flexible', subtitle: false, height: 120 }, + { variant: 'large-flexible', subtitle: true, height: 152 }, + ] as const)( + 'returns $height for $variant with subtitle=$subtitle', + ({ variant, subtitle, height }) => { + expect(getAppbarHeight(variant, subtitle)).toBe(height); + } + ); +}); + +describe('getTrailingActionsWidth', () => { + it('accounts for standard, filled, and wide filled actions', () => { + expect(getTrailingActionsWidth([])).toBe(0); + expect( + getTrailingActionsWidth([ + { key: 'first', icon: 'star', 'aria-label': 'First' }, + { key: 'second', icon: 'heart', 'aria-label': 'Second' }, + ]) + ).toBe(96); + expect( + getTrailingActionsWidth([ + { + key: 'filled', + icon: 'star', + 'aria-label': 'Filled', + variant: 'filled', + }, + ]) + ).toBe(48); + expect( + getTrailingActionsWidth([ + { + key: 'wide', + icon: 'star', + 'aria-label': 'Wide', + variant: 'tonal', + width: 'wide', + }, + ]) + ).toBe(64); + }); +}); diff --git a/src/index.tsx b/src/index.tsx index 1807d2f249..f1c8603f90 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -54,11 +54,21 @@ export { default as Text, customText } from './components/Typography/Text'; // Types export type { Props as ActivityIndicatorProps } from './components/ActivityIndicator'; -export type { Props as AppbarProps } from './components/Appbar/Appbar'; -export type { Props as AppbarActionProps } from './components/Appbar/AppbarAction'; -export type { Props as AppbarBackActionProps } from './components/Appbar/AppbarBackAction'; -export type { Props as AppbarContentProps } from './components/Appbar/AppbarContent'; -export type { Props as AppbarHeaderProps } from './components/Appbar/AppbarHeader'; +export type { + Props as AppbarProps, + AppbarActionDecorator, + AppbarFilledTrailingAction, + AppbarHeadlineAlignment, + AppbarHeadlinePressableProps, + AppbarHeadlineTextProps, + AppbarLeadingButton, + AppbarSearchbarProps, + AppbarStandardTrailingAction, + AppbarTextProps, + AppbarTrailingAction, + AppbarTrailingActions, + AppbarVariant, +} from './components/Appbar'; export type { Props as AvatarIconProps } from './components/Avatar/AvatarIcon'; export type { Props as AvatarImageProps } from './components/Avatar/AvatarImage'; export type { Props as AvatarTextProps } from './components/Avatar/AvatarText'; From 3cff4863d6fa00a4b0a90689db0ce178606b5188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kata?= Date: Fri, 11 Sep 2026 09:07:14 +0200 Subject: [PATCH 2/3] feat(appbar): remove unnecessary derived testID --- docs/6.x/docs/guides/migration.md | 5 +++- src/components/Appbar/Appbar.tsx | 12 +++------ src/components/Appbar/AppbarContent.tsx | 17 +++--------- .../__tests__/Appbar/Appbar.test.tsx | 27 ++++++++++--------- 4 files changed, 24 insertions(+), 37 deletions(-) diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index 7ccb666525..a8971aeacf 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -72,7 +72,6 @@ You can use the component's color prop where available, or override the correspo Hardcoded default test IDs have been removed for the components listed below: -- `Appbar`: `${testID}-root-layer` - `BottomNavigation`: `bottom-navigation` - `BottomNavigation.Bar`: `bottom-navigation-bar` - `Button`: `button` @@ -156,6 +155,10 @@ const MyComponent = () => ( Material Design 3 drops the bottom bar support contained previously in the `Appbar` scope and moves it to `Toolbars`, hence you can't use the component to construct a bottom bar anymore - for these cases please use the `Toolbar` component. +#### Test IDs + +`Appbar` no longer derives internal, implementation-only test IDs (e.g. for its surface, content, or search layout wrappers) from the `testID` prop. Only the `testID` prop itself is set on the root element; the `Searchbar` rendered by the `search` variant accepts its own `testID` through `searchBar.testID`. + ### Surface - The `elevation` prop no longer accepts a React Native `Animated.Value`. Any `elevation` changes are animated automatically. diff --git a/src/components/Appbar/Appbar.tsx b/src/components/Appbar/Appbar.tsx index e8a0fcce25..12a615509d 100644 --- a/src/components/Appbar/Appbar.tsx +++ b/src/components/Appbar/Appbar.tsx @@ -257,18 +257,15 @@ const Appbar = ({ const { inputStyle: _searchInputStyle, style: _searchStyle, - testID: searchTestID = `${testID}-search`, + testID: searchTestID, ...searchProps } = searchBar; return ( {renderLeadingButton()} - - + + headlineImage ? ( ; - testID: string; }; const headlineVariants: Record = { @@ -57,7 +56,6 @@ const AppbarContent = ({ onHeadlinePress, subtitle, subtitleProps, - testID, theme, variant, style, @@ -97,11 +95,7 @@ const AppbarContent = ({ ); const content = hasHeadlineImage ? ( - + {headlineImage} ) : ( @@ -132,11 +126,6 @@ const AppbarContent = ({ ); - const wrapperProps = { - testID, - style: wrapperStyle, - }; - if (onHeadlinePress) { const { 'aria-label': ariaLabel, @@ -147,7 +136,7 @@ const AppbarContent = ({ return ( { ); - expect(screen.getByTestId('appbar-content')).toHaveStyle({ + expect(screen.getByText('Inbox').parent).toHaveStyle({ marginStart: 12, }); @@ -182,7 +182,7 @@ describe('Appbar content', () => { /> ); - expect(screen.getByTestId('appbar-content')).toHaveStyle({ + expect(screen.getByText('Inbox').parent).toHaveStyle({ marginStart: 4, }); }); @@ -199,7 +199,7 @@ describe('Appbar content', () => { /> ); - expect(screen.getByTestId('appbar-content')).toHaveStyle({ + expect(screen.getByText('Inbox').parent).toHaveStyle({ flexBasis: 'auto', }); } @@ -213,7 +213,7 @@ describe('Appbar surface', () => { ); - expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + expect(screen.getByTestId(testIDPrefix).parent).toHaveStyle({ backgroundColor: LightTheme.colors.surface, }); @@ -226,7 +226,7 @@ describe('Appbar surface', () => { /> ); - expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + expect(screen.getByTestId(testIDPrefix).parent).toHaveStyle({ backgroundColor: LightTheme.colors.surfaceContainer, }); @@ -240,7 +240,7 @@ describe('Appbar surface', () => { /> ); - expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + expect(screen.getByTestId(testIDPrefix).parent).toHaveStyle({ backgroundColor: customBackground, }); }); @@ -267,7 +267,7 @@ describe('Appbar surface', () => { ); - expect(screen.getByTestId('appbar-root-layer')).toHaveStyle({ + expect(screen.getByTestId(testIDPrefix).parent).toHaveStyle({ borderBottomLeftRadius: 16, borderBottomRightRadius: 16, paddingTop: 20, @@ -468,7 +468,9 @@ describe('Appbar search', () => { expect( screen.getByRole('searchbox', { name: 'Search messages' }) ).toBeOnTheScreen(); - expect(screen.getByTestId('appbar-search').parent).toHaveStyle({ + expect( + screen.getByRole('searchbox', { name: 'Search messages' }).parent + ).toHaveStyle({ backgroundColor: LightTheme.colors.surfaceContainer, }); @@ -549,7 +551,7 @@ describe('Appbar search', () => { expect(screen.getByTestId('message-search').parent).toHaveStyle({ width: '100%', }); - expect(screen.getByTestId('appbar-search-width-limiter')).toHaveStyle({ + expect(screen.getByTestId('message-search').parent?.parent).toHaveStyle({ width: '100%', maxWidth: 720, }); @@ -750,10 +752,9 @@ describe('Appbar accessibility', () => { ) ); - const imageContainer = screen.getByTestId( - 'appbar-content-headline-image', - { includeHiddenElements: true } - ); + const imageContainer = screen.getByLabelText('Brand artwork', { + includeHiddenElements: true, + }).parent?.parent; expect(imageContainer).toHaveProp('aria-hidden', true); expect( From 150434e497a4fe9ad1bfc8a6a5c565564e78d76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kata?= Date: Fri, 25 Sep 2026 13:50:07 +0200 Subject: [PATCH 3/3] feat(appbar): flatten props structure --- docs/6.x/docs/guides/migration.md | 6 + docs/component-docs.config.ts | 28 ++--- src/components/Appbar/Appbar.tsx | 111 +++++++++++++++++- src/components/Appbar/AppbarContent.tsx | 2 +- src/components/Appbar/index.ts | 2 +- src/components/Appbar/types.ts | 95 +-------------- .../__tests__/Appbar/Appbar.test.tsx | 90 ++++++++++++++ src/index.tsx | 2 +- 8 files changed, 221 insertions(+), 115 deletions(-) diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index a8971aeacf..83d46aaa0c 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -151,6 +151,12 @@ const MyComponent = () => ( ); ``` +`Appbar`'s props are a single flat type shared by every `variant`. Passing a prop to a variant it doesn't apply to doesn't raise a type error — each prop's own JSDoc states which variant(s) it's usable for: + +- Usable only for `small`, `medium-flexible`, and `large-flexible`: `headline`, `subtitle`, `headlineProps`, `subtitleProps`, `headlineImage`, `headlineAlignment`, `contentStyle`, `onHeadlinePress`, `headlinePressableProps`. Omitting `headline` logs a development warning. +- Usable only for `search`: `searchBar`. Omitting it logs a development warning. +- Usable for every variant, including `search`: `trailingActions`, using the same shape — a standard array, or a single filled/tonal action — everywhere, though a filled/tonal action isn't intended for `search`. + #### Bottom toolbar support Material Design 3 drops the bottom bar support contained previously in the `Appbar` scope and moves it to `Toolbars`, hence you can't use the component to construct a bottom bar anymore - for these cases please use the `Toolbar` component. diff --git a/docs/component-docs.config.ts b/docs/component-docs.config.ts index 19eebcb076..f17a768626 100644 --- a/docs/component-docs.config.ts +++ b/docs/component-docs.config.ts @@ -21,13 +21,6 @@ export type Pages = Record>; type ComponentDocsConfig = { sourceRootDir: string; pages: Pages; - typescriptProps: Record< - string, - { - sourcePath: string; - typeName: string; - } - >; customFields: { moreExamples: Record>; knownIssues: Record>; @@ -39,7 +32,13 @@ type ComponentDocsConfig = { const pages = { ActivityIndicator: 'ActivityIndicator', - Appbar: 'Appbar/Appbar', + Appbar: { + source: 'Appbar/Appbar', + // The default export is the memoized `MemoizedAppbar`, but the JSDoc + // lives on the inner `Appbar` declaration — same pattern as `Portal` + // and `Typography/Text` below. + component: 'Appbar', + }, Avatar: { AvatarIcon: 'Avatar/AvatarIcon', AvatarImage: 'Avatar/AvatarImage', @@ -167,19 +166,6 @@ const pages = { const componentDocsConfig: ComponentDocsConfig = { sourceRootDir: path.join(__dirname, '..', 'src', 'components'), pages, - typescriptProps: { - 'Appbar/Appbar': { - sourcePath: path.join( - __dirname, - '..', - 'src', - 'components', - 'Appbar', - 'types.ts' - ), - typeName: 'Props', - }, - }, customFields: { moreExamples: { Portal: { diff --git a/src/components/Appbar/Appbar.tsx b/src/components/Appbar/Appbar.tsx index 12a615509d..4cd2ad471b 100644 --- a/src/components/Appbar/Appbar.tsx +++ b/src/components/Appbar/Appbar.tsx @@ -1,12 +1,27 @@ import * as React from 'react'; import { StyleSheet, View } from 'react-native'; -import type { ColorValue, ViewProps, ViewStyle } from 'react-native'; +import type { + ColorValue, + GestureResponderEvent, + StyleProp, + ViewProps, + ViewStyle, +} from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import AppbarButton from './AppbarButton'; import AppbarContent from './AppbarContent'; -import type { Props } from './types'; +import type { + AppbarBaseProps, + AppbarHeadlineAlignment, + AppbarHeadlinePressableProps, + AppbarHeadlineTextProps, + AppbarSearchbarProps, + AppbarTextProps, + AppbarTrailingActions, + AppbarVariant, +} from './types'; import { APPBAR_HEADLINE_IMAGE_HEIGHT, APPBAR_ICON_BUTTON_SIZE, @@ -21,6 +36,75 @@ import Surface from '../Surface'; const EMPTY_TRAILING_ACTIONS = [] as const; +export type Props = AppbarBaseProps & { + /** Visual and layout variant of the app bar. */ + variant: AppbarVariant; + /** + * Written headline displayed by the app bar. Usable for the `small`, + * `medium-flexible`, and `large-flexible` variants — it is the accessible + * heading for those variants (including when `headlineImage` visually + * replaces it on `small`) and omitting it logs a development warning. + */ + headline?: string; + /** + * Optional supporting text displayed below the headline. Usable for the + * `small`, `medium-flexible`, and `large-flexible` variants. + */ + subtitle?: string; + /** + * Props applied to the headline heading. Usable for the `small`, + * `medium-flexible`, and `large-flexible` variants. + */ + headlineProps?: AppbarHeadlineTextProps; + /** + * Props applied to the subtitle text. Usable for the `small`, + * `medium-flexible`, and `large-flexible` variants. + */ + subtitleProps?: AppbarTextProps; + /** + * Image or logo displayed in the app bar. It should fit within 32dp + * height. Usable for the `small`, `medium-flexible`, and `large-flexible` + * variants. On `small` it replaces the visible headline text (`headline` + * is still required for accessibility labeling). On `medium-flexible` and + * `large-flexible` it is shown alongside the written headline and + * subtitle. + */ + headlineImage?: React.ReactElement; + /** + * Headline and subtitle alignment. Usable for the `small`, + * `medium-flexible`, and `large-flexible` variants. + */ + headlineAlignment?: AppbarHeadlineAlignment; + /** + * Style applied to the headline and subtitle area. Usable for the + * `small`, `medium-flexible`, and `large-flexible` variants. + */ + contentStyle?: StyleProp; + /** + * Called when the headline area is pressed. Usable for the `small`, + * `medium-flexible`, and `large-flexible` variants, independently of + * `headlinePressableProps`. + */ + onHeadlinePress?: (event: GestureResponderEvent) => void; + /** + * Props applied to the interactive headline area. Usable for the `small`, + * `medium-flexible`, and `large-flexible` variants; inert unless + * `onHeadlinePress` is also set. + */ + headlinePressableProps?: AppbarHeadlinePressableProps; + /** + * Trailing actions. Usable for every variant, including `search`. A + * single filled or tonal action isn't intended for `search`, but this + * isn't enforced at the type level. + */ + trailingActions?: AppbarTrailingActions; + /** + * Props forwarded to the existing Paper Searchbar. Usable only for the + * `search` variant; omitting it logs a development warning. + */ + searchBar?: AppbarSearchbarProps; +}; + /** * A Material Design app bar for displaying a page headline, navigation, and * contextual actions. Appbar supports small, medium flexible, large flexible, @@ -140,6 +224,15 @@ const EMPTY_TRAILING_ACTIONS = [] as const; * `variant="medium-flexible"` and `variant="large-flexible"`; centered * content uses `headlineAlignment="center"`. * + * `Appbar`'s props are a single flat type: every prop is available on every + * `variant`, and each prop's own JSDoc states which variant(s) it's usable + * for (for example, `headline` and `headlineImage` are usable for `small`, + * `medium-flexible`, and `large-flexible`, while `searchBar` is usable only + * for `search`). Passing a prop to a variant it doesn't apply to doesn't + * raise a type error; it's either silently ignored by that variant's render + * logic, or — for a missing `headline` on a non-search variant, or a + * missing `searchBar` on `search` — logged as a development warning. + * * ## Bottom toolbar support * * Material Design 3 drops the bottom bar support contained previously in the Appbar scope @@ -172,6 +265,19 @@ const Appbar = ({ ...rest }: Props) => { const theme = useInternalTheme(themeOverrides); + + if (variant !== 'search' && !headline) { + console.warn( + `Appbar variant "${variant}" is missing a headline, so it has no accessible heading` + ); + } + + if (variant === 'search' && !searchBar) { + console.warn( + 'Appbar variant "search" is missing a searchBar, so its body will render blank' + ); + } + const detectedInsets = useSafeAreaInsets(); const { customBackground, restStyle, borderRadius } = React.useMemo(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion @@ -461,3 +567,4 @@ export default MemoizedAppbar; // @component-docs ignore-next-line export { MemoizedAppbar as Appbar }; +export type { Props as AppbarProps }; diff --git a/src/components/Appbar/AppbarContent.tsx b/src/components/Appbar/AppbarContent.tsx index 0219cd220b..c28d221af1 100644 --- a/src/components/Appbar/AppbarContent.tsx +++ b/src/components/Appbar/AppbarContent.tsx @@ -3,9 +3,9 @@ import { Pressable, StyleSheet, View } from 'react-native'; import type { StyleProp, ViewStyle } from 'react-native'; import type { + AppbarProps, AppbarHeadlineAlignment, AppbarHeadlineVariant, - Props as AppbarProps, } from './types'; import { APPBAR_HEADLINE_IMAGE_HEIGHT } from './utils'; import type { Theme, TypescaleKey } from '../../theme/types'; diff --git a/src/components/Appbar/index.ts b/src/components/Appbar/index.ts index c996944de4..013065cb60 100644 --- a/src/components/Appbar/index.ts +++ b/src/components/Appbar/index.ts @@ -13,5 +13,5 @@ export type { AppbarTrailingAction, AppbarTrailingActions, AppbarVariant, - Props, } from './types'; +export type { AppbarProps } from './Appbar'; diff --git a/src/components/Appbar/types.ts b/src/components/Appbar/types.ts index 1335deae18..f119ae6904 100644 --- a/src/components/Appbar/types.ts +++ b/src/components/Appbar/types.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import type { ColorValue, - GestureResponderEvent, StyleProp, TextStyle, View, @@ -115,45 +114,7 @@ type AppbarBackButton = Omit< export type AppbarLeadingButton = AppbarLeadingIconButton | AppbarBackButton; -type AppbarWrittenHeadline = { - /** Written headline displayed by the app bar. */ - headline: string; - /** Optional supporting text displayed below the headline. */ - subtitle?: string; - /** Props applied to the headline heading. */ - headlineProps?: AppbarHeadlineTextProps; - /** Props applied to the subtitle text. */ - subtitleProps?: AppbarTextProps; -}; - -type AppbarHeadlineImage = { - /** Image or logo displayed in the app bar. It should fit within 32dp height. */ - headlineImage: React.ReactElement; -}; - -type AppbarTextHeadline = AppbarWrittenHeadline & { - /** Visual and layout variant of the app bar. */ - variant: AppbarHeadlineVariant; - headlineImage?: never; -}; - -type AppbarSmallImageHeadline = AppbarHeadlineImage & { - /** Visual and layout variant of the app bar. */ - variant: 'small'; - /** Accessible page headline. The image replaces this text visually. */ - headline: string; - subtitle?: never; - headlineProps?: never; - subtitleProps?: never; -}; - -type AppbarFlexibleImageHeadline = AppbarWrittenHeadline & - AppbarHeadlineImage & { - /** Visual and layout variant of the app bar. */ - variant: Exclude; - }; - -type AppbarBaseProps = Omit< +export type AppbarBaseProps = Omit< ViewProps, 'accessibilityLabel' | 'accessibilityRole' | 'children' | 'style' | 'testID' > & { @@ -179,27 +140,6 @@ type AppbarBaseProps = Omit< testID?: string; }; -type AppbarHeadlineProps = { - /** Headline and subtitle alignment. */ - headlineAlignment?: AppbarHeadlineAlignment; - /** Trailing actions. */ - trailingActions?: AppbarTrailingActions; - /** Style applied to the headline and subtitle area. */ - contentStyle?: StyleProp; - searchBar?: never; -} & ( - | { - /** Called when the headline area is pressed. */ - onHeadlinePress: (event: GestureResponderEvent) => void; - /** Props applied to the interactive headline area. */ - headlinePressableProps?: AppbarHeadlinePressableProps; - } - | { - onHeadlinePress?: never; - headlinePressableProps?: never; - } -); - export type AppbarSearchbarProps = Omit< SearchbarProps, 'elevation' | 'mode' | 'showDivider' | 'theme' @@ -208,31 +148,8 @@ export type AppbarSearchbarProps = Omit< placeholder: string; }; -type AppbarSearchProps = { - /** Visual and layout variant of the app bar. */ - variant: 'search'; - /** Props forwarded to the existing Paper Searchbar. */ - searchBar: AppbarSearchbarProps; - /** Exterior trailing actions. */ - trailingActions?: readonly AppbarStandardTrailingAction[]; - headline?: never; - subtitle?: never; - headlineImage?: never; - headlineAlignment?: never; - headlineProps?: never; - subtitleProps?: never; - onHeadlinePress?: never; - headlinePressableProps?: never; - contentStyle?: never; -}; - -export type Props = AppbarBaseProps & - ( - | (AppbarHeadlineProps & - ( - | AppbarTextHeadline - | AppbarSmallImageHeadline - | AppbarFlexibleImageHeadline - )) - | AppbarSearchProps - ); +// `Props` is declared in `Appbar.tsx` (co-located with the `Appbar` component) +// so the docs-generation parser — which looks for a `Props` type-alias +// declaration in the same source file as the component it documents — can +// find it. +export type { Props as AppbarProps } from './Appbar'; diff --git a/src/components/__tests__/Appbar/Appbar.test.tsx b/src/components/__tests__/Appbar/Appbar.test.tsx index 486acbc46a..471c509d77 100644 --- a/src/components/__tests__/Appbar/Appbar.test.tsx +++ b/src/components/__tests__/Appbar/Appbar.test.tsx @@ -204,6 +204,22 @@ describe('Appbar content', () => { }); } ); + + it('renders only the headline image and drops the subtitle on a small app bar', async () => { + await render( + } + subtitle="3 unread" + /> + ); + + expect( + screen.getByTestId('brand-mark', { includeHiddenElements: true }) + ).toBeOnTheScreen(); + expect(screen.queryByText('3 unread')).not.toBeOnTheScreen(); + }); }); describe('Appbar surface', () => { @@ -556,6 +572,55 @@ describe('Appbar search', () => { maxWidth: 720, }); }); + + it('warns when a search app bar is missing a searchBar', async () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + await render(); + + expect(console.warn).toHaveBeenCalledWith( + 'Appbar variant "search" is missing a searchBar, so its body will render blank' + ); + + jest.restoreAllMocks(); + }); + + it('does not warn when a search app bar has a searchBar', async () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + await render( + + ); + + expect(console.warn).not.toHaveBeenCalled(); + + jest.restoreAllMocks(); + }); + + it('renders a filled trailing action on a search app bar', async () => { + await render( + + ); + + expect(screen.getByTestId('expressive-action').parent).toHaveStyle({ + backgroundColor: LightTheme.colors.primary, + }); + }); }); describe('Appbar accessibility', () => { @@ -785,6 +850,31 @@ describe('Appbar accessibility', () => { ]); }); + it.each(writtenHeadlineVariants)( + 'warns when a %s app bar is missing a headline', + async (variant) => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + await render(); + + expect(console.warn).toHaveBeenCalledWith( + `Appbar variant "${variant}" is missing a headline, so it has no accessible heading` + ); + + jest.restoreAllMocks(); + } + ); + + it('does not warn when a written headline variant has a headline', async () => { + jest.spyOn(console, 'warn').mockImplementation(() => {}); + + await render(); + + expect(console.warn).not.toHaveBeenCalled(); + + jest.restoreAllMocks(); + }); + it('does not remount a surviving trailing action when its configuration changes', async () => { const { rerender } = await render(