Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/2824 date input #2864

Open
wants to merge 22 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-trains-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@evidence-dev/core-components': patch
---

Feature: DateInput - supports single and ranged calendar date inputs
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@
}
},
"packageManager": "[email protected]+sha512.499434c9d8fdd1a2794ebf4552b3b25c0a633abcee5bb15e7b5de90f32f47b513aca98cd5cfd001c31f0db454bc3804edccd578501e4ca293a6816166bbd9f81"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script context="module">
/** @type {import("@storybook/svelte").Meta}*/
export const meta = {
title: 'Atoms/inputs/DateInput',
argTypes: {},
args: {
title: 'Date Input',
name: 'DateInput',
omitGroup: []
}
};
</script>

<script>
import { Template, Story } from '@storybook/addon-svelte-csf';
import DateInput from './DateInput.svelte';
import { getInputContext } from '@evidence-dev/sdk/utils/svelte';
const inputStore = getInputContext();

// Mock "today"
import MockDate from 'mockdate';
MockDate.set('2024-12-30');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this persist after you switch to another story?

Would probably be good to reset it

Suggested change
MockDate.set('2024-12-30');
MockDate.set('2024-12-30');
onMount(() => () => MockDate.reset());

</script>

<Template let:args>
<DateInput {...args} />
</Template>

<Story name="Basic Usage" let:args>
<h1>
Input Store:
<h2>value: {$inputStore.dateInput.value}</h2>
<h2>start: {$inputStore.dateInput.start}</h2>
<h2>end: {$inputStore.dateInput.end}</h2>
</h1>
<DateInput {...args} name="dateInput" />
</Story>
<Story name="dateInput_range" let:args>
<h1>
Input Store:
<h2>value: {$inputStore.dateInput_range.value}</h2>
<h2>start: {$inputStore.dateInput_range.start}</h2>
<h2>end: {$inputStore.dateInput_range.end}</h2>
</h1>
<DateInput {...args} name="dateInput_range" range />
</Story>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<script context="module">
export const evidenceInclude = true;
</script>

<script>
import DateInput from './_DateInput.svelte';
import { Query } from '@evidence-dev/sdk/usql';
import { getQueryFunction } from '@evidence-dev/component-utilities/buildQuery';
import { getLocalTimeZone } from '@internationalized/date';
import HiddenInPrint from '../shared/HiddenInPrint.svelte';
import { page } from '$app/stores';
import QueryLoad from '$lib/atoms/query-load/QueryLoad.svelte';
import { Skeleton } from '$lib/atoms/skeletons/index.js';
import { getInputContext } from '@evidence-dev/sdk/utils/svelte';

function dateToYYYYMMDD(date) {
return date.toISOString().split('T')[0];
}

const inputs = getInputContext();

/** @type {string} */
export let name;
/** @type {string | undefined} */
export let title;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional props like these should have a default value of undefined, otherwise a bunch of warnings pop up in dev

/** @type {boolean} */
export let hideDuringPrint = true;

/** @type {string | Date | undefined} */
export let start;
/** @type {string | Date | undefined} */
export let end;

/** @type {Query | string | undefined} */
export let data;
/** @type {string | undefined} */
export let dates;
/** @type {[]string | undefined} */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** @type {[]string | undefined} */
/** @type {string[] | undefined} */

export let presetRanges;
/** @type {string | undefined} */
export let defaultValue;
/** @type {boolean} */
export let range = false;
Copy link
Member

@archiewood archiewood Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any user facing boolean should accept a string also

<DateInput
  range=true
/>

as well as

<DateInput
  range={true}
/>


const exec = getQueryFunction();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ItsMeBrianD is there an alternative we should prefer to direct Query.create/getQueryFunction here

let query;
$: if (data && dates) {
const source = typeof data === 'string' ? data : `(${data.text})`;
query = Query.create(
`SELECT min(${dates}) as start, max(${dates}) as end FROM ${source}`,
exec,
{
initialData: $page?.data?.data[`DateInput-${name}_data`],
knownColumns: $page?.data?.data[`DateInput-${name}_columns`],
disableCache: true,
noResolve: false,
id: `DateInput-${name}`
}
);
query.fetch();
}
const YYYYMMDD = /^\d{4}-\d{2}-\d{2}$/;
$: startString =
typeof start === 'string' && YYYYMMDD.test(start)
? start
: start instanceof Date
? dateToYYYYMMDD(start)
: $query?.[0].start instanceof Date
? dateToYYYYMMDD($query?.[0].start)
: dateToYYYYMMDD(new Date(0));
$: endString =
typeof end === 'string' && YYYYMMDD.test(end)
? end
: end instanceof Date
? dateToYYYYMMDD(end)
: $query?.[0].end instanceof Date
? dateToYYYYMMDD($query?.[0].end)
: dateToYYYYMMDD(new Date());

$: if ((query && $query.dataLoaded) || !query) {
if (range) {
$inputs[name] = { value: undefined, start: startString, end: endString };
} else {
$inputs[name] = { value: startString, start: startString, end: undefined };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set start here? Seems more like it should just be start/end for range vs. value for non-range

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I'd prefer to omit the undefined props rather than giving them the value of undefined

Copy link
Member

@archiewood archiewood Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was my suggestion

My initial thought was start is probably worth keeping start as someone might change from <DateInput range=true to range=false, and then it break the rest of their code.

honestly, probably should remove start from DateInput range=false

now I think about it you are likely going to have to change the SQL anyway from between to =

}
}

let currentDate = dateToYYYYMMDD(new Date(Date.now()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let currentDate = dateToYYYYMMDD(new Date(Date.now()));
let currentDate = dateToYYYYMMDD(new Date());

new Date() will create a date with the current date/time, no need for Date.now()


let selectedDateInput;
$: if (selectedDateInput && (selectedDateInput.start || selectedDateInput.end) && range) {
$inputs[name] = {
value: undefined,
start: dateToYYYYMMDD(selectedDateInput.start?.toDate(getLocalTimeZone()) ?? new Date(0)),
end: dateToYYYYMMDD(selectedDateInput.end?.toDate(getLocalTimeZone()) ?? new Date())
};
} else if (selectedDateInput && selectedDateInput && !range) {
$inputs[name] = {
value: dateToYYYYMMDD(selectedDateInput.toDate(getLocalTimeZone()) ?? new Date(0)),
start: dateToYYYYMMDD(selectedDateInput.toDate(getLocalTimeZone()) ?? new Date(0)),
end: undefined
};
}
</script>

<HiddenInPrint enabled={hideDuringPrint}>
<div class="mt-2 mb-4 ml-0 mr-2 inline-block">
{#if title}
<span class="text-sm text-gray-500 block mb-1">{title}</span>
{/if}

{#if $query?.error}
<span
class="group inline-flex items-center relative cursor-help cursor-helpfont-sans px-1 border border-red-200 py-[1px] bg-red-50 rounded"
>
<span class="inline font-sans font-medium text-xs text-red-600">error</span>
<span
class="hidden text-white font-sans group-hover:inline absolute -top-1 left-[105%] text-sm z-10 px-2 py-1 bg-gray-800/80 leading-relaxed min-w-[150px] w-max max-w-[400px] rounded-md"
>
{$query.error}
</span>
</span>
{:else}
<QueryLoad data={query} let:loaded>
<svelte:fragment slot="skeleton">
<Skeleton class="h-8 w-72" />
</svelte:fragment>

<DateInput
bind:selectedDateInput
start={startString}
end={endString}
loaded={loaded?.ready ?? true}
{presetRanges}
{defaultValue}
{range}
{currentDate}
/>
</QueryLoad>
{/if}
</div>
</HiddenInPrint>
Loading