DEV Community

Cover image for Build a custom time picker React component
Anlisha Maharjan
Anlisha Maharjan

Posted on • Originally published at anlisha.com.np on

3 2

Build a custom time picker React component

In this article, we’ll create a custom time picker component combining react-datetime and react-input-mask packages.

Step1 — Start Basic CRA

npx create-react-app axon
Enter fullscreen mode Exit fullscreen mode

Step2 — Install react-datetime and react-input-mask packages

cd axon
npm i react-datetime react-input-mask moment
Enter fullscreen mode Exit fullscreen mode

Step3 — Setup time picker component

import React, { useState } from "react";
import Datetime from "react-datetime";
import InputMask from "react-input-mask";
import moment from "moment";
const CustomTimePicker = ({ label, classes, ...props }) => {
const [selectedTime, setSelectedTime] = useState("00:00");
const [formatChars] = useState({
1: "[0-2]",
2: "[0-9]",
3: "[0-5]",
4: "[0-9]",
5: "[0-5]",
6: "[0-9]",
});
const handleTimeChange = (dateString) => {
if (dateString) {
setSelectedTime(moment(dateString, "HH:mm"));
} else {
setSelectedTime(null);
}
};
const beforeMaskedValueChange = (newState) => {
const { value } = newState;
// Conditional mask for the 2nd digit base on the first digit
if (value.startsWith("2")) formatChars["2"] = "[0-3]";
// To block 24, 25, etc.
else formatChars["2"] = "[0-9]"; // To allow 05, 12, etc.
return { value, selection: newState.selection };
};
const maskInput = (props) => {
return (
<>
<InputMask
className="field"
mask="12:34"
maskchar="0"
formatChars={formatChars}
beforeMaskedValueChange={beforeMaskedValueChange}
{...props}
/>
</>
);
};
return (
<div className={`field-group mb-0`}>
<label htmlFor="" className={`field-label`}>
{label}
</label>
<div className={`field-wrap icon-end kc-timepicker outlined`}>
<Datetime
value={selectedTime}
open={true}
dateFormat={false}
timeFormat={"HH:mm"}
renderInput={maskInput}
inputProps={{
className: `field ${classes?.input || ""}`,
}}
onChange={handleTimeChange}
/>
</div>
</div>
);
};
export default CustomTimePicker;

Step4 — Style time picker component

* {
box-sizing: border-box;
}
html,
body {
margin: 0;
}
/* ##### Custom Timepicker InputMask & ReactDateTime ##### */
$inputBorderColor: #d9dce4;
$pickerFieldHeight: 5rem;
$pickerFieldPadding: (
"top": 1.2rem,
"bottom": 1.2rem,
"left": 1.8rem,
"right": 1.8rem,
) !default;
$pickerFieldPaddingXDense: (
"top": 1.2rem,
"bottom": 1.2rem,
"left": 1rem,
"right": 1rem,
) !default;
$pickerFieldHeightDense: 3rem;
$pickerFieldPaddingDense: (
"top": 0.5rem,
"bottom": 0.5rem,
"left": 0.8rem,
"right": 0.8rem,
) !default;
@mixin pickerStyle($height, $paddings, $fontSize) {
input {
height: $height;
padding-top: map-get($map: $paddings, $key: "top");
padding-bottom: map-get($map: $paddings, $key: "bottom");
padding-left: map-get($map: $paddings, $key: "left");
padding-right: map-get($map: $paddings, $key: "right");
font-size: $fontSize;
}
.rdtPicker {
.rdtCounter {
height: calc(#{$height} - 0.1rem);
left: calc(
(#{map-get($map: $paddings, $key: "left")} - (#{$fontSize} / 1.5)) +
1.2rem
);
&:nth-child(3) {
left: calc(
(#{map-get($map: $paddings, $key: "left")} - (#{$fontSize} / 1.5)) +
3.2rem
);
}
&:nth-child(5) {
left: calc(
(#{map-get($map: $paddings, $key: "left")} - (#{$fontSize} / 1.5)) +
4.7rem
);
}
.rdtBtn {
font-size: calc(#{$fontSize} / 1);
}
}
}
}
.field-group {
position: relative;
&-error {
input {
border-color: red;
color: red !important;
}
}
}
.field-label {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
color: #172b4d;
font-size: 1.4rem;
font-weight: 600;
line-height: 1;
transform: translate(0) !important;
}
.field-wrap {
width: 7.5rem;
display: flex;
position: relative;
border-radius: 6px;
background-color: #fff;
}
.error {
display: block;
margin-bottom: 8px;
color: red !important;
font-size: 12px;
text-align: right;
}
.kc-timepicker {
input {
width: 100%;
border: none;
border-radius: 0.3125rem;
background-color: transparent;
color: #535a63;
z-index: 1;
}
.rdt {
width: 100%;
> div {
width: 100%;
}
}
.rdtPicker {
min-width: initial;
position: absolute;
top: 0;
left: 0%;
margin: 0;
padding: 0;
opacity: 0;
visibility: hidden;
.rdtCounterSeparator,
.rdtCount {
display: none;
}
.rdtCounter {
width: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
position: absolute;
top: 0;
cursor: pointer;
.rdtBtn {
height: auto;
font-size: 0.625rem;
visibility: visible;
opacity: 0.4;
line-height: 1;
user-select: none;
&:hover {
opacity: 1;
transform: scale(1.5);
background-color: transparent;
}
}
}
}
@include pickerStyle($pickerFieldHeight, $pickerFieldPadding, 1.4rem);
&.outlined {
input {
border: 1px solid $inputBorderColor;
}
}
&.dense {
@include pickerStyle(
$pickerFieldHeightDense,
$pickerFieldPaddingDense,
1.4rem
);
}
&.XDense {
@include pickerStyle($pickerFieldHeight, $pickerFieldPaddingXDense, 1.4rem);
}
&:hover {
.rdtPicker {
opacity: 1;
}
}
}

Step5 — Import CustomTimePicker component in App.js

import React from "react";
import CustomTimePicker from "./custom-time-picker";
import './custom-time-picker.scss';
function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        alignItems: "center",
        width: "100vw",
        height: "100vh",
      }}
    >
      <CustomTimePicker name="time" label="Time" />
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode

And that’s it!

The post Build a custom time picker React component first appeared on Anlisha Maharjan.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay