From b6ebbe3dbdaaae4e9c0dc16ba44380e5930e2c14 Mon Sep 17 00:00:00 2001 From: Vladimir Kharlampidi Date: Thu, 29 Sep 2022 14:08:30 +0300 Subject: [PATCH] feat(popover): m3 styles, new `angle` (corner) parameter --- src/core/components/popover/popover-class.js | 152 ++++++++---------- src/core/components/popover/popover-ios.less | 50 ------ src/core/components/popover/popover-vars.less | 16 +- src/core/components/popover/popover.d.ts | 2 + src/core/components/popover/popover.js | 1 + src/core/components/popover/popover.less | 56 ++++++- src/react/components/popover.jsx | 3 + src/svelte/components/popover.svelte | 2 + src/vue/components/popover.vue | 6 + 9 files changed, 139 insertions(+), 149 deletions(-) diff --git a/src/core/components/popover/popover-class.js b/src/core/components/popover/popover-class.js index e77c0708c5..2e0d49a616 100644 --- a/src/core/components/popover/popover-class.js +++ b/src/core/components/popover/popover-class.js @@ -64,7 +64,7 @@ class Popover extends Modal { // Find Angle let $angleEl; - if ($el.find('.popover-angle').length === 0) { + if ($el.find('.popover-angle').length === 0 && popover.params.angle) { $angleEl = $('
'); $el.prepend($angleEl); } else { @@ -191,16 +191,17 @@ class Popover extends Modal { let angleSize = 0; let angleLeft; let angleTop; - if (app.theme === 'ios') { + const hasAngle = $angleEl.length > 0; + const angleMin = app.theme === 'ios' ? 13 : 24; + if (hasAngle) { $angleEl.removeClass('on-left on-right on-top on-bottom').css({ left: '', top: '' }); angleSize = $angleEl.width() / 2; - } else { - $el - .removeClass( - 'popover-on-left popover-on-right popover-on-top popover-on-bottom popover-on-middle', - ) - .css({ left: '', top: '' }); } + $el + .removeClass( + 'popover-on-left popover-on-right popover-on-top popover-on-bottom popover-on-middle', + ) + .css({ left: '', top: '' }); let targetWidth; let targetHeight; @@ -234,102 +235,79 @@ class Popover extends Modal { let [left, top, diff] = [0, 0, 0]; // Top Position const forcedPosition = verticalPosition === 'auto' ? false : verticalPosition; - let position = forcedPosition || (app.theme === 'md' ? 'bottom' : 'top'); - if (app.theme === 'md') { - if ( - forcedPosition === 'bottom' || - (!forcedPosition && height < app.height - targetOffsetTop - targetHeight) - ) { - // On bottom - position = 'bottom'; - top = targetOffsetTop + targetHeight; - } else if ( - forcedPosition === 'top' || - (!forcedPosition && height < targetOffsetTop - safeAreaTop) - ) { - // On top - top = targetOffsetTop - height; - position = 'top'; - } else { - // On middle - position = 'middle'; - top = targetHeight / 2 + targetOffsetTop - height / 2; - } - top = Math.max(8, Math.min(top, app.height - height - 8)); + let position = forcedPosition || 'top'; - // Horizontal Position - let hPosition; - if (targetOffsetLeft < app.width / 2) { - hPosition = 'right'; - left = position === 'middle' ? targetOffsetLeft + targetWidth : targetOffsetLeft; - } else { - hPosition = 'left'; - left = - position === 'middle' ? targetOffsetLeft - width : targetOffsetLeft + targetWidth - width; - } - left = Math.max(8, Math.min(left, app.width - width - 8 - safeAreaRight), safeAreaLeft); - $el.addClass(`popover-on-${position} popover-on-${hPosition}`); + if ( + forcedPosition === 'top' || + (!forcedPosition && height + angleSize < targetOffsetTop - safeAreaTop) + ) { + // On top + top = targetOffsetTop - height - angleSize; + } else if ( + forcedPosition === 'bottom' || + (!forcedPosition && height + angleSize < app.height - targetOffsetTop - targetHeight) + ) { + // On bottom + position = 'bottom'; + top = targetOffsetTop + targetHeight + angleSize; } else { - // ios - if ( - forcedPosition === 'top' || - (!forcedPosition && height + angleSize < targetOffsetTop - safeAreaTop) - ) { - // On top - top = targetOffsetTop - height - angleSize; - } else if ( - forcedPosition === 'bottom' || - (!forcedPosition && height + angleSize < app.height - targetOffsetTop - targetHeight) - ) { - // On bottom - position = 'bottom'; - top = targetOffsetTop + targetHeight + angleSize; - } else { - // On middle - position = 'middle'; - top = targetHeight / 2 + targetOffsetTop - height / 2; - diff = top; - top = Math.max(5, Math.min(top, app.height - height - 5)); - diff -= top; + // On middle + position = 'middle'; + top = targetHeight / 2 + targetOffsetTop - height / 2; + diff = top; + top = Math.max(5, Math.min(top, app.height - height - 5)); + diff -= top; + } + + // Horizontal Position + if (position === 'top' || position === 'bottom') { + left = targetWidth / 2 + targetOffsetLeft - width / 2; + diff = left; + left = Math.max(5, Math.min(left, app.width - width - 5)); + if (safeAreaLeft) { + left = Math.max(left, safeAreaLeft); + } + if (safeAreaRight && left + width > app.width - 5 - safeAreaRight) { + left = app.width - 5 - safeAreaRight - width; } - // Horizontal Position - if (position === 'top' || position === 'bottom') { - left = targetWidth / 2 + targetOffsetLeft - width / 2; - diff = left; - left = Math.max(5, Math.min(left, app.width - width - 5)); - if (safeAreaLeft) { - left = Math.max(left, safeAreaLeft); - } - if (safeAreaRight && left + width > app.width - 5 - safeAreaRight) { - left = app.width - 5 - safeAreaRight - width; - } + diff -= left; + if (hasAngle) { if (position === 'top') { $angleEl.addClass('on-bottom'); } if (position === 'bottom') { $angleEl.addClass('on-top'); } - diff -= left; angleLeft = width / 2 - angleSize + diff; - angleLeft = Math.max(Math.min(angleLeft, width - angleSize * 2 - 13), 13); + angleLeft = Math.max(Math.min(angleLeft, width - angleSize * 2 - angleMin), angleMin); $angleEl.css({ left: `${angleLeft}px` }); - } else if (position === 'middle') { - left = targetOffsetLeft - width - angleSize; - $angleEl.addClass('on-right'); - if (left < 5 || left + width + safeAreaRight > app.width || left < safeAreaLeft) { - if (left < 5) left = targetOffsetLeft + targetWidth + angleSize; - if (left + width + safeAreaRight > app.width) - left = app.width - width - 5 - safeAreaRight; - if (left < safeAreaLeft) left = safeAreaLeft; - $angleEl.removeClass('on-right').addClass('on-left'); - } + } + } else if (position === 'middle') { + left = targetOffsetLeft - width - angleSize; + if (hasAngle) $angleEl.addClass('on-right'); + if (left < 5 || left + width + safeAreaRight > app.width || left < safeAreaLeft) { + if (left < 5) left = targetOffsetLeft + targetWidth + angleSize; + if (left + width + safeAreaRight > app.width) left = app.width - width - 5 - safeAreaRight; + if (left < safeAreaLeft) left = safeAreaLeft; + if (hasAngle) $angleEl.removeClass('on-right').addClass('on-left'); + } + if (hasAngle) { angleTop = height / 2 - angleSize + diff; - angleTop = Math.max(Math.min(angleTop, height - angleSize * 2 - 13), 13); + angleTop = Math.max(Math.min(angleTop, height - angleSize * 2 - angleMin), angleMin); $angleEl.css({ top: `${angleTop}px` }); } } + // Horizontal Position + let hPosition; + if (targetOffsetLeft < app.width / 2) { + hPosition = 'right'; + } else { + hPosition = 'left'; + } + $el.addClass(`popover-on-${position} popover-on-${hPosition}`); + // Apply Styles $el.css({ top: `${top}px`, left: `${left}px` }); } diff --git a/src/core/components/popover/popover-ios.less b/src/core/components/popover/popover-ios.less index 1c4342a282..bc054898be 100644 --- a/src/core/components/popover/popover-ios.less +++ b/src/core/components/popover/popover-ios.less @@ -3,54 +3,4 @@ transform: none; transition-property: opacity; } - .popover-angle { - width: 26px; - height: 26px; - position: absolute; - left: -26px; - top: 0; - z-index: 100; - overflow: hidden; - &:after { - content: ''; - background: var(--f7-popover-bg-color); - width: 26px; - height: 26px; - position: absolute; - left: 0; - top: 0; - border-radius: 3px; - transform: rotate(45deg); - } - &.on-left { - left: -26px; - &:after { - left: 19px; - top: 0; - } - } - &.on-right { - left: 100%; - &:after { - left: -19px; - top: 0; - } - } - &.on-top { - left: 0; - top: -26px; - &:after { - left: 0; - top: 19px; - } - } - &.on-bottom { - left: 0; - top: 100%; - &:after { - left: 0; - top: -19px; - } - } - } } diff --git a/src/core/components/popover/popover-vars.less b/src/core/components/popover/popover-vars.less index 7bbf84b297..ce3841855e 100644 --- a/src/core/components/popover/popover-vars.less +++ b/src/core/components/popover/popover-vars.less @@ -3,8 +3,8 @@ } .ios-vars({ --f7-popover-border-radius: 13px; - --f7-popover-box-shadow: none; --f7-popover-actions-icon-size: 28px; + --f7-popover-transition-timing-function: initial; .light-vars({ --f7-popover-bg-color: rgba(255,255,255,0.95); --f7-popover-actions-label-text-color: rgba(0,0,0,0.45); @@ -15,15 +15,9 @@ }); }); .md-vars({ - --f7-popover-border-radius: 4px; - --f7-popover-box-shadow: var(--f7-elevation-8); + --f7-popover-transition-timing-function: cubic-bezier(0, 0.8, 0.34, 1); + --f7-popover-border-radius: 28px; --f7-popover-actions-icon-size: 24px; - .light-vars({ - --f7-popover-bg-color: #fff; - --f7-popover-actions-label-text-color: rgba(0,0,0,0.54); - }); - .dark-vars({ - --f7-popover-bg-color: #1c1c1d; - --f7-popover-actions-label-text-color: rgba(255,255,255,0.54); - }); + --f7-popover-bg-color: var(--f7-md-surface-3); + --f7-popover-actions-label-text-color: var(--f7-md-on-surface-variant); }); diff --git a/src/core/components/popover/popover.d.ts b/src/core/components/popover/popover.d.ts index d1848f0327..1ce059045b 100644 --- a/src/core/components/popover/popover.d.ts +++ b/src/core/components/popover/popover.d.ts @@ -23,6 +23,8 @@ export namespace Popover { el?: HTMLElement | CSSSelector; /** Full Popover HTML layout string. Can be useful if you want to create Popover element dynamically. */ content?: string; + /** Enables Popover angle/corner. (default true) */ + angle?: boolean; /** Enables Popover backdrop (dark semi transparent layer behind). (default true) */ backdrop?: boolean; /** Backdrop element to share across instances */ diff --git a/src/core/components/popover/popover.js b/src/core/components/popover/popover.js index b4a58e3063..1d9423152b 100644 --- a/src/core/components/popover/popover.js +++ b/src/core/components/popover/popover.js @@ -8,6 +8,7 @@ export default { params: { popover: { verticalPosition: 'auto', + angle: true, backdrop: true, backdropEl: undefined, backdropUnique: false, diff --git a/src/core/components/popover/popover.less b/src/core/components/popover/popover.less index 10de7e8a97..40011cf367 100644 --- a/src/core/components/popover/popover.less +++ b/src/core/components/popover/popover.less @@ -5,6 +5,7 @@ z-index: 13500; } } + .popover { width: var(--f7-popover-width); z-index: 13500; @@ -17,7 +18,6 @@ transition-duration: 300ms; background-color: var(--f7-popover-bg-color); border-radius: var(--f7-popover-border-radius); - box-shadow: var(--f7-popover-box-shadow); will-change: transform, opacity; .list { margin: 0; @@ -69,6 +69,10 @@ transition-duration: 0ms; } } +.popover, +.popover-backdrop { + transition-timing-function: var(--f7-popover-transition-timing-function); +} .popover-inner { .scrollable(); } @@ -96,6 +100,56 @@ .hairline-remove(bottom); } } +.popover-angle { + width: 26px; + height: 26px; + position: absolute; + left: -26px; + top: 0; + z-index: 100; + overflow: hidden; + &:after { + content: ''; + background: var(--f7-popover-bg-color); + width: 26px; + height: 26px; + position: absolute; + left: 0; + top: 0; + border-radius: 3px; + transform: rotate(45deg); + } + &.on-left { + left: -26px; + &:after { + left: 19px; + top: 0; + } + } + &.on-right { + left: 100%; + &:after { + left: -19px; + top: 0; + } + } + &.on-top { + left: 0; + top: -26px; + &:after { + left: 0; + top: 19px; + } + } + &.on-bottom { + left: 0; + top: 100%; + &:after { + left: 0; + top: -19px; + } + } +} .if-ios-theme({ @import './popover-ios.less'; }); diff --git a/src/react/components/popover.jsx b/src/react/components/popover.jsx index d5b49223e4..7edfb47308 100644 --- a/src/react/components/popover.jsx +++ b/src/react/components/popover.jsx @@ -17,6 +17,7 @@ import { Popover } from 'framework7/types'; opened? : boolean animate? : boolean targetEl? : string | object + angle? : boolean backdrop? : boolean backdropEl? : string | object closeByBackdropClick? : boolean @@ -43,6 +44,7 @@ const Popover = forwardRef((props, ref) => { opened, animate, targetEl, + angle, backdrop, backdropEl, closeByBackdropClick, @@ -108,6 +110,7 @@ const Popover = forwardRef((props, ref) => { if ('closeByBackdropClick' in props) popoverParams.closeByBackdropClick = closeByBackdropClick; if ('closeByOutsideClick' in props) popoverParams.closeByOutsideClick = closeByOutsideClick; if ('closeOnEscape' in props) popoverParams.closeOnEscape = closeOnEscape; + if ('angle' in props) popoverParams.angle = angle; if ('backdrop' in props) popoverParams.backdrop = backdrop; if ('backdropEl' in props) popoverParams.backdropEl = backdropEl; if ('animate' in props) popoverParams.animate = animate; diff --git a/src/svelte/components/popover.svelte b/src/svelte/components/popover.svelte index c425c0c0e8..6e1bda376d 100644 --- a/src/svelte/components/popover.svelte +++ b/src/svelte/components/popover.svelte @@ -14,6 +14,7 @@ export let opened = undefined; export let animate = undefined; export let targetEl = undefined; + export let angle = undefined; export let backdrop = undefined; export let backdropEl = undefined; export let closeByBackdropClick = undefined; @@ -93,6 +94,7 @@ if (typeof closeByOutsideClick !== 'undefined') params.closeByOutsideClick = closeByOutsideClick; if (typeof closeOnEscape !== 'undefined') params.closeOnEscape = closeOnEscape; + if (typeof angle !== 'undefined') params.angle = angle; if (typeof backdrop !== 'undefined') params.backdrop = backdrop; if (typeof backdropEl !== 'undefined') params.backdropEl = backdropEl; if (typeof containerEl !== 'undefined') params.containerEl = containerEl; diff --git a/src/vue/components/popover.vue b/src/vue/components/popover.vue index f9ff9bb2dd..03b1ac6059 100644 --- a/src/vue/components/popover.vue +++ b/src/vue/components/popover.vue @@ -23,6 +23,10 @@ export default { type: [String, Object], default: undefined, }, + angle: { + type: Boolean, + default: undefined, + }, backdrop: { type: Boolean, default: undefined, @@ -109,6 +113,7 @@ export default { closeByBackdropClick, closeByOutsideClick, closeOnEscape, + angle, backdrop, backdropEl, containerEl, @@ -120,6 +125,7 @@ export default { if (typeof closeByOutsideClick !== 'undefined') popoverParams.closeByOutsideClick = closeByOutsideClick; if (typeof closeOnEscape !== 'undefined') popoverParams.closeOnEscape = closeOnEscape; + if (typeof angle !== 'undefined') popoverParams.angle = angle; if (typeof backdrop !== 'undefined') popoverParams.backdrop = backdrop; if (typeof backdropEl !== 'undefined') popoverParams.backdropEl = backdropEl; if (typeof containerEl !== 'undefined') popoverParams.containerEl = containerEl;