-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove v-calender replace with native browser input date field for ex…
…piryDates (#11377)
- Loading branch information
1 parent
193d77a
commit 6cb9068
Showing
31 changed files
with
469 additions
and
1,018 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
changelog/unreleased/enhancement-replace-custom-datepicker-with-native-element
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Enhancement: Replace custom datepicker with native html element | ||
|
||
We've replaced the custom datepicker with a native html date input element. | ||
This change will improve the user experience and accessibility of the datepicker. | ||
|
||
https://github.com/owncloud/web/pull/11377 | ||
https://github.com/owncloud/web/issues/11374 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 35 additions & 17 deletions
52
packages/design-system/src/components/OcDatepicker/OcDatepicker.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,41 @@ | ||
import { defineComponent } from 'vue' | ||
import Datepicker from './OcDatepicker.vue' | ||
import { mount } from 'web-test-helpers' | ||
import { ComponentProps, defaultPlugins, shallowMount } from 'web-test-helpers' | ||
import { DateTime } from 'luxon' | ||
import { nextTick } from 'vue' | ||
|
||
const DatePickerComponent = defineComponent({ | ||
template: '<div id="foo"><slot></slot></div>' | ||
describe('OcDatePicker', () => { | ||
it('renders', () => { | ||
const wrapper = getWrapper({ label: 'Datepicker label' }) | ||
expect(wrapper.html()).toMatchSnapshot() | ||
}) | ||
it('sets the initial date correctly', async () => { | ||
const wrapper = getWrapper({ label: 'Datepicker label', currentDate: DateTime.now() }) | ||
await nextTick() | ||
const inputEl = wrapper.find('.oc-text-input').element as HTMLInputElement | ||
expect(inputEl.value).toEqual(DateTime.now().toISODate()) | ||
}) | ||
it('sets the minimum date correctly', async () => { | ||
const wrapper = getWrapper({ label: 'Datepicker label', minDate: DateTime.now() }) | ||
await nextTick() | ||
const inputEl = wrapper.find('.oc-text-input') | ||
expect(inputEl.attributes('min')).toEqual(DateTime.now().toISODate()) | ||
}) | ||
it('emits event on date change', async () => { | ||
const wrapper = getWrapper({ label: 'Datepicker label' }) | ||
const inputEl = wrapper.find('.oc-text-input') | ||
await inputEl.setValue(DateTime.now().toISODate()) | ||
expect(wrapper.emitted('dateChanged')).toBeTruthy() | ||
}) | ||
}) | ||
|
||
describe('OcDatePicker', () => { | ||
it('renders default scoped slot', () => { | ||
const slotDefault = "<button id='default-slot'>Open datepicker</button>" | ||
const wrapper = mount(Datepicker, { | ||
slots: { default: slotDefault }, | ||
props: { value: null }, | ||
global: { | ||
renderStubDefaultSlot: true, | ||
stubs: { DatePicker: DatePickerComponent } | ||
function getWrapper(props: ComponentProps<typeof Datepicker>) { | ||
return shallowMount(Datepicker, { | ||
props, | ||
global: { | ||
plugins: [...defaultPlugins()], | ||
stubs: { | ||
OcTextInput: false | ||
} | ||
}) | ||
|
||
expect(wrapper.find('#default-slot').exists()).toBeTruthy() | ||
} | ||
}) | ||
}) | ||
} |
186 changes: 77 additions & 109 deletions
186
packages/design-system/src/components/OcDatepicker/OcDatepicker.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,145 +1,113 @@ | ||
<template> | ||
<date-picker ref="datePicker" class="oc-datepicker" v-bind="$attrs" :popover="popperOpts"> | ||
<template #default="args"> | ||
<!-- @slot Default slot to use as the popover anchor for datepicker --> | ||
<!-- args is undefined during initial render, hence we check it here --> | ||
<slot | ||
v-if="args" | ||
:input-value="args.inputValue" | ||
:toggle-popover="args.togglePopover" | ||
:hide-popover="args.hidePopover" | ||
/> | ||
</template> | ||
</date-picker> | ||
<oc-text-input | ||
v-model="dateInputString" | ||
v-bind="$attrs" | ||
:label="label" | ||
type="date" | ||
:min="minDate?.toISODate()" | ||
:fix-message-line="true" | ||
:error-message="errorMessage" | ||
:clear-button-enabled="isClearable" | ||
:clear-button-accessible-label="$gettext('Clear date')" | ||
class="oc-date-picker" | ||
/> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { | ||
computed, | ||
ComponentPublicInstance, | ||
defineComponent, | ||
defineAsyncComponent, | ||
ref, | ||
unref, | ||
watch | ||
} from 'vue' | ||
import { Modifier } from '@popperjs/core' | ||
import 'v-calendar/dist/style.css' | ||
import OcSpinner from '../OcSpinner/OcSpinner.vue' | ||
/** | ||
* Datepicker component based on [v-calendar](https://vcalendar.io/). For detailed documentation, please visit https://vcalendar.io/vue-3.html | ||
*/ | ||
import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue' | ||
import { useGettext } from 'vue3-gettext' | ||
import { DateTime } from 'luxon' | ||
export default defineComponent({ | ||
name: 'OcDatepicker', | ||
status: 'ready', | ||
release: '1.0.0', | ||
props: { | ||
label: { type: String, required: true }, | ||
isClearable: { type: Boolean, default: true }, | ||
currentDate: { type: Object as PropType<DateTime>, required: false, default: null }, | ||
minDate: { type: Object as PropType<DateTime>, required: false, default: null } | ||
}, | ||
emits: ['dateChanged'], | ||
setup(props, { emit }) { | ||
const { $gettext, current } = useGettext() | ||
const dateInputString = ref<string>('') | ||
components: { | ||
DatePicker: defineAsyncComponent({ | ||
loader: async () => { | ||
const { DatePicker } = await import('v-calendar') | ||
return DatePicker | ||
}, | ||
loadingComponent: OcSpinner | ||
const date = computed(() => { | ||
const date = DateTime.fromISO(unref(dateInputString)).endOf('day') | ||
return date.isValid ? date : null | ||
}) | ||
}, | ||
inheritAttrs: true, | ||
setup() { | ||
const datePicker = ref<ComponentPublicInstance>() | ||
const isMinDateUndercut = computed(() => { | ||
if (!props.minDate || !unref(date)) { | ||
return false | ||
} | ||
return unref(date) < props.minDate | ||
}) | ||
const popperOpts = computed(() => { | ||
return { | ||
modifiers: [ | ||
{ | ||
name: 'fixVerticalPosition', | ||
enabled: true, | ||
phase: 'beforeWrite', | ||
requiresIfExists: ['offset', 'flip'], | ||
fn({ state }) { | ||
const dropHeight = | ||
state.modifiersData.fullHeight || state.elements.popper.offsetHeight | ||
const rect = state.elements.popper.getBoundingClientRect() | ||
const neededScreenSpace = | ||
(state.elements.reference as HTMLElement).offsetHeight + rect.top + dropHeight | ||
const errorMessage = computed(() => { | ||
if (unref(isMinDateUndercut)) { | ||
return $gettext('The date must be after %{date}', { | ||
date: props.minDate | ||
.minus({ day: 1 }) | ||
.setLocale(current) | ||
.toLocaleString(DateTime.DATE_SHORT) | ||
}) | ||
} | ||
return '' | ||
}) | ||
if (state.placement !== 'top-start' && neededScreenSpace > window.innerHeight) { | ||
state.styles.popper.top = `-${150}px` | ||
} | ||
} | ||
} as Modifier<'fixVerticalPosition', unknown> | ||
] | ||
onMounted(() => { | ||
if (props.currentDate) { | ||
dateInputString.value = props.currentDate.toISODate() | ||
} | ||
}) | ||
watch( | ||
datePicker, | ||
date, | ||
() => { | ||
if (unref(datePicker)) { | ||
// for e2e tests | ||
unref(datePicker).$el.__datePicker = unref(datePicker) | ||
} | ||
emit('dateChanged', { date: unref(date), error: unref(isMinDateUndercut) }) | ||
}, | ||
{ immediate: true } | ||
{ | ||
deep: true | ||
} | ||
) | ||
return { popperOpts, datePicker } | ||
return { | ||
dateInputString, | ||
errorMessage | ||
} | ||
} | ||
}) | ||
</script> | ||
|
||
<style lang="scss"> | ||
.vc-pane-layout { | ||
color: var(--oc-color-text-default) !important; | ||
background-color: var(--oc-color-background-default) !important; | ||
} | ||
.vc-arrow svg path { | ||
fill: var(--oc-color-text-default) !important; | ||
} | ||
.vc-title { | ||
color: var(--oc-color-text-default) !important; | ||
} | ||
.vc-weekday { | ||
color: var(--oc-color-text-muted) !important; | ||
} | ||
.vc-day { | ||
color: var(--oc-color-text-default) !important; | ||
font-weight: var(--oc-font-weight-bold); | ||
} | ||
.vc-highlights { | ||
.vc-highlight { | ||
background-color: var(--oc-color-swatch-primary-default) !important; | ||
} | ||
+ span { | ||
color: var(--oc-color-text-inverse) !important; | ||
.oc-date-picker { | ||
input::-webkit-calendar-picker-indicator { | ||
cursor: pointer; | ||
} | ||
} | ||
.vc-day-content.is-disabled { | ||
color: var(--oc-color-text-muted) !important; | ||
background: none; | ||
cursor: not-allowed; | ||
font-weight: var(--oc-font-weight-extralight); | ||
} | ||
</style> | ||
<docs> | ||
```js | ||
<template> | ||
<div> | ||
<oc-datepicker v-model="date"> | ||
<template #default="{ togglePopover }"> | ||
<oc-button @click="togglePopover">Open datepicker</oc-button> | ||
</template> | ||
</oc-datepicker> | ||
<p v-if="date" v-text="date" /> | ||
</div> | ||
<div> | ||
<oc-datepicker :current-date="currentDate" :min-date="minDate" label="Enter or pick a date" | ||
@date-changed="onDateChanged"/> | ||
<p v-if="selectedDate" v-text="selectedDate"/> | ||
</div> | ||
</template> | ||
<script> | ||
export default { | ||
data: () => ({ date: null }) | ||
} | ||
import {DateTime} from "luxon"; | ||
|
||
export default { | ||
data: () => ({ | ||
minDate: DateTime.now(), currentDate: DateTime.now(), selectedDate: '' | ||
}), | ||
methods: { | ||
onDateChanged({date}) { | ||
this.selectedDate = date ? date.toLocaleString(DateTime.DATE_FULL) : '' | ||
} | ||
} | ||
} | ||
</script> | ||
``` | ||
</docs> |
14 changes: 14 additions & 0 deletions
14
packages/design-system/src/components/OcDatepicker/__snapshots__/OcDatepicker.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`OcDatePicker > renders 1`] = ` | ||
"<div class="oc-date-picker"><label class="oc-label" for="oc-textinput-1">Datepicker label</label> | ||
<div class="oc-position-relative"> | ||
<!--v-if--> <input id="oc-textinput-1" aria-invalid="false" class="oc-text-input oc-input oc-rounded" type="date"> | ||
<!--v-if--> | ||
</div> | ||
<div class="oc-text-input-message"> | ||
<!--v-if--> <span id="oc-textinput-1-message" class=""></span> | ||
</div> | ||
<!--v-if--> | ||
</div>" | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.