diff options
Diffstat (limited to 'app/components/text-field')
-rw-r--r-- | app/components/text-field/text-field.story.tsx | 159 | ||||
-rw-r--r-- | app/components/text-field/text-field.tsx | 98 |
2 files changed, 257 insertions, 0 deletions
diff --git a/app/components/text-field/text-field.story.tsx b/app/components/text-field/text-field.story.tsx new file mode 100644 index 0000000..74a4da0 --- /dev/null +++ b/app/components/text-field/text-field.story.tsx @@ -0,0 +1,159 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable react-native/no-color-literals */ + +import * as React from "react" +import { storiesOf } from "@storybook/react-native" +import { StoryScreen, Story, UseCase } from "../../../storybook/views" +import { Text, TextField } from "../" +import { State } from "react-powerplug" +import { ViewStyle, TextStyle, Alert } from "react-native" + +declare let module + +const styleArray: ViewStyle[] = [{ paddingHorizontal: 30 }, { borderWidth: 30 }] + +const inputStyleArray: TextStyle[] = [ + { + backgroundColor: "rebeccapurple", + color: "white", + padding: 40, + }, + { + borderWidth: 10, + borderRadius: 4, + borderColor: "#7fff00", + }, +] +let alertWhenFocused = true + +storiesOf("TextField", module) + .addDecorator((fn) => <StoryScreen>{fn()}</StoryScreen>) + .add("Labelling", () => ( + <Story> + <UseCase text="Normal text" usage="Use placeholder and label to set the text."> + <State initial={{ value: "" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + label="Name" + placeholder="omg your name" + /> + )} + </State> + </UseCase> + + <UseCase text="i18n text" usage="Use placeholderTx and labelTx for i18n lookups"> + <State initial={{ value: "" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + placeholderTx="storybook.placeholder" + labelTx="storybook.field" + /> + )} + </State> + </UseCase> + </Story> + )) + .add("Style Overrides", () => ( + <Story> + <UseCase + noPad + text="Container Styles" + usage="Useful for applying margins when laying out a form to remove padding if the form brings its own." + > + <State initial={{ value: "Inigo" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + label="First Name" + style={{ paddingTop: 0, paddingHorizontal: 40 }} + /> + )} + </State> + <State initial={{ value: "Montoya" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + label="Last Name" + style={{ paddingBottom: 0 }} + /> + )} + </State> + </UseCase> + <UseCase + text="Input Styles" + usage="Useful for 1-off exceptions. Try to steer towards presets for this kind of thing." + > + <State initial={{ value: "fancy colour" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + label="Name" + inputStyle={{ + backgroundColor: "rebeccapurple", + color: "white", + padding: 40, + borderWidth: 10, + borderRadius: 4, + borderColor: "hotpink", + }} + /> + )} + </State> + <Text text="* attention designers: i am so sorry" preset="secondary" /> + </UseCase> + + <UseCase text="Style array" usage="Useful for 1-off exceptions, but using style arrays."> + <State initial={{ value: "fancy colour" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + label="Name" + style={styleArray} + inputStyle={inputStyleArray} + /> + )} + </State> + <Text text="* attention designers: i am so sorry" preset="secondary" /> + </UseCase> + </Story> + )) + .add("Ref Forwarding", () => ( + <Story> + <UseCase text="Ref Forwarding" usage=""> + <State initial={{ value: "fancy colour" }}> + {({ state, setState }) => ( + <TextField + onChangeText={(value) => setState({ value })} + value={state.value} + label="Name" + inputStyle={{ + backgroundColor: "rebeccapurple", + color: "white", + padding: 40, + borderWidth: 10, + borderRadius: 4, + borderColor: "hotpink", + }} + forwardedRef={(ref) => ref} + onFocus={() => { + if (alertWhenFocused) { + // Prevent text field focus from being repeatedly triggering alert + alertWhenFocused = false + Alert.alert("Text field focuesed with forwarded ref!") + } + }} + /> + )} + </State> + <Text text="* attention designers: i am so sorry" preset="secondary" /> + </UseCase> + </Story> + )) diff --git a/app/components/text-field/text-field.tsx b/app/components/text-field/text-field.tsx new file mode 100644 index 0000000..eea1a70 --- /dev/null +++ b/app/components/text-field/text-field.tsx @@ -0,0 +1,98 @@ +import React from "react" +import { StyleProp, TextInput, TextInputProps, TextStyle, View, ViewStyle } from "react-native" +import { color, spacing, typography } from "../../theme" +import { translate, TxKeyPath } from "../../i18n" +import { Text } from "../text/text" + +// the base styling for the container +const CONTAINER: ViewStyle = { + paddingVertical: spacing[3], +} + +// the base styling for the TextInput +const INPUT: TextStyle = { + fontFamily: typography.primary, + color: color.text, + minHeight: 44, + fontSize: 18, + backgroundColor: color.palette.white, +} + +// currently we have no presets, but that changes quickly when you build your app. +const PRESETS: { [name: string]: ViewStyle } = { + default: {}, +} + +export interface TextFieldProps extends TextInputProps { + /** + * The placeholder i18n key. + */ + placeholderTx?: TxKeyPath + + /** + * The Placeholder text if no placeholderTx is provided. + */ + placeholder?: string + + /** + * The label i18n key. + */ + labelTx?: TxKeyPath + + /** + * The label text if no labelTx is provided. + */ + label?: string + + /** + * Optional container style overrides useful for margins & padding. + */ + style?: StyleProp<ViewStyle> + + /** + * Optional style overrides for the input. + */ + inputStyle?: StyleProp<TextStyle> + + /** + * Various look & feels. + */ + preset?: keyof typeof PRESETS + + forwardedRef?: any +} + +/** + * A component which has a label and an input together. + */ +export function TextField(props: TextFieldProps) { + const { + placeholderTx, + placeholder, + labelTx, + label, + preset = "default", + style: styleOverride, + inputStyle: inputStyleOverride, + forwardedRef, + ...rest + } = props + + const containerStyles = [CONTAINER, PRESETS[preset], styleOverride] + const inputStyles = [INPUT, inputStyleOverride] + const actualPlaceholder = placeholderTx ? translate(placeholderTx) : placeholder + + return ( + <View style={containerStyles}> + <Text preset="fieldLabel" tx={labelTx} text={label} /> + <TextInput + placeholder={actualPlaceholder} + placeholderTextColor={color.palette.lighterGrey} + underlineColorAndroid={color.transparent} + {...rest} + style={inputStyles} + ref={forwardedRef} + /> + </View> + ) +} |