diff --git a/.changeset/cold-trains-visit.md b/.changeset/cold-trains-visit.md new file mode 100644 index 0000000000..d8a6a9f41c --- /dev/null +++ b/.changeset/cold-trains-visit.md @@ -0,0 +1,5 @@ +--- +'@evidence-dev/core-components': patch +--- + +Feature: DateInput - supports single and ranged calendar date inputs diff --git a/package.json b/package.json index 856c0b2519..b3650776cb 100644 --- a/package.json +++ b/package.json @@ -113,4 +113,4 @@ } }, "packageManager": "pnpm@8.15.9+sha512.499434c9d8fdd1a2794ebf4552b3b25c0a633abcee5bb15e7b5de90f32f47b513aca98cd5cfd001c31f0db454bc3804edccd578501e4ca293a6816166bbd9f81" -} +} \ No newline at end of file diff --git a/packages/ui/core-components/src/lib/atoms/inputs/date-input/DateInput.stories.svelte b/packages/ui/core-components/src/lib/atoms/inputs/date-input/DateInput.stories.svelte new file mode 100644 index 0000000000..369651f511 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/inputs/date-input/DateInput.stories.svelte @@ -0,0 +1,46 @@ + + + + + + + +

+ Input Store: +

value: {$inputStore.dateInput.value}

+

start: {$inputStore.dateInput.start}

+

end: {$inputStore.dateInput.end}

+ + +
+ +

+ Input Store: +

value: {$inputStore.dateInput_range.value}

+

start: {$inputStore.dateInput_range.start}

+

end: {$inputStore.dateInput_range.end}

+ + +
diff --git a/packages/ui/core-components/src/lib/atoms/inputs/date-input/DateInput.svelte b/packages/ui/core-components/src/lib/atoms/inputs/date-input/DateInput.svelte new file mode 100644 index 0000000000..0543d6faf4 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/inputs/date-input/DateInput.svelte @@ -0,0 +1,128 @@ + + + + + +
+ {#if title && range} + {title} + {/if} + + {#if $query?.error} + + error + + + {:else} + + + + + + + {/if} +
+
diff --git a/packages/ui/core-components/src/lib/atoms/inputs/date-range/_DateRange.svelte b/packages/ui/core-components/src/lib/atoms/inputs/date-input/_DateInput.svelte similarity index 56% rename from packages/ui/core-components/src/lib/atoms/inputs/date-range/_DateRange.svelte rename to packages/ui/core-components/src/lib/atoms/inputs/date-input/_DateInput.svelte index 28ee4e4135..300e142af0 100644 --- a/packages/ui/core-components/src/lib/atoms/inputs/date-range/_DateRange.svelte +++ b/packages/ui/core-components/src/lib/atoms/inputs/date-input/_DateInput.svelte @@ -14,6 +14,9 @@ import * as Select from '$lib/atoms/shadcn/select/index.js'; import * as Popover from '$lib/atoms/shadcn/popover/index.js'; import { Separator } from '$lib/atoms/shadcn/separator/index.js'; + import { Calendar } from '$lib/atoms/shadcn/calendar/index.js'; + import { Icon } from '@steeze-ui/svelte-icon'; + import { CalendarEvent as CalendarIcon } from '@steeze-ui/tabler-icons'; function YYYYMMDDToCalendar(yyyymmdd) { const pieces = yyyymmdd.split('-'); @@ -29,7 +32,7 @@ }); /** @type {import('bits-ui').DateRange | undefined} */ - export let selectedDateRange = undefined; + export let selectedDateInput = undefined; /** @type {string} */ export let start; /** @type {string} */ @@ -39,6 +42,10 @@ export let presetRanges; /** @type {string] | undefined} */ export let defaultValue; + /** @type {boolean} */ + export let range = false; + /** @type {string} */ + export let title; /** @type { { label: string, group: string, range: import('bits-ui').DateRange }[] } */ $: presets = [ @@ -176,13 +183,17 @@ lowerCaseNoSpaceString(typeof v === 'string' ? v : v.label) ); if (!targetPreset) return; - selectedDateRange = targetPreset.range ?? targetPreset.value; selectedPreset = targetPreset; + if (range) { + selectedDateInput = targetPreset.range ?? targetPreset.value; + } else { + selectedDateInput = targetPreset.range; + } } $: if ( typeof defaultValue === 'string' && - !selectedDateRange && + !selectedDateInput && !selectedPreset && presets.length ) @@ -193,129 +204,159 @@ function updateDateRange(start, end) { if (selectedPreset) return; - selectedDateRange = { start, end }; - } + if (range) { + selectedDateInput = { start, end }; + } else { + selectedDateInput = start; + } + } $: updateDateRange(calendarStart, calendarEnd); -
+
- { - selectedPreset = undefined; - selectedDateRange = value; - }} - minValue={calendarStart} - maxValue={calendarEnd} - /> + {#if range} + { + selectedPreset = undefined; + selectedDateInput = value; + }} + minValue={calendarStart} + maxValue={calendarEnd} + /> + {:else} + { + selectedPreset = undefined; + selectedDateInput = value; + }} + minValue={calendarStart} + maxValue={calendarEnd} + /> + {/if} - - { - v.range = v.value; - applyPreset(v); - }} - bind:selected={selectedPreset} - disabled={!loaded} - > - - {#if selectedPreset} - {selectedPreset.label} + {#if range} + { + v.range = v.value; + applyPreset(v); + }} + bind:selected={selectedPreset} + disabled={!loaded} + > + + {#if selectedPreset} + {selectedPreset.label} + {:else} + + Range + {/if} + + {#if presets && presets.length === 0} + +

No Valid Presets

+
{:else} - - Range + + {#each presets.filter((d) => d.group === 'Days') as preset} + {preset.label} + {/each} + {#if groupExists('Months')} + + {/if} + {#each presets.filter((d) => d.group === 'Months') as preset} + {preset.label} + {/each} + {#if groupExists('Last')} + + {/if} + {#each presets.filter((d) => d.group === 'Last') as preset} + {preset.label} + {/each} + {#if groupExists('To Date')} + + {/if} + {#each presets.filter((d) => d.group === 'To Date') as preset} + {preset.label} + {/each} + {/if} -
- {#if presets && presets.length === 0} - -

No Valid Presets

-
- {:else} - - {#each presets.filter((d) => d.group === 'Days') as preset} - {preset.label} - {/each} - {#if groupExists('Months')} - - {/if} - {#each presets.filter((d) => d.group === 'Months') as preset} - {preset.label} - {/each} - {#if groupExists('Last')} - - {/if} - {#each presets.filter((d) => d.group === 'Last') as preset} - {preset.label} - {/each} - {#if groupExists('To Date')} - - {/if} - {#each presets.filter((d) => d.group === 'To Date') as preset} - {preset.label} - {/each} - - {/if} -
+ + {/if}
diff --git a/packages/ui/core-components/src/lib/atoms/inputs/date-input/helpers.js b/packages/ui/core-components/src/lib/atoms/inputs/date-input/helpers.js new file mode 100644 index 0000000000..302f3a74e1 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/inputs/date-input/helpers.js @@ -0,0 +1,17 @@ +const YYYYMMDD = /^\d{4}-\d{2}-\d{2}$/; + +function dateToYYYYMMDD(date) { + return date.toISOString().split('T')[0]; +} + +function formatDateString(date) { + if (typeof date === 'string' && YYYYMMDD.test(date)) { + return date; + } else if (date instanceof Date) { + return dateToYYYYMMDD(date); + } else { + return dateToYYYYMMDD(new Date()); + } +} + +export { dateToYYYYMMDD, formatDateString }; diff --git a/packages/ui/core-components/src/lib/atoms/inputs/date-input/index.js b/packages/ui/core-components/src/lib/atoms/inputs/date-input/index.js new file mode 100644 index 0000000000..966031e43a --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/inputs/date-input/index.js @@ -0,0 +1 @@ +export { default as DateInput } from './DateInput.svelte'; diff --git a/packages/ui/core-components/src/lib/atoms/inputs/date-range/DateRange.stories.svelte b/packages/ui/core-components/src/lib/atoms/inputs/date-range/DateRange.stories.svelte index d591d58f60..68df33b211 100644 --- a/packages/ui/core-components/src/lib/atoms/inputs/date-range/DateRange.stories.svelte +++ b/packages/ui/core-components/src/lib/atoms/inputs/date-range/DateRange.stories.svelte @@ -14,6 +14,8 @@ @@ -111,13 +96,14 @@ - {/if} diff --git a/packages/ui/core-components/src/lib/atoms/inputs/index.js b/packages/ui/core-components/src/lib/atoms/inputs/index.js index b3e72e168b..2abde54a92 100644 --- a/packages/ui/core-components/src/lib/atoms/inputs/index.js +++ b/packages/ui/core-components/src/lib/atoms/inputs/index.js @@ -4,3 +4,4 @@ export * from './button-group'; export * from './date-range'; export * from './checkbox'; export * from './slider'; +export * from './date-input'; diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-cell.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-cell.svelte new file mode 100644 index 0000000000..4403358207 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-cell.svelte @@ -0,0 +1,19 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-day.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-day.svelte new file mode 100644 index 0000000000..abeb09d3f1 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-day.svelte @@ -0,0 +1,40 @@ + + + + + {date.day} + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-body.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-body.svelte new file mode 100644 index 0000000000..1c8f671e1d --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-body.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-head.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-head.svelte new file mode 100644 index 0000000000..13714ca75d --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-head.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-row.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-row.svelte new file mode 100644 index 0000000000..defc4def5b --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid-row.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid.svelte new file mode 100644 index 0000000000..d2061c88e3 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-grid.svelte @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-head-cell.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-head-cell.svelte new file mode 100644 index 0000000000..6cec056385 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-head-cell.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-header.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-header.svelte new file mode 100644 index 0000000000..81084dc5e2 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-header.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-heading.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-heading.svelte new file mode 100644 index 0000000000..5f2dfc1a8c --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-heading.svelte @@ -0,0 +1,17 @@ + + + + + {headingValue} + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-months.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-months.svelte new file mode 100644 index 0000000000..840829065f --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-months.svelte @@ -0,0 +1,13 @@ + + +
+ +
diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-next-button.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-next-button.svelte new file mode 100644 index 0000000000..1c8f7c610f --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-next-button.svelte @@ -0,0 +1,25 @@ + + + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-prev-button.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-prev-button.svelte new file mode 100644 index 0000000000..455cc094cc --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar-prev-button.svelte @@ -0,0 +1,27 @@ + + + + + + + diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar.svelte new file mode 100644 index 0000000000..8f15743787 --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/calendar.svelte @@ -0,0 +1,177 @@ + + + +
+ + + { + if (!v || !placeholder) return; + if (v.value === placeholder?.month) return; + placeholder = placeholder.set({ month: v.value }); + }} + > + + + + + {#each monthOptions as { value, label }} + + {label} + + {/each} + + + { + if (!v || !placeholder) return; + if (v.value === placeholder?.year) return; + placeholder = placeholder.set({ year: v.value }); + }} + > + + + + + {#each yearOptions as { value, label }} + + {label} + + {/each} + + + + +
+ + {#each months as month} + + + + {#each weekdays as weekday} + + {weekday.slice(0, 2)} + + {/each} + + + + {#each month.weeks as weekDates} + + {#each weekDates as date} + + + + {/each} + + {/each} + + + {/each} + +
diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/calendar/index.js b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/index.js new file mode 100644 index 0000000000..5d60fa56eb --- /dev/null +++ b/packages/ui/core-components/src/lib/atoms/shadcn/calendar/index.js @@ -0,0 +1,30 @@ +import Root from './calendar.svelte'; +import Cell from './calendar-cell.svelte'; +import Day from './calendar-day.svelte'; +import Grid from './calendar-grid.svelte'; +import Header from './calendar-header.svelte'; +import Months from './calendar-months.svelte'; +import GridRow from './calendar-grid-row.svelte'; +import Heading from './calendar-heading.svelte'; +import GridBody from './calendar-grid-body.svelte'; +import GridHead from './calendar-grid-head.svelte'; +import HeadCell from './calendar-head-cell.svelte'; +import NextButton from './calendar-next-button.svelte'; +import PrevButton from './calendar-prev-button.svelte'; + +export { + Day, + Cell, + Grid, + Header, + Months, + GridRow, + Heading, + GridBody, + GridHead, + HeadCell, + NextButton, + PrevButton, + // + Root as Calendar +}; diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/range-calendar/range-calendar.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/range-calendar/range-calendar.svelte index 79a3e07bcf..3e96699df3 100644 --- a/packages/ui/core-components/src/lib/atoms/shadcn/range-calendar/range-calendar.svelte +++ b/packages/ui/core-components/src/lib/atoms/shadcn/range-calendar/range-calendar.svelte @@ -15,7 +15,7 @@ /** @type {DateValue | undefined} */ let placeholder = undefined; /** @type {{ start: DateValue; end: DateValue; } | undefined} */ - let selectedDateRange = undefined; + let selectedDateInput = undefined; /** @type {'short' | 'long' | undefined} */ let weekdayFormat = 'short'; /** @type {DateValue | undefined} */ @@ -66,7 +66,7 @@ placeholder, weekdayFormat, startValue, - selectedDateRange, + selectedDateInput, minValue, maxValue }; @@ -76,7 +76,7 @@ bind:value bind:placeholder bind:startValue - bind:selectedDateRange + bind:selectedDateInput {minValue} {maxValue} {weekdayFormat} diff --git a/packages/ui/core-components/src/lib/atoms/shadcn/select/select-trigger.svelte b/packages/ui/core-components/src/lib/atoms/shadcn/select/select-trigger.svelte index 134a69fa7c..cf4be36e53 100644 --- a/packages/ui/core-components/src/lib/atoms/shadcn/select/select-trigger.svelte +++ b/packages/ui/core-components/src/lib/atoms/shadcn/select/select-trigger.svelte @@ -6,7 +6,6 @@ /** @type {string | undefined | null} */ let className = undefined; - export { className as class }; diff --git a/sites/docs/components/DocTab.svelte b/sites/docs/components/DocTab.svelte index 9dce689588..9ee18d1800 100644 --- a/sites/docs/components/DocTab.svelte +++ b/sites/docs/components/DocTab.svelte @@ -5,11 +5,9 @@ let activeTab = 'preview'; const tabs = ['preview', 'code']; - let tabButtons = []; - function setTab(tab, index) { + function setTab(tab) { activeTab = tab; - updateActiveBorder(index); } const [send, receive] = crossfade({ @@ -21,15 +19,14 @@
- {#each tabs as tab, index} + {#each tabs as tab}
@@ -51,7 +48,9 @@ class={activeTab !== 'preview' ? 'h-[0px]' : 'mb-3 mt-2'} class:invisible={activeTab !== 'preview'} > - +
+ +
div:first-of-type) { margin: 0; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } diff --git a/sites/docs/pages/components/date-input/index.md b/sites/docs/pages/components/date-input/index.md new file mode 100644 index 0000000000..95a41d7cd8 --- /dev/null +++ b/sites/docs/pages/components/date-input/index.md @@ -0,0 +1,301 @@ +--- +title: Date Input +sidebar_position: 1 +description: A date input component allows the user to select a date or a range of dates. The selected dates can be used as inputs to queries or components. +queries: +- orders_by_day.sql +--- + +A date input component allows the user to select a date or a range of dates. The selected dates can be used as inputs to queries or components. + +To see how to filter a query using an input component, see [Filters](/core-concepts/filters). + +```sql filtered_query +select +* +from ${orders_by_day} +where day > '${inputs.date_filtering_a_query.value}' +``` + + +
+ + + +
+ +````markdown + +```sql filtered_query +select + * +from ${orders_by_day} +where day > '${inputs.range_filtering_a_query.value}' +``` + + + + +```` +
+ +## Examples + +### Using Date Input from a Query + +The Date selected can be accessed through the `inputs.[name].value` + + +
+ + + Date Selected: {inputs.date_input_from_query.value} +
+ +````markdown + + +Date Selected: {inputs.date_input_from_query.value} +```` +
+ +### With a Title + + +
+ +
+ +```markdown + +``` +
+ +## Date Range + +Creates a date picker for selecting a date range to filter queries, with selectable preset date options. + +### Filtering a Query with Range Calendar + +The Date selected can be accessed through the `inputs.[name].start` & `inputs.[name].end` + +```sql filtered_query_ranged +select + * +from ${orders_by_day} +where day between '${inputs.range_filtering_a_query.start}' and '${inputs.range_filtering_a_query.end}' +``` + + +
+ + + +
+ +````markdown + +```sql filtered_query_ranged +select + * +from ${orders_by_day} +where day between '${inputs.range_filtering_a_query.start}' - '${inputs.range_filtering_a_query.end}' +``` + + + + +```` +
+ +### Default Value for Preset Ranges + + +
+ +
+ +````svelte + +```` +
+ +### Customizing Single Preset Ranges + + +
+ +
+ +```svelte + +``` +
+ +### Customizing Multiple Preset Ranges + + +
+ +
+ +````svelte + +```` +
+ +### Manually Specifying a Range + + +
+ +
+ +```markdown + +``` +
+ +## Options + + + + + + + + + +Title to display in the Date Input component + + + + +Customize "Select a Range" drop down, by including present range options. **Range options**: `'Last 7 Days'` `'Last 30 Days'` `'Last 90 Days'` `'Last 3 Months'` `'Last 6 Months'` `'Last 12 Months'` `'Last Month'` `'Last Year'` `'Month to Date'` `'Year to Date'` `'All Time'` + + + + + +Accepts preset in string format to apply default value in Date Input picker. **Range options**: `'Last 7 Days'` `'Last 30 Days'` `'Last 90 Days'` `'Last 3 Months'` `'Last 6 Months'` `'Last 12 Months'` `'Last Month'` `'Last Year'` `'Month to Date'` `'Year to Date'` `'All Time'` + + + diff --git a/vercel.json b/vercel.json index d929a27d4c..94e729fd85 100644 --- a/vercel.json +++ b/vercel.json @@ -45,6 +45,11 @@ "source": "/guides/updating-your-project", "destination": "/guides/updating-your-app/", "permanent": true + }, + { + "source": "/components/date-range/", + "destination": "/components/date-input/", + "permanent": true } ] }