import React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import type { TimePickerProps } from '../../../types/time-picker';
import { Button } from '../../button/public';
import { View } from '../../layout/public';
import { Text } from '../../typography/public';
const MINUTES_PER_DAY = 24 * 60;
const DEFAULT_STEP_MINUTES = 30;
/*** Renders shared time options for native BottomSheet and web Popover hosts. */
export function TimePickerContent({
description,
formatTime,
interactionPolicy,
label,
maxTime,
minTime,
onDismiss,
onSelect,
stepMinutes,
testID,
value,
}: TimePickerContentProps) {
const options = React.useMemo(
() => generateTimeOptions({ maxTime, minTime, stepMinutes }),
[maxTime, minTime, stepMinutes],
);
return (
{options.map((option) => (
))}
);
}
interface TimePickerContentProps extends Pick<
TimePickerProps,
| 'description'
| 'formatTime'
| 'interactionPolicy'
| 'label'
| 'maxTime'
| 'minTime'
| 'stepMinutes'
| 'testID'
| 'value'
> {
onDismiss: () => void;
onSelect: (value: string) => void;
}
/*** Renders the shared title block used by picker overlays. */
function PickerHeader({
description,
title,
}: {
description: React.ReactNode | undefined;
title: React.ReactNode;
}) {
return (
{title}
{description ? (
{description}
) : null}
);
}
/*** Renders one accessible selectable time option. */
function TimePickerOption({
formatTime,
interactionPolicy,
onSelect,
option,
selected,
testID,
}: Pick & {
onSelect: (value: string) => void;
option: string;
selected: boolean;
}) {
return (
);
}
/*** Parses one HH:mm value to minutes since midnight. */
function parseTimeToMinutes(value: string | undefined): number | undefined {
if (!value) return undefined;
const match = /^(\d{2}):(\d{2})$/.exec(value);
if (!match) return undefined;
const hours = Number(match[1]);
const minutes = Number(match[2]);
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return undefined;
return hours * 60 + minutes;
}
/*** Formats minutes since midnight as HH:mm. */
function formatMinutes(value: number): string {
const hours = Math.floor(value / 60);
const minutes = value % 60;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
}
/*** Normalizes the configured time step to a positive whole minute. */
function resolveStepMinutes(stepMinutes: number | undefined): number {
if (stepMinutes === undefined || !Number.isFinite(stepMinutes) || stepMinutes <= 0) {
return DEFAULT_STEP_MINUTES;
}
return Math.max(1, Math.floor(stepMinutes));
}
/*** Generates selectable HH:mm values within the configured range. */
function generateTimeOptions({
minTime,
maxTime,
stepMinutes,
}: Pick): string[] {
const step = resolveStepMinutes(stepMinutes);
const min = parseTimeToMinutes(minTime) ?? 0;
const max = parseTimeToMinutes(maxTime) ?? MINUTES_PER_DAY - 1;
return Array.from({ length: Math.ceil(MINUTES_PER_DAY / step) }, (_, index) => index * step)
.filter((minute) => minute >= min && minute <= max)
.map(formatMinutes);
}
const styles = StyleSheet.create({
options: { maxHeight: 360 },
});