mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(portals): posters-only cover wall for movie and series grids (#1604)
* feat(portals): posters-only cover wall for movie and series grids Add `Settings.showCoverTitles` (Settings > General, default on). Turning it off drops the title row under VOD/series covers in catalog, favorites and recent grids and reveals the title as a bottom-gradient overlay on hover and keyboard focus, pinned open for items whose cover is missing or failed. `CoverTitlesService` is the single resolver: the opt-out AND a hover-capable pointer, so touch-only devices keep their titles. Live channel grids, search results, "recently added" rails and dashboard rails always keep labels. Catalog and collection cards become keyboard buttons (role, tabindex, aria-label, Enter/Space, focus ring) and poster alt text is the title. The default-on boolean coercion moves into `settings-opt-out.util.ts` because the settings store reached the max-lines limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): keep nested Remove key presses from activating the card Enter/Space on the content card's nested Remove button bubbled into the card's own key handlers: Enter opened the item before removing it and Space opened it while cancelling the removal. Only keys pressed on the card element itself now activate it. Regression spec added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): keep cover titles while an in-section search filters the grid The posters-only wall exempts search results because they are identified by the name the user typed; the category grid's own in-section filter is the same case, so `app-grid-list` now keeps the title row while its `searchTerm` is non-blank. Contract docs updated, regression spec added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): keep cover titles while the collection tab search is active The unified favorites/recent tab filters by its own search term, so its matches are identified by name like every other search result. The tab now opts its cards out of the posters-only wall while the term is non-blank. Contract docs updated, regression spec added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): move the card Remove control out of the button surface An interactive control nested inside a role="button" is an invalid accessibility structure. The content card's activation surface is now its own inner element and the Remove button a sibling positioned over the poster corner, labelled by its tooltip text. Spec asserts the control is never a descendant of the button. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): draw the collection card focus ring where it is not clipped The card's overflow: hidden clipped an outline drawn on the inner activation surface on every edge, so keyboard users saw no focus indication. The ring now sits on the outer card via :has(> .content-card__activation:focus-visible). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(portals): detect any hover-capable pointer for the posters-only wall `hover` describes only the primary pointer, so a touch-first tablet with a mouse or hover-capable stylus attached lost the wall. The resolver now reads `(any-hover: hover)`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
40092f6efc
commit
ef3f98d026
49 files changed
+1297
-106
No files matched your search
@@ -883,6 +883,48 @@ type XtreamRawEpgListing = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
test('@xtream posters-only wall — hides VOD titles behind a hover caption after Save', async ({
|
||||
page,
|
||||
}) => {
|
||||
await addXtreamPortal(page);
|
||||
await page.locator('.context-panel .category-item').first().click();
|
||||
|
||||
const grid = page.locator('app-grid-list');
|
||||
const firstCard = grid.locator('mat-card').first();
|
||||
await expect(firstCard).toBeVisible({ timeout: 10_000 });
|
||||
await expect(firstCard.locator('.title')).toBeVisible();
|
||||
await expect(grid).not.toHaveClass(/grid-list--posters-only/);
|
||||
|
||||
const catalogUrl = page.url();
|
||||
await page.goto('/workspace/settings/general');
|
||||
const toggle = page.locator('[data-test-id="cover-titles-toggle"]');
|
||||
await expect(toggle.locator('input')).toBeChecked();
|
||||
await toggle.click();
|
||||
const saveButton = page.locator('[data-test-id="save-settings"]');
|
||||
await saveButton.click();
|
||||
await expect(saveButton).toBeHidden();
|
||||
|
||||
await page.goto(catalogUrl);
|
||||
await page.locator('.context-panel .category-item').first().click();
|
||||
await expect(firstCard).toBeVisible({ timeout: 10_000 });
|
||||
await expect(grid).toHaveClass(/grid-list--posters-only/);
|
||||
await expect(firstCard.locator('.title')).toHaveCount(0);
|
||||
|
||||
// The caption is a hover/focus reveal: hidden at rest, shown when the
|
||||
// pointer or keyboard focus lands on the card.
|
||||
const caption = firstCard.locator('.cover-title-overlay');
|
||||
await expect(caption).toHaveCSS('opacity', '0');
|
||||
await firstCard.hover();
|
||||
await expect(caption).toHaveCSS('opacity', '1');
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(caption).toHaveCSS('opacity', '0');
|
||||
await firstCard.focus();
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await expect(firstCard).toBeFocused();
|
||||
await expect(caption).toHaveCSS('opacity', '1');
|
||||
});
|
||||
|
||||
async function getEpgFixtureStream(
|
||||
request: APIRequestContext
|
||||
): Promise<XtreamLiveStream> {
|
||||
|
||||
Reference in new issue
Block a user