TimePicker

Time Picker is an input field for entering or selecting a specific time.

Available from eds-core/1.1.0

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { TimePicker } from '@adaptavant/eds-core';

Format

Define the time format for the TimePicker using the format prop. 12 is the default value.

const handleSelect = ({ newOption }) => {
  console.log(newOption?.time);
};

return (
  <div className="flex gap-3 items-center">
    <TimePicker
      onSelect={handleSelect}
      label="Select time"
      labelVisibility="visible"
      titleForMobile="Select time"
      closeButtonPropsForMobile={{ label: "Close" }}
    />

    <TimePicker
      format="24"
      onSelect={handleSelect}
      label="Select time"
      labelVisibility="visible"
      titleForMobile="Select time"
      closeButtonPropsForMobile={{ label: "Close" }}
    />
  </div>
);

Interval

Specify time interval in minutes using interval prop. 60 minutes is the default interval.

const handleSelect = ({ newOption }) => {
  console.log(newOption?.time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker onSelect={handleSelect} />
    <TimePicker interval={45} onSelect={handleSelect} />
    <TimePicker interval={30} onSelect={handleSelect} />
    <TimePicker interval={15} onSelect={handleSelect} />
  </div>
);

Variant

The Timepicker is available in 2 variants, standard is the default.

const handleSelect = ({ newOption }) => {
  console.log(newOption?.time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker onSelect={handleSelect} />
    <TimePicker variant="subtle" onSelect={handleSelect} />
  </div>
);

DefaultValue

Use defaultValue prop to set a default time . It accepts a Date object or a string.

The <TimePicker/> will use the last two parameters (hour and minute) from the Date object and ignore the first three (year, month, date). It will then set the defaultOption in the provided format.

In the example below, the <TimePicker/> will set the defaultValue to 3:00 AM because the default format is 12.

Note: The component rerenders with every update of defaultValue. Please be mindful when using this prop to avoid performance issues.

const [defaultValue,setDefaultValue] = React.useState(new Date(2021,9,20,3,0));
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker defaultValue={defaultValue} onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

MinValue

Use the minValue prop to set the minimum time for the <TimePicker/>. It accepts a Date object, but only the hours and minutes are considered; the date part is ignored. Time options earlier than this value will not be rendered.

-
const [startTime, setStartTime] = React.useState("");
const [endTime, setEndTime] = React.useState("");
const [endMinValue, setEndMinValue] = React.useState("");

const handleStartSelect = ({ newOption, inputState }) => {
  const time = newOption ? newOption.time : "";
  setStartTime(time);
  if (time) {
    setEndMinValue(newOption.dateObject);
  }
};

const handleEndSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setEndTime(time);
};

return (
  <Box className="flex items-center gap-3">
    <Box className="flex items-start gap-4">
      <TimePicker interval={30} onSelect={handleStartSelect} />
      <Track as="span" className="text-lg text-gray-500 self-center	h-8">
        -
      </Track>
      <TimePicker
        interval={30}
        minValue={endMinValue}
        onSelect={handleEndSelect}
      />
    </Box>
    {startTime && endTime && (
      <Text className="text-body-12 text-secondary">
        Selected Range: {startTime} - {endTime}
      </Text>
    )}
  </Box>
);

MaxValue

Use the maxValue prop to set the maximum time for the <TimePicker/>. It accepts a Date object, but only the hours and minutes are considered; the date part is ignored. Time options later than this value will not be rendered.

-
const [startTime, setStartTime] = React.useState("");
const [endTime, setEndTime] = React.useState("");
const [startMaxValue, setStartMaxValue] = React.useState("");

const handleStartSelect = ({ newOption, inputState }) => {
  const time = newOption ? newOption.time : "";
  setStartTime(time);
};

const handleEndSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setEndTime(time);
  //Set MaxValue for startTime to limit the selection
  if (time) {
    setStartMaxValue(newOption.dateObject);
  }
};

return (
  <Box className="flex items-center gap-3">
    <Box className="flex items-start gap-4">
      <TimePicker
        interval={30}
        maxValue={startMaxValue}
        onSelect={handleStartSelect}
      />
      <Track as="span" className="text-lg text-gray-500 self-center	h-8">
        -
      </Track>
      <TimePicker interval={30} onSelect={handleEndSelect} />
    </Box>
    {startTime && endTime && (
      <Text className="text-body-12 text-secondary">
        Selected Range: {startTime} - {endTime}
      </Text>
    )}
  </Box>
);

CustomValues

Use the customValues prop to add specific time options to the list in addition to the default options. This prop accepts an array of Date objects. By default, time options are generated at fixed intervals (e.g., 15, 30 minutes), so if you need a specific time not covered by these intervals, such as 11:59 PM, you can use customValues.

The <TimePicker /> will use only the hour and minute from the Date object, ignoring the year, month, and date, and will include this time in the list of available options.

However, note that certain edge cases will prevent a customValue from being added to the list:

  • If the time is less than or equal to minValue or greater than or equal to maxValue.
  • If the time is already present in the generated options list.
const [customValues,setCustomValues] = React.useState([new Date(0,0,0,23,59)]);
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker format="24" customValues={customValues} onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

DurationLabel

Use the showDurationLabel prop in combination with the minValue prop to display a duration label for each time option. The showDurationLabel prop enables the label visibility, while the minValue prop sets the minimum time threshold for the options to include duration labels.

const now = new Date(); // Get the current date and time
const todayAt534AM = new Date(
  now.getFullYear(),
  now.getMonth(),
  now.getDate(),
  5, // Hours
  34, // Minutes
  0, // Seconds
  0 // Milliseconds
);
const [minValue,setMinValue] = React.useState(todayAt534AM);
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker minValue={minValue} showDurationLabel onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

Label

Use label to provide a concise description of the purpose for <TimePicker/>.

By default label is hidden , Use labelVisibility to display the label.

Notes: In Version 1.1.0, the label prop is optional. It becomes mandatory in the subsequent versions.Additionally, the labelVisibility prop is introduced in the following version.

const handleSelect = ({ newOption }) => {
  console.log(newOption?.time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker label="Start time" onSelect={handleSelect} />
    <TimePicker
      label="Start time"
      labelVisibility="visible"
      format="24"
      onSelect={handleSelect}
      closeButtonPropsForMobile={{ label: "Select time" }}
    />
  </div>
);

Disabled

Use the isDisabled prop to show that a <Timepicker/> isn't usable.

const [defaultValue,setDefaultValue] = React.useState(new Date(2021,9,20,3,0));
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker isDisabled defaultValue={defaultValue} onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

Clear

When set to true, the TimePicker will clear its selected value and input. This can be used to programmatically reset the TimePicker.

const [value, setValue] = React.useState("");
const [clear, setClear] = React.useState(false);

const handleSelect = ({ newOption, inputState }) => {
  // Reset the clear state if it's true
  clear && setClear(false);
  const time = newOption?.time ?? "";
  // If the input state is invalid, set the clear state to true, otherwise set the value
  inputState === "INVALID_FORMAT" ? setClear(true) : setValue(time);
};

return (
  <div className="flex gap-3 items-center">
    <TimePicker clear={clear} onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

Events

Use onSelect prop to trigger a callback when a time option is selected. It expects an object with the following keys

  • newOption - Represents each option in the TimePicker and the data sent to the callback.
    • time - Represents the selected time as a string.
    • dateObject - Represents System Timezone using Date object.
  • inputState - Represents the state of the TimePicker input value.Below are the possible states.

State

Description

EMPTY

Indicates that the TimePicker input is empty. This state occurs when the user has not selected or entered any time

VALID

Represents a valid time input. The user has selected or entered a time that meets all the validation criteria, including being within any specified constraints such as minValue.

INVALID_FORMAT

Indicates that the user has entered a time in an invalid format. This state is used to notify the user that the time format does not match the expected format (e.g., "HH AM/PM" for 12-hour format)

LTE_MIN

Indicates that the selected time is less than or equal to the specified minValue. This state occurs when the user inputs a time that falls before the allowed minimum time.

Note: This can happen if the user enters a time manually.

GTE_MAX

Indicates that the selected time is greater than or equal to the specified maxValue. This state occurs when the user inputs a time later than the allowed maximum time.

Note: This can happen if the user enters a time manually.

const [value, setValue] = React.useState("");
const handleSelect = ({ newOption, inputState }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

Error Message

Use the errorMessage prop to set the invalid state for the TimePicker. Check the input value state using the inputState prop from the onSelect callback.

The Timepicker's error state relies on the errorMessage prop. If it isn't provided, no error message will be shown even if the input is invalid.

const [selectedTime, setSelectedTime] = React.useState("");
const [errorMsg, setErrorMsg] = React.useState("");
const handleSelect = ({ newOption, inputState }) => {
  const time = newOption ? newOption.time : "";
  setSelectedTime(time);
  if (inputState === 'INVALID_FORMAT') {
    setErrorMsg('Invalid');
  }
  if (inputState === 'VALID' || inputState === 'EMPTY') {
    setErrorMsg('');
  }
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker errorMessage={errorMsg} onSelect={handleSelect} />
    {selectedTime && (
      <Text className="text-body-12 text-secondary">
        Selected time: {selectedTime}
      </Text>
    )}
  </div>
);

CustomDays

Use customDays prop to define custom options like "FullDay" or "24 Hours" in the <TimePicker/>. These options will be non-scrollable and remain fixed at the top, while the rest of the options will be scrollable.

Note: We do not recommend using more than two customDays options for optimal usability.

const [customDays,setCustomdays] = React.useState(["Full Day"]);
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker format="24" customDays={customDays} onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

To have a custom day selected by default on load, you can pass it using the defaultValue prop along with the customDays prop.

const [customDays,setCustomdays] = React.useState(["Full Day"]);
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker format="24" customDays={customDays} defaultValue="Full Day" onSelect={handleSelect} />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

TimePicker parts

const [selectedTime, setSelectedTime] = React.useState("");
const [customDays,setCustomDays] = React.useState(["Full Day"]);
const [errorMsg, setErrorMsg] = React.useState("");
const handleSelect = ({ newOption, inputState }) => {
  const time = newOption ? newOption.time : "";
  setSelectedTime(time);
  if (inputState === "INVALID_FORMAT") {
    setErrorMsg("Invalid");
  }
  if (inputState === "VALID" || inputState === "EMPTY") {
    setErrorMsg("");
  }
};
return (
  <div className="flex gap-3">
    <TimePicker
      customDays={customDays}
      onSelect={handleSelect}
      label="Select"
      labelVisibility="visible"
      errorMessage={errorMsg}
      classNames={{
        label: "text-secondary",
        field: "sm:w-[78px]",
        input: "text-positive",
        errorMessage: "text-caution",
        errorIcon: "text-caution",
        errorTrack: "bg-canvas-tertiary",
        listboxItem: "text-left",
        separator: "border-accent",
        popover: "max-w-[80px]",
      }}
    />
    {selectedTime && (
      <Text className="text-body-12 text-secondary mt-1">
        Selected Slot: {selectedTime}
      </Text>
    )}
  </div>
);
//Custom styles for Duration Label 
const now = new Date(); // Get the current date and time
const todayAt534AM = new Date(
  now.getFullYear(),
  now.getMonth(),
  now.getDate(),
  5, // Hours
  34, // Minutes
  0, // Seconds
  0 // Milliseconds
);
const [minValue, setMinValue] = React.useState(todayAt534AM);
const [value, setValue] = React.useState("");
const handleSelect = ({ newOption }) => {
  const time = newOption ? newOption.time : "";
  setValue(time);
};
return (
  <div className="flex gap-3 items-center">
    <TimePicker
      minValue={minValue}
      showDurationLabel
      onSelect={handleSelect}
      classNames={{
        durationLabel: "text-positive",
        timeSlot: "text-critical",
        timeSlotWrapper: "gap-3",
      }}
    />
    {value && (
      <Text className="text-body-12 text-secondary">
        Selected time: {value}
      </Text>
    )}
  </div>
);

Stylable Parts

Description

durationLabel

The text containing the secondary label with duration in hours and minutes.

errorMessage

The text content displaying an error message.

errorIcon

The icon shown in the error state.

errorTrack

The track that highlights the error visually.

field

The container for the time input field.

input

The input element for entering or selecting the time.

label

The label for the timepicker.

listbox

The dropdown displaying available time options.

listboxItem

Individual time option within the listbox.

popover

The container that wraps the dropdown and its items.

separator

The space between the scrollable and non-scrollable options in the popover.

timeSlot

Represents each time option string displayed in the listbox (i.e) 8:00AM 9:00AM and so on.

timeSlotWrapper

The container that wraps the the timeSlot and optional durationLabel.

Note: The Timepicker doesn't have a root stylable part, so applying classNames directly may not be effective.

Usage guidelines

Do

  1. Use clear labels: Clearly label the TimePicker to indicate its purpose, such as "Select time" or "Choose appointment time."
  2. Consistent styling: Ensure that the TimePicker follows the same design and styling as other elements in your interface for a cohesive user experience.
  3. Accessibility: Implement proper HTML attributes to make the TimePicker accessible to users of assistive technologies, such as screen readers.

Don’t

  1. Avoid ambiguity: Avoid using vague labels or icons that may confuse users about the function of the TimePicker.
  2. Overcomplicate: Refrain from adding too many features or functionalities to the TimePicker. Keep it simple and focused on selecting a time.
  3. Inconsistent placement: Do not place the TimePicker in unpredictable locations across your interface. Follow established design patterns for its placement.
  4. Ignoring mobile responsiveness: Ensure that the TimePicker is responsive and functions well on various screen sizes, including mobile devices. Test its usability across different devices and resolutions to ensure a seamless experience for all users.

Best practices

Do

timepicker1

Ensure that the label within the TimePicker display complete and descriptive text. If the text can't fit horizontally, stack them vertically to maintain readability.

Don’t

timepicker2

Steer clear of using label text that is excessively long and requires wrapping onto multiple lines, as it can negatively impact readability and user experience.

Do

timepicker3

Use label text that is clear and prompts the user to take action, such as "Select time" or "Start time"

Don’t

timepicker4

Refrain from adding icons to emphasize label text. Keep the label text simple and focused on the action it performs.

Do

timepicker5

Use sentence-case capitalization for label text to improve readability and consistency.

Don’t

timepicker6

Avoid using title case or all uppercase (all caps) for label text, as this can make the text harder to read and may disrupt the visual harmony of the interface.