ui icon indicating copy to clipboard operation
ui copied to clipboard

[InputDate] Implement component

Open benjamincanac opened this issue 1 year ago • 8 comments

https://www.shadcn-vue.com/docs/components/date-picker.html

I think we can just use a Button + Popover + Calendar for this instead of implementing:

  • https://www.radix-vue.com/components/date-field.html
  • https://www.radix-vue.com/components/date-picker.html
  • https://www.radix-vue.com/components/date-range-field.html
  • https://www.radix-vue.com/components/date-range-picker.html

benjamincanac avatar Nov 04 '24 10:11 benjamincanac

Why are you not considering adding a DateField to the DatePicker?

mehaac avatar Nov 05 '24 00:11 mehaac

I'm a bit hesitant on this actually, are date pickers not mostly used with buttons instead of inputs? 🤔

benjamincanac avatar Nov 05 '24 09:11 benjamincanac

I'm a bit hesitant on this actually, are date pickers not mostly used with buttons instead of inputs? 🤔

If buttons do have the edge, it's not by much, in my experience. In companies related to booking trains and flights, it's about even - some allow you to fill in fields directly, others use only buttons.

aa.com britishairways.com

mehaac avatar Nov 06 '24 08:11 mehaac

I agree with @mehaac

My vision of the component: DateField with scope slot #default, by default it is UInput, whoever needs it will pass UButton to it.

<script setup lang="ts">
const date = ref(new Date())
</script>

<template>
  <!-- Default -->
  <UDatePicker v-model="date" />

  <!-- Replace UInput to UButton -->
  <UDatePicker>
    <template #default="{ open, date }">
      <UButton :label="date.toLocaleDateString()" @click="open = true" />
    </template>
  </UDatePicker>
</template>

hywax avatar Nov 06 '24 09:11 hywax

Renaming this InputDate for consistency with InputNumber.

benjamincanac avatar Nov 23 '24 18:11 benjamincanac

For ergonomics and speed on desktop, the keyboard input and navigation of the Reka UI Date Picker and Date Range Picker is way superior to just a calendar in a popup, IMO.

smt-joen avatar Jan 13 '25 15:01 smt-joen

In terms of accessibility, i think the inputs are also a must have 👍 Even if the calendar is keyboard enabled, its way easier to set the value through the inputs.

jonathan-lws avatar Feb 16 '25 07:02 jonathan-lws

Hi, Do we have any time estimate for this?

tiagomatosweb avatar Apr 18 '25 05:04 tiagomatosweb

Hi,

What is the update on this? The Calendar seems to be very limited in terms of output. There is no way to get a date with current time it seems. Also no option for a time picker at the moment.

nawazishali avatar May 27 '25 10:05 nawazishali

I'll be starting working on this soon to ship it in the next minor 😊

benjamincanac avatar May 27 '25 11:05 benjamincanac

Isn't this "done by example" with https://ui.nuxt.com/components/calendar#as-a-datepicker ?

<script setup lang="ts">
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'

const df = new DateFormatter('en-US', {
  dateStyle: 'medium'
})

const modelValue = shallowRef(new CalendarDate(2022, 1, 10))
</script>

<template>
  <UPopover>
    <UButton color="neutral" variant="subtle" icon="i-lucide-calendar">
      {{ modelValue ? df.format(modelValue.toDate(getLocalTimeZone())) : 'Select a date' }}
    </UButton>

    <template #content>
      <UCalendar v-model="modelValue" class="p-2" />
    </template>
  </UPopover>
</template>

homersimpsons avatar Jun 06 '25 16:06 homersimpsons

Hello! Do we have any updates on this?

delprestonx avatar Jul 14 '25 19:07 delprestonx

Here is a component I created that recreates the calendar with input field.

<template>
  <UPopover
    v-model:open="isOpen"
    :content="{ side: 'bottom', align: 'start', sideOffset: 8 }"
    mode="click">
    <UInput
      :model-value="formattedDate"
      :placeholder="placeholder"
      :icon="icon"
      readonly
      class="cursor-pointer"
      :ui="{ base: 'text-left' }" />

    <template #content>
      <div class="p-4">
        <UCalendar
          :model-value="calendarValue"
          :number-of-months="3"
          @update:model-value="handleDateChange" />
      </div>
    </template>
  </UPopover>
</template>

<script setup lang="ts">
  import { format } from 'date-fns'
  import { CalendarDate } from '@internationalized/date'
  import type { DateValue } from '@internationalized/date'

  /**
   * Component Props Interface
   */
  interface Props {
    placeholder?: string
    icon?: string
  }

  // Define props with destructuring and defaults
  const { placeholder = 'Select date', icon = 'ph:calendar-dots-duotone' } =
    defineProps<Props>()

  // Use defineModel for v-model - accepts Date objects or ISO strings
  const modelValue = defineModel<Date | string | null>({ default: null })

  // Popover open state
  const isOpen = ref(false)

  /**
   * Computed: Calendar Value
   * Converts JavaScript Date or ISO string to DateValue for UCalendar
   */
  const calendarValue = computed(() => {
    if (!modelValue.value) return undefined

    try {
      // Handle both Date objects and ISO strings
      const date = typeof modelValue.value === 'string' 
        ? new Date(modelValue.value) 
        : modelValue.value
        
      return new CalendarDate(
        date.getFullYear(),
        date.getMonth() + 1,
        date.getDate()
      )
    } catch (error) {
      return undefined
    }
  })

  /**
   * Computed: Formatted Date Display
   * Formats the selected date for display in the input
   */
  const formattedDate = computed(() => {
    if (!modelValue.value) return ''

    try {
      // Handle both Date objects and ISO strings
      const date = typeof modelValue.value === 'string' 
        ? new Date(modelValue.value) 
        : modelValue.value
        
      return format(date, 'MMM dd, yyyy')
    } catch (error) {
      return ''
    }
  })

  /**
   * Date Change Handler
   * Handles the calendar date selection and converts DateValue to Date
   */
  const handleDateChange = (newDate: any) => {
    // Handle only single date selection (DateValue)
    if (
      !newDate ||
      Array.isArray(newDate) ||
      (typeof newDate === 'object' && 'start' in newDate)
    ) {
      modelValue.value = null
      isOpen.value = false
      return
    }

    // Convert DateValue to Date object
    try {
      const dateValue = newDate as DateValue
      const jsDate = new Date(
        dateValue.year,
        dateValue.month - 1,
        dateValue.day
      )
      modelValue.value = jsDate
      isOpen.value = false // Close popover after selection
    } catch (error) {
      modelValue.value = null
      isOpen.value = false
    }
  }
</script>

Use like this

<InputCalendar v-model="date" icon="icon-name" />

andyjamesn avatar Jul 19 '25 20:07 andyjamesn