mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
style(ui): ADR and one global kit layer for menus, select panels and destructive buttons (#1936)
* docs(ui): record the Angular Material role decision in the UI guidelines Angular Material stays the behaviour and accessibility layer; the look comes from the design kit through mat.*-overrides() on --app-* tokens in the shared partials, kit-only components build on CDK, and plain actions may become native buttons. Records the measured Material and CDK share of the initial bytes, why a replacement would not pay, and the v22 behaviour changes to keep covered in E2E. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * style(ui): one global kit layer for menus, select panels and destructive buttons - `<mat-menu class="app-menu">` gets the kit panel from one rule in styles.scss; the two lazy components drop their `::ng-deep` copies. - Select option panels follow the same panel: bordered, 10px radius, 6px padding, 36px rows at 13px, the chosen row on the list-selection tokens. - `.app-destructive-button` is the kit's red tint on the `--app-danger-*` tokens (tinted fill, red text, no ripple, 40% opacity when disabled) instead of a solid error fill, for filled, text, outlined and icon buttons. - The backdrop blur is scoped to the dark backdrop the CDK gives modal overlays; menus, selects and popovers open over a transparent backdrop that no longer matches the rule. - `mat.elevation-classes()` is dropped: nothing uses `.mat-elevation-z*`, and its 25 rules cost 4.4 KB of the initial stylesheet. The stack now measures 1,595,843 initial bytes (was 1,597,764). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(e2e): wait for focus to enter an opened menu before navigating it Material focuses a menu's first item after the next render, which the zoneless scheduler runs on a later task than the keydown that opened it. The panel is visible before that, so End sent as soon as it appeared still reached the trigger: on the Linux runner the Sources row-menu delete test then found "Remove playlist" inactive on every retry. `waitForMenuFocus` polls for the active element inside the open menu; the Sources keyboard and switcher keyboard tests use it before their first navigation key. 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
88765c7bf0
commit
3b2af73baa
10 files changed
+253
-59
No files matched your search
+88
-22
@@ -3,7 +3,8 @@
|
||||
@use '@angular/material' as mat;
|
||||
@use 'styles';
|
||||
|
||||
@include mat.elevation-classes();
|
||||
// No `mat.elevation-classes()`: nothing in the app uses the `.mat-elevation-z*`
|
||||
// utilities, and the 25 rules cost 4.4 KB of the initial stylesheet.
|
||||
@include mat.app-background();
|
||||
|
||||
// ─── Typography ─────────────────────────────────────────────────────────────
|
||||
@@ -407,39 +408,104 @@ html {
|
||||
font-size: 13.5px;
|
||||
}
|
||||
|
||||
// A select stays inside a form; its option panel follows the kit menu:
|
||||
// a bordered 10px panel with 6px padding, 36px rows at 13px and the
|
||||
// chosen row on the list-selection tokens. The overrides sit on the
|
||||
// panel, which renders in the overlay container inside the theme
|
||||
// context, so the `--app-*` values resolve per theme. The selector is
|
||||
// doubled because Material sets the panel's padding and attached
|
||||
// corners from `div.mat-mdc-select-panel` and
|
||||
// `.mat-mdc-select-panel-above div.mat-mdc-select-panel`, injected after
|
||||
// the global stylesheet.
|
||||
div.mat-mdc-select-panel.mat-mdc-select-panel {
|
||||
@include mat.select-overrides(
|
||||
(
|
||||
panel-background-color: var(--app-widget-bg),
|
||||
container-elevation-shadow: 0 18px 40px rgba(0, 0, 0, 0.35),
|
||||
)
|
||||
);
|
||||
@include mat.option-overrides(
|
||||
(
|
||||
label-text-size: 13px,
|
||||
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),
|
||||
)
|
||||
);
|
||||
padding: 6px;
|
||||
border: 1px solid var(--app-line-strong);
|
||||
border-radius: 10px;
|
||||
|
||||
.mat-mdc-option {
|
||||
min-height: 36px;
|
||||
padding: 0 10px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 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
|
||||
// discard user data opt in with this class instead; the error tokens are
|
||||
// declared per theme context above, so light and dark each get their own
|
||||
// error/on-error pair.
|
||||
// discard user data opt in with this class instead. The kit draws them as
|
||||
// a red tint (`--app-danger-*`, declared per theme context above), never
|
||||
// a solid error fill: hover changes the fill only, the ripple is off and
|
||||
// a disabled button is the same button at 40% opacity.
|
||||
.app-destructive-button {
|
||||
@include mat.button-overrides(
|
||||
(
|
||||
filled-container-color: var(--mat-sys-error),
|
||||
filled-label-text-color: var(--mat-sys-on-error),
|
||||
filled-state-layer-color: var(--mat-sys-on-error),
|
||||
filled-ripple-color:
|
||||
color-mix(in srgb, var(--mat-sys-on-error) 12%, transparent),
|
||||
text-label-text-color: var(--mat-sys-error),
|
||||
text-state-layer-color: var(--mat-sys-error),
|
||||
text-ripple-color:
|
||||
color-mix(in srgb, var(--mat-sys-error) 12%, transparent),
|
||||
outlined-label-text-color: var(--mat-sys-error),
|
||||
outlined-outline-color: var(--mat-sys-error),
|
||||
outlined-state-layer-color: var(--mat-sys-error),
|
||||
outlined-ripple-color:
|
||||
color-mix(in srgb, var(--mat-sys-error) 12%, transparent),
|
||||
filled-container-color: var(--app-danger-surface),
|
||||
filled-disabled-container-color: var(--app-danger-surface),
|
||||
filled-label-text-color: var(--app-danger-text),
|
||||
filled-disabled-label-text-color: var(--app-danger-text),
|
||||
filled-state-layer-color: var(--app-danger-text),
|
||||
filled-hover-state-layer-opacity: 0.08,
|
||||
filled-focus-state-layer-opacity: 0.08,
|
||||
filled-pressed-state-layer-opacity: 0.12,
|
||||
filled-ripple-color: transparent,
|
||||
text-label-text-color: var(--app-danger-text),
|
||||
text-disabled-label-text-color: var(--app-danger-text),
|
||||
text-state-layer-color: var(--app-danger-text),
|
||||
text-hover-state-layer-opacity: 0.08,
|
||||
text-focus-state-layer-opacity: 0.08,
|
||||
text-pressed-state-layer-opacity: 0.12,
|
||||
text-ripple-color: transparent,
|
||||
outlined-label-text-color: var(--app-danger-text),
|
||||
outlined-disabled-label-text-color: var(--app-danger-text),
|
||||
outlined-outline-color: transparent,
|
||||
outlined-disabled-outline-color: transparent,
|
||||
outlined-state-layer-color: var(--app-danger-text),
|
||||
outlined-hover-state-layer-opacity: 0.08,
|
||||
outlined-focus-state-layer-opacity: 0.08,
|
||||
outlined-pressed-state-layer-opacity: 0.12,
|
||||
outlined-ripple-color: transparent,
|
||||
)
|
||||
);
|
||||
@include mat.icon-button-overrides(
|
||||
(
|
||||
icon-color: var(--mat-sys-error),
|
||||
state-layer-color: var(--mat-sys-error),
|
||||
ripple-color:
|
||||
color-mix(in srgb, var(--mat-sys-error) 12%, transparent),
|
||||
icon-color: var(--app-danger-text),
|
||||
disabled-icon-color: var(--app-danger-text),
|
||||
state-layer-color: var(--app-danger-text),
|
||||
hover-state-layer-opacity: 0.08,
|
||||
focus-state-layer-opacity: 0.08,
|
||||
pressed-state-layer-opacity: 0.12,
|
||||
ripple-color: transparent,
|
||||
)
|
||||
);
|
||||
|
||||
// The kit has no outlined buttons: an outlined destructive button
|
||||
// carries the same tint as a filled one.
|
||||
&.mat-mdc-outlined-button {
|
||||
background-color: var(--app-danger-surface);
|
||||
}
|
||||
|
||||
&[disabled],
|
||||
&.mat-mdc-button-disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-form-field-subscript-wrapper {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@use '@angular/material' as mat;
|
||||
@use '../../../libs/ui/components/src/lib/resizable/resizable.scss';
|
||||
@use '../../../libs/ui/styles/focus-ring';
|
||||
@use '../../../libs/ui/styles/app-controls' as controls;
|
||||
@use './settings-theme';
|
||||
@use './cover-size';
|
||||
|
||||
@@ -238,10 +239,20 @@ body.dark-theme {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.cdk-overlay-backdrop {
|
||||
// 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
|
||||
// `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);
|
||||
}
|
||||
|
||||
// UI-kit menu panel: `<mat-menu class="app-menu">`. The panel renders in the
|
||||
// overlay container, outside every component view, so the rule is global.
|
||||
.mat-mdc-menu-panel.app-menu {
|
||||
@include controls.menu-panel;
|
||||
}
|
||||
|
||||
// Command palette dialog — the palette-shell renders its own background, border,
|
||||
// radius and shadow, so strip MDC's dialog surface chrome to avoid a darker
|
||||
// halo peeking out behind the rounded corners.
|
||||
|
||||
Reference in new issue
Block a user