Add very early work on date range picker
This commit is contained in:
parent
725eacec97
commit
27056990aa
|
@ -1,15 +1,183 @@
|
||||||
<script>
|
<script>
|
||||||
import FlatpickrDatePicker from "./FlatpickrDatePicker.svelte"
|
import "@spectrum-css/calendar/dist/index-vars.css"
|
||||||
import SpectrumDatePicker from "./SpectrumDatePicker.svelte"
|
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
||||||
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
|
import Popover from "../../../Popover/Popover.svelte"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import { createEventDispatcher, onMount } from "svelte"
|
||||||
|
import TimePicker from "./TimePicker.svelte"
|
||||||
|
import Calendar from "./Calendar.svelte"
|
||||||
|
import DateTimeInput from "./DateInput.svelte"
|
||||||
|
import ActionButton from "../../../ActionButton/ActionButton.svelte"
|
||||||
|
import { parseDate } from "../../../helpers"
|
||||||
|
|
||||||
export let api
|
export let id = null
|
||||||
|
export let disabled = false
|
||||||
|
export let error = null
|
||||||
|
export let enableTime = true
|
||||||
|
export let value = null
|
||||||
|
export let placeholder = null
|
||||||
|
export let timeOnly = false
|
||||||
|
export let ignoreTimezones = false
|
||||||
|
export let useKeyboardShortcuts = true
|
||||||
|
export let appendTo = null
|
||||||
|
export let api = null
|
||||||
|
export let align = "left"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let isOpen = false
|
||||||
|
let anchor
|
||||||
|
let popover
|
||||||
|
let calendar
|
||||||
|
|
||||||
|
$: parsedValue = parseDate(value, { timeOnly, dateOnly: !enableTime })
|
||||||
|
$: showCalendar = !timeOnly
|
||||||
|
$: showTime = enableTime || timeOnly
|
||||||
|
|
||||||
|
const clearDateOnBackspace = event => {
|
||||||
|
// Ignore if we're typing a value
|
||||||
|
if (document.activeElement?.tagName.toLowerCase() === "input") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
||||||
|
handleChange(null)
|
||||||
|
popover?.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOpen = () => {
|
||||||
|
isOpen = true
|
||||||
|
if (useKeyboardShortcuts) {
|
||||||
|
document.addEventListener("keyup", clearDateOnBackspace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
isOpen = false
|
||||||
|
if (useKeyboardShortcuts) {
|
||||||
|
document.removeEventListener("keyup", clearDateOnBackspace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = date => {
|
||||||
|
if (!date) {
|
||||||
|
dispatch("change", null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let newValue = date.toISOString()
|
||||||
|
|
||||||
|
// If time only set date component to 2000-01-01
|
||||||
|
if (timeOnly) {
|
||||||
|
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// For date-only fields, construct a manual timestamp string without a time
|
||||||
|
// or time zone
|
||||||
|
else if (!enableTime) {
|
||||||
|
const year = date.year()
|
||||||
|
const month = `${date.month() + 1}`.padStart(2, "0")
|
||||||
|
const day = `${date.date()}`.padStart(2, "0")
|
||||||
|
newValue = `${year}-${month}-${day}T00:00:00.000`
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
||||||
|
// time picked, without timezone
|
||||||
|
else if (enableTime && ignoreTimezones) {
|
||||||
|
const offset = new Date().getTimezoneOffset() * 60000
|
||||||
|
newValue = new Date(date.valueOf() - offset).toISOString().slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch("change", newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const setToNow = () => {
|
||||||
|
const now = dayjs()
|
||||||
|
calendar?.setDate(now)
|
||||||
|
handleChange(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
api = {
|
||||||
|
open: () => popover?.show(),
|
||||||
|
close: () => popover?.hide(),
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:component
|
<DateTimeInput
|
||||||
this={$$props.range ? FlatpickrDatePicker : SpectrumDatePicker}
|
bind:anchor
|
||||||
{...$$props}
|
{disabled}
|
||||||
on:change
|
{error}
|
||||||
|
{placeholder}
|
||||||
|
{id}
|
||||||
|
{enableTime}
|
||||||
|
{timeOnly}
|
||||||
|
focused={isOpen}
|
||||||
|
value={parsedValue}
|
||||||
|
on:click={popover?.show}
|
||||||
|
icon={timeOnly ? "Clock" : "Calendar"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
bind:this={popover}
|
||||||
on:open
|
on:open
|
||||||
on:close
|
on:close
|
||||||
bind:api
|
on:open={onOpen}
|
||||||
/>
|
on:close={onClose}
|
||||||
|
{anchor}
|
||||||
|
portalTarget={appendTo}
|
||||||
|
{align}
|
||||||
|
>
|
||||||
|
{#if isOpen}
|
||||||
|
<div class="date-time-popover">
|
||||||
|
{#if showCalendar}
|
||||||
|
<Calendar
|
||||||
|
value={parsedValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
bind:this={calendar}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<div class="footer" class:spaced={showCalendar}>
|
||||||
|
{#if showTime}
|
||||||
|
<TimePicker value={parsedValue} onChange={handleChange} />
|
||||||
|
{/if}
|
||||||
|
<div class="actions">
|
||||||
|
<ActionButton
|
||||||
|
disabled={!value}
|
||||||
|
size="S"
|
||||||
|
on:click={() => handleChange(null)}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton size="S" on:click={setToNow}>
|
||||||
|
{showTime ? "Now" : "Today"}
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.date-time-popover {
|
||||||
|
padding: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 60px;
|
||||||
|
}
|
||||||
|
.footer.spaced {
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
padding: 4px 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,268 +0,0 @@
|
||||||
<script>
|
|
||||||
import Flatpickr from "svelte-flatpickr"
|
|
||||||
import "flatpickr/dist/flatpickr.css"
|
|
||||||
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
|
||||||
import "@spectrum-css/picker/dist/index-vars.css"
|
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import { uuid } from "../../../helpers"
|
|
||||||
|
|
||||||
export let id = null
|
|
||||||
export let disabled = false
|
|
||||||
export let readonly = false
|
|
||||||
export let enableTime = true
|
|
||||||
export let value = null
|
|
||||||
export let placeholder = null
|
|
||||||
export let appendTo = undefined
|
|
||||||
export let timeOnly = false
|
|
||||||
export let ignoreTimezones = false
|
|
||||||
export let time24hr = false
|
|
||||||
export let range = false
|
|
||||||
export let flatpickr
|
|
||||||
export let useKeyboardShortcuts = true
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
const flatpickrId = `${uuid()}-wrapper`
|
|
||||||
|
|
||||||
let open = false
|
|
||||||
let flatpickrOptions
|
|
||||||
|
|
||||||
// Another classic flatpickr issue. Errors were randomly being thrown due to
|
|
||||||
// flatpickr internal code. Making sure that "destroy" is a valid function
|
|
||||||
// fixes it. The sooner we remove flatpickr the better.
|
|
||||||
$: {
|
|
||||||
if (flatpickr && !flatpickr.destroy) {
|
|
||||||
flatpickr.destroy = () => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveTimeStamp = timestamp => {
|
|
||||||
let maskedDate = new Date(`0-${timestamp}`)
|
|
||||||
|
|
||||||
if (maskedDate instanceof Date && !isNaN(maskedDate.getTime())) {
|
|
||||||
return maskedDate
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: flatpickrOptions = {
|
|
||||||
element: `#${flatpickrId}`,
|
|
||||||
enableTime: timeOnly || enableTime || false,
|
|
||||||
noCalendar: timeOnly || false,
|
|
||||||
altInput: true,
|
|
||||||
time_24hr: time24hr || false,
|
|
||||||
altFormat: timeOnly ? "H:i" : enableTime ? "F j Y, H:i" : "F j, Y",
|
|
||||||
wrap: true,
|
|
||||||
mode: range ? "range" : "single",
|
|
||||||
appendTo,
|
|
||||||
disableMobile: "true",
|
|
||||||
onReady: () => {
|
|
||||||
let timestamp = resolveTimeStamp(value)
|
|
||||||
if (timeOnly && timestamp) {
|
|
||||||
dispatch("change", timestamp.toISOString())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onOpen: () => dispatch("open"),
|
|
||||||
onClose: () => dispatch("close"),
|
|
||||||
}
|
|
||||||
|
|
||||||
$: redrawOptions = {
|
|
||||||
timeOnly,
|
|
||||||
enableTime,
|
|
||||||
time24hr,
|
|
||||||
disabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = event => {
|
|
||||||
const [dates] = event.detail
|
|
||||||
const noTimezone = enableTime && !timeOnly && ignoreTimezones
|
|
||||||
let newValue = dates[0]
|
|
||||||
if (newValue) {
|
|
||||||
newValue = newValue.toISOString()
|
|
||||||
}
|
|
||||||
// If time only set date component to 2000-01-01
|
|
||||||
if (timeOnly) {
|
|
||||||
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For date-only fields, construct a manual timestamp string without a time
|
|
||||||
// or time zone
|
|
||||||
else if (!enableTime) {
|
|
||||||
const year = dates[0].getFullYear()
|
|
||||||
const month = `${dates[0].getMonth() + 1}`.padStart(2, "0")
|
|
||||||
const day = `${dates[0].getDate()}`.padStart(2, "0")
|
|
||||||
newValue = `${year}-${month}-${day}T00:00:00.000`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
|
||||||
// time picked, without timezone
|
|
||||||
else if (noTimezone) {
|
|
||||||
const offset = dates[0].getTimezoneOffset() * 60000
|
|
||||||
newValue = new Date(dates[0].getTime() - offset)
|
|
||||||
.toISOString()
|
|
||||||
.slice(0, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (range) {
|
|
||||||
dispatch("change", event.detail)
|
|
||||||
} else {
|
|
||||||
dispatch("change", newValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearDateOnBackspace = event => {
|
|
||||||
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
|
||||||
dispatch("change", "")
|
|
||||||
flatpickr.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onOpen = () => {
|
|
||||||
open = true
|
|
||||||
if (useKeyboardShortcuts) {
|
|
||||||
document.addEventListener("keyup", clearDateOnBackspace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
open = false
|
|
||||||
if (useKeyboardShortcuts) {
|
|
||||||
document.removeEventListener("keyup", clearDateOnBackspace)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually blur all input fields since flatpickr creates a second
|
|
||||||
// duplicate input field.
|
|
||||||
// We need to blur both because the focus styling does not get properly
|
|
||||||
// applied.
|
|
||||||
const els = document.querySelectorAll(`#${flatpickrId} input`)
|
|
||||||
els.forEach(el => el.blur())
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseDate = val => {
|
|
||||||
if (!val) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let date
|
|
||||||
let time
|
|
||||||
|
|
||||||
// it is a string like 00:00:00, just time
|
|
||||||
let ts = resolveTimeStamp(val)
|
|
||||||
|
|
||||||
if (timeOnly && ts) {
|
|
||||||
date = ts
|
|
||||||
} else if (val instanceof Date) {
|
|
||||||
// Use real date obj if already parsed
|
|
||||||
date = val
|
|
||||||
} else if (isNaN(val)) {
|
|
||||||
// Treat as date string of some sort
|
|
||||||
date = new Date(val)
|
|
||||||
} else {
|
|
||||||
// Treat as numerical timestamp
|
|
||||||
date = new Date(parseInt(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
time = date.getTime()
|
|
||||||
if (isNaN(time)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// By rounding to the nearest second we avoid locking up in an endless
|
|
||||||
// loop in the builder, caused by potentially enriching {{ now }} to every
|
|
||||||
// millisecond.
|
|
||||||
return new Date(Math.floor(time / 1000) * 1000)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#key redrawOptions}
|
|
||||||
<Flatpickr
|
|
||||||
bind:flatpickr
|
|
||||||
value={range ? value : parseDate(value)}
|
|
||||||
on:open={onOpen}
|
|
||||||
on:close={onClose}
|
|
||||||
options={flatpickrOptions}
|
|
||||||
on:change={handleChange}
|
|
||||||
element={`#${flatpickrId}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
id={flatpickrId}
|
|
||||||
class:is-disabled={disabled || readonly}
|
|
||||||
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
|
||||||
class:is-focused={open}
|
|
||||||
aria-readonly="false"
|
|
||||||
aria-required="false"
|
|
||||||
aria-haspopup="true"
|
|
||||||
>
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div
|
|
||||||
on:click={flatpickr?.open}
|
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
{disabled}
|
|
||||||
{readonly}
|
|
||||||
data-input
|
|
||||||
type="text"
|
|
||||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
{placeholder}
|
|
||||||
{id}
|
|
||||||
{value}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
|
||||||
tabindex="-1"
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
on:click={flatpickr?.open}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Calendar"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Calendar" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Flatpickr>
|
|
||||||
{/key}
|
|
||||||
{#if open}
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
||||||
<div class="overlay" on:mousedown|self={flatpickr?.close} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.spectrum-Textfield-input {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.spectrum-Textfield:not(.is-disabled):hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.flatpickr {
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.flatpickr .spectrum-Textfield {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 999;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
:global(.flatpickr-calendar) {
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
}
|
|
||||||
.is-disabled {
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,183 +0,0 @@
|
||||||
<script>
|
|
||||||
import "@spectrum-css/calendar/dist/index-vars.css"
|
|
||||||
import "@spectrum-css/inputgroup/dist/index-vars.css"
|
|
||||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
|
||||||
import Popover from "../../../Popover/Popover.svelte"
|
|
||||||
import dayjs from "dayjs"
|
|
||||||
import { createEventDispatcher, onMount } from "svelte"
|
|
||||||
import TimePicker from "./TimePicker.svelte"
|
|
||||||
import Calendar from "./Calendar.svelte"
|
|
||||||
import DateTimeInput from "./DateInput.svelte"
|
|
||||||
import ActionButton from "../../../ActionButton/ActionButton.svelte"
|
|
||||||
import { parseDate } from "../../../helpers"
|
|
||||||
|
|
||||||
export let id = null
|
|
||||||
export let disabled = false
|
|
||||||
export let error = null
|
|
||||||
export let enableTime = true
|
|
||||||
export let value = null
|
|
||||||
export let placeholder = null
|
|
||||||
export let timeOnly = false
|
|
||||||
export let ignoreTimezones = false
|
|
||||||
export let useKeyboardShortcuts = true
|
|
||||||
export let appendTo = null
|
|
||||||
export let api = null
|
|
||||||
export let align = "left"
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
|
|
||||||
let isOpen = false
|
|
||||||
let anchor
|
|
||||||
let popover
|
|
||||||
let calendar
|
|
||||||
|
|
||||||
$: parsedValue = parseDate(value)
|
|
||||||
$: showCalendar = !timeOnly
|
|
||||||
$: showTime = enableTime || timeOnly
|
|
||||||
|
|
||||||
const clearDateOnBackspace = event => {
|
|
||||||
// Ignore if we're typing a value
|
|
||||||
if (document.activeElement?.tagName.toLowerCase() === "input") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
|
|
||||||
handleChange(null)
|
|
||||||
popover?.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onOpen = () => {
|
|
||||||
isOpen = true
|
|
||||||
if (useKeyboardShortcuts) {
|
|
||||||
document.addEventListener("keyup", clearDateOnBackspace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = () => {
|
|
||||||
isOpen = false
|
|
||||||
if (useKeyboardShortcuts) {
|
|
||||||
document.removeEventListener("keyup", clearDateOnBackspace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = date => {
|
|
||||||
if (!date) {
|
|
||||||
dispatch("change", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let newValue = date.toISOString()
|
|
||||||
|
|
||||||
// If time only set date component to 2000-01-01
|
|
||||||
if (timeOnly) {
|
|
||||||
newValue = `2000-01-01T${newValue.split("T")[1]}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For date-only fields, construct a manual timestamp string without a time
|
|
||||||
// or time zone
|
|
||||||
else if (!enableTime) {
|
|
||||||
const year = date.year()
|
|
||||||
const month = `${date.month() + 1}`.padStart(2, "0")
|
|
||||||
const day = `${date.date()}`.padStart(2, "0")
|
|
||||||
newValue = `${year}-${month}-${day}T00:00:00.000`
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-timezone-aware fields, create an ISO 8601 timestamp of the exact
|
|
||||||
// time picked, without timezone
|
|
||||||
else if (enableTime && ignoreTimezones) {
|
|
||||||
const offset = new Date().getTimezoneOffset() * 60000
|
|
||||||
newValue = new Date(date.valueOf() - offset).toISOString().slice(0, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch("change", newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setToNow = () => {
|
|
||||||
const now = dayjs()
|
|
||||||
calendar?.setDate(now)
|
|
||||||
handleChange(now)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
api = {
|
|
||||||
open: () => popover?.show(),
|
|
||||||
close: () => popover?.hide(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DateTimeInput
|
|
||||||
bind:anchor
|
|
||||||
{disabled}
|
|
||||||
{error}
|
|
||||||
{placeholder}
|
|
||||||
{id}
|
|
||||||
{enableTime}
|
|
||||||
{timeOnly}
|
|
||||||
focused={isOpen}
|
|
||||||
value={parsedValue}
|
|
||||||
on:click={popover?.show}
|
|
||||||
icon={timeOnly ? "Clock" : "Calendar"}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Popover
|
|
||||||
bind:this={popover}
|
|
||||||
on:open
|
|
||||||
on:close
|
|
||||||
on:open={onOpen}
|
|
||||||
on:close={onClose}
|
|
||||||
{anchor}
|
|
||||||
portalTarget={appendTo}
|
|
||||||
{align}
|
|
||||||
>
|
|
||||||
{#if isOpen}
|
|
||||||
<div class="date-time-popover">
|
|
||||||
{#if showCalendar}
|
|
||||||
<Calendar
|
|
||||||
value={parsedValue}
|
|
||||||
onChange={handleChange}
|
|
||||||
bind:this={calendar}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<div class="footer" class:spaced={showCalendar}>
|
|
||||||
{#if showTime}
|
|
||||||
<TimePicker value={parsedValue} onChange={handleChange} />
|
|
||||||
{/if}
|
|
||||||
<div class="actions">
|
|
||||||
<ActionButton
|
|
||||||
disabled={!value}
|
|
||||||
size="S"
|
|
||||||
on:click={() => handleChange(null)}
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</ActionButton>
|
|
||||||
<ActionButton size="S" on:click={setToNow}>
|
|
||||||
{showTime ? "Now" : "Today"}
|
|
||||||
</ActionButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Popover>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.date-time-popover {
|
|
||||||
padding: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 60px;
|
|
||||||
}
|
|
||||||
.footer.spaced {
|
|
||||||
padding-top: 14px;
|
|
||||||
}
|
|
||||||
.actions {
|
|
||||||
padding: 4px 0;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<script>
|
||||||
|
import CoreDatePicker from "./DatePicker/DatePicker.svelte"
|
||||||
|
import Icon from "../../Icon/Icon.svelte"
|
||||||
|
|
||||||
|
export let value = null
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let error = null
|
||||||
|
export let appendTo = undefined
|
||||||
|
export let ignoreTimezones = false
|
||||||
|
|
||||||
|
let fromDate
|
||||||
|
let toDate
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="date-range">
|
||||||
|
<CoreDatePicker
|
||||||
|
value={fromDate}
|
||||||
|
on:change={e => (fromDate = e.detail)}
|
||||||
|
enableTime={false}
|
||||||
|
/>
|
||||||
|
<div class="arrow">
|
||||||
|
<Icon name="ChevronRight" />
|
||||||
|
</div>
|
||||||
|
<CoreDatePicker
|
||||||
|
value={toDate}
|
||||||
|
on:change={e => (toDate = e.detail)}
|
||||||
|
enableTime={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.date-range {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
border: 1px solid var(--spectrum-alias-border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.date-range :global(.spectrum-InputGroup),
|
||||||
|
.date-range :global(.spectrum-Textfield),
|
||||||
|
.date-range :global(input) {
|
||||||
|
min-width: 0 !important;
|
||||||
|
width: 150px !important;
|
||||||
|
}
|
||||||
|
.date-range :global(input) {
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.date-range :global(button) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.date-range :global(> :first-child input),
|
||||||
|
.date-range :global(> :first-child) {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
.date-range :global(> :last-child input),
|
||||||
|
.date-range :global(> :last-child) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
.arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,6 +9,7 @@ export { default as CoreCombobox } from "./Combobox.svelte"
|
||||||
export { default as CoreSwitch } from "./Switch.svelte"
|
export { default as CoreSwitch } from "./Switch.svelte"
|
||||||
export { default as CoreSearch } from "./Search.svelte"
|
export { default as CoreSearch } from "./Search.svelte"
|
||||||
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
|
export { default as CoreDatePicker } from "./DatePicker/DatePicker.svelte"
|
||||||
|
export { default as CoreDateRangePicker } from "./DateRangePicker.svelte"
|
||||||
export { default as CoreDropzone } from "./Dropzone.svelte"
|
export { default as CoreDropzone } from "./Dropzone.svelte"
|
||||||
export { default as CoreStepper } from "./Stepper.svelte"
|
export { default as CoreStepper } from "./Stepper.svelte"
|
||||||
export { default as CoreRichTextField } from "./RichTextField.svelte"
|
export { default as CoreRichTextField } from "./RichTextField.svelte"
|
||||||
|
|
|
@ -15,18 +15,12 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let appendTo = undefined
|
export let appendTo = undefined
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
export let range = false
|
|
||||||
export let helpText = null
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
if (range) {
|
value = e.detail
|
||||||
// Flatpickr cant take two dates and work out what to display, needs to be provided a string.
|
|
||||||
// Like - "Date1 to Date2". Hence passing in that specifically from the array
|
|
||||||
value = e?.detail[1]
|
|
||||||
} else {
|
|
||||||
value = e.detail
|
|
||||||
}
|
|
||||||
dispatch("change", e.detail)
|
dispatch("change", e.detail)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -43,7 +37,6 @@
|
||||||
{time24hr}
|
{time24hr}
|
||||||
{appendTo}
|
{appendTo}
|
||||||
{ignoreTimezones}
|
{ignoreTimezones}
|
||||||
{range}
|
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script>
|
||||||
|
import Field from "./Field.svelte"
|
||||||
|
import DateRangePicker from "./Core/DateRangePicker.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value = null
|
||||||
|
export let label = null
|
||||||
|
export let labelPosition = "above"
|
||||||
|
export let disabled = false
|
||||||
|
export let readonly = false
|
||||||
|
export let error = null
|
||||||
|
export let helpText = null
|
||||||
|
export let appendTo = undefined
|
||||||
|
export let ignoreTimezones = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const onChange = e => {
|
||||||
|
value = e.detail
|
||||||
|
dispatch("change", e.detail)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
|
<DateRangePicker
|
||||||
|
{error}
|
||||||
|
{disabled}
|
||||||
|
{readonly}
|
||||||
|
{value}
|
||||||
|
{appendTo}
|
||||||
|
{ignoreTimezones}
|
||||||
|
on:change={onChange}
|
||||||
|
/>
|
||||||
|
</Field>
|
|
@ -3,13 +3,34 @@ import "./bbui.css"
|
||||||
// Spectrum icons
|
// Spectrum icons
|
||||||
import "@spectrum-css/icon/dist/index-vars.css"
|
import "@spectrum-css/icon/dist/index-vars.css"
|
||||||
|
|
||||||
// Components
|
// Form components
|
||||||
export { default as Input } from "./Form/Input.svelte"
|
export { default as Input } from "./Form/Input.svelte"
|
||||||
export { default as Stepper } from "./Form/Stepper.svelte"
|
export { default as Stepper } from "./Form/Stepper.svelte"
|
||||||
export { default as TextArea } from "./Form/TextArea.svelte"
|
export { default as TextArea } from "./Form/TextArea.svelte"
|
||||||
export { default as Select } from "./Form/Select.svelte"
|
export { default as Select } from "./Form/Select.svelte"
|
||||||
export { default as Combobox } from "./Form/Combobox.svelte"
|
export { default as Combobox } from "./Form/Combobox.svelte"
|
||||||
export { default as Dropzone } from "./Form/Dropzone.svelte"
|
export { default as Dropzone } from "./Form/Dropzone.svelte"
|
||||||
|
export { default as DatePicker } from "./Form/DatePicker.svelte"
|
||||||
|
export { default as DateRangePicker } from "./Form/DateRangePicker.svelte"
|
||||||
|
export { default as Toggle } from "./Form/Toggle.svelte"
|
||||||
|
export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
||||||
|
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
||||||
|
export { default as InputDropdown } from "./Form/InputDropdown.svelte"
|
||||||
|
export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
|
||||||
|
export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
|
||||||
|
export { default as Multiselect } from "./Form/Multiselect.svelte"
|
||||||
|
export { default as Search } from "./Form/Search.svelte"
|
||||||
|
export { default as RichTextField } from "./Form/RichTextField.svelte"
|
||||||
|
export { default as Slider } from "./Form/Slider.svelte"
|
||||||
|
export { default as File } from "./Form/File.svelte"
|
||||||
|
|
||||||
|
// Core form components to be used elsewhere (standard components)
|
||||||
|
export * from "./Form/Core"
|
||||||
|
|
||||||
|
// Fancy form components
|
||||||
|
export * from "./FancyForm"
|
||||||
|
|
||||||
|
// Components
|
||||||
export { default as Drawer } from "./Drawer/Drawer.svelte"
|
export { default as Drawer } from "./Drawer/Drawer.svelte"
|
||||||
export { default as DrawerContent } from "./Drawer/DrawerContent.svelte"
|
export { default as DrawerContent } from "./Drawer/DrawerContent.svelte"
|
||||||
export { default as Avatar } from "./Avatar/Avatar.svelte"
|
export { default as Avatar } from "./Avatar/Avatar.svelte"
|
||||||
|
@ -21,12 +42,6 @@ export { default as ButtonGroup } from "./ButtonGroup/ButtonGroup.svelte"
|
||||||
export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
|
export { default as ClearButton } from "./ClearButton/ClearButton.svelte"
|
||||||
export { default as Icon } from "./Icon/Icon.svelte"
|
export { default as Icon } from "./Icon/Icon.svelte"
|
||||||
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
export { default as IconAvatar } from "./Icon/IconAvatar.svelte"
|
||||||
export { default as Toggle } from "./Form/Toggle.svelte"
|
|
||||||
export { default as RadioGroup } from "./Form/RadioGroup.svelte"
|
|
||||||
export { default as Checkbox } from "./Form/Checkbox.svelte"
|
|
||||||
export { default as InputDropdown } from "./Form/InputDropdown.svelte"
|
|
||||||
export { default as PickerDropdown } from "./Form/PickerDropdown.svelte"
|
|
||||||
export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
|
|
||||||
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
export { default as DetailSummary } from "./DetailSummary/DetailSummary.svelte"
|
||||||
export { default as Popover } from "./Popover/Popover.svelte"
|
export { default as Popover } from "./Popover/Popover.svelte"
|
||||||
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte"
|
||||||
|
@ -37,11 +52,6 @@ export { default as Page } from "./Layout/Page.svelte"
|
||||||
export { default as Link } from "./Link/Link.svelte"
|
export { default as Link } from "./Link/Link.svelte"
|
||||||
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
|
export { default as Tooltip } from "./Tooltip/Tooltip.svelte"
|
||||||
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
|
export { default as TempTooltip } from "./Tooltip/TempTooltip.svelte"
|
||||||
export {
|
|
||||||
default as AbsTooltip,
|
|
||||||
TooltipPosition,
|
|
||||||
TooltipType,
|
|
||||||
} from "./Tooltip/AbsTooltip.svelte"
|
|
||||||
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
export { default as TooltipWrapper } from "./Tooltip/TooltipWrapper.svelte"
|
||||||
export { default as Menu } from "./Menu/Menu.svelte"
|
export { default as Menu } from "./Menu/Menu.svelte"
|
||||||
export { default as MenuSection } from "./Menu/Section.svelte"
|
export { default as MenuSection } from "./Menu/Section.svelte"
|
||||||
|
@ -53,9 +63,6 @@ export { default as NotificationDisplay } from "./Notification/NotificationDispl
|
||||||
export { default as Notification } from "./Notification/Notification.svelte"
|
export { default as Notification } from "./Notification/Notification.svelte"
|
||||||
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
export { default as SideNavigation } from "./SideNavigation/Navigation.svelte"
|
||||||
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
|
export { default as SideNavigationItem } from "./SideNavigation/Item.svelte"
|
||||||
export { default as DatePicker } from "./Form/DatePicker.svelte"
|
|
||||||
export { default as DateTimePicker } from "./Form/Core/DatePicker/SpectrumDatePicker.svelte"
|
|
||||||
export { default as Multiselect } from "./Form/Multiselect.svelte"
|
|
||||||
export { default as Context } from "./context"
|
export { default as Context } from "./context"
|
||||||
export { default as Table } from "./Table/Table.svelte"
|
export { default as Table } from "./Table/Table.svelte"
|
||||||
export { default as Tabs } from "./Tabs/Tabs.svelte"
|
export { default as Tabs } from "./Tabs/Tabs.svelte"
|
||||||
|
@ -65,7 +72,6 @@ export { default as Tag } from "./Tags/Tag.svelte"
|
||||||
export { default as TreeView } from "./TreeView/Tree.svelte"
|
export { default as TreeView } from "./TreeView/Tree.svelte"
|
||||||
export { default as TreeItem } from "./TreeView/Item.svelte"
|
export { default as TreeItem } from "./TreeView/Item.svelte"
|
||||||
export { default as Divider } from "./Divider/Divider.svelte"
|
export { default as Divider } from "./Divider/Divider.svelte"
|
||||||
export { default as Search } from "./Form/Search.svelte"
|
|
||||||
export { default as Pagination } from "./Pagination/Pagination.svelte"
|
export { default as Pagination } from "./Pagination/Pagination.svelte"
|
||||||
export { default as Badge } from "./Badge/Badge.svelte"
|
export { default as Badge } from "./Badge/Badge.svelte"
|
||||||
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
|
export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
|
||||||
|
@ -77,15 +83,15 @@ export { default as CopyInput } from "./Input/CopyInput.svelte"
|
||||||
export { default as BannerDisplay } from "./Banner/BannerDisplay.svelte"
|
export { default as BannerDisplay } from "./Banner/BannerDisplay.svelte"
|
||||||
export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
|
export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
|
||||||
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
|
export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
|
||||||
export { default as RichTextField } from "./Form/RichTextField.svelte"
|
|
||||||
export { default as List } from "./List/List.svelte"
|
export { default as List } from "./List/List.svelte"
|
||||||
export { default as ListItem } from "./List/ListItem.svelte"
|
export { default as ListItem } from "./List/ListItem.svelte"
|
||||||
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte"
|
||||||
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte"
|
||||||
export { default as Slider } from "./Form/Slider.svelte"
|
|
||||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||||
export { default as File } from "./Form/File.svelte"
|
|
||||||
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
|
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
|
||||||
|
export { default as AbsTooltip } from "./Tooltip/AbsTooltip.svelte"
|
||||||
|
export { TooltipPosition, TooltipType } from "./Tooltip/AbsTooltip.svelte"
|
||||||
|
|
||||||
// Renderers
|
// Renderers
|
||||||
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||||
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
||||||
|
@ -97,9 +103,6 @@ export { default as Heading } from "./Typography/Heading.svelte"
|
||||||
export { default as Detail } from "./Typography/Detail.svelte"
|
export { default as Detail } from "./Typography/Detail.svelte"
|
||||||
export { default as Code } from "./Typography/Code.svelte"
|
export { default as Code } from "./Typography/Code.svelte"
|
||||||
|
|
||||||
// Core form components to be used elsewhere (standard components)
|
|
||||||
export * from "./Form/Core"
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
export { default as autoResizeTextArea } from "./Actions/autoresize_textarea"
|
export { default as autoResizeTextArea } from "./Actions/autoresize_textarea"
|
||||||
export { default as positionDropdown } from "./Actions/position_dropdown"
|
export { default as positionDropdown } from "./Actions/position_dropdown"
|
||||||
|
@ -111,6 +114,3 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
export * as Helpers from "./helpers"
|
export * as Helpers from "./helpers"
|
||||||
|
|
||||||
// Fancy form components
|
|
||||||
export * from "./FancyForm"
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DatePicker,
|
DateRangePicker,
|
||||||
Divider,
|
Divider,
|
||||||
Layout,
|
Layout,
|
||||||
notifications,
|
notifications,
|
||||||
|
@ -236,14 +236,12 @@
|
||||||
bind:value={filterOpt}
|
bind:value={filterOpt}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DateRangePicker
|
||||||
range={true}
|
value={[startDate, endDate]}
|
||||||
label="Date Range"
|
label="Date Range"
|
||||||
on:change={e => {
|
on:change={e => {
|
||||||
if (e.detail[0].length > 1) {
|
startDate = e.detail?.[0]
|
||||||
startDate = e.detail[0][0].toISOString()
|
endDate = e.detail?.[1]
|
||||||
endDate = e.detail[0][1].toISOString()
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue