Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-worklets/plugin'],
};
10 changes: 10 additions & 0 deletions docs/docs/guides/01-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ npm install react-native-paper
npm install react-native-safe-area-context
```

- You also need to install [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/) and [react-native-worklets](https://docs.swmansion.com/react-native-worklets/) for animations.

```bash npm2yarn
npm install react-native-reanimated react-native-worklets
```

:::note
If you're using a bare React Native project (not Expo), you need to add `react-native-worklets/plugin` to your `babel.config.js` plugins array. See the [Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) for details.
:::

Additionaly for `iOS` platform there is a requirement to link the native parts of the library:

```bash
Expand Down
6 changes: 6 additions & 0 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ const config = {
TextInputAffix: 'TextInput/Adornment/TextInputAffix',
TextInputIcon: 'TextInput/Adornment/TextInputIcon',
},
TextField: {
TextField: 'TextField/TextField',
TextFieldIcon: 'TextField/TextFieldIcon',
},
ToggleButton: {
ToggleButton: 'ToggleButton/ToggleButton',
ToggleButtonGroup: 'ToggleButton/ToggleButtonGroup',
Expand Down Expand Up @@ -210,6 +214,8 @@ const config = {
'src/components/TextInput/Adornment/TextInputAffix.tsx',
TextInputIcon:
'src/components/TextInput/Adornment/TextInputIcon.tsx',
TextField: 'src/components/TextField/TextField.tsx',

Text: 'src/components/Typography/Text.tsx',
showcase: 'docs/src/components/Showcase.tsx',
};
Expand Down
14 changes: 12 additions & 2 deletions docs/src/components/PropTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,25 @@ const typeDefinitions = {
'https://github.com/callstack/react-native-paper/blob/main/src/components/Icon.tsx#L16',
ThemeProp:
'https://callstack.github.io/react-native-paper/docs/guides/theming#theme-properties',
'ComponentType<TextFieldAccessoryProps>':
'https://github.com/callstack/react-native-paper/blob/main/src/components/TextField/TextField.tsx#L20',
AccessibilityState:
'https://reactnative.dev/docs/accessibility#accessibilitystate',
'StyleProp<ViewStyle>': 'https://reactnative.dev/docs/view-style-props',
'StyleProp<TextStyle>': 'https://reactnative.dev/docs/text-style-props',
TextProps: 'https://reactnative.dev/docs/text#props',
AccessibilityProps:
'https://reactnative.dev/docs/accessibility#accessibilityprops',
};

const renderBadge = (annotation: string) => {
const [annotType, ...annotLabel] = annotation.split(' ');

// eslint-disable-next-line prettier/prettier
return `<span class="badge badge-${annotType.replace('@', '')} ">${annotLabel.join(' ')}</span>`;
return `<span class="badge badge-${annotType.replace(
'@',
''
)} ">${annotLabel.join(' ')}</span>`;
};

export default function PropTable({
Expand Down Expand Up @@ -56,7 +64,9 @@ export default function PropTable({
if (line.includes('@')) {
const annotIndex = line.indexOf('@');
// eslint-disable-next-line prettier/prettier
return `${line.substr(0, annotIndex)} ${renderBadge(line.substr(annotIndex))}`;
return `${line.substr(0, annotIndex)} ${renderBadge(
line.substr(annotIndex)
)}`;
} else {
return line;
}
Expand Down
4 changes: 4 additions & 0 deletions docs/src/data/screenshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ const screenshots = {
},
'TextInput.Affix': 'screenshots/textinput-outline.affix.png',
'TextInput.Icon': 'screenshots/textinput-flat.icon.png',
TextField: {
filled: 'screenshots/text-field-filled.png',
outlined: 'screenshots/text-field-outlined.png',
},
ToggleButton: 'screenshots/toggle-button.png',
'ToggleButton.Group': 'screenshots/toggle-button-group.gif',
'ToggleButton.Row': 'screenshots/toggle-button-row.gif',
Expand Down
Binary file added docs/static/screenshots/text-field-filled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/static/screenshots/text-field-outlined.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
"react-native": "0.81.4",
"react-native-gesture-handler": "~2.28.0",
"react-native-monorepo-config": "^0.1.6",
"react-native-reanimated": "~4.1.1",
"react-native-reanimated": "^4.3.0",
"react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.16.0",
"react-native-web": "^0.21.0",
"react-native-worklets": "0.5.1",
"react-native-worklets": "^0.8.1",
"typeface-roboto": "^1.1.13"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions example/src/ExampleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import SwitchExample from './Examples/SwitchExample';
import TeamDetails from './Examples/TeamDetails';
import TeamsList from './Examples/TeamsList';
import TextExample from './Examples/TextExample';
import TextFieldExample from './Examples/TextFieldExample';
import TextInputExample from './Examples/TextInputExample';
import ThemeExample from './Examples/ThemeExample';
import ThemingWithReactNavigation from './Examples/ThemingWithReactNavigation';
Expand Down Expand Up @@ -90,6 +91,7 @@ export const mainExamples: Record<
switch: SwitchExample,
text: TextExample,
textInput: TextInputExample,
textField: TextFieldExample,
toggleButton: ToggleButtonExample,
tooltipExample: TooltipExample,
touchableRipple: TouchableRippleExample,
Expand Down
244 changes: 244 additions & 0 deletions example/src/Examples/TextFieldExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
import * as React from 'react';
Comment thread
adrcotfas marked this conversation as resolved.
import {
StyleSheet,
TextInput,
View,
type TextStyle,
type ViewStyle,
} from 'react-native';

import {
Divider,
List,
Switch,
Text,
TextField,
TouchableRipple,
type TextFieldAccessoryProps,
type TextFieldVariant,
} from 'react-native-paper';

import { useExampleTheme } from '../hooks/useExampleTheme';
import ScreenWrapper from '../ScreenWrapper';

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

type DemoControls = {
error: boolean;
disabled: boolean;
leadingIcon: boolean;
trailingIcon: boolean;
counter: boolean;
showPrefix: boolean;
showSuffix: boolean;
multiline: boolean;
};

type DemoModifiers = {
label: string;
helperText: string;
placeholder: string;
prefix: string;
suffix: string;
};

// ---------------------------------------------------------------------------
// TextFieldDemo
// ---------------------------------------------------------------------------

type TextFieldDemoProps = {
variant: TextFieldVariant;
};

const TextFieldDemo = ({ variant }: TextFieldDemoProps) => {
const theme = useExampleTheme();

const [value, setValue] = React.useState('');

const [controls, setControls] = React.useState<DemoControls>({
error: false,
disabled: false,
leadingIcon: false,
trailingIcon: false,
counter: false,
showPrefix: false,
showSuffix: false,
multiline: false,
});

const [modifiers, setModifiers] = React.useState<DemoModifiers>({
label: 'Label',
helperText: 'Supporting text',
placeholder: 'Placeholder',
prefix: '$',
suffix: '/100',
});

const toggleControl = (key: keyof DemoControls) =>
setControls((prev) => ({ ...prev, [key]: !prev[key] }));

const setModifier = (key: keyof DemoModifiers, text: string) =>
setModifiers((prev) => ({ ...prev, [key]: text }));

const LeadingIcon = React.useCallback(
(props: TextFieldAccessoryProps) => (
<TextField.Icon {...props} icon="magnify" />
),
[]
);

const TrailingIcon = React.useCallback(
(props: TextFieldAccessoryProps) => (
<TextField.Icon {...props} icon="close" onPress={() => setValue('')} />
),
[]
);

const inputColor = theme.colors.onSurfaceVariant;
const borderColor = theme.colors.outlineVariant;

const modifierInputStyle: TextStyle = {
flex: 1,
color: inputColor,
fontSize: 14,
paddingVertical: 4,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: borderColor,
};

const SWITCH_CONTROLS: { label: string; key: keyof DemoControls }[] = [
{ label: 'Error', key: 'error' },
{ label: 'Disabled', key: 'disabled' },
{ label: 'Leading icon', key: 'leadingIcon' },
{ label: 'Trailing icon', key: 'trailingIcon' },
{ label: 'Counter', key: 'counter' },
{ label: 'Prefix', key: 'showPrefix' },
{ label: 'Suffix', key: 'showSuffix' },
{ label: 'Multiline', key: 'multiline' },
];

const MODIFIER_FIELDS: { label: string; key: keyof DemoModifiers }[] = [
{ label: 'Label', key: 'label' },
{ label: 'Helper', key: 'helperText' },
{ label: 'Placeholder', key: 'placeholder' },
{ label: 'Prefix', key: 'prefix' },
{ label: 'Suffix', key: 'suffix' },
];

return (
<View style={styles.demoContainer}>
{/* Live TextField */}
<TextField
variant={variant}
label={modifiers.label || undefined}
placeholder={modifiers.placeholder || undefined}
supportingText={modifiers.helperText || undefined}
error={controls.error}
editable={!controls.disabled}
value={value}
onChangeText={setValue}
multiline={controls.multiline}
counter={controls.counter}
maxLength={controls.counter ? 100 : undefined}
prefix={controls.showPrefix ? modifiers.prefix : undefined}
suffix={controls.showSuffix ? modifiers.suffix : undefined}
StartAccessory={controls.leadingIcon ? LeadingIcon : undefined}
EndAccessory={controls.trailingIcon ? TrailingIcon : undefined}
/>

<Divider style={styles.divider} />

{/* Controls */}
<List.Subheader style={styles.subheader}>Controls</List.Subheader>
{SWITCH_CONTROLS.map(({ label, key }) => (
<TouchableRipple key={key} onPress={() => toggleControl(key)}>
<View style={styles.switchRow}>
<Text variant="bodyMedium">{label}</Text>
<View pointerEvents="none">
<Switch value={controls[key]} />
</View>
</View>
</TouchableRipple>
))}

<Divider style={styles.divider} />

{/* Modifiers */}
<List.Subheader style={styles.subheader}>Modifiers</List.Subheader>
{MODIFIER_FIELDS.map(({ label, key }) => (
<View key={key} style={styles.modifierRow}>
<Text variant="bodyMedium" style={styles.modifierLabel}>
{label}
</Text>
<TextInput
value={modifiers[key]}
onChangeText={(text) => setModifier(key, text)}
style={modifierInputStyle}
placeholderTextColor={theme.colors.outline}
placeholder={`Enter ${label.toLowerCase()}…`}
/>
</View>
))}
</View>
);
};

// ---------------------------------------------------------------------------
// TextFieldExample
// ---------------------------------------------------------------------------

const TextFieldExample = () => {
return (
<ScreenWrapper contentContainerStyle={styles.container}>
<List.Section title="Filled">
<TextFieldDemo variant="filled" />
</List.Section>
<List.Section title="Outlined">
<TextFieldDemo variant="outlined" />
</List.Section>
</ScreenWrapper>
);
};

TextFieldExample.title = 'TextField';

// ---------------------------------------------------------------------------
// Styles
// ---------------------------------------------------------------------------

const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
paddingVertical: 8,
} satisfies ViewStyle,
demoContainer: {
gap: 4,
} satisfies ViewStyle,
divider: {
marginVertical: 8,
} satisfies ViewStyle,
subheader: {
paddingHorizontal: 0,
} satisfies TextStyle,
switchRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 8,
paddingHorizontal: 8,
} satisfies ViewStyle,
modifierRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
paddingVertical: 8,
paddingHorizontal: 8,
} satisfies ViewStyle,
modifierLabel: {
width: 80,
} satisfies TextStyle,
});

export default TextFieldExample;
1 change: 1 addition & 0 deletions jestSetupAfterEnv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('react-native-reanimated').setUpTests();
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@
"react-dom": "18.3.1",
"react-native": "0.82.1",
"react-native-builder-bob": "^0.21.3",
"react-native-reanimated": "^4.3.0",
"react-native-safe-area-context": "5.5.2",
"react-native-worklets": "^0.8.1",
"react-test-renderer": "19.1.1",
"release-it": "^13.4.0",
"rimraf": "^3.0.2",
Expand All @@ -105,7 +107,9 @@
"peerDependencies": {
"react": "*",
"react-native": "*",
"react-native-safe-area-context": "*"
"react-native-reanimated": ">=4.3.0",
"react-native-safe-area-context": "*",
"react-native-worklets": ">=0.8.1"
},
"husky": {
"hooks": {
Expand All @@ -119,6 +123,7 @@
"<rootDir>/testSetup.js"
],
"setupFilesAfterEnv": [
"<rootDir>/jestSetupAfterEnv.js",
"@testing-library/jest-native/extend-expect"
],
"cacheDirectory": "./cache/jest",
Expand Down
Loading
Loading