Align modals with refactored markup

This commit is contained in:
thePR0M3TH3AN committed 2025-10-10 13:12:19 -04:00
1 parent ea7df786d2
commit 22cb57ccec
4 files changed
+137 -58

No files matched your search

+20 -25
View File
@@ -345,16 +345,15 @@ export function buildHistoryCard({
metadataPreference,
}) {
const article = document.createElement("article");
article.className = "card flex flex-wrap items-stretch gap-6 p-5";
article.className = "watch-history-card";
article.dataset.pointerKey = item.pointerKey;
article.dataset.historyCard = "true";
if (video?.isPrivate) {
article.dataset.state = "private";
} else {
delete article.dataset.state;
article.classList.add("watch-history-card--private");
}
const primary = document.createElement("div");
primary.className = "flex min-w-0 flex-1 gap-4";
primary.className = "watch-history-card__primary";
const playbackData = (() => {
if (!video) {
@@ -373,8 +372,7 @@ export function buildHistoryCard({
const pointerVideoId = getPointerVideoId(video, item.pointer);
const thumbnailLink = document.createElement("a");
thumbnailLink.className =
"focus-ring block max-w-[14rem] flex-shrink-0 overflow-hidden rounded-lg bg-slate-900 transition hover:opacity-90";
thumbnailLink.className = "watch-history-card__thumbnail";
thumbnailLink.href = "#";
thumbnailLink.dataset.historyAction = "play";
thumbnailLink.dataset.pointerKey = item.pointerKey;
@@ -389,9 +387,8 @@ export function buildHistoryCard({
}
const thumbnailInner = document.createElement("div");
thumbnailInner.className = "aspect-video w-full overflow-hidden rounded-lg bg-slate-900";
thumbnailInner.className = "watch-history-card__thumbnailInner";
const thumbnailImg = document.createElement("img");
thumbnailImg.className = "h-full w-full object-cover";
thumbnailImg.alt = video?.title || "Video thumbnail";
const thumbnailSrc =
(video && typeof video.thumbnail === "string" && video.thumbnail.trim())
@@ -404,11 +401,10 @@ export function buildHistoryCard({
thumbnailLink.appendChild(thumbnailInner);
const details = document.createElement("div");
details.className = "flex min-w-0 flex-col justify-center gap-2";
details.className = "watch-history-card__details";
const titleLink = document.createElement("a");
titleLink.className =
"focus-ring text-left text-lg font-semibold text-white transition hover:text-blue-400";
titleLink.className = "watch-history-card__title";
titleLink.href = "#";
titleLink.dataset.historyAction = "play";
titleLink.dataset.pointerKey = item.pointerKey;
@@ -424,7 +420,7 @@ export function buildHistoryCard({
titleLink.textContent = video?.title || "Untitled video";
const created = document.createElement("p");
created.className = "text-sm text-gray-400";
created.className = "watch-history-card__created";
const createdAt = Number.isFinite(video?.created_at) ? video.created_at : null;
created.textContent = createdAt
? `Published ${formatRelativeTime(createdAt)}`
@@ -437,29 +433,27 @@ export function buildHistoryCard({
primary.appendChild(details);
const meta = document.createElement("div");
meta.className = "flex min-w-[14rem] flex-col justify-between gap-4";
meta.className = "watch-history-card__meta";
const watched = document.createElement("p");
watched.className = "text-sm text-gray-500";
watched.className = "watch-history-card__watched";
watched.textContent = item.watchedAt
? `Watched ${formatRelativeTime(item.watchedAt)}`
: "Watched time unavailable";
meta.appendChild(watched);
const creator = document.createElement("div");
creator.className = "flex items-center gap-3";
creator.className = "watch-history-card__creator";
const creatorAvatarButton = document.createElement("button");
creatorAvatarButton.type = "button";
creatorAvatarButton.className =
"focus-ring flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-slate-800";
creatorAvatarButton.className = "watch-history-card__creatorAvatar";
creatorAvatarButton.dataset.historyAction = "channel";
creatorAvatarButton.dataset.pointerKey = item.pointerKey;
if (video?.pubkey) {
creatorAvatarButton.dataset.author = video.pubkey;
}
const avatarImg = document.createElement("img");
avatarImg.className = "h-full w-full object-cover";
const avatarSrc =
(profile && typeof profile.picture === "string" && profile.picture.trim())
? profile.picture.trim()
@@ -470,8 +464,7 @@ export function buildHistoryCard({
const creatorNameButton = document.createElement("button");
creatorNameButton.type = "button";
creatorNameButton.className =
"focus-ring text-left text-sm font-medium text-gray-200 transition hover:text-blue-400";
creatorNameButton.className = "watch-history-card__creatorName";
creatorNameButton.dataset.historyAction = "channel";
creatorNameButton.dataset.pointerKey = item.pointerKey;
if (video?.pubkey) {
@@ -491,11 +484,12 @@ export function buildHistoryCard({
meta.appendChild(creator);
const actions = document.createElement("div");
actions.className = "flex flex-wrap items-center gap-3";
actions.className = "watch-history-card__actions";
const playButton = document.createElement("button");
playButton.type = "button";
playButton.className = "btn focus-ring";
playButton.className =
"watch-history-card__action watch-history-card__action--primary";
playButton.dataset.historyAction = "play";
playButton.dataset.pointerKey = item.pointerKey;
if (pointerVideoId) {
@@ -511,7 +505,7 @@ export function buildHistoryCard({
const channelButton = document.createElement("button");
channelButton.type = "button";
channelButton.className = "btn-ghost focus-ring";
channelButton.className = "watch-history-card__action";
channelButton.dataset.historyAction = "channel";
channelButton.dataset.pointerKey = item.pointerKey;
if (video?.pubkey) {
@@ -521,7 +515,8 @@ export function buildHistoryCard({
const removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.className = "btn-ghost focus-ring";
removeButton.className =
"watch-history-card__action watch-history-card__action--danger";
removeButton.dataset.variant = "danger";
removeButton.dataset.historyAction = "remove";
removeButton.dataset.pointerKey = item.pointerKey;
+21 -1
View File
@@ -43,6 +43,7 @@ export class EditModal {
this.root = null;
this.overlay = null;
this.panel = null;
this.form = null;
this.closeButton = null;
this.cancelButton = null;
@@ -140,7 +141,14 @@ export class EditModal {
}
cacheElements(context) {
this.overlay = context.querySelector("#editVideoModalOverlay") || null;
this.overlay =
context.querySelector("#editVideoModalOverlay") ||
context.querySelector(".bv-modal-backdrop") ||
null;
this.panel =
context.querySelector(".bv-modal__panel") ||
context.querySelector(".modal-content") ||
null;
this.form = context.querySelector("#editVideoForm") || null;
this.closeButton = context.querySelector("#closeEditVideoModal") || null;
this.cancelButton = context.querySelector("#cancelEditVideo") || null;
@@ -388,8 +396,19 @@ export class EditModal {
if (this.root) {
this.root.classList.remove("hidden");
this.root.setAttribute("aria-hidden", "false");
this.isVisible = true;
this.setGlobalModalState("editVideo", true);
const focusTarget =
this.form?.querySelector("[data-initial-focus]") ||
this.panel ||
this.root;
window.requestAnimationFrame(() => {
if (typeof focusTarget?.focus === "function") {
focusTarget.focus();
}
});
}
return true;
@@ -532,6 +551,7 @@ export class EditModal {
close({ emitCancel = false } = {}) {
if (this.root) {
this.root.classList.add("hidden");
this.root.setAttribute("aria-hidden", "true");
}
this.isVisible = false;
this.setGlobalModalState("editVideo", false);
+95 -31
View File
@@ -655,6 +655,9 @@ export class ProfileModalController {
};
this.profileModal = null;
this.profileModalRoot = null;
this.profileModalPanel = null;
this.profileModalBackdrop = null;
this.profileAvatar = null;
this.profileName = null;
this.profileNpub = null;
@@ -743,6 +746,7 @@ export class ProfileModalController {
this.boundKeydown = null;
this.boundFocusIn = null;
this.focusableElements = [];
this.focusTrapContainer = null;
this.profileSwitcherSelectionPubkey = null;
this.previouslyFocusedElement = null;
this.setActivePane(this.getActivePane());
@@ -771,7 +775,12 @@ export class ProfileModalController {
}
this.modalContainer.appendChild(template.content);
this.profileModalRoot = modalRoot;
this.profileModal = modalRoot;
this.profileModalPanel =
modalRoot.querySelector(".bv-modal__panel") || modalRoot;
this.profileModalBackdrop =
modalRoot.querySelector(".bv-modal-backdrop") || null;
this.cacheDomReferences();
this.registerEventListeners();
@@ -782,7 +791,12 @@ export class ProfileModalController {
}
cacheDomReferences() {
this.profileModal = document.getElementById("profileModal") || null;
this.profileModalRoot = document.getElementById("profileModal") || null;
this.profileModalPanel =
this.profileModalRoot?.querySelector(".bv-modal__panel") || null;
this.profileModalBackdrop =
this.profileModalRoot?.querySelector(".bv-modal-backdrop") || null;
this.profileModal = this.profileModalRoot;
this.closeButton = document.getElementById("closeProfileModal") || null;
this.logoutButton = document.getElementById("profileLogoutBtn") || null;
this.channelLink = document.getElementById("profileChannelLink") || null;
@@ -1284,7 +1298,6 @@ export class ProfileModalController {
normalizedSelection && normalizedPubkey === normalizedSelection;
if (isSelected) {
button.dataset.state = "active";
button.classList.add("ring-1", "ring-blue-400/60", "bg-blue-500/10");
button.setAttribute("aria-pressed", "true");
} else {
delete button.dataset.state;
@@ -3598,16 +3611,21 @@ export class ProfileModalController {
}
updateFocusTrap() {
if (!(this.profileModal instanceof HTMLElement)) {
const container =
this.profileModalPanel instanceof HTMLElement
? this.profileModalPanel
: this.profileModal instanceof HTMLElement
? this.profileModal
: null;
if (!container) {
this.focusableElements = [];
return;
}
const selector =
'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])';
const nodes = Array.from(
this.profileModal.querySelectorAll(selector),
);
const nodes = Array.from(container.querySelectorAll(selector));
this.focusableElements = nodes.filter((node) => {
if (!(node instanceof HTMLElement)) {
return false;
@@ -3625,11 +3643,19 @@ export class ProfileModalController {
return true;
});
this.bindFocusTrap();
this.bindFocusTrap(container);
}
bindFocusTrap() {
if (!(this.profileModal instanceof HTMLElement)) {
bindFocusTrap(container) {
const targetContainer =
container ||
(this.profileModalPanel instanceof HTMLElement
? this.profileModalPanel
: this.profileModal instanceof HTMLElement
? this.profileModal
: null);
if (!targetContainer) {
return;
}
@@ -3647,8 +3673,10 @@ export class ProfileModalController {
if (!this.focusableElements.length) {
event.preventDefault();
if (typeof this.profileModal.focus === "function") {
this.profileModal.focus();
const fallback =
this.profileModalPanel || this.profileModal || targetContainer;
if (typeof fallback?.focus === "function") {
fallback.focus();
}
return;
}
@@ -3658,7 +3686,7 @@ export class ProfileModalController {
const active = document.activeElement;
if (event.shiftKey) {
if (active === first || !this.profileModal.contains(active)) {
if (active === first || !targetContainer.contains(active)) {
event.preventDefault();
if (typeof last?.focus === "function") {
last.focus();
@@ -3678,22 +3706,36 @@ export class ProfileModalController {
if (!this.boundFocusIn) {
this.boundFocusIn = (event) => {
const modalRoot = this.profileModalRoot || this.profileModal;
if (
!this.profileModal ||
this.profileModal.classList.contains("hidden") ||
this.profileModal.contains(event.target)
!modalRoot ||
modalRoot.classList.contains("hidden") ||
modalRoot.contains(event.target)
) {
return;
}
const target = this.focusableElements[0] || this.profileModal;
if (typeof target?.focus === "function") {
target.focus();
const fallback =
this.focusableElements[0] ||
this.profileModalPanel ||
this.profileModal;
if (typeof fallback?.focus === "function") {
fallback.focus();
}
};
}
this.profileModal.addEventListener("keydown", this.boundKeydown);
if (
this.focusTrapContainer &&
this.focusTrapContainer !== targetContainer &&
this.boundKeydown
) {
this.focusTrapContainer.removeEventListener("keydown", this.boundKeydown);
}
targetContainer.addEventListener("keydown", this.boundKeydown);
this.focusTrapContainer = targetContainer;
document.addEventListener("focusin", this.boundFocusIn);
}
@@ -3707,11 +3749,14 @@ export class ProfileModalController {
document.activeElement instanceof HTMLElement
? document.activeElement
: null;
if (
activeElement &&
this.profileModal instanceof HTMLElement &&
this.profileModal.contains(activeElement)
) {
const modalRoot =
this.profileModalRoot instanceof HTMLElement
? this.profileModalRoot
: this.profileModal instanceof HTMLElement
? this.profileModal
: null;
if (activeElement && modalRoot && modalRoot.contains(activeElement)) {
this.previouslyFocusedElement = null;
} else {
this.previouslyFocusedElement = activeElement;
@@ -3757,16 +3802,26 @@ export class ProfileModalController {
}
open(pane = "account") {
if (!(this.profileModal instanceof HTMLElement)) {
const modalRoot =
this.profileModalRoot instanceof HTMLElement
? this.profileModalRoot
: this.profileModal instanceof HTMLElement
? this.profileModal
: null;
if (!modalRoot) {
return;
}
this.profileModal.classList.remove("hidden");
this.profileModal.setAttribute("aria-hidden", "false");
modalRoot.classList.remove("hidden");
modalRoot.setAttribute("aria-hidden", "false");
this.setGlobalModalState("profile", true);
this.selectPane(pane);
const focusTarget = this.focusableElements[0] || this.profileModal;
const focusTarget =
this.focusableElements[0] ||
this.profileModalPanel ||
modalRoot;
window.requestAnimationFrame(() => {
if (typeof focusTarget?.focus === "function") {
focusTarget.focus();
@@ -3779,15 +3834,22 @@ export class ProfileModalController {
options && typeof options === "object" ? options : {};
const modalElement =
this.profileModal instanceof HTMLElement ? this.profileModal : null;
this.profileModalRoot instanceof HTMLElement
? this.profileModalRoot
: this.profileModal instanceof HTMLElement
? this.profileModal
: null;
if (modalElement) {
modalElement.classList.add("hidden");
modalElement.setAttribute("aria-hidden", "true");
this.setGlobalModalState("profile", false);
if (this.boundKeydown) {
modalElement.removeEventListener("keydown", this.boundKeydown);
if (this.boundKeydown && this.focusTrapContainer) {
this.focusTrapContainer.removeEventListener(
"keydown",
this.boundKeydown,
);
}
}
@@ -3795,6 +3857,8 @@ export class ProfileModalController {
document.removeEventListener("focusin", this.boundFocusIn);
}
this.focusTrapContainer = null;
if (
this.boundProfileHistoryVisibility &&
typeof document !== "undefined" &&
+1 -1
View File
@@ -10,7 +10,7 @@ function resolveCardElement(trigger) {
if (!(trigger instanceof HTMLElement)) {
return null;
}
const candidate = trigger.closest(".video-card");
const candidate = trigger.closest("[data-history-card]");
return candidate instanceof HTMLElement ? candidate : null;
}