Rask
Rask
v0.23.1-alpha.0.494

Components

Time picker

A time picked from a list, for scheduling events or setting appointments. The type it binds says how many: a TimeOnly? for one or none, a collection of TimeOnly for several.

Drawn and behaving as Flux UI's time picker does, measured against its live docs.

Time picker

A button over a list of times: the button keeps the focus, the arrows move through the list and Enter picks.

TimePickerExample.cs
using System.Globalization;

namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerExample : Component
{
    private TimeOnly? _time;

    protected override Component? Render() =>
        Div.Class("max-w-xs space-y-3")[
            Ui.TimePicker.Value(_time).OnChange(time => { _time = time; }).Id("ui-time"),
            Ui.Text.Data("testid", "ui-time-picker-state")[
                _time is { } at ? "Chosen: " + at.ToString("HH:mm", CultureInfo.InvariantCulture) : "No time chosen."
            ]
        ];
}
Live result

No time chosen.

Basic usage

Bound to a TimeOnly? that starts at 11:30, as Flux's value does.

TimePickerBoundExample.cs
using System.Globalization;

namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerBoundExample : Component
{
    private readonly Meeting _meeting = new();

    protected override Component? Render() =>
        Div.Class("max-w-xs space-y-3")[
            Ui.TimePicker.Bind(() => _meeting.StartsAt).Live().Label("Starts at"),
            Ui.Text[_meeting.StartsAt is { } at ? "Starts at " + at.ToString("HH:mm", CultureInfo.InvariantCulture) + "." : "No time yet."]
        ];

    public sealed class Meeting
    {
        public TimeOnly? StartsAt { get; set; } = new TimeOnly(11, 30);
    }
}
Live result

Starts at 11:30.

Input trigger

Hour, minute and the half of the day typed as one field: 9 can only be nine o'clock, so it moves on, and p is the afternoon.

TimePickerInputExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerInputExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().Type(Ui.TimePickerType.Input).Id("ui-time-input")
        ];
}
Live result
: 

Without dropdown

The typed trigger with no list under it.

TimePickerNoDropdownExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerNoDropdownExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().Type(Ui.TimePickerType.Input).Dropdown(false).Id("ui-time-typed")
        ];
}
Live result
: 

Multiple times

Opened on a collection: the list stays open while times are picked.

TimePickerMultipleExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerMultipleExample : Component
{
    private List<TimeOnly> _times = [];

    protected override Component? Render() =>
        Div.Class("max-w-xs space-y-3")[
            Ui.TimePicker.Value(_times).OnChange(times => { _times = [.. times]; }).Id("ui-time-multiple"),
            Ui.Text.Data("testid", "ui-time-picker-slots")[$"{_times.Count} times"]
        ];
}
Live result

0 times

Time format

The culture's own clock unless TwelveHour or TwentyFourHour says otherwise.

TimePickerFormatExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerFormatExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs space-y-4")[
            Ui.TimePicker.Of<TimeOnly?>().TwelveHour.Id("ui-time-twelve"),
            Ui.TimePicker.Of<TimeOnly?>().TwentyFourHour.Id("ui-time-twenty-four")
        ];
}
Live result

Interval

Minutes between the times on offer: 30 unless set.

TimePickerIntervalExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerIntervalExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().Interval(60).Id("ui-time-interval")
        ];
}
Live result

Min/max times

Nine to five. Flux's "now" shorthand is the time it is: TimeOnly.FromDateTime(…).

TimePickerMinMaxExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerMinMaxExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().Min(new TimeOnly(9, 0)).Max(new TimeOnly(17, 0)).Id("ui-time-min-max")
        ];
}
Live result

Unavailable times

Times, and stretches of time as a UiTimeRange, listed and disabled: booked slots.

TimePickerUnavailableExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerUnavailableExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().Id("ui-time-unavailable").Unavailable([
                new TimeOnly(3, 0), new TimeOnly(4, 0), new UiTimeRange(new TimeOnly(5, 30), new TimeOnly(7, 29)),
            ])
        ];
}
Live result

Open to

Where the list opens while nothing is chosen. Otherwise it opens on the chosen time, or the time it is now.

TimePickerOpenToExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerOpenToExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().OpenTo(new TimeOnly(10, 0)).Id("ui-time-open")
        ];
}
Live result

Localization

The times are written in the app's culture unless Locale names another.

TimePickerLocaleExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class TimePickerLocaleExample : Component
{
    protected override Component? Render() =>
        Div.Class("max-w-xs")[
            Ui.TimePicker.Of<TimeOnly?>().Locale("ja-JP").Id("ui-time-locale")
        ];
}
Live result

Behaviour

What it binds

TimeOnly? picks one time or none, TimeOnly always one, and a collection of TimeOnly several, Flux's multiple. A bound time is written with the reader's next action, at once with .Live(). Unavailable takes times and UiTimeRange stretches, listed but disabled; Clearable() puts a cross in the button while a time is chosen.

The keyboard

The button is the combobox and keeps the focus. The open list holds the page still (data-rask-lock), and scrolls itself to keep the active time in view while the page stays.

Key
What happens
Space, ArrowDown, ArrowUpOpen the list. Enter on the closed button does nothing, as on Flux.
ArrowDown, ArrowUpMove the cursor through the list.
EnterPicks the lit time and closes; with several it switches the time and the list stays open.
EscapeCloses.

Typed times

The typed trigger is one field in parts, as the date picker's is: 9 is nine o'clock and moves on, a and p set the half of the day, and a whole time reaches the binding through one hidden field as HH:mm.

The same departure from Flux as the date picker's, by the same decision: an hour with no minute is no time, "Enter a valid time." shows under the field and the save is refused, and every part emptied is no time chosen.

Reference

Ui.TimePicker

flux:time-picker

A button over a list of times. A TimeOnly? picks one or none, a collection of TimeOnly several.

Step
Takes
What it does
.Bind(…)() => a TThe member it reads and writes: () => m.StartsAt. Or Value with OnChange.
.Value(…)T: a TimeOnly?, a TimeOnly, or timesWhat it holds, for a page that holds it itself.
.OnChange(…)a handler given the new valueRuns with the new value when a time is picked.
.AfterBind(…)a handler given the new valueRuns after Bind has written the model.
.Validate(…)a validatorA rule the value is checked by when it is written.
.Live()boolWrites the model as soon as a time is picked, not with the next action.
.Type(…)
Ui.TimePickerType
Button
Input

Each is a step of its own too, unless an HTML tag has its name.

A button, or fields to type the time into. The button when unset.
.Dropdown()boolFalse leaves the typed trigger without its list.
.TimeFormat(…)
Ui.TimePickerTimeFormat
Auto
TwelveHour
TwentyFourHour

Each is a step of its own too, unless an HTML tag has its name.

12-hour or 24-hour, each a step too (.TwelveHour). The culture's own when unset.
.Interval(…)intMinutes between the times on offer. 30 when unset.
.Min(…)a TimeOnlyThe earliest time on offer.
.Max(…)a TimeOnlyThe latest time on offer.
.Unavailable(…)TimeOnly values and UiTimeRange stretchesTimes, and stretches of time, listed and disabled.
.OpenTo(…)a TimeOnlyWhere the list opens while nothing is chosen.
.Label(…)stringDraws a field around it, with this label and the error.
.Description(…)stringHelp text between the label and the picker.
.DescriptionTrailing(…)stringHelp text under the picker.
.Badge(…)stringA word beside the label: "Optional".
.Placeholder(…)stringWhat the button shows while no time is chosen. "Select a time" when unset.
.Size(…)
Ui.TimePickerSize
Base
Sm
Xs

Each is a step of its own too, unless an HTML tag has its name.

How tall it is. 40px when unset.
.Clearable()boolA button that empties the picker, while a time is chosen.
.Disabled()boolTakes no input and is skipped by the keyboard.
.Invalid()boolError styling the form did not ask for.
.Locale(…)stringThe culture the times are written in: "ja-JP". The app's culture when unset.
.ShowValidation()boolFalse leaves the member's message to a Ui.Error placed elsewhere.
Confirm