mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -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
@@ -3,6 +3,7 @@ import type { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { PORTAL_SHELL_ACTIONS } from '@iptvnator/portal/shared/util/tokens';
|
||||
import { LazyMatDialog } from '@iptvnator/ui/components/lazy-dialog';
|
||||
import { dialogSize } from '@iptvnator/ui/components/dialog-size';
|
||||
import {
|
||||
WORKSPACE_SHELL_ACTIONS,
|
||||
WorkspaceAccountInfoData,
|
||||
@@ -26,13 +27,9 @@ export class AppWorkspaceShellActionsService implements WorkspaceShellActions {
|
||||
import('@iptvnator/playlist/import/feature'),
|
||||
(dialog, { AddPlaylistDialogComponent }) => {
|
||||
dialog.open(AddPlaylistDialogComponent, {
|
||||
// Width sized for the 5-card method picker plus the
|
||||
// selected method's form below. Matches the v0.22
|
||||
// mockup; falls back to viewport-clamped width on
|
||||
// narrow screens so the grid can collapse via the
|
||||
// SCSS responsive breakpoints.
|
||||
width: '780px',
|
||||
maxWidth: '92vw',
|
||||
// The method-card grid collapses through its own
|
||||
// breakpoints inside the large preset.
|
||||
...dialogSize('lg'),
|
||||
data: type ? { type } : {},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "مسح",
|
||||
"CLEAR_SEARCH": "مسح البحث",
|
||||
"CLOSE": "إغلاق",
|
||||
"CLOSE_DIALOG": "إغلاق مربع الحوار",
|
||||
"REMOVE": "إزالة",
|
||||
"BACK": "رجوع",
|
||||
"SHOW_MORE": "المزيد",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "مسح",
|
||||
"CLEAR_SEARCH": "مسح البحث",
|
||||
"CLOSE": "سد",
|
||||
"CLOSE_DIALOG": "سدّ مربع الحوار",
|
||||
"REMOVE": "حيد",
|
||||
"BACK": "رجع",
|
||||
"SHOW_MORE": "وري كثر",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Ачысціць",
|
||||
"CLEAR_SEARCH": "Ачысціць пошук",
|
||||
"CLOSE": "Закрыць",
|
||||
"CLOSE_DIALOG": "Закрыць дыялог",
|
||||
"REMOVE": "Выдаліць",
|
||||
"BACK": "Назад",
|
||||
"SHOW_MORE": "Болей",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Leeren",
|
||||
"CLEAR_SEARCH": "Suche löschen",
|
||||
"CLOSE": "Schließen",
|
||||
"CLOSE_DIALOG": "Dialog schließen",
|
||||
"REMOVE": "Entfernen",
|
||||
"BACK": "Zurück",
|
||||
"SHOW_MORE": "Mehr",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Εκκαθάριση",
|
||||
"CLEAR_SEARCH": "Καθαρισμός αναζήτησης",
|
||||
"CLOSE": "Κλείσιμο",
|
||||
"CLOSE_DIALOG": "Κλείσιμο παραθύρου διαλόγου",
|
||||
"REMOVE": "Αφαίρεση",
|
||||
"BACK": "Πίσω",
|
||||
"SHOW_MORE": "Περισσότερα",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Clear",
|
||||
"CLEAR_SEARCH": "Clear search",
|
||||
"CLOSE": "Close",
|
||||
"CLOSE_DIALOG": "Close dialog",
|
||||
"REMOVE": "Remove",
|
||||
"BACK": "Back",
|
||||
"SHOW_MORE": "More",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Borrar",
|
||||
"CLEAR_SEARCH": "Borrar búsqueda",
|
||||
"CLOSE": "Cerrar",
|
||||
"CLOSE_DIALOG": "Cerrar el diálogo",
|
||||
"REMOVE": "Quitar",
|
||||
"BACK": "Atrás",
|
||||
"SHOW_MORE": "Más",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Effacer",
|
||||
"CLEAR_SEARCH": "Effacer la recherche",
|
||||
"CLOSE": "Fermer",
|
||||
"CLOSE_DIALOG": "Fermer la boîte de dialogue",
|
||||
"REMOVE": "Retirer",
|
||||
"BACK": "Retour",
|
||||
"SHOW_MORE": "Plus",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Törlés",
|
||||
"CLEAR_SEARCH": "Keresés törlése",
|
||||
"CLOSE": "Bezárás",
|
||||
"CLOSE_DIALOG": "Párbeszédablak bezárása",
|
||||
"REMOVE": "Eltávolítás",
|
||||
"BACK": "Vissza",
|
||||
"SHOW_MORE": "Több mutatása",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Cancella",
|
||||
"CLEAR_SEARCH": "Cancella ricerca",
|
||||
"CLOSE": "Chiudi",
|
||||
"CLOSE_DIALOG": "Chiudi la finestra di dialogo",
|
||||
"REMOVE": "Rimuovi",
|
||||
"BACK": "Indietro",
|
||||
"SHOW_MORE": "Altro",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "クリア",
|
||||
"CLEAR_SEARCH": "検索をクリア",
|
||||
"CLOSE": "閉じる",
|
||||
"CLOSE_DIALOG": "ダイアログを閉じる",
|
||||
"REMOVE": "削除",
|
||||
"BACK": "戻る",
|
||||
"SHOW_MORE": "もっと見る",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "지우기",
|
||||
"CLEAR_SEARCH": "검색 지우기",
|
||||
"CLOSE": "닫기",
|
||||
"CLOSE_DIALOG": "대화 상자 닫기",
|
||||
"REMOVE": "제거",
|
||||
"BACK": "뒤로",
|
||||
"SHOW_MORE": "더 보기",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Wissen",
|
||||
"CLEAR_SEARCH": "Zoekopdracht wissen",
|
||||
"CLOSE": "Sluiten",
|
||||
"CLOSE_DIALOG": "Dialoogvenster sluiten",
|
||||
"REMOVE": "Verwijderen",
|
||||
"BACK": "Terug",
|
||||
"SHOW_MORE": "Meer",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Wyczyść",
|
||||
"CLEAR_SEARCH": "Wyczyść wyszukiwanie",
|
||||
"CLOSE": "Zamknij",
|
||||
"CLOSE_DIALOG": "Zamknij okno dialogowe",
|
||||
"REMOVE": "Usuń",
|
||||
"BACK": "Wstecz",
|
||||
"SHOW_MORE": "Więcej",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Limpar",
|
||||
"CLEAR_SEARCH": "Limpar pesquisa",
|
||||
"CLOSE": "Fechar",
|
||||
"CLOSE_DIALOG": "Fechar a caixa de diálogo",
|
||||
"REMOVE": "Remover",
|
||||
"BACK": "Voltar",
|
||||
"SHOW_MORE": "Mais",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Очистить",
|
||||
"CLEAR_SEARCH": "Очистить поиск",
|
||||
"CLOSE": "Закрыть",
|
||||
"CLOSE_DIALOG": "Закрыть диалог",
|
||||
"REMOVE": "Удалить",
|
||||
"BACK": "Назад",
|
||||
"SHOW_MORE": "Больше",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "Temizle",
|
||||
"CLEAR_SEARCH": "Aramayı temizle",
|
||||
"CLOSE": "Kapat",
|
||||
"CLOSE_DIALOG": "İletişim kutusunu kapat",
|
||||
"REMOVE": "Kaldır",
|
||||
"BACK": "Geri",
|
||||
"SHOW_MORE": "Daha fazla",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "清除",
|
||||
"CLEAR_SEARCH": "清除搜索",
|
||||
"CLOSE": "关闭",
|
||||
"CLOSE_DIALOG": "关闭对话框",
|
||||
"REMOVE": "移除",
|
||||
"BACK": "返回",
|
||||
"SHOW_MORE": "更多",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"CLEAR": "清除",
|
||||
"CLEAR_SEARCH": "清除搜尋",
|
||||
"CLOSE": "關閉",
|
||||
"CLOSE_DIALOG": "關閉對話方塊",
|
||||
"REMOVE": "移除",
|
||||
"BACK": "返回",
|
||||
"SHOW_MORE": "更多",
|
||||
|
||||
+50
-15
@@ -11,8 +11,7 @@
|
||||
// The stack is declared once on html; every Material typography token and the
|
||||
// typography hierarchy read it through the variable. Inlined, the 103-char
|
||||
// stack was copied into ~90 declarations (about 7 KB of the initial CSS).
|
||||
$app-font-stack-value:
|
||||
"'DM Sans', 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
|
||||
$app-font-stack-value: "'DM Sans', 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif";
|
||||
$app-font-stack: var(--app-font-family);
|
||||
|
||||
html {
|
||||
@@ -179,7 +178,8 @@ html {
|
||||
--app-widget-bg: #ffffff;
|
||||
--app-widget-header-bg: #fafbfc;
|
||||
--app-widget-border: transparent;
|
||||
--app-widget-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
--app-widget-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||
--app-widget-header-border: rgba(0, 0, 0, 0.06);
|
||||
--app-card-hover-bg: #f0f2f5;
|
||||
--app-separator: rgba(0, 0, 0, 0.14);
|
||||
@@ -224,7 +224,8 @@ html {
|
||||
|
||||
// Source lists (Sources page, playlist switcher) and their popovers.
|
||||
--app-line-strong: rgba(0, 0, 0, 0.12);
|
||||
--app-font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
--app-font-mono:
|
||||
'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
// A failing source's tag and destructive menu items.
|
||||
--app-danger-text: #b3261e;
|
||||
--app-danger-surface: color-mix(in srgb, #d12c2c 12%, transparent);
|
||||
@@ -309,10 +310,10 @@ html {
|
||||
--app-separator: rgba(255, 255, 255, 0.14);
|
||||
|
||||
// Typography — NOT pure white. Warm graphite-tinted whites
|
||||
--app-heading-color: #d8dce8; // soft off-white, not #fff
|
||||
--app-body-color: #8b93a8; // muted blue-grey
|
||||
--app-muted-color: #5c6478; // dimmed
|
||||
--app-eyebrow-color: #6b7494; // eyebrow labels
|
||||
--app-heading-color: #d8dce8; // soft off-white, not #fff
|
||||
--app-body-color: #8b93a8; // muted blue-grey
|
||||
--app-muted-color: #5c6478; // dimmed
|
||||
--app-eyebrow-color: #6b7494; // eyebrow labels
|
||||
|
||||
// Provider accents (slightly brighter for dark bg)
|
||||
--app-provider-xtream: #78adff;
|
||||
@@ -365,9 +366,13 @@ html {
|
||||
// cascade. Pairs with the outline + dynamic MAT_FORM_FIELD_DEFAULT_OPTIONS
|
||||
// that provideFormFieldDefaults() (@iptvnator/ui/components) sets on each
|
||||
// component rendering a form field.
|
||||
$app-refined-input-density: mat.define-theme((
|
||||
density: (scale: -2),
|
||||
));
|
||||
$app-refined-input-density: mat.define-theme(
|
||||
(
|
||||
density: (
|
||||
scale: -2,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
html {
|
||||
@include mat.form-field-density($app-refined-input-density);
|
||||
@@ -430,10 +435,16 @@ html {
|
||||
label-text-color: var(--app-heading-color),
|
||||
selected-state-label-text-color: var(--app-list-selected-fg),
|
||||
selected-state-layer-color: var(--app-list-selected-bg),
|
||||
hover-state-layer-color:
|
||||
color-mix(in srgb, var(--app-on-surface) 8%, transparent),
|
||||
focus-state-layer-color:
|
||||
color-mix(in srgb, var(--app-on-surface) 10%, transparent),
|
||||
hover-state-layer-color: color-mix(
|
||||
in srgb,
|
||||
var(--app-on-surface) 8%,
|
||||
transparent
|
||||
),
|
||||
focus-state-layer-color: color-mix(
|
||||
in srgb,
|
||||
var(--app-on-surface) 10%,
|
||||
transparent
|
||||
),
|
||||
)
|
||||
);
|
||||
padding: 6px;
|
||||
@@ -447,6 +458,27 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
// Every dialog shares one frame: the kit's 16px panel on the widget
|
||||
// surface, a hairline border and the popover shadow. The tokens sit on
|
||||
// the container, inside the overlay, so the `--app-*` values resolve
|
||||
// per theme. A dialog that draws its own surface (the command palette,
|
||||
// the programme dialog) strips this frame through its panel class.
|
||||
.mat-mdc-dialog-container {
|
||||
@include mat.dialog-overrides(
|
||||
(
|
||||
container-shape: 16px,
|
||||
container-color: var(--app-widget-bg),
|
||||
container-elevation-shadow: var(--app-popover-shadow),
|
||||
subhead-color: var(--app-heading-color),
|
||||
supporting-text-color: var(--app-body-color),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
.mat-mdc-dialog-surface {
|
||||
border: 1px solid var(--app-line-strong);
|
||||
}
|
||||
|
||||
// ─── Destructive actions ───────────────────────────────────────────────
|
||||
// Material only emits `.mat-warn` button colors for M2 themes, so the
|
||||
// `warn` color input is a no-op with this M3 theme. Buttons that remove or
|
||||
@@ -457,6 +489,9 @@ html {
|
||||
.app-destructive-button {
|
||||
@include mat.button-overrides(
|
||||
(
|
||||
filled-container-shape: 10px,
|
||||
text-container-shape: 10px,
|
||||
outlined-container-shape: 10px,
|
||||
filled-container-color: var(--app-danger-surface),
|
||||
filled-disabled-container-color: var(--app-danger-surface),
|
||||
filled-label-text-color: var(--app-danger-text),
|
||||
|
||||
+11
-17
@@ -239,12 +239,19 @@ body.dark-theme {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
// Modal overlays blur the page behind their backdrop. The CDK gives a dialog
|
||||
// the dark backdrop class by default; menus, selects and popovers open over
|
||||
// Modal overlays tint and blur the page behind their backdrop, on the same
|
||||
// token as the switcher popover. The CDK gives a dialog the dark backdrop
|
||||
// class by default; menus, selects and popovers open over
|
||||
// `cdk-overlay-transparent-backdrop`, which sits at opacity 0 and stays a
|
||||
// plain layer without a filter of its own.
|
||||
.cdk-overlay-dark-backdrop {
|
||||
backdrop-filter: blur(4px);
|
||||
background: var(--app-popover-backdrop);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
// Dialog surfaces may overlap the frameless title bar's drag region.
|
||||
.mat-mdc-dialog-surface {
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
// UI-kit menu panel: `<mat-menu class="app-menu">`. The panel renders in the
|
||||
@@ -258,20 +265,7 @@ body.dark-theme {
|
||||
// halo peeking out behind the rounded corners.
|
||||
.workspace-command-palette-overlay .mat-mdc-dialog-surface {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border-radius: 14px;
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.workspace-shortcuts-overlay,
|
||||
.workspace-shortcuts-overlay .mat-mdc-dialog-surface,
|
||||
.workspace-shortcuts-overlay .mat-mdc-dialog-container {
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.workspace-shortcuts-overlay .mat-mdc-dialog-surface {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 14px;
|
||||
padding: 0;
|
||||
|
||||
Reference in new issue
Block a user