Pantrix UI

Time Text Field

A text field for a time of day: the time is typed in digits, or chosen from a time picker that a button at the box's end opens.

import com.pantrixui.components.date.timetextfield.PantrixTimeTextField

Examples

A meeting's start, on the quarter hour

A meeting's start, on the quarter hour: the picker's minutes come fifteen apart. A minute between them can be typed too. Once the time is whole it is written below.

var start by remember { mutableStateOf<PantrixTime?>(null) }
val strings = PantrixTheme.strings
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixTimeTextField(
        value = start,
        onValueChange = { start = it },
        label = "Meeting start",
        minuteStep = QUARTER,
        showClearButton = true,
        tagKey = "meeting_start"
    )
    PantrixText(
        text = start?.let { "Start: ${strings.time(it.hour, it.minute, is24Hour = true)}" }
            ?: "No time yet",
        style = PantrixTheme.typography.body
    )
}

private const val QUARTER = 15

Within working hours

A call within working hours: a time typed before 09:00 or after 18:00 is not taken, and the field says the two ends. In the picker the hours outside are faint and cannot be pressed.

var call by remember { mutableStateOf<PantrixTime?>(null) }
PantrixTimeTextField(
    value = call,
    onValueChange = { call = it },
    label = "Call time",
    supportingText = "Weekdays from 09:00 to 18:00",
    minTime = PantrixTime(OPENS, 0),
    maxTime = PantrixTime(CLOSES, 0),
    tagKey = "call"
)

private const val OPENS = 9

private const val CLOSES = 18

A clock of twelve hours

A clock of twelve hours: the hours are typed 01 to 12, and the half of the day stands in the box beside the digits. A press on it turns it between AM and PM; the picker has it as a third column too.

var alarm by remember { mutableStateOf<PantrixTime?>(PantrixTime(7, 30)) }
val strings = PantrixTheme.strings
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixTimeTextField(
        value = alarm,
        onValueChange = { alarm = it },
        label = "Alarm",
        is24Hour = false,
        tagKey = "alarm"
    )
    PantrixText(
        text = alarm?.let { "Alarm: ${strings.time(it.hour, it.minute, is24Hour = false)}" } ?: "Alarm off",
        style = PantrixTheme.typography.body
    )
}

The picker in a sheet or a dialog

The picker opens in a sheet: near the thumb on a phone, as wide as the screen. presentation = Dialog opens it in a dialog in the middle of the window. The sheet stays open at each press; a press outside or back closes it.

var pickup by remember { mutableStateOf<PantrixTime?>(null) }
var presentation by remember { mutableStateOf(DatePickerPresentation.Sheet) }
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixSegmentButton(
        options = listOf(
            SegmentOption(DatePickerPresentation.Popover, "Popover", tagKey = "popover"),
            SegmentOption(DatePickerPresentation.Sheet, "Sheet", tagKey = "sheet"),
            SegmentOption(DatePickerPresentation.Dialog, "Dialog", tagKey = "dialog")
        ),
        selected = presentation,
        onSelect = { presentation = it },
        fullWidth = false,
        tagKey = "presentation"
    )
    PantrixTimeTextField(
        value = pickup,
        onValueChange = { pickup = it },
        label = "Pickup time",
        minuteStep = FIVE_MINUTES,
        presentation = presentation,
        tagKey = "pickup"
    )
}

private const val FIVE_MINUTES = 5

The app's error and disabled

The app's own error comes before the field's: a time already taken is said with error and supportingText. The field under it is disabled: it takes no focus and opens no picker.

var slot by remember { mutableStateOf<PantrixTime?>(PantrixTime(BOOKED, 0)) }
val taken = slot == PantrixTime(BOOKED, 0)
PantrixTimeTextField(
    value = slot,
    onValueChange = { slot = it },
    label = "Appointment time",
    supportingText = if (taken) "This time is taken, choose another" else "A half-hour appointment",
    error = taken,
    tagKey = "slot"
)
PantrixTimeTextField(
    value = PantrixTime(OPENS, 0),
    onValueChange = {},
    enabled = false,
    label = "Opening time",
    tagKey = "opening"
)

private const val OPENS = 9

private const val BOOKED = 14

Read-only: from the picker alone

A read-only field is not typed into: the time is chosen from the picker alone. A press on the box, Enter or Space opens the picker. For a place where a time must be chosen from a list.

var departure by remember { mutableStateOf<PantrixTime?>(null) }
PantrixTimeTextField(
    value = departure,
    onValueChange = { departure = it },
    readOnly = true,
    label = "Departure time",
    placeholder = "Choose from the picker",
    minuteStep = QUARTER,
    tagKey = "departure"
)

private const val QUARTER = 15

Bound to a form

Bound to a form's field: with no time, "Save" shows the error and sends the focus to the field. The error shows on leaving the field too; going into the picker is no leaving it. Digits that make no time leave the value null, and the rule catches it.

val form = rememberFormState()
val scope = rememberCoroutineScope()
val report = LocalExampleEvents.current
val start = form.field<PantrixTime?>("shift_start", initial = null, Rules.required("Enter the shift's start"))
PantrixTimeTextField(
    field = start,
    modifier = Modifier.fillMaxWidth(),
    form = form,
    label = "Shift start",
    minuteStep = QUARTER
)
PantrixButton(
    text = "Save",
    onClick = { scope.launch { form.submit { report("Saved") } } },
    form = form,
    tagKey = "save_shift"
)

private const val QUARTER = 15

On a state

On a state: state.time is the field's time, state.text the digits typed. Together they tell an empty field from a half-typed one. The button changes the state's time, and the field writes it in itself, by a clock of twelve hours with the half of the day.

val state = rememberTimeTextFieldState()
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixTimeTextField(
        state = state,
        label = "Reminder",
        is24Hour = false,
        tagKey = "reminder"
    )
    PantrixText(
        text = when {
            state.time != null -> "Time: ${state.time}"
            state.text.isEmpty() -> "The field is empty"
            else -> "Half typed: ${state.text}"
        },
        style = PantrixTheme.typography.body
    )
    PantrixButton(
        text = "Write now",
        onClick = { state.time = PantrixTime.now() },
        variant = ButtonVariant.Outline,
        tagKey = "write_now"
    )
}

API

value

A text field for a time of day: the time is typed in digits, or chosen from a time picker that a button at the box's end opens.

ParameterTypeDefaultWhat it is
valuePantrixTime?the time the field shows, or null.
onValueChange(PantrixTime?) -> Unittold the field's time each time the user changes it: a time, or null for none.
modifierModifierModifierthe modifier of the whole field: its label, its box and what is under it.
enabledBooleantruewhether the field can be used. One that cannot takes no focus and opens no picker.
readOnlyBooleanfalsewhether the time can only be chosen from the picker, and not typed.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader, and its picker's.
placeholderString?nullshown in the box while it is empty, in the place of the pattern the field shows by itself.
supportingTextString?nullhelp under the box. With error it is the message that says what to put right; without it, the field's own error takes its place while there is one.
errorBooleanfalsewhether the caller finds the time wrong, as a time already booked: the field is drawn in error, and supportingText is the message.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text. A slot does not size what is put in it.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
minTimePantrixTime?nullthe first time the field takes, or null for no first. Where it is after maxTime, the times run past midnight, as a night's do.
maxTimePantrixTime?nullthe last time the field takes, or null for no last.
minuteStepInt1how many minutes apart the picker's minutes are, 1 to 60. A minute typed between them is taken.
is24HourBooleantruewhether the field and its picker count the hours of a day, 00 to 23, or by a clock of twelve hours.
presentationDatePickerPresentationDatePickerPresentation.Adaptivewhat the picker opens in: by default a sheet in a narrow window and a popover under the box in a wider one, chosen as it opens; or always a popover, a sheet or a dialog.
styleTextFieldStyle?nullthis call's own style of the field: over every enclosing one and under the density.
colorsTextFieldColors?nullthis call's own colours of the field, in place of the theme's roles.
pickerStyleTimePickerStyle?nullthis call's own style of the picker.
pickerColorsTimePickerColors?nullthis call's own colours of the picker.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
tagKeyString?nullthe field's automation key: the field is TimeTextField_<tagKey>, its button TimeTextFieldClock_<tagKey>, its half of the day TimeTextFieldPeriod_<tagKey>, its clear button TimeTextFieldClear_<tagKey> and its picker TimePicker_<tagKey>. The field reports that the picker opened and that a value was chosen in it, and never the time.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted: its focus, its hover and its presses.

state

A time field on a state its caller keeps, as PantrixTimeTextField of a value is in every other way: the state's time is the field's time, setting it writes the time in, and its text tells a field that is half typed from an empty one.

ParameterTypeDefaultWhat it is
stateTimeTextFieldStatethe digits typed and the time they make. Create it with rememberTimeTextFieldState().
modifierModifierModifierthe modifier of the whole field: its label, its box and what is under it.
enabledBooleantruewhether the field can be used. One that cannot takes no focus and opens no picker.
readOnlyBooleanfalsewhether the time can only be chosen from the picker, and not typed.
labelString?nullwhat the field is for, written above the box: the field's name for a screen reader, and its picker's.
placeholderString?nullshown in the box while it is empty, in the place of the pattern the field shows by itself.
supportingTextString?nullhelp under the box. With error it is the message; without it, the field's own error takes its place while there is one.
errorBooleanfalsewhether the caller finds the time wrong: the field is drawn in error, and supportingText is the message.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
minTimePantrixTime?nullthe first time the field takes, or null for no first; after maxTime, the times run past midnight.
maxTimePantrixTime?nullthe last time the field takes, or null for no last.
minuteStepInt1how many minutes apart the picker's minutes are, 1 to 60.
is24HourBooleantruewhether the field and its picker count the hours of a day, or by a clock of twelve hours.
presentationDatePickerPresentationDatePickerPresentation.Adaptivewhat the picker opens in: by default a sheet in a narrow window and a popover under the box in a wider one, chosen as it opens; or always a popover, a sheet or a dialog.
styleTextFieldStyle?nullthis call's own style of the field.
colorsTextFieldColors?nullthis call's own colours of the field.
pickerStyleTimePickerStyle?nullthis call's own style of the picker.
pickerColorsTimePickerColors?nullthis call's own colours of the picker.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
tagKeyString?nullthe field's automation key, as PantrixTimeTextField of a value takes it.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted.

field

A time field bound to a form's field: its time, its error and its name come from field.

ParameterTypeDefaultWhat it is
fieldFormField<PantrixTime?>the form's field the time field shows and changes.
modifierModifierModifierthe modifier of the whole field.
formFormState?nullgiven, the keyboard's action key submits the form through the button registered with it.
enabledBooleantruewhether the field can be used.
readOnlyBooleanfalsewhether the time can only be chosen from the picker.
labelString?nullwhat the field is for, written above the box.
placeholderString?nullshown in the box while it is empty, in the place of the pattern.
supportingTextString?nullhelp under the box, while the field has no error to show there.
leadingIcon(@Composable () -> Unit)?nullwhat stands before the text.
showClearButtonBooleanfalseshows a button that empties the field, while it can be typed into and is not empty.
minTimePantrixTime?nullthe first time the field takes, or null for no first; after maxTime, the times run past midnight.
maxTimePantrixTime?nullthe last time the field takes, or null for no last.
minuteStepInt1how many minutes apart the picker's minutes are, 1 to 60.
is24HourBooleantruewhether the field and its picker count the hours of a day, or by a clock of twelve hours.
presentationDatePickerPresentationDatePickerPresentation.Adaptivewhat the picker opens in: by default a sheet in a narrow window and a popover under the box in a wider one, chosen as it opens; or always a popover, a sheet or a dialog.
styleTextFieldStyle?nullthis call's own style of the field.
colorsTextFieldColors?nullthis call's own colours of the field.
pickerStyleTimePickerStyle?nullthis call's own style of the picker.
pickerColorsTimePickerColors?nullthis call's own colours of the picker.
contentDescriptionString?nullwhat a screen reader says for the field where it has no label.
interactionSourceMutableInteractionSource?nullthe field's interactions, hoisted.

TextFieldStyle

PantrixTextField's box: its shape, how tall and how wide it is at the least, the room around its text, its icons and its border.

ParameterTypeDefaultWhat it is
shapeShapeSpecShapeSpec.UnspecifiedThe box's shape. A fully round one, ShapeRole.Full, is a pill only while the field holds one line: a field of several lines takes ShapeRole.Large instead, since a capsule's curve runs into the first line of its text.
heightDpDp.UnspecifiedThe least the box is tall.
minWidthDpDp.UnspecifiedThe least the box is wide, which keeps an empty field from growing as the user types into it.
horizontalPaddingDpDp.UnspecifiedThe room between the box's edge and its text, where no icon is.
verticalPaddingDpDp.UnspecifiedThe room above the first line of the text and below the last.
iconPaddingDpDp.UnspecifiedThe room around an icon in its slot.
iconSizeDpDp.UnspecifiedThe size an icon in a slot should take. A slot does not size what is put in it.
borderWidthDpDp.UnspecifiedHow thick the box's border is.
focusedBorderWidthDpDp.UnspecifiedHow thick the border is while the field has the focus: the border is what shows it.
textRoleTypeRole?nullThe role of the type scale the text is typed in, and its placeholder set in: the body's, 16sp, on Android and iOS, as Material 3 and iOS type a field, and the label's, 14sp, on the desktop and the web, as a page under a pointer does. A select's box and the fields made on this style take it as well.

TextFieldColors

What PantrixTextField is painted with, for a field that does not take the theme's: a colour that is set is used, and one that is left out stays the theme's role. A product gives the colours it changes and no others:

ParameterTypeDefaultWhat it is
containerColorColor.UnspecifiedThe box's fill. The theme's surface.
borderColorColor.UnspecifiedThe box's border at rest. The theme's border.
hoveredBorderColorColor.UnspecifiedThe border under a pointer. The theme's borderHover, a notch stronger than border.
focusedBorderColorColor.UnspecifiedThe border while the field has the focus. The theme's primary.
errorColorColor.UnspecifiedWhat says the text is wrong: the border, the label, the message under the box, the caret, and the count once it is past its limit. The theme's danger.
textColorColor.UnspecifiedThe typed text. The theme's textPrimary.
labelColorColor.UnspecifiedThe label above the box. Left out, it is drawn as any text is where the field stands, since it is outside the box on whatever the field is put on: in textPrimary on the theme's own ground, and in the content colour of a fill.
placeholderColorColor.UnspecifiedThe placeholder. The theme's textSecondary.
supportingTextColorColor.UnspecifiedThe help under the box, and the count. The theme's textSecondary.
iconColorColor.UnspecifiedWhat an icon in a slot is drawn in unless it sets a tint of its own, and the clear button's cross. The theme's textSecondary.
cursorColorColor.UnspecifiedThe caret, and with it the handles and the highlight of a selection. The theme's primary.
scrollBarColorColor.UnspecifiedThe bar that shows how far the text of a box of several lines is scrolled. The theme's borderStrong: it is a mark beside the text, and the box's soft edge would not be seen there.