your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 // @ts-nocheck
3 import { TimeField } from 'bits-ui';
4 import { Time } from '@internationalized/date';
5 import { untrack } from 'svelte';
6
7 let {
8 value = $bindable(''),
9 required = false,
10 locale = 'en'
11 }: {
12 value: string;
13 required?: boolean;
14 locale?: string;
15 } = $props();
16
17 let internalValue: Time | undefined = $state(undefined);
18
19 function parseTimeStr(str: string): Time | undefined {
20 if (!str) return undefined;
21 const [hourStr, minuteStr] = str.split(':');
22 const hour = parseInt(hourStr, 10);
23 const minute = parseInt(minuteStr, 10);
24 if (isNaN(hour) || isNaN(minute)) return undefined;
25 return new Time(hour, minute);
26 }
27
28 function formatTimeStr(t: Time): string {
29 const h = String(t.hour).padStart(2, '0');
30 const m = String(t.minute).padStart(2, '0');
31 return `${h}:${m}`;
32 }
33
34 $effect(() => {
35 const parsed = parseTimeStr(value);
36 untrack(() => {
37 if (parsed) {
38 if (
39 !internalValue ||
40 parsed.hour !== internalValue.hour ||
41 parsed.minute !== internalValue.minute
42 ) {
43 internalValue = parsed;
44 }
45 } else {
46 internalValue = undefined;
47 }
48 });
49 });
50
51 function handleValueChange(newVal: Time | undefined) {
52 if (newVal && newVal instanceof Time) {
53 internalValue = newVal;
54 value = formatTimeStr(newVal);
55 }
56 }
57</script>
58
59<TimeField.Root
60 bind:value={internalValue}
61 onValueChange={handleValueChange}
62 granularity="minute"
63 {locale}
64 {required}
65>
66 <div
67 class="border-base-300 bg-base-100 text-base-900 focus-within:border-accent-500 dark:border-base-700 dark:bg-base-800 dark:text-base-100 dark:focus-within:border-accent-400 flex items-center rounded-xl border px-2.5 py-1.5 text-sm transition-colors"
68 >
69 <TimeField.Input>
70 {#snippet children({ segments })}
71 {#each segments as segment, i (segment.part + i)}
72 {#if segment.part === 'literal'}
73 <span class="text-base-400 dark:text-base-500">{segment.value}</span>
74 {:else}
75 <TimeField.Segment
76 part={segment.part}
77 class="hover:bg-base-200 focus:bg-base-200 dark:hover:bg-base-700 dark:focus:bg-base-700 rounded px-0.5 focus:outline-none"
78 >
79 {segment.value}
80 </TimeField.Segment>
81 {/if}
82 {/each}
83 {/snippet}
84 </TimeField.Input>
85
86 <svg
87 xmlns="http://www.w3.org/2000/svg"
88 fill="none"
89 viewBox="0 0 24 24"
90 stroke-width="1.5"
91 stroke="currentColor"
92 class="text-base-400 dark:text-base-500 ml-auto size-4 pl-0.5"
93 >
94 <path
95 stroke-linecap="round"
96 stroke-linejoin="round"
97 d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
98 />
99 </svg>
100 </div>
101</TimeField.Root>