Basics Done

This commit is contained in:
2026-04-13 15:08:49 +05:00
parent 9bef5813ae
commit 06ec22704b
75 changed files with 5036 additions and 2733 deletions
@@ -0,0 +1,151 @@
@namespace Enciphered.Blazor.UIComponents
@* ── shadcn/ui-style time picker with scrollable columns ─────────────── *@
<div class="flex items-stretch gap-1 p-4" @attributes="AdditionalAttributes">
@* ── Hour column ── *@
<div class="flex flex-col items-center">
<span class="text-xs font-medium text-muted-foreground mb-2">Hr</span>
<div class="h-52 w-16 overflow-y-auto flex flex-col gap-1 scrollbar-thin pr-1">
@for (int h = 0; h < (_use12Hour ? 12 : 24); h++)
{
var hour = _use12Hour ? (h == 0 ? 12 : h) : h;
var hourValue = h;
var isSelected = _selectedHour == hourValue;
<button type="button"
class="@TimeItemClass(isSelected)"
@onclick="() => SelectHour(hourValue)">
@hour.ToString("D2")
</button>
}
</div>
</div>
<div class="flex items-center px-1.5 text-muted-foreground font-medium text-lg">:</div>
@* ── Minute column ── *@
<div class="flex flex-col items-center">
<span class="text-xs font-medium text-muted-foreground mb-2">Min</span>
<div class="h-52 w-16 overflow-y-auto flex flex-col gap-1 scrollbar-thin pr-1">
@for (int m = 0; m < 60; m += _minuteStep)
{
var minute = m;
var isSelected = _selectedMinute == minute;
<button type="button"
class="@TimeItemClass(isSelected)"
@onclick="() => SelectMinute(minute)">
@minute.ToString("D2")
</button>
}
</div>
</div>
@if (_use12Hour)
{
<div class="flex items-center px-1"></div>
@* ── AM/PM column ── *@
<div class="flex flex-col items-center">
<span class="text-xs font-medium text-muted-foreground mb-2"> </span>
<div class="flex flex-col gap-1">
<button type="button"
class="@TimeItemClass(!_isPm)"
@onclick='() => SelectPeriod(false)'>
AM
</button>
<button type="button"
class="@TimeItemClass(_isPm)"
@onclick='() => SelectPeriod(true)'>
PM
</button>
</div>
</div>
}
</div>
@code {
/// <summary>The currently selected time (two-way bindable).</summary>
[Parameter] public TimeOnly? SelectedTime { get; set; }
[Parameter] public EventCallback<TimeOnly?> SelectedTimeChanged { get; set; }
/// <summary>Use 12-hour format with AM/PM. Default is false (24-hour).</summary>
[Parameter] public bool Use12Hour { get; set; }
/// <summary>Minute step interval. Default is 1.</summary>
[Parameter] public int MinuteStep { get; set; } = 1;
/// <summary>Any extra HTML attributes.</summary>
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }
private int _selectedHour;
private int _selectedMinute;
private bool _isPm;
private bool _use12Hour;
private int _minuteStep = 1;
protected override void OnInitialized()
{
_use12Hour = Use12Hour;
_minuteStep = MinuteStep < 1 ? 1 : MinuteStep;
ApplyFromValue();
}
protected override void OnParametersSet()
{
_use12Hour = Use12Hour;
_minuteStep = MinuteStep < 1 ? 1 : MinuteStep;
ApplyFromValue();
}
private void ApplyFromValue()
{
if (SelectedTime is { } t)
{
if (_use12Hour)
{
_isPm = t.Hour >= 12;
_selectedHour = t.Hour % 12;
_selectedMinute = t.Minute;
}
else
{
_selectedHour = t.Hour;
_selectedMinute = t.Minute;
}
}
}
private async Task SelectHour(int hour)
{
_selectedHour = hour;
await EmitValue();
}
private async Task SelectMinute(int minute)
{
_selectedMinute = minute;
await EmitValue();
}
private async Task SelectPeriod(bool isPm)
{
_isPm = isPm;
await EmitValue();
}
private async Task EmitValue()
{
var hour = _use12Hour ? (_selectedHour % 12) + (_isPm ? 12 : 0) : _selectedHour;
var time = new TimeOnly(hour, _selectedMinute);
SelectedTime = time;
await SelectedTimeChanged.InvokeAsync(time);
}
private static string TimeItemClass(bool isSelected)
{
const string baseClass = "h-9 w-full inline-flex items-center justify-center rounded-md text-sm transition-colors cursor-pointer";
return isSelected
? $"{baseClass} bg-primary text-primary-foreground font-semibold"
: $"{baseClass} hover:bg-accent hover:text-accent-foreground";
}
}