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

Add DatePicker component #5509

Merged
merged 33 commits into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
37135b9
ExpensiTextInput: add support for non controlled input
kidroca Sep 24, 2021
125894a
ExpensiTextInput: add forceActiveLabel prop
kidroca Sep 24, 2021
f8ac6b6
Create Datepicker component for web & desktop
kidroca Sep 24, 2021
c1922b8
Create Datepicker component for ios
kidroca Sep 24, 2021
a2c9fa5
RequestorStep format dob string
kidroca Sep 24, 2021
5226357
Include Datepicker on Company and Identity forms
kidroca Sep 24, 2021
03ad0e5
ExpensiTextInput - handle programatic value uptates
kidroca Sep 24, 2021
c149599
DatePicker: local state and handling
kidroca Sep 24, 2021
9f647d5
DatePicker: Use "Confirm" instead of "Save"
kidroca Sep 24, 2021
b29b1c6
DatePicker: Android picker
kidroca Sep 24, 2021
215a925
Refactor add `onPress` handler to BaseExpensiTextInput
kidroca Sep 24, 2021
3173150
DatePicker update Android handling
kidroca Sep 24, 2021
014e4f8
Android theming
kidroca Sep 24, 2021
52beb8d
DatePicker: Extract common datepicker prop types
kidroca Sep 24, 2021
e0259b6
DatePicker: handle disabled state
kidroca Sep 24, 2021
374f3a3
ExpensiTextInput: Add default for `value`
kidroca Sep 24, 2021
73fbf05
Update date prop type for IdentityForm
kidroca Sep 24, 2021
0b2e111
Force active label only for large screens
kidroca Sep 24, 2021
6eb8946
DatePicker: replace discard with reset
kidroca Sep 24, 2021
3d15dd8
ExpesniTextInput: add `onPressOut` handler
kidroca Sep 24, 2021
8019668
Use blue brand color for Android accent
kidroca Sep 28, 2021
fcb1687
DatePicker: address code style issues
kidroca Sep 28, 2021
0a6174c
DatePicker: ios small tweaks
kidroca Sep 28, 2021
4325871
DatePicker: android clicking outside or cancel dismisses date
kidroca Sep 28, 2021
0f710a2
DatePicker: placeholder spacing on web and desktop
kidroca Sep 28, 2021
9a3e83b
DatePicker: mWeb show datepicker on focus
kidroca Sep 28, 2021
9db7aa9
DatePicker: jsdocs parameters
kidroca Sep 28, 2021
71a8c32
Merge branch 'main' into kidroca/datepicker
kidroca Sep 28, 2021
f635517
DatePicker: ios use the same button labels as mWeb
kidroca Sep 28, 2021
dfb3d17
DatePicker: make Safari date alignment to the left
kidroca Sep 29, 2021
61f0f61
Storybook: add datepicker story
kidroca Sep 29, 2021
859e196
Pass the `hasError` prop to the datepicker text field
kidroca Sep 29, 2021
00799de
Move datepicker styles to the component
kidroca Sep 29, 2021
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
3 changes: 3 additions & 0 deletions android/app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<resources>
<color name="bootsplash_background">#FFFFFF</color>
<color name="accent">#0185ff</color>
<color name="dark">#0b1b34</color>
<color name="gray4">#7D8B8F</color>
</resources>
7 changes: 3 additions & 4 deletions android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:textColor">#000000</item>
<!-- colors.gray4 -->
<item name="android:colorEdgeEffect">#7D8B8F</item>
<item name="android:textColor">@color/dark</item>
<item name="android:colorEdgeEffect">@color/gray4</item>
<item name="colorAccent">@color/accent</item>
</style>

<style name="BootTheme" parent="AppTheme">
<!-- set the generated bootsplash.xml drawable as activity background -->
<item name="android:background">@drawable/bootsplash</item>
</style>

</resources>
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@ PODS:
- React-Core
- RNCPicker (1.9.11):
- React-Core
- RNDateTimePicker (3.5.2):
- React-Core
- RNFBAnalytics (12.3.0):
- Firebase/Analytics (= 8.4.0)
- React-Core
Expand Down Expand Up @@ -666,6 +668,7 @@ DEPENDENCIES:
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
- "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
- "RNCPicker (from `../node_modules/@react-native-picker/picker`)"
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
Expand Down Expand Up @@ -826,6 +829,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-masked-view/masked-view"
RNCPicker:
:path: "../node_modules/@react-native-picker/picker"
RNDateTimePicker:
:path: "../node_modules/@react-native-community/datetimepicker"
RNFBAnalytics:
:path: "../node_modules/@react-native-firebase/analytics"
RNFBApp:
Expand Down Expand Up @@ -956,6 +961,7 @@ SPEC CHECKSUMS:
RNCClipboard: 5e299c6df8e0c98f3d7416b86ae563d3a9f768a3
RNCMaskedView: 138134c4d8a9421b4f2bf39055a79aa05c2d47b1
RNCPicker: 6780c753e9e674065db90d9c965920516402579d
RNDateTimePicker: c9911be59b1f8670b9f244b85af3a7c295e175ed
RNFBAnalytics: 8ba84c2d31c64374d054c8621b998f25145ffddc
RNFBApp: 64c90ab78b6010ed5c3ade026dfe5ff6442c21fd
RNFBCrashlytics: 1de18b8cc36d9bcf86407c4a354399228cc84a61
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@react-native-async-storage/async-storage": "^1.15.5",
"@react-native-community/cli": "4.13.1",
"@react-native-community/clipboard": "^1.5.1",
"@react-native-community/datetimepicker": "^3.5.2",
"@react-native-community/netinfo": "^5.9.10",
"@react-native-community/progress-bar-android": "^1.0.4",
"@react-native-community/progress-view": "^1.2.3",
Expand Down
22 changes: 22 additions & 0 deletions src/components/DatePicker/datepickerPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import PropTypes from 'prop-types';
import {
propTypes as fieldPropTypes,
defaultProps as defaultFieldPropTypes,
} from '../ExpensiTextInput/baseExpensiTextInputPropTypes';

kidroca marked this conversation as resolved.
Show resolved Hide resolved
const propTypes = {
...fieldPropTypes,

/**
* The datepicker supports any value that `moment` can parse.
* `onChange` would always be called with a Date (or null)
*/
value: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
};

const defaultProps = {
...defaultFieldPropTypes,
value: undefined,
};

export {propTypes, defaultProps};
83 changes: 83 additions & 0 deletions src/components/DatePicker/index.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import RNDatePicker from '@react-native-community/datetimepicker';
import moment from 'moment';
import ExpensiTextInput from '../ExpensiTextInput';
import CONST from '../../CONST';
import {propTypes, defaultProps} from './datepickerPropTypes';

kidroca marked this conversation as resolved.
Show resolved Hide resolved
class DatePicker extends React.Component {
constructor(props) {
super(props);

this.state = {
isPickerVisible: false,
};

this.showPicker = this.showPicker.bind(this);
this.raiseDateChange = this.raiseDateChange.bind(this);
}

/**
* @param {Event} event
*/
showPicker(event) {
this.setState({isPickerVisible: true});
event.preventDefault();
}

/**
* @param {Event} event
* @param {Date} selectedDate
*/
raiseDateChange(event, selectedDate) {
if (event.type === 'set') {
this.props.onChange(selectedDate);
}

this.setState({isPickerVisible: false});
}

render() {
const {
value,
label,
placeholder,
hasError,
errorText,
translateX,
containerStyles,
disabled,
} = this.props;

const dateAsText = value ? moment(value).format(CONST.DATE.MOMENT_FORMAT_STRING) : '';

return (
<>
<ExpensiTextInput
label={label}
value={dateAsText}
placeholder={placeholder}
hasError={hasError}
errorText={errorText}
containerStyles={containerStyles}
translateX={translateX}
onPress={this.showPicker}
editable={false}
disabled={disabled}
/>
{this.state.isPickerVisible && (
<RNDatePicker
value={value ? moment(value).toDate() : new Date()}
mode="date"
onChange={this.raiseDateChange}
/>
)}
</>
);
}
}

DatePicker.propTypes = propTypes;
DatePicker.defaultProps = defaultProps;

export default DatePicker;
133 changes: 133 additions & 0 deletions src/components/DatePicker/index.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import {Button, View} from 'react-native';
import RNDatePicker from '@react-native-community/datetimepicker';
import moment from 'moment';
import ExpensiTextInput from '../ExpensiTextInput';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import Popover from '../Popover';
import CONST from '../../CONST';
import styles from '../../styles/styles';
import themeColors from '../../styles/themes/default';
import {propTypes, defaultProps} from './datepickerPropTypes';

const datepickerPropTypes = {
...propTypes,
...withLocalizePropTypes,
};

class Datepicker extends React.Component {
constructor(props) {
super(props);

this.state = {
isPickerVisible: false,
selectedDate: props.value ? moment(props.value).toDate() : new Date(),
};

this.showPicker = this.showPicker.bind(this);
this.reset = this.reset.bind(this);
this.selectDate = this.selectDate.bind(this);
this.updateLocalDate = this.updateLocalDate.bind(this);
}

/**
* @param {Event} event
*/
showPicker(event) {
this.initialValue = this.state.selectedDate;
this.setState({isPickerVisible: true});
event.preventDefault();
}

/**
* Reset the date spinner to the initial value
*/
reset() {
this.setState({selectedDate: this.initialValue});
}

/**
* Accept the current spinner changes, close the spinner and propagate the change
* to the parent component (props.onChange)
*/
selectDate() {
this.setState({isPickerVisible: false});
this.props.onChange(this.state.selectedDate);
}

/**
* @param {Event} event
* @param {Date} selectedDate
*/
updateLocalDate(event, selectedDate) {
this.setState({selectedDate});
}

render() {
const {
value,
label,
placeholder,
hasError,
errorText,
translateX,
containerStyles,
disabled,
} = this.props;

const dateAsText = value ? moment(value).format(CONST.DATE.MOMENT_FORMAT_STRING) : '';

return (
<>
<ExpensiTextInput
label={label}
value={dateAsText}
placeholder={placeholder}
hasError={hasError}
errorText={errorText}
containerStyles={containerStyles}
translateX={translateX}
onPress={this.showPicker}
editable={false}
disabled={disabled}
/>
<Popover
isVisible={this.state.isPickerVisible}
onClose={this.selectDate}
>
<View style={[
styles.flexRow,
styles.justifyContentBetween,
styles.borderBottom,
styles.pb1,
styles.ph4,
]}
>
<Button
title={this.props.translate('common.reset')}
color={themeColors.textError}
onPress={this.reset}
/>
<Button
title={this.props.translate('common.done')}
color={themeColors.link}
onPress={this.selectDate}
/>
</View>
<RNDatePicker
value={this.state.selectedDate}
mode="date"
display="spinner"
onChange={this.updateLocalDate}
locale={this.props.preferredLocale}
/>
</Popover>
</>
);
}
}

Datepicker.propTypes = datepickerPropTypes;
Datepicker.defaultProps = defaultProps;

export default withLocalize(Datepicker);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB but maybe useful to mention somewhere that iOS is the only platform where we have to manage localization ourselves, and on all others it's controlled by the OS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll open a separate request to put a note about this

We're localizing the "Reset" and "Done" button that we put on our own popup.
RNDatepicker would be shown in the system locale. I'm passing the locale to RNDatepicker because it will be weird to have the "Done" and "Reset" buttons in one locale and the picker months in another - it can happen

On Android the buttons and the modal are handled by the system so they are always in the system locale

Loading