Last active
March 24, 2024 15:54
-
-
Save artyorsh/f9846c93e42a5ed45364ea747d0c4101 to your computer and use it in GitHub Desktop.
Time Picker example with UI Kitten and DateTimePicker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"components": { | |
"Timepicker": { | |
"meta": { | |
"scope": "all", | |
"parameters": { | |
"minHeight": { | |
"type": "number" | |
}, | |
"paddingHorizontal": { | |
"type": "number" | |
}, | |
"paddingVertical": { | |
"type": "number" | |
}, | |
"borderRadius": { | |
"type": "number" | |
}, | |
"borderColor": { | |
"type": "string" | |
}, | |
"borderWidth": { | |
"type": "number" | |
}, | |
"backgroundColor": { | |
"type": "string" | |
}, | |
"textMarginHorizontal": { | |
"type": "number" | |
}, | |
"textFontSize": { | |
"type": "number" | |
}, | |
"textFontWeight": { | |
"type": "string" | |
}, | |
"textFontFamily": { | |
"type": "string" | |
}, | |
"textColor": { | |
"type": "string" | |
}, | |
"placeholderColor": { | |
"type": "string" | |
}, | |
"iconWidth": { | |
"type": "number" | |
}, | |
"iconHeight": { | |
"type": "number" | |
}, | |
"iconMarginHorizontal": { | |
"type": "number" | |
}, | |
"iconTintColor": { | |
"type": "string" | |
}, | |
"labelColor": { | |
"type": "string" | |
}, | |
"labelFontFamily": { | |
"type": "string" | |
}, | |
"labelFontSize": { | |
"type": "number" | |
}, | |
"labelFontWeight": { | |
"type": "string" | |
}, | |
"labelMarginBottom": { | |
"type": "number" | |
}, | |
"captionMarginTop": { | |
"type": "number" | |
}, | |
"captionColor": { | |
"type": "string" | |
}, | |
"captionFontFamily": { | |
"type": "string" | |
}, | |
"captionFontSize": { | |
"type": "number" | |
}, | |
"captionFontWeight": { | |
"type": "string" | |
}, | |
"captionIconWidth": { | |
"type": "number" | |
}, | |
"captionIconHeight": { | |
"type": "number" | |
}, | |
"captionIconMarginRight": { | |
"type": "number" | |
}, | |
"captionIconTintColor": { | |
"type": "string" | |
} | |
}, | |
"appearances": { | |
"default": { | |
"default": true | |
} | |
}, | |
"variantGroups": { | |
"status": { | |
"basic": { | |
"default": true | |
}, | |
"primary": { | |
"default": false | |
}, | |
"success": { | |
"default": false | |
}, | |
"info": { | |
"default": false | |
}, | |
"warning": { | |
"default": false | |
}, | |
"danger": { | |
"default": false | |
}, | |
"control": { | |
"default": false | |
} | |
}, | |
"size": { | |
"small": { | |
"default": false | |
}, | |
"medium": { | |
"default": true | |
}, | |
"large": { | |
"default": false | |
} | |
} | |
}, | |
"states": { | |
"disabled": { | |
"default": false, | |
"priority": 0, | |
"scope": "all" | |
}, | |
"active": { | |
"default": false, | |
"priority": 1, | |
"scope": "all" | |
} | |
} | |
}, | |
"appearances": { | |
"default": { | |
"mapping": { | |
"paddingHorizontal": 8, | |
"textMarginHorizontal": 8, | |
"textFontFamily": "text-font-family", | |
"iconWidth": 24, | |
"iconHeight": 24, | |
"iconMarginHorizontal": 8, | |
"labelMarginBottom": 4, | |
"labelFontSize": "text-label-font-size", | |
"labelFontWeight": "text-label-font-weight", | |
"labelFontFamily": "text-label-font-family", | |
"captionMarginTop": 4, | |
"captionFontSize": "text-caption-1-font-size", | |
"captionFontWeight": "text-caption-1-font-weight", | |
"captionFontFamily": "text-caption-1-font-family", | |
"captionIconWidth": 10, | |
"captionIconHeight": 10, | |
"captionIconMarginRight": 8 | |
}, | |
"variantGroups": { | |
"status": { | |
"basic": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-basic-color", | |
"labelColor": "text-hint-color", | |
"captionColor": "text-hint-color", | |
"placeholderColor": "text-hint-color", | |
"iconTintColor": "text-hint-color", | |
"captionIconTintColor": "text-hint-color", | |
"state": { | |
"active": { | |
"borderColor": "color-primary-default", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-disabled-color", | |
"iconTintColor": "text-disabled-color" | |
} | |
} | |
}, | |
"primary": { | |
"borderColor": "color-primary-default", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-basic-color", | |
"labelColor": "text-hint-color", | |
"captionColor": "text-primary-color", | |
"placeholderColor": "text-hint-color", | |
"iconTintColor": "text-primary-color", | |
"captionIconTintColor": "text-primary-color", | |
"state": { | |
"active": { | |
"borderColor": "color-primary-active", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-disabled-color", | |
"iconTintColor": "text-disabled-color" | |
} | |
} | |
}, | |
"success": { | |
"borderColor": "color-success-default", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-basic-color", | |
"labelColor": "text-hint-color", | |
"captionColor": "text-success-color", | |
"placeholderColor": "text-hint-color", | |
"iconTintColor": "text-success-color", | |
"captionIconTintColor": "text-success-color", | |
"state": { | |
"active": { | |
"borderColor": "color-success-active", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-disabled-color", | |
"iconTintColor": "text-disabled-color" | |
} | |
} | |
}, | |
"info": { | |
"borderColor": "color-info-default", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-basic-color", | |
"labelColor": "text-hint-color", | |
"captionColor": "text-info-color", | |
"placeholderColor": "text-hint-color", | |
"iconTintColor": "text-info-color", | |
"captionIconTintColor": "text-info-color", | |
"state": { | |
"active": { | |
"borderColor": "color-info-active", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-disabled-color", | |
"iconTintColor": "text-disabled-color" | |
} | |
} | |
}, | |
"warning": { | |
"borderColor": "color-warning-default", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-basic-color", | |
"labelColor": "text-hint-color", | |
"captionColor": "text-warning-color", | |
"placeholderColor": "text-hint-color", | |
"iconTintColor": "text-warning-color", | |
"captionIconTintColor": "text-warning-color", | |
"state": { | |
"active": { | |
"borderColor": "color-warning-active", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-disabled-color", | |
"iconTintColor": "text-disabled-color" | |
} | |
} | |
}, | |
"danger": { | |
"borderColor": "color-danger-default", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-basic-color", | |
"labelColor": "text-hint-color", | |
"captionColor": "text-danger-color", | |
"placeholderColor": "text-hint-color", | |
"iconTintColor": "text-danger-color", | |
"captionIconTintColor": "text-danger-color", | |
"state": { | |
"active": { | |
"borderColor": "color-danger-active", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "border-basic-color-4", | |
"backgroundColor": "background-basic-color-2", | |
"textColor": "text-disabled-color", | |
"iconTintColor": "text-disabled-color" | |
} | |
} | |
}, | |
"control": { | |
"borderColor": "color-basic-control-transparent-500", | |
"backgroundColor": "color-basic-control-transparent-300", | |
"textColor": "text-control-color", | |
"labelColor": "text-control-color", | |
"captionColor": "text-control-color", | |
"placeholderColor": "text-control-color", | |
"iconTintColor": "text-control-color", | |
"captionIconTintColor": "text-control-color", | |
"state": { | |
"active": { | |
"borderColor": "color-control-transparent-active-border", | |
"backgroundColor": "background-basic-color-1", | |
"textColor": "text-basic-color" | |
}, | |
"disabled": { | |
"borderColor": "color-control-transparent-disabled-border", | |
"backgroundColor": "color-control-transparent-disabled", | |
"textColor": "text-control-color", | |
"iconTintColor": "text-control-color" | |
} | |
} | |
} | |
}, | |
"size": { | |
"small": { | |
"minHeight": "size-small", | |
"borderRadius": "border-radius", | |
"borderWidth": "border-width", | |
"paddingVertical": 3, | |
"textFontSize": "text-subtitle-2-font-size", | |
"textFontWeight": "normal" | |
}, | |
"medium": { | |
"minHeight": "size-medium", | |
"borderRadius": "border-radius", | |
"borderWidth": "border-width", | |
"paddingVertical": 7, | |
"textFontSize": "text-subtitle-1-font-size", | |
"textFontWeight": "normal" | |
}, | |
"large": { | |
"minHeight": "size-large", | |
"borderRadius": "border-radius", | |
"borderWidth": "border-width", | |
"paddingVertical": 11, | |
"textFontSize": "text-subtitle-1-font-size", | |
"textFontWeight": "normal" | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { | |
GestureResponderEvent, | |
ImageProps, | |
StyleProp, | |
StyleSheet, | |
TouchableOpacityProps, | |
View, | |
ViewProps, | |
ViewStyle, | |
Platform, | |
} from 'react-native'; | |
import DateTimePicker, { TimePickerOptions } from '@react-native-community/datetimepicker'; | |
import { | |
EvaInputSize, | |
EvaStatus, | |
FalsyFC, | |
FalsyText, | |
RenderProp, | |
TouchableWithoutFeedback, | |
} from '@ui-kitten/components/devsupport'; | |
import { PopoverPlacement } from '@ui-kitten/components/ui/popover/type'; | |
import { | |
Interaction, | |
StyledComponentProps, | |
StyleType, | |
TextProps, | |
styled, | |
Card, | |
Popover, | |
PopoverPlacements, | |
DateService, | |
NativeDateService, | |
} from '@ui-kitten/components'; | |
type TimePickerOmitChangeProps = Omit<TimePickerOptions, 'onChange'>; | |
type TimePickerOmitProps = Omit<TimePickerOmitChangeProps, 'value'>; | |
export interface TimepickerProps<D = Date> extends StyledComponentProps, TouchableOpacityProps, TimePickerOmitProps { | |
controlStyle?: StyleProp<ViewStyle>; | |
date?: D; | |
label?: RenderProp<TextProps> | React.ReactText; | |
caption?: RenderProp<TextProps> | React.ReactText; | |
captionIcon?: RenderProp<Partial<ImageProps>>; | |
accessoryLeft?: RenderProp<Partial<ImageProps>>; | |
accessoryRight?: RenderProp<Partial<ImageProps>>; | |
status?: EvaStatus; | |
size?: EvaInputSize; | |
placeholder?: RenderProp<TextProps> | React.ReactText; | |
placement?: PopoverPlacement | string; | |
backdropStyle?: StyleProp<ViewStyle>; | |
onSelect?: (date: D) => void; | |
onFocus?: () => void; | |
onBlur?: () => void; | |
dateService?: DateService<D>; | |
} | |
interface State { | |
visible: boolean; | |
} | |
@styled('Timepicker') | |
export class Timepicker<P> extends React.Component<TimepickerProps & P, State> { | |
static defaultProps: Partial<TimepickerProps> = { | |
placement: PopoverPlacements.BOTTOM_START, | |
dateService: new NativeDateService(), | |
}; | |
public state: State = { | |
visible: false, | |
}; | |
private get title(): RenderProp<TextProps> | React.ReactText | undefined { | |
const formattedDate = this.props.date && this.props.dateService?.format(this.props.date as Date, 'HH:mm'); | |
return formattedDate || this.props.placeholder; | |
} | |
private getComponentStyle = (style: StyleType) => { | |
const { | |
textMarginHorizontal, | |
textFontFamily, | |
textFontSize, | |
textFontWeight, | |
textColor, | |
placeholderColor, | |
iconWidth, | |
iconHeight, | |
iconMarginHorizontal, | |
iconTintColor, | |
labelColor, | |
labelFontSize, | |
labelMarginBottom, | |
labelFontWeight, | |
labelFontFamily, | |
captionMarginTop, | |
captionColor, | |
captionFontSize, | |
captionFontWeight, | |
captionFontFamily, | |
captionIconWidth, | |
captionIconHeight, | |
captionIconMarginRight, | |
captionIconTintColor, | |
popoverWidth, | |
...controlParameters | |
} = style; | |
return { | |
control: controlParameters, | |
captionContainer: { | |
marginTop: captionMarginTop, | |
}, | |
text: { | |
marginHorizontal: textMarginHorizontal, | |
fontFamily: textFontFamily, | |
fontSize: textFontSize, | |
fontWeight: textFontWeight, | |
color: textColor, | |
}, | |
placeholder: { | |
marginHorizontal: textMarginHorizontal, | |
color: placeholderColor, | |
}, | |
icon: { | |
width: iconWidth, | |
height: iconHeight, | |
marginHorizontal: iconMarginHorizontal, | |
tintColor: iconTintColor, | |
}, | |
label: { | |
color: labelColor, | |
fontSize: labelFontSize, | |
fontFamily: labelFontFamily, | |
marginBottom: labelMarginBottom, | |
fontWeight: labelFontWeight, | |
}, | |
captionIcon: { | |
width: captionIconWidth, | |
height: captionIconHeight, | |
tintColor: captionIconTintColor, | |
marginRight: captionIconMarginRight, | |
}, | |
captionLabel: { | |
fontSize: captionFontSize, | |
fontWeight: captionFontWeight, | |
fontFamily: captionFontFamily, | |
color: captionColor, | |
}, | |
popover: { | |
width: popoverWidth, | |
marginBottom: captionMarginTop, | |
}, | |
}; | |
}; | |
private onPress = (event: GestureResponderEvent): void => { | |
this.setPickerVisible(); | |
this.props.onPress && this.props.onPress(event); | |
}; | |
private onPressIn = (event: GestureResponderEvent): void => { | |
// @ts-ignore | |
this.props.eva.dispatch([Interaction.ACTIVE]); | |
this.props.onPressIn && this.props.onPressIn(event); | |
}; | |
private onPressOut = (event: GestureResponderEvent): void => { | |
// @ts-ignore | |
this.props.eva.dispatch([]); | |
this.props.onPressOut && this.props.onPressOut(event); | |
}; | |
private onValueChange = (event: React.SyntheticEvent, value?: Date): void => { | |
Platform.OS !== 'ios' && this.setPickerInvisible(); | |
value && this.props.onSelect && this.props.onSelect(value); | |
}; | |
private onPickerVisible = (): void => { | |
// @ts-ignore | |
this.props.eva.dispatch([Interaction.ACTIVE]); | |
this.props.onFocus && this.props.onFocus(); | |
}; | |
private onPickerInvisible = (): void => { | |
// @ts-ignore | |
this.props.eva.dispatch([]); | |
this.props.onBlur && this.props.onBlur(); | |
}; | |
private setPickerVisible = (): void => { | |
this.setState({ visible: true }, this.onPickerVisible); | |
}; | |
private setPickerInvisible = (): void => { | |
this.setState({ visible: false }, this.onPickerInvisible); | |
}; | |
private renderPickerDefault = (): React.ReactElement => { | |
return ( | |
<DateTimePicker | |
mode='time' | |
display={this.props.display} | |
is24Hour={this.props.is24Hour} | |
value={this.props.date as Date || new Date()} | |
onChange={this.onValueChange} | |
/> | |
); | |
}; | |
private renderPickerIOS = (): React.ReactElement => { | |
return ( | |
<Card disabled={true}> | |
{this.renderPickerDefault()} | |
</Card> | |
); | |
}; | |
private renderInputDefault = (props: Partial<TimepickerProps>, evaStyle): React.ReactElement => { | |
return ( | |
<TouchableWithoutFeedback | |
{...props} | |
style={[evaStyle.control, styles.control, props.controlStyle]} | |
onPress={this.onPress} | |
onPressIn={this.onPressIn} | |
onPressOut={this.onPressOut}> | |
<FalsyFC | |
style={evaStyle.icon} | |
component={props.accessoryLeft} | |
/> | |
<FalsyText | |
style={evaStyle.text} | |
numberOfLines={1} | |
ellipsizeMode='tail' | |
component={this.title} | |
/> | |
<FalsyFC | |
style={evaStyle.icon} | |
component={this.props.accessoryRight} | |
/> | |
</TouchableWithoutFeedback> | |
); | |
}; | |
private renderInputIOS = (props: Partial<TimepickerProps>, evaStyle): React.ReactElement => { | |
return ( | |
<Popover | |
style={[evaStyle.popover, styles.popover]} | |
backdropStyle={props.backdropStyle} | |
fullWidth={true} | |
placement={props.placement} | |
visible={this.state.visible} | |
anchor={() => this.renderInputDefault(props, evaStyle)} | |
onBackdropPress={this.setPickerInvisible}> | |
{this.renderPickerIOS()} | |
</Popover> | |
); | |
}; | |
private renderInput = (props: Partial<TimepickerProps>, evaStyle): React.ReactElement => { | |
return Platform.select({ | |
ios: this.renderInputIOS(props, evaStyle), | |
default: this.renderInputDefault(props, evaStyle), | |
}); | |
}; | |
public render(): React.ReactElement<ViewProps> { | |
const { eva, style, label, caption, captionIcon, ...inputProps } = this.props; | |
const evaStyle = this.getComponentStyle(this.props.eva?.style as StyleType); | |
return ( | |
<React.Fragment> | |
<View style={style}> | |
<FalsyText | |
style={[evaStyle.label, styles.label]} | |
component={label} | |
/> | |
{this.renderInput(inputProps, evaStyle)} | |
<View style={[evaStyle.captionContainer, styles.captionContainer]}> | |
<FalsyFC | |
style={evaStyle.captionIcon} | |
component={captionIcon} | |
/> | |
<FalsyText | |
style={[evaStyle.captionLabel, styles.captionLabel]} | |
component={caption} | |
/> | |
</View> | |
</View> | |
{Platform.OS !== 'ios' && this.state.visible && this.renderPickerDefault()} | |
</React.Fragment> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
popover: { | |
borderWidth: 0, | |
}, | |
control: { | |
flexDirection: 'row', | |
alignItems: 'center', | |
justifyContent: 'space-between', | |
}, | |
label: { | |
textAlign: 'left', | |
}, | |
captionContainer: { | |
flexDirection: 'row', | |
alignItems: 'center', | |
}, | |
captionLabel: { | |
textAlign: 'left', | |
}, | |
closeButton: { | |
alignSelf: 'flex-end', | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@artyorsh thanks for amazing library and work on this component.
Since there is still no time-picker component I just wanted to warn anyone using this code - time display in input is not honoring is24Hour property.
In order to correct it, just replace title() function (lines 78-84) in time-picker.component.tsx with: