Skip to content

Commit

Permalink
Merge pull request #37820 from VickyStash/ts-migration/g16-stories
Browse files Browse the repository at this point in the history
[TS migration] Migrate 'Composer.stories.js', 'DragAndDrop.stories.js', 'Form.stories.js' and 'RadioButtonWithLabel.stories.js' stories to TypeScript
  • Loading branch information
NikkiWines authored Mar 12, 2024
2 parents c5e04ec + 42d69f1 commit 9117956
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 60 deletions.
2 changes: 2 additions & 0 deletions src/components/Form/FormProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,5 @@ export default withOnyx<FormProviderProps, FormProviderOnyxProps>({
key: (props) => `${props.formID}Draft` as any,
},
})(forwardRef(FormProvider)) as <TFormID extends OnyxFormKey>(props: Omit<FormProviderProps<TFormID>, keyof FormProviderOnyxProps>) => ReactNode;

export type {FormProviderProps};
2 changes: 2 additions & 0 deletions src/components/RadioButtonWithLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@ function RadioButtonWithLabel({LabelComponent, style, label = '', hasError = fal
RadioButtonWithLabel.displayName = 'RadioButtonWithLabel';

export default RadioButtonWithLabel;

export type {RadioButtonWithLabelProps};
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type {ComponentMeta} from '@storybook/react';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import React, {useState} from 'react';
import {Image, View} from 'react-native';
import Composer from '@components/Composer';
import type {ComposerProps} from '@components/Composer/types';
import RenderHTML from '@components/RenderHTML';
import Text from '@components/Text';
import withNavigationFallback from '@components/withNavigationFallback';
import useStyleUtils from '@hooks/useStyleUtils';
// eslint-disable-next-line no-restricted-imports
import {defaultStyles} from '@styles/index';
// eslint-disable-next-line no-restricted-imports
import {defaultTheme} from '@styles/theme';
import CONST from '@src/CONST';
import {defaultStyles} from '@src/styles';

const ComposerWithNavigation = withNavigationFallback(Composer);

Expand All @@ -19,43 +19,40 @@ const ComposerWithNavigation = withNavigationFallback(Composer);
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
const story = {
const story: ComponentMeta<typeof ComposerWithNavigation> = {
title: 'Components/Composer',
component: ComposerWithNavigation,
};

const parser = new ExpensiMark();

function Default(args) {
function Default(props: ComposerProps) {
const StyleUtils = useStyleUtils();
const [pastedFile, setPastedFile] = useState(null);
const [comment, setComment] = useState(args.defaultValue);
const renderedHTML = parser.replace(comment);
const [pastedFile, setPastedFile] = useState<File | null>(null);
const [comment, setComment] = useState(props.defaultValue);
const renderedHTML = parser.replace(comment ?? '');

return (
<View>
<View style={[defaultStyles.border, defaultStyles.p4]}>
<ComposerWithNavigation
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
{...props}
multiline
onChangeText={setComment}
onPasteFile={setPastedFile}
style={[defaultStyles.textInputCompose, defaultStyles.w100, defaultStyles.verticalAlignTop]}
/>
</View>
<View style={[defaultStyles.flexRow, defaultStyles.mv5, defaultStyles.flexWrap, defaultStyles.w100]}>
<View
style={[defaultStyles.border, defaultStyles.noLeftBorderRadius, defaultStyles.noRightBorderRadius, defaultStyles.p5, defaultStyles.flex1]}
id={CONST.REPORT.DROP_NATIVE_ID}
>
<View style={[defaultStyles.border, defaultStyles.noLeftBorderRadius, defaultStyles.noRightBorderRadius, defaultStyles.p5, defaultStyles.flex1]}>
<Text style={[defaultStyles.mb2, defaultStyles.textLabelSupporting]}>Entered Comment (Drop Enabled)</Text>
<Text>{comment}</Text>
</View>
<View style={[defaultStyles.p5, defaultStyles.borderBottom, defaultStyles.borderRight, defaultStyles.borderTop, defaultStyles.flex1]}>
<Text style={[defaultStyles.mb2, defaultStyles.textLabelSupporting]}>Rendered Comment</Text>
{Boolean(renderedHTML) && <RenderHTML html={renderedHTML} />}
{Boolean(pastedFile) && (
{!!renderedHTML && <RenderHTML html={renderedHTML} />}
{!!pastedFile && (
<View style={defaultStyles.mv3}>
<Image
source={{uri: URL.createObjectURL(pastedFile)}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import lodashGet from 'lodash/get';
import type {ComponentMeta} from '@storybook/react';
import React, {useState} from 'react';
import {Image, View} from 'react-native';
import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
import DragAndDropProvider from '@components/DragAndDrop/Provider';
import Text from '@components/Text';
// eslint-disable-next-line no-restricted-imports
import {defaultStyles} from '@styles/index';
import {defaultStyles} from '@src/styles';

/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
const story = {
const story: ComponentMeta<typeof DragAndDropConsumer> = {
title: 'Components/DragAndDrop',
component: DragAndDropConsumer,
};

function Default() {
const [fileURL, setFileURL] = useState('');

return (
<View
style={[
Expand Down Expand Up @@ -46,11 +46,11 @@ function Default() {
)}
</View>
<DragAndDropConsumer
onDrop={(e) => {
const file = lodashGet(e, ['dataTransfer', 'files', 0]);
if (file && file.type.includes('image')) {
onDrop={(event) => {
const file = event.dataTransfer?.files?.[0];
if (file?.type.includes('image')) {
const reader = new FileReader();
reader.addEventListener('load', () => setFileURL(reader.result));
reader.addEventListener('load', () => setFileURL(reader.result as string));
reader.readAsDataURL(file);
}
}}
Expand Down
88 changes: 59 additions & 29 deletions src/stories/Form.stories.js → src/stories/Form.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
import type {ComponentMeta, Story} from '@storybook/react';
import React, {useState} from 'react';
import {View} from 'react-native';
import AddressSearch from '@components/AddressSearch';
import CheckboxWithLabel from '@components/CheckboxWithLabel';
import DatePicker from '@components/DatePicker';
import FormProvider from '@components/Form/FormProvider';
import type {FormProviderProps} from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import Picker from '@components/Picker';
import StatePicker from '@components/StatePicker';
import Text from '@components/Text';
import TextInput from '@components/TextInput';
import type {MaybePhraseKey} from '@libs/Localize';
import NetworkConnection from '@libs/NetworkConnection';
import * as ValidationUtils from '@libs/ValidationUtils';
// eslint-disable-next-line no-restricted-imports
import {defaultStyles} from '@styles/index';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import type {OnyxFormValuesMapping} from '@src/ONYXKEYS';
import {defaultStyles} from '@src/styles';

type FormStory = Story<FormProviderProps>;

type StorybookFormValues = {
routingNumber?: string;
accountNumber?: string;
street?: string;
dob?: string;
pickFruit?: string;
pickAnotherFruit?: string;
state?: string;
checkbox?: boolean;
};

type StorybookFormErrors = Partial<Record<keyof StorybookFormValues, string>>;

const STORYBOOK_FORM_ID = 'TestForm' as keyof OnyxFormValuesMapping;

/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
const story = {
const story: ComponentMeta<typeof FormProvider> = {
title: 'Components/Form',
component: FormProvider,
subcomponents: {
Expand All @@ -35,16 +55,21 @@ const story = {
},
};

function Template(args) {
function Template(props: FormProviderProps) {
// Form consumes data from Onyx, so we initialize Onyx with the necessary data here
NetworkConnection.setOfflineStatus(false);
FormActions.setIsLoading(args.formID, args.formState.isLoading);
FormActions.setErrors(args.formID, args.formState.error);
FormActions.setDraftValues(args.formID, args.draftValues);
FormActions.setIsLoading(props.formID, !!props.formState?.isLoading);
FormActions.setDraftValues(props.formID, props.draftValues);

if (props.formState?.error) {
FormActions.setErrors(props.formID, {error: props.formState.error as MaybePhraseKey});
} else {
FormActions.clearErrors(props.formID);
}

return (
// eslint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...args}>
<FormProvider {...props}>
<View>
<InputWrapper
InputComponent={TextInput}
Expand All @@ -61,27 +86,28 @@ function Template(args) {
label="Account number"
accessibilityLabel="Account number"
inputID="accountNumber"
containerStyles={[defaultStyles.mt4]}
containerStyles={defaultStyles.mt4}
/>
<InputWrapper
InputComponent={AddressSearch}
label="Street"
inputID="street"
containerStyles={[defaultStyles.mt4]}
containerStyles={defaultStyles.mt4}
hint="common.noPO"
/>
<InputWrapper
InputComponent={DatePicker}
inputID="dob"
label="Date of Birth"
containerStyles={[defaultStyles.mt4]}
containerStyles={defaultStyles.mt4}
/>
<View>
<InputWrapper
InputComponent={Picker}
label="Fruit"
inputID="pickFruit"
containerStyles={[defaultStyles.mt4]}
onInputChange={() => {}}
containerStyles={defaultStyles.mt4}
shouldSaveDraft
items={[
{
Expand All @@ -103,7 +129,8 @@ function Template(args) {
InputComponent={Picker}
label="Another Fruit"
inputID="pickAnotherFruit"
containerStyles={[defaultStyles.mt4]}
onInputChange={() => {}}
containerStyles={defaultStyles.mt4}
items={[
{
label: 'Select a Fruit',
Expand Down Expand Up @@ -139,21 +166,24 @@ function Template(args) {

/**
* Story to exhibit the native event handlers for TextInput in the Form Component
* @param {Object} args
* @returns {JSX}
*/
function WithNativeEventHandler(args) {
function WithNativeEventHandler(props: FormProviderProps) {
const [log, setLog] = useState('');

// Form consumes data from Onyx, so we initialize Onyx with the necessary data here
NetworkConnection.setOfflineStatus(false);
FormActions.setIsLoading(args.formID, args.formState.isLoading);
FormActions.setErrors(args.formID, args.formState.error);
FormActions.setDraftValues(args.formID, args.draftValues);
FormActions.setIsLoading(props.formID, !!props.formState?.isLoading);
FormActions.setDraftValues(props.formID, props.draftValues);

if (props.formState?.error) {
FormActions.setErrors(props.formID, {error: props.formState.error as MaybePhraseKey});
} else {
FormActions.clearErrors(props.formID);
}

return (
// eslint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...args}>
<FormProvider {...props}>
<InputWrapper
InputComponent={TextInput}
role={CONST.ROLE.PRESENTATION}
Expand All @@ -170,16 +200,16 @@ function WithNativeEventHandler(args) {

// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Default = Template.bind({});
const Loading = Template.bind({});
const ServerError = Template.bind({});
const InputError = Template.bind({});
const Default: FormStory = Template.bind({});
const Loading: FormStory = Template.bind({});
const ServerError: FormStory = Template.bind({});
const InputError: FormStory = Template.bind({});

const defaultArgs = {
formID: 'TestForm',
formID: STORYBOOK_FORM_ID,
submitButtonText: 'Submit',
validate: (values) => {
const errors = {};
validate: (values: StorybookFormValues) => {
const errors: StorybookFormErrors = {};
if (!ValidationUtils.isRequiredFulfilled(values.routingNumber)) {
errors.routingNumber = 'Please enter a routing number';
}
Expand All @@ -206,10 +236,10 @@ const defaultArgs = {
}
return errors;
},
onSubmit: (values) => {
onSubmit: (values: StorybookFormValues) => {
setTimeout(() => {
alert(`Form submitted!\n\nInput values: ${JSON.stringify(values, null, 4)}`);
FormActions.setIsLoading('TestForm', false);
FormActions.setIsLoading(STORYBOOK_FORM_ID, false);
}, 1000);
},
formState: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
import type {ComponentMeta, ComponentStory} from '@storybook/react';
import React from 'react';
import RadioButtonWithLabel from '@components/RadioButtonWithLabel';
import type {RadioButtonWithLabelProps} from '@components/RadioButtonWithLabel';

type RadioButtonWithLabelStory = ComponentStory<typeof RadioButtonWithLabel>;

/**
* We use the Component Story Format for writing stories. Follow the docs here:
*
* https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
*/
const story = {
const story: ComponentMeta<typeof RadioButtonWithLabel> = {
title: 'Components/RadioButtonWithLabel',
component: RadioButtonWithLabel,
};

function Template(args) {
function Template(props: RadioButtonWithLabelProps) {
// eslint-disable-next-line react/jsx-props-no-spreading
return <RadioButtonWithLabel {...args} />;
return <RadioButtonWithLabel {...props} />;
}

// Arguments can be passed to the component by binding
// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Default = Template.bind({});
const Checked = Template.bind({});
const Default: RadioButtonWithLabelStory = Template.bind({});
const Checked: RadioButtonWithLabelStory = Template.bind({});
Default.args = {
isChecked: false,
label: 'This radio button is unchecked',
onInputChange: () => {},
};

Checked.args = {
isChecked: true,
label: 'This radio button is checked',
onInputChange: () => {},
};

export default story;
Expand Down
2 changes: 1 addition & 1 deletion src/styles/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ function getBackgroundColorWithOpacityStyle(backgroundColor: string, opacity: nu
return {};
}

function getWidthAndHeightStyle(width: number, height?: number): ViewStyle {
function getWidthAndHeightStyle(width: number, height?: number): Pick<ViewStyle, 'height' | 'width'> {
return {
width,
height: height ?? width,
Expand Down

0 comments on commit 9117956

Please sign in to comment.