From 42f02634499a4d1853d848e92cc42b296390b5ec Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 16 Jul 2021 10:39:15 +0300 Subject: [PATCH 01/14] wip --- .../QDatePicker/src/QDatePicker.vue | 59 +++++++++-- .../QDatePicker/src/q-date-picker.scss | 98 +++++++++++++++++++ .../src/tables/DateTable/date-table.scss | 3 +- .../QDatePicker/src/tables/period-table.scss | 5 +- src/qComponents/QDatePicker/src/types.ts | 2 + src/qComponents/QDialog/src/QDialog.vue | 11 ++- 6 files changed, 166 insertions(+), 12 deletions(-) diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index d53cb707..d0d8890d 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -73,7 +73,23 @@ :to="teleportTo" :disabled="!teleportTo" > + + + (document.body.clientWidth); + + const isMobileView = computed( + () => dynamicClientWidth.value < 768 + ); + const calcFirstDayOfWeek = computed(() => { if (isNumber(props.firstDayOfWeek)) return props.firstDayOfWeek; return getConfig('locale') === 'ru' ? 1 : 0; @@ -356,11 +382,6 @@ export default defineComponent({ const isRanged = computed(() => props.type.includes('range')); - const iconClass = computed(() => { - if (isPickerDisabled.value) return 'q-icon-lock'; - return state.showCloseIcon ? 'q-icon-close' : 'q-icon-calendar'; - }); - const panelComponent = computed< | typeof DateRangePanel | typeof MonthRangePanel @@ -387,6 +408,15 @@ export default defineComponent({ return !transformedToDate.value; }); + const iconClass = computed(() => { + if (isPickerDisabled.value) return 'q-icon-lock'; + if (isMobileView.value) + return !isValueEmpty.value && props.clearable + ? 'q-icon-close' + : 'q-icon-calendar'; + return state.showCloseIcon ? 'q-icon-close' : 'q-icon-calendar'; + }); + const displayValue = computed>(() => { let formattedValue: string | number | Date | (string | number | Date)[] = ''; @@ -589,7 +619,6 @@ export default defineComponent({ const handleFocus = (): void => { if (isPickerDisabled.value) return; - state.pickerVisible = true; ctx.emit('focus'); if (!transformedToDate.value || Array.isArray(transformedToDate.value)) @@ -632,6 +661,10 @@ export default defineComponent({ ctx.emit('focus'); }; + const updateClientWidth = (): void => { + dynamicClientWidth.value = document.body.clientWidth; + }; + const handleInput = ({ target, inputType @@ -669,11 +702,20 @@ export default defineComponent({ } ); + onMounted(() => { + window.addEventListener('resize', updateClientWidth, true); + }); + + onUnmounted(() => { + window.removeEventListener('resize', updateClientWidth); + }); + onBeforeUnmount(() => destroyPopper()); provide('qDatePicker', { emit: ctx.emit, firstDayOfWeek: calcFirstDayOfWeek, + isMobileView, emitChange, handlePickClick, type: toRef(props, 'type'), @@ -686,6 +728,7 @@ export default defineComponent({ root, panel, reference, + isMobileView, isRanged, isPickerDisabled, calcFirstDayOfWeek, diff --git a/src/qComponents/QDatePicker/src/q-date-picker.scss b/src/qComponents/QDatePicker/src/q-date-picker.scss index d9850a18..3904e732 100644 --- a/src/qComponents/QDatePicker/src/q-date-picker.scss +++ b/src/qComponents/QDatePicker/src/q-date-picker.scss @@ -185,6 +185,7 @@ &-sign { display: flex; flex-basis: 72px; + flex-grow: 1; justify-content: center; margin-right: 12px; margin-left: 12px; @@ -245,6 +246,7 @@ min-width: 20px; height: 20px; padding: 0; + margin: 0 6px 0 0; font-size: 20px; color: var(--color-primary-blue); cursor: pointer; @@ -297,4 +299,100 @@ } } } + + .dialog-view { + .q-dialog__inner { + padding: 0; + } + + .q-dialog__close { + position: absolute; + top: 100px; + left: 50%; + transform: translateX(-50%); + } + + .q-dialog__content { + display: flex; + align-items: flex-end; + justify-content: center; + + .q-picker-panel { + max-width: 415px; + &__body { + width: 100%; + } + + &__content { + width: 100%; + } + + &__header-label { + font-size: 14px; + } + + &__icon-btn { + width: 30px; + height: 30px; + font-size: 29px; + } + + .q-date-table__days { + font-size: 12px; + } + } + + .q-date-table__cell-wrapper { + width: 30px; + height: 30px; + + .cell { + width: 30px; + height: 30px; + font-size: 14px; + } + } + + .q-period-table__cell { + width: 60px; + height: 60px; + font-size: 16px; + } + } + } +} + +@media (min-width: 375px) { + .q-date-picker { + .dialog-view { + .q-dialog__content { + .q-picker-panel { + &__header-label { + font-size: 16px; + } + + .q-date-table__days { + font-size: 14px; + } + } + + .q-date-table__cell-wrapper { + width: 40px; + height: 40px; + + .cell { + width: 40px; + height: 40px; + font-size: 15px; + } + } + + .q-period-table__cell { + width: 80px; + height: 80px; + font-size: 16px; + } + } + } + } } diff --git a/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss b/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss index e39885fc..15862b6a 100755 --- a/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss +++ b/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss @@ -68,13 +68,14 @@ &::before { position: absolute; top: -4px; - left: 9px; + left: 50%; display: block; width: 2px; height: 2px; content: ''; background-color: var(--color-primary); border-radius: 50%; + transform: translateX(-50%); } &:not(.cell_disabled) { diff --git a/src/qComponents/QDatePicker/src/tables/period-table.scss b/src/qComponents/QDatePicker/src/tables/period-table.scss index 8b00d4fa..a7a09393 100755 --- a/src/qComponents/QDatePicker/src/tables/period-table.scss +++ b/src/qComponents/QDatePicker/src/tables/period-table.scss @@ -49,14 +49,15 @@ &::before { position: absolute; - top: -5px; - left: 19px; + top: -12%; + left: 50%; display: block; width: 2px; height: 2px; content: ''; background-color: var(--color-primary); border-radius: 50%; + transform: translateX(-50%); } } diff --git a/src/qComponents/QDatePicker/src/types.ts b/src/qComponents/QDatePicker/src/types.ts index 83068726..e374573e 100644 --- a/src/qComponents/QDatePicker/src/types.ts +++ b/src/qComponents/QDatePicker/src/types.ts @@ -78,6 +78,7 @@ interface QDatePickerProvider { { hidePicker }?: HandlePickClickSecondArg ) => void; firstDayOfWeek: ComputedRef; + isMobileView: ComputedRef; disabledValues: Ref; shortcuts: Ref>; emitChange: (val: QDatePickerPropModelValue, intermediate: boolean) => void; @@ -92,6 +93,7 @@ interface QDatePickerInstance { isRanged: ComputedRef; isPickerDisabled: ComputedRef; isValueEmpty: ComputedRef; + isMobileView: ComputedRef; calcFirstDayOfWeek: ComputedRef; transformedToDate: ComputedRef>; rangeClasses: ComputedRef>; diff --git a/src/qComponents/QDialog/src/QDialog.vue b/src/qComponents/QDialog/src/QDialog.vue index 9176c951..d6a113d9 100644 --- a/src/qComponents/QDialog/src/QDialog.vue +++ b/src/qComponents/QDialog/src/QDialog.vue @@ -152,6 +152,13 @@ export default defineComponent({ renderOnMount: { type: Boolean, default: false + }, + /** + * cancel focus on document.activeElement after modal was closed + */ + preventFocusAfterClosing: { + type: Boolean, + default: false } }, @@ -240,7 +247,9 @@ export default defineComponent({ return; } - elementToFocusAfterClosing = document.activeElement as HTMLElement; + if (!props.preventFocusAfterClosing) { + elementToFocusAfterClosing = document.activeElement as HTMLElement; + } nextTick(() => { dialog.value?.focus(); }); From 99c8ae9c11b5a6ac041e70bb375e817ab78d176b Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 19 Jul 2021 11:04:25 +0300 Subject: [PATCH 02/14] wip --- src/qComponents/QDatePicker/src/QDatePicker.vue | 2 +- .../QDatePicker/src/panel/DateRange/DateRange.vue | 1 + src/qComponents/QDatePicker/src/q-date-picker.scss | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index d0d8890d..e6494362 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -2,6 +2,7 @@
+ Выбрать окончание
Date: Mon, 19 Jul 2021 11:38:20 +0300 Subject: [PATCH 03/14] add mobile range --- .../QDatePicker/src/panel/DateRange/DateRange.vue | 11 +++++------ .../QDatePicker/src/panel/DateRange/types.ts | 1 + src/qComponents/QDatePicker/src/q-date-picker.scss | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/qComponents/QDatePicker/src/panel/DateRange/DateRange.vue b/src/qComponents/QDatePicker/src/panel/DateRange/DateRange.vue index 476a0861..6c4f6957 100644 --- a/src/qComponents/QDatePicker/src/panel/DateRange/DateRange.vue +++ b/src/qComponents/QDatePicker/src/panel/DateRange/DateRange.vue @@ -39,16 +39,12 @@
@@ -249,6 +245,7 @@ export default defineComponent({ const rightYear = computed(() => state.rightDate.getFullYear()); const enableMonthArrow = computed(() => { + if (picker.isMobileView.value) return true; const nextMonth = (leftMonth.value + 1) % 12; const yearOffset = leftMonth.value + 1 >= 12 ? 1 : 0; return ( @@ -258,6 +255,7 @@ export default defineComponent({ }); const enableYearArrow = computed(() => { + if (picker.isMobileView.value) return true; return Boolean( rightYear.value * MONTHS_COUNT + rightMonth.value - @@ -489,7 +487,8 @@ export default defineComponent({ handleRightPrevMonthClick, handleRangeSelecting, navigateDropdown, - shortcuts: picker.shortcuts + shortcuts: picker.shortcuts, + isMobileView: picker.isMobileView }; } }); diff --git a/src/qComponents/QDatePicker/src/panel/DateRange/types.ts b/src/qComponents/QDatePicker/src/panel/DateRange/types.ts index 46c004ba..4126d537 100644 --- a/src/qComponents/QDatePicker/src/panel/DateRange/types.ts +++ b/src/qComponents/QDatePicker/src/panel/DateRange/types.ts @@ -43,6 +43,7 @@ interface DateRangePanelInstance { leftMonth: ComputedRef; rightMonth: ComputedRef; shortcuts: Ref>; + isMobileView: ComputedRef; handleRangePick: (val: RangePickValue, close?: boolean) => void; handleShortcutClick: (shortcut: Date) => void; handleClear: () => void; diff --git a/src/qComponents/QDatePicker/src/q-date-picker.scss b/src/qComponents/QDatePicker/src/q-date-picker.scss index 18e9c664..88a6add2 100644 --- a/src/qComponents/QDatePicker/src/q-date-picker.scss +++ b/src/qComponents/QDatePicker/src/q-date-picker.scss @@ -265,7 +265,7 @@ background-color: var(--color-tertiary-gray); } - &_disabled { + &:disabled { color: var(--color-tertiary-gray-ultra-darker); &:hover { @@ -369,6 +369,11 @@ &__header-label { font-size: 16px; } + + &__mobile-next-btn { + width: calc(100% - 26px); + margin: 13px 13px 20px 13px; + } } } } From 7fa44c71c1d1e17f17bf5e0067d726e1e23a9993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=87=D0=BA=D0=B0=D1=80=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B9?= Date: Mon, 19 Jul 2021 16:07:36 +0300 Subject: [PATCH 04/14] done --- .../QDatePicker/src/panel/Date/DatePanel.vue | 1 + .../QDatePicker/src/panel/Date/types.ts | 1 + .../src/panel/MonthRange/MonthRange.vue | 13 ++++----- .../QDatePicker/src/panel/MonthRange/types.ts | 1 + .../src/panel/YearRange/YearRange.vue | 12 +++++---- .../QDatePicker/src/panel/YearRange/types.ts | 1 + .../QDatePicker/src/q-date-picker.scss | 27 +++++++++++++++++++ src/qComponents/QDialog/src/types.ts | 1 + stories/components/QDialog.stories.ts | 1 + 9 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/qComponents/QDatePicker/src/panel/Date/DatePanel.vue b/src/qComponents/QDatePicker/src/panel/Date/DatePanel.vue index 91b2c336..616a1266 100644 --- a/src/qComponents/QDatePicker/src/panel/Date/DatePanel.vue +++ b/src/qComponents/QDatePicker/src/panel/Date/DatePanel.vue @@ -435,6 +435,7 @@ export default defineComponent({ state, root, shortcuts: picker.shortcuts, + isMobileView: picker.isMobileView, datePanel, panelContentClasses, currentMonth, diff --git a/src/qComponents/QDatePicker/src/panel/Date/types.ts b/src/qComponents/QDatePicker/src/panel/Date/types.ts index 89f285ab..1bdf106a 100644 --- a/src/qComponents/QDatePicker/src/panel/Date/types.ts +++ b/src/qComponents/QDatePicker/src/panel/Date/types.ts @@ -26,6 +26,7 @@ interface DatePanelInstance { state: DatePanelState; root: Ref>; shortcuts: Ref>; + isMobileView: ComputedRef; datePanel: Ref>; panelContentClasses: ComputedRef>; isPeriodTableShown: ComputedRef; diff --git a/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue b/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue index 46b1e754..405c3240 100644 --- a/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue +++ b/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue @@ -33,7 +33,6 @@
@@ -192,9 +192,10 @@ export default defineComponent({ getActualMonth(state.rightDate, 1) ); - const enableYearArrow = computed( - () => rightYear.value > leftYear.value + 1 - ); + const enableYearArrow = computed(() => { + if (picker.isMobileView.value) return true; + return rightYear.value > leftYear.value + 1; + }); const handleRangeSelecting = (value: RangeState): void => { state.rangeState = value; @@ -224,7 +225,6 @@ export default defineComponent({ state.maxDate = null; state.leftDate = new Date(); state.rightDate = addYears(new Date(), 1); - ctx.emit('pick', null); }; const handleLeftPrevYearClick = (): void => { @@ -342,7 +342,8 @@ export default defineComponent({ handleRightNextYearClick, handleRightPrevYearClick, navigateDropdown, - shortcuts: picker.shortcuts + shortcuts: picker.shortcuts, + isMobileView: picker.isMobileView }; } }); diff --git a/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts b/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts index b3ad67f9..ecf2c637 100644 --- a/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts +++ b/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts @@ -28,6 +28,7 @@ interface MonthRangePanelInstance { leftPanel: Ref>; rightPanel: Ref>; shortcuts: Ref>; + isMobileView: ComputedRef; state: MonthRangeState; leftPanelClasses: ComputedRef>; rightPanelClasses: ComputedRef>; diff --git a/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue b/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue index ee4e33c2..68091efd 100644 --- a/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue +++ b/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue @@ -51,6 +51,7 @@ />
@@ -180,9 +181,10 @@ export default defineComponent({ getLabelFromDate(state.rightDate, picker.type.value) ); - const enableYearArrow = computed( - () => rightYear.value > leftYear.value + YEARS_IN_DECADE - ); + const enableYearArrow = computed(() => { + if (picker.isMobileView.value) return true; + return rightYear.value > leftYear.value + YEARS_IN_DECADE; + }); const leftPanelClasses = computed(() => ({ 'q-picker-panel__content': true, 'q-picker-panel__content_no-right-borders': true, @@ -212,7 +214,6 @@ export default defineComponent({ state.maxDate = null; state.leftDate = new Date(); state.rightDate = addYears(new Date(), YEARS_IN_DECADE); - ctx.emit('pick', null); }; const handleRangePick = (val: RangePickValue, close = true): void => { @@ -333,7 +334,8 @@ export default defineComponent({ handleRangeSelecting, navigateDropdown, handleShortcutClick, - shortcuts: picker.shortcuts + shortcuts: picker.shortcuts, + isMobileView: picker.isMobileView }; } }); diff --git a/src/qComponents/QDatePicker/src/panel/YearRange/types.ts b/src/qComponents/QDatePicker/src/panel/YearRange/types.ts index 705a153f..f87f8bed 100644 --- a/src/qComponents/QDatePicker/src/panel/YearRange/types.ts +++ b/src/qComponents/QDatePicker/src/panel/YearRange/types.ts @@ -36,6 +36,7 @@ interface YearRangePanelInstance { leftPanelClasses: ComputedRef>; rightPanelClasses: ComputedRef>; shortcuts: Ref>; + isMobileView: ComputedRef; handleLeftNextYearClick: () => void; handleLeftPrevYearClick: () => void; handleRightNextYearClick: () => void; diff --git a/src/qComponents/QDatePicker/src/q-date-picker.scss b/src/qComponents/QDatePicker/src/q-date-picker.scss index 88a6add2..e2cc08f4 100644 --- a/src/qComponents/QDatePicker/src/q-date-picker.scss +++ b/src/qComponents/QDatePicker/src/q-date-picker.scss @@ -310,6 +310,10 @@ top: 100px; left: 50%; transform: translateX(-50%); + + .q-icon-close { + margin: 2px 0 0 2px; + } } .q-dialog__content { @@ -320,7 +324,21 @@ .q-picker-panel { max-width: 415px; &__body { + flex-direction: column; + width: 100%; + } + + &__sidebar { width: 100%; + max-height: 132px; + margin-bottom: 1px; + overflow-y: scroll; + } + + &__shortcut { + display: inline-block; + width: auto; + font-size: 16px; } &__content { @@ -331,6 +349,10 @@ font-size: 14px; } + &__header-sign { + font-size: 14px; + } + &__icon-btn { width: 30px; height: 30px; @@ -389,6 +411,11 @@ font-size: 16px; } + &__header-sign, + &__header-label { + font-size: 16px; + } + .q-date-table__days { font-size: 14px; } diff --git a/src/qComponents/QDialog/src/types.ts b/src/qComponents/QDialog/src/types.ts index 84692cf2..9c73c2cd 100644 --- a/src/qComponents/QDialog/src/types.ts +++ b/src/qComponents/QDialog/src/types.ts @@ -16,6 +16,7 @@ export interface QDialogProps { customClass: Nullable; teleportTo: QDialogPropTeleportTo; renderOnMount: Nullable; + preventFocusAfterClosing: Nullable; } export interface QDialogInstance { diff --git a/stories/components/QDialog.stories.ts b/stories/components/QDialog.stories.ts index 7644fdfd..86de598e 100644 --- a/stories/components/QDialog.stories.ts +++ b/stories/components/QDialog.stories.ts @@ -61,6 +61,7 @@ const QDialogStory: Story = args => :custom-class="args.customClass" :teleport-to="args.teleportTo" :render-on-mount="args.renderOnMount" + :prevent-focus-after-closing="args.preventFocusAfterClosing" @open="handleOpen" @opened="handleOpened" @close="handleClose" From 81bdda31908268fd63838e90f62b4e3dd52ca7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=87=D0=BA=D0=B0=D1=80=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B9?= Date: Mon, 19 Jul 2021 16:33:55 +0300 Subject: [PATCH 05/14] close picker on mobile watcher --- src/qComponents/QDatePicker/src/QDatePicker.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index e6494362..1db1b9ab 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -2,7 +2,7 @@
@@ -702,6 +701,12 @@ export default defineComponent({ } ); + watch(isMobileView, value => { + if (value) { + handleClose(); + } + }); + onMounted(() => { window.addEventListener('resize', updateClientWidth, true); }); From 10e0811d7aeb15212108830809130a18985ef56e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=87=D0=BA=D0=B0=D1=80=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B9?= Date: Wed, 21 Jul 2021 19:00:35 +0300 Subject: [PATCH 06/14] refactoring --- .../QDatePicker/src/QDatePicker.vue | 30 +- .../src/panel/MonthRange/MonthRange.vue | 2 +- .../src/panel/YearRange/YearRange.vue | 12 +- .../QDatePicker/src/panel/q-picker-panel.scss | 298 +++++++++++ .../QDatePicker/src/q-date-picker.scss | 482 ++++-------------- src/qComponents/QDatePicker/src/types.ts | 2 +- src/qComponents/QDialog/src/QDialog.vue | 13 +- src/qComponents/QDialog/src/q-dialog.scss | 2 +- 8 files changed, 434 insertions(+), 407 deletions(-) create mode 100644 src/qComponents/QDatePicker/src/panel/q-picker-panel.scss diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index 1db1b9ab..dfb13cca 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -11,8 +11,9 @@ > - {{ rangeSeparator }} + {{ rangeSeparator }}
@@ -77,20 +78,20 @@ @@ -373,10 +374,9 @@ export default defineComponent({ ); const rangeClasses = computed>(() => ({ - 'q-date-editor': true, - 'q-range-editor': true, - 'q-range-editor_disabled': isPickerDisabled.value, - 'q-range-editor_focused': state.pickerVisible + 'q-date-picker__range-wrapper': true, + 'q-date-picker__range-wrapper_disabled': isPickerDisabled.value, + 'q-date-picker__range-wrapper_focused': state.pickerVisible })); const isRanged = computed(() => props.type.includes('range')); @@ -649,7 +649,7 @@ export default defineComponent({ } }; - const handleClose = (): void => { + const closePicker = (): void => { if (!state.pickerVisible) return; state.pickerVisible = false; }; @@ -703,7 +703,7 @@ export default defineComponent({ watch(isMobileView, value => { if (value) { - handleClose(); + closePicker(); } }); @@ -752,7 +752,7 @@ export default defineComponent({ handleInput, handleMouseEnter, handleRangeClick, - handleClose, + closePicker, handleIconClick, t }; diff --git a/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue b/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue index 405c3240..ba806bba 100644 --- a/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue +++ b/src/qComponents/QDatePicker/src/panel/MonthRange/MonthRange.vue @@ -176,7 +176,7 @@ export default defineComponent({ 'q-picker-panel__content_focused': state.panelInFocus === 'right' })); - const rightYear = computed(() => { + const rightYear = computed(() => { if (isDate(state.rightDate) && isDate(state.leftDate)) { return state.rightDate.getFullYear() === state.leftDate.getFullYear() ? state.leftDate.getFullYear() + 1 diff --git a/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue b/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue index 68091efd..c04bc9fd 100644 --- a/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue +++ b/src/qComponents/QDatePicker/src/panel/YearRange/YearRange.vue @@ -173,25 +173,25 @@ export default defineComponent({ return new Date().getFullYear() + YEARS_IN_DECADE; }); - const leftYear = computed(() => leftYearComposable(state.leftDate)); - const leftLabel = computed(() => + const leftYear = computed(() => leftYearComposable(state.leftDate)); + const leftLabel = computed(() => getLabelFromDate(state.leftDate, picker.type.value) ); - const rightLabel = computed(() => + const rightLabel = computed(() => getLabelFromDate(state.rightDate, picker.type.value) ); - const enableYearArrow = computed(() => { + const enableYearArrow = computed(() => { if (picker.isMobileView.value) return true; return rightYear.value > leftYear.value + YEARS_IN_DECADE; }); - const leftPanelClasses = computed(() => ({ + const leftPanelClasses = computed>(() => ({ 'q-picker-panel__content': true, 'q-picker-panel__content_no-right-borders': true, 'q-picker-panel__content_focused': state.panelInFocus === 'left' })); - const rightPanelClasses = computed(() => ({ + const rightPanelClasses = computed>(() => ({ 'q-picker-panel__content': true, 'q-picker-panel__content_no-left-borders': true, 'q-picker-panel__content_focused': state.panelInFocus === 'right' diff --git a/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss b/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss new file mode 100644 index 00000000..e291d538 --- /dev/null +++ b/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss @@ -0,0 +1,298 @@ +.q-picker-panel { + z-index: 1; + border-radius: 4px; + + &__content { + position: relative; + box-sizing: border-box; + flex-shrink: 0; + width: 220px; + background: var(--color-tertiary-gray-light); + border-radius: 4px; + box-shadow: var(--box-shadow-pressed); + + &_no-right-borders { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &_no-left-borders { + margin-left: 1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + &_focused { + z-index: 2; + border-radius: 4px; + outline: none; + box-shadow: var(--box-shadow-secondary); + transition: transform 0.2s; + transform: scale(1.03); + } + } + + &__sidebar { + flex-shrink: 0; + width: 114px; + padding: 16px 8px; + overflow: auto; + background: var(--color-tertiary-gray-light); + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + box-shadow: var(--box-shadow-pressed); + + & + .q-picker-panel__content { + margin-left: 1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + &__body, + &__body-wrapper { + display: flex; + flex-shrink: 0; + height: 100%; + background: var(--color-tertiary-gray-light); + border-radius: 4px; + box-shadow: var(--box-shadow-secondary); + + &::after { + display: table; + clear: both; + content: ''; + } + } + + &__header { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 8px 0; + text-align: center; + text-transform: uppercase; + + &-sign { + display: flex; + flex-basis: 72px; + flex-grow: 1; + justify-content: center; + margin-right: 12px; + margin-left: 12px; + font-size: 12px; + font-weight: 600; + line-height: 15px; + color: var(--color-primary-black); + white-space: nowrap; + } + } + + &__header-label { + padding: 0; + font-weight: 600; + line-height: 15px; + color: var(--color-primary-black); + text-transform: uppercase; + white-space: nowrap; + cursor: pointer; + user-select: none; + border: none; + + .q-picker-panel_dialog-view & { + font-size: 14px; + } + + &:nth-child(2) { + margin-left: 5px; + } + + &[data-focus-visible-added], + &:hover:not(.q-picker-panel__header-label_hoverless) { + color: var(--color-primary-blue); + } + } + + &__shortcut { + display: block; + width: 100%; + padding-bottom: 8px; + font-size: 10px; + line-height: 12px; + color: rgba(var(--color-rgb-gray), 0.64); + text-align: left; + cursor: pointer; + background-color: transparent; + border: 0; + outline: none; + + &:hover { + color: var(--color-primary-blue); + } + + &[data-focus-visible-added] { + color: var(--color-primary-blue); + text-decoration: underline; + } + } + + &__icon-btn { + width: 20px; + min-width: 20px; + height: 20px; + padding: 0; + margin: 0 6px 0 0; + font-size: 20px; + color: var(--color-primary-blue); + cursor: pointer; + background: var(--color-tertiary-gray-light); + border: 0; + border-radius: 20px; + outline: none; + box-shadow: var(--box-shadow-primary); + + .q-picker-panel_dialog-view & { + width: 30px; + height: 30px; + font-size: 29px; + } + + &:hover { + color: var(--color-primary-black); + } + + &[data-focus-visible-added] { + color: var(--color-primary-black); + background-color: var(--color-tertiary-gray); + } + + &:disabled { + color: var(--color-tertiary-gray-ultra-darker); + + &:hover { + color: var(--color-tertiary-gray-ultra-darker); + cursor: not-allowed; + } + } + } + + &__link-btn { + vertical-align: middle; + } + + &_dialog-view { + width: 100%; + max-width: 415px; + // reject user-select on mobile + -webkit-user-select: none; + user-select: none; + + .q-picker-panel__body { + flex-direction: column; + width: 100%; + } + + .q-picker-panel__sidebar { + width: 100%; + max-height: 132px; + margin-bottom: 1px; + overflow-y: scroll; + } + + .q-picker-panel__shortcut { + display: inline-block; + width: auto; + font-size: 16px; + } + + .q-picker-panel__content { + width: 100%; + } + + .q-picker-panel__header-sign { + font-size: 14px; + } + + .q-date-table__days { + font-size: 12px; + } + + .q-date-table__cell-wrapper { + width: 30px; + height: 30px; + + .cell { + width: 30px; + height: 30px; + font-size: 14px; + } + } + + .q-period-table__cell { + width: 60px; + height: 60px; + font-size: 16px; + } + + &__header-label { + font-size: 16px; + } + } + + &-animation { + &-enter-active, + &-leave-active { + transition: opacity var(--transition-spline-base) 0.25s; + + .q-picker-panel__body { + transition: margin-top var(--transition-spline-base) 0.25s; + } + } + + &-enter-from, + &-leave-to { + opacity: 0; + + .q-picker-panel__body { + margin-top: -8px; + } + } + } +} + +@media (min-width: 375px) { + .q-picker-panel { + &_dialog-view { + .q-picker-panel__header-label { + font-size: 16px; + } + + .q-picker-panel__header-sign, + .q-picker-panel__header-label { + font-size: 16px; + } + + .q-date-table__days { + font-size: 14px; + } + + .q-date-table__cell-wrapper { + width: 40px; + height: 40px; + + .cell { + width: 40px; + height: 40px; + font-size: 15px; + } + } + + .q-period-table__cell { + width: 80px; + height: 80px; + font-size: 16px; + } + } + } +} diff --git a/src/qComponents/QDatePicker/src/q-date-picker.scss b/src/qComponents/QDatePicker/src/q-date-picker.scss index e2cc08f4..4160271d 100644 --- a/src/qComponents/QDatePicker/src/q-date-picker.scss +++ b/src/qComponents/QDatePicker/src/q-date-picker.scss @@ -1,13 +1,20 @@ +@import './panel/q-picker-panel.scss'; @import './tables/DateTable/date-table.scss'; @import './tables/period-table.scss'; .q-date-picker { - .q-date-editor { - --field-box-shadow-focus: -1px -1px 3px rgba(var(--color-rgb-white), 0.25), - 1px 1px 3px rgba(var(--color-rgb-blue), 0.4), - inset -1px -1px 1px rgba(var(--color-rgb-white), 0.7), - inset 1px 1px 2px rgba(var(--color-rgb-blue), 0.2); - + --field-icon-color-base: var(--color-primary-blue); + --field-icon-color-hover: var(--color-primary-black); + --field-box-shadow-focus: -1px -1px 3px rgba(var(--color-rgb-white), 0.25), + 1px 1px 3px rgba(var(--color-rgb-blue), 0.4), + inset -1px -1px 1px rgba(var(--color-rgb-white), 0.7), + inset 1px 1px 2px rgba(var(--color-rgb-blue), 0.2); + --field-box-shadow-hover: -1px -1px 4px rgba(var(--color-rgb-white), 0.25), + 1px 1px 4px rgba(var(--color-rgb-blue), 0.4), + 4px 4px 8px rgba(var(--color-rgb-blue), 0.4), + -4px -4px 8px rgba(var(--color-rgb-white), 0.8); + + &__input { position: relative; display: inline-block; text-align: left; @@ -20,292 +27,124 @@ color: var(--color-primary-black); } } - - .q-range-input { - display: inline-block; - width: 42%; - height: 100%; - padding: 0; - margin: 0; - font-size: inherit; - font-weight: var(--font-weight-base); - line-height: 40px; - color: var(--color-primary-black); - text-overflow: ellipsis; - white-space: nowrap; - border: none; - outline: none; - appearance: none; - - &::placeholder { - color: rgba(var(--color-rgb-gray), 0.32); - opacity: 1; - } - } - - .q-range-separator { - display: inline-block; - width: 5%; - height: 100%; - padding: 0 2px; - margin: 0; - font-size: 14px; - line-height: 32px; - color: var(--color-primary-black); - text-align: center; - } } - .q-range-editor { - display: inline-flex; - align-items: center; - width: 100%; - padding-left: 5px; - cursor: pointer; - background-color: var(--color-tertiary-gray-light); - border: none; - border-radius: var(--border-radius-base); - outline: none; - box-shadow: var(--box-shadow-primary); - - .q-form-item_is-error & { - border: var(--border-error); - } - - &:hover:not(.q-range-editor_disabled) { - background-color: var(--color-tertiary-gray); - box-shadow: var(--box-shadow-hover); - } - - .q-range-input { - line-height: 1; - text-align: center; - background: none; - } - - .q-input__icon { - margin-left: auto; - } - - &_focused { - background-color: var(--color-tertiary-gray-ultra-light); - outline: none; - box-shadow: var(--field-box-shadow-focus); - } - - &_disabled { - cursor: not-allowed; - background-color: var(--color-tertiary-gray); - box-shadow: var(--box-shadow-pressed); - - .q-range-separator, - .q-range-input { - color: var(--field-color-disabled); - cursor: not-allowed; - } - } - } - - .q-picker-panel { - z-index: 1; - border-radius: 4px; - - &__content { + &_ranged { + .q-date-picker__range-wrapper { position: relative; - box-sizing: border-box; - flex-shrink: 0; - width: 220px; - background: var(--color-tertiary-gray-light); - border-radius: 4px; - box-shadow: var(--box-shadow-pressed); - - &_no-right-borders { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - &_no-left-borders { - margin-left: 1px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - &_focused { - z-index: 2; - border-radius: 4px; - outline: none; - box-shadow: var(--box-shadow-secondary); - transition: transform 0.2s; - transform: scale(1.03); - } - } - - &__sidebar { - flex-shrink: 0; - width: 114px; - padding: 16px 8px; - overflow: auto; - background: var(--color-tertiary-gray-light); - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - box-shadow: var(--box-shadow-pressed); - - & + .q-picker-panel__content { - margin-left: 1px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - } - - &__body, - &__body-wrapper { - display: flex; - flex-shrink: 0; - height: 100%; - background: var(--color-tertiary-gray-light); - border-radius: 4px; - box-shadow: var(--box-shadow-secondary); + display: inline-flex; + align-items: stretch; + width: 100%; + height: 40px; + padding-left: 5px; + cursor: pointer; + background-color: var(--color-tertiary-gray-light); + border: none; + border-radius: var(--border-radius-base); + outline: none; + box-shadow: var(--box-shadow-primary); - &::after { - display: table; - clear: both; - content: ''; + &:hover { + background-color: var(--color-tertiary-gray); + box-shadow: var(--field-box-shadow-hover); } - } - &__header { - position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 8px 0; - text-align: center; - text-transform: uppercase; - - &-sign { - display: flex; - flex-basis: 72px; - flex-grow: 1; - justify-content: center; - margin-right: 12px; - margin-left: 12px; - font-size: 12px; - font-weight: 600; - line-height: 15px; + .q-date-picker__input { + width: 42%; + height: 100%; + padding: 0; + margin: auto 0; + font-size: inherit; + font-weight: var(--font-weight-base); + line-height: 40px; color: var(--color-primary-black); + text-align: center; + text-overflow: ellipsis; white-space: nowrap; + cursor: pointer; + background: none; + border: none; + outline: none; + appearance: none; + + &::placeholder { + color: rgba(var(--color-rgb-gray), 0.32); + opacity: 1; + } } - } - &__header-label { - padding: 0; - font-weight: 600; - line-height: 15px; - color: var(--color-primary-black); - text-transform: uppercase; - white-space: nowrap; - cursor: pointer; - user-select: none; - border: none; + .q-date-picker__suffix { + display: block; + flex-shrink: 0; + width: 40px; + color: var(--field-icon-color-inactive); + text-align: center; + pointer-events: all; + transition: all 0.3s; - &:nth-child(2) { - margin-left: 5px; - } + &::before { + font-size: 24px; + line-height: 40px; + } - &[data-focus-visible-added], - &:hover:not(.q-picker-panel__header-label_hoverless) { - color: var(--color-primary-blue); - } - } + &.q-icon-close { + color: var(--field-icon-color-base); - &__shortcut { - display: block; - width: 100%; - padding-bottom: 8px; - font-size: 10px; - line-height: 12px; - color: rgba(var(--color-rgb-gray), 0.64); - text-align: left; - cursor: pointer; - background-color: transparent; - border: 0; - outline: none; + &::before { + // stylelint-disable-next-line font-family-no-missing-generic-family-keyword + font-family: 'qicon'; + } - &:hover { - color: var(--color-primary-blue); + &.focus-visible, + &:hover { + color: var(--field-icon-color-hover); + } + } } - &[data-focus-visible-added] { - color: var(--color-primary-blue); - text-decoration: underline; + .q-date-picker__range-separator { + margin: auto 0; } - } - &__icon-btn { - width: 20px; - min-width: 20px; - height: 20px; - padding: 0; - margin: 0 6px 0 0; - font-size: 20px; - color: var(--color-primary-blue); - cursor: pointer; - background: var(--color-tertiary-gray-light); - border: 0; - border-radius: 20px; - outline: none; - box-shadow: var(--box-shadow-primary); - - &:hover { - color: var(--color-primary-black); + &_focused { + background-color: var(--color-tertiary-gray-ultra-light); + outline: none; + box-shadow: var(--field-box-shadow-focus); } - &[data-focus-visible-added] { - color: var(--color-primary-black); + &_disabled { + cursor: not-allowed; background-color: var(--color-tertiary-gray); - } - - &:disabled { - color: var(--color-tertiary-gray-ultra-darker); + box-shadow: var(--box-shadow-pressed); - &:hover { - color: var(--color-tertiary-gray-ultra-darker); + .q-date-picker__range-separator, + .q-date-picker__input { + color: var(--field-color-disabled); cursor: not-allowed; } } } + } - &__link-btn { - vertical-align: middle; - } - - &_animation { - &-enter-active, - &-leave-active { - transition: opacity var(--transition-spline-base) 0.25s; - - .q-picker-panel__body { - transition: margin-top var(--transition-spline-base) 0.25s; - } - } - - &-enter-from, - &-leave-to { - opacity: 0; + .q-input__icon { + margin-left: auto; + } + .q-form-item_is-error & { + border: var(--border-error); + } - .q-picker-panel__body { - margin-top: -8px; - } - } + .q-dialog { + &__content { + display: flex; + align-items: flex-end; + justify-content: center; } - } - .dialog-view { - .q-dialog__inner { + &__inner { padding: 0; } - .q-dialog__close { + &__close { position: absolute; top: 100px; left: 50%; @@ -315,128 +154,17 @@ margin: 2px 0 0 2px; } } - - .q-dialog__content { - display: flex; - align-items: flex-end; - justify-content: center; - - .q-picker-panel { - max-width: 415px; - &__body { - flex-direction: column; - width: 100%; - } - - &__sidebar { - width: 100%; - max-height: 132px; - margin-bottom: 1px; - overflow-y: scroll; - } - - &__shortcut { - display: inline-block; - width: auto; - font-size: 16px; - } - - &__content { - width: 100%; - } - - &__header-label { - font-size: 14px; - } - - &__header-sign { - font-size: 14px; - } - - &__icon-btn { - width: 30px; - height: 30px; - font-size: 29px; - } - - .q-date-table__days { - font-size: 12px; - } - } - - .q-date-table__cell-wrapper { - width: 30px; - height: 30px; - - .cell { - width: 30px; - height: 30px; - font-size: 14px; - } - } - - .q-period-table__cell { - width: 60px; - height: 60px; - font-size: 16px; - } - } - } - - &_ranged { - .dialog-view { - .q-dialog__content { - .q-picker-panel { - width: 100%; - &__header-label { - font-size: 16px; - } - - &__mobile-next-btn { - width: calc(100% - 26px); - margin: 13px 13px 20px 13px; - } - } - } - } } } -@media (min-width: 375px) { +@media (min-width: 415px) { .q-date-picker { - .dialog-view { - .q-dialog__content { - .q-picker-panel { - &__header-label { - font-size: 16px; - } - - &__header-sign, - &__header-label { - font-size: 16px; - } - - .q-date-table__days { - font-size: 14px; - } - } - - .q-date-table__cell-wrapper { - width: 40px; - height: 40px; - - .cell { - width: 40px; - height: 40px; - font-size: 15px; - } - } - - .q-period-table__cell { - width: 80px; - height: 80px; - font-size: 16px; - } + .q-dialog { + &__close { + top: 20px; + right: 20px; + left: auto; + transform: translateX(0); } } } diff --git a/src/qComponents/QDatePicker/src/types.ts b/src/qComponents/QDatePicker/src/types.ts index e374573e..f04579af 100644 --- a/src/qComponents/QDatePicker/src/types.ts +++ b/src/qComponents/QDatePicker/src/types.ts @@ -109,6 +109,7 @@ interface QDatePickerInstance { handleKeyUp: (e: KeyboardEvent) => void; popperInit: () => void; destroyPopper: () => void; + closePicker: () => void; handlePickClick: ( val: QDatePickerPropModelValue, options?: { @@ -119,7 +120,6 @@ interface QDatePickerInstance { handleInput: (arg: { target: HTMLInputElement; inputType: string }) => void; handleMouseEnter: () => void; handleRangeClick: () => void; - handleClose: () => void; handleIconClick: (event: MouseEvent) => void; t: (key: string) => string; } diff --git a/src/qComponents/QDialog/src/QDialog.vue b/src/qComponents/QDialog/src/QDialog.vue index d6a113d9..74d4881e 100644 --- a/src/qComponents/QDialog/src/QDialog.vue +++ b/src/qComponents/QDialog/src/QDialog.vue @@ -241,15 +241,16 @@ export default defineComponent({ isRendered.value = false; } - nextTick(() => { - elementToFocusAfterClosing?.focus(); - }); + if (!props.preventFocusAfterClosing) { + nextTick(() => { + elementToFocusAfterClosing?.focus(); + }); + } + return; } - if (!props.preventFocusAfterClosing) { - elementToFocusAfterClosing = document.activeElement as HTMLElement; - } + elementToFocusAfterClosing = document.activeElement as HTMLElement; nextTick(() => { dialog.value?.focus(); }); diff --git a/src/qComponents/QDialog/src/q-dialog.scss b/src/qComponents/QDialog/src/q-dialog.scss index 8fdb0922..cb445b3f 100644 --- a/src/qComponents/QDialog/src/q-dialog.scss +++ b/src/qComponents/QDialog/src/q-dialog.scss @@ -2,11 +2,11 @@ position: fixed; top: 0; right: 0; + bottom: 0; left: 0; display: flex; align-items: center; justify-content: center; - height: 100vh; &__container { width: 100%; From 499301d06a9de2822a866674cf2e0770b8d16ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=87=D0=BA=D0=B0=D1=80=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B9?= Date: Mon, 26 Jul 2021 20:22:04 +0300 Subject: [PATCH 07/14] add hooks & helpers --- .../QDatePicker/src/QDatePicker.vue | 29 ++++--------------- .../QDatePicker/src/panel/Date/types.ts | 2 +- .../QDatePicker/src/panel/DateRange/types.ts | 2 +- .../QDatePicker/src/panel/MonthRange/types.ts | 2 +- .../QDatePicker/src/panel/YearRange/types.ts | 2 +- .../QDatePicker/src/panel/q-picker-panel.scss | 16 +++++----- .../QDatePicker/src/q-date-picker.scss | 5 ---- src/qComponents/QDatePicker/src/types.ts | 4 +-- src/qComponents/helpers/index.ts | 1 + src/qComponents/helpers/isTouchDevice.ts | 4 +++ src/qComponents/hooks/helpers.ts | 13 +++++++++ src/qComponents/hooks/index.ts | 5 +++- src/qComponents/hooks/useMediaQuery/index.ts | 26 +++++++++++++++++ 13 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 src/qComponents/helpers/isTouchDevice.ts create mode 100644 src/qComponents/hooks/helpers.ts create mode 100644 src/qComponents/hooks/useMediaQuery/index.ts diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index dfb13cca..01223b0d 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -85,7 +85,7 @@ :is="panelComponent" ref="panel" v-model="transformedToDate" - class="q-picker-panel_dialog-view" + class="q-picker-panel__dialog-view" @pick="handlePickClick" /> @@ -117,9 +117,7 @@ import { watch, onBeforeUnmount, provide, - toRef, - onMounted, - onUnmounted + toRef } from 'vue'; import type { PropType } from 'vue'; import { createPopper } from '@popperjs/core'; @@ -136,7 +134,8 @@ import { import { getConfig } from '@/qComponents/config'; import { t } from '@/qComponents/locale'; -import { notNull, validateArray } from '@/qComponents/helpers'; +import { notNull, validateArray, isTouchDevice } from '@/qComponents/helpers'; +import { useMediaQuery } from '@/qComponents/hooks'; import QInput from '@/qComponents/QInput'; import QDialog from '@/qComponents/QDialog'; import type { QFormProvider } from '@/qComponents/QForm'; @@ -338,11 +337,7 @@ export default defineComponent({ popper: null }); - const dynamicClientWidth = ref(document.body.clientWidth); - - const isMobileView = computed( - () => dynamicClientWidth.value < 768 - ); + const isMobileView = useMediaQuery('(max-width: 768px)'); const calcFirstDayOfWeek = computed(() => { if (isNumber(props.firstDayOfWeek)) return props.firstDayOfWeek; @@ -409,7 +404,7 @@ export default defineComponent({ const iconClass = computed(() => { if (isPickerDisabled.value) return 'q-icon-lock'; - if (isMobileView.value) + if (isTouchDevice()) return !isValueEmpty.value && props.clearable ? 'q-icon-close' : 'q-icon-calendar'; @@ -660,10 +655,6 @@ export default defineComponent({ ctx.emit('focus'); }; - const updateClientWidth = (): void => { - dynamicClientWidth.value = document.body.clientWidth; - }; - const handleInput = ({ target, inputType @@ -707,14 +698,6 @@ export default defineComponent({ } }); - onMounted(() => { - window.addEventListener('resize', updateClientWidth, true); - }); - - onUnmounted(() => { - window.removeEventListener('resize', updateClientWidth); - }); - onBeforeUnmount(() => destroyPopper()); provide('qDatePicker', { diff --git a/src/qComponents/QDatePicker/src/panel/Date/types.ts b/src/qComponents/QDatePicker/src/panel/Date/types.ts index 1bdf106a..7aad810a 100644 --- a/src/qComponents/QDatePicker/src/panel/Date/types.ts +++ b/src/qComponents/QDatePicker/src/panel/Date/types.ts @@ -26,7 +26,7 @@ interface DatePanelInstance { state: DatePanelState; root: Ref>; shortcuts: Ref>; - isMobileView: ComputedRef; + isMobileView: Ref; datePanel: Ref>; panelContentClasses: ComputedRef>; isPeriodTableShown: ComputedRef; diff --git a/src/qComponents/QDatePicker/src/panel/DateRange/types.ts b/src/qComponents/QDatePicker/src/panel/DateRange/types.ts index 4126d537..8fb4cd9d 100644 --- a/src/qComponents/QDatePicker/src/panel/DateRange/types.ts +++ b/src/qComponents/QDatePicker/src/panel/DateRange/types.ts @@ -43,7 +43,7 @@ interface DateRangePanelInstance { leftMonth: ComputedRef; rightMonth: ComputedRef; shortcuts: Ref>; - isMobileView: ComputedRef; + isMobileView: Ref; handleRangePick: (val: RangePickValue, close?: boolean) => void; handleShortcutClick: (shortcut: Date) => void; handleClear: () => void; diff --git a/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts b/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts index ecf2c637..87462cb7 100644 --- a/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts +++ b/src/qComponents/QDatePicker/src/panel/MonthRange/types.ts @@ -28,7 +28,7 @@ interface MonthRangePanelInstance { leftPanel: Ref>; rightPanel: Ref>; shortcuts: Ref>; - isMobileView: ComputedRef; + isMobileView: Ref; state: MonthRangeState; leftPanelClasses: ComputedRef>; rightPanelClasses: ComputedRef>; diff --git a/src/qComponents/QDatePicker/src/panel/YearRange/types.ts b/src/qComponents/QDatePicker/src/panel/YearRange/types.ts index f87f8bed..07551e21 100644 --- a/src/qComponents/QDatePicker/src/panel/YearRange/types.ts +++ b/src/qComponents/QDatePicker/src/panel/YearRange/types.ts @@ -36,7 +36,7 @@ interface YearRangePanelInstance { leftPanelClasses: ComputedRef>; rightPanelClasses: ComputedRef>; shortcuts: Ref>; - isMobileView: ComputedRef; + isMobileView: Ref; handleLeftNextYearClick: () => void; handleLeftPrevYearClick: () => void; handleRightNextYearClick: () => void; diff --git a/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss b/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss index e291d538..a27bf821 100644 --- a/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss +++ b/src/qComponents/QDatePicker/src/panel/q-picker-panel.scss @@ -1,6 +1,6 @@ .q-picker-panel { z-index: 1; - border-radius: 4px; + border-radius: var(--border-radius-base); &__content { position: relative; @@ -8,7 +8,7 @@ flex-shrink: 0; width: 220px; background: var(--color-tertiary-gray-light); - border-radius: 4px; + border-radius: var(--border-radius-base); box-shadow: var(--box-shadow-pressed); &_no-right-borders { @@ -24,7 +24,7 @@ &_focused { z-index: 2; - border-radius: 4px; + border-radius: var(--border-radius-base); outline: none; box-shadow: var(--box-shadow-secondary); transition: transform 0.2s; @@ -55,7 +55,7 @@ flex-shrink: 0; height: 100%; background: var(--color-tertiary-gray-light); - border-radius: 4px; + border-radius: var(--border-radius-base); box-shadow: var(--box-shadow-secondary); &::after { @@ -100,7 +100,7 @@ user-select: none; border: none; - .q-picker-panel_dialog-view & { + .q-picker-panel__dialog-view & { font-size: 14px; } @@ -152,7 +152,7 @@ outline: none; box-shadow: var(--box-shadow-primary); - .q-picker-panel_dialog-view & { + .q-picker-panel__dialog-view & { width: 30px; height: 30px; font-size: 29px; @@ -181,7 +181,7 @@ vertical-align: middle; } - &_dialog-view { + &__dialog-view { width: 100%; max-width: 415px; // reject user-select on mobile @@ -263,7 +263,7 @@ @media (min-width: 375px) { .q-picker-panel { - &_dialog-view { + &__dialog-view { .q-picker-panel__header-label { font-size: 16px; } diff --git a/src/qComponents/QDatePicker/src/q-date-picker.scss b/src/qComponents/QDatePicker/src/q-date-picker.scss index 4160271d..0fcb2824 100644 --- a/src/qComponents/QDatePicker/src/q-date-picker.scss +++ b/src/qComponents/QDatePicker/src/q-date-picker.scss @@ -90,11 +90,6 @@ &.q-icon-close { color: var(--field-icon-color-base); - &::before { - // stylelint-disable-next-line font-family-no-missing-generic-family-keyword - font-family: 'qicon'; - } - &.focus-visible, &:hover { color: var(--field-icon-color-hover); diff --git a/src/qComponents/QDatePicker/src/types.ts b/src/qComponents/QDatePicker/src/types.ts index f04579af..2ae81a5c 100644 --- a/src/qComponents/QDatePicker/src/types.ts +++ b/src/qComponents/QDatePicker/src/types.ts @@ -78,7 +78,7 @@ interface QDatePickerProvider { { hidePicker }?: HandlePickClickSecondArg ) => void; firstDayOfWeek: ComputedRef; - isMobileView: ComputedRef; + isMobileView: Ref; disabledValues: Ref; shortcuts: Ref>; emitChange: (val: QDatePickerPropModelValue, intermediate: boolean) => void; @@ -93,7 +93,7 @@ interface QDatePickerInstance { isRanged: ComputedRef; isPickerDisabled: ComputedRef; isValueEmpty: ComputedRef; - isMobileView: ComputedRef; + isMobileView: Ref; calcFirstDayOfWeek: ComputedRef; transformedToDate: ComputedRef>; rangeClasses: ComputedRef>; diff --git a/src/qComponents/helpers/index.ts b/src/qComponents/helpers/index.ts index f8eea76e..788ad01a 100644 --- a/src/qComponents/helpers/index.ts +++ b/src/qComponents/helpers/index.ts @@ -1,3 +1,4 @@ export * from './randId'; export * from './validateArray'; export * from './notNull'; +export * from './isTouchDevice'; diff --git a/src/qComponents/helpers/isTouchDevice.ts b/src/qComponents/helpers/isTouchDevice.ts new file mode 100644 index 00000000..d8e8fd4c --- /dev/null +++ b/src/qComponents/helpers/isTouchDevice.ts @@ -0,0 +1,4 @@ +export const isTouchDevice = (): boolean => + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + navigator.msMaxTouchPoints > 0; diff --git a/src/qComponents/hooks/helpers.ts b/src/qComponents/hooks/helpers.ts new file mode 100644 index 00000000..7eb6a274 --- /dev/null +++ b/src/qComponents/hooks/helpers.ts @@ -0,0 +1,13 @@ +import { getCurrentInstance, onUnmounted } from 'vue'; + +type Fn = () => void; + +/** + * Call onUnmounted() if it's inside a component lifecycle, if not, do nothing + * + * @param fn + */ + +export const tryOnUnmounted = (fn: Fn): void => { + if (getCurrentInstance()) onUnmounted(fn); +}; diff --git a/src/qComponents/hooks/index.ts b/src/qComponents/hooks/index.ts index 21c44477..bb5f5c9c 100644 --- a/src/qComponents/hooks/index.ts +++ b/src/qComponents/hooks/index.ts @@ -1 +1,4 @@ -export { useResizeListener } from './useResizeListener'; +import { useResizeListener } from './useResizeListener'; +import { useMediaQuery } from './useMediaQuery'; + +export { useResizeListener, useMediaQuery }; diff --git a/src/qComponents/hooks/useMediaQuery/index.ts b/src/qComponents/hooks/useMediaQuery/index.ts new file mode 100644 index 00000000..91bc350d --- /dev/null +++ b/src/qComponents/hooks/useMediaQuery/index.ts @@ -0,0 +1,26 @@ +import { ref, Ref } from 'vue'; +import { tryOnUnmounted } from '../helpers'; + +/** + * Reactive Media Query. + * + * @param query + * @param options + */ + +export const useMediaQuery = (query: string): Ref => { + if (!window) return ref(false); + + const mediaQuery = window.matchMedia(query); + const matches = ref(mediaQuery.matches); + + const handler = (event: MediaQueryListEvent): void => { + matches.value = event.matches; + }; + + mediaQuery.addEventListener('change', handler); + + tryOnUnmounted(() => mediaQuery.removeEventListener('change', handler)); + + return matches; +}; From a973b77e87faf3ba8a3a78d2c57c33e1d5bd72f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=87=D0=BA=D0=B0=D1=80=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B9?= Date: Mon, 26 Jul 2021 21:23:25 +0300 Subject: [PATCH 08/14] define isTouchDevice with media query --- src/qComponents/QDatePicker/src/QDatePicker.vue | 5 +++-- .../QDatePicker/src/tables/DateTable/date-table.scss | 2 +- src/qComponents/helpers/index.ts | 1 - src/qComponents/helpers/isTouchDevice.ts | 4 ---- 4 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 src/qComponents/helpers/isTouchDevice.ts diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index 01223b0d..fe00c766 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -134,7 +134,7 @@ import { import { getConfig } from '@/qComponents/config'; import { t } from '@/qComponents/locale'; -import { notNull, validateArray, isTouchDevice } from '@/qComponents/helpers'; +import { notNull, validateArray } from '@/qComponents/helpers'; import { useMediaQuery } from '@/qComponents/hooks'; import QInput from '@/qComponents/QInput'; import QDialog from '@/qComponents/QDialog'; @@ -338,6 +338,7 @@ export default defineComponent({ }); const isMobileView = useMediaQuery('(max-width: 768px)'); + const isTouchMode = useMediaQuery('(pointer: coarse)'); const calcFirstDayOfWeek = computed(() => { if (isNumber(props.firstDayOfWeek)) return props.firstDayOfWeek; @@ -404,7 +405,7 @@ export default defineComponent({ const iconClass = computed(() => { if (isPickerDisabled.value) return 'q-icon-lock'; - if (isTouchDevice()) + if (isTouchMode.value) return !isValueEmpty.value && props.clearable ? 'q-icon-close' : 'q-icon-calendar'; diff --git a/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss b/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss index 15862b6a..9575931e 100755 --- a/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss +++ b/src/qComponents/QDatePicker/src/tables/DateTable/date-table.scss @@ -103,7 +103,7 @@ height: 20px; margin: auto; content: ''; - border-radius: 4px; + border-radius: var(--border-radius-base); box-shadow: var(--box-shadow-primary); } diff --git a/src/qComponents/helpers/index.ts b/src/qComponents/helpers/index.ts index 788ad01a..f8eea76e 100644 --- a/src/qComponents/helpers/index.ts +++ b/src/qComponents/helpers/index.ts @@ -1,4 +1,3 @@ export * from './randId'; export * from './validateArray'; export * from './notNull'; -export * from './isTouchDevice'; diff --git a/src/qComponents/helpers/isTouchDevice.ts b/src/qComponents/helpers/isTouchDevice.ts deleted file mode 100644 index d8e8fd4c..00000000 --- a/src/qComponents/helpers/isTouchDevice.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const isTouchDevice = (): boolean => - 'ontouchstart' in window || - navigator.maxTouchPoints > 0 || - navigator.msMaxTouchPoints > 0; From bb38daa3cf02be8ede04c89da148a2df2a635990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=87=D0=BA=D0=B0=D1=80=D0=B5=D0=B2=20=D0=A2?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=84=D0=B5=D0=B9?= Date: Fri, 30 Jul 2021 10:43:50 +0300 Subject: [PATCH 09/14] ref --- src/qComponents/hooks/index.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qComponents/hooks/index.ts b/src/qComponents/hooks/index.ts index bb5f5c9c..afe884bb 100644 --- a/src/qComponents/hooks/index.ts +++ b/src/qComponents/hooks/index.ts @@ -1,4 +1,2 @@ -import { useResizeListener } from './useResizeListener'; -import { useMediaQuery } from './useMediaQuery'; - -export { useResizeListener, useMediaQuery }; +export * from './useResizeListener'; +export * from './useMediaQuery'; From c14c4b9314162e79d588a8b13a23e480deeaa77c Mon Sep 17 00:00:00 2001 From: Tim Bochkarev Date: Mon, 9 Aug 2021 15:24:21 +0300 Subject: [PATCH 10/14] add live v-model to story --- stories/components/QDatePicker.stories.ts | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/stories/components/QDatePicker.stories.ts b/stories/components/QDatePicker.stories.ts index bb95d6f3..9d43659c 100644 --- a/stories/components/QDatePicker.stories.ts +++ b/stories/components/QDatePicker.stories.ts @@ -3,6 +3,7 @@ import type { Meta, Story } from '@storybook/vue3'; import { addMonths, startOfYesterday, subMonths, subWeeks } from 'date-fns'; import QDatePicker from '@/qComponents/QDatePicker'; +import QPopover from '@/qComponents/QPopover'; import type { QDatePickerPropModelValue, QDatePickerProps @@ -12,7 +13,7 @@ const now = new Date(); const storyMetadata: Meta = { title: 'Components/QDatePicker', - component: QDatePicker, + component: [QDatePicker, QPopover], argTypes: { 'v-model': { control: { type: 'none' } }, disabledValues: { @@ -91,6 +92,27 @@ const Template: Story = args => }, template: `
+

Value: + + + + outputFormat prop for type date doesn't work in template (always shows iso), better check the browser console + +

+
v-model: {{ JSON.stringify(state.value) }}
+
+
Date: Wed, 18 Aug 2021 10:46:23 +0300 Subject: [PATCH 11/14] fix story --- stories/components/QDatePicker.stories.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/stories/components/QDatePicker.stories.ts b/stories/components/QDatePicker.stories.ts index 9d43659c..d1964334 100644 --- a/stories/components/QDatePicker.stories.ts +++ b/stories/components/QDatePicker.stories.ts @@ -3,7 +3,6 @@ import type { Meta, Story } from '@storybook/vue3'; import { addMonths, startOfYesterday, subMonths, subWeeks } from 'date-fns'; import QDatePicker from '@/qComponents/QDatePicker'; -import QPopover from '@/qComponents/QPopover'; import type { QDatePickerPropModelValue, QDatePickerProps @@ -13,7 +12,7 @@ const now = new Date(); const storyMetadata: Meta = { title: 'Components/QDatePicker', - component: [QDatePicker, QPopover], + component: QDatePicker, argTypes: { 'v-model': { control: { type: 'none' } }, disabledValues: { @@ -106,13 +105,11 @@ const Template: Story = args => icon="q-icon-question-mark" /> - outputFormat prop for type date doesn't work in template (always shows iso), better check the browser console
v-model: {{ JSON.stringify(state.value) }}

-
Date: Thu, 16 Sep 2021 18:03:50 +0300 Subject: [PATCH 12/14] feat: allow left, right arrows for changing caret position --- .../QDatePicker/src/QDatePicker.vue | 33 ++++++++++++------- src/qComponents/QDatePicker/src/types.ts | 3 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index dcbc3cc7..6bfaae78 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -36,7 +36,7 @@
>(null); const qForm = inject>('qForm', null); const qFormItem = inject>('qFormItem', null); - const reference = - ref | HTMLElement>>(null); + const reference = ref>>(null); + const rangedReference = ref>(null); const state = reactive({ pickerVisible: false, @@ -522,7 +522,11 @@ export default defineComponent({ const handleKeydown = (event: KeyboardEvent): void => { // prevent letters input - if (event.key.match(/^(?!Enter$|Backspace$)[^0-9]+/g)) { + if ( + event.key.match( + /^(?!Enter$|Escape$|Tab$|Backspace$|ArrowLeft$|ArrowRight$)[^0-9]+/g + ) + ) { event.preventDefault(); } }; @@ -534,13 +538,21 @@ export default defineComponent({ } switch (e.key) { - case 'ArrowRight': case 'ArrowUp': - case 'ArrowLeft': case 'ArrowDown': { panel.value?.navigateDropdown(e); break; } + + case 'ArrowRight': + case 'ArrowLeft': { + const nativeInput = reference.value?.input ?? null; + if (nativeInput !== document.activeElement) { + panel.value?.navigateDropdown(e); + } + break; + } + case 'Escape': { state.pickerVisible = false; e.stopPropagation(); @@ -578,11 +590,9 @@ export default defineComponent({ const popperInit = (): void => { const panelEl = panel.value?.$el ?? null; - let referenceEl: HTMLElement; - if (reference.value instanceof HTMLElement) { - referenceEl = reference.value; - } else { - referenceEl = reference.value?.$el; + let referenceEl = reference.value?.$el; + if (isRanged.value) { + referenceEl = rangedReference.value; } state.popper = createPopper(referenceEl, panelEl, { @@ -734,6 +744,7 @@ export default defineComponent({ root, panel, reference, + rangedReference, isMobileView, isRanged, isPickerDisabled, diff --git a/src/qComponents/QDatePicker/src/types.ts b/src/qComponents/QDatePicker/src/types.ts index d787e382..a78e1683 100644 --- a/src/qComponents/QDatePicker/src/types.ts +++ b/src/qComponents/QDatePicker/src/types.ts @@ -89,7 +89,8 @@ interface QDatePickerInstance { state: QDatePickerState; root: Ref>; panel: Ref>; - reference: Ref | HTMLElement>>; + reference: Ref>>; + rangedReference: Ref>; isRanged: ComputedRef; isPickerDisabled: ComputedRef; isValueEmpty: ComputedRef; From a90cc4346faecdcb0483b29c963f4f73676c06e1 Mon Sep 17 00:00:00 2001 From: Tim Bochkarev Date: Thu, 16 Sep 2021 18:48:46 +0300 Subject: [PATCH 13/14] fix: types & firefox focus styles --- src/normalize.scss | 12 ------------ src/qComponents/QDatePicker/src/QDatePicker.vue | 6 +++--- src/qComponents/QDatePicker/src/types.ts | 4 ++-- src/vars.scss | 4 ++-- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/normalize.scss b/src/normalize.scss index 2425bd07..41e1144b 100644 --- a/src/normalize.scss +++ b/src/normalize.scss @@ -84,7 +84,6 @@ a { abbr[title] { text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ border-bottom: none; /* 1 */ } @@ -213,17 +212,6 @@ button::-moz-focus-inner, border-style: none; } -/** - * Restore the focus styles unset by the previous rule. - */ - -button:-moz-focusring, -[type='button']:-moz-focusring, -[type='reset']:-moz-focusring, -[type='submit']:-moz-focusring { - outline: 1px dotted ButtonText; -} - /** * Correct the padding in Firefox. */ diff --git a/src/qComponents/QDatePicker/src/QDatePicker.vue b/src/qComponents/QDatePicker/src/QDatePicker.vue index 6bfaae78..5e20b142 100644 --- a/src/qComponents/QDatePicker/src/QDatePicker.vue +++ b/src/qComponents/QDatePicker/src/QDatePicker.vue @@ -23,7 +23,7 @@ @keyup="handleKeyUp" @input="handleInput" @change="handleInputDateChange" - @keydown="handleKeydown" + @keydown="handleKeyDown" >