Skip to content

Commit

Permalink
fix: input's adornment and helper disabled colors (callstack#3726)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukewalczak committed Mar 6, 2023
1 parent 43a9b17 commit 252b1a6
Show file tree
Hide file tree
Showing 15 changed files with 146 additions and 56 deletions.
2 changes: 1 addition & 1 deletion docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const config = {
AnimatedFAB: 'FAB/AnimatedFAB',
FABGroup: 'FAB/FABGroup',
},
HelperText: 'HelperText',
HelperText: { HelperText: 'HelperText/HelperText' },
IconButton: {
IconButton: 'IconButton/IconButton',
},
Expand Down
33 changes: 33 additions & 0 deletions example/src/Examples/TextInputExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,22 @@ const TextInputExample = () => {
label="Disabled flat input with value"
value="Disabled flat input value"
/>
<TextInput
style={styles.inputContainerStyle}
label="Flat input"
disabled
value="Disabled flat input with adornments"
left={
<TextInput.Icon
icon="magnify"
color={flatLeftIcon}
onPress={() => {
changeIconColor('flatLeftIcon');
}}
/>
}
right={<TextInput.Affix text="/100" />}
/>
<TextInput
mode="outlined"
disabled
Expand All @@ -316,6 +332,23 @@ const TextInputExample = () => {
label="Disabled outlined input"
value="Disabled outlined input with value"
/>
<TextInput
style={styles.inputContainerStyle}
label="Flat input"
disabled
mode="outlined"
value="Disabled flat input with adornments"
left={
<TextInput.Icon
icon="magnify"
color={flatLeftIcon}
onPress={() => {
changeIconColor('flatLeftIcon');
}}
/>
}
right={<TextInput.Affix text="/100" />}
/>
</List.Section>
<List.Section title="Dense inputs">
<TextInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import {
TextStyle,
} from 'react-native';

import color from 'color';

import { useInternalTheme } from '../core/theming';
import type { $Omit, ThemeProp } from '../types';
import AnimatedText from './Typography/AnimatedText';
import { useInternalTheme } from '../../core/theming';
import type { $Omit, ThemeProp } from '../../types';
import AnimatedText from '../Typography/AnimatedText';
import { getTextColor } from './utils';

export type Props = $Omit<
$Omit<React.ComponentPropsWithRef<typeof AnimatedText>, 'padding'>,
Expand All @@ -21,6 +20,10 @@ export type Props = $Omit<
* Type of the helper text.
*/
type: 'error' | 'info';
/**
* Text content of the HelperText.
*/
children: React.ReactNode;
/**
* Whether to display the helper text.
*/
Expand All @@ -30,9 +33,9 @@ export type Props = $Omit<
*/
padding?: 'none' | 'normal';
/**
* Text content of the HelperText.
* Whether the text input tied with helper text is disabled.
*/
children: React.ReactNode;
disabled?: boolean;
style?: StyleProp<TextStyle>;
/**
* @optional
Expand Down Expand Up @@ -86,6 +89,7 @@ const HelperText = ({
theme: themeOverrides,
onLayout,
padding = 'normal',
disabled,
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
Expand Down Expand Up @@ -122,15 +126,7 @@ const HelperText = ({
textHeight = e.nativeEvent.layout.height;
};

const { colors, dark } = theme;

const infoTextColor = theme.isV3
? theme.colors.onSurfaceVariant
: color(theme?.colors?.text)
.alpha(dark ? 0.7 : 0.54)
.rgb()
.string();
const textColor = type === 'error' ? colors?.error : infoTextColor;
const textColor = getTextColor({ theme, disabled, type });

return (
<AnimatedText
Expand Down
30 changes: 30 additions & 0 deletions src/components/HelperText/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import color from 'color';

import type { InternalTheme } from '../../types';

type BaseProps = {
theme: InternalTheme;
disabled?: boolean;
type?: 'error' | 'info';
};

export function getTextColor({ theme, disabled, type }: BaseProps) {
const { colors, dark } = theme;

if (type === 'error') {
return colors?.error;
}

if (theme.isV3) {
if (disabled) {
return theme.colors.onSurfaceDisabled;
} else {
return theme.colors.onSurfaceVariant;
}
}

return color(theme?.colors?.text)
.alpha(dark ? 0.7 : 0.54)
.rgb()
.string();
}
3 changes: 3 additions & 0 deletions src/components/TextInput/Adornment/TextInputAdornment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export interface TextInputAdornmentProps {
paddingHorizontal?: number | string;
maxFontSizeMultiplier?: number | undefined | null;
theme?: ThemeProp;
disabled?: boolean;
}

const TextInputAdornment: React.FunctionComponent<TextInputAdornmentProps> = ({
Expand All @@ -148,6 +149,7 @@ const TextInputAdornment: React.FunctionComponent<TextInputAdornmentProps> = ({
paddingHorizontal,
maxFontSizeMultiplier,
theme,
disabled,
}) => {
if (adornmentConfig.length) {
return (
Expand All @@ -165,6 +167,7 @@ const TextInputAdornment: React.FunctionComponent<TextInputAdornmentProps> = ({
testID: `${side}-${type}-adornment`,
isTextInputFocused,
paddingHorizontal,
disabled,
};
if (type === AdornmentType.Icon) {
return (
Expand Down
16 changes: 7 additions & 9 deletions src/components/TextInput/Adornment/TextInputAffix.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import {
ViewStyle,
} from 'react-native';

import color from 'color';

import { useInternalTheme } from '../../../core/theming';
import type { ThemeProp } from '../../../types';
import { getConstants } from '../helpers';
import { AdornmentSide } from './enums';
import { getTextColor } from './utils';

export type Props = {
/**
Expand All @@ -41,6 +40,7 @@ type ContextState = {
paddingHorizontal?: number | string;
maxFontSizeMultiplier?: number | undefined | null;
testID?: string;
disabled?: boolean;
};

const AffixContext = React.createContext<ContextState>({
Expand All @@ -64,6 +64,7 @@ const AffixAdornment: React.FunctionComponent<
paddingHorizontal,
maxFontSizeMultiplier,
testID,
disabled,
}) => {
return (
<AffixContext.Provider
Expand All @@ -76,6 +77,7 @@ const AffixAdornment: React.FunctionComponent<
paddingHorizontal,
maxFontSizeMultiplier,
testID,
disabled,
}}
>
{affix}
Expand Down Expand Up @@ -132,15 +134,9 @@ const TextInputAffix = ({
paddingHorizontal,
maxFontSizeMultiplier,
testID,
disabled,
} = React.useContext(AffixContext);

const textColor = color(
theme.isV3 ? theme.colors.onSurface : theme.colors?.text
)
.alpha(theme.dark ? 0.7 : 0.54)
.rgb()
.string();

const offset =
typeof paddingHorizontal === 'number' ? paddingHorizontal : AFFIX_OFFSET;

Expand All @@ -149,6 +145,8 @@ const TextInputAffix = ({
[side]: offset,
} as ViewStyle;

const textColor = getTextColor({ theme, disabled });

return (
<Animated.View
style={[
Expand Down
25 changes: 13 additions & 12 deletions src/components/TextInput/Adornment/TextInputIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { IconSource } from '../../Icon';
import IconButton from '../../IconButton/IconButton';
import { ICON_SIZE } from '../constants';
import { getConstants } from '../helpers';
import { getIconColor } from './utils';

export type Props = $Omit<
React.ComponentProps<typeof IconButton>,
Expand Down Expand Up @@ -47,6 +48,7 @@ type StyleContextType = {
isTextInputFocused: boolean;
forceFocus: () => void;
testID: string;
disabled?: boolean;
};

const StyleContext = React.createContext<StyleContextType>({
Expand All @@ -63,6 +65,7 @@ const IconAdornment: React.FunctionComponent<
topPosition: number;
side: 'left' | 'right';
theme?: ThemeProp;
disabled?: boolean;
} & Omit<StyleContextType, 'style'>
> = ({
icon,
Expand All @@ -72,6 +75,7 @@ const IconAdornment: React.FunctionComponent<
forceFocus,
testID,
theme: themeOverrides,
disabled,
}) => {
const { isV3 } = useInternalTheme(themeOverrides);
const { ICON_OFFSET } = getConstants(isV3);
Expand All @@ -80,7 +84,13 @@ const IconAdornment: React.FunctionComponent<
top: topPosition,
[side]: ICON_OFFSET,
};
const contextState = { style, isTextInputFocused, forceFocus, testID };
const contextState = {
style,
isTextInputFocused,
forceFocus,
testID,
disabled,
};

return (
<StyleContext.Provider value={contextState}>{icon}</StyleContext.Provider>
Expand Down Expand Up @@ -125,7 +135,7 @@ const TextInputIcon = ({
theme: themeOverrides,
...rest
}: Props) => {
const { style, isTextInputFocused, forceFocus, testID } =
const { style, isTextInputFocused, forceFocus, testID, disabled } =
React.useContext(StyleContext);

const onPressWithFocusControl = React.useCallback(
Expand All @@ -141,16 +151,7 @@ const TextInputIcon = ({

const theme = useInternalTheme(themeOverrides);

let iconColor = color;

if (theme.isV3) {
if (rest.disabled) {
iconColor = theme.colors.onSurface;
}
iconColor = theme.colors.onSurfaceVariant;
} else {
iconColor = theme.colors.text;
}
const iconColor = getIconColor({ theme, disabled });

return (
<View style={[styles.container, style]}>
Expand Down
33 changes: 33 additions & 0 deletions src/components/TextInput/Adornment/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import color from 'color';

import type { InternalTheme } from '../../../types';

type BaseProps = {
theme: InternalTheme;
disabled?: boolean;
};

export function getTextColor({ theme, disabled }: BaseProps) {
if (theme.isV3) {
if (disabled) {
return theme.colors.onSurfaceDisabled;
}
return theme.colors.onSurfaceVariant;
}
return color(theme.colors?.text)
.alpha(theme.dark ? 0.7 : 0.54)
.rgb()
.string();
}

export function getIconColor({ theme, disabled }: BaseProps) {
if (!theme.isV3) {
return theme.colors.text;
}

if (disabled) {
return theme.colors.onSurfaceDisabled;
}

return theme.colors.onSurfaceVariant;
}
3 changes: 2 additions & 1 deletion src/components/TextInput/TextInputFlat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ const TextInputFlat = ({
onAffixChange,
isTextInputFocused: parentState.focused,
maxFontSizeMultiplier: rest.maxFontSizeMultiplier,
disabled,
};
if (adornmentConfig.length) {
adornmentProps = {
Expand Down Expand Up @@ -340,7 +341,7 @@ const TextInputFlat = ({
},
]}
>
{!isAndroid && multiline && !!label && (
{!isAndroid && multiline && !!label && !disabled && (
// Workaround for: https://github.com/callstack/react-native-paper/issues/2799
// Patch for a multiline TextInput with fixed height, which allow to avoid covering input label with its value.
<View
Expand Down
1 change: 1 addition & 0 deletions src/components/TextInput/TextInputOutlined.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ const TextInputOutlined = ({
onAffixChange,
isTextInputFocused: parentState.focused,
maxFontSizeMultiplier: rest.maxFontSizeMultiplier,
disabled,
};
if (adornmentConfig.length) {
adornmentProps = {
Expand Down
7 changes: 1 addition & 6 deletions src/components/TextInput/helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import color from 'color';

import { MD3LightTheme } from '../../styles/themes';
import type { InternalTheme } from '../../types';
import { AdornmentSide, AdornmentType } from './Adornment/enums';
import type { AdornmentConfig } from './Adornment/types';
Expand Down Expand Up @@ -393,11 +392,7 @@ const getPlaceholderColor = ({ theme, disabled }: BaseProps) => {
const getFlatBackgroundColor = ({ theme, disabled }: BaseProps) => {
if (theme.isV3) {
if (disabled) {
// @ts-ignore According to Figma for both themes the base color for disabled in `onSecondaryContainer`
return color(MD3LightTheme.colors.onSecondaryContainer)
.alpha(0.08)
.rgb()
.string();
return color(theme.colors.onSurface).alpha(0.04).rgb().string();
} else {
return theme.colors.surfaceVariant;
}
Expand Down
Loading

0 comments on commit 252b1a6

Please sign in to comment.