FIX: uses popper for cards

This commit is contained in:
Joffrey JAFFEUX 2023-01-30 21:12:30 +01:00 committed by GitHub
parent fda834d01c
commit 73488f2f33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 68 additions and 109 deletions

View File

@ -2,12 +2,13 @@ import { alias, match } from "@ember/object/computed";
import { schedule, throttle } from "@ember/runloop"; import { schedule, throttle } from "@ember/runloop";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import afterTransition from "discourse/lib/after-transition";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { wantsNewWindow } from "discourse/lib/intercept-click"; import { wantsNewWindow } from "discourse/lib/intercept-click";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
import { createPopper } from "@popperjs/core";
import { headerOffset } from "discourse/lib/offset-calculator";
const DEFAULT_SELECTOR = "#main-outlet"; const DEFAULT_SELECTOR = "#main-outlet";
@ -36,9 +37,10 @@ export default Mixin.create({
loading: null, loading: null,
cardTarget: null, cardTarget: null,
post: null, post: null,
isFixed: false,
isDocked: false, isDocked: false,
_popperReference: null,
_show(username, target, event) { _show(username, target, event) {
// No user card for anon // No user card for anon
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
@ -56,12 +58,6 @@ export default Mixin.create({
this.appEvents.trigger("card:show", username, target, event); this.appEvents.trigger("card:show", username, target, event);
const currentUsername = this.username;
if (username === currentUsername || this.loading === username) {
this._positionCard($(target));
return;
}
const closestArticle = target.closest("article"); const closestArticle = target.closest("article");
const postId = closestArticle?.dataset?.postId || null; const postId = closestArticle?.dataset?.postId || null;
const wasVisible = this.visible; const wasVisible = this.visible;
@ -88,6 +84,7 @@ export default Mixin.create({
this.appEvents.trigger("user-card:show", { username }); this.appEvents.trigger("user-card:show", { username });
this._showCallback(username, $(target)).then((user) => { this._showCallback(username, $(target)).then((user) => {
this.appEvents.trigger("user-card:after-show", { user }); this.appEvents.trigger("user-card:after-show", { user });
this._positionCard($(target), event);
}); });
// We bind scrolling on mobile after cards are shown to hide them if user scrolls // We bind scrolling on mobile after cards are shown to hide them if user scrolls
@ -100,7 +97,7 @@ export default Mixin.create({
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
afterTransition($(this.element), this._hide);
const id = this.elementId; const id = this.elementId;
const triggeringLinkClass = this.triggeringLinkClass; const triggeringLinkClass = this.triggeringLinkClass;
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`; const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
@ -168,7 +165,7 @@ export default Mixin.create({
}, },
_topicHeaderTrigger(username, target) { _topicHeaderTrigger(username, target) {
this.setProperties({ isFixed: true, isDocked: true }); this.setProperties({ isDocked: true });
return this._show(username, target); return this._show(username, target);
}, },
@ -188,104 +185,75 @@ export default Mixin.create({
}, },
_previewClick($target) { _previewClick($target) {
this.set("isFixed", true);
return this._show($target.text().replace(/^@/, ""), $target); return this._show($target.text().replace(/^@/, ""), $target);
}, },
_positionCard(target) { _positionCard(target, event) {
const rtl = $("html").css("direction") === "rtl"; this._popperReference?.destroy();
if (!target) {
return;
}
const width = $(this.element).width();
const height = 175;
const isFixed = this.isFixed;
const isDocked = this.isDocked;
let verticalAdjustments = 0;
schedule("afterRender", () => { schedule("afterRender", () => {
if (target) { if (!target) {
if (!this.site.mobileView) { return;
let position = target.offset(); }
if (target.parents(".d-header").length > 0) {
position.top = target.position().top;
}
if (position) { if (this.site.desktopView) {
position.bottom = "unset"; const avatarOverflowSize = 44;
this._popperReference = createPopper(target[0], this.element, {
placement: "right",
modifiers: [
{
name: "preventOverflow",
options: {
padding: {
top: headerOffset() + avatarOverflowSize,
right: 10,
bottom: 10,
left: 10,
},
},
},
{ name: "eventListeners", enabled: false },
{ name: "offset", options: { offset: [10, 10] } },
],
});
} else {
document.querySelector(".card-cloak")?.classList.remove("hidden");
this._popperReference = createPopper(target[0], this.element, {
modifiers: [
{ name: "eventListeners", enabled: false },
{
name: "computeStyles",
enabled: true,
fn({ state }) {
// mimics our modal top of the screen positioning
state.styles.popper = {
...state.styles.popper,
position: "fixed",
left: `${
(window.innerWidth - state.rects.popper.width) / 2
}px`,
top: "10%",
transform: "translateY(-10%)",
};
if (rtl) { return state;
// The site direction is rtl },
position.right = $(window).width() - position.left + 10; },
position.left = "auto"; ],
let overage = $(window).width() - 50 - (position.right + width); });
if (overage < 0) { }
position.right += overage;
position.top += target.height() + 48;
verticalAdjustments += target.height() + 48;
}
} else {
// The site direction is ltr
position.left += target.width() + 10;
let overage = $(window).width() - 50 - (position.left + width); this.element.classList.toggle("docked-card", this.isDocked);
if (overage < 0) {
position.left += overage;
position.top += target.height() + 48;
verticalAdjustments += target.height() + 48;
}
}
// It looks better to have the card aligned slightly higher // After the card is shown, focus on the first link
position.top -= 24; //
// note: we DO NOT use afterRender here cause _positionCard may
if (isFixed) { // run afterwards, if we allowed this to happen the usercard
position.top -= $("html").scrollTop(); // may be offscreen and we may scroll all the way to it on focus
//if content is fixed and will be cut off on the bottom, display it above... if (event?.pointerId === -1) {
if ( discourseLater(() => {
position.top + height + verticalAdjustments > this.element.querySelector("a")?.focus();
$(window).height() - 50 }, 350);
) {
position.bottom =
$(window).height() -
(target.offset().top - $("html").scrollTop());
if (verticalAdjustments > 0) {
position.bottom += 48;
}
position.top = "unset";
}
}
const avatarOverflowSize = 44;
if (isDocked && position.top < avatarOverflowSize) {
position.top = avatarOverflowSize;
}
$(this.element).css(position);
}
}
if (this.site.mobileView) {
$(".card-cloak").removeClass("hidden");
let position = target.offset();
position.top = "10%"; // match modal behaviour
position.left = 0;
$(this.element).css(position);
}
$(this.element).toggleClass("docked-card", isDocked);
// After the card is shown, focus on the first link
//
// note: we DO NOT use afterRender here cause _positionCard may
// run afterwards, if we allowed this to happen the usercard
// may be offscreen and we may scroll all the way to it on focus
if (event.pointerId === -1) {
discourseLater(() => {
const firstLink = this.element.querySelector("a");
firstLink && firstLink.focus();
}, 350);
}
} }
}); });
}, },
@ -307,7 +275,6 @@ export default Mixin.create({
loading: null, loading: null,
cardTarget: null, cardTarget: null,
post: null, post: null,
isFixed: false,
isDocked: false, isDocked: false,
}); });

View File

@ -37,14 +37,8 @@ $avatar_margin: -50px; // negative margin makes avatars extend above cards
color: var(--primary); color: var(--primary);
background: var(--secondary) center center; background: var(--secondary) center center;
background-size: cover; background-size: cover;
transition: opacity 0.2s, transform 0.2s;
opacity: 0;
outline: 2px solid transparent; outline: 2px solid transparent;
@include transform(scale(0.9));
&.show {
opacity: 1;
@include transform(scale(1));
}
.card-content { .card-content {
padding: 10px; padding: 10px;
background: rgba(var(--secondary-rgb), 0.85); background: rgba(var(--secondary-rgb), 0.85);

View File

@ -1,7 +1,6 @@
// shared styles for user and group cards // shared styles for user and group cards
.user-card, .user-card,
.group-card { .group-card {
position: absolute;
z-index: z("usercard"); z-index: z("usercard");
&.fixed { &.fixed {
position: fixed; position: fixed;

View File

@ -3,7 +3,6 @@ $avatar_width: 120px;
// shared styles for user and group cards // shared styles for user and group cards
.user-card, .user-card,
.group-card { .group-card {
position: fixed;
// mobile cards should always be on top of everything - 1102 // mobile cards should always be on top of everything - 1102
z-index: z("mobile-composer") + 2; z-index: z("mobile-composer") + 2;
max-width: 95vw; max-width: 95vw;