mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
feat(ui): one dialog frame, a shared dialog shell and three width presets (#1942)
* feat(ui): one dialog frame, a shared dialog shell and three width presets
Every dialog gets the kit frame from m3-theme.scss: a 16px panel on the
widget surface with a hairline border and the popover shadow, over a tinted
backdrop blurred at 6px. The command palette and the programme dialog keep
their own surface through their panel classes.
`app-dialog-shell` (@iptvnator/ui/components) draws the header (optional
icon tile, the heading as mat-dialog-title, subtitle, a header-end slot and
a close button), the body and a footer that renders only when an element
marked appDialogFooter wraps the actions. The close button is last in the
DOM, so first-tabbable autofocus lands in the body. Footer buttons take the
kit look: ghost, accent (new accent-button mixin) or secondary; destructive
buttons keep their tint and get the kit's 10px radius.
`dialogSize('sm' | 'md' | 'lg')` owns the width and viewport limits with a
type-only MatDialogConfig import; MAT_DIALOG_DEFAULT_OPTIONS is deliberately
not provided at the root, which would put Material's dialog back on the
startup path.
Migrated: the confirm dialog (sm), the add-playlist dialog (lg) and the
keyboard shortcuts dialog (lg, close button only). Slot wrappers render as
display: contents, because a control-flow block at the top level of the
content is projected into the body, not into a named slot.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(ui): name the dialog shell's close button "Close dialog"
A confirm dialog opened with a "Close" dismiss (the download recovery
prompt, the VOD download prompt) had two buttons named "Close" once the
shell added its header ×, and the Electron download-recovery test failed
on a strict-mode violation for `getByRole('button', { name: 'Close' })`.
The × is now named by the new `CLOSE_DIALOG` key ("Close dialog") in every
locale, so a footer "Close" stays the only button of that name; the same
holds for the dialogs PR 3 migrates, whose E2E flows select "Close".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(ui): let short windows scroll the whole dialog shell
With the shell capped at the viewport, a stacked footer (the add-playlist
Xtream mode has five rows) left the body a 40px sliver at 360×480. Under
600px of viewport height the cap comes off and Material's surface scrolls
the whole dialog as the single scroll owner, which is how the dialog
behaved before the shell.
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
9b5586d328
commit
2139ddd5c0
47 files changed
+904
-451
No files matched your search
+67
-102
@@ -1,105 +1,70 @@
|
||||
<section class="shortcuts-dialog">
|
||||
<header class="shortcuts-dialog__header">
|
||||
<div class="shortcuts-dialog__title-block">
|
||||
<span class="shortcuts-dialog__hero-icon" aria-hidden="true">
|
||||
<mat-icon>keyboard</mat-icon>
|
||||
</span>
|
||||
<div>
|
||||
<h2 mat-dialog-title>
|
||||
{{ 'WORKSPACE.SHORTCUTS.TITLE' | translate }}
|
||||
</h2>
|
||||
<p>{{ 'WORKSPACE.SHORTCUTS.SUBTITLE' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts-dialog__header-actions">
|
||||
<span class="shortcuts-dialog__platform">
|
||||
<mat-icon aria-hidden="true">{{ data.platformIcon }}</mat-icon>
|
||||
{{ data.platformLabelKey | translate }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
class="shortcuts-dialog__close"
|
||||
[attr.aria-label]="'CLOSE' | translate"
|
||||
(click)="close()"
|
||||
>
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<app-dialog-shell
|
||||
icon="keyboard"
|
||||
[heading]="'WORKSPACE.SHORTCUTS.TITLE' | translate"
|
||||
[subtitle]="'WORKSPACE.SHORTCUTS.SUBTITLE' | translate"
|
||||
>
|
||||
<span class="shortcuts-dialog__platform" appDialogHeaderEnd>
|
||||
<mat-icon aria-hidden="true">{{ data.platformIcon }}</mat-icon>
|
||||
{{ data.platformLabelKey | translate }}
|
||||
</span>
|
||||
<div class="shortcuts-dialog__groups">
|
||||
@for (group of groups; track group.id) {
|
||||
<section class="shortcuts-dialog__group">
|
||||
<header class="shortcuts-dialog__group-header">
|
||||
<span
|
||||
class="shortcuts-dialog__group-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<mat-icon>{{ group.icon }}</mat-icon>
|
||||
</span>
|
||||
<h3>{{ group.labelKey | translate }}</h3>
|
||||
</header>
|
||||
|
||||
<mat-dialog-content class="shortcuts-dialog__content">
|
||||
<div class="shortcuts-dialog__groups">
|
||||
@for (group of groups; track group.id) {
|
||||
<section class="shortcuts-dialog__group">
|
||||
<header class="shortcuts-dialog__group-header">
|
||||
<span
|
||||
class="shortcuts-dialog__group-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<mat-icon>{{ group.icon }}</mat-icon>
|
||||
</span>
|
||||
<h3>{{ group.labelKey | translate }}</h3>
|
||||
</header>
|
||||
|
||||
<div class="shortcuts-dialog__rows">
|
||||
@for (item of group.items; track item.id) {
|
||||
<article class="shortcuts-dialog__row">
|
||||
<span
|
||||
class="shortcuts-dialog__action-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<mat-icon>{{ item.icon }}</mat-icon>
|
||||
</span>
|
||||
<span class="shortcuts-dialog__label">
|
||||
{{ item.labelKey | translate }}
|
||||
</span>
|
||||
<span class="shortcuts-dialog__chords">
|
||||
@for (
|
||||
chord of item.chords;
|
||||
track chord.id
|
||||
) {
|
||||
<span
|
||||
class="shortcuts-dialog__chord"
|
||||
[attr.aria-label]="chord.ariaLabel"
|
||||
>
|
||||
@for (
|
||||
key of chord.keys;
|
||||
track key.id
|
||||
) {
|
||||
<kbd
|
||||
[attr.aria-label]="
|
||||
key.ariaLabel
|
||||
"
|
||||
[class.shortcuts-dialog__keycap--modifier]="
|
||||
key.isModifier
|
||||
"
|
||||
<div class="shortcuts-dialog__rows">
|
||||
@for (item of group.items; track item.id) {
|
||||
<article class="shortcuts-dialog__row">
|
||||
<span
|
||||
class="shortcuts-dialog__action-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<mat-icon>{{ item.icon }}</mat-icon>
|
||||
</span>
|
||||
<span class="shortcuts-dialog__label">
|
||||
{{ item.labelKey | translate }}
|
||||
</span>
|
||||
<span class="shortcuts-dialog__chords">
|
||||
@for (chord of item.chords; track chord.id) {
|
||||
<span
|
||||
class="shortcuts-dialog__chord"
|
||||
[attr.aria-label]="chord.ariaLabel"
|
||||
>
|
||||
@for (key of chord.keys; track key.id) {
|
||||
<kbd
|
||||
[attr.aria-label]="
|
||||
key.ariaLabel
|
||||
"
|
||||
[class.shortcuts-dialog__keycap--modifier]="
|
||||
key.isModifier
|
||||
"
|
||||
>
|
||||
{{ key.label }}
|
||||
</kbd>
|
||||
@if (!$last) {
|
||||
<span
|
||||
class="shortcuts-dialog__joiner"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ key.label }}
|
||||
</kbd>
|
||||
@if (!$last) {
|
||||
<span
|
||||
class="shortcuts-dialog__joiner"
|
||||
aria-hidden="true"
|
||||
>
|
||||
+
|
||||
</span>
|
||||
}
|
||||
+
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end" class="shortcuts-dialog__actions">
|
||||
<button type="button" mat-button (click)="close()">
|
||||
{{ 'CLOSE' | translate }}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</section>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
</app-dialog-shell>
|
||||
-124
@@ -1,9 +1,5 @@
|
||||
:host {
|
||||
display: block;
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.shortcuts-dialog {
|
||||
--shortcut-surface: var(--app-widget-bg, #1e2330);
|
||||
--shortcut-surface-raised: var(--app-widget-header-bg, #232840);
|
||||
--shortcut-text: var(--app-heading-color, #d8dce8);
|
||||
@@ -26,80 +22,6 @@
|
||||
white 12%
|
||||
);
|
||||
--shortcut-key-shadow: color-mix(in srgb, black 24%, transparent);
|
||||
|
||||
color: var(--shortcut-text);
|
||||
background:
|
||||
linear-gradient(
|
||||
145deg,
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--shortcut-accent) 8%,
|
||||
var(--shortcut-surface) 92%
|
||||
),
|
||||
var(--shortcut-surface) 42%
|
||||
),
|
||||
var(--shortcut-surface);
|
||||
border: 1px solid var(--shortcut-border);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 22px 70px color-mix(in srgb, black 34%, transparent);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
padding: 20px 22px 16px;
|
||||
border-bottom: 1px solid var(--shortcut-border);
|
||||
}
|
||||
|
||||
.shortcuts-dialog__title-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__hero-icon {
|
||||
display: inline-grid;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
color: var(--shortcut-accent);
|
||||
background: color-mix(in srgb, var(--shortcut-accent) 14%, transparent);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--shortcut-accent) 28%, transparent);
|
||||
border-radius: 8px;
|
||||
|
||||
mat-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcuts-dialog__header h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1.08rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__header p {
|
||||
margin: 5px 0 0;
|
||||
color: var(--shortcut-muted);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__header-actions {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__platform {
|
||||
@@ -128,15 +50,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.shortcuts-dialog__close {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__content {
|
||||
max-height: min(68vh, 670px);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__groups {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -288,32 +201,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
.shortcuts-dialog__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 9px 14px 14px;
|
||||
border-top: 1px solid var(--shortcut-border);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.shortcuts-dialog {
|
||||
animation: shortcuts-dialog-enter 160ms ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.shortcuts-dialog__header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
padding: 16px 16px 12px;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__header-actions {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.shortcuts-dialog__groups {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -328,15 +216,3 @@
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shortcuts-dialog-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px) scale(0.985);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
+11
-14
@@ -1,13 +1,12 @@
|
||||
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogModule,
|
||||
MatDialogRef,
|
||||
} from '@angular/material/dialog';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { KeyboardShortcutDisplayGroup } from '@iptvnator/portal/shared/util';
|
||||
import {
|
||||
DialogHeaderEndDirective,
|
||||
DialogShellComponent,
|
||||
} from '@iptvnator/ui/components';
|
||||
|
||||
export interface WorkspaceKeyboardShortcutsDialogData {
|
||||
groups: readonly KeyboardShortcutDisplayGroup[];
|
||||
@@ -17,21 +16,19 @@ export interface WorkspaceKeyboardShortcutsDialogData {
|
||||
|
||||
@Component({
|
||||
selector: 'app-workspace-keyboard-shortcuts-dialog',
|
||||
imports: [MatButtonModule, MatDialogModule, MatIconModule, TranslatePipe],
|
||||
imports: [
|
||||
DialogHeaderEndDirective,
|
||||
DialogShellComponent,
|
||||
MatIconModule,
|
||||
TranslatePipe,
|
||||
],
|
||||
templateUrl: './workspace-keyboard-shortcuts-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './workspace-keyboard-shortcuts-dialog.component.scss',
|
||||
})
|
||||
export class WorkspaceKeyboardShortcutsDialogComponent {
|
||||
private readonly dialogRef = inject(
|
||||
MatDialogRef<WorkspaceKeyboardShortcutsDialogComponent>
|
||||
);
|
||||
readonly data =
|
||||
inject<WorkspaceKeyboardShortcutsDialogData>(MAT_DIALOG_DATA);
|
||||
|
||||
readonly groups = this.data.groups;
|
||||
|
||||
close(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -1,4 +1,5 @@
|
||||
import { DestroyRef, Injectable, inject } from '@angular/core';
|
||||
import { dialogSize } from '@iptvnator/ui/components';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import {
|
||||
@@ -21,9 +22,12 @@ export class WorkspaceKeyboardShortcutsService {
|
||||
private readonly runtime = inject(RuntimeCapabilitiesService);
|
||||
// Root-provided; optional keeps standalone unit tests light. The help key must not open a dialog over the modal phone
|
||||
// context drawer.
|
||||
private readonly contextDrawer = inject(WorkspaceShellContextDrawerService, {
|
||||
optional: true,
|
||||
});
|
||||
private readonly contextDrawer = inject(
|
||||
WorkspaceShellContextDrawerService,
|
||||
{
|
||||
optional: true,
|
||||
}
|
||||
);
|
||||
private readonly onDocumentKeydown = (event: KeyboardEvent): void =>
|
||||
this.handleKeydown(event);
|
||||
|
||||
@@ -51,9 +55,7 @@ export class WorkspaceKeyboardShortcutsService {
|
||||
WorkspaceKeyboardShortcutsDialogComponent,
|
||||
WorkspaceKeyboardShortcutsDialogData
|
||||
>(WorkspaceKeyboardShortcutsDialogComponent, {
|
||||
width: 'min(960px, 94vw)',
|
||||
maxWidth: '94vw',
|
||||
panelClass: 'workspace-shortcuts-overlay',
|
||||
...dialogSize('lg'),
|
||||
autoFocus: false,
|
||||
data: {
|
||||
groups: getKeyboardShortcutGroups({
|
||||
@@ -185,5 +187,4 @@ export class WorkspaceKeyboardShortcutsService {
|
||||
|
||||
return /Mac|iPhone|iPad|iPod/i.test(platform) ? 'mac' : 'other';
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user