Skip to content

Instantly share code, notes, and snippets.

@artyorsh
Last active March 24, 2024 15:54
Show Gist options
  • Save artyorsh/f9846c93e42a5ed45364ea747d0c4101 to your computer and use it in GitHub Desktop.
Save artyorsh/f9846c93e42a5ed45364ea747d0c4101 to your computer and use it in GitHub Desktop.
Time Picker example with UI Kitten and DateTimePicker
{
"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',
},
});
@lyqht
Copy link

lyqht commented Apr 14, 2022

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!

image

@m-vujicic
Copy link

@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;
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment