Skip to content
This repository has been archived by the owner on Feb 1, 2025. It is now read-only.

Commit

Permalink
Use Melt combobox for datetime input
Browse files Browse the repository at this point in the history
  • Loading branch information
tylermercer committed Jul 6, 2024
1 parent b23ca16 commit f589dac
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 50 deletions.
176 changes: 130 additions & 46 deletions src/lib/components/DatetimeInput.svelte
Original file line number Diff line number Diff line change
@@ -1,62 +1,146 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { parse } from 'chrono-node';
import { createCombobox, melt, type ComboboxOptionProps } from '@melt-ui/svelte';
import { parse, type ParsedResult } from 'chrono-node';
import { dateToString } from '$lib/util/dateUtils';
import { fly } from 'svelte/transition';
import UpArrow from 'virtual:icons/teenyicons/up-outline';
export let date = new Date();
export let id = '';
let inputValue = dateToString(date);
let displayValue = '';
const now = new Date();
function getDate(input: string) {
const results = parse(input);
return results.length > 0 ? results[0].date() : undefined;
}
export let date = now;
function handleInput(event: Event) {
const input = (event.target as HTMLInputElement).value;
const result = getDate(input);
displayValue = result ? dateToString(result) : '';
}
const toOption = (result: ParsedResult): ComboboxOptionProps<ParsedResult> => ({
value: result,
label: dateToString(result.date())
});
function handleBlur() {
inputValue = dateToString(date);
displayValue = '';
}
const {
elements: { menu, input, option, label },
states: { open, inputValue, touchedInput, selected }
// helpers: { isSelected }
} = createCombobox<ParsedResult>({
forceVisible: true
});
function handleEnter(event: Event) {
const kbdEvent = event as KeyboardEvent;
if (kbdEvent.key === 'Enter' && displayValue) {
const result = getDate((kbdEvent.target as HTMLInputElement).value);
if (result) {
date = result;
inputValue = result.toLocaleString();
(kbdEvent.target as HTMLInputElement).blur();
}
}
$: if (!$open) {
$inputValue = $selected?.label ?? '';
}
function handleFocus(event: FocusEvent) {
(event.target as HTMLInputElement).select();
$: resultDates = $touchedInput ? parse($inputValue) : [];
$: {
const submitted = $selected?.value.date();
if (submitted != null) {
date = submitted;
} else {
date = now;
}
}
</script>

<input
{id}
type="text"
bind:value={inputValue}
on:input={handleInput}
on:blur={handleBlur}
on:keydown={handleEnter}
on:focus={handleFocus}
/>
{#if displayValue}
<div class="result">{displayValue}</div>
<div class="flex flex-col gap-1">
<!-- svelte-ignore a11y-label-has-associated-control - $label contains the 'for' attribute -->
<label use:melt={$label}>Date and time</label>

<div class="input-container">
<input use:melt={$input} placeholder="Right now" />
<div class="arrow" class:arrow-open={$open}>
<UpArrow />
</div>
</div>
</div>
{#if $open}
<ul class="menu" use:melt={$menu} transition:fly={{ duration: 150, y: -5 }}>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div class="inner-menu" tabindex="0">
{#each resultDates as result, index (index)}
<li use:melt={$option(toOption(result))} class="option">{dateToString(result.date())}</li>
{:else}
<li class="no-results">Unknown date</li>
{/each}
</div>
</ul>
{/if}

<style>
.result {
margin-top: 5px;
<style lang="scss">
.input-container {
position: relative;
}
li {
list-style-type: none;
}
.menu {
list-style-type: none;
padding: 0;
z-index: 10;
display: flex;
max-height: 300px;
flex-direction: column;
overflow: hidden;
border-radius: 0.5rem;
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.inner-menu {
display: flex;
max-height: 100%;
flex-direction: column;
gap: 0;
overflow-y: auto;
background-color: white;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: black;
}
.arrow {
pointer-events: none;
transform-origin: center;
font-size: var(--step--1);
position: absolute;
right: 0;
aspect-ratio: 1;
height: 100%;
top: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
color: var(--gray-11);
transition: transform ease-in-out 100ms;
&-open {
transform: rotate(-180deg);
}
}
.option {
position: relative;
cursor: pointer;
border-radius: 0.375rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 1rem;
padding-right: 1rem;
color: var(--gray-12);
&:hover {
background-color: var(--primary-3);
color: var(--primary-12);
}
&[data-highlighted] {
background-color: var(--primary-4);
color: var(--primary-12);
}
&[data-disabled] {
opacity: 0.5;
}
}
.no-results {
position: relative;
border-radius: 0.375rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
padding-left: 2rem;
padding-right: 1rem;
}
</style>
5 changes: 1 addition & 4 deletions src/routes/(nav)/logs/[formId]/new-entry/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@
{/each}
<p>{status}</p>
<div class="l-switcher l-space-xs date-and-buttons">
<label for="datetime" class="label-datetime">
Date and time
<DatetimeInput id="datetime" bind:date={datetime} />
</label>
<DatetimeInput id="datetime" bind:date={datetime} />
<div class="l-cluster-r l-space-xs">
<button type="submit" aria-busy={saving}>
{#if saving}
Expand Down

0 comments on commit f589dac

Please sign in to comment.