Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/component-docs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ const pages = {
props: 'TextInputAccessoryProps',
},
},
Toolbar: {
Toolbar: 'Toolbar/Toolbar',
},
Tooltip: {
Tooltip: 'Tooltip/Tooltip',
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions docs/src/data/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ export const screenshots = {
filled: 'screenshots/text-input-filled.png',
outlined: 'screenshots/text-input-outlined.png',
},
Toolbar: {
'floating horizontal':
'screenshots/toolbar_floating_horizontal_standard.png',
'floating horizontal - vibrant':
'screenshots/toolbar_floating_horizontal_vibrant.png',
'floating vertical': 'screenshots/toolbar_floating_vertical_standard.png',
'floating vertical - vibrant':
'screenshots/toolbar_floating_vertical_vibrant.png',
docked: 'screenshots/toolbar_docked_standard.png',
'docked - vibrant': 'screenshots/toolbar_docked_vibrant.png',
},
Tooltip: 'screenshots/tooltip.png',
TouchableRipple: 'screenshots/touchable-ripple.gif',
};
24 changes: 24 additions & 0 deletions docs/src/data/themeColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,30 @@ export const themeColors = {
textColor: 'theme.colors.onSurface',
},
},
Toolbar: {
standard: {
unselected: {
backgroundColor: 'theme.colors.surfaceContainer',
iconColor: 'theme.colors.onSurfaceVariant',
textColor: 'theme.colors.onSurfaceVariant',
},
selected: {
backgroundColor: 'theme.colors.secondaryContainer',
iconColor: 'theme.colors.onSecondaryContainer',
},
},
vibrant: {
unselected: {
backgroundColor: 'theme.colors.primaryContainer',
iconColor: 'theme.colors.onPrimaryContainer',
textColor: 'theme.colors.onPrimaryContainer',
},
selected: {
backgroundColor: 'theme.colors.surfaceContainer',
iconColor: 'theme.colors.onSurface',
},
},
},
Tooltip: {
'-': {
backgroundColor: 'theme.colors.onSurface',
Expand Down
2 changes: 2 additions & 0 deletions example/src/ExampleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import TextExample from './Examples/TextExample';
import TextInputExample from './Examples/TextInputExample';
import ThemeExample from './Examples/ThemeExample';
import ThemingWithReactNavigation from './Examples/ThemingWithReactNavigation';
import ToolbarExample from './Examples/ToolbarExample';
import TooltipExample from './Examples/TooltipExample';
import TouchableRippleExample from './Examples/TouchableRippleExample';

Expand Down Expand Up @@ -82,6 +83,7 @@ export const mainExamples = {
Switch: SwitchExample,
Text: TextExample,
TextInput: TextInputExample,
Toolbar: ToolbarExample,
TooltipExample,
TouchableRipple: TouchableRippleExample,
Theme: ThemeExample,
Expand Down
236 changes: 236 additions & 0 deletions example/src/Examples/ToolbarExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import * as React from 'react';
import { FlatList, ScrollView, StyleSheet, View } from 'react-native';

import {
Chip,
Divider,
IconButton,
Text,
Toolbar,
useTheme,
} from 'react-native-paper';
import type {
ToolbarColorScheme,
ToolbarOrientation,
ToolbarVariant,
} from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const variants: ToolbarVariant[] = ['floating', 'docked'];
const orientations: ToolbarOrientation[] = ['horizontal', 'vertical'];
const colorSchemes: ToolbarColorScheme[] = ['standard', 'vibrant'];

const toolbarItems = [
{ icon: 'format-bold', label: 'Bold' },
{ icon: 'format-italic', label: 'Italic' },
{ icon: 'format-underline', label: 'Underline' },
] as const;

// Dummy list content, purely to give the screen something to scroll behind
// the toolbar.
const rows = Array.from({ length: 40 }, (_, i) => ({
id: String(i + 1),
text: `Item ${i + 1}`,
}));

type ChipRowProps<T extends string> = {
label: string;
options: readonly T[];
value: T;
onChange: (value: T) => void;
// Controls that don't apply to the current variant stay visible but
// greyed out and non-interactive, per MD3's disabled-state guidance—
// hiding them outright would shift the layout and lose the user's place.
disabled?: boolean;
};

const ChipRow = <T extends string>({
label,
options,
value,
onChange,
disabled = false,
}: ChipRowProps<T>) => (
<View style={[styles.chipRow, disabled && styles.disabled]}>
<Text variant="labelLarge" style={styles.chipRowLabel}>
{label}
</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.chipRowContent}
>
{options.map((option) => (
<Chip
key={option}
selected={option === value}
showSelectedOverlay
disabled={disabled}
onPress={() => onChange(option)}
>
{option}
</Chip>
))}
</ScrollView>
</View>
);

const ToolbarExample = () => {
const theme = useTheme();
const insets = useSafeAreaInsets();

const [variant, setVariant] = React.useState<ToolbarVariant>('floating');
const [orientation, setOrientation] =
React.useState<ToolbarOrientation>('horizontal');
const [colorScheme, setColorScheme] =
React.useState<ToolbarColorScheme>('standard');

const isFloating = variant === 'floating';
const isVertical = isFloating && orientation === 'vertical';

const renderItem = React.useCallback(
({ item }: { item: (typeof rows)[number] }) => (
<View style={styles.listItem}>
<Text variant="bodyLarge">{item.text}</Text>
</View>
),
[]
);

const toolbarChildren = (
<>
{toolbarItems.map(({ icon, label }) => (
<IconButton
key={label}
icon={icon}
aria-label={label}
onPress={() => {}}
/>
))}
</>
);

return (
<View
style={[styles.container, { backgroundColor: theme.colors.background }]}
>
<View style={styles.controls}>
<ChipRow
label="Variant"
options={variants}
value={variant}
onChange={setVariant}
/>
<ChipRow
label="Orientation"
options={orientations}
value={orientation}
onChange={setOrientation}
disabled={!isFloating}
/>
<ChipRow
label="Color scheme"
options={colorSchemes}
value={colorScheme}
onChange={setColorScheme}
/>
<Divider
bold
style={[styles.divider, { backgroundColor: theme.colors.outline }]}
/>
</View>
<FlatList
style={styles.list}
data={rows}
renderItem={renderItem}
keyExtractor={(item) => item.id}
contentContainerStyle={[
styles.listContent,
!isFloating
? { paddingBottom: insets.bottom + 64 }
: isVertical
? { paddingBottom: insets.bottom + 24 }
: { paddingBottom: insets.bottom + 96 },
]}
/>
{isFloating ? (
<View
pointerEvents="box-none"
style={
isVertical
? [styles.verticalAnchor, { right: insets.right + 24 }]
: [styles.horizontalAnchor, { bottom: insets.bottom + 16 }]
}
>
<Toolbar
variant={variant}
colorScheme={colorScheme}
orientation={orientation}
>
{toolbarChildren}
</Toolbar>
</View>
) : (
<Toolbar
variant={variant}
colorScheme={colorScheme}
orientation={orientation}
>
{toolbarChildren}
</Toolbar>
)}
</View>
);
};

ToolbarExample.title = 'Toolbar';

const styles = StyleSheet.create({
container: {
flex: 1,
},
horizontalAnchor: {
position: 'absolute',
left: 0,
right: 0,
alignItems: 'center',
},
verticalAnchor: {
position: 'absolute',
top: 0,
bottom: 0,
justifyContent: 'center',
},
controls: {
paddingTop: 8,
paddingBottom: 8,
},
divider: {
marginTop: 8,
},
chipRow: {
paddingVertical: 4,
},
chipRowLabel: {
paddingHorizontal: 16,
paddingBottom: 6,
},
chipRowContent: {
paddingHorizontal: 16,
gap: 8,
},
disabled: {
opacity: 0.38,
},
list: {
flex: 1,
},
listContent: {
paddingHorizontal: 16,
},
listItem: {
paddingVertical: 12,
},
});

export default ToolbarExample;
20 changes: 19 additions & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import Icon from '../Icon';
import type { IconSource } from '../Icon';
import Surface from '../Surface';
import type { SurfaceStyle } from '../Surface';
import {
resolveLabelColor as resolveToolbarLabelColor,
ToolbarColorContext,
} from '../Toolbar/ToolbarColorContext';
import TouchableRipple from '../TouchableRipple/TouchableRipple';
import type { Props as TouchableRippleProps } from '../TouchableRipple/TouchableRipple';
import Text from '../Typography/Text';
Expand Down Expand Up @@ -245,6 +249,20 @@ const Button = ({
const borderRadius = theme.shapes.corner.largeIncreased;
const iconSize = 18;

// A mode-less (`'text'`) `Button` inside a `Toolbar` picks up its ambient
// label color, unless it already has its own spec-defined coloring (a
// non-`'text'` `mode` or an explicit color prop).
const toolbarColors = React.useContext(ToolbarColorContext);
const hasOwnColoring =
mode !== 'text' || customTextColor != null || customButtonColor != null;
const resolvedCustomTextColor =
toolbarColors && !hasOwnColoring
? resolveToolbarLabelColor({
theme: toolbarColors.theme,
colorScheme: toolbarColors.colorScheme,
})
: customTextColor;

const {
backgroundColor,
borderColor,
Expand All @@ -254,7 +272,7 @@ const Button = ({
backgroundOpacity,
} = getButtonColors({
customButtonColor,
customTextColor,
customTextColor: resolvedCustomTextColor,
theme,
mode,
disabled,
Expand Down
Loading
Loading