Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Expose autoFocus prop on OTPInput #252

Merged
merged 3 commits into from
May 2, 2024
Merged
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
168 changes: 121 additions & 47 deletions example/src/pages/OTPInput.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,152 @@
import * as React from "react";
import { View } from "react-native";
import { useHeaderHeight } from "@react-navigation/elements";
import { KeyboardAvoidingView, Platform, ScrollView, View } from "react-native";
import {
H1,
H5,
IOStyles,
VSpacer,
OTPInput,
LabelSmall,
ButtonSolid
ButtonSolid,
ContentWrapper,
ButtonOutline
} from "@pagopa/io-app-design-system";
import { useState } from "react";
import { Screen } from "../components/Screen";

const OTP_LENGTH = 8;
const OTP_COMPARE = "12345678";

type WrapperProps = {
secret?: boolean;
validation?: boolean;
autoFocus?: boolean;
};

const OTPWrapper = ({ secret = false, validation = false }: WrapperProps) => {
const OTPWrapper = ({
secret = false,
validation = false,
autoFocus = false
}: WrapperProps) => {
const [value, setValue] = useState("");
const onValueChange = (v: string) => {
const onValueChange = React.useCallback((v: string) => {
if (v.length <= OTP_LENGTH) {
setValue(v);
}
};
}, []);

const onValidate = (v: string) => !validation || v === OTP_COMPARE;
const onValidate = React.useCallback(
(v: string) => !validation || v === OTP_COMPARE,
[validation]
);

return (
<>
<OTPInput
value={value}
accessibilityLabel={"OTP Input"}
onValueChange={onValueChange}
length={OTP_LENGTH}
secret={secret}
onValidate={onValidate}
errorMessage={"Wrong OTP"}
/>
<VSpacer />
<ButtonSolid onPress={() => setValue("")} label={"Pulisci valore"} />
</>
return React.useMemo(
() => (
<>
<OTPInput
value={value}
accessibilityLabel={"OTP Input"}
onValueChange={onValueChange}
length={OTP_LENGTH}
secret={secret}
onValidate={onValidate}
errorMessage={"Wrong OTP"}
autoFocus={autoFocus}
/>
<VSpacer />
<ButtonSolid onPress={() => setValue("")} label={"Pulisci valore"} />
</>
),
[value, onValueChange, secret, onValidate, autoFocus]
);
};

const scrollVerticallyToView = (
scrollViewRef: React.RefObject<ScrollView>,
targetViewRef: React.RefObject<View>
) => {
if (targetViewRef.current && scrollViewRef.current) {
targetViewRef.current.measureLayout(
scrollViewRef.current.getInnerViewNode(),
(_: number, y: number) => {
scrollViewRef.current?.scrollTo({ y, animated: true });
}
);
}
};

/**
* This Screen is used to test components in isolation while developing.
* @returns a screen with a flexed view where you can test components
*/
export const OTPInputScreen = () => (
<View
style={{
flexGrow: 1
}}
>
<Screen>
<View style={IOStyles.alignCenter}>
<H1>OTP Input</H1>
</View>
<VSpacer />
<H5>Default</H5>
<VSpacer />
<OTPWrapper />
<VSpacer />
<H5>Secret</H5>
<VSpacer />
<OTPWrapper secret />
<VSpacer />
<H5>Validation+Secret</H5>
<LabelSmall>Correct OTP {`${OTP_COMPARE}`}</LabelSmall>
<VSpacer />
<OTPWrapper secret validation />
</Screen>
</View>
);
export const OTPInputScreen = () => {
const scrollViewRef = React.useRef<ScrollView>(null);
const autofocusableOTPViewRef = React.useRef<View>(null);
const [showAutofocusableOTP, setShowAutofocusableOTP] = useState(false);
const headerHeight = useHeaderHeight();

const ToggleButton = showAutofocusableOTP ? ButtonSolid : ButtonOutline;

return (
<View
style={{
flexGrow: 1
}}
>
<KeyboardAvoidingView
behavior={Platform.select({
ios: "padding",
android: undefined
})}
contentContainerStyle={[IOStyles.flex, { paddingBottom: 70 }]}
style={IOStyles.flex}
keyboardVerticalOffset={headerHeight}
>
<ScrollView ref={scrollViewRef}>
<ContentWrapper>
<View style={IOStyles.alignCenter}>
<H1>OTP Input</H1>
</View>
<VSpacer />
<H5>Default</H5>
<VSpacer />
<OTPWrapper />
<VSpacer />
<H5>Secret</H5>
<VSpacer />
<OTPWrapper secret />
<VSpacer />
<H5>Validation+Secret</H5>
<LabelSmall>Correct OTP {`${OTP_COMPARE}`}</LabelSmall>
<VSpacer />
<OTPWrapper secret validation />
<VSpacer />
<H5>Autofocus</H5>
<VSpacer />
<ToggleButton
onPress={() => {
setShowAutofocusableOTP(!showAutofocusableOTP);
setTimeout(() => {
scrollVerticallyToView(
scrollViewRef,
autofocusableOTPViewRef
);
}, 100);
}}
label={`${
showAutofocusableOTP ? "Hide" : "Show"
} Autofocusable OTP`}
/>
<VSpacer />
{showAutofocusableOTP && (
<View ref={autofocusableOTPViewRef}>
<OTPWrapper autoFocus />
<VSpacer />
</View>
)}
</ContentWrapper>
</ScrollView>
</KeyboardAvoidingView>
</View>
);
};
7 changes: 5 additions & 2 deletions src/components/otpInput/OTPInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Props = {
accessibilityLabel?: string;
accessibilityHint?: string;
inputAccessoryViewID?: string;
autoFocus?: boolean;
};

/**
Expand Down Expand Up @@ -46,11 +47,12 @@ export const OTPInput = React.forwardRef<View, Props>(
errorMessage = "",
secret = false,
autocomplete = false,
inputAccessoryViewID
inputAccessoryViewID,
autoFocus = false
},
ref
) => {
const [hasFocus, setHasFocus] = React.useState(false);
const [hasFocus, setHasFocus] = React.useState(autoFocus);
const [hasError, setHasError] = React.useState(false);

const { translate, animatedStyle, shakeAnimation } =
Expand Down Expand Up @@ -119,6 +121,7 @@ export const OTPInput = React.forwardRef<View, Props>(
autoComplete={autocomplete ? "sms-otp" : undefined}
inputAccessoryViewID={inputAccessoryViewID}
accessible={true}
autoFocus={autoFocus}
/>
{[...Array(length)].map((_, i) => (
<BoxedInput
Expand Down
Loading