-
-
Save artyorsh/f9846c93e42a5ed45364ea747d0c4101 to your computer and use it in GitHub Desktop.
{ | |
"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" | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} |
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', | |
}, | |
}); |
Great! Thank you!
Thanks for the amazing library, really loving how easy it is to use... how can I theme the time picker pop up window though? it pops up with a blue theme, but my theme color is red and it really clashes with my UI... I used this exact code to create the timepicker component... Thanks for your help... @artyorsh
Thank you!
@psalm987 did you find any solution?
@artyorsh thanks for your work... but the time picker pop-up color is not changing based on the mapping or eva design. Is it possible to change the color of the picker dialog if it allows us?
Tried using this and got an error
ERROR TypeError: undefined is not an object (evaluating 'this.meta.appearances')
Not sure what is causing it, any way to resolve this?
EDIT: after modifying the metro.config.js following the docs here, it works!
@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:
private get title(): RenderProp<TextProps> | string | undefined {
const tformat = this.props.is24Hour ? 'HH:mm' : 'hh:mm a';
const formattedDate =
this.props.date &&
this.props.dateService?.format(this.props.date as Date, tformat);
return formattedDate || this.props.placeholder;
}
This component relies on the native time picker implementation, which should be installed into your project, see the guide.
There are two files: mapping.json and time-picker.component.tsx, which is the component itself.
The component uses Eva styling engine. Please see this guide to make it work.
The usage of the component is pretty similar to Datepicker component usage, e.g:
The rest of the properties are similar to Datepicker, please see the API tab of that component for the reference.